2007-07-19 12:40:28 +02:00
< ? php
/**
* Base class for all fields that contain other fields .
* 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
2008-01-09 05:18:36 +01:00
* @ package forms
* @ subpackage fields - structural
2007-07-19 12:40:28 +02:00
*/
class CompositeField extends FormField {
2008-10-03 17:56:14 +02:00
/**
2011-05-11 09:51:54 +02:00
* @ var FieldList
2008-10-03 17:56:14 +02:00
*/
2007-07-19 12:40:28 +02:00
protected $children ;
2008-10-03 17:56:14 +02:00
2007-07-19 12:40:28 +02:00
/**
* Set to true when this field is a readonly field
*/
protected $readonly ;
/**
* @ 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 .
* Caution : Please make sure that this variable actually matches the
* count of your $children .
*/
protected $columnCount = null ;
2012-02-16 15:44:36 +01:00
/**
* @ var String custom HTML tag to render with , e . g . to produce a < fieldset >.
*/
protected $tag = 'div' ;
2007-07-19 12:40:28 +02:00
2012-02-16 16:40:19 +01: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 ;
2007-07-19 12:40:28 +02:00
public function __construct ( $children = null ) {
2011-05-11 09:51:54 +02:00
if ( $children instanceof FieldList ) {
2007-07-19 12:40:28 +02:00
$this -> children = $children ;
} elseif ( is_array ( $children )) {
2011-05-11 09:51:54 +02:00
$this -> children = new FieldList ( $children );
2007-07-19 12:40:28 +02:00
} else {
$children = is_array ( func_get_args ()) ? func_get_args () : array ();
2011-05-11 09:51:54 +02:00
$this -> children = new FieldList ( $children );
2007-07-19 12:40:28 +02:00
}
2008-09-11 02:02:49 +02:00
$this -> children -> setContainerField ( $this );
2008-08-20 06:41:55 +02:00
2009-07-06 23:48:12 +02:00
// Skipping FormField::__construct(), but we have to make sure this
// doesn't count as a broken constructor
$this -> brokenOnConstruct = false ;
2007-07-19 12:40:28 +02:00
Object :: __construct ();
}
/**
2011-05-11 09:51:54 +02:00
* Returns all the sub - fields , suitable for <% control FieldList %>
2007-07-19 12:40:28 +02:00
*/
2011-05-11 09:51:54 +02:00
public function FieldList () {
2007-07-19 12:40:28 +02:00
return $this -> children ;
}
2011-05-11 09:51:54 +02:00
/**
* @ deprecated 3.0 Please use { @ link FieldList ()} .
*/
public function FieldSet () {
2011-10-29 01:02:11 +02:00
Deprecation :: notice ( '3.0' , 'Use FieldList() instead.' );
2011-05-11 09:51:54 +02:00
return $this -> FieldList ();
}
2007-07-19 12:40:28 +02:00
public function setID ( $id ) {
$this -> id = $id ;
2012-02-17 13:35:26 +01:00
return $this ;
2007-07-19 12:40:28 +02:00
}
2007-10-18 03:05:52 +02:00
2008-08-20 06:41:55 +02:00
/**
* Accessor method for $this -> children
2011-05-11 09:51:54 +02:00
* @ return FieldList
2008-08-20 06:41:55 +02:00
*/
public function getChildren () {
return $this -> children ;
}
2008-12-04 23:38:32 +01:00
/**
2011-05-11 09:51:54 +02:00
* @ param FieldList $children
2008-12-04 23:38:32 +01:00
*/
public function setChildren ( $children ) {
$this -> children = $children ;
2012-02-17 13:35:26 +01:00
return $this ;
2008-12-04 23:38:32 +01:00
}
2007-07-19 12:40:28 +02:00
2012-02-16 15:44:36 +01:00
/** @param String */
public function setTag ( $tag ) {
$this -> tag = $tag ;
return $this ;
}
/** @return String */
public function getTag () {
return $this -> tag ;
}
2012-02-16 16:40:19 +01:00
/** @param String */
public function setLegend ( $legend ) {
$this -> legend = $legend ;
return $this ;
}
/** @return String */
public function getLegend () {
return $this -> legend ;
}
2011-12-22 13:10:57 +01:00
function extraClasses () {
$classes = array ( 'field' , 'CompositeField' , parent :: extraClasses ());
if ( $this -> columnCount ) $classes [] = 'multicolumn' ;
return implode ( ' ' , $classes );
}
function getAttributes () {
return array_merge (
parent :: getAttributes (),
2012-02-16 16:40:19 +01:00
array (
'tabindex' => null ,
'type' => null ,
'value' => null ,
'type' => null ,
'title' => ( $this -> tag == 'fieldset' ) ? null : $this -> legend
)
2011-12-22 13:10:57 +01:00
);
}
2012-04-11 07:33:36 +02:00
public function Field ( $properties = array ()) {
2012-02-16 16:40:19 +01:00
$content = '' ;
if ( $this -> tag == 'fieldset' && $this -> legend ) {
$content .= '<legend>' . $this -> legend . '<legend>' ;
}
$fs = $this -> FieldList ();
foreach ( $fs as $subfield ) {
if ( $this -> columnCount ) {
$className = " column { $this -> columnCount } " ;
if ( ! next ( $fs )) $className .= " lastcolumn " ;
$content .= " \n <div class= \" { $className } \" > \n " . $subfield -> Field () . " \n </div> \n " ;
} else if ( $subfield ){
$content .= " \n " . $subfield -> Field () . " \n " ;
}
}
return $this -> createTag ( $this -> getTag (), $this -> getAttributes (), $content );
}
2007-07-19 12:40:28 +02:00
/**
* Returns the fields nested inside another DIV
*/
function FieldHolder () {
2011-12-22 13:10:57 +01:00
$content = '' ;
2012-02-16 16:40:19 +01:00
if ( $this -> tag == 'fieldset' && $this -> legend ) {
$content .= '<legend>' . $this -> legend . '<legend>' ;
}
2011-05-11 09:51:54 +02:00
$fs = $this -> FieldList ();
2007-07-19 12:40:28 +02:00
foreach ( $fs as $subfield ) {
if ( $this -> columnCount ) {
$className = " column { $this -> columnCount } " ;
if ( ! next ( $fs )) $className .= " lastcolumn " ;
2007-10-18 03:05:52 +02:00
$content .= " \n <div class= \" { $className } \" > \n " . $subfield -> FieldHolder () . " \n </div> \n " ;
2007-07-19 12:40:28 +02:00
} else if ( $subfield ){
2007-10-18 03:05:52 +02:00
$content .= " \n " . $subfield -> FieldHolder () . " \n " ;
2007-07-19 12:40:28 +02:00
}
}
2012-02-16 15:44:36 +01:00
return $this -> createTag ( $this -> getTag (), $this -> getAttributes (), $content );
2007-09-16 18:05:05 +02:00
}
/**
* Returns the fields in the restricted field holder inside a DIV .
*/
2012-02-16 16:40:19 +01:00
function SmallFieldHolder () {
2011-05-11 09:51:54 +02:00
$fs = $this -> FieldList ();
2012-02-16 15:44:36 +01:00
$tag = $this -> getTag ();
2007-09-16 18:05:05 +02:00
$idAtt = isset ( $this -> id ) ? " id= \" { $this -> id } \" " : '' ;
$className = ( $this -> columnCount ) ? " field CompositeField { $this -> extraClass () } multicolumn " : " field CompositeField { $this -> extraClass () } " ;
2012-02-16 15:44:36 +01:00
$content = " < $tag class= \" $className\ " $idAtt > " ;
2012-02-16 16:40:19 +01:00
if ( $this -> tag == 'fieldset' && $this -> legend ) {
$content .= '<legend>' . $this -> legend . '<legend>' ;
}
2007-09-16 18:05:05 +02:00
2012-02-16 16:40:19 +01:00
foreach ( $fs as $subfield ) {
2007-09-16 18:05:05 +02:00
if ( $this -> columnCount ) {
$className = " column { $this -> columnCount } " ;
if ( ! next ( $fs )) $className .= " lastcolumn " ;
$content .= " <div class= \" { $className } \" > " . $subfield -> FieldHolder () . " </div> " ;
} else if ( $subfield ){
$content .= $subfield -> SmallFieldHolder () . " " ;
}
}
2012-02-16 15:44:36 +01:00
$content .= " </ $tag > " ;
2007-07-19 12:40:28 +02:00
2007-09-16 18:05:05 +02:00
return $content ;
}
2007-07-19 12:40:28 +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
*/
2008-08-12 04:58:48 +02:00
public function collateDataFields ( & $list , $saveableOnly = false ) {
2007-07-19 12:40:28 +02:00
foreach ( $this -> children as $field ) {
if ( is_object ( $field )) {
2008-08-12 04:58:48 +02:00
if ( $field -> isComposite ()) $field -> collateDataFields ( $list , $saveableOnly );
if ( $saveableOnly ) {
$isIncluded = ( $field -> hasData () && ! $field -> isReadonly () && ! $field -> isDisabled ());
} else {
$isIncluded = ( $field -> hasData ());
}
if ( $isIncluded ) {
2011-10-29 06:01:52 +02:00
$name = $field -> getName ();
2007-07-19 12:40:28 +02:00
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 ;
}
}
}
}
}
function setForm ( $form ) {
foreach ( $this -> children as $f ) if ( is_object ( $f )) $f -> setForm ( $form );
parent :: setForm ( $form );
2012-02-17 13:35:26 +01:00
return $this ;
2007-07-19 12:40:28 +02:00
}
function setColumnCount ( $columnCount ) {
$this -> columnCount = $columnCount ;
2012-02-17 13:35:26 +01:00
return $this ;
2007-07-19 12:40:28 +02:00
}
2012-02-17 13:35:26 +01:00
function isComposite () {
return true ;
}
function hasData () {
return false ;
}
2007-07-19 12:40:28 +02:00
2008-01-06 22:55:27 +01:00
public function fieldByName ( $name ) {
return $this -> children -> fieldByName ( $name );
}
2007-07-19 12:40:28 +02:00
/**
* Add a new child field to the end of the set .
*/
public function push ( FormField $field ) {
$this -> children -> push ( $field );
}
2008-11-11 03:35:54 +01:00
/**
2011-05-11 09:51:54 +02:00
* @ uses FieldList -> insertBefore ()
2008-11-11 03:35:54 +01:00
*/
2007-07-19 12:40:28 +02:00
public function insertBefore ( $field , $insertBefore ) {
2008-11-11 03:35:54 +01:00
$ret = $this -> children -> insertBefore ( $field , $insertBefore );
$this -> sequentialSet = null ;
return $ret ;
2007-09-16 18:05:05 +02:00
}
2008-11-11 03:35:54 +01:00
public function insertAfter ( $field , $insertAfter ) {
$ret = $this -> children -> insertAfter ( $field , $insertAfter );
$this -> sequentialSet = null ;
return $ret ;
}
2008-09-12 06:42:24 +02:00
/**
* Remove a field from this CompositeField by Name .
* The field could also be inside a CompositeField .
*
* @ 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 );
2007-07-19 12:40:28 +02:00
}
public function replaceField ( $fieldName , $newField ) {
return $this -> children -> replaceField ( $fieldName , $newField );
}
2008-09-11 08:24:40 +02:00
function rootFieldSet () {
if ( is_object ( $this -> containerFieldSet )) return $this -> containerFieldSet -> rootFieldSet ();
else return $this -> children ;
}
2007-07-19 12:40:28 +02:00
/**
* Return a readonly version of this field . Keeps the composition but returns readonly
* versions of all the children
*/
public function performReadonlyTransformation () {
2011-05-11 09:51:54 +02:00
$newChildren = new FieldList ();
2008-12-04 23:38:32 +01:00
$clone = clone $this ;
foreach ( $clone -> getChildren () as $idx => $child ) {
2007-07-19 12:40:28 +02:00
if ( is_object ( $child )) $child = $child -> transform ( new ReadonlyTransformation ());
$newChildren -> push ( $child , $idx );
}
2008-12-04 23:38:32 +01:00
$clone -> children = $newChildren ;
$clone -> readonly = true ;
return $clone ;
2007-07-19 12:40:28 +02:00
}
/**
* Return a readonly version of this field . Keeps the composition but returns readonly
* versions of all the children
*/
public function performDisabledTransformation ( $trans ) {
2011-05-11 09:51:54 +02:00
$newChildren = new FieldList ();
2008-12-04 23:38:32 +01:00
$clone = clone $this ;
if ( $clone -> getChildren ()) foreach ( $clone -> getChildren () as $idx => $child ) {
2007-07-19 12:40:28 +02:00
if ( is_object ( $child )) {
$child = $child -> transform ( $trans );
}
$newChildren -> push ( $child , $idx );
}
2008-12-04 23:38:32 +01:00
$clone -> children = $newChildren ;
$clone -> readonly = true ;
2007-07-19 12:40:28 +02:00
2008-12-04 23:38:32 +01:00
return $clone ;
2007-07-19 12:40:28 +02:00
}
2007-11-23 02:10:19 +01:00
2007-07-19 12:40:28 +02:00
function IsReadonly () {
return $this -> readonly ;
}
2008-11-11 03:35:54 +01:00
/**
* Find the numerical position of a field within
* the children collection . Doesn ' t work recursively .
*
* @ param string | FormField
* @ return Position in children collection ( first position starts with 0 ) . Returns FALSE if the field can ' t be found .
*/
function fieldPosition ( $field ) {
if ( is_string ( $field )) $field = $this -> fieldByName ( $field );
if ( ! $field ) return false ;
$i = 0 ;
foreach ( $this -> children as $child ) {
2011-10-29 06:01:52 +02:00
if ( $child -> getName () == $field -> getName ()) return $i ;
2008-11-11 03:35:54 +01:00
$i ++ ;
}
return false ;
}
2008-12-04 23:38:32 +01:00
/**
* Transform the named field into a readonly feld .
*
* @ param string | FormField
*/
function makeFieldReadonly ( $field ) {
2011-10-29 06:01:52 +02:00
$fieldName = ( $field instanceof FormField ) ? $field -> getName () : $field ;
2008-12-04 23:38:32 +01: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.
2011-10-29 06:01:52 +02:00
if ( $item -> getName () == $fieldName ) {
2008-12-04 23:38:32 +01:00
$this -> children -> replaceField ( $fieldName , $item -> transform ( new ReadonlyTransformation ()));
// Clear an internal cache
$this -> sequentialSet = null ;
// A true results indicates that the field was foudn
return true ;
}
}
}
return false ;
}
2007-07-19 12:40:28 +02:00
function debug () {
2007-09-14 20:05:21 +02:00
$result = " $this->class ( $this->name ) <ul> " ;
2007-07-19 12:40:28 +02:00
foreach ( $this -> children as $child ) {
$result .= " <li> " . Debug :: text ( $child ) . " </li> " ;
}
$result .= " </ul> " ;
return $result ;
}
2007-08-23 07:47:54 +02:00
function validate ( $validator ){
$valid = true ;
foreach ( $this -> children as $idx => $child ){
2008-10-08 04:00:12 +02:00
$valid = ( $child && $child -> validate ( $validator ) && $valid );
2007-08-23 07:47:54 +02:00
}
return $valid ;
}
2007-07-19 12:40:28 +02:00
}