Webylon 3.1 API Docs
  • Package
  • Class
  • Tree
  • Deprecated
  • Download
Version: current
  • 3.2
  • 3.1

Packages

  • auth
  • Booking
  • cart
    • shipping
    • steppedcheckout
  • Catalog
  • cms
    • assets
    • batchaction
    • batchactions
    • bulkloading
    • comments
    • content
    • core
    • export
    • newsletter
    • publishers
    • reports
    • security
    • tasks
  • Dashboard
  • DataObjectManager
  • event
  • faq
  • forms
    • actions
    • core
    • fields-basic
    • fields-dataless
    • fields-datetime
    • fields-files
    • fields-formatted
    • fields-formattedinput
    • fields-relational
    • fields-structural
    • transformations
    • validators
  • googlesitemaps
  • guestbook
  • installer
  • newsletter
  • None
  • photo
    • gallery
  • PHP
  • polls
  • recaptcha
  • sapphire
    • api
    • bulkloading
    • control
    • core
    • cron
    • dev
    • email
    • fields-formattedinput
    • filesystem
    • formatters
    • forms
    • i18n
    • integration
    • misc
    • model
    • parsers
    • search
    • security
    • tasks
    • testing
    • tools
    • validation
    • view
    • widgets
  • seo
    • open
      • graph
  • sfDateTimePlugin
  • spamprotection
  • stealth
    • captha
  • subsites
  • userform
    • pagetypes
  • userforms
  • webylon
  • widgets

Classes

  • AssetManager
  • CartTableListField_Item
  • ComplexTableField
  • ComplexTableField_Item
  • ComplexTableField_ItemRequest
  • ComplexTableField_Popup
  • CountryDropdownField
  • DataObjectManager
  • DataObjectManager_Item
  • DataObjectManager_ItemRequest
  • DocumentPageFiles_Manager
  • FileDataObjectManager
  • FileDataObjectManager_Item
  • FileDataObjectManager_ItemRequest
  • HasManyComplexTableField
  • HasManyComplexTableField_Item
  • HasManyDataObjectManager
  • HasManyDataObjectManager_Item
  • HasManyFileDataObjectManager
  • HasManyFileDataObjectManager_Item
  • HasOneComplexTableField
  • HasOneComplexTableField_Item
  • HasOneDataObjectManager
  • HasOneDataObjectManager_Item
  • HasOneFileDataObjectManager
  • HasOneFileDataObjectManager_Item
  • ImageAssetManager
  • ImageDataObjectManager
  • ImageDataObjectManager_Item
  • ImageDataObjectManager_ItemRequest
  • LanguageDropdownField
  • ManyManyComplexTableField
  • ManyManyComplexTableField_Item
  • ManyManyDataObjectManager
  • ManyManyDataObjectManager_Item
  • ManyManyFileDataObjectManager
  • ManyManyFileDataObjectManager_Item
  • Mediaweb3DPageFiles_Manager
  • MediawebPageFiles_Manager
  • MediawebPagePhoto_Manager
  • MediawebPageTexture_Manager
  • PhotoAlbumManager
  • ScaffoldingComplexTableField_Popup
  • SubpageListField_Item
  • SubPageListField_ItemRequest
  • SubsiteAgnosticTableListField
  • TableField
  • TableField_Item
  • TableListField
  • TableListField_Item
  • TableListField_ItemRequest
  • TreeDropdownField
  • TreeDropdownField_Readonly
  • TreeMultiselectField
  • TreeMultiselectField_Readonly
  • TreeSelectorField
  1 <?php
  2 /**
  3  * Dropdown-like field that allows you to select an item from a hierachical AJAX-expandable tree
  4  *
  5  * @package forms
  6  * @subpackage fields-relational
  7  */
  8 class TreeDropdownField extends FormField {
  9     
 10     public static $url_handlers = array (
 11         '$Action!/$ID' => '$Action'
 12     );
 13     
 14     public static $allowed_actions = array (
 15         'tree'
 16     );
 17     
 18     /**
 19      * @ignore
 20      */
 21     protected $sourceObject, $keyField, $labelField, $filterCallback, $searchCallback, $baseID = 0;
 22     
 23     /**
 24      * Used by field search to leave only the relevant entries
 25      */
 26     protected $searchIds = null, $searchExpanded = array();
 27     
 28     /**
 29      * CAVEAT: for search to work properly $labelField must be a database field, or you need to setSearchFunction.
 30      *
 31      * @param string $name the field name
 32      * @param string $title the field label
 33      * @param sourceObject The object-type to list in the tree.  Must be a 'hierachy' object.  Alternatively,
 34      * you can set this to an array of key/value pairs, like a dropdown source.  In this case, the field
 35      * will act like show a flat list of tree items, without any hierachy.   This is most useful in
 36      * conjunction with TreeMultiselectField, for presenting a set of checkboxes in a compact view.
 37      * @param string $keyField to field on the source class to save as the field value (default ID).
 38      * @param string $labelField the field name to show as the human-readable value on the tree (default Title).
 39      * @param string $showSearch enable the ability to search the tree by entering the text in the input field.
 40      */
 41     public function __construct($name, $title = null, $sourceObject = 'Group', $keyField = 'ID', $labelField = 'Title', $showSearch = false) {
 42         $this->sourceObject = $sourceObject;
 43         $this->keyField     = $keyField;
 44         $this->labelField   = $labelField;
 45         $this->showSearch   = $showSearch;
 46         
 47         parent::__construct($name, $title);
 48     }
 49     
 50     /**
 51      * Set the ID of the root node of the tree. This defaults to 0 - i.e. displays the whole tree.
 52      *
 53      * @param int $ID
 54      */
 55     public function setTreeBaseID($ID) {
 56         $this->baseID = (int) $ID;
 57     }
 58     
 59     /**
 60      * Set a callback used to filter the values of the tree before displaying to the user.
 61      *
 62      * @param callback $callback
 63      */
 64     public function setFilterFunction($callback) {
 65         if(!is_callable($callback, true)) {
 66             throw new InvalidArgumentException('TreeDropdownField->setFilterCallback(): not passed a valid callback');
 67         }
 68         
 69         $this->filterCallback = $callback;
 70     }
 71     
 72     /**
 73      * Set a callback used to search the hierarchy globally, even before applying the filter.
 74      *
 75      * @param callback $callback
 76      */
 77     public function setSearchFunction($callback) {
 78         if(!is_callable($callback, true)) {
 79             throw new InvalidArgumentException('TreeDropdownField->setSearchFunction(): not passed a valid callback');
 80         }
 81         
 82         $this->searchCallback = $callback;
 83     }
 84     
 85     /**
 86      * @return string
 87      */
 88     public function Field() {
 89         Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
 90         
 91         Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/prototype/prototype.js');
 92         Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/behaviour/behaviour.js');
 93         Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery/jquery.js');
 94         Requirements::javascript(SAPPHIRE_DIR . '/javascript/jquery_improvements.js');
 95         Requirements::javascript(SAPPHIRE_DIR . '/javascript/tree/tree.js');
 96         // needed for errorMessage()
 97         Requirements::javascript(SAPPHIRE_DIR . '/javascript/LeftAndMain.js');
 98         Requirements::javascript(SAPPHIRE_DIR . '/javascript/TreeSelectorField.js');
 99         
100         Requirements::css(SAPPHIRE_DIR . '/javascript/tree/tree.css');
101         Requirements::css(SAPPHIRE_DIR . '/css/TreeDropdownField.css');
102     
103         if($this->Value() && $record = $this->objectForKey($this->Value())) {
104             $title = $record->{$this->labelField};
105         } else {
106             $title = $this->showSearch ? _t('DropdownField.CHOOSEORTYPE','Choose or type') : _t('DropdownField.CHOOSE', '(Choose)', PR_MEDIUM, 'start value of a dropdown');
107         }
108         
109         return $this->createTag (
110             'div',
111             array (
112                 'id'    => "TreeDropdownField_{$this->id()}",
113                 'class' => 'TreeDropdownField single' . ($this->extraClass() ? " {$this->extraClass()}" : ''),
114                 'href' => $this->form ? $this->Link() : "",
115             ),
116             $this->createTag (
117                 'input',
118                 array (
119                     'id'    => $this->id(),
120                     'type'  => 'hidden',
121                     'name'  => $this->name,
122                     'value' => $this->value
123                 )
124             ) . ($this->showSearch ?
125                     $this->createTag(
126                         'input',
127                         array(
128                             'class' => 'items',
129                             'value' => $title,
130                         )
131                     ) :
132                     $this->createTag (
133                         'span',
134                         array (
135                             'class' => 'items'
136                         ),
137                         $title
138                     )                   
139             ) . $this->createTag (
140                 'a',
141                 array (
142                     'href'  => '#',
143                     'title' => _t('TreeDropdownField.OPEN','Open'),
144                     'class' => 'editLink'
145                 ),
146                 '&nbsp;'
147             ) . $this->createTag (
148                     'a',
149                     array (
150                         'href'  => '#',
151                         'title' => _t('TreeDropdownField.CLEAR','Clear'),
152                         'class' => 'cleanLink',                 
153                     ),
154                 '&nbsp;'
155             ) 
156         );
157     }
158     
159     /**
160      * Get the whole tree of a part of the tree via an AJAX request.
161      *
162      * @param SS_HTTPRequest $request
163      * @return string
164      */
165     public function tree(SS_HTTPRequest $request) {
166         $this->search = Convert::Raw2SQL($request->getVar('search'));
167         
168         // Array sourceObject is an explicit list of values - construct a "flat tree"
169         if(is_array($this->sourceObject)) {
170             $output = "<ul class=\"tree\">\n";
171             foreach($this->sourceObject as $k => $v) {
172                 if (!trim($this->search) || (($this->search != "") && (mb_strpos($v, $this->search) !== false))) {
173                     $output .= '<li id="selector-' . $this->name . '-' . $k  . '"><a>' . $v . '</a>';
174                 }
175             }
176             $output .= "</ul>";
177             return $output;
178         }
179         
180         // Regular source specification
181         $isSubTree = false;
182 
183         if($ID = (int) $request->latestparam('ID')) {
184             $obj       = DataObject::get_by_id($this->sourceObject, $ID);
185             $isSubTree = true;
186             
187             if(!$obj) {
188                 throw new Exception (
189                     "TreeDropdownField->tree(): the object #$ID of type $this->sourceObject could not be found"
190                 );
191             }
192         } else {
193             if($this->baseID) {
194                 $obj = DataObject::get_by_id($this->sourceObject, $this->baseID);
195             }
196             
197             if(!$this->baseID || !$obj) $obj = singleton($this->sourceObject);
198         }
199 
200         // pre-process the tree - search needs to operate globally, not locally as marking filter does
201         if ( $this->search != "" )
202             $this->populateIDs();
203         
204         if ($this->filterCallback || $this->sourceObject == 'Folder' || $this->search != "" )
205             $obj->setMarkingFilterFunction(array($this, "filterMarking"));
206         
207         $obj->markPartialTree();
208         
209         // allow to pass values to be selected within the ajax request
210         if( isset($_REQUEST['forceValue']) || $this->value ) {
211             $forceValue = ( isset($_REQUEST['forceValue']) ? $_REQUEST['forceValue'] : $this->value);
212             if(($values = preg_split('/,\s*/', $forceValue)) && count($values)) foreach($values as $value) {
213                 if($this->keyField == 'ID' && !is_numeric($value)) continue;
214                 
215                 $obj->markToExpose($this->objectForKey($value));
216             }
217         }
218         
219         $eval = '"<li id=\"selector-' . $this->Name() . '-{$child->' . $this->keyField . '}\" class=\"$child->class"' .
220                 ' . $child->markingClasses() . "\"><a rel=\"$child->ID\">" . $child->' . $this->labelField . ' . "</a>"';
221         
222         if($isSubTree) {
223             return substr(trim($obj->getChildrenAsUL('', $eval, null, true)), 4, -5);
224         }
225         
226         return $obj->getChildrenAsUL('class="tree"', $eval, null, true);
227     }
228 
229     /**
230      * Marking function for the tree, which combines different filters sensibly. If a filter function has been set,
231      * that will be called. If the source is a folder, automatically filter folder. And if search text is set, filter on that
232      * too. Return true if all applicable conditions are true, false otherwise.
233      * @param $node
234      * @return unknown_type
235      */
236     function filterMarking($node) {
237         if ($this->labelField == 'Title' && !$node->Title) return false;
238         if ($this->filterCallback && !call_user_func($this->filterCallback, $node)) return false;
239         if ($this->sourceObject == "Folder" && $node->ClassName != 'Folder') return false;
240         if (is_a($this->sourceObject, 'File', true) && ($node->Hidden == 1)) return false;
241         if ($this->search != "") {
242             return isset($this->searchIds[$node->ID]) && $this->searchIds[$node->ID] ? true : false;
243         }
244         
245         return true;
246     }
247     
248     /**
249      * Populate $this->searchIds with the IDs of the pages matching the searched parameter and their parents.
250      * Reverse-constructs the tree starting from the leaves. Initially taken from CMSSiteTreeFilter, but modified
251      * with pluggable search function.
252      */
253     protected function populateIDs() {
254         // get all the leaves to be displayed
255         if ( $this->searchCallback )
256             $res = call_user_func($this->searchCallback, $this->sourceObject, $this->labelField, $this->search);
257         else
258             $res = DataObject::get($this->sourceObject, "\"$this->labelField\" LIKE '%$this->search%'");
259 
260         if( $res ) {
261             // iteratively fetch the parents in bulk, until all the leaves can be accessed using the tree control
262             foreach($res as $row) {
263                 if ($row->ParentID) $parents[$row->ParentID] = true;
264                 $this->searchIds[$row->ID] = true;
265             }
266             while (!empty($parents)) {
267                 // Fix query. Inxo. 04.04.11
268                 $res = DB::query('SELECT ParentID, ID FROM ' . $this->sourceObject . ' WHERE ID in ('.implode(',',array_keys($parents)).')');
269                 $parents = array();
270 
271                 foreach($res as $row) {
272                     if ($row['ParentID']) $parents[$row['ParentID']] = true;
273                     $this->searchIds[$row['ID']] = true;
274                     $this->searchExpanded[$row['ID']] = true;
275                 }
276             }
277         }
278     }
279 
280     /**
281      * Get the object where the $keyField is equal to a certain value
282      *
283      * @param string|int $key
284      * @return DataObject
285      */
286     protected function objectForKey($key) {
287         if (is_array($this->sourceObject))
288             return new ArrayData(array('ID'=> $key, 'Title' => $this->sourceObject[$key]));
289             
290         if($this->keyField == 'ID') {
291             return DataObject::get_by_id($this->sourceObject, Convert::raw2sql($key));
292         } else {
293             return DataObject::get_one($this->sourceObject, "\"{$this->keyField}\" = '" . Convert::raw2sql($key) . "'");
294         }
295     }
296 
297     /**
298      * Changes this field to the readonly field.
299      */
300     function performReadonlyTransformation() {
301         return new TreeDropdownField_Readonly($this->name, $this->title, $this->sourceObject, $this->keyField, $this->labelField);
302     }
303 }
304 
305 /**
306  * @package forms
307  * @subpackage fields-relational
308  */
309 class TreeDropdownField_Readonly extends TreeDropdownField {
310     protected $readonly = true;
311     
312     function Field() {
313         $fieldName = $this->labelField;
314         if($this->value) {
315             $keyObj = $this->objectForKey($this->value);
316             $obj = $keyObj ? $keyObj->$fieldName : '';
317         } else {
318             $obj = null;
319         }
320 
321         $source = array(
322             $this->value => $obj
323         );
324 
325         $field = new LookupField($this->name, $this->title, $source);
326         $field->setValue($this->value);
327         $field->setForm($this->form);
328         return $field->Field();
329     }
330 }
331 
[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. -
Webylon 3.1 API Docs API documentation generated by ApiGen 2.8.0