ENHANCEMENT Allowing custom attributes in (most) FormField implementations, which allows for HTML5 data attributes

This commit is contained in:
Ingo Schommer 2011-12-22 13:10:57 +01:00
parent c77f4e8421
commit b5421d9598
45 changed files with 407 additions and 137 deletions

View File

@ -63,6 +63,27 @@ not when simply using the CMS or developing other CMS functionality.
If you want to extend the CMS stylesheets for your own projects without SCSS, If you want to extend the CMS stylesheets for your own projects without SCSS,
please create a new CSS file and link it into the CMS via `[api:LeftAndMain::require_css()]`. please create a new CSS file and link it into the CMS via `[api:LeftAndMain::require_css()]`.
### FormField consistently adds classes to HTML elements ###
The [api:FormField] API has been refactored to use SilverStripe templates
for constructing the field HTML, as well as new accessors for HTML attributes.
This change makes the HTML a bit more predictable, but it also means that
you need to check any code (CSS, JavaScript, etc) relying on the old inconsistencies.
Particularly, CSS class names applied through [api:FormField->addExtraClass()]
and the "type" class are now consistently added to the container `<div>`
as well as the HTML form element itself.
:::html
Before (abbreviated):
<div class="field checkbox extraClass"...>
<input type="checkbox".../>
</div>
After (abbreviated):
<div class="field checkbox extraClass"...>
<input type="checkbox" class="checkbox extraClass".../>
</div>
### Restructured files and folders ### ### Restructured files and folders ###
In order to make the `sapphire` framework useable without the `cms` module, In order to make the `sapphire` framework useable without the `cms` module,

View File

@ -22,8 +22,6 @@ class CheckboxField extends FormField {
return ($this->value) ? 1 : 0; return ($this->value) ? 1 : 0;
} }
}
/** /**
* Returns a restricted field holder used within things like FieldGroups * Returns a restricted field holder used within things like FieldGroups
*/ */
@ -35,6 +33,18 @@ class CheckboxField extends FormField {
return $result; return $result;
} }
function getAttributes() {
$attrs = parent::getAttributes();
$attrs['value'] = 1;
return array_merge(
$attrs,
array(
'checked' => ($this->Value()) ? 'checked' : null,
'type' => 'checkbox',
)
);
}
/** /**
* Returns a readonly version of this field * Returns a readonly version of this field
*/ */

View File

@ -98,12 +98,9 @@ class CheckboxSetField extends OptionsetField {
} }
$odd = 0; $odd = 0;
$options = ''; $options = array();
if ($source == null) { if ($source == null) $source = array();
$source = array();
$options = "<li>No options available</li>";
}
if($source) { if($source) {
foreach($source as $value => $item) { foreach($source as $value => $item) {
@ -122,7 +119,7 @@ class CheckboxSetField extends OptionsetField {
$options[] = new ArrayData(array( $options[] = new ArrayData(array(
'ID' => $itemID, 'ID' => $itemID,
'Class' => $extraClass, 'Class' => $extraClass,
'Name' => $this->name, 'Name' => "{$this->name}[{$value}]",
'Value' => $value, 'Value' => $value,
'Title' => $title, 'Title' => $title,
'isChecked' => in_array($value, $items) || in_array($value, $this->defaultItems), 'isChecked' => in_array($value, $items) || in_array($value, $this->defaultItems),
@ -283,6 +280,10 @@ class CheckboxSetField extends OptionsetField {
return $field; return $field;
} }
function Type() {
return 'optionset checkboxset';
}
function ExtraOptions() { function ExtraOptions() {
return FormField::ExtraOptions(); return FormField::ExtraOptions();

View File

@ -83,15 +83,25 @@ class CompositeField extends FormField {
$this->children = $children; $this->children = $children;
} }
function extraClasses() {
$classes = array('field', 'CompositeField', parent::extraClasses());
if($this->columnCount) $classes[] = 'multicolumn';
return implode(' ', $classes);
}
function getAttributes() {
return array_merge(
parent::getAttributes(),
array('tabindex' => null, 'type' => null, 'value' => null, 'type' => null)
);
}
/** /**
* Returns the fields nested inside another DIV * Returns the fields nested inside another DIV
*/ */
function FieldHolder() { function FieldHolder() {
$content = '';
$fs = $this->FieldList(); $fs = $this->FieldList();
$idAtt = isset($this->id) ? " id=\"{$this->id}\"" : '';
$className = ($this->columnCount) ? "field CompositeField {$this->extraClass()} multicolumn" : "field CompositeField {$this->extraClass()}";
$content = "<div class=\"$className\"$idAtt>\n";
foreach($fs as $subfield) { foreach($fs as $subfield) {
if($this->columnCount) { if($this->columnCount) {
$className = "column{$this->columnCount}"; $className = "column{$this->columnCount}";
@ -101,9 +111,8 @@ class CompositeField extends FormField {
$content .= "\n" . $subfield->FieldHolder() . "\n"; $content .= "\n" . $subfield->FieldHolder() . "\n";
} }
} }
$content .= "</div>\n";
return $content; $this->createTag('div', $this->getAttributes(), $content);
} }
/** /**

View File

@ -22,14 +22,14 @@ class CountryDropdownField extends DropdownField {
function defaultToVisitorCountry($val) { function defaultToVisitorCountry($val) {
$this->defaultToVisitorCountry = $val; $this->defaultToVisitorCountry = $val;
} }
function Field() { function Value() {
$source = $this->getSource(); $source = $this->getSource();
if($this->defaultToVisitorCountry && !$this->value || !isset($source[$this->value])) { if($this->defaultToVisitorCountry && !$this->value || !isset($source[$this->value])) {
$this->value = ($vc = Geoip::visitor_country()) ? $vc : Geoip::get_default_country_code(); return ($vc = Geoip::visitor_country()) ? $vc : Geoip::get_default_country_code();
} else {
return $this->value;
} }
return parent::Field();
} }
} }

View File

@ -9,6 +9,8 @@ class CreditCardField extends TextField {
function Field() { function Field() {
$parts = explode("\n", chunk_split($this->value,4,"\n")); $parts = explode("\n", chunk_split($this->value,4,"\n"));
$parts = array_pad($parts, 4, ""); $parts = array_pad($parts, 4, "");
// TODO Mark as disabled/readonly
$field = "<span id=\"{$this->name}_Holder\" class=\"creditCardField\">" . $field = "<span id=\"{$this->name}_Holder\" class=\"creditCardField\">" .
"<input autocomplete=\"off\" name=\"{$this->name}[0]\" value=\"$parts[0]\" maxlength=\"4\"" . $this->getTabIndexHTML(0) . " /> - " . "<input autocomplete=\"off\" name=\"{$this->name}[0]\" value=\"$parts[0]\" maxlength=\"4\"" . $this->getTabIndexHTML(0) . " /> - " .
"<input autocomplete=\"off\" name=\"{$this->name}[1]\" value=\"$parts[1]\" maxlength=\"4\"" . $this->getTabIndexHTML(1) . " /> - " . "<input autocomplete=\"off\" name=\"{$this->name}[1]\" value=\"$parts[1]\" maxlength=\"4\"" . $this->getTabIndexHTML(1) . " /> - " .

View File

@ -27,6 +27,11 @@ class CurrencyField extends TextField {
return 0.00; return 0.00;
} }
} }
function Type() {
return 'currency text';
}
/** /**
* Create a new class for this field * Create a new class for this field
*/ */

View File

@ -18,6 +18,15 @@ class DatalessField extends FormField {
* Always returns false. * Always returns false.
*/ */
function hasData() { return false; } function hasData() { return false; }
function getAttributes() {
return array_merge(
parent::getAttributes(),
array(
'type' => 'hidden',
)
);
}
/** /**
* Returns the field's representation in the form. * Returns the field's representation in the form.
@ -58,4 +67,8 @@ class DatalessField extends FormField {
return $this->allowHTML; return $this->allowHTML;
} }
function Type() {
return 'readonly';
}
} }

View File

@ -174,6 +174,10 @@ class DateField extends TextField {
return $html; return $html;
} }
function Type() {
return 'date text';
}
/** /**
* Sets the internal value to ISO date format. * Sets the internal value to ISO date format.

View File

@ -162,6 +162,13 @@ class DropdownField extends FormField {
return $this->customise($properties)->renderWith($this->getTemplate()); return $this->customise($properties)->renderWith($this->getTemplate());
} }
function getAttributes() {
return array_merge(
parent::getAttributes(),
array('type' => null)
);
}
/** /**
* @return boolean * @return boolean
*/ */
@ -230,12 +237,6 @@ class DropdownField extends FormField {
return $field; return $field;
} }
function extraClass() {
$ret = parent::extraClass();
if($this->extraClass) $ret .= " $this->extraClass";
return $ret;
}
/** /**
* Set form being disabled * Set form being disabled
*/ */

View File

@ -6,6 +6,10 @@
*/ */
class EmailField extends TextField { class EmailField extends TextField {
function Type() {
return 'email text';
}
function jsValidation() { function jsValidation() {
$formID = $this->form->FormName(); $formID = $this->form->FormName();
$error = _t('EmailField.VALIDATIONJS', 'Please enter an email address.'); $error = _t('EmailField.VALIDATIONJS', 'Please enter an email address.');
@ -40,14 +44,6 @@ if(typeof fromAnOnBlur != 'undefined'){
JS; JS;
} }
/**
* Returns the field type - used by templates.
* @return string
*/
function Type() {
return 'text';
}
/** /**
* Validates for RFC 2822 compliant email adresses. * Validates for RFC 2822 compliant email adresses.
* *

View File

@ -117,6 +117,13 @@ class FileField extends FormField {
return $this->customise($properties)->renderWith($this->getTemplate()); return $this->customise($properties)->renderWith($this->getTemplate());
} }
function getAttributes() {
return array_merge(
parent::getAttributes(),
array('type' => 'file')
);
}
public function saveInto(DataObject $record) { public function saveInto(DataObject $record) {
if(!isset($_FILES[$this->name])) return false; if(!isset($_FILES[$this->name])) return false;
$fileClass = File::get_class_for_file_extension(pathinfo($_FILES[$this->name]['name'], PATHINFO_EXTENSION)); $fileClass = File::get_class_for_file_extension(pathinfo($_FILES[$this->name]['name'], PATHINFO_EXTENSION));

View File

@ -91,7 +91,22 @@ class FormAction extends FormField {
} }
public function Type() { public function Type() {
return ($this->useButtonTag) ? 'button' : 'submit'; return 'action';
}
function getAttributes() {
return array_merge(
parent::getAttributes(),
array(
'disabled' => ($this->isReadonly() || $this->isDisabled()),
'value' => $this->Title(),
'type' => ($this->useButtonTag) ? null : 'submit'
)
);
}
function extraClass() {
return 'action ' . parent::extraClass();
} }
/** /**

View File

@ -89,6 +89,12 @@ class FormField extends RequestHandler {
*/ */
protected $fieldHolderTemplate = 'FieldHolder'; protected $fieldHolderTemplate = 'FieldHolder';
/**
* @var array All attributes on the form field (not the field holder).
* Partially determined based on other instance properties, please use {@link getAttributes()}.
*/
protected $attributes = array();
/** /**
* Create a new field. * Create a new field.
* @param name The internal field name, passed to forms. * @param name The internal field name, passed to forms.
@ -253,21 +259,22 @@ class FormField extends RequestHandler {
* @return String CSS-classnames * @return String CSS-classnames
*/ */
function extraClass() { function extraClass() {
$output = ""; $classes = array();
if(is_array($this->extraClasses)) {
$output = " " . implode($this->extraClasses, " "); $classes[] = $this->Type();
}
if($this->extraClasses) $classes = array_merge($classes, array_values($this->extraClasses));
// Allow customization of label and field tag positioning // Allow customization of label and field tag positioning
if(!$this->Title()) $output .= " nolabel"; if(!$this->Title()) $classes[] = "nolabel";
// Allow custom styling of any element in the container based // Allow custom styling of any element in the container based
// on validation errors, e.g. red borders on input tags. // on validation errors, e.g. red borders on input tags.
// CSS-Class needs to be different from the one rendered // CSS-Class needs to be different from the one rendered
// through {@link FieldHolder()} // through {@link FieldHolder()}
if($this->Message()) $output .= " holder-" . $this->MessageType(); if($this->Message()) $classes[] .= "holder-" . $this->MessageType();
return $output; return implode(' ', $classes);
} }
/** /**
@ -288,6 +295,73 @@ class FormField extends RequestHandler {
if(isset($this->extraClasses) && array_key_exists($class, $this->extraClasses)) unset($this->extraClasses[$class]); if(isset($this->extraClasses) && array_key_exists($class, $this->extraClasses)) unset($this->extraClasses[$class]);
} }
/**
* Set an HTML attribute on the field element, mostly an <input> tag.
*
* CAUTION Doesn't work on most fields which are composed of more than one HTML form field:
* AjaxUniqueTextField, CheckboxSetField, ComplexTableField, CompositeField, ConfirmedPasswordField, CountryDropdownField,
* CreditCardField, CurrencyField, DateField, DatetimeField, FieldGroup, GridField, HtmlEditorField,
* ImageField, ImageFormAction, InlineFormAction, ListBoxField, etc.
*
* @param String
* @param String
*/
function setAttribute($name, $value) {
$this->attributes[$name] = $value;
}
/**
* Get an HTML attribute defined by the field, or added through {@link setAttribute()}.
* Caution: Doesn't work on all fields, see {@link setAttribute()}.
*
* @return String
*/
function getAttribute($name) {
$attrs = $this->getAttributes();
return @$attrs[$name];
}
/**
* @return array
*/
function getAttributes() {
$attrs = array(
'type' => 'text',
'name' => $this->getName(),
'value' => $this->Value(),
'class' => $this->extraClass(),
'id' => $this->ID(),
'tabindex' => $this->getTabIndex(),
'disabled' => $this->isDisabled(),
);
return array_merge($attrs, $this->attributes);
}
/**
* @param Array Custom attributes to process. Falls back to {@link getAttributes()}.
* If at least one argument is passed as a string, all arguments act as excludes by name.
* @return String HTML attributes, ready for insertion into an HTML tag
*/
function getAttributesHTML($attrs = null) {
$exclude = (is_string($attrs)) ? func_get_args() : null;
if(!$attrs || is_string($attrs)) $attrs = $this->getAttributes();
// Remove empty
$attrs = array_filter((array)$attrs, create_function('$v', 'return ($v || $v === 0);')); ;
// Remove excluded
if($exclude) $attrs = array_diff_key($attrs, array_flip($exclude));
// Create markkup
$parts = array();
foreach($attrs as $name => $value) {
$parts[] = ($value === true) ? "{$name}=\"{$name}\"" : "{$name}=\"" . Convert::raw2att($value) . "\"";
}
return implode(' ', $parts);
}
/** /**
* Returns a version of a title suitable for insertion into an HTML attribute * Returns a version of a title suitable for insertion into an HTML attribute
*/ */
@ -555,15 +629,13 @@ class FormField extends RequestHandler {
/** /**
* Returns the field type - used by templates. * Returns the field type - used by templates.
* The field type is the class name with the word Field dropped off the end, all lowercase. * The field type is the class name with the word Field dropped off the end, all lowercase.
* It's handy for assigning HTML classes. * It's handy for assigning HTML classes. Doesn't signify the <input type> attribute,
* see {link getAttributes()}.
*
* @return string * @return string
*/ */
function Type() { function Type() {
if(get_class($this) == 'FormField') { return strtolower(ereg_replace('Field$', '', $this->class));
return 'hidden';
} else {
return strtolower(ereg_replace('Field$', '', $this->class));
}
} }
/** /**

View File

@ -40,14 +40,7 @@
class GroupedDropdownField extends DropdownField { class GroupedDropdownField extends DropdownField {
function Field() { function Field() {
// Initialisations
$options = ''; $options = '';
$classAttr = '';
if($extraClass = trim($this->extraClass())) {
$classAttr = "class=\"$extraClass\"";
}
foreach($this->getSource() as $value => $title) { foreach($this->getSource() as $value => $title) {
if(is_array($title)) { if(is_array($title)) {
$options .= "<optgroup label=\"$value\">"; $options .= "<optgroup label=\"$value\">";
@ -62,9 +55,7 @@ class GroupedDropdownField extends DropdownField {
} }
} }
$id = $this->id(); return $this->createTag('select', $this->getAttributes(), $options);
return "<select $classAttr name=\"$this->name\" id=\"$id\">$options</select>";
} }
} }

View File

@ -36,4 +36,18 @@ class HeaderField extends DatalessField {
return $this->headingLevel; return $this->headingLevel;
} }
function getAttributes() {
return array_merge(
array(
'id' => $this->ID(),
'class' => $this->extraClass()
),
$this->attributes
);
}
function Type() {
return null;
}
} }

View File

@ -22,6 +22,13 @@ class HiddenField extends FormField {
return true; return true;
} }
function getAttributes() {
return array_merge(
parent::getAttributes(),
array('type' => 'hidden')
);
}
static function create($name) { static function create($name) {
return new HiddenField($name); return new HiddenField($name);
} }

View File

@ -22,8 +22,6 @@ class HtmlEditorField extends TextareaField {
public function __construct($name, $title = null, $rows = 30, $cols = 20, $value = '', $form = null) { public function __construct($name, $title = null, $rows = 30, $cols = 20, $value = '', $form = null) {
parent::__construct($name, $title, $rows, $cols, $value, $form); parent::__construct($name, $title, $rows, $cols, $value, $form);
$this->addExtraClass('htmleditor');
self::include_js(); self::include_js();
} }
@ -47,18 +45,21 @@ class HtmlEditorField extends TextareaField {
return $this->createTag ( return $this->createTag (
'textarea', 'textarea',
array ( $this->getAttributes(),
'class' => $this->extraClass(),
'rows' => $this->rows,
'cols' => $this->cols,
'style' => 'width: 97%; height: ' . ($this->rows * 16) . 'px', // prevents horizontal scrollbars
'tinymce' => 'true',
'id' => $this->id(),
'name' => $this->name
),
htmlentities($value->getContent(), ENT_COMPAT, 'UTF-8') htmlentities($value->getContent(), ENT_COMPAT, 'UTF-8')
); );
} }
function getAttributes() {
return array_merge(
parent::getAttributes(),
array(
'tinymce' => 'true',
'style' => 'width: 97%; height: ' . ($this->rows * 16) . 'px', // prevents horizontal scrollbars
'value' => null,
)
);
}
public function saveInto($record) { public function saveInto($record) {
if($record->escapeTypeForField($this->name) != 'xml') { if($record->escapeTypeForField($this->name) != 'xml') {

View File

@ -60,18 +60,12 @@ class ListboxField extends DropdownField {
/** /**
* Returns a <select> tag containing all the appropriate <option> tags * Returns a <select> tag containing all the appropriate <option> tags
*/ */
function Field() { function Field($properties = array()) {
$size = '';
$multiple = '';
if($this->size) $size = "size=\"$this->size\"";
if($this->multiple) { if($this->multiple) {
$multiple = "multiple=\"multiple\"";
$this->name .= '[]'; $this->name .= '[]';
} }
$options = ""; $options = array();
// We have an array of values // We have an array of values
if(is_array($this->value)){ if(is_array($this->value)){
@ -86,18 +80,36 @@ class ListboxField extends DropdownField {
break; break;
} }
} }
$options .= "<option$selected value=\"$value\">$title</option>\n"; $options[] = new ArrayData(array(
'Title' => $title,
'Value' => $value,
'Selected' => $selected,
));
} }
}else{ } else {
// Listbox was based a singlular value, so treat it like a dropdown. // Listbox was based a singlular value, so treat it like a dropdown.
foreach($this->getSource() as $value => $title) { foreach($this->getSource() as $value => $title) {
$selected = $value == $this->value ? " selected=\"selected\"" : ""; $selected = $value == $this->value ? " selected=\"selected\"" : "";
$options .= "<option$selected value=\"$value\">$title</option>"; $options[] = new ArrayData(array(
'Title' => $title,
'Value' => $value,
'Selected' => $selected,
));
} }
} }
$id = $this->id(); $properties = array_merge($properties, array('Options' => new ArrayList($options)));
return "<select $size $multiple name=\"$this->name\" id=\"$id\">$options</select>"; return $this->customise($properties)->renderWith($this->getTemplate());
}
function getAttributes() {
return array_merge(
parent::getAttributes(),
array(
'multiple' => $this->multiple,
'size' => $this->size
)
);
} }
/** /**

View File

@ -13,6 +13,10 @@ class NumericField extends TextField{
return $html; return $html;
} }
function Type() {
return 'numeric text';
}
function jsValidation() { function jsValidation() {
$formID = $this->form->FormName(); $formID = $this->form->FormName();

View File

@ -24,8 +24,11 @@ class PasswordField extends TextField {
} }
function Type() { function getAttributes() {
return 'password'; return array_merge(
parent::getAttributes(),
array('type' => 'password')
);
} }
/** /**
@ -39,6 +42,10 @@ class PasswordField extends TextField {
$field->setReadonly(true); $field->setReadonly(true);
return $field; return $field;
} }
function Type() {
return 'text password';
}
} }
?> ?>

View File

@ -14,4 +14,23 @@ class ReadonlyField extends FormField {
function performReadonlyTransformation() { function performReadonlyTransformation() {
return clone $this; return clone $this;
} }
function Value() {
if($this->value) return $this->dontEscape ? $this->value : Convert::raw2xml($this->value);
else return '<i>(' . _t('FormField.NONE', 'none') . ')</i>';
}
function getAttributes() {
return array_merge(
parent::getAttributes(),
array(
'type' => 'hidden',
'value' => null,
)
);
}
function Type() {
return 'readonly';
}
} }

View File

@ -7,8 +7,15 @@
*/ */
class ResetFormAction extends FormAction { class ResetFormAction extends FormAction {
public function Type() { function getAttributes() {
return 'reset'; return array_merge(
parent::getAttributes(),
array('type' => 'reset')
);
}
function Type() {
return 'resetformaction';
} }
} }

View File

@ -36,16 +36,14 @@ class TextField extends FormField {
return $this->maxLength; return $this->maxLength;
} }
function Field($properties = array()) { function getAttributes() {
$properties = array_merge( return array_merge(
$properties, parent::getAttributes(),
array( array(
'MaxLength' => ($this->getMaxLength()) ? $this->getMaxLength() : null, 'maxlength' => $this->getMaxLength(),
'Size' => ($this->getMaxLength()) ? min($this->getMaxLength(), 30) : null 'size' => ($this->getMaxLength()) ? min($this->getMaxLength(), 30) : null
) )
); );
return parent::Field($properties);
} }
function InternallyLabelledField() { function InternallyLabelledField() {

View File

@ -42,24 +42,16 @@ class TextareaField extends FormField {
parent::__construct($name, $title, $value, $form); parent::__construct($name, $title, $value, $form);
} }
/** function getAttributes() {
* Create the <textarea> or <span> HTML tag with the return array_merge(
* attributes for this instance of TextareaField. This parent::getAttributes(),
* makes use of {@link FormField->createTag()} functionality.
*
* @return HTML code for the textarea OR span element
*/
function Field($properties = array()) {
$properties = array_merge(
$properties,
array( array(
'Rows' => $this->rows, 'rows' => $this->rows,
'Cols' => $this->cols, 'cols' => $this->cols,
'Value' => htmlentities($this->value, ENT_COMPAT, 'UTF-8') 'value' => null,
'type' => null
) )
); );
return parent::Field($properties);
} }
function getTemplate() { function getTemplate() {
@ -113,4 +105,8 @@ class TextareaField extends FormField {
function setColumns($cols) { function setColumns($cols) {
$this->cols = $cols; $this->cols = $cols;
} }
function Value() {
return htmlentities($this->value, ENT_COMPAT, 'UTF-8');
}
} }

View File

@ -72,6 +72,10 @@ class TimeField extends TextField {
return parent::Field(); return parent::Field();
} }
function Type() {
return 'time text';
}
/** /**
* Sets the internal value to ISO date format. * Sets the internal value to ISO date format.
* *

View File

@ -164,12 +164,16 @@ class TreeDropdownField extends FormField {
$properties, $properties,
array( array(
'Title' => $title, 'Title' => $title,
'Metadata' => ($metadata) ? Convert::raw2json($metadata) : null 'Metadata' => ($metadata) ? Convert::raw2att(Convert::raw2json($metadata)) : null
) )
); );
return $this->customise($properties)->renderWith('TreeDropdownField'); return $this->customise($properties)->renderWith('TreeDropdownField');
} }
function extraClass() {
return implode(' ', array(parent::extraClass(), ($this->showSearch ? "searchable" : null)));
}
/** /**
* Get the whole tree of a part of the tree via an AJAX request. * Get the whole tree of a part of the tree via an AJAX request.

View File

@ -1,5 +1,5 @@
<% if IsReadonly %> <% if IsReadonly %>
<ul class="SelectionGroup$extraClass"> <ul class="SelectionGroup<% if extraClass %> $extraClass<% end_if %>">
<% control FieldSet %> <% control FieldSet %>
<% if Selected %> <% if Selected %>
<li$Selected> <li$Selected>
@ -10,5 +10,5 @@
<% end_if %> <% end_if %>
<% end_control %> <% end_control %>
<% else %> <% else %>
<ul class="SelectionGroup$extraClass"><% control FieldSet %><li$Selected>{$RadioButton}{$RadioLabel}{$FieldHolder}</li><% end_control %></ul> <ul class="SelectionGroup<% if extraClass %> $extraClass<% end_if %>"><% control FieldSet %><li$Selected>{$RadioButton}{$RadioLabel}{$FieldHolder}</li><% end_control %></ul>
<% end_if %> <% end_if %>

View File

@ -1 +1 @@
<input id="$ID" class="checkbox$extraClass" type="$Type" value="1" name="$Name"<% if TabIndex %> tabindex="$TabIndex"<% end_if %><% if isDisabled %> disabled<% end_if %><% if Value %> checked<% end_if %>> <input $AttributesHTML>

View File

@ -1,4 +1,4 @@
<div id="$Name" class="field $Type<% if extraClass %>$extraClass<% end_if %>"> <div id="$Name" class="field<% if extraClass %> $extraClass<% end_if %>">
$Field $Field
<label class="right" for="$ID">$Title</label> <label class="right" for="$ID">$Title</label>
<% if Message %><span class="message $MessageType">$messageBlock</span><% end_if %> <% if Message %><span class="message $MessageType">$messageBlock</span><% end_if %>

View File

@ -1,8 +1,12 @@
<ul id="$ID" class="optionset checkboxset$extraClass"> <ul id="$ID" class="$extraClass">
<% control Options %> <% if Options.Count %>
<li class="$Class"> <% control Options %>
<input id="$ID" class="checkbox" name="$Name" type="checkbox" value="$Value"<% if isChecked %> checked<% end_if %><% if isDisabled %> disabled<% end_if %>> <li class="$Class">
<label for="$ID">$Title</label> <input id="$ID" class="checkbox" name="$Name" type="checkbox" value="$Value"<% if isChecked %> checked="checked"<% end_if %><% if isDisabled %> disabled="disabled"<% end_if %>>
</li> <label for="$ID">$Title</label>
<% end_control %> </li>
<% end_control %>
<% else %>
<li>No options available</li>
<% end_if %>
</ul> </ul>

View File

@ -1,4 +1,4 @@
<select id="$ID" class="dropdown$extraClass" name="$Name"<% if TabIndex %> tabindex="$TabIndex"<% end_if %><% if isDisabled %> disabled<% end_if %>> <select $AttributesHTML>
<% control Options %> <% control Options %>
<option value="$Value"<% if Selected %> selected<% end_if %>>$Title</option> <option value="$Value"<% if Selected %> selected<% end_if %>>$Title</option>
<% end_control %> <% end_control %>

View File

@ -1,6 +1,8 @@
<div id="$Name" class="field $Type<% if extraClass %>$extraClass<% end_if %>"> <div id="$Name" class="field<% if extraClass %> $extraClass<% end_if %>">
<% if Title %><label class="left" for="$ID">$Title</label><% end_if %> <% if Title %><label class="left" for="$ID">$Title</label><% end_if %>
<div class="middleColumn">$Field</div> <div class="middleColumn">
$Field
</div>
<% if RightTitle %><label class="right" for="$ID">$RightTitle</label><% end_if %> <% if RightTitle %><label class="right" for="$ID">$RightTitle</label><% end_if %>
<% if Message %><span class="message $MessageType">$Message</span><% end_if %> <% if Message %><span class="message $MessageType">$Message</span><% end_if %>
</div> </div>

View File

@ -1,2 +1,2 @@
<input id="$ID" class="file$extraClass" type="file" name="$Name"<% if TabIndex %> tabindex="$TabIndex"<% end_if %>> <input $AttributesHTML>
<input type="hidden" name="MAX_FILE_SIZE" value="$MaxFileSize"> <input type="hidden" name="MAX_FILE_SIZE" value="$MaxFileSize">

View File

@ -1,5 +1,5 @@
<% if UseButtonTag %> <% if UseButtonTag %>
<button id="$ID" class="action$extraClass" type="$Type" title="$Title" value="$Title" name="$Name"<% if TabIndex %> $TabIndex<% end_if %><% if isDisabled %> disabled<% end_if %>></button> <button $AttributesHTML></button>
<% else %> <% else %>
<input id="$ID" class="action$extraClass" type="$Type" title="$Title" value="$Title" name="$Name"<% if TabIndex %> $TabIndex<% end_if %><% if isDisabled %> disabled<% end_if %>> <input $AttributesHTML>
<% end_if %> <% end_if %>

View File

@ -1,2 +1,2 @@
<span id="$ID" class="field$extraClass">$NiceValue</span> <span id="$ID"<% if extraClass %> class="$extraClass"<% end_if %>>$Value</span>
<hidden type="hidden" name="$Name" value="$Value"<% if TabIndex %> tabindex="$TabIndex"<% end_if %>> <input $AttributesHTML>

View File

@ -1 +1 @@
<h$HeadingLevel id="$ID" class="header$extraClass">$Title</h$HeadingLevel> <h$HeadingLevel $AttributesHTML>$Title</h$HeadingLevel>

View File

@ -1 +1 @@
<input class="hidden$extraClass" type="hidden" id="$ID" name="$Name" value="$Value"> <input $AttributesHTML>

View File

@ -1 +1 @@
<label id="$ID" class="label$extraClass">$Title</label> <label id="$ID" class="$extraClass">$Title</label>

View File

@ -1,4 +1,4 @@
<ul id="$ID" class="optionset$extraClass"> <ul id="$ID" class="$extraClass">
<% control Options %> <% control Options %>
<li class="$Class"> <li class="$Class">
<input id="$ID" class="radio" name="$Name" type="radio" value="$Value"<% if isChecked %> checked<% end_if %><% if isDisabled %> disabled<% end_if %>> <input id="$ID" class="radio" name="$Name" type="radio" value="$Value"<% if isChecked %> checked<% end_if %><% if isDisabled %> disabled<% end_if %>>

View File

@ -1 +1 @@
<input id="$ID" class="text$extraClass" type="$Type" value="$Value" name="$Name"<% if TabIndex %> tabindex="$TabIndex"<% end_if %><% if MaxLength %> maxlength="$MaxLength"<% end_if %><% if Size %> size="$Size"<% end_if %><% if isDisabled %> disabled<% end_if %> /> <input $AttributesHTML />

View File

@ -1 +1 @@
<textarea id="$ID" class="textarea$extraClass" name="$Name" rows="$Rows" cols="$Cols"<% if isDisabled %> disabled<% end_if %>>$Value</textarea> <textarea $AttributesHTML>$Value</textarea>

View File

@ -1,3 +1,3 @@
<div id="TreeDropdownField_$ID" class="TreeDropdownField single$extraClass<% if ShowSearch %> searchable<% end_if %>" data-url-tree="$Link(tree)" data-tile="$Title"<% if Metadata %> data-metadata="$Metadata"<% end_if %>> <div id="TreeDropdownField_$ID" class="TreeDropdownField single<% if extraClass %> $extraClass<% end_if %><% if ShowSearch %> searchable<% end_if %>" data-url-tree="$Link(tree)" data-title="$Title"<% if Metadata %> data-metadata="$Metadata"<% end_if %>>
<input id="$ID" type="hidden" name="$Name" value="$Value"> <input id="$ID" type="hidden" name="$Name" value="$Value" />
</div> </div>

View File

@ -4,6 +4,50 @@
* @subpackage tests * @subpackage tests
*/ */
class FormFieldTest extends SapphireTest { class FormFieldTest extends SapphireTest {
function testAttributes() {
$field = new FormField('MyField');
$field->setAttribute('foo', 'bar');
$this->assertEquals('bar', $field->getAttribute('foo'));
$attrs = $field->getAttributes();
$this->assertArrayHasKey('foo', $attrs);
$this->assertEquals('bar', $attrs['foo']);
}
function testAttributesHTML() {
$field = new FormField('MyField');
$field->setAttribute('foo', 'bar');
$this->assertContains('foo="bar"', $field->getAttributesHTML());
$field->setAttribute('foo', null);
$this->assertNotContains('foo=', $field->getAttributesHTML());
$field->setAttribute('foo', '');
$this->assertNotContains('foo=', $field->getAttributesHTML());
$field->setAttribute('foo', false);
$this->assertNotContains('foo=', $field->getAttributesHTML());
$field->setAttribute('foo', true);
$this->assertContains('foo="foo"', $field->getAttributesHTML());
$field->setAttribute('foo', 'false');
$this->assertContains('foo="false"', $field->getAttributesHTML());
$field->setAttribute('foo', 'true');
$this->assertContains('foo="true"', $field->getAttributesHTML());
$field->setAttribute('foo', 0);
$this->assertContains('foo="0"', $field->getAttributesHTML());
$field->setAttribute('one', 1);
$field->setAttribute('two', 2);
$field->setAttribute('three', 3);
$this->assertNotContains('one="1"', $field->getAttributesHTML('one', 'two'));
$this->assertNotContains('two="2"', $field->getAttributesHTML('one', 'two'));
$this->assertContains('three="3"', $field->getAttributesHTML('one', 'two'));
}
function testEveryFieldTransformsReadonlyAsClone() { function testEveryFieldTransformsReadonlyAsClone() {
$fieldClasses = ClassInfo::subclassesFor('FormField'); $fieldClasses = ClassInfo::subclassesFor('FormField');

View File

@ -8,6 +8,6 @@ class LabelFieldTest extends SapphireTest {
function testFieldHasNoNameAttribute() { function testFieldHasNoNameAttribute() {
$field = new LabelField('MyName', 'MyTitle'); $field = new LabelField('MyName', 'MyTitle');
$this->assertEquals($field->Field(), '<label id="MyName" class="label">MyTitle</label>'); $this->assertEquals($field->Field(), '<label id="MyName" class="readonly">MyTitle</label>');
} }
} }