silverstripe-framework/forms/CompositeField.php

390 lines
9.4 KiB
PHP
Raw Normal View History

<?php
/**
* Base class for all fields that contain other fields.
*
2014-08-15 08:53:05 +02:00
* Implements sequentialisation - so that when we're saving / loading data, we
* can populate a tabbed form properly. All of the children are stored in
* $this->children
*
* @package forms
* @subpackage fields-structural
*/
class CompositeField extends FormField {
2014-08-15 08:53:05 +02:00
/**
* @var FieldList
*/
protected $children;
2014-08-15 08:53:05 +02:00
/**
* Set to true when this field is a readonly field
*/
protected $readonly;
2014-08-15 08:53:05 +02:00
/**
2014-08-15 08:53:05 +02:00
* @var $columnCount int Toggle different css-rendering for multiple columns
* ("onecolumn", "twocolumns", "threecolumns"). The content is determined
* by the $children-array, so wrap all items you want to have grouped in a
* column inside a CompositeField.
2014-08-15 08:53:05 +02:00
* Caution: Please make sure that this variable actually matches the
* count of your $children.
*/
protected $columnCount = null;
/**
* @var String custom HTML tag to render with, e.g. to produce a <fieldset>.
*/
protected $tag = 'div';
2014-08-15 08:53:05 +02:00
/**
* @var String Optional description for this set of fields.
* If the {@link $tag} property is set to use a 'fieldset', this will be
* rendered as a <legend> tag, otherwise its a 'title' attribute.
*/
protected $legend;
public function __construct($children = null) {
if($children instanceof FieldList) {
$this->children = $children;
} elseif(is_array($children)) {
2014-08-15 08:53:05 +02:00
$this->children = new FieldList($children);
} else {
//filter out null/empty items
$children = array_filter(func_get_args());
2014-08-15 08:53:05 +02:00
$this->children = new FieldList($children);
}
$this->children->setContainerField($this);
2014-08-15 08:53:05 +02:00
// Skipping FormField::__construct(), but we have to make sure this
// doesn't count as a broken constructor
$this->brokenOnConstruct = false;
Object::__construct();
}
/**
* Returns all the sub-fields, suitable for <% loop FieldList %>
*
* @return FieldList
*/
public function FieldList() {
return $this->children;
}
public function setID($id) {
$this->id = $id;
return $this;
}
/**
* Accessor method for $this->children
*
* @return FieldList
*/
public function getChildren() {
return $this->children;
}
2014-08-15 08:53:05 +02:00
/**
* @param FieldList $children
*/
public function setChildren($children) {
$this->children = $children;
return $this;
}
2014-08-15 08:53:05 +02:00
/**
* @param string
*/
public function setTag($tag) {
$this->tag = $tag;
2014-08-15 08:53:05 +02:00
return $this;
}
/**
2014-08-15 08:53:05 +02:00
* @return string
*/
public function getTag() {
return $this->tag;
}
2014-08-15 08:53:05 +02:00
/**
* @param string
*/
public function setLegend($legend) {
$this->legend = $legend;
return $this;
}
/**
2014-08-15 08:53:05 +02:00
* @return string
*/
public function getLegend() {
return $this->legend;
}
public function extraClasses() {
$classes = array('field', 'CompositeField', parent::extraClasses());
if($this->columnCount) $classes[] = 'multicolumn';
2014-08-15 08:53:05 +02:00
return implode(' ', $classes);
}
public function getAttributes() {
return array_merge(
parent::getAttributes(),
array(
2014-08-15 08:53:05 +02:00
'tabindex' => null,
'type' => null,
'value' => null,
'type' => null,
'title' => ($this->tag == 'fieldset') ? null : $this->legend
)
);
}
/**
2014-08-15 08:53:05 +02:00
* Add all of the non-composite fields contained within this field to the
* list.
*
* Sequentialisation is used when connecting the form to its data source
*/
public function collateDataFields(&$list, $saveableOnly = false) {
foreach($this->children as $field) {
if(is_object($field)) {
if($field->isComposite()) $field->collateDataFields($list, $saveableOnly);
if($saveableOnly) {
$isIncluded = ($field->hasData() && !$field->isReadonly() && !$field->isDisabled());
} else {
$isIncluded = ($field->hasData());
}
if($isIncluded) {
$name = $field->getName();
if($name) {
$formName = (isset($this->form)) ? $this->form->FormName() : '(unknown form)';
if(isset($list[$name])) {
user_error("collateDataFields() I noticed that a field called '$name' appears twice in"
. " your form: '{$formName}'. One is a '{$field->class}' and the other is a"
. " '{$list[$name]->class}'", E_USER_ERROR);
}
$list[$name] = $field;
}
}
}
}
}
public function setForm($form) {
2014-08-15 08:53:05 +02:00
foreach($this->children as $f)
if(is_object($f)) $f->setForm($form);
2014-08-15 08:53:05 +02:00
parent::setForm($form);
2014-08-15 08:53:05 +02:00
return $this;
}
2014-08-15 08:53:05 +02:00
public function setColumnCount($columnCount) {
$this->columnCount = $columnCount;
return $this;
}
2014-08-15 08:53:05 +02:00
public function getColumnCount() {
return $this->columnCount;
}
2014-08-15 08:53:05 +02:00
public function isComposite() {
2014-08-15 08:53:05 +02:00
return true;
}
public function hasData() {
2014-08-15 08:53:05 +02:00
return false;
}
public function fieldByName($name) {
return $this->children->fieldByName($name);
}
2014-08-15 08:53:05 +02:00
/**
* Add a new child field to the end of the set.
2014-08-15 08:53:05 +02:00
*
* @param FormField
*/
public function push(FormField $field) {
$this->children->push($field);
}
2014-08-15 08:53:05 +02:00
/**
* Add a new child field to the beginning of the set.
*
* @param FormField
*/
public function unshift(FormField $field) {
$this->children->unshift($field);
}
/**
* @uses FieldList->insertBefore()
*/
public function insertBefore($insertBefore, $field) {
$ret = $this->children->insertBefore($insertBefore, $field);
$this->sequentialSet = null;
return $ret;
}
/**
* @uses FieldList->insertAfter()
*/
public function insertAfter($insertAfter, $field) {
$ret = $this->children->insertAfter($insertAfter, $field);
$this->sequentialSet = null;
return $ret;
}
/**
* Remove a field from this CompositeField by Name.
* The field could also be inside a CompositeField.
2014-08-15 08:53:05 +02:00
*
* @param string $fieldName The name of the field
* @param boolean $dataFieldOnly If this is true, then a field will only
* be removed if it's a data field. Dataless fields, such as tabs, will
* be left as-is.
*/
public function removeByName($fieldName, $dataFieldOnly = false) {
$this->children->removeByName($fieldName, $dataFieldOnly);
}
public function replaceField($fieldName, $newField) {
return $this->children->replaceField($fieldName, $newField);
}
public function rootFieldList() {
if(is_object($this->containerFieldList)) return $this->containerFieldList->rootFieldList();
else return $this->children;
}
2014-08-15 08:53:05 +02:00
/**
* Return a readonly version of this field. Keeps the composition but returns readonly
* versions of all the child {@link FormField} objects.
*
* @return CompositeField
*/
public function performReadonlyTransformation() {
$newChildren = new FieldList();
$clone = clone $this;
if($clone->getChildren()) foreach($clone->getChildren() as $idx => $child) {
if(is_object($child)) $child = $child->transform(new ReadonlyTransformation());
$newChildren->push($child, $idx);
}
$clone->children = $newChildren;
$clone->readonly = true;
$clone->addExtraClass($this->extraClass());
$clone->setDescription($this->getDescription());
return $clone;
}
/**
* Return a disabled version of this field. Keeps the composition but returns disabled
* versions of all the child {@link FormField} objects.
*
* @return CompositeField
*/
public function performDisabledTransformation() {
$newChildren = new FieldList();
$clone = clone $this;
if($clone->getChildren()) foreach($clone->getChildren() as $idx => $child) {
if(is_object($child)) $child = $child->transform(new DisabledTransformation());
$newChildren->push($child, $idx);
}
$clone->children = $newChildren;
$clone->readonly = true;
$clone->addExtraClass($this->extraClass());
$clone->setDescription($this->getDescription());
foreach($this->attributes as $k => $v) {
$clone->setAttribute($k, $v);
}
return $clone;
}
switch externals to trunk. Inform-merge: from the changeset: r32477: Merge 2.0-inform from trunk previously r32478: Merge 2.0-inform from trunk previously r32481: merge 2.0infom with lastes chunk r32483: merge 2.0infom with lastes chunk r33526: Final styling of all forms and combined communication form add/alter javascript for height adjustment of First / Second block r33580: styling for combined form communication in myinfom pages r33706: styling of combined form (communication) in Email r33881: made compatible to $extraClass r33885: added defaultVal r33887: fixed typo r34728: modified SmallFieldHolder?() r34729: added "validationError"-class r34914: WIP3866: Factfinder: Hide "self emplyed" block r34964: Change current plan upto TraumaInsurance? r35038: disabled friggin field focus r35230: #1032 Fixed hash-link insertion r35887: conditionally setting parameters in sourceID() - to avoid empty overrides r35892: Saving value in SQL-compatible format (YYYY-MM-DD instead of DD/MM/YYYY), with fallback for non-sql values (just passed through without conversion) r35928: Removed "create a" from PageType?-dropdown, sorting alphabetically, falling back to $singular_name r35990: branched off for membertablefield r35994: fix for membertablefield r36024: added array-condition needed for DMYDateField r36083: fix bug for compositeField -> dropDatalessField r36394: removed debug code r36826: change wrong indent format r36828: WIP 4262: Logging out of My Inform goes to blank page r36858: Fixed error caused in r12472 while merging to Session-class r37132: Merged partial changesets from branches/2.0-nzct, only adding childID to detailform when not in add-mode r40815: add an unsubscribe record when a member subscribe a newslettertype r41113: fix the bug described in http://support.silverstripe.com/info/ticket/31: CRM not showing search results r43226: fixed search (partial merge from trunk) r43268: merged createNewPassword() from trunk, was referencing a non-existinent global function randomString() git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@45473 467b73ca-7a2a-4603-9d3b-597d59a354a9
2007-11-23 02:10:19 +01:00
public function IsReadonly() {
return $this->readonly;
}
2014-08-15 08:53:05 +02:00
/**
* Find the numerical position of a field within
* the children collection. Doesn't work recursively.
2014-08-15 08:53:05 +02:00
*
* @param string|FormField
* @return int Position in children collection (first position starts with 0). Returns FALSE if the field can't
* be found.
*/
public function fieldPosition($field) {
if(is_string($field)) $field = $this->fieldByName($field);
if(!$field) return false;
2014-08-15 08:53:05 +02:00
$i = 0;
foreach($this->children as $child) {
if($child->getName() == $field->getName()) return $i;
$i++;
}
2014-08-15 08:53:05 +02:00
return false;
}
2014-08-15 08:53:05 +02:00
/**
* Transform the named field into a readonly feld.
2014-08-15 08:53:05 +02:00
*
* @param string|FormField
*/
public function makeFieldReadonly($field) {
$fieldName = ($field instanceof FormField) ? $field->getName() : $field;
2014-08-15 08:53:05 +02:00
// Iterate on items, looking for the applicable field
foreach($this->children as $i => $item) {
if($item->isComposite()) {
$item->makeFieldReadonly($fieldName);
} else {
// Once it's found, use FormField::transform to turn the field into a readonly version of itself.
if($item->getName() == $fieldName) {
$this->children->replaceField($fieldName, $item->transform(new ReadonlyTransformation()));
// Clear an internal cache
$this->sequentialSet = null;
// A true results indicates that the field was found
return true;
}
}
}
return false;
}
public function debug() {
$result = "$this->class ($this->name) <ul>";
foreach($this->children as $child) {
$result .= "<li>" . Debug::text($child) . "&nbsp;</li>";
}
$result .= "</ul>";
return $result;
}
/**
* Validate this field
*
* @param Validator $validator
* @return bool
*/
public function validate($validator) {
$valid = true;
foreach($this->children as $idx => $child){
$valid = ($child && $child->validate($validator) && $valid);
}
return $valid;
}
}