Commit deab35cfc388688e50121389710a201a414e6170

Authored by nbm
1 parent eaa69e2a

Add complex boolean search ability - being able to search for this group

of things and/or this group of things.


git-svn-id: https://kt-dms.svn.sourceforge.net/svnroot/kt-dms/trunk@3906 c91229c3-7414-0410-bfa2-8a42b809f60b
presentation/lookAndFeel/knowledgeTree/js/constructed_search.js
@@ -19,6 +19,22 @@ function removeFromElementEvent(elem, event_name, func) { @@ -19,6 +19,22 @@ function removeFromElementEvent(elem, event_name, func) {
19 } 19 }
20 } 20 }
21 21
  22 +var booleanGroups = Array();
  23 +
  24 +function getBooleanGroupId(table) {
  25 + for (var i=0; i<booleanGroups.length; i++) {
  26 + if (booleanGroups[i] == table) {
  27 + return i;
  28 + }
  29 + }
  30 + // nothing found.
  31 + simpleLog('DEBUG','no entry found for table.');
  32 + booleanGroups.push(table);
  33 + simpleLog('DEBUG','added entry at '+(booleanGroups.length-1));
  34 + return booleanGroups.length-1; // int loc.
  35 +}
  36 +
  37 +
22 // quick and dirty helper - find the nearest parent item matching tagName. 38 // quick and dirty helper - find the nearest parent item matching tagName.
23 // FIXME steal the klass or tagName logic from MochiK. 39 // FIXME steal the klass or tagName logic from MochiK.
24 function breadcrumbFind(elem, tagName) { 40 function breadcrumbFind(elem, tagName) {
@@ -41,7 +57,9 @@ var autoIndexCriteria = Array(); @@ -41,7 +57,9 @@ var autoIndexCriteria = Array();
41 // initiate the criteria creation process. 57 // initiate the criteria creation process.
42 function addNewCriteria(add_button) { 58 function addNewCriteria(add_button) {
43 var parent_row = breadcrumbFind(add_button, 'TR'); 59 var parent_row = breadcrumbFind(add_button, 'TR');
  60 + var parent_table = breadcrumbFind(parent_row, 'TABLE');
44 simpleLog('DEBUG','addNewCriteria found parent row: '+parent_row); 61 simpleLog('DEBUG','addNewCriteria found parent row: '+parent_row);
  62 + simpleLog('DEBUG','addNewCriteria found parent table: '+parent_table);
45 var select_source = parent_row.getElementsByTagName('select'); 63 var select_source = parent_row.getElementsByTagName('select');
46 if (select_source.length == 0) { 64 if (select_source.length == 0) {
47 simpleLog('ERROR','addNewCriteria found no criteria specification source.: '+parent_row); 65 simpleLog('ERROR','addNewCriteria found no criteria specification source.: '+parent_row);
@@ -59,12 +77,15 @@ function addNewCriteria(add_button) { @@ -59,12 +77,15 @@ function addNewCriteria(add_button) {
59 // make this one identifiable. 77 // make this one identifiable.
60 autoIndexCriteria.push(0); 78 autoIndexCriteria.push(0);
61 var critId = autoIndexCriteria.length; 79 var critId = autoIndexCriteria.length;
  80 +
  81 + var tableId = getBooleanGroupId(parent_table);
  82 + simpleLog('DEBUG','got boolean group id'+tableId);
62 83
63 // ok, warn the user that we're loading the item. 84 // ok, warn the user that we're loading the item.
64 replaceChildNodes(notify_message, 'loading...'); 85 replaceChildNodes(notify_message, 'loading...');
65 var newCriteriaText = scrapeText(select.options[select.selectedIndex])+' '; // FIXME insert the "input" here. 86 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(); 87 + replaceChildNodes(select.parentNode, newCriteriaText, INPUT({'type':'hidden', 'name':'boolean_search['+tableId+']['+critId+'][type]','value':select.value})); // works thanks to DOM co-ercion.
  88 + createAdditionalCriteriaOption(parent_table);
68 var removeButton = INPUT({'type':'button', 'value':'Remove'}); 89 var removeButton = INPUT({'type':'button', 'value':'Remove'});
69 attachToElementEvent(removeButton, 'click', partial(removeCriteria, removeButton)); 90 attachToElementEvent(removeButton, 'click', partial(removeCriteria, removeButton));
70 add_button.parentNode.replaceChild(removeButton, add_button); 91 add_button.parentNode.replaceChild(removeButton, add_button);
@@ -76,7 +97,7 @@ function addNewCriteria(add_button) { @@ -76,7 +97,7 @@ function addNewCriteria(add_button) {
76 simpleLog('DEBUG','addNewCriteria initiating request to: '+targeturl); 97 simpleLog('DEBUG','addNewCriteria initiating request to: '+targeturl);
77 98
78 var deferred = doSimpleXMLHttpRequest(targeturl); 99 var deferred = doSimpleXMLHttpRequest(targeturl);
79 - deferred.addCallbacks(partial(do_addNewCriteria, dest_cell, critId), handleAjaxError); 100 + deferred.addCallbacks(partial(do_addNewCriteria, dest_cell, critId, tableId), handleAjaxError);
80 } 101 }
81 102
82 103
@@ -84,7 +105,7 @@ function addNewCriteria(add_button) { @@ -84,7 +105,7 @@ function addNewCriteria(add_button) {
84 // - check for the presence of [ or ]. if so, use everything before [ as 105 // - check for the presence of [ or ]. if so, use everything before [ as
85 // the key, and append everything after [. 106 // the key, and append everything after [.
86 // actually replace the contents of the specified td with the responseText. 107 // actually replace the contents of the specified td with the responseText.
87 -function do_addNewCriteria(destination_cell, crit_id, req) { 108 +function do_addNewCriteria(destination_cell, crit_id, table_id, req) {
88 simpleLog('DEBUG','replacing content of cell with: \n'+req.responseText); 109 simpleLog('DEBUG','replacing content of cell with: \n'+req.responseText);
89 destination_cell.innerHTML = req.responseText; 110 destination_cell.innerHTML = req.responseText;
90 // whatever was passed in almost certainly has the wrong name, but that's what 111 // whatever was passed in almost certainly has the wrong name, but that's what
@@ -96,11 +117,11 @@ function do_addNewCriteria(destination_cell, crit_id, req) { @@ -96,11 +117,11 @@ function do_addNewCriteria(destination_cell, crit_id, req) {
96 117
97 for (var i=0; i<inputs.length; i++) { 118 for (var i=0; i<inputs.length; i++) {
98 var obj = inputs[i]; 119 var obj = inputs[i];
99 - obj.name = "boolean_search["+crit_id+"][data]["+obj.name+"]"; 120 + obj.name = "boolean_search["+table_id+"]["+crit_id+"][data]["+obj.name+"]";
100 } 121 }
101 for (var i=0; i<selects.length; i++) { 122 for (var i=0; i<selects.length; i++) {
102 var obj = selects[i]; 123 var obj = selects[i];
103 - obj.name = "boolean_search["+crit_id+"][data]["+obj.name+"]"; 124 + obj.name = "boolean_search["+table_id+"]["+crit_id+"][data]["+obj.name+"]";
104 } 125 }
105 simpleLog('DEBUG','criteria addition complete.'); 126 simpleLog('DEBUG','criteria addition complete.');
106 } 127 }
@@ -119,8 +140,8 @@ function removeCriteria(removeButton) { @@ -119,8 +140,8 @@ function removeCriteria(removeButton) {
119 simpleLog('DEBUG','criteria removed.'); 140 simpleLog('DEBUG','criteria removed.');
120 } 141 }
121 142
122 -function createAdditionalCriteriaOption() {  
123 - var tbody = getSearchContainer(); 143 +function createAdditionalCriteriaOption(parent_table) {
  144 + var tbody = getSearchContainer(parent_table);
124 if (tbody == null) { 145 if (tbody == null) {
125 simpleLog('ERROR','createAdditionalCriteriaOption: No criteria table found.'); 146 simpleLog('ERROR','createAdditionalCriteriaOption: No criteria table found.');
126 return null; 147 return null;
@@ -144,8 +165,8 @@ function createAdditionalCriteriaOption() { @@ -144,8 +165,8 @@ function createAdditionalCriteriaOption() {
144 tbody.appendChild(TR(null, select_entry, notification_entry, add_entry)); 165 tbody.appendChild(TR(null, select_entry, notification_entry, add_entry));
145 } 166 }
146 167
147 -function getSearchContainer() {  
148 - var container = getElement('advanced-search-form'); 168 +function getSearchContainer(parent_table) {
  169 + var container = parent_table;
149 if (container == null) { 170 if (container == null) {
150 simpleLog('ERROR','No criteria table found.'); 171 simpleLog('ERROR','No criteria table found.');
151 return null; 172 return null;
@@ -163,3 +184,48 @@ function getSearchContainer() { @@ -163,3 +184,48 @@ function getSearchContainer() {
163 return container; 184 return container;
164 } 185 }
165 186
  187 +// uses addbutton to insertBefore
  188 +function addBooleanGroup(addbutton) {
  189 + bodyObj = addbutton.parentNode;
  190 + simpleLog('DEBUG','adding boolean group to '+bodyObj);
  191 +
  192 + // i hate me. i also want sane multiline
  193 + sourceString = ' <fieldset> <legend>Criteria Group</legend> <table class="advanced-search-form"> <tbody> <tr> <th>Criteria</th> <th>Values</th> </tr></tbody></table> </fieldset>';
  194 +
  195 +
  196 + // add the fieldset
  197 + var t = DIV(null);
  198 + t.innerHTML = sourceString; // urgh.
  199 + var fieldsetObj = t.getElementsByTagName('FIELDSET')[0];
  200 + bodyObj.insertBefore(fieldsetObj, addbutton);
  201 + tableObj = fieldsetObj.getElementsByTagName('TABLE')[0];
  202 + // get an id for the table.
  203 + var table_id = getBooleanGroupId(tableObj);
  204 + // add the grouping string
  205 + groupingString = '<p class="helpText">Return items which match &nbsp;<select name="boolean_condition['+table_id+']"><option value="AND">all</option><option value="OR">any</option></select> of the criteria specified.</p>';
  206 + t = DIV(null);
  207 + t.innerHTML = groupingString;
  208 + var paraObj = t.getElementsByTagName('P')[0];
  209 + fieldsetObj.insertBefore(paraObj, tableObj);
  210 +
  211 + // add a basic item to the table.
  212 + createAdditionalCriteriaOption(tableObj);
  213 + // done.
  214 + simpleLog('DEBUG','done adding boolean group.');
  215 +}
  216 +
  217 +// FIXME do we want a "remove boolean group" setting?
  218 +// FIXME yes, and its easy (find parent, find ITS parent, toast.)
  219 +function initialiseChecks() {
  220 +
  221 + var initialTables = getElementsByTagAndClassName('TABLE','advanced-search-form');
  222 + simpleLog('DEBUG','initialising '+initialTables.length+' criteria groups.');
  223 + var t = TABLE(null);
  224 + for (var i=0; i<initialTables.length; i++) {
  225 + if (typeof(initialTables[i]) == typeof(t)) {
  226 + getBooleanGroupId(initialTables[i]);
  227 + }
  228 + }
  229 +}
  230 +
  231 +addLoadEvent(initialiseChecks);
presentation/lookAndFeel/knowledgeTree/search/booleanSearch.php
@@ -48,7 +48,7 @@ class BooleanSearchDispatcher extends KTStandardDispatcher { @@ -48,7 +48,7 @@ class BooleanSearchDispatcher extends KTStandardDispatcher {
48 // TODO finally return via PatternBrowseableSearchResults (urgh.) 48 // TODO finally return via PatternBrowseableSearchResults (urgh.)
49 49
50 $datavars = KTUtil::arrayGet($_REQUEST, 'boolean_search'); 50 $datavars = KTUtil::arrayGet($_REQUEST, 'boolean_search');
51 - $booleanJoinName = KTUtil::arrayGet($_REQUEST, 'boolean_condition'); 51 + $booleanJoinName = KTUtil::arrayGet($_REQUEST, 'outer_boolean_condition');
52 52
53 if (empty($datavars)) { 53 if (empty($datavars)) {
54 $this->errorRedirectToMain('You need to have at least 1 condition.'); 54 $this->errorRedirectToMain('You need to have at least 1 condition.');
@@ -59,23 +59,24 @@ class BooleanSearchDispatcher extends KTStandardDispatcher { @@ -59,23 +59,24 @@ class BooleanSearchDispatcher extends KTStandardDispatcher {
59 59
60 // Step 1: extract the criteria selection, and create an array of criteria. 60 // Step 1: extract the criteria selection, and create an array of criteria.
61 $criteria_set = array(); 61 $criteria_set = array();
62 - foreach ($datavars as $order => $dataset) {  
63 - $oCriterion = Criteria::getCriterionByNumber($dataset["type"]);  
64 - if (PEAR::isError($oCriterion)) {  
65 - $this->errorRedirectToMain('Invalid criteria specified.'); 62 + foreach (array_keys($datavars) as $k) {
  63 + foreach ($datavars[$k] as $order => $dataset) {
  64 + $oCriterion = Criteria::getCriterionByNumber($dataset["type"]);
  65 + if (PEAR::isError($oCriterion)) {
  66 + $this->errorRedirectToMain('Invalid criteria specified.');
  67 + }
  68 + $criteria_set[$k][] = array($oCriterion, $dataset["data"]);
66 } 69 }
67 - $criteria_set[] = array($oCriterion, $dataset["data"]);  
68 } 70 }
69 - $res = $this->handleCriteriaSet($criteria_set, $booleanJoinName); 71 + $res = $this->handleCriteriaSet($criteria_set, $booleanJoinName, $_REQUEST['boolean_condition']);
70 72
71 return $res; 73 return $res;
72 } 74 }
73 -  
74 - function handleCriteriaSet($aCriteriaSet, $mergeType='AND') {  
75 - global $default;  
76 - $aSQL = array();  
77 - $aJoinSQL = array();  
78 - foreach ($aCriteriaSet as $oCriterionPair) { 75 +
  76 + function _oneCriteriaSetToSQL($aOneCriteriaSet) {
  77 + $aSQL = array();
  78 + $aJoinSQL = array();
  79 + foreach ($aOneCriteriaSet as $oCriterionPair) {
79 $oCriterion = $oCriterionPair[0]; 80 $oCriterion = $oCriterionPair[0];
80 $aReq = $oCriterionPair[1]; 81 $aReq = $oCriterionPair[1];
81 $res = $oCriterion->searchSQL($aReq); 82 $res = $oCriterion->searchSQL($aReq);
@@ -104,8 +105,27 @@ class BooleanSearchDispatcher extends KTStandardDispatcher { @@ -104,8 +105,27 @@ class BooleanSearchDispatcher extends KTStandardDispatcher {
104 exit(0); 105 exit(0);
105 } 106 }
106 107
107 - $sSQLSearchString = join("\n ".$mergeType." ", $aCritQueries); 108 + return array($aCritQueries, $aCritParams, $aJoinSQL);
  109 + }
  110 +
  111 + function criteriaSetToSQL($aCriteriaSet, $mergeType='AND', $innerMergeTypes = null) {
  112 + $aJoinSQL = array();
  113 + $aSearchStrings = array();
  114 + $aParams = array();
  115 + foreach ($aCriteriaSet as $k => $aOneCriteriaSet) {
  116 + list($aThisCritQueries, $aThisParams, $aThisJoinSQL) = $this->_oneCriteriaSetToSQL($aOneCriteriaSet);
  117 + $aJoinSQL = array_merge($aJoinSQL, $aThisJoinSQL);
  118 + $aParams = array_merge($aParams, $aThisParams);
  119 + $aSearchStrings[] = "\n\t\t(\n\t\t\t" . join("\n " . KTUtil::arrayGet($innerMergeTypes, $k, "AND") . " ", $aThisCritQueries) . "\n\t\t)";
  120 + }
108 $sJoinSQL = join(" ", $aJoinSQL); 121 $sJoinSQL = join(" ", $aJoinSQL);
  122 + $sSearchString = "\n\t(" . join("\n\t\t" . $mergeType . " ", $aSearchStrings) . "\n\t)";
  123 + return array($sSearchString, $aParams, $sJoinSQL);
  124 + }
  125 +
  126 + function handleCriteriaSet($aCriteriaSet, $mergeType='AND', $innerMergeTypes = null) {
  127 + global $default;
  128 + list($sSQLSearchString, $aCritParams, $sJoinSQL) = $this->criteriaSetToSQL($aCriteriaSet, $mergeType);
109 129
110 $sToSearch = KTUtil::arrayGet($aOrigReq, 'fToSearch', 'Live'); // actually never present in this version. 130 $sToSearch = KTUtil::arrayGet($aOrigReq, 'fToSearch', 'Live'); // actually never present in this version.
111 131
@@ -141,7 +161,7 @@ class BooleanSearchDispatcher extends KTStandardDispatcher { @@ -141,7 +161,7 @@ class BooleanSearchDispatcher extends KTStandardDispatcher {
141 $aParams[] = $sToSearch; 161 $aParams[] = $sToSearch;
142 $aParams = array_merge($aParams, $aCritParams); 162 $aParams = array_merge($aParams, $aCritParams);
143 163
144 - //'<pre>'.var_dump(DBUtil::getResultArray(array($sQuery, $aParams))); 164 + //print '<pre>';var_dump(DBUtil::getResultArray(array($sQuery, $aParams)));
145 //exit(0); 165 //exit(0);
146 //return '<pre>'.print_r(DBUtil::getResultArray(array($sQuery, $aParams)), true).'</pre>'; 166 //return '<pre>'.print_r(DBUtil::getResultArray(array($sQuery, $aParams)), true).'</pre>';
147 $iStartIndex = 1; 167 $iStartIndex = 1;
templates/ktcore/boolean_search.smarty
@@ -50,12 +50,15 @@ legend { border: 1px dotted #999;} @@ -50,12 +50,15 @@ legend { border: 1px dotted #999;}
50 50
51 <form> 51 <form>
52 <input type="hidden" name="action" value="performSearch" /> 52 <input type="hidden" name="action" value="performSearch" />
  53 +
  54 + <p class="helpText">Return items which match &nbsp;<select name="outer_boolean_condition"><option value="AND">all</option><option value="OR">any</option></select> of the <strong>criteria groups</strong> specified.</p>
  55 +
53 <fieldset> 56 <fieldset>
54 - <legend>Boolean Search.</legend> 57 + <legend>Criteria Group</legend>
55 58
56 - <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> 59 + <p class="helpText">Return items which match &nbsp;<select name="boolean_condition[0]"><option value="AND">all</option><option value="OR">any</option></select> of the criteria specified.</p>
57 60
58 - <table id="advanced-search-form"> 61 + <table class="advanced-search-form">
59 <tbody> 62 <tbody>
60 <tr> 63 <tr>
61 <th>Criteria</th> 64 <th>Criteria</th>
@@ -76,8 +79,13 @@ legend { border: 1px dotted #999;} @@ -76,8 +79,13 @@ legend { border: 1px dotted #999;}
76 </table> 79 </table>
77 80
78 </fieldset> 81 </fieldset>
79 - <input type="submit" name="submit" value="performSearch" /> 82 +
  83 + <input type="button" value="add another set of criteria" onclick="addBooleanGroup(this)"/>
  84 +
  85 +
  86 + <input type="submit" name="submit" value="Search" />
80 </form> 87 </form>
  88 +
81 <!-- 89 <!--
82 <table id="brad-log" width="100%"> 90 <table id="brad-log" width="100%">
83 <thead> 91 <thead>
@@ -88,7 +96,8 @@ legend { border: 1px dotted #999;} @@ -88,7 +96,8 @@ legend { border: 1px dotted #999;}
88 </tr> 96 </tr>
89 </thead> 97 </thead>
90 <tbody> 98 <tbody>
  99 +-->
91 100
92 </tbody> 101 </tbody>
93 </table> 102 </table>
94 ---> 103 +