Commit deab35cfc388688e50121389710a201a414e6170
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
Showing
3 changed files
with
125 additions
and
30 deletions
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 <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 <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 <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 <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 | + |