Version: current

      1 <?php
      2 /**
      3  * Base class for all fields that contain other fields.
      4  * Implements sequentialisation - so that when we're saving / loading data, we can populate
      5  * a tabbed form properly.  All of the children are stored in $this->children
      6  * @package forms
      7  * @subpackage fields-structural
      8  */
      9 class CompositeField extends FormField {
     10
     11     /**
     12      * @var FieldSet
     13      */
     14     protected $children;
     15
     16     /**
     17      * Set to true when this field is a readonly field
     18      */
     19     protected $readonly;
     20
     21     /**
     22      * @var $columnCount int Toggle different css-rendering for multiple columns
     23      * ("onecolumn", "twocolumns", "threecolumns"). The content is determined
     24      * by the $children-array, so wrap all items you want to have grouped in a
     25      * column inside a CompositeField.
     26      * Caution: Please make sure that this variable actually matches the
     27      * count of your $children.
     28      */
     29     protected $columnCount = null;
     30
     31     public function __construct($children = null) {
     32         if($children instanceof FieldSet) {
     33             $this->children = $children;
     34         } elseif(is_array($children)) {
     35             $this->children = new FieldSet($children);
     36         } else {
     37             $children = is_array(func_get_args()) ? func_get_args() : array();
     38             $this->children = new FieldSet($children);
     39         }
     40         $this->children->setContainerField($this);
     41
     42         // Skipping FormField::__construct(), but we have to make sure this
     43         // doesn't count as a broken constructor
     44         $this->brokenOnConstruct = false;
     45         Object::__construct();
     46     }
     47
     48     /**
     49      * Returns all the sub-fields, suitable for <% control FieldSet %>
     50      */
     51     public function FieldSet() {
     52         return $this->children;
     53     }
     54
     55     public function setID($id) {
     56         $this->id = $id;
     57     }
     58
     59     public function Field() {
     60         $attributes = array(
     61             'class' => ($this->columnCount) ? "field {$this->class} {$this->extraClass()} multicolumn" : "field {$this->class} {$this->extraClass()}",
     62         );
     63         if (isset($this->id)) {
     64             $attributes['id'] = $this->id;
     65         }
     66         return $this->createTag('input', $attributes);
     67     }
     68
     69     /**
     70      * Accessor method for $this->children
     71      * @return FieldSet
     72      */
     73     public function getChildren() {
     74         return $this->children;
     75     }
     76
     77     /**
     78      * @param FieldSet $children
     79      */
     80     public function setChildren($children) {
     81         $this->children = $children;
     82     }
     83
     84     /**
     85      * Returns the fields nested inside another DIV
     86      */
     87      /*
     88     function FieldHolder() {
     89         $fs = $this->FieldSet();
     90         $idAtt = isset($this->id) ? " id=\"{$this->id}\"" : '';
     91         $className = ($this->columnCount) ? "field {$this->class} {$this->extraClass()} multicolumn" : "field {$this->class} {$this->extraClass()}";
     92         $content = "<div class=\"$className\"$idAtt>\n";
     93
     94         foreach($fs as $subfield) {
     95             if($this->columnCount) {
     96                 $className = "column{$this->columnCount}";
     97                 if(!next($fs)) $className .= " lastcolumn";
     98                 $content .= "\n<div class=\"{$className}\">\n" . $subfield->FieldHolder() . "\n</div>\n";
     99             } else if($subfield){
    100                 $content .= "\n" . $subfield->FieldHolder() . "\n";
    101             }
    102         }
    103         $content .= "</div>\n";
    104
    105         return $content;
    106     }
    107     */
    108
    109     /**
    110      * Returns the fields in the restricted field holder inside a DIV.
    111      */
    112      /*
    113     function SmallFieldHolder() {//return $this->FieldHolder();
    114         $fs = $this->FieldSet();
    115         $idAtt = isset($this->id) ? " id=\"{$this->id}\"" : '';
    116         $className = ($this->columnCount) ? "field CompositeField {$this->extraClass()} multicolumn" : "field CompositeField {$this->extraClass()}";
    117         $content = "<div class=\"$className\"$idAtt>";
    118
    119         foreach($fs as $subfield) {//echo ' subf'.$subfield->Name();
    120             if($this->columnCount) {
    121                 $className = "column{$this->columnCount}";
    122                 if(!next($fs)) $className .= " lastcolumn";
    123                 $content .= "<div class=\"{$className}\">" . $subfield->FieldHolder() . "</div>";
    124             } else if($subfield){
    125                 $content .= $subfield->SmallFieldHolder() . " ";
    126             }
    127         }
    128         $content .= "</div>";
    129
    130         return $content;
    131     }
    132     */
    133     /**
    134      * Add all of the non-composite fields contained within this field to the list.
    135      * Sequentialisation is used when connecting the form to its data source
    136      */
    137     public function collateDataFields(&$list, $saveableOnly = false) {
    138         foreach($this->children as $field) {
    139             if(is_object($field)) {
    140                 if($field->isComposite()) $field->collateDataFields($list, $saveableOnly);
    141                 if($saveableOnly) {
    142                     $isIncluded =  ($field->hasData() && !$field->isReadonly() && !$field->isDisabled());
    143                 } else {
    144                     $isIncluded =  ($field->hasData());
    145                 }
    146                 if($isIncluded) {
    147                     $name = $field->Name();
    148                     if($name) {
    149                         $formName = (isset($this->form)) ? $this->form->FormName() : '(unknown form)';
    150                         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);
    151                         $list[$name] = $field;
    152                     }
    153                 }
    154             }
    155         }
    156     }
    157
    158     function setForm($form) {
    159         foreach($this->children as $f) if(is_object($f)) $f->setForm($form);
    160         parent::setForm($form);
    161     }
    162
    163     function setColumnCount($columnCount) {
    164         $this->columnCount = $columnCount;
    165     }
    166
    167     function isComposite() { return true; }
    168     function hasData() { return false; }
    169
    170     public function fieldByName($name) {
    171         return $this->children->fieldByName($name);
    172     }
    173     /**
    174      * Add a new child field to the end of the set.
    175      */
    176     public function push(FormField $field) {
    177         $this->children->push($field);
    178     }
    179
    180     /**
    181      * @uses FieldSet->insertBefore()
    182      */
    183     public function insertBefore($field, $insertBefore) {
    184         $ret = $this->children->insertBefore($field, $insertBefore);
    185         $this->sequentialSet = null;
    186         return $ret;
    187     }
    188
    189     public function insertAfter($field, $insertAfter) {
    190         $ret = $this->children->insertAfter($field, $insertAfter);
    191         $this->sequentialSet = null;
    192         return $ret;
    193     }
    194
    195     /**
    196      * Remove a field from this CompositeField by Name.
    197      * The field could also be inside a CompositeField.
    198      *
    199      * @param string $fieldName The name of the field
    200      * @param boolean $dataFieldOnly If this is true, then a field will only
    201      * be removed if it's a data field.  Dataless fields, such as tabs, will
    202      * be left as-is.
    203      */
    204     public function removeByName($fieldName, $dataFieldOnly = false) {
    205         $this->children->removeByName($fieldName, $dataFieldOnly);
    206     }
    207
    208     public function replaceField($fieldName, $newField) {
    209         return $this->children->replaceField($fieldName, $newField);
    210     }
    211
    212     function rootFieldSet() {
    213         if(is_object($this->containerFieldSet)) return $this->containerFieldSet->rootFieldSet();
    214         else return $this->children;
    215     }
    216
    217     /**
    218      * Return a readonly version of this field.  Keeps the composition but returns readonly
    219      * versions of all the children
    220      */
    221     public function performReadonlyTransformation() {
    222         $newChildren = new FieldSet();
    223         $clone = clone $this;
    224         foreach($clone->getChildren() as $idx => $child) {
    225             if(is_object($child)) $child = $child->transform(new ReadonlyTransformation());
    226             $newChildren->push($child, $idx);
    227         }
    228
    229         $clone->children = $newChildren;
    230         $clone->readonly = true;
    231         return $clone;
    232     }
    233
    234     /**
    235      * Return a readonly version of this field.  Keeps the composition but returns readonly
    236      * versions of all the children
    237      */
    238     public function performDisabledTransformation($trans) {
    239         $newChildren = new FieldSet();
    240         $clone = clone $this;
    241         if($clone->getChildren()) foreach($clone->getChildren() as $idx => $child) {
    242             if(is_object($child)) {
    243                 $child = $child->transform($trans);
    244             }
    245             $newChildren->push($child, $idx);
    246         }
    247
    248         $clone->children = $newChildren;
    249         $clone->readonly = true;
    250
    251         return $clone;
    252     }
    253
    254     function IsReadonly() {
    255         return $this->readonly;
    256     }
    257
    258     /**
    259      * Find the numerical position of a field within
    260      * the children collection. Doesn't work recursively.
    261      *
    262      * @param string|FormField
    263      * @return Position in children collection (first position starts with 0). Returns FALSE if the field can't be found.
    264      */
    265     function fieldPosition($field) {
    266         if(is_string($field)) $field = $this->fieldByName($field);
    267         if(!$field) return false;
    268
    269         $i = 0;
    270         foreach($this->children as $child) {
    271             if($child->Name() == $field->Name()) return $i;
    272             $i++;
    273         }
    274
    275         return false;
    276     }
    277
    278     /**
    279      * Transform the named field into a readonly feld.
    280      *
    281      * @param string|FormField
    282      */
    283     function makeFieldReadonly($field) {
    284         $fieldName = ($field instanceof FormField) ? $field->Name() : $field;
    285
    286         // Iterate on items, looking for the applicable field
    287         foreach($this->children as $i => $item) {
    288             if($item->isComposite()) {
    289                 $item->makeFieldReadonly($fieldName);
    290             } else {
    291                 // Once it's found, use FormField::transform to turn the field into a readonly version of itself.
    292                 if($item->Name() == $fieldName) {
    293                     $this->children->replaceField($fieldName, $item->transform(new ReadonlyTransformation()));
    294
    295                     // Clear an internal cache
    296                     $this->sequentialSet = null;
    297
    298                     // A true results indicates that the field was foudn
    299                     return true;
    300                 }
    301             }
    302         }
    303         return false;
    304     }
    305
    306     function debug() {
    307         $result = "$this->class ($this->name) <ul>";
    308         foreach($this->children as $child) {
    309             $result .= "<li>" . Debug::text($child) . "&nbsp;</li>";
    310         }
    311         $result .= "</ul>";
    312         return $result;
    313     }
    314
    315     function validate($validator){
    316
    317         $valid = true;
    318         foreach($this->children as $idx => $child){
    319             $valid = ($child && $child->validate($validator) && $valid);
    320         }
    321
    322         return $valid;
    323     }
    324 }
    325
    326 ?>

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -