Commit b08b869b1c2b7714bcff0e165abd4032f0e997fe

Authored by Brad Shuttleworth
1 parent 6b061e49

first layer of the Boolean Search AJAX UI.


git-svn-id: https://kt-dms.svn.sourceforge.net/svnroot/kt-dms/trunk@3697 c91229c3-7414-0410-bfa2-8a42b809f60b
presentation/lookAndFeel/knowledgeTree/js/constructed_search.js 0 → 100644
  1 +// simple event stacking.
  2 +// i don't like Mochikit's one.
  3 +
  4 +function attachToElementEvent(elem, event_name, func) {
  5 + // catch IE (grumble)
  6 + if (elem.attachEvent) {
  7 + elem.attachEvent('on'+event_name, func);
  8 + } else {
  9 + elem.addEventListener(event_name, func, false);
  10 + }
  11 +}
  12 +
  13 +function removeFromElementEvent(elem, event_name, func) {
  14 + // catch IE (grumble)
  15 + if (elem.detachEvent) {
  16 + elem.detachEvent('on'+event_name, func);
  17 + } else {
  18 + elem.removeEventListener(event_name, func, false);
  19 + }
  20 +}
  21 +
  22 +// quick and dirty helper - find the nearest parent item matching tagName.
  23 +// FIXME steal the klass or tagName logic from MochiK.
  24 +function breadcrumbFind(elem, tagName) {
  25 + var stopTag = 'BODY';
  26 + var currentTag = elem.tagName;
  27 + var currentElem = elem;
  28 + while ((currentTag != stopTag) && (currentTag != tagName)) {
  29 + currentElem = currentElem.parentNode;
  30 + currentTag = currentElem.tagName;
  31 + }
  32 + if (currentTag == tagName) {
  33 + return currentElem;
  34 + } else {
  35 + return null;
  36 + }
  37 +}
  38 +
  39 +var autoIndexCriteria = Array();
  40 +
  41 +// initiate the criteria creation process.
  42 +function addNewCriteria(add_button) {
  43 + var parent_row = breadcrumbFind(add_button, 'TR');
  44 + simpleLog('DEBUG','addNewCriteria found parent row: '+parent_row);
  45 + var select_source = parent_row.getElementsByTagName('select');
  46 + if (select_source.length == 0) {
  47 + simpleLog('ERROR','addNewCriteria found no criteria specification source.: '+parent_row);
  48 + } else {
  49 + var select = select_source[0];
  50 + }
  51 + var notify_source = parent_row.getElementsByTagName('p');
  52 + if (notify_source.length == 0) {
  53 + simpleLog('ERROR','addNewCriteria found no message storage in row: '+parent_row);
  54 + } else {
  55 + var notify_message = notify_source[0];
  56 + }
  57 +
  58 +
  59 + // make this one identifiable.
  60 + autoIndexCriteria.push(0);
  61 + var critId = autoIndexCriteria.length;
  62 +
  63 + // ok, warn the user that we're loading the item.
  64 + replaceChildNodes(notify_message, 'loading...');
  65 + var newCriteriaText = scrapeText(select.options[select.selectedIndex])+' '; // FIXME insert the "input" here.
  66 + replaceChildNodes(select.parentNode, newCriteriaText, INPUT({'type':'hidden', 'name':'boolean_search['+critId+'][\'type\']','value':select.value})); // works thanks to DOM co-ercion.
  67 + createAdditionalCriteriaOption();
  68 + var removeButton = INPUT({'type':'button', 'value':'Remove'});
  69 + attachToElementEvent(removeButton, 'click', partial(removeCriteria, removeButton));
  70 + add_button.parentNode.replaceChild(removeButton, add_button);
  71 +
  72 +
  73 + // fetch.
  74 + var dest_cell = notify_message.parentNode;
  75 + var targeturl='ajaxBooleanSearch.php?action=getNewCriteria&type='+select.value+'&critId='+critId;
  76 + simpleLog('DEBUG','addNewCriteria initiating request to: '+targeturl);
  77 +
  78 + var deferred = doSimpleXMLHttpRequest(targeturl);
  79 + deferred.addCallbacks(partial(do_addNewCriteria, dest_cell, critId), handleAjaxError);
  80 +}
  81 +
  82 +
  83 +// FIXME multi-select items using PHP array[] syntax won't work. we'd need to:
  84 +// - check for the presence of [ or ]. if so, use everything before [ as
  85 +// the key, and append everything after [.
  86 +// actually replace the contents of the specified td with the responseText.
  87 +function do_addNewCriteria(destination_cell, crit_id, req) {
  88 + simpleLog('DEBUG','replacing content of cell with: \n'+req.responseText);
  89 + destination_cell.innerHTML = req.responseText;
  90 + // whatever was passed in almost certainly has the wrong name, but that's what
  91 + // will be expected in the backend.
  92 + // wrap it so we don't get clashes.
  93 +
  94 + var inputs = destination_cell.getElementsByTagName('INPUT');
  95 + var selects = destination_cell.getElementsByTagName('SELECT');
  96 +
  97 + for (var i=0; i<inputs.length; i++) {
  98 + var obj = inputs[i];
  99 + obj.name = "boolean_search["+crit_id+"]['data']['"+obj.name+"']";
  100 + }
  101 + for (var i=0; i<selects.length; i++) {
  102 + var obj = selects[i];
  103 + obj.name = "boolean_search["+crit_id+"]['data']['"+obj.name+"']";
  104 + }
  105 + simpleLog('DEBUG','criteria addition complete.');
  106 +}
  107 +function handleAjaxError(err) {
  108 + simpleLog('ERROR','ajax error: '+err);
  109 +}
  110 +
  111 +
  112 +function removeCriteria(removeButton) {
  113 + var parent_row = breadcrumbFind(removeButton, 'TR');
  114 + if (parent_row == null) {
  115 + simpleLog('ERROR','removeCriteria found no effective parent row.');
  116 + } else {
  117 + parent_row.parentNode.removeChild(parent_row);
  118 + }
  119 + simpleLog('DEBUG','criteria removed.');
  120 +}
  121 +
  122 +function createAdditionalCriteriaOption() {
  123 + var tbody = getSearchContainer();
  124 + if (tbody == null) {
  125 + simpleLog('ERROR','createAdditionalCriteriaOption: No criteria table found.');
  126 + return null;
  127 + }
  128 +
  129 + // now we need to instantiate the selection stuff.
  130 + // to do this we find a hidden div in the page which contains the container types...
  131 + var master_div = getElement('search-criteria-container');
  132 + if (master_div == null) {
  133 + simpleLog('ERROR','createAdditionalCriteriaOption: No criteria types identified within the request.');
  134 + return null;
  135 + }
  136 +
  137 + // clone, and append.
  138 + var clonedObject = master_div.getElementsByTagName('select')[0].cloneNode(true);
  139 + var select_entry = TD(null, clonedObject);
  140 + var notification_entry = TD(null, createDOM('P',{'class':'helpText'},'first select a type of query'));
  141 + var add_button = INPUT({'type':'button', 'value':'Add'});
  142 + attachToElementEvent(add_button, 'click', partial(addNewCriteria, add_button));
  143 + var add_entry = TD(null, add_button);
  144 + tbody.appendChild(TR(null, select_entry, notification_entry, add_entry));
  145 +}
  146 +
  147 +function getSearchContainer() {
  148 + var container = getElement('advanced-search-form');
  149 + if (container == null) {
  150 + simpleLog('ERROR','No criteria table found.');
  151 + return null;
  152 + } else {
  153 + var innerContainer = container.getElementsByTagName('TBODY');
  154 + }
  155 +
  156 + if (innerContainer.length == 0) {
  157 + simpleLog('ERROR','No criteria tbody found.');
  158 + return null;
  159 + } else {
  160 + var container = innerContainer[0];
  161 + delete innerContainer;
  162 + }
  163 + return container;
  164 +}
  165 +
... ...
presentation/lookAndFeel/knowledgeTree/js/taillog.js 0 → 100644
  1 +/*
  2 +// once site is in production, rather use:
  3 +
  4 +function simpleLog(severity, item) { ; }
  5 +
  6 +*/
  7 +
  8 +// inline logger
  9 +function simpleLog(severity,item) {
  10 + var logTable = getElement('brad-log');
  11 + if (logTable == null) return ;
  12 +
  13 + // we have a table, do the log.
  14 + newRow = createDOM('TR', {'class':'logtable','valign':'top'},
  15 + TD({'class':'severity-'+severity}, severity),
  16 + TD({'class':'timestamp'},toISOTime(new Date())),
  17 + TD({'class':'explanation'}, item)
  18 + );
  19 + logTable.getElementsByTagName('tbody')[0].appendChild(newRow);
  20 +}
... ...
presentation/lookAndFeel/knowledgeTree/search/ajaxBooleanSearch.php 0 → 100644
  1 +<?php
  2 +require_once("../../../../config/dmsDefaults.php");
  3 +require_once(KT_DIR . "/presentation/Html.inc");
  4 +require_once(KT_LIB_DIR . "/templating/templating.inc.php");
  5 +
  6 +require_once(KT_LIB_DIR . "/database/dbutil.inc");
  7 +require_once(KT_LIB_DIR . "/util/ktutil.inc");
  8 +require_once(KT_LIB_DIR . "/dispatcher.inc.php");
  9 +$sectionName = "General";
  10 +require_once(KT_DIR . "/presentation/webpageTemplate.inc");
  11 +
  12 +require_once(KT_LIB_DIR . "/browse/Criteria.inc");
  13 +
  14 +/*
  15 + * example code - tests the frontend behaviour. remember to check ajaxConditional.php
  16 + *
  17 + */
  18 +
  19 +class AjaxBooleanSearchDispatcher extends KTDispatcher {
  20 +
  21 + function handle_output($data) {
  22 + return $data;
  23 + }
  24 +
  25 + function do_getNewCriteria() {
  26 + $criteriaType = KTUtil::arrayGet($_REQUEST, 'type');
  27 + if (empty($criteriaType)) {
  28 + return 'AJAX Error: no criteria type specified.';
  29 + }
  30 + $critObj = Criteria::getCriterionByNumber($criteriaType);
  31 + if (PEAR::isError($critObj)) {
  32 + return 'AJAX Error: failed to initialise critiria of type "'.$type.'".';
  33 + }
  34 + // NBM: there appears to be no reason to take $aRequest into searchWidget...
  35 + $noRequest = array();
  36 + return $critObj->searchWidget($noRequest);
  37 + }
  38 +
  39 + function do_main() {
  40 + return "Ajax Error. ajaxBooleanSearch::do_main should not be reachable.";
  41 + }
  42 +
  43 +
  44 +}
  45 +
  46 +$oDispatcher = new AjaxBooleanSearchDispatcher();
  47 +$oDispatcher->dispatch();
  48 +
  49 +?>
... ...
presentation/lookAndFeel/knowledgeTree/search/booleanSearch.php 0 → 100644
  1 +<?php
  2 +
  3 +// boilerplate includes
  4 +require_once("../../../../config/dmsDefaults.php");
  5 +require_once(KT_DIR . "/presentation/Html.inc");
  6 +require_once(KT_LIB_DIR . "/templating/templating.inc.php");
  7 +require_once(KT_LIB_DIR . "/database/dbutil.inc");
  8 +require_once(KT_LIB_DIR . "/util/ktutil.inc");
  9 +require_once(KT_LIB_DIR . "/dispatcher.inc.php");
  10 +require_once(KT_LIB_DIR . "/browse/Criteria.inc");
  11 +
  12 +// specific includes
  13 +
  14 +$sectionName = "General";
  15 +require_once(KT_DIR . "/presentation/webpageTemplate.inc");
  16 +
  17 +/*
  18 + * example code - tests the frontend behaviour. remember to check ajaxConditional.php
  19 + *
  20 + */
  21 +
  22 +class BooleanSearchDispatcher extends KTStandardDispatcher {
  23 + function do_main() {
  24 + $oTemplating = new KTTemplating;
  25 + $oTemplate = $oTemplating->loadTemplate("ktcore/boolean_search");
  26 +
  27 + $aCriteria = Criteria::getAllCriteria();
  28 +
  29 + $aTemplateData = array(
  30 + "aCriteria" => $aCriteria,
  31 + );
  32 + return $oTemplate->render($aTemplateData);
  33 + }
  34 +
  35 + function handleOutput($data) {
  36 + global $main;
  37 + $main->bFormDisabled = true;
  38 + $main->setCentralPayload($data);
  39 + $main->render();
  40 + }
  41 +}
  42 +
  43 +$oDispatcher = new BooleanSearchDispatcher();
  44 +$oDispatcher->dispatch();
  45 +
  46 +?>
... ...
presentation/lookAndFeel/knowledgeTree/search/booleanSearchUtil.inc 0 → 100644
  1 +<?php
  2 +/**
  3 + * $Id$
  4 + *
  5 + * Business logic related to Boolean Search. This allows client-side construction
  6 + * of complex searches.
  7 + *
  8 + * Copyright (c) 2005 Jam Warehouse http://www.jamwarehouse.com
  9 + *
  10 + * This program is free software; you can redistribute it and/or modify
  11 + * it under the terms of the GNU General Public License as published by
  12 + * the Free Software Foundation; either version 2 of the License, or
  13 + * (at your option) any later version.
  14 + *
  15 + * This program is distributed in the hope that it will be useful,
  16 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18 + * GNU General Public License for more details.
  19 + *
  20 + * You should have received a copy of the GNU General Public License
  21 + * along with this program; if not, write to the Free Software
  22 + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  23 + *
  24 + * @version $Revision$
  25 + * @author Brad Shuttleworth, Jam Warehouse (Pty) Ltd, South Africa
  26 + * @package search
  27 + */
  28 +
  29 +/* getBooleanSearchResults.
  30 + *
  31 + * What gets passed in from the Ajax generated environment looks as follows:
  32 + * boolean_search[x] = array(); // x is a client-side generated index for the criterion.
  33 + * boolean_search[x]["type"] = Criterion::getID; // straight out, so none of this "bumpf".
  34 + * boolean_search[x]["data"] = array();
  35 + * boolean_search[x]["data"][y] = value; // where "y" is whatever-the-hell each thing is. essentially, pass boolean_search[x]["data"] as $aRequest;.
  36 + */
  37 +
  38 +function getBooleanSearchResults($aOrigReq, $iStartIndex) {
  39 + global $default;
  40 +
  41 + $sRefreshMessage = "<table><tr><td align=\"center\">" . _("If your browser displays a 'Warning: Page has Expired' message when you attempt to return to these search results, please click your browser's 'Refresh' button") . "</td></tr></table>";
  42 +
  43 + $aReq = array();
  44 + foreach ($aOrigReq as $k => $v) {
  45 + if (searchCriteria($k) === 1) {
  46 + $v = trim($v);
  47 + if ($v === "") {
  48 + continue;
  49 + }
  50 + if ($v === "-1") {
  51 + continue;
  52 + }
  53 + $aReq[$k] = $v;
  54 + }
  55 + }
  56 +
  57 + $aIDs = array_unique(array_map("criteriaNumber", array_keys($aReq)));
  58 + $aSQL = array();
  59 + $aJoinSQL = array();
  60 + foreach ($aIDs as $iID) {
  61 + $oCriterion =& Criteria::getCriterionByNumber($iID);
  62 + $res = $oCriterion->searchSQL($aReq);
  63 + if (!is_null($res)) {
  64 + $aSQL[] = $res;
  65 + }
  66 + $res = $oCriterion->searchJoinSQL();
  67 + if (!is_null($res)) {
  68 + $aJoinSQL[] = $res;
  69 + }
  70 + }
  71 + $aCritParams = array();
  72 + $aCritQueries = array();
  73 + foreach ($aSQL as $sSQL) {
  74 + if (is_array($sSQL)) {
  75 + $aCritQueries[] = $sSQL[0];
  76 + $aCritParams = array_merge($aCritParams , $sSQL[1]);
  77 + } else {
  78 + $aCritQueries[] = $sSQL;
  79 + }
  80 + }
  81 +
  82 + if (count($aCritQueries) == 0) {
  83 + return "No search criteria were specified";
  84 + }
  85 +
  86 + $sSQLSearchString = join(" AND ", $aCritQueries);
  87 + $sJoinSQL = join(" ", $aJoinSQL);
  88 +
  89 + $sToSearch = KTUtil::arrayGet($aOrigReq, 'fToSearch', 'Live');
  90 +
  91 + $sQuery = DBUtil::compactQuery("
  92 +SELECT
  93 + F.name AS folder_name, F.id AS folder_id, D.id AS document_id,
  94 + D.name AS document_name, D.filename AS file_name, COUNT(D.id) AS doc_count, 'View' AS view
  95 +FROM
  96 + $default->documents_table AS D
  97 + INNER JOIN $default->folders_table AS F ON D.folder_id = F.id
  98 + $sJoinSQL
  99 + INNER JOIN $default->search_permissions_table AS SDUL ON SDUL.document_id = D.id
  100 + INNER JOIN $default->status_table AS SL on D.status_id=SL.id
  101 +WHERE
  102 + (F.is_public OR
  103 + SDUL.user_id = ?)
  104 + AND SL.name = ?
  105 + AND ($sSQLSearchString)
  106 +GROUP BY D.id
  107 +ORDER BY doc_count DESC");
  108 +
  109 + $aParams = array();
  110 + $aParams[] = $_SESSION["userID"];
  111 + $aParams[] = $sToSearch;
  112 + $aParams = array_merge($aParams, $aCritParams);
  113 +
  114 + //var_dump(DBUtil::getResultArray(array($sQuery, $aParams)));
  115 + //exit(0);
  116 +
  117 + $aColumns = array("folder_name", "file_name", "document_name", "doc_count", "view");
  118 + $aColumnTypes = array(3,3,3,1,3);
  119 + $aColumnHeaders = array("<font color=\"ffffff\"><img src=$default->graphicsUrl/widgets/dfolder.gif>" . _("Folder") . "</font>", "<font color=\"ffffff\">" . _("Name") . "</font>", "<font color=\"ffffff\">" . _("Title") . "</font>", "<font color=\"ffffff\">" . _("Matches") . "</font>", "<font color=\"ffffff\">" . _("View") . "</font>");
  120 + $aLinkURLs = array("$default->rootUrl/control.php?action=browse","$default->rootUrl/control.php?action=viewDocument", "$default->rootUrl/control.php?action=viewDocument", null, "$default->rootUrl/control.php?action=downloadDocument");
  121 + $aDBQueryStringColumns = array("document_id","folder_id");
  122 + $aQueryStringVariableNames = array("fDocumentID", "fFolderID");
  123 +
  124 + $oPatternBrowse = & new PatternBrowseableSearchResults(array($sQuery, $aParams), 10, $aColumns, $aColumnTypes, $aColumnHeaders, $aLinkURLs, $aDBQueryStringColumns, $aQueryStringVariableNames);
  125 + $oPatternBrowse->setStartIndex($iStartIndex);
  126 + $oPatternBrowse->setSearchText("");
  127 + $oPatternBrowse->setRememberValues($aReq);
  128 + $sForSearch = "<input type=\"hidden\" name=\"fForSearch\" value=\"1\" />";
  129 +
  130 + return renderHeading(_("Advanced Search")) . $oPatternBrowse->render() . $sForSearch . $sRefreshMessage;
  131 +}
  132 +
  133 +function dealWithAdvancedSearch($aReq, $iStartIndex) {
  134 + global $main;
  135 + $oPatternCustom = & new PatternCustom();
  136 + $oPatternCustom->setHtml(getAdvancedSearchResults($aReq, $iStartIndex));
  137 + $main->setCentralPayload($oPatternCustom);
  138 + $main->render();
  139 +}
  140 +
  141 +?>
... ...
templates/ktcore/boolean_search.smarty 0 → 100644
  1 +<!-- NBM: FIX THE TEMPLATING SYSTEM -->
  2 +
  3 +<script language="javascript" src="/thirdpartyjs/MochiKit/Base.js"> </script>
  4 +<script language="javascript" src="/thirdpartyjs/MochiKit/DateTime.js"> </script>
  5 +<script language="javascript" src="/thirdpartyjs/MochiKit/Iter.js"> </script>
  6 +<script language="javascript" src="/thirdpartyjs/MochiKit/DOM.js"> </script>
  7 +<script language="javascript" src="/thirdpartyjs/MochiKit/Async.js"> </script>
  8 +<script language="javascript" src="/presentation/lookAndFeel/knowledgeTree/js/taillog.js"> </script>
  9 +<script language="javascript" src="/presentation/lookAndFeel/knowledgeTree/js/constructed_search.js"> </script>
  10 +
  11 +{literal}
  12 +
  13 +<script type="text/javascript">
  14 +function testStartup() {
  15 + simpleLog('INFO','Log initialised.');
  16 +}
  17 +
  18 +addLoadEvent(testStartup);
  19 +</script>
  20 +
  21 +<style>
  22 +
  23 +fieldset { border: 1px dotted #999; }
  24 +legend { border: 1px dotted #999;}
  25 +
  26 +.helpText { color: #666; }
  27 +
  28 +/* logging support */
  29 +#brad-log thead th { border-bottom: 1px solid black; }
  30 +#brad-log {font-size: smaller; }
  31 +#brad-log .severity-INFO { color: blue; font-weight: bold; }
  32 +#brad-log .severity-DEBUG { color: green; font-weight: bold; }
  33 +#brad-log .severity-ERROR { color: red; font-weight: bold; }
  34 +#brad-log .explanation { font-family: monospace; white-space: pre; }
  35 +
  36 +</style>
  37 +
  38 +{/literal}
  39 +
  40 +<!-- this is bad, but we really don't need a roundtrip -->
  41 +<div style="display: none" id="search-criteria-container">
  42 + <select name="querytype">
  43 + {foreach item=oCriteria from=$aCriteria}
  44 + <option value="{$oCriteria->getID()}">{$oCriteria->headerDisplay()}</option>
  45 + {/foreach}
  46 + </select>
  47 +</div>
  48 +
  49 +<h2>Advanced Search</h2>
  50 +
  51 +<form>
  52 + <fieldset>
  53 + <legend>Hello World.</legend>
  54 +
  55 + <p class="helpText">Return items which match &nbsp;<select name="boolean_condition"><option value="AND">all</option><option value="OR">any</option></select> of the criteria specified.</p>
  56 +
  57 + <table id="advanced-search-form">
  58 + <tbody>
  59 + <tr>
  60 + <td><select name="querytype">
  61 + {foreach item=oCriteria from=$aCriteria}
  62 + <option value="{$oCriteria->getID()}">{$oCriteria->headerDisplay()}</option>
  63 + {/foreach}
  64 + </select>
  65 + </td>
  66 + <td><p class="helpText">first select a type of query</p></td>
  67 + <td><input type="button" value="Add" onclick="addNewCriteria(this);" /></td>
  68 + </tr>
  69 + </tbody>
  70 + </table>
  71 +
  72 + </fieldset>
  73 +</form>
  74 +<!--
  75 +<table id="brad-log" width="100%">
  76 +<thead>
  77 + <tr>
  78 + <th width="10%">Severity</th>
  79 + <th width="10%">Time</th>
  80 + <th>Entry</th>
  81 + </tr>
  82 +</thead>
  83 +<tbody>
  84 +
  85 +</tbody>
  86 +</table>
  87 +-->
0 88 \ No newline at end of file
... ...