1 <?php
2 3 4 5 6 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 20
21 protected $sourceObject, $keyField, $labelField, $filterCallback, $searchCallback, $baseID = 0;
22
23 24 25
26 protected $searchIds = null, $searchExpanded = array();
27
28 29 30 31 32 33 34 35 36 37 38 39 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 52 53 54
55 public function setTreeBaseID($ID) {
56 $this->baseID = (int) $ID;
57 }
58
59 60 61 62 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 74 75 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 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
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 ' '
147 ) . $this->createTag (
148 'a',
149 array (
150 'href' => '#',
151 'title' => _t('TreeDropdownField.CLEAR','Clear'),
152 'class' => 'cleanLink',
153 ),
154 ' '
155 )
156 );
157 }
158
159 160 161 162 163 164
165 public function tree(SS_HTTPRequest $request) {
166 $this->search = Convert::Raw2SQL($request->getVar('search'));
167
168
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
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
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
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 231 232 233 234 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 250 251 252
253 protected function populateIDs() {
254
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
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
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 282 283 284 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 299
300 function performReadonlyTransformation() {
301 return new TreeDropdownField_Readonly($this->name, $this->title, $this->sourceObject, $this->keyField, $this->labelField);
302 }
303 }
304
305 306 307 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.
-