diff --git a/config/vufind/config.ini b/config/vufind/config.ini index 32f16f73d2ad369dcd880b8f197f6dc446c0d331..ed924d1eb1c59b5b9d74f7ff865e8b8554435131 100644 --- a/config/vufind/config.ini +++ b/config/vufind/config.ini @@ -776,4 +776,11 @@ HMACkey = mySuperSecretValue ; field of the Solr index. driver = Default ; Should we display hierarchy trees? (default = false) -;showTree = true \ No newline at end of file +;showTree = true +; "Search within trees" can be disabled here if set to "false" (default = true) +search = true +; You can limit the number of search results highlighted when searching the tree; +; a limit is recommended if you have large trees, as otherwise large numbers of +; results can cause performance problems. If treeSearchLimit is -1 or not set, +; results will be unlimited. +treeSearchLimit = 100 \ No newline at end of file diff --git a/languages/en-gb.ini b/languages/en-gb.ini index eabcbe868fdf19c033c35f6c1e12f4f7518157f2..56bf29e89ed69d6376b72ae4998b491036751b0f 100644 --- a/languages/en-gb.ini +++ b/languages/en-gb.ini @@ -716,6 +716,7 @@ too_many_new_items = "There are too many new items to display in a single list. too_many_reserves = "There are too many course reserves to display in a single list. Try limiting your search." top_facet_additional_prefix = "Additional " top_facet_suffix = "... within your search." +tree_search_limit_reached_html = "Your search returned too many results to display in the tree. Showing only the first <b>%%limit%%</b> items. For a full search click <a id="fullSearchLink" href="%%url%%" target="_blank">here.</a>" upgrade_description = "If you are upgrading a previous VuFind version, you can load your old settings with this tool." view already selected = "view already selected" vudl_tab_docs = "Docs" diff --git a/languages/en.ini b/languages/en.ini index 8a1d5e043c0f12808c360b677c11063377b7b14b..7df08689af3f68219aadac11fc7109bf6355d8cb 100644 --- a/languages/en.ini +++ b/languages/en.ini @@ -716,6 +716,7 @@ too_many_new_items = "There are too many new items to display in a single list. too_many_reserves = "There are too many course reserves to display in a single list. Try limiting your search." top_facet_additional_prefix = "Additional " top_facet_suffix = "... within your search." +tree_search_limit_reached_html = "Your search returned too many results to display in the tree. Showing only the first <b>%%limit%%</b> items. For a full search click <a id="fullSearchLink" href="%%url%%" target="_blank">here.</a>" upgrade_description = "If you are upgrading a previous VuFind version, you can load your old settings with this tool." view already selected = "view already selected" vudl_tab_docs = "Docs" diff --git a/module/VuFind/src/VuFind/Controller/HierarchyController.php b/module/VuFind/src/VuFind/Controller/HierarchyController.php index 44e4c24d9b93dd35b62297c0b41c73c0994072fc..5b60453f1573476f3a1e98a3ef83a61f24feeb1c 100644 --- a/module/VuFind/src/VuFind/Controller/HierarchyController.php +++ b/module/VuFind/src/VuFind/Controller/HierarchyController.php @@ -45,7 +45,7 @@ class HierarchyController extends AbstractBase * * @return \Zend\Http\Response */ - public function output($xml) + protected function output($xml) { $response = $this->getResponse(); $headers = $response->getHeaders(); @@ -54,6 +54,59 @@ class HierarchyController extends AbstractBase return $response; } + /** + * Output JSON + * + * @param string $json A JSON string + * + * @return \Zend\Http\Response + */ + protected function outputJSON($json) + { + $response = $this->getResponse(); + $headers = $response->getHeaders(); + $headers->addHeaderLine('Content-type', 'application/json'); + $response->setContent($json); + return $response; + } + + /** + * Search the tree and echo a json result of items that + * matched the keywords. + * + * @return void + * @access public + */ + public function searchtreeAction() + { + $config = \VuFind\Config\Reader::getConfig(); + $limit = isset($config->Hierarchy->treeSearchLimit) + ? $config->Hierarchy->treeSearchLimit : -1; + $resultIDs = array(); + $hierarchyID = $this->params()->fromQuery('hierarchyID'); + $lookfor = $this->params()->fromQuery('lookfor', ''); + $searchType = $this->params()->fromQuery('type', 'AllFields'); + + $results = $this->getSearchManager()->setSearchClassId('Solr')->getResults(); + $results->getParams()->setBasicSearch($lookfor, $searchType); + $results->getParams()->addFilter('hierarchy_top_id:' . $hierarchyID); + $facets = $results->getFullFieldFacets(array('id'), false, $limit+1); + + $callback = function ($data) { + return $data['value']; + }; + $resultIDs = isset($facets['id']['data']['list']) + ? array_map($callback, $facets['id']['data']['list']) : array(); + + $limitReached = ($limit > 0 && count($resultIDs) > $limit); + + $returnArray = array( + "limitReached" => $limitReached, + "results" => array_slice($resultIDs, 0, $limit) + ); + return $this->outputJSON(json_encode($returnArray)); + } + /** * Gets a Hierarchy Tree * diff --git a/module/VuFind/src/VuFind/RecordTab/HierarchyTree.php b/module/VuFind/src/VuFind/RecordTab/HierarchyTree.php index 5eca4a815ce990f52f0d788c41646f93d25836ed..5ab1debb8eb02b4d3456d210e00ff94a8d4a1365 100644 --- a/module/VuFind/src/VuFind/RecordTab/HierarchyTree.php +++ b/module/VuFind/src/VuFind/RecordTab/HierarchyTree.php @@ -46,6 +46,26 @@ class HierarchyTree extends AbstractBase */ protected $treeList = null; + /** + * Configuration + * + * @var \Zend\Config\Config + */ + protected $config = null; + + /** + * Get the VuFind configuration. + * + * @return \Zend\Config\Config + */ + protected function getConfig() + { + if (null === $this->config) { + $this->config = ConfigReader::getConfig(); + } + return $this->config; + } + /** * Get the on-screen description for this tab. * @@ -155,4 +175,27 @@ class HierarchyTree extends AbstractBase } return ''; } + + /** + * Is tree searching active? + * + * @return bool + */ + public function searchActive() + { + $config = $this->getConfig(); + return (!isset($config->Hierarchy->search) || $config->Hierarchy->search); + } + + /** + * Get the tree search result limit. + * + * @return int + */ + public function getSearchLimit() + { + $config = $this->getConfig(); + return isset($config->Hierarchy->treeSearchLimit) + ? $config->Hierarchy->treeSearchLimit : -1; + } } \ No newline at end of file diff --git a/themes/blueprint/css/styles.css b/themes/blueprint/css/styles.css index 8d57697478a5bfcca447a9c04226e252a2df1213..601dbb855e805240f07bf013f806454c3342b569 100644 --- a/themes/blueprint/css/styles.css +++ b/themes/blueprint/css/styles.css @@ -1751,6 +1751,50 @@ div#closeContextHelp:active { overflow-y: auto; } +#treeSearch { + display:none; + float: right; + width: 100%; + border: 1px solid #DCDCDC; +} + +#treeSearch #treeSearchText{ + display:block; + float:right; + margin: 1px 0px 0px 0px; +} + +#treeSearch #search { + display:block; + float:right; + margin-bottom: 0px; +} + +#treeSearch #treeSearchType { + display:block; + float:right; + margin:2px 4px 0px 4px; +} + +#treeSearch #treeSearchNoResults { + display:none; + float:left; + color: #AF1B0A; + margin:3px 0px 0px 5px; +} + +#treeSearch #treeSearchLoadingImg { + display:none; + float:right; + margin:3px 4px 4px 5px; +} + +#treeSearchLimitReached { + display:none; + color: #AF1B0A; + margin:3px 0px 0px 5px; +} + #modalDialog #hierarchyTree { max-height: 100%; min-height: 100%; diff --git a/themes/blueprint/images/loading.gif b/themes/blueprint/images/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..471c1a4f93f2cabf0b3a85c3ff8e0a8aadefc548 Binary files /dev/null and b/themes/blueprint/images/loading.gif differ diff --git a/themes/blueprint/js/hierarchyTree_JSTree.js b/themes/blueprint/js/hierarchyTree_JSTree.js index c5cf88e1d28e385028fa327a61a8f1913cec34cc..059d3de3e31edd1ebe0cbb24833c27cc5cb89495 100644 --- a/themes/blueprint/js/hierarchyTree_JSTree.js +++ b/themes/blueprint/js/hierarchyTree_JSTree.js @@ -1,6 +1,7 @@ var hierarchyID; -$(document).ready(function() { - +var baseTreeSearchFullURL; +$(document).ready(function() +{ hierarchyID = $("#hierarchyTree").find(".hiddenHierarchyId")[0].value; var recordID = $("#hierarchyTree").find(".hiddenRecordId")[0].value; var scroller = hierarchySettings.lightboxMode ? '#modalDialog' : '#hierarchyTree'; @@ -9,7 +10,8 @@ $(document).ready(function() { if (!hierarchySettings.fullHierarchy) { // Set Up Partial Hierarchy View Toggle $('#hierarchyTree').parent().prepend('<a href="#" id="toggleTree" class="closed">' + vufindString.showTree + '</a>'); - $('#toggleTree').click(function(e) { + $('#toggleTree').click(function(e) + { e.preventDefault(); $(this).toggleClass("open"); $(this).hasClass("open") ? scroll(scroller, "show") : scroll(scroller, "hide"); @@ -18,15 +20,18 @@ $(document).ready(function() { } $("#hierarchyTree") - .bind("loaded.jstree", function (event, data) { + .bind("loaded.jstree", function (event, data) + { var idList = $('#hierarchyTree .JSTreeID'); - $(idList).each(function() { + $(idList).each(function() + { var id = $.trim($(this).text()); $(this).before('<input type="hidden" class="jsTreeID '+context+ '" value="'+id+'" />'); $(this).remove(); }); - $("#hierarchyTree a").click(function(e) { + $("#hierarchyTree a").click(function(e) + { e.preventDefault(); if (context == "Record") { window.location = $(this).attr("href"); @@ -60,7 +65,8 @@ $(document).ready(function() { "xml_data" : { "ajax" : { "url" : path + '/Hierarchy/GetTree?' + $.param({'hierarchyID': hierarchyID, 'id': recordID, 'context': context, mode: "Tree"}), - success: function(data) { + success: function(data) + { // Necessary as data is a string var dataAsXML = $.parseXML(data); if(dataAsXML) { @@ -75,7 +81,8 @@ $(document).ready(function() { showTreeError("Unable to Parse XML"); } }, - failure: function() { + failure: function() + { showTreeError("Unable to Load Tree"); } }, @@ -85,16 +92,29 @@ $(document).ready(function() { "themes" : { "url": path + '/themes/blueprint/js/jsTree/themes/vufind/style.css' } - }).bind("open_node.jstree close_node.jstree", function (e, data) { + }).bind("open_node.jstree close_node.jstree", function (e, data) + { $(data.args[0]).find("li").show(); }); + + $('#treeSearch').show(); + $('#treeSearchText').bind('keypress', function(e) + { + var code = (e.keyCode ? e.keyCode : e.which); + if(code == 13) { + // Enter keycode should call the search code + doTreeSearch(); + } + }); }); -function showTreeError(msg) { +function showTreeError(msg) +{ $("#hierarchyTreeHolder").html('<p class="error">' + msg + '</p>'); } -function scroll(scroller, mode) { +function scroll(scroller, mode) +{ // Get the currently cicked item var jsTreeNode = $(".jstree-clicked").parent('li'); // Toggle display of closed nodes @@ -114,7 +134,8 @@ function scroll(scroller, mode) { } } -function hideFullHierarchy(jsTreeNode) { +function hideFullHierarchy(jsTreeNode) +{ // Hide all nodes $('#hierarchyTree li').hide(); // Show the nodes on the current path @@ -122,3 +143,77 @@ function hideFullHierarchy(jsTreeNode) { // Show the nodes below the current path $(jsTreeNode).find("li").show(); } + +function changeNoResultLabel(display) +{ + display ? $("#treeSearchNoResults").show() : $("#treeSearchNoResults").hide(); +} + +function changeLimitReachedLabel(display) +{ + display ? $("#treeSearchLimitReached").show() : $("#treeSearchLimitReached").hide(); +} + +function doTreeSearch() +{ + var keyword = $("#treeSearchText").val(); + if (keyword == ""){ + changeNoResultLabel(true); + return; + } + var searchType = $("#treeSearchType").val(); + + $("#treeSearchLoadingImg").show(); + $.getJSON(path + '/Hierarchy/SearchTree?' + $.param({'lookfor': keyword, 'hierarchyID': hierarchyID, 'type': searchType}), function(results) + { + if (results["limitReached"] == true) { + if(typeof(baseTreeSearchFullURL) == "undefined" || baseTreeSearchFullURL == null){ + baseTreeSearchFullURL = $("#fullSearchLink").attr("href"); + } + $("#fullSearchLink").attr("href", baseTreeSearchFullURL + "?lookfor="+ keyword + "&filter[]=hierarchy_top_id:\"" + hierarchyID + "\""); + changeLimitReachedLabel(true); + } else { + changeLimitReachedLabel(false); + } + + if (results["results"].length >= 1) { + $("#hierarchyTree .jstree-search").removeClass("jstree-search"); + $("#hierarchyTree").jstree("close_all", hierarchyID); + changeNoResultLabel(false); + } else { + $("#hierarchyTree .jstree-search").removeClass("jstree-search"); + changeNoResultLabel(true); + } + + $.each(results["results"], function(key, val) + { + var jsTreeNode = $(".jsTreeID:input[value="+val+"]").parent(); + if (jsTreeNode.hasClass("jstree-closed")) { + jsTreeNode.removeClass("jstree-closed").addClass("jstree-open"); + } + jsTreeNode.show().children('a:first').addClass("jstree-search"); + var parents = $(jsTreeNode).parents(); + parents.each(function() { + if ($(this).hasClass("jstree-closed")) { + $(this).removeClass("jstree-closed").addClass("jstree-open"); + } + $(this).show(); + }); + }); + $("#treeSearchLoadingImg").hide(); + }); +} +// Code for the search button +$(function () +{ + $("#treeSearch input").click(function () + { + switch(this.id) { + case "search": + doTreeSearch(); + break; + default: + break; + } + }); +}); \ No newline at end of file diff --git a/themes/blueprint/templates/RecordTab/hierarchytree.phtml b/themes/blueprint/templates/RecordTab/hierarchytree.phtml index a60ae1bfefcd97beb193a85786549f768558636d..9c19fda27b1b0be4b65325e72afc1477e67bbbd3 100644 --- a/themes/blueprint/templates/RecordTab/hierarchytree.phtml +++ b/themes/blueprint/templates/RecordTab/hierarchytree.phtml @@ -27,6 +27,19 @@ <? endif; ?> <? if ($activeTree): ?> <div id="hierarchyTreeHolder"> + <? if ($this->tab->searchActive()): ?> + <div id="treeSearch"> + <span id="treeSearchNoResults"><?=$this->transEsc('nohit_heading')?></span> + <input id="search" type="button" value="search" /> + <select id="treeSearchType" name="type"> + <option value="AllFields"><?=$this->transEsc('All Fields')?></option> + <option value="Title"><?=$this->transEsc('Title')?></option> + </select> + <input id="treeSearchText" type="text" value="" /> + <span id="treeSearchLoadingImg"><img src="<?=$this->imageLink('loading.gif')?>"/></span> + </div> + <div id="treeSearchLimitReached"><?=$this->translate('tree_search_limit_reached_html', array('%%url%%' => $this->url('search-results'), '%%limit%%' => $this->tab->getSearchLimit()))?></div> + <? endif; ?> <div id="hierarchyTree"> <input type="hidden" value="<?=$this->escapeHtml($this->driver->getUniqueId())?>" class="hiddenRecordId" /> <input type="hidden" value="<?=$this->escapeHtml($activeTree)?>" class="hiddenHierarchyId" />