Merge pull request #4125 from assertchris/clean-up-form-field

Clean up FormField
This commit is contained in:
Daniel Hensby 2015-06-20 16:30:44 +01:00
commit ebeea242fe

View File

@ -1,19 +1,25 @@
<?php <?php
/** /**
* Represents a field in a form. * Represents a field in a form.
* *
* A FieldList contains a number of FormField objects which make up the whole of a form. * A FieldList contains a number of FormField objects which make up the whole of a form.
* In addition to single fields, FormField objects can be "composite", for example, the {@link TabSet} *
* field. Composite fields let us define complex forms without having to resort to custom HTML. * In addition to single fields, FormField objects can be "composite", for example, the
* * {@link TabSet} field. Composite fields let us define complex forms without having to resort to
* <b>Subclassing</b> * custom HTML.
* *
* Define a {@link dataValue()} method that returns a value suitable for inserting into a single database field. * To subclass:
* For example, you might tidy up the format of a date or currency field. *
* Define {@link saveInto()} to totally customise saving. * Define a {@link dataValue()} method that returns a value suitable for inserting into a single
* For example, data might be saved to the filesystem instead of the data record, * database field.
* or saved to a component of the data record instead of the data record itself. *
* * For example, you might tidy up the format of a date or currency field. Define {@link saveInto()}
* to totally customise saving.
*
* For example, data might be saved to the filesystem instead of the data record, or saved to a
* component of the data record instead of the data record itself.
*
* @package forms * @package forms
* @subpackage core * @subpackage core
*/ */
@ -24,153 +30,244 @@ class FormField extends RequestHandler {
*/ */
protected $form; protected $form;
protected $name, $title, $value ,$message, $messageType, $extraClass;
/** /**
* @var $description string Adds a "title"-attribute to the markup. * @var string
*/
protected $name;
/**
* @var null|string
*/
protected $title;
/**
* @var mixed
*/
protected $value;
/**
* @var string
*/
protected $message;
/**
* @var string
*/
protected $messageType;
/**
* @var string
*/
protected $extraClass;
/**
* Adds a title attribute to the markup.
*
* @var string
*
* @todo Implement in all subclasses * @todo Implement in all subclasses
*/ */
protected $description; protected $description;
/** /**
* @var $extraClasses array Extra CSS-classes for the formfield-container * Extra CSS classes for the FormField container.
*
* @var array
*/ */
protected $extraClasses; protected $extraClasses;
public $dontEscape;
/** /**
* @var $rightTitle string Used in SmallFieldHolder to force a right-aligned label, or in FieldHolder * @var bool
* to create contextual label. */
public $dontEscape;
/**
* Right-aligned, contextual label for the field.
*
* @var string
*/ */
protected $rightTitle; protected $rightTitle;
/** /**
* @var $leftTitle string Used in SmallFieldHolder() to force a left-aligned label with correct spacing. * Left-aligned, contextual label for the field.
* Please use $title for FormFields rendered with FieldHolder(). *
* @var string
*/ */
protected $leftTitle; protected $leftTitle;
/** /**
* Stores a reference to the FieldList that contains this object. * Stores a reference to the FieldList that contains this object.
*
* @var FieldList * @var FieldList
*/ */
protected $containerFieldList; protected $containerFieldList;
/** /**
* @var boolean * @var bool
*/ */
protected $readonly = false; protected $readonly = false;
/** /**
* @var boolean * @var bool
*/ */
protected $disabled = false; protected $disabled = false;
/** /**
* @var string custom validation message for the Field * Custom validation message for the field.
*/
protected $customValidationMessage = "";
/**
* Name of the template used to render this form field. If not set, then
* will look up the class ancestry for the first matching template where
* the template name equals the class name.
*
* To explicitly use a custom template or one named other than the form
* field see {@link setTemplate()}, {@link setFieldHolderTemplate()}
* *
* @var string * @var string
*/ */
protected protected $customValidationMessage = '';
$template,
$fieldHolderTemplate,
$smallFieldHolderTemplate;
/** /**
* @var array All attributes on the form field (not the field holder). * Name of the template used to render this form field. If not set, then will look up the class
* Partially determined based on other instance properties, please use {@link getAttributes()}. * ancestry for the first matching template where the template name equals the class name.
*
* To explicitly use a custom template or one named other than the form field see
* {@link setTemplate()}.
*
* @var string
*/
protected $template;
/**
* Name of the template used to render this form field. If not set, then will look up the class
* ancestry for the first matching template where the template name equals the class name.
*
* To explicitly use a custom template or one named other than the form field see
* {@link setFieldHolderTemplate()}.
*
* @var string
*/
protected $fieldHolderTemplate;
/**
* @var string
*/
protected $smallFieldHolderTemplate;
/**
* All attributes on the form field (not the field holder).
*
* Partially determined based on other instance properties.
*
* @see getAttributes()
*
* @var array
*/ */
protected $attributes = array(); protected $attributes = array();
/** /**
* Takes a fieldname and converts camelcase to spaced * Takes a field name and converts camelcase to spaced words. Also resolves combined field
* words. Also resolves combined fieldnames with dot syntax * names with dot syntax to spaced words.
* to spaced words.
* *
* Examples: * Examples:
*
* - 'TotalAmount' will return 'Total Amount' * - 'TotalAmount' will return 'Total Amount'
* - 'Organisation.ZipCode' will return 'Organisation Zip Code' * - 'Organisation.ZipCode' will return 'Organisation Zip Code'
* *
* @param string $fieldName * @param string $fieldName
*
* @return string * @return string
*/ */
public static function name_to_label($fieldName) { public static function name_to_label($fieldName) {
if(strpos($fieldName, '.') !== false) { if(strpos($fieldName, '.') !== false) {
$parts = explode('.', $fieldName); $parts = explode('.', $fieldName);
$label = $parts[count($parts)-2] . ' ' . $parts[count($parts)-1];
$label = $parts[count($parts) - 2] . ' ' . $parts[count($parts) - 1];
} else { } else {
$label = $fieldName; $label = $fieldName;
} }
$label = preg_replace("/([a-z]+)([A-Z])/","$1 $2", $label);
return preg_replace('/([a-z]+)([A-Z])/', '$1 $2', $label);
return $label;
} }
/** /**
* Construct and return HTML tag. * Construct and return HTML tag.
*
* @param string $tag
* @param array $attributes
* @param null|string $content
*
* @return string
*/ */
public static function create_tag($tag, $attributes, $content = null) { public static function create_tag($tag, $attributes, $content = null) {
$preparedAttributes = ''; $preparedAttributes = '';
foreach($attributes as $k => $v) {
// Note: as indicated by the $k == value item here; the decisions over what to include in the attributes foreach($attributes as $attributeKey => $attributeValue) {
// can sometimes get finicky if(!empty($attributeValue) || $attributeValue === '0' || ($attributeKey == 'value' && $attributeValue !== null)) {
if(!empty($v) || $v === '0' || ($k == 'value' && $v !== null) ) { $preparedAttributes .= sprintf(
$preparedAttributes .= " $k=\"" . Convert::raw2att($v) . "\""; ' %s="%s"', $attributeKey, Convert::raw2att($attributeValue)
);
} }
} }
if($content || $tag != 'input') return "<$tag$preparedAttributes>$content</$tag>"; if($content || $tag != 'input') {
else return "<$tag$preparedAttributes />"; return sprintf(
'<%s%s>%s</%s>', $tag, $preparedAttributes, $content, $tag
);
}
return sprintf(
'<%s%s />', $tag, $preparedAttributes
);
} }
/** /**
* Creates a new field. * Creates a new field.
* *
* @param string $name The internal field name, passed to forms. * @param string $name The internal field name, passed to forms.
* @param string $title The human-readable field label. * @param null|string $title The human-readable field label.
* @param mixed $value The value of the field. * @param mixed $value The value of the field.
*/ */
public function __construct($name, $title = null, $value = null) { public function __construct($name, $title = null, $value = null) {
$this->name = $name; $this->name = $name;
$this->title = ($title === null) ? self::name_to_label($name) : $title;
if($value !== NULL) $this->setValue($value); if($title === null) {
$this->title = self::name_to_label($name);
} else {
$this->title = $title;
}
if($value !== null) {
$this->setValue($value);
}
parent::__construct(); parent::__construct();
} }
/** /**
* Return a Link to this field * Return a Link to this field.
*
* @param null|string $action
*
* @return string
*/ */
public function Link($action = null) { public function Link($action = null) {
return Controller::join_links($this->form->FormAction(), 'field/' . $this->name, $action); return Controller::join_links($this->form->FormAction(), 'field/' . $this->name, $action);
} }
/** /**
* Returns the HTML ID of the field - used in the template by label tags. * Returns the HTML ID of the field.
* The ID is generated as FormName_FieldName. All Field functions should ensure *
* that this ID is included in the field. * The ID is generated as FormName_FieldName. All Field functions should ensure that this ID is
* included in the field.
*/ */
public function ID() { public function ID() {
$name = preg_replace('/(^-)|(-$)/', '', preg_replace('/[^A-Za-z0-9_-]+/', '-', $this->name)); $name = $this->name;
if($this->form) return $this->form->FormName() . '_' . $name; $name = preg_replace('/[^A-Za-z0-9_-]+/', '-', $name);
else return $name; $name = preg_replace('/(^-)|(-$)/', '', $name);
if($this->form) {
return $this->form->FormName() . '_' . $name;
}
return $name;
} }
/** /**
* Returns the field name - used by templates. * Returns the field name.
* *
* @return string * @return string
*/ */
public function getName() { public function getName() {
@ -179,130 +276,160 @@ class FormField extends RequestHandler {
/** /**
* Returns the field message, used by form validation. * Returns the field message, used by form validation.
*
* Use {@link setError()} to set this property. * Use {@link setError()} to set this property.
* *
* @return string * @return string
*/ */
public function Message() { public function Message() {
return $this->message; return $this->message;
} }
/** /**
* Returns the field message type, used by form validation. * Returns the field message type.
* Arbitrary value which is mostly used for CSS classes *
* in the rendered HTML, e.g. "required". * Arbitrary value which is mostly used for CSS classes in the rendered HTML, e.g "required".
*
* Use {@link setError()} to set this property. * Use {@link setError()} to set this property.
* *
* @return string * @return string
*/ */
public function MessageType() { public function MessageType() {
return $this->messageType; return $this->messageType;
} }
/** /**
* Returns the field value - used by templates. * Returns the field value.
*
* @return mixed
*/ */
public function Value() { public function Value() {
return $this->value; return $this->value;
} }
/** /**
* Method to save this form field into the given data object. * Method to save this form field into the given data object.
* By default, makes use of $this->dataValue() *
* * @param DataObjectInterface $record
* @param DataObjectInterface $record DataObject to save data into
*/ */
public function saveInto(DataObjectInterface $record) { public function saveInto(DataObjectInterface $record) {
if($this->name) { if($this->name) {
$record->setCastedField($this->name, $this->dataValue()); $record->setCastedField($this->name, $this->dataValue());
} }
} }
/** /**
* Returns the field value suitable for insertion into the data object * Returns the field value suitable for insertion into the data object.
*
* @return mixed
*/ */
public function dataValue() { public function dataValue() {
return $this->value; return $this->value;
} }
/** /**
* Returns the field label - used by templates. * Returns the field label.
*/ */
public function Title() { public function Title() {
return $this->title; return $this->title;
} }
public function setTitle($val) { /**
$this->title = $val; * @param string $title
*
* @return $this
*/
public function setTitle($title) {
$this->title = $title;
return $this; return $this;
} }
/** /**
* Gets the contextual label than can be used for additional field description. * @return string
* Can be shown to the right or under the field in question.
*
* @return string Contextual label text.
*/ */
public function RightTitle() { public function RightTitle() {
return $this->rightTitle; return $this->rightTitle;
} }
/** /**
* Sets the contextual label. * @param string $rightTitle
* *
* @param $val string Text to set on the label. * @return $this
*/ */
public function setRightTitle($val) { public function setRightTitle($rightTitle) {
$this->rightTitle = $val; $this->rightTitle = $rightTitle;
return $this;
}
public function LeftTitle() {
return $this->leftTitle;
}
public function setLeftTitle($val) {
$this->leftTitle = $val;
return $this; return $this;
} }
/** /**
* Compiles all CSS-classes. Optionally includes a "nolabel"-class * @return string
* if no title was set on the formfield. */
* Uses {@link Message()} and {@link MessageType()} to add validatoin public function LeftTitle() {
* error classes which can be used to style the contained tags. return $this->leftTitle;
* }
* @return string CSS-classnames
/**
* @param string $leftTitle
*
* @return $this
*/
public function setLeftTitle($leftTitle) {
$this->leftTitle = $leftTitle;
return $this;
}
/**
* Compiles all CSS-classes. Optionally includes a "nolabel" class if no title was set on the
* FormField.
*
* Uses {@link Message()} and {@link MessageType()} to add validation error classes which can
* be used to style the contained tags.
*
* @return string
*/ */
public function extraClass() { public function extraClass() {
$classes = array(); $classes = array();
$classes[] = $this->Type(); $classes[] = $this->Type();
if($this->extraClasses) $classes = array_merge($classes, array_values($this->extraClasses)); if($this->extraClasses) {
$classes = array_merge(
// Allow customization of label and field tag positioning $classes,
if(!$this->Title()) $classes[] = "nolabel"; array_values($this->extraClasses)
);
// Allow custom styling of any element in the container based }
// on validation errors, e.g. red borders on input tags.
// CSS-Class needs to be different from the one rendered if(!$this->Title()) {
// through {@link FieldHolder()} $classes[] = 'nolabel';
if($this->Message()) $classes[] .= "holder-" . $this->MessageType(); }
// Allow custom styling of any element in the container based on validation errors,
// e.g. red borders on input tags.
//
// CSS class needs to be different from the one rendered through {@link FieldHolder()}.
if($this->Message()) {
$classes[] .= 'holder-' . $this->MessageType();
}
return implode(' ', $classes); return implode(' ', $classes);
} }
/** /**
* Add one or more CSS-classes to the formfield-container. Multiple class * Add one or more CSS-classes to the FormField container.
* names should be space delimited. *
* * Multiple class names should be space delimited.
*
* @param string $class * @param string $class
*
* @return $this
*/ */
public function addExtraClass($class) { public function addExtraClass($class) {
$classes = preg_split('/\s+/', $class); $classes = preg_split('/\s+/', $class);
foreach ($classes as $class) { foreach($classes as $class) {
$this->extraClasses[$class] = $class; $this->extraClasses[$class] = $class;
} }
@ -310,199 +437,252 @@ class FormField extends RequestHandler {
} }
/** /**
* Remove one or more CSS-classes from the formfield-container. * Remove one or more CSS-classes from the FormField container.
* *
* @param string $class * @param string $class
*
* @return $this
*/ */
public function removeExtraClass($class) { public function removeExtraClass($class) {
$classes = preg_split('/\s+/', $class); $classes = preg_split('/\s+/', $class);
foreach ($classes as $class) { foreach($classes as $class) {
unset($this->extraClasses[$class]); unset($this->extraClasses[$class]);
} }
return $this; return $this;
} }
/** /**
* Set an HTML attribute on the field element, mostly an <input> tag. * Set an HTML attribute on the field element, mostly an <input> tag.
* *
* Some attributes are best set through more specialized methods, to avoid interfering with built-in behaviour: * Some attributes are best set through more specialized methods, to avoid interfering with
* built-in behaviour:
*
* - 'class': {@link addExtraClass()} * - 'class': {@link addExtraClass()}
* - 'title': {@link setDescription()} * - 'title': {@link setDescription()}
* - 'value': {@link setValue} * - 'value': {@link setValue}
* - 'name': {@link setName} * - 'name': {@link setName}
* *
* CAUTION Doesn't work on most fields which are composed of more than one HTML form field: * Caution: this doesn't work on most fields which are composed of more than one HTML form
* AjaxUniqueTextField, CheckboxSetField, ComplexTableField, CompositeField, ConfirmedPasswordField, * field.
* CountryDropdownField, CreditCardField, CurrencyField, DateField, DatetimeField, FieldGroup, GridField, *
* HtmlEditorField, ImageField, ImageFormAction, InlineFormAction, ListBoxField, etc. * @param string $name
* * @param string $value
* @param string *
* @param string * @return $this
*/ */
public function setAttribute($name, $value) { public function setAttribute($name, $value) {
$this->attributes[$name] = $value; $this->attributes[$name] = $value;
return $this; return $this;
} }
/** /**
* Get an HTML attribute defined by the field, or added through {@link setAttribute()}. * Get an HTML attribute defined by the field, or added through {@link setAttribute()}.
* Caution: Doesn't work on all fields, see {@link setAttribute()}. *
* * Caution: this doesn't work on all fields, see {@link setAttribute()}.
* @return string *
* @return null|string
*/ */
public function getAttribute($name) { public function getAttribute($name) {
$attrs = $this->getAttributes(); $attributes = $this->getAttributes();
if(isset($attrs[$name])) return $attrs[$name];
if(isset($attributes[$name])) {
return $attributes[$name];
}
return null;
} }
/** /**
* @return array * @return array
*/ */
public function getAttributes() { public function getAttributes() {
$attrs = array( $attributes = array(
'type' => 'text', 'type' => 'text',
'name' => $this->getName(), 'name' => $this->getName(),
'value' => $this->Value(), 'value' => $this->Value(),
'class' => $this->extraClass(), 'class' => $this->extraClass(),
'id' => $this->ID(), 'id' => $this->ID(),
'disabled' => $this->isDisabled(), 'disabled' => $this->isDisabled(),
'readonly' => $this->isReadonly() 'readonly' => $this->isReadonly()
); );
if ($this->Required()) { if($this->Required()) {
$attrs['required'] = 'required'; $attributes['required'] = 'required';
$attrs['aria-required'] = 'true'; $attributes['aria-required'] = 'true';
} }
return array_merge($attrs, $this->attributes); return array_merge($attributes, $this->attributes);
} }
/** /**
* @param Array Custom attributes to process. Falls back to {@link getAttributes()}. * 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 * If at least one argument is passed as a string, all arguments act as excludes, by name.
*
* @param array $attributes
*
* @return string
*/ */
public function getAttributesHTML($attrs = null) { public function getAttributesHTML($attributes = null) {
$exclude = (is_string($attrs)) ? func_get_args() : null; $exclude = null;
if(!$attrs || is_string($attrs)) $attrs = $this->getAttributes(); if(is_string($attributes)) {
$exclude = func_get_args();
}
// Remove empty if(!$attributes || is_string($attributes)) {
$attrs = array_filter((array)$attrs, function($v) { $attributes = $this->getAttributes();
}
$attributes = (array) $attributes;
$attributes = array_filter($attributes, function ($v) {
return ($v || $v === 0 || $v === '0'); return ($v || $v === 0 || $v === '0');
}); });
// Remove excluded if($exclude) {
if($exclude) $attrs = array_diff_key($attrs, array_flip($exclude)); $attributes = array_diff_key(
$attributes,
array_flip($exclude)
);
}
// Create markkup
$parts = array(); $parts = array();
foreach($attrs as $name => $value) {
$parts[] = ($value === true) ? "{$name}=\"{$name}\"" : "{$name}=\"" . Convert::raw2att($value) . "\""; foreach($attributes as $name => $value) {
if($value === true) {
$parts[] = sprintf('%s="%s"', $name, $name);
} else {
$parts[] = sprintf('%s="%s"', $name, Convert::raw2att($value));
}
} }
return implode(' ', $parts); 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.
*
* @return string
*/ */
public function attrTitle() { public function attrTitle() {
return Convert::raw2att($this->title); return Convert::raw2att($this->title);
} }
/** /**
* 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.
*
* @return string
*/ */
public function attrValue() { public function attrValue() {
return Convert::raw2att($this->value); return Convert::raw2att($this->value);
} }
/** /**
* Set the field value. * Set the field value.
* *
* @param mixed $value * @param mixed $value
* @param mixed $data Optional data source passed in by {@see Form::loadDataFrom} * @param null|array|DataObject $data {@see Form::loadDataFrom}
* @return FormField Self reference *
* @return $this
*/ */
public function setValue($value) { public function setValue($value) {
$this->value = $value; $this->value = $value;
return $this; return $this;
} }
/** /**
* Set the field name * Set the field name.
*
* @param string $name
*
* @return $this
*/ */
public function setName($name) { public function setName($name) {
$this->name = $name; $this->name = $name;
return $this; return $this;
} }
/** /**
* Set the container form. * Set the container form.
* This is called whenever you create a new form and put fields inside it, so that you don't *
* have to worry about linking the two. * This is called automatically when fields are added to forms.
*
* @param Form $form
*
* @return $this
*/ */
public function setForm($form) { public function setForm($form) {
$this->form = $form; $this->form = $form;
return $this; return $this;
} }
/** /**
* Get the currently used form. * Get the currently used form.
* *
* @return Form * @return Form
*/ */
public function getForm() { public function getForm() {
return $this->form; return $this->form;
} }
/** /**
* Return TRUE if security token protection is enabled on the parent {@link Form}. * Return true if security token protection is enabled on the parent {@link Form}.
* *
* @return bool * @return bool
*/ */
public function securityTokenEnabled() { public function securityTokenEnabled() {
$form = $this->getForm(); $form = $this->getForm();
if(!$form) return false;
if(!$form) {
return false;
}
return $form->getSecurityToken()->isEnabled(); return $form->getSecurityToken()->isEnabled();
} }
/** /**
* Sets the error message to be displayed on the form field * Sets the error message to be displayed on the form field.
* Set by php validation of the form.
* *
* @param string $message Message to show to the user. Allows HTML content, * Allows HTML content, so remember to use Convert::raw2xml().
* which means you need to use Convert::raw2xml() for any user supplied data. *
* @param string $message
* @param string $messageType
*
* @return $this
*/ */
public function setError($message, $messageType) { public function setError($message, $messageType) {
$this->message = $message; $this->message = $message;
$this->messageType = $messageType; $this->messageType = $messageType;
return $this; return $this;
} }
/** /**
* Set the custom error message to show instead of the default * Set the custom error message to show instead of the default format.
* format of Please Fill In XXX. Different from setError() as *
* that appends it to the standard error messaging * Different from setError() as that appends it to the standard error messaging.
* *
* @param string Message for the error * @param string $customValidationMessage
*/ *
public function setCustomValidationMessage($msg) { * @return $this
$this->customValidationMessage = $msg; */
public function setCustomValidationMessage($customValidationMessage) {
return $this; $this->customValidationMessage = $customValidationMessage;
}
return $this;
/** }
* Get the custom error message for this form field. If a custom
* message has not been defined then just return blank. The default /**
* error is defined on {@link Validator}. * Get the custom error message for this form field. If a custom message has not been defined
* then just return blank. The default error is defined on {@link Validator}.
* *
* @todo Should the default error message be stored here instead
* @return string * @return string
*/ */
public function getCustomValidationMessage() { public function getCustomValidationMessage() {
@ -511,98 +691,120 @@ class FormField extends RequestHandler {
/** /**
* Set name of template (without path or extension). * Set name of template (without path or extension).
* Caution: Not consistently implemented in all subclasses, *
* please check the {@link Field()} method on the subclass for support. * Caution: Not consistently implemented in all subclasses, please check the {@link Field()}
* * method on the subclass for support.
* @param string *
* @param string $template
*
* @return $this
*/ */
public function setTemplate($template) { public function setTemplate($template) {
$this->template = $template; $this->template = $template;
return $this; return $this;
} }
/** /**
* @return string * @return string
*/ */
public function getTemplate() { public function getTemplate() {
return $this->template; return $this->template;
} }
/** /**
* @return string * @return string
*/ */
public function getFieldHolderTemplate() { public function getFieldHolderTemplate() {
return $this->fieldHolderTemplate; return $this->fieldHolderTemplate;
} }
/** /**
* Set name of template (without path or extension) for the holder, * Set name of template (without path or extension) for the holder, which in turn is
* which in turn is responsible for rendering {@link Field()}. * responsible for rendering {@link Field()}.
* *
* Caution: Not consistently implemented in all subclasses, * Caution: Not consistently implemented in all subclasses, please check the {@link Field()}
* please check the {@link Field()} method on the subclass for support. * method on the subclass for support.
* *
* @param string * @param string $fieldHolderTemplate
*
* @return $this
*/ */
public function setFieldHolderTemplate($template) { public function setFieldHolderTemplate($fieldHolderTemplate) {
$this->fieldHolderTemplate = $template; $this->fieldHolderTemplate = $fieldHolderTemplate;
return $this; return $this;
} }
/** /**
* @return string * @return string
*/ */
public function getSmallFieldHolderTemplate() { public function getSmallFieldHolderTemplate() {
return $this->smallFieldHolderTemplate; return $this->smallFieldHolderTemplate;
} }
/** /**
* Set name of template (without path or extension) for the small holder, * Set name of template (without path or extension) for the small holder, which in turn is
* which in turn is responsible for rendering {@link Field()}. * responsible for rendering {@link Field()}.
* *
* Caution: Not consistently implemented in all subclasses, * Caution: Not consistently implemented in all subclasses, please check the {@link Field()}
* please check the {@link Field()} method on the subclass for support. * method on the subclass for support.
* *
* @param string * @param string $smallFieldHolderTemplate
*
* @return $this
*/ */
public function setSmallFieldHolderTemplate($template) { public function setSmallFieldHolderTemplate($smallFieldHolderTemplate) {
$this->smallFieldHolderTemplate = $template; $this->smallFieldHolderTemplate = $smallFieldHolderTemplate;
return $this; return $this;
} }
/** /**
* Returns the form field - used by templates. * Returns the form field.
*
* Although FieldHolder is generally what is inserted into templates, all of the field holder * Although FieldHolder is generally what is inserted into templates, all of the field holder
* templates make use of $Field. It's expected that FieldHolder will give you the "complete" * templates make use of $Field. It's expected that FieldHolder will give you the "complete"
* representation of the field on the form, whereas Field will give you the core editing widget, * representation of the field on the form, whereas Field will give you the core editing widget,
* such as an input tag. * such as an input tag.
* *
* @param array $properties key value pairs of template variables * @param array $properties
*
* @return string * @return string
*/ */
public function Field($properties = array()) { public function Field($properties = array()) {
$obj = ($properties) ? $this->customise($properties) : $this; $context = $this;
if(count($properties)) {
$context = $context->customise($properties);
}
$this->extend('onBeforeRender', $this); $this->extend('onBeforeRender', $this);
return $obj->renderWith($this->getTemplates());
return $context->renderWith($this->getTemplates());
} }
/** /**
* Returns a "field holder" for this field - used by templates. * Returns a "field holder" for this field.
* *
* Forms are constructed by concatenating a number of these field holders. * Forms are constructed by concatenating a number of these field holders.
*
* The default field holder is a label and a form field inside a div. * The default field holder is a label and a form field inside a div.
*
* @see FieldHolder.ss * @see FieldHolder.ss
* *
* @param array $properties key value pairs of template variables * @param array $properties
*
* @return string * @return string
*/ */
public function FieldHolder($properties = array()) { public function FieldHolder($properties = array()) {
$obj = ($properties) ? $this->customise($properties) : $this; $context = $this;
return $obj->renderWith($this->getFieldHolderTemplates()); if(count($properties)) {
$context = $this->customise($properties);
}
return $context->renderWith($this->getFieldHolderTemplates());
} }
/** /**
@ -613,71 +815,81 @@ class FormField extends RequestHandler {
* @return string * @return string
*/ */
public function SmallFieldHolder($properties = array()) { public function SmallFieldHolder($properties = array()) {
$obj = ($properties) ? $this->customise($properties) : $this; $context = $this;
return $obj->renderWith($this->getSmallFieldHolderTemplates()); if(count($properties)) {
$context = $this->customise($properties);
}
return $context->renderWith($this->getSmallFieldHolderTemplates());
} }
/** /**
* Returns an array of templates to use for rendering {@link FieldH} * Returns an array of templates to use for rendering {@link FieldHolder}.
* *
* @return array * @return array
*/ */
public function getTemplates() { public function getTemplates() {
return $this->_templates($this->getTemplate()); return $this->_templates($this->getTemplate());
} }
/** /**
* Returns an array of templates to use for rendering {@link FieldHolder} * Returns an array of templates to use for rendering {@link FieldHolder}.
* *
* @return array * @return array
*/ */
public function getFieldHolderTemplates() { public function getFieldHolderTemplates() {
return $this->_templates( return $this->_templates(
$this->getFieldHolderTemplate(), $this->getFieldHolderTemplate(),
'_holder' '_holder'
); );
} }
/** /**
* Returns an array of templates to use for rendering {@link SmallFieldHolder} * Returns an array of templates to use for rendering {@link SmallFieldHolder}.
* *
* @return array * @return array
*/ */
public function getSmallFieldHolderTemplates() { public function getSmallFieldHolderTemplates() {
return $this->_templates( return $this->_templates(
$this->getSmallFieldHolderTemplate(), $this->getSmallFieldHolderTemplate(),
'_holder_small' '_holder_small'
); );
} }
/** /**
* Generate an array of classname strings to use for rendering this form * Generate an array of class name strings to use for rendering this form field into HTML.
* field into HTML
* *
* @param string $custom custom template (if set) * @param string $customTemplate
* @param string $suffix template suffix * @param string $customTemplateSuffix
* *
* @return array $stack a stack of * @return array
*/ */
private function _templates($custom = null, $suffix = null) { private function _templates($customTemplate = null, $customTemplateSuffix = null) {
$matches = array(); $matches = array();
foreach(array_reverse(ClassInfo::ancestry($this)) as $className) { foreach(array_reverse(ClassInfo::ancestry($this)) as $className) {
$matches[] = $className . $suffix; $matches[] = $className . $customTemplateSuffix;
if($className == "FormField") break; if($className == "FormField") {
break;
}
} }
if($custom) array_unshift($matches, $custom); if($customTemplate) {
array_unshift($matches, $customTemplate);
}
return $matches; return $matches;
} }
/** /**
* Returns true if this field is a composite field. * Returns true if this field is a composite field.
*
* To create composite field types, you should subclass {@link CompositeField}. * To create composite field types, you should subclass {@link CompositeField}.
*
* @return bool
*/ */
public function isComposite() { public function isComposite() {
return false; return false;
@ -685,68 +897,91 @@ class FormField extends RequestHandler {
/** /**
* Returns true if this field has its own data. * Returns true if this field has its own data.
* Some fields, such as titles and composite fields, don't actually have any data. It doesn't *
* make sense for data-focused methods to look at them. By overloading hasData() to return false, * Some fields, such as titles and composite fields, don't actually have any data. It doesn't
* you can prevent any data-focused methods from looking at it. * make sense for data-focused methods to look at them. By overloading hasData() to return
* false, you can prevent any data-focused methods from looking at it.
* *
* @see FieldList::collateDataFields() * @see FieldList::collateDataFields()
*
* @return bool
*/ */
public function hasData() { public function hasData() {
return true; return true;
} }
/** /**
* @return boolean * @return bool
*/ */
public function isReadonly() { public function isReadonly() {
return $this->readonly; return $this->readonly;
} }
/** /**
* Sets readonly-flag on form-field. Please use performReadonlyTransformation() * Sets a read-only flag on this FormField.
* to actually transform this instance. *
* @param $bool boolean Setting "false" has no effect on the field-state. * Use performReadonlyTransformation() to transform this instance.
*
* Setting this to false has no effect on the field.
*
* @param bool $readonly
*
* @return $this
*/ */
public function setReadonly($bool) { public function setReadonly($readonly) {
$this->readonly = $bool; $this->readonly = $readonly;
return $this; return $this;
} }
/** /**
* @return boolean * @return bool
*/ */
public function isDisabled() { public function isDisabled() {
return $this->disabled; return $this->disabled;
} }
/** /**
* Sets disabed-flag on form-field. Please use performDisabledTransformation() * Sets a disabled flag on this FormField.
* to actually transform this instance. *
* @param $bool boolean Setting "false" has no effect on the field-state. * Use performDisabledTransformation() to transform this instance.
*
* Setting this to false has no effect on the field.
*
* @param bool $disabled
*
* @return $this
*/ */
public function setDisabled($bool) { public function setDisabled($disabled) {
$this->disabled = $bool; $this->disabled = $disabled;
return $this; return $this;
} }
/** /**
* Returns a readonly version of this field * Returns a read-only version of this field.
*
* @return FormField
*/ */
public function performReadonlyTransformation() { public function performReadonlyTransformation() {
$copy = $this->castedCopy('ReadonlyField'); $copy = $this->castedCopy('ReadonlyField');
$copy->setReadonly(true); $copy->setReadonly(true);
return $copy; return $copy;
} }
/** /**
* Return a disabled version of this field. * Return a disabled version of this field.
* Tries to find a class of the class name of this field suffixed with "_Disabled", *
* failing that, finds a method {@link setDisabled()}. * Tries to find a class of the class name of this field suffixed with "_Disabled", failing
* that, finds a method {@link setDisabled()}.
* *
* @return FormField * @return FormField
*/ */
public function performDisabledTransformation() { public function performDisabledTransformation() {
$disabledClassName = $this->class . '_Disabled'; $disabledClassName = $this->class . '_Disabled';
if(ClassInfo::exists($disabledClassName)) { if(ClassInfo::exists($disabledClassName)) {
$clone = $this->castedCopy($disabledClassName); $clone = $this->castedCopy($disabledClassName);
} else { } else {
@ -754,46 +989,70 @@ class FormField extends RequestHandler {
$clone->setDisabled(true); $clone->setDisabled(true);
} }
return $clone; return $clone;
}
public function transform(FormTransformation $trans) {
return $trans->transform($this);
} }
public function hasClass($class){ /**
$patten = '/'.strtolower($class).'/i'; * @param FormTransformation $transformation
$subject = strtolower($this->class." ".$this->extraClass()); *
* @return mixed
*/
public function transform(FormTransformation $transformation) {
return $transformation->transform($this);
}
/**
* @param string $class
*
* @return int
*/
public function hasClass($class) {
$patten = '/' . strtolower($class) . '/i';
$subject = strtolower($this->class . ' ' . $this->extraClass());
return preg_match($patten, $subject); return preg_match($patten, $subject);
} }
/** /**
* Returns the field type - used by templates. * Returns the field type.
*
* 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. Doesn't signify the <input type> attribute, *
* see {link getAttributes()}. * It's handy for assigning HTML classes. Doesn't signify the <input type> attribute.
* *
* @see {link getAttributes()}.
*
* @return string * @return string
*/ */
public function Type() { public function Type() {
return strtolower(preg_replace('/Field$/', '', $this->class)); return strtolower(preg_replace('/Field$/', '', $this->class));
} }
/** /**
* @deprecated 3.2 Use FormField::create_tag() * @deprecated 3.2 Use FormField::create_tag()
*
* @param string $tag
* @param array $attributes
* @param null|string $content
*
* @return string
*/ */
public function createTag($tag, $attributes, $content = null) { public function createTag($tag, $attributes, $content = null) {
Deprecation::notice('3.2', 'Use FormField::create_tag()'); Deprecation::notice('3.2', 'Use FormField::create_tag()');
return self::create_tag($tag, $attributes, $content); return self::create_tag($tag, $attributes, $content);
} }
/** /**
* Abstract method each {@link FormField} subclass must implement, * Abstract method each {@link FormField} subclass must implement, determines whether the field
* determines whether the field is valid or not based on the value. * is valid or not based on the value.
*
* @todo Make this abstract. * @todo Make this abstract.
* *
* @param Validator * @param Validator $validator
* @return boolean *
* @return bool
*/ */
public function validate($validator) { public function validate($validator) {
return true; return true;
@ -801,13 +1060,16 @@ class FormField extends RequestHandler {
/** /**
* Describe this field, provide help text for it. * Describe this field, provide help text for it.
* By default, renders as a <span class="description"> *
* underneath the form field. * By default, renders as a <span class="description"> underneath the form field.
* *
* @return string Description * @param string $description
*
* @return $this
*/ */
public function setDescription($description) { public function setDescription($description) {
$this->description = $description; $this->description = $description;
return $this; return $this;
} }
@ -817,38 +1079,52 @@ class FormField extends RequestHandler {
public function getDescription() { public function getDescription() {
return $this->description; return $this->description;
} }
public function debug() {
return "$this->class ($this->name: $this->title : <font style='color:red;'>$this->message</font>)"
. " = $this->value";
}
/** /**
* This function is used by the template processor. If you refer to a field as a $ variable, it * @return string
*/
public function debug() {
return sprintf(
'%s (%s: %s : <span style="color:red;">%s</span>) = %s',
$this->class,
$this->name,
$this->title,
$this->message,
$this->value
);
}
/**
* This function is used by the template processor. If you refer to a field as a $ variable, it
* will return the $Field value. * will return the $Field value.
*
* @return string
*/ */
public function forTemplate() { public function forTemplate() {
return $this->Field(); return $this->Field();
} }
/** /**
* @uses Validator->fieldIsRequired() * @return bool
* @return boolean
*/ */
public function Required() { public function Required() {
if($this->form && ($validator = $this->form->Validator)) { if($this->form && ($validator = $this->form->Validator)) {
return $validator->fieldIsRequired($this->name); return $validator->fieldIsRequired($this->name);
} }
return false;
} }
/** /**
* Set the FieldList that contains this field. * Set the FieldList that contains this field.
* *
* @param FieldList $list * @param FieldList $containerFieldList
*
* @return FieldList * @return FieldList
*/ */
public function setContainerFieldList($list) { public function setContainerFieldList($containerFieldList) {
$this->containerFieldList = $list; $this->containerFieldList = $containerFieldList;
return $this; return $this;
} }
@ -861,47 +1137,61 @@ class FormField extends RequestHandler {
return $this->containerFieldList; return $this->containerFieldList;
} }
public function rootFieldList() {
if(is_object($this->containerFieldList)) return $this->containerFieldList->rootFieldList();
else user_error("rootFieldList() called on $this->class object without a containerFieldList", E_USER_ERROR);
}
/** /**
* Returns another instance of this field, but "cast" to a different class. * @return null|FieldList
* The logic tries to retain all of the instance properties, */
* and may be overloaded by subclasses to set additional ones. public function rootFieldList() {
if(is_object($this->containerFieldList)) {
return $this->containerFieldList->rootFieldList();
}
user_error(
"rootFieldList() called on $this->class object without a containerFieldList",
E_USER_ERROR
);
return null;
}
/**
* Returns another instance of this field, but "cast" to a different class. The logic tries to
* retain all of the instance properties, and may be overloaded by subclasses to set additional
* ones.
* *
* Assumes the standard FormField parameter signature with * Assumes the standard FormField parameter signature with its name as the only mandatory
* its name as the only mandatory argument. Mainly geared towards * argument. Mainly geared towards creating *_Readonly or *_Disabled subclasses of the same
* creating *_Readonly or *_Disabled subclasses of the same type, * type, or casting to a {@link ReadonlyField}.
* or casting to a {@link ReadonlyField}. *
* Does not copy custom field templates, since they probably won't apply to the new instance.
*
* @param mixed $classOrCopy Class name for copy, or existing copy instance to update
* *
* Does not copy custom field templates, since they probably won't apply to
* the new instance.
*
* @param String $classOrCopy Class name for copy, or existing copy instance to update
* @return FormField * @return FormField
*/ */
public function castedCopy($classOrCopy) { public function castedCopy($classOrCopy) {
$field = (is_object($classOrCopy)) ? $classOrCopy : new $classOrCopy($this->name); $field = $classOrCopy;
if(!is_object($field)) {
$field = new $classOrCopy($this->name);
}
$field $field
->setValue($this->value) // get value directly from property, avoid any conversions ->setValue($this->value)
->setForm($this->form) ->setForm($this->form)
->setTitle($this->Title()) ->setTitle($this->Title())
->setLeftTitle($this->LeftTitle()) ->setLeftTitle($this->LeftTitle())
->setRightTitle($this->RightTitle()) ->setRightTitle($this->RightTitle())
->addExtraClass($this->extraClass()) ->addExtraClass($this->extraClass())
->setDescription($this->getDescription()); ->setDescription($this->getDescription());
// Only include built-in attributes, ignore anything // Only include built-in attributes, ignore anything set through getAttributes().
// set through getAttributes(), since those might change important characteristics // Those might change important characteristics of the field, e.g. its "type" attribute.
// of the field, e.g. its "type" attribute. foreach($this->attributes as $attributeKey => $attributeValue) {
foreach($this->attributes as $k => $v) { $field->setAttribute($attributeKey, $attributeValue);
$field->setAttribute($k, $v); }
}
$field->dontEscape = $this->dontEscape; $field->dontEscape = $this->dontEscape;
return $field; return $field;
} }
} }