mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
FEATURE: upgrading the search functionality of the TreeDropdownTree with pluggable search function
BUGFIX: the search was only operating on the part of the tree (as returned by markPartialTree), now it searches globally MINOR: renamed 'filter' to 'search' git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/branches/2.4@97031 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
parent
27645dec22
commit
0d314dfc99
@ -18,7 +18,12 @@ class TreeDropdownField extends FormField {
|
|||||||
/**
|
/**
|
||||||
* @ignore
|
* @ignore
|
||||||
*/
|
*/
|
||||||
protected $sourceObject, $keyField, $labelField, $filterCallback, $baseID = 0;
|
protected $sourceObject, $keyField, $labelField, $filterCallback, $searchCallback, $baseID = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by field search to leave only the relevant entries
|
||||||
|
*/
|
||||||
|
protected $searchIds = null, $searchExpanded = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $name the field name
|
* @param string $name the field name
|
||||||
@ -27,11 +32,11 @@ class TreeDropdownField extends FormField {
|
|||||||
* @param string $keyField to field on the source class to save as the field value (default ID).
|
* @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).
|
* @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', $showFilter = false) {
|
public function __construct($name, $title = null, $sourceObject = 'Group', $keyField = 'ID', $labelField = 'Title', $showSearch = false) {
|
||||||
$this->sourceObject = $sourceObject;
|
$this->sourceObject = $sourceObject;
|
||||||
$this->keyField = $keyField;
|
$this->keyField = $keyField;
|
||||||
$this->labelField = $labelField;
|
$this->labelField = $labelField;
|
||||||
$this->showFilter = $showFilter;
|
$this->showSearch = $showSearch;
|
||||||
|
|
||||||
if(!Object::has_extension($this->sourceObject, 'Hierarchy')) {
|
if(!Object::has_extension($this->sourceObject, 'Hierarchy')) {
|
||||||
throw new Exception (
|
throw new Exception (
|
||||||
@ -74,6 +79,19 @@ class TreeDropdownField extends FormField {
|
|||||||
$this->filterCallback = $callback;
|
$this->filterCallback = $callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a callback used to search the hierarchy globally, before even applying the filter.
|
||||||
|
*
|
||||||
|
* @param callback $callback
|
||||||
|
*/
|
||||||
|
public function setSearchFunction($callback) {
|
||||||
|
if(!is_callable($callback, true)) {
|
||||||
|
throw new InvalidArgumentException('TreeDropdownField->setSearchFunction(): not passed a valid callback');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->searchCallback = $callback;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
@ -98,12 +116,12 @@ class TreeDropdownField extends FormField {
|
|||||||
'name' => $this->name,
|
'name' => $this->name,
|
||||||
'value' => $this->value
|
'value' => $this->value
|
||||||
)
|
)
|
||||||
) . ($this->showFilter ?
|
) . ($this->showSearch ?
|
||||||
$this->createTag(
|
$this->createTag(
|
||||||
'input',
|
'input',
|
||||||
array(
|
array(
|
||||||
'class' => 'items',
|
'class' => 'items',
|
||||||
'value' => '(Choose or type filter)'
|
'value' => '(Choose or type search)'
|
||||||
)
|
)
|
||||||
) :
|
) :
|
||||||
$this->createTag (
|
$this->createTag (
|
||||||
@ -134,7 +152,7 @@ class TreeDropdownField extends FormField {
|
|||||||
public function tree(SS_HTTPRequest $request) {
|
public function tree(SS_HTTPRequest $request) {
|
||||||
$isSubTree = false;
|
$isSubTree = false;
|
||||||
|
|
||||||
$this->filter = Convert::Raw2SQL($request->getVar('filter'));
|
$this->search = Convert::Raw2SQL($request->getVar('search'));
|
||||||
|
|
||||||
if($ID = (int) $request->latestparam('ID')) {
|
if($ID = (int) $request->latestparam('ID')) {
|
||||||
$obj = DataObject::get_by_id($this->sourceObject, $ID);
|
$obj = DataObject::get_by_id($this->sourceObject, $ID);
|
||||||
@ -153,7 +171,10 @@ class TreeDropdownField extends FormField {
|
|||||||
if(!$this->baseID || !$obj) $obj = singleton($this->sourceObject);
|
if(!$this->baseID || !$obj) $obj = singleton($this->sourceObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->filterCallback || $this->sourceObject == 'Folder' || $this->filter != "")
|
if ( $this->search != "" )
|
||||||
|
$this->populateIDs();
|
||||||
|
|
||||||
|
if ($this->filterCallback || $this->sourceObject == 'Folder' || $this->search != "" )
|
||||||
$obj->setMarkingFilterFunction(array($this, "filterMarking"));
|
$obj->setMarkingFilterFunction(array($this, "filterMarking"));
|
||||||
|
|
||||||
$obj->markPartialTree();
|
$obj->markPartialTree();
|
||||||
@ -176,7 +197,7 @@ class TreeDropdownField extends FormField {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Marking function for the tree, which combines different filters sensibly. If a filter function has been set,
|
* 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
|
* that will be called. If the source is a folder, automatically filter folder. And if search text is set, filter on that
|
||||||
* too. Return true if all applicable conditions are true, false otherwise.
|
* too. Return true if all applicable conditions are true, false otherwise.
|
||||||
* @param $node
|
* @param $node
|
||||||
* @return unknown_type
|
* @return unknown_type
|
||||||
@ -184,12 +205,42 @@ class TreeDropdownField extends FormField {
|
|||||||
function filterMarking($node) {
|
function filterMarking($node) {
|
||||||
if ($this->filterCallback && !call_user_func($this->filterCallback, $node)) return false;
|
if ($this->filterCallback && !call_user_func($this->filterCallback, $node)) return false;
|
||||||
if ($this->sourceObject == "Folder" && $node->ClassName != 'Folder') return false;
|
if ($this->sourceObject == "Folder" && $node->ClassName != 'Folder') return false;
|
||||||
if ($this->filter != "") {
|
if ($this->search != "") {
|
||||||
$f = $this->labelField;
|
return isset($this->searchIds[$node->ID]) && $this->searchIds[$node->ID] ? true : false;
|
||||||
return (strpos(strtoupper($node->$f), strtoupper($this->filter)) === FALSE) ? false : true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populate $this->searchIds with the IDs of the pages matching the searched parameter and their parents.
|
||||||
|
* Reverse-constructs the tree starting from the leaves. Initially taken from CMSSiteTreeFilter.
|
||||||
|
*/
|
||||||
|
protected function populateIDs() {
|
||||||
|
if ( $this->searchCallback )
|
||||||
|
$res = call_user_func($this->searchCallback, $this->sourceObject, $this->labelField, $this->search);
|
||||||
|
else
|
||||||
|
$res = DataObject::get($this->sourceObject, "$this->labelField LIKE '%$this->search%'");
|
||||||
|
|
||||||
|
if( $res ) {
|
||||||
|
/* And keep a record of parents we don't need to get parents of themselves, as well as IDs to mark */
|
||||||
|
foreach($res as $row) {
|
||||||
|
if ($row->ParentID) $parents[$row->ParentID] = true;
|
||||||
|
$this->searchIds[$row->ID] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!empty($parents)) {
|
||||||
|
$res = DB::query('SELECT "ParentID", "ID" FROM '.$this->sourceObject.' WHERE "ID" in ('.implode(',',array_keys($parents)).')');
|
||||||
|
$parents = array();
|
||||||
|
|
||||||
|
foreach($res as $row) {
|
||||||
|
if ($row['ParentID']) $parents[$row['ParentID']] = true;
|
||||||
|
$this->searchIds[$row['ID']] = true;
|
||||||
|
$this->searchExpanded[$row['ID']] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the object where the $keyField is equal to a certain value
|
* Get the object where the $keyField is equal to a certain value
|
||||||
|
@ -7,15 +7,15 @@ TreeDropdownField.prototype = {
|
|||||||
// Hook up all the fieldy bits
|
// Hook up all the fieldy bits
|
||||||
this.editLink = this.getElementsByTagName('a')[0];
|
this.editLink = this.getElementsByTagName('a')[0];
|
||||||
if (this.getElementsByTagName('span').length > 0) {
|
if (this.getElementsByTagName('span').length > 0) {
|
||||||
// no filter, humanItems is a span
|
// no search, humanItems is a span
|
||||||
this.humanItems = this.getElementsByTagName('span')[0];
|
this.humanItems = this.getElementsByTagName('span')[0];
|
||||||
this.inputTag = this.getElementsByTagName('input')[0];
|
this.inputTag = this.getElementsByTagName('input')[0];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// filter is present, humanItems is an input
|
// search is present, humanItems is an input
|
||||||
this.inputTag = this.getElementsByTagName('input')[0];
|
this.inputTag = this.getElementsByTagName('input')[0];
|
||||||
this.humanItems = this.getElementsByTagName('input')[1];
|
this.humanItems = this.getElementsByTagName('input')[1];
|
||||||
this.humanItems.onkeyup = this.filter_onkeyup;
|
this.humanItems.onkeyup = this.search_onkeyup;
|
||||||
}
|
}
|
||||||
this.editLink.treeDropdownField = this;
|
this.editLink.treeDropdownField = this;
|
||||||
this.humanItems.treeDropdownField = this;
|
this.humanItems.treeDropdownField = this;
|
||||||
@ -115,7 +115,7 @@ TreeDropdownField.prototype = {
|
|||||||
saveCurrentState: function() {
|
saveCurrentState: function() {
|
||||||
this.origHumanText = this.getHumanText();
|
this.origHumanText = this.getHumanText();
|
||||||
this.defaultCleared = false;
|
this.defaultCleared = false;
|
||||||
this.filtered = false;
|
this.searched = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
restoreOriginalState: function() {
|
restoreOriginalState: function() {
|
||||||
@ -176,7 +176,7 @@ TreeDropdownField.prototype = {
|
|||||||
var ajaxURL = this.helperURLBase() + 'tree/';
|
var ajaxURL = this.helperURLBase() + 'tree/';
|
||||||
ajaxURL += $('SecurityID') ? '&SecurityID=' + $('SecurityID').value : '';
|
ajaxURL += $('SecurityID') ? '&SecurityID=' + $('SecurityID').value : '';
|
||||||
if($('Form_EditForm_Locale')) ajaxURL += "&locale=" + $('Form_EditForm_Locale').value;
|
if($('Form_EditForm_Locale')) ajaxURL += "&locale=" + $('Form_EditForm_Locale').value;
|
||||||
if (this.filter() != null) ajaxURL += "&filter=" + this.filter();
|
if (this.search() != null) ajaxURL += "&search=" + this.search();
|
||||||
new Ajax.Request(ajaxURL, {
|
new Ajax.Request(ajaxURL, {
|
||||||
method : 'get',
|
method : 'get',
|
||||||
onSuccess : after,
|
onSuccess : after,
|
||||||
@ -184,8 +184,8 @@ TreeDropdownField.prototype = {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
filter: function() {
|
search: function() {
|
||||||
if (this.humanItems.nodeName != 'INPUT' || !this.filtered) return null;
|
if (this.humanItems.nodeName != 'INPUT' || !this.searched) return null;
|
||||||
return this.humanItems.value;
|
return this.humanItems.value;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -229,8 +229,8 @@ TreeDropdownField.prototype = {
|
|||||||
var ajaxURL = this.options.dropdownField.helperURLBase() + 'tree/' + this.getIdx();
|
var ajaxURL = this.options.dropdownField.helperURLBase() + 'tree/' + this.getIdx();
|
||||||
ajaxURL += $('SecurityID') ? '&SecurityID=' + $('SecurityID').value : '';
|
ajaxURL += $('SecurityID') ? '&SecurityID=' + $('SecurityID').value : '';
|
||||||
if($('Form_EditForm_Locale')) ajaxURL += "&locale=" + $('Form_EditForm_Locale').value;
|
if($('Form_EditForm_Locale')) ajaxURL += "&locale=" + $('Form_EditForm_Locale').value;
|
||||||
// ajaxExpansion is called in context of TreeNode, not Tree, so filter() doesn't exist.
|
// ajaxExpansion is called in context of TreeNode, not Tree, so search() doesn't exist.
|
||||||
if (this.filter && this.filter() != null) ajaxURL += "&filter=" + this.filter();
|
if (this.search && this.search() != null) ajaxURL += "&search=" + this.search();
|
||||||
|
|
||||||
new Ajax.Request(ajaxURL, {
|
new Ajax.Request(ajaxURL, {
|
||||||
onSuccess : this.installSubtree.bind(this),
|
onSuccess : this.installSubtree.bind(this),
|
||||||
@ -260,7 +260,7 @@ TreeDropdownField.prototype = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateTreeLabel: function() {
|
updateTreeLabel: function() {
|
||||||
if (this.humanItems.nodeName == 'INPUT') return; // don't update the filter
|
if ( this.searched || (this.humanItems.nodeName == 'INPUT' && !this.inputTag.value) ) return; // don't update the search
|
||||||
var treeNode;
|
var treeNode;
|
||||||
if(treeNode = $('selector-' + this.getName() + '-' + this.inputTag.value)) {
|
if(treeNode = $('selector-' + this.getName() + '-' + this.inputTag.value)) {
|
||||||
this.setHumanText(treeNode.getTitle());
|
this.setHumanText(treeNode.getTitle());
|
||||||
@ -299,16 +299,20 @@ TreeDropdownField.prototype = {
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
filter_onkeyup: function(e) {
|
search_onkeyup: function(e) {
|
||||||
if(typeof window.event!="undefined") e=window.event; //code for IE
|
if(typeof window.event!="undefined") e=window.event; //code for IE
|
||||||
if (e.keyCode == 27) { // esc, cancel the selection and hide the tree.
|
if (e.keyCode == 27) { // esc, cancel the selection and hide the tree.
|
||||||
this.treeDropdownField.restoreOriginalState();
|
this.treeDropdownField.restoreOriginalState();
|
||||||
this.treeDropdownField.hideTree();
|
this.treeDropdownField.hideTree();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.treeDropdownField.filtered = true;
|
var that = this;
|
||||||
this.treeDropdownField.deleteTreeNode();
|
clearTimeout(this.timeout);
|
||||||
this.treeDropdownField.showTree();
|
this.timeout = setTimeout(function() {
|
||||||
|
that.treeDropdownField.searched = true;
|
||||||
|
that.treeDropdownField.deleteTreeNode();
|
||||||
|
that.treeDropdownField.showTree();
|
||||||
|
}, 750);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user