+
+#### FormField wrapper containers suffixed with `_Holder`
+
+Previously both the container div and FormField tag shared the same ID in
+certain cases. Now, the wrapper div in the default `FormField` template will be
+suffixed with `_Holder`.
+
+ Before:
+
+
+
+ After:
+
+
+#### Reverting to the old specification
+
+If upgrading existing forms is not feasible, developers can opt out of the new
+specifications by using the `FormTemplateHelper_Pre32` class rules instead of
+the default ones.
+
+ :::yaml
+ # mysite/config/_config.yml
+
+ Injector:
+ FormTemplateHelper:
+ class: FormTemplateHelper_Pre32
diff --git a/forms/Form.php b/forms/Form.php
index 19fabdddc..32679b16e 100644
--- a/forms/Form.php
+++ b/forms/Form.php
@@ -149,6 +149,21 @@ class Form extends RequestHandler {
*/
protected $attributes = array();
+ /**
+ * @var FormTemplateHelper
+ */
+ private $templateHelper = null;
+
+ /**
+ * @ignore
+ */
+ private $htmlID = null;
+
+ /**
+ * @ignore
+ */
+ private $formActionPath = false;
+
/**
* Create a new form, with the given fields an action buttons.
*
@@ -642,13 +657,14 @@ class Form extends RequestHandler {
public function getAttributes() {
$attrs = array(
- 'id' => $this->FormName(),
+ 'id' => $this->getTemplateHelper()->generateFormID($this),
'action' => $this->FormAction(),
'method' => $this->FormMethod(),
'enctype' => $this->getEncType(),
'target' => $this->target,
'class' => $this->extraClass(),
);
+
if($this->validator && $this->validator->getErrors()) {
if(!isset($attrs['class'])) $attrs['class'] = '';
$attrs['class'] .= ' validationerror';
@@ -671,6 +687,7 @@ class Form extends RequestHandler {
if(!$attrs || is_string($attrs)) $attrs = $this->getAttributes();
+
// Figure out if we can cache this form
// - forms with validation shouldn't be cached, cos their error messages won't be shown
// - forms with security tokens shouldn't be cached because security tokens expire
@@ -706,13 +723,43 @@ class Form extends RequestHandler {
}
/**
- * Set the target of this form to any value - useful for opening the form contents in a new window or refreshing
- * another frame
- *
- * @param target The value of the target
- */
+ * Set the {@link FormTemplateHelper}
+ *
+ * @param string|FormTemplateHelper
+ */
+ public function setTemplateHelper($helper) {
+ $this->templateHelper = $helper;
+ }
+
+ /**
+ * Return a {@link FormTemplateHelper} for this form. If one has not been
+ * set, return the default helper.
+ *
+ * @return FormTemplateHelper
+ */
+ public function getTemplateHelper() {
+ if($this->templateHelper) {
+ if(is_string($this->templateHelper)) {
+ return Injector::inst()->get($this->templateHelper);
+ }
+
+ return $this->templateHelper;
+ }
+
+ return Injector::inst()->get('FormTemplateHelper');
+ }
+
+ /**
+ * Set the target of this form to any value - useful for opening the form
+ * contents in a new window or refreshing another frame.
+ *
+ * @param target $target The value of the target
+ *
+ * @return FormField
+ */
public function setTarget($target) {
$this->target = $target;
+
return $this;
}
@@ -862,38 +909,50 @@ class Form extends RequestHandler {
}
}
- /** @ignore */
- private $formActionPath = false;
-
/**
* Set the form action attribute to a custom URL.
*
- * Note: For "normal" forms, you shouldn't need to use this method. It is recommended only for situations where
- * you have two relatively distinct parts of the system trying to communicate via a form post.
+ * Note: For "normal" forms, you shouldn't need to use this method. It is
+ * recommended only for situations where you have two relatively distinct
+ * parts of the system trying to communicate via a form post.
*/
public function setFormAction($path) {
$this->formActionPath = $path;
+
return $this;
}
/**
- * @ignore
- */
- private $htmlID = null;
-
- /**
- * Returns the name of the form
+ * Returns the name of the form.
+ *
+ * @return string
*/
public function FormName() {
- if($this->htmlID) return $this->htmlID;
- else return $this->class . '_' . str_replace(array('.', '/'), '', $this->name);
+ if($this->htmlID) {
+ return $this->htmlID;
+ } else {
+ return $this->class . '_' . str_replace(array('.', '/'), '', $this->name);
+ }
}
/**
- * Set the HTML ID attribute of the form
+ * Set the HTML ID attribute of the form.
+ *
+ * @param string $id
+ *
+ * @return FormField
*/
public function setHTMLID($id) {
$this->htmlID = $id;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getHTMLID() {
+ return $this->htmlID;
}
/**
diff --git a/forms/FormField.php b/forms/FormField.php
index 0496e7d92..55b67bb70 100644
--- a/forms/FormField.php
+++ b/forms/FormField.php
@@ -115,6 +115,7 @@ class FormField extends RequestHandler {
} else {
$label = $fieldName;
}
+
$label = preg_replace("/([a-z]+)([A-Z])/","$1 $2", $label);
return $label;
@@ -177,17 +178,44 @@ class FormField extends RequestHandler {
/**
* Returns the HTML ID of the field - used in the template by label tags.
+ *
* The ID is generated as FormName_FieldName. All Field functions should ensure
* that this ID is included in the field.
+ *
+ * @return string
*/
public function ID() {
- $name = preg_replace('/(^-)|(-$)/', '', preg_replace('/[^A-Za-z0-9_-]+/', '-', $this->name));
- if($this->form) return $this->form->FormName() . '_' . $name;
- else return $name;
+ return $this->getTemplateHelper()->generateFieldID($this);
}
/**
- * Returns the field name - used by templates.
+ * Returns the HTML ID for the form field holder element.
+ *
+ * @return string
+ */
+ public function HolderID() {
+ return $this->getTemplateHelper()->generateFieldHolderID($this);
+ }
+
+ /**
+ * Returns the current {@link FormTemplateHelper} on either the parent
+ * Form or the global helper set through the {@link Injector} layout.
+ *
+ * To customize a single {@link FormField}, use {@link setTemplate} and
+ * provide a custom template name.
+ *
+ * @return FormTemplateHelper
+ */
+ public function getTemplateHelper() {
+ if($this->form) {
+ return $this->form->getTemplateHelper();
+ }
+
+ return Injector::inst()->get('FormTemplateHelper');
+ }
+
+ /**
+ * Returns the raw field name.
*
* @return string
*/
@@ -552,7 +580,6 @@ class FormField extends RequestHandler {
* 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
*/
public function getCustomValidationMessage() {
diff --git a/forms/FormTemplateHelper.php b/forms/FormTemplateHelper.php
new file mode 100644
index 000000000..9fbe9090b
--- /dev/null
+++ b/forms/FormTemplateHelper.php
@@ -0,0 +1,122 @@
+
+ * $form->setTemplateHelper('ClassName');
+ *
+ *
+ * Globally, the FormTemplateHelper can be set via the {@link Injector} API.
+ *
+ * For backwards compatibility, with < 3.2 use the {@link FormTemplateHelper_Pre32}
+ * class which will preserve the old style form field attributes.
+ *
+ *
+ * Injector:
+ * FormTemplateHelper:
+ * class: FormTemplateHelper_Pre32
+ *
+ *
+ * @package framework
+ * @subpackage forms
+ */
+class FormTemplateHelper {
+
+ /**
+ * @param Form $form
+ *
+ * @return string
+ */
+ public function generateFormID($form) {
+ if($id = $form->getHTMLID()) {
+ return Convert::raw2htmlid($id);
+ }
+
+ return Convert::raw2htmlid($form->FormName());
+ }
+
+ /**
+ * @param FormField $field
+ *
+ * @return string
+ */
+ public function generateFieldHolderID($field) {
+ return $this->generateFieldID($field) . '_Holder';
+ }
+
+ /**
+ * Generate the field ID value
+ *
+ * @param FormField
+ *
+ * @return string
+ */
+ public function generateFieldID($field) {
+ if($form = $field->getForm()) {
+ return sprintf("%s_%s",
+ $form->getHTMLID(),
+ Convert::raw2htmlid($field->getName())
+ );
+ }
+
+ return Convert::raw2htmlid($field->getName());
+ }
+
+}
+
+/**
+ * Note that this will cause duplicate and invalid ID attributes.
+ *
+ * @deprecated 4.0
+ *
+ * @package framework
+ * @subpackage forms
+ */
+class FormTemplateHelper_Pre32 extends FormTemplateHelper {
+
+ /**
+ * @param Form
+ *
+ * @return string
+ */
+ public function generateFormID($form) {
+ if($id = $form->getHTMLID()) {
+ return $id;
+ }
+
+ return $form->class . '_' . str_replace(array('.', '/'), '', $form->getName());
+ }
+
+ /**
+ * @param FormField
+ *
+ * @return string
+ */
+ public function generateFieldHolderID($field) {
+ return $field->getName();
+ }
+
+ /**
+ * @param FormField
+ *
+ * @return string
+ */
+ public function generateFieldID($field) {
+ $name = preg_replace('/(^-)|(-$)/', '', preg_replace('/[^A-Za-z0-9_-]+/', '-', $field->getName()));
+
+ if($form = $field->getForm()) {
+ return $form->FormName() . '_' . $name;
+ }
+
+ return $name;
+ }
+}
diff --git a/templates/forms/FormField_holder.ss b/templates/forms/FormField_holder.ss
index fa23b8595..ae6f5353b 100644
--- a/templates/forms/FormField_holder.ss
+++ b/templates/forms/FormField_holder.ss
@@ -1,4 +1,4 @@
-