From 69818fad173ccab17cfbf211f9b7a8300c4e164a Mon Sep 17 00:00:00 2001 From: Mark Stephens Date: Sun, 29 Nov 2009 23:29:31 +0000 Subject: [PATCH] ENHANCEMENT: Change to TreeDropdownField, giving it filtering behaviour as described in ticket http://open.silverstripe.org/ticket/3007 . Its disabled by default for legacy compatibility, but enabled for HtmlEditorField so that link editor is filterable for local links, via an extra boolean parameter on TreeDowndownField. git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@93932 467b73ca-7a2a-4603-9d3b-597d59a354a9 --- forms/HtmlEditorField.php | 4 +- forms/TreeDropdownField.php | 54 ++++++++++++----- javascript/TreeSelectorField.js | 101 ++++++++++++++++++++++++++++---- 3 files changed, 132 insertions(+), 27 deletions(-) diff --git a/forms/HtmlEditorField.php b/forms/HtmlEditorField.php index 0d297b89e..299895770 100755 --- a/forms/HtmlEditorField.php +++ b/forms/HtmlEditorField.php @@ -225,10 +225,10 @@ class HtmlEditorField_Toolbar extends RequestHandler { 'file' => _t('HtmlEditorField.LINKFILE', 'Download a file'), ) ), - new TreeDropdownField('internal', _t('HtmlEditorField.PAGE', "Page"), 'SiteTree', 'URLSegment', 'MenuTitle'), + new TreeDropdownField('internal', _t('HtmlEditorField.PAGE', "Page"), 'SiteTree', 'URLSegment', 'MenuTitle', true), new TextField('external', _t('HtmlEditorField.URL', 'URL'), 'http://'), new EmailField('email', _t('HtmlEditorField.EMAIL', 'Email address')), - new TreeDropdownField('file', _t('HtmlEditorField.FILE', 'File'), 'File', 'Filename'), + new TreeDropdownField('file', _t('HtmlEditorField.FILE', 'File'), 'File', 'Filename', 'Title', true), new TextField('Anchor', _t('HtmlEditorField.ANCHORVALUE', 'Anchor')), new TextField('LinkText', _t('HtmlEditorField.LINKTEXT', 'Link text')), new TextField('Description', _t('HtmlEditorField.LINKDESCR', 'Link description')), diff --git a/forms/TreeDropdownField.php b/forms/TreeDropdownField.php index 2dd6d4784..bcf65b0f3 100755 --- a/forms/TreeDropdownField.php +++ b/forms/TreeDropdownField.php @@ -27,10 +27,11 @@ class TreeDropdownField extends FormField { * @param string $keyField to field on the source class to save as the field value (default ID). * @param string $labelField the field name to show as the human-readable value on the tree (default Title). */ - public function __construct($name, $title = null, $sourceObject = 'Group', $keyField = 'ID', $labelField = 'Title') { + public function __construct($name, $title = null, $sourceObject = 'Group', $keyField = 'ID', $labelField = 'Title', $showFilter = false) { $this->sourceObject = $sourceObject; $this->keyField = $keyField; $this->labelField = $labelField; + $this->showFilter = $showFilter; if(!Object::has_extension($this->sourceObject, 'Hierarchy')) { throw new Exception ( @@ -95,12 +96,21 @@ class TreeDropdownField extends FormField { 'name' => $this->name, 'value' => $this->value ) - ) . $this->createTag ( - 'span', - array ( - 'class' => 'items' - ), - $title + ) . ($this->showFilter ? + $this->createTag( + 'input', + array( + 'class' => 'items', + 'value' => '(Choose or type filter)' + ) + ) : + $this->createTag ( + 'span', + array ( + 'class' => 'items' + ), + $title + ) ) . $this->createTag ( 'a', array ( @@ -121,7 +131,9 @@ class TreeDropdownField extends FormField { */ public function tree(SS_HTTPRequest $request) { $isSubTree = false; - + + $this->filter = Convert::Raw2SQL($request->getVar('filter')); + if($ID = (int) $request->latestparam('ID')) { $obj = DataObject::get_by_id($this->sourceObject, $ID); $isSubTree = true; @@ -139,11 +151,8 @@ class TreeDropdownField extends FormField { if(!$this->baseID || !$obj) $obj = singleton($this->sourceObject); } - if($this->filterCallback) { - $obj->setMarkingFilterFunction($this->filterCallback); - } elseif($this->sourceObject == 'Folder') { - $obj->setMarkingFilter('ClassName', 'Folder'); - } + if ($this->filterCallback || $this->sourceObject == 'Folder' || $this->filter != "") + $obj->setMarkingFilterFunction(array($this, "filterMarking")); $obj->markPartialTree(); @@ -162,7 +171,24 @@ class TreeDropdownField extends FormField { return $obj->getChildrenAsUL('class="tree"', $eval, null, true); } - + + /** + * Marking function for the tree, which combines different filters sensibly. If a filter function has been set, + * that will be called. If the source is a folder, automatically filter folder. And if filter text is set, filter on that + * too. Return true if all applicable conditions are true, false otherwise. + * @param $node + * @return unknown_type + */ + function filterMarking($node) { + if ($this->filterCallback && !call_user_func($this->filterCallback, $node)) return false; + if ($this->sourceObject == "Folder" && $node->ClassName != 'Folder') return false; + if ($this->filter != "") { + $f = $this->labelField; + return (strpos(strtoupper($node->$f), strtoupper($this->filter)) === FALSE) ? false : true; + } + return true; + } + /** * Get the object where the $keyField is equal to a certain value * diff --git a/javascript/TreeSelectorField.js b/javascript/TreeSelectorField.js index cec8109fe..80cf6a06f 100755 --- a/javascript/TreeSelectorField.js +++ b/javascript/TreeSelectorField.js @@ -6,14 +6,23 @@ TreeDropdownField.prototype = { initialize: function() { // Hook up all the fieldy bits this.editLink = this.getElementsByTagName('a')[0]; - this.humanItems = this.getElementsByTagName('span')[0]; - this.inputTag = this.getElementsByTagName('input')[0]; + if (this.getElementsByTagName('span').length > 0) { + // no filter, humanItems is a span + this.humanItems = this.getElementsByTagName('span')[0]; + this.inputTag = this.getElementsByTagName('input')[0]; + } + else { + // filter is present, humanItems is an input + this.inputTag = this.getElementsByTagName('input')[0]; + this.humanItems = this.getElementsByTagName('input')[1]; + this.humanItems.onkeyup = this.filter_onkeyup; + } this.editLink.treeDropdownField = this; this.humanItems.treeDropdownField = this; this.inputTag.treeDropdownField = this; this.editLink.onclick = this.edit_click; - this.humanItems.onclick = this.edit_click; + this.humanItems.onclick = this.human_click; this.inputTag.setValue = this.setValue.bind(this); }, @@ -75,8 +84,16 @@ TreeDropdownField.prototype = { this.appendChild(this.itemTree); } }, - + + deleteTreeNode: function() { + if (!this.itemTree) return; + var parent = this.itemTree.parentNode; + parent.removeChild(this.itemTree); + this.itemTree = null; + }, + showTree: function () { + if (!this.treeShown) this.saveCurrentState(); this.treeShown = true; if(this.itemTree) { @@ -95,6 +112,16 @@ TreeDropdownField.prototype = { } }, + saveCurrentState: function() { + this.origHumanText = this.getHumanText(); + this.defaultCleared = false; + this.filtered = false; + }, + + restoreOriginalState: function() { + this.setHumanText(this.origHumanText); + }, + /** * If this control is inside an iframe, stretch the iframe out to fit the tree. */ @@ -149,13 +176,19 @@ TreeDropdownField.prototype = { var ajaxURL = this.helperURLBase() + 'tree/'; ajaxURL += $('SecurityID') ? '&SecurityID=' + $('SecurityID').value : ''; if($('Form_EditForm_Locale')) ajaxURL += "&locale=" + $('Form_EditForm_Locale').value; + if (this.filter() != null) ajaxURL += "&filter=" + this.filter(); new Ajax.Request(ajaxURL, { method : 'get', onSuccess : after, onFailure : function(response) { errorMessage("Error getting data", response); } }) }, - + + filter: function() { + if (this.humanItems.nodeName != 'INPUT' || !this.filtered) return null; + return this.humanItems.value; + }, + /** * Called once the tree has been delivered from ajax */ @@ -196,6 +229,7 @@ TreeDropdownField.prototype = { var ajaxURL = this.options.dropdownField.helperURLBase() + 'tree/' + this.getIdx(); ajaxURL += $('SecurityID') ? '&SecurityID=' + $('SecurityID').value : ''; if($('Form_EditForm_Locale')) ajaxURL += "&locale=" + $('Form_EditForm_Locale').value; + if (this.filter() != null) ajaxURL += "&filter=" + this.filter(); new Ajax.Request(ajaxURL, { onSuccess : this.installSubtree.bind(this), @@ -225,21 +259,35 @@ TreeDropdownField.prototype = { } } }, + updateTreeLabel: function() { + if (this.humanItems.nodeName == 'INPUT') return; // don't update the filter var treeNode; if(treeNode = $('selector-' + this.getName() + '-' + this.inputTag.value)) { - this.humanItems.innerHTML = treeNode.getTitle(); + this.setHumanText(treeNode.getTitle()); if(treeNode.tree.selected && treeNode.tree.selected.removeNodeClass) treeNode.tree.selected.removeNodeClass('current'); treeNode.addNodeClass('current'); this.tree.selected = treeNode; } else { - this.humanItems.innerHTML = this.inputTag.value ? this.inputTag.value : '(Choose)'; + this.setHumanText(this.inputTag.value ? this.inputTag.value : '(Choose)'); } }, + + getHumanText: function() { + return this.humanItems.nodeName == 'INPUT' ? this.humanItems.value : this.humanItems.innerHTML; + }, + + setHumanText: function (s) { + if (this.humanItems.nodeName == 'INPUT') + this.humanItems.value = s; + else + this.humanItems.innerHTML = s; + }, + setValueFromTree: function(treeID, title) { - this.humanItems.innerHTML = title; + this.setHumanText(title); this.inputTag.value = treeID.replace('selector-' + this.getName() + '-',''); jQuery(this).trigger('ss.TreeDropdownField.change', {val: this.inputTag.value}); this.notify('Change', this.inputTag.value); @@ -248,9 +296,40 @@ TreeDropdownField.prototype = { }, edit_click : function() { + if (this.treeDropdownField.treeShown) this.treeDropdownField.restoreOriginalState(); this.treeDropdownField.toggleTree(); return false; }, + + filter_onkeyup: function(e) { + if(typeof window.event!="undefined") e=window.event; //code for IE + if (e.keyCode == 27) { // esc, cancel the selection and hide the tree. + this.treeDropdownField.restoreOriginalState(); + this.treeDropdownField.hideTree(); + } + else { + this.treeDropdownField.filtered = true; + this.treeDropdownField.deleteTreeNode(); + this.treeDropdownField.showTree(); + } + }, + + human_click: function() { + if (this.treeDropdownField.humanItems.nodeName != 'INPUT') { + if (this.treeDropdownField.treeShown) this.treeDropdownField.restoreOriginalState(); + this.treeDropdownField.toggleTree(); + return false; + } + + if (!this.treeDropdownField.treeShown) this.treeDropdownField.toggleTree(); + if (!this.treeDropdownField.defaultCleared) { + this.treeDropdownField.defaultCleared = true; + this.treeDropdownField.setHumanText(''); + } + + return false; + }, + tree_click : function() { this.options.dropdownField.setValueFromTree(this.id, this.getTitle()); @@ -303,7 +382,7 @@ TreeMultiselectField.prototype = { } this.inputTag.value = internalVal; - this.humanItems.innerHTML = humanVal; + this.setHumanText(humanVal); }, updateTreeLabel: function() { @@ -319,9 +398,9 @@ TreeMultiselectField.prototype = { innerHTML += selectedItems[i]; } } - this.humanItems.innerHTML = innerHTML; + this.setHumanText(innerHTML); } else { - this.humanItems.innerHTML = '(Choose)'; + this.setHumanText('(Choose)'); } }