/third_party/jqgrid/plugins/jquery.searchFilter.js

https://bitbucket.org/Prvnpn216/inclusionsurvey · JavaScript · 716 lines · 357 code · 58 blank · 301 comment · 99 complexity · ccdc75752a7da82a07f54208d0b675c3 MD5 · raw file

  1. /* Plugin: searchFilter v1.2.9
  2. * Author: Kasey Speakman (kasey@cornerspeed.com)
  3. * License: Dual Licensed, MIT and GPL v2 (http://www.gnu.org/licenses/gpl-2.0.html)
  4. *
  5. * REQUIREMENTS:
  6. * jQuery 1.3+ (http://jquery.com/)
  7. * A Themeroller Theme (http://jqueryui.com/themeroller/)
  8. *
  9. * SECURITY WARNING
  10. * You should always implement server-side checking to ensure that
  11. * the query will fail when forged/invalid data is received.
  12. * Clever users can send any value they want through JavaScript and HTTP POST/GET.
  13. *
  14. * THEMES
  15. * Simply include the CSS file for your Themeroller theme.
  16. *
  17. * DESCRIPTION
  18. * This plugin creates a new searchFilter object in the specified container
  19. *
  20. * INPUT TYPE
  21. * fields: an array of field objects. each object has the following properties:
  22. * text: a string containing the display name of the field (e.g. "Field 1")
  23. * itemval: a string containing the actual field name (e.g. "field1")
  24. * optional properties:
  25. * ops: an array of operators in the same format as jQuery.fn.searchFilter.defaults.operators
  26. * that is: [ { op: 'gt', text: 'greater than'}, { op:'lt', text: 'less than'}, ... ]
  27. * if not specified, the passed-in options used, and failting that, jQuery.fn.searchFilter.defaults.operators will be used
  28. * *** NOTE ***
  29. * Specifying a dataUrl or dataValues property means that a <select ...> (drop-down-list) will be generated
  30. * instead of a text input <input type='text'.../> where the user would normally type in their search data
  31. * ************
  32. * dataUrl: a url that will return the html select for this field, this url will only be called once for this field
  33. * dataValues: the possible values for this field in the form [ { text: 'Data Display Text', value: 'data_actual_value' }, { ... } ]
  34. * dataInit: a function that you can use to initialize the data field. this function is passed the jQuery-fied data element
  35. * dataEvents: list of events to apply to the data element. uses $("#id").bind(type, [data], fn) to bind events to data element
  36. * *** JSON of this object could look like this: ***
  37. * var fields = [
  38. * {
  39. * text: 'Field Display Name',
  40. * itemval: 'field_actual_name',
  41. * // below this are optional values
  42. * ops: [ // this format is the same as jQuery.fn.searchFilter.defaults.operators
  43. * { op: 'gt', text: 'greater than' },
  44. * { op: 'lt', text: 'less than' }
  45. * ],
  46. * dataUrl: 'http://server/path/script.php?propName=propValue', // using this creates a select for the data input instead of an input type='text'
  47. * dataValues: [ // using this creates a select for the data input instead of an input type='text'
  48. * { text: 'Data Value Display Name', value: 'data_actual_value' },
  49. * { ... }
  50. * ],
  51. * dataInit: function(jElem) { jElem.datepicker(options); },
  52. * dataEvents: [ // these are the same options that you pass to $("#id").bind(type, [data], fn)
  53. * { type: 'click', data: { i: 7 }, fn: function(e) { console.log(e.data.i); } },
  54. * { type: 'keypress', fn: function(e) { console.log('keypress'); } }
  55. * ]
  56. * },
  57. * { ... }
  58. * ]
  59. * options: name:value properties containing various creation options
  60. * see jQuery.fn.searchFilter.defaults for the overridable options
  61. *
  62. * RETURN TYPE: This plugin returns a SearchFilter object, which has additional SearchFilter methods:
  63. * Methods
  64. * add: Adds a filter. added to the end of the list unless a jQuery event object or valid row number is passed.
  65. * del: Removes a filter. removed from the end of the list unless a jQuery event object or valid row number is passed.
  66. * reset: resets filters back to original state (only one blank filter), and calls onReset
  67. * search: puts the search rules into an object and calls onSearch with it
  68. * close: calls the onClose event handler
  69. *
  70. * USAGE
  71. * HTML
  72. * <head>
  73. * ...
  74. * <script src="path/to/jquery.min.js" type="text/javascript"></script>
  75. * <link href="path/to/themeroller.css" rel="Stylesheet" type="text/css" />
  76. * <script src="path/to/jquery.searchFilter.js" type="text/javascript"></script>
  77. * <link href="path/to/jquery.searchFilter.css" rel="Stylesheet" type="text/css" />
  78. * ...
  79. * </head>
  80. * <body>
  81. * ...
  82. * <div id='mySearch'></div>
  83. * ...
  84. * </body>
  85. * JQUERY
  86. * Methods
  87. * initializing: $("#mySearch").searchFilter([{text: "Field 1", value: "field1"},{text: "Field 2", value: "field2"}], {onSearch: myFilterRuleReceiverFn, onReset: myFilterResetFn });
  88. * Manual Methods (there's no need to call these methods unless you are trying to manipulate searchFilter with script)
  89. * add: $("#mySearch").searchFilter().add(); // appends a blank filter
  90. * $("#mySearch").searchFilter().add(0); // copies the first filter as second
  91. * del: $("#mySearch").searchFilter().del(); // removes the bottom filter
  92. * $("#mySearch").searchFilter().del(1); // removes the second filter
  93. * search: $("#mySearch").searchFilter().search(); // invokes onSearch, passing it a ruleGroup object
  94. * reset: $("#mySearch").searchFilter().reset(); // resets rules and invokes onReset
  95. * close: $("#mySearch").searchFilter().close(); // without an onClose handler, equivalent to $("#mySearch").hide();
  96. *
  97. * NOTE: You can get the jQuery object back from the SearchFilter object by chaining .$
  98. * Example
  99. * $("#mySearch").searchFilter().add().add().reset().$.hide();
  100. * Verbose Example
  101. * $("#mySearch") // gets jQuery object for the HTML element with id="mySearch"
  102. * .searchFilter() // gets the SearchFilter object for an existing search filter
  103. * .add() // adds a new filter to the end of the list
  104. * .add() // adds another new filter to the end of the list
  105. * .reset() // resets filters back to original state, triggers onReset
  106. * .$ // returns jQuery object for $("#mySearch")
  107. * .hide(); // equivalent to $("#mySearch").hide();
  108. */
  109. jQuery.fn.searchFilter = function(fields, options) {
  110. function SearchFilter(jQ, fields, options) {
  111. //---------------------------------------------------------------
  112. // PUBLIC VARS
  113. //---------------------------------------------------------------
  114. this.$ = jQ; // makes the jQuery object available as .$ from the return value
  115. //---------------------------------------------------------------
  116. // PUBLIC FUNCTIONS
  117. //---------------------------------------------------------------
  118. this.add = function(i) {
  119. if (i == null) jQ.find(".ui-add-last").click();
  120. else jQ.find(".sf:eq(" + i + ") .ui-add").click();
  121. return this;
  122. };
  123. this.del = function(i) {
  124. if (i == null) jQ.find(".sf:last .ui-del").click();
  125. else jQ.find(".sf:eq(" + i + ") .ui-del").click();
  126. return this;
  127. };
  128. this.search = function(e) {
  129. jQ.find(".ui-search").click();
  130. return this;
  131. };
  132. this.reset = function(o) {
  133. if(o===undefined) o = false;
  134. jQ.find(".ui-reset").trigger('click',[o]);
  135. return this;
  136. };
  137. this.close = function() {
  138. jQ.find(".ui-closer").click();
  139. return this;
  140. };
  141. //---------------------------------------------------------------
  142. // "CONSTRUCTOR" (in air quotes)
  143. //---------------------------------------------------------------
  144. if (fields != null) { // type coercion matches undefined as well as null
  145. //---------------------------------------------------------------
  146. // UTILITY FUNCTIONS
  147. //---------------------------------------------------------------
  148. function hover() {
  149. jQuery(this).toggleClass("ui-state-hover");
  150. return false;
  151. }
  152. function active(e) {
  153. jQuery(this).toggleClass("ui-state-active", (e.type == "mousedown"));
  154. return false;
  155. }
  156. function buildOpt(value, text) {
  157. return "<option value='" + value + "'>" + text + "</option>";
  158. }
  159. function buildSel(className, options, isHidden) {
  160. return "<select class='" + className + "'" + (isHidden ? " style='display:none;'" : "") + ">" + options + "</select>";
  161. }
  162. function initData(selector, fn) {
  163. var jElem = jQ.find("tr.sf td.data " + selector);
  164. if (jElem[0] != null)
  165. fn(jElem);
  166. }
  167. function bindDataEvents(selector, events) {
  168. var jElem = jQ.find("tr.sf td.data " + selector);
  169. if (jElem[0] != null) {
  170. jQuery.each(events, function() {
  171. if (this.data != null)
  172. jElem.bind(this.type, this.data, this.fn);
  173. else
  174. jElem.bind(this.type, this.fn);
  175. });
  176. }
  177. }
  178. //---------------------------------------------------------------
  179. // SUPER IMPORTANT PRIVATE VARS
  180. //---------------------------------------------------------------
  181. // copies jQuery.fn.searchFilter.defaults.options properties onto an empty object, then options onto that
  182. var opts = jQuery.extend({}, jQuery.fn.searchFilter.defaults, options);
  183. // this is keeps track of the last asynchronous setup
  184. var highest_late_setup = -1;
  185. //---------------------------------------------------------------
  186. // CREATION PROCESS STARTS
  187. //---------------------------------------------------------------
  188. // generate the global ops
  189. var gOps_html = "";
  190. jQuery.each(opts.groupOps, function() { gOps_html += buildOpt(this.op, this.text); });
  191. gOps_html = "<select name='groupOp'>" + gOps_html + "</select>";
  192. /* original content - doesn't minify very well
  193. jQ
  194. .html("") // clear any old content
  195. .addClass("ui-searchFilter") // add classes
  196. .append( // add content
  197. "\
  198. <div class='ui-widget-overlay' style='z-index: -1'>&nbsp;</div>\
  199. <table class='ui-widget-content ui-corner-all'>\
  200. <thead>\
  201. <tr>\
  202. <td colspan='5' class='ui-widget-header ui-corner-all' style='line-height: 18px;'>\
  203. <div class='ui-closer ui-state-default ui-corner-all ui-helper-clearfix' style='float: right;'>\
  204. <span class='ui-icon ui-icon-close'></span>\
  205. </div>\
  206. " + opts.windowTitle + "\
  207. </td>\
  208. </tr>\
  209. </thead>\
  210. <tbody>\
  211. <tr class='sf'>\
  212. <td class='fields'></td>\
  213. <td class='ops'></td>\
  214. <td class='data'></td>\
  215. <td><div class='ui-del ui-state-default ui-corner-all'><span class='ui-icon ui-icon-minus'></span></div></td>\
  216. <td><div class='ui-add ui-state-default ui-corner-all'><span class='ui-icon ui-icon-plus'></span></div></td>\
  217. </tr>\
  218. <tr>\
  219. <td colspan='5' class='divider'><div>&nbsp;</div></td>\
  220. </tr>\
  221. </tbody>\
  222. <tfoot>\
  223. <tr>\
  224. <td colspan='3'>\
  225. <span class='ui-reset ui-state-default ui-corner-all' style='display: inline-block; float: left;'><span class='ui-icon ui-icon-arrowreturnthick-1-w' style='float: left;'></span><span style='line-height: 18px; padding: 0 7px 0 3px;'>" + opts.resetText + "</span></span>\
  226. <span class='ui-search ui-state-default ui-corner-all' style='display: inline-block; float: right;'><span class='ui-icon ui-icon-search' style='float: left;'></span><span style='line-height: 18px; padding: 0 7px 0 3px;'>" + opts.searchText + "</span></span>\
  227. <span class='matchText'>" + opts.matchText + "</span> \
  228. " + gOps_html + " \
  229. <span class='rulesText'>" + opts.rulesText + "</span>\
  230. </td>\
  231. <td>&nbsp;</td>\
  232. <td><div class='ui-add-last ui-state-default ui-corner-all'><span class='ui-icon ui-icon-plusthick'></span></div></td>\
  233. </tr>\
  234. </tfoot>\
  235. </table>\
  236. ");
  237. /* end hard-to-minify code */
  238. /* begin easier to minify code */
  239. jQ.html("").addClass("ui-searchFilter").append("<div class='ui-widget-overlay' style='z-index: -1'>&#160;</div><table class='ui-widget-content ui-corner-all'><thead><tr><td colspan='5' class='ui-widget-header ui-corner-all' style='line-height: 18px;'><div class='ui-closer ui-state-default ui-corner-all ui-helper-clearfix' style='float: right;'><span class='ui-icon ui-icon-close'></span></div>" + opts.windowTitle + "</td></tr></thead><tbody><tr class='sf'><td class='fields'></td><td class='ops'></td><td class='data'></td><td><div class='ui-del ui-state-default ui-corner-all'><span class='ui-icon ui-icon-minus'></span></div></td><td><div class='ui-add ui-state-default ui-corner-all'><span class='ui-icon ui-icon-plus'></span></div></td></tr><tr><td colspan='5' class='divider'><hr class='ui-widget-content' style='margin:1px'/></td></tr></tbody><tfoot><tr><td colspan='3'><span class='ui-reset ui-state-default ui-corner-all' style='display: inline-block; float: left;'><span class='ui-icon ui-icon-arrowreturnthick-1-w' style='float: left;'></span><span style='line-height: 18px; padding: 0 7px 0 3px;'>" + opts.resetText + "</span></span><span class='ui-search ui-state-default ui-corner-all' style='display: inline-block; float: right;'><span class='ui-icon ui-icon-search' style='float: left;'></span><span style='line-height: 18px; padding: 0 7px 0 3px;'>" + opts.searchText + "</span></span><span class='matchText'>" + opts.matchText + "</span> " + gOps_html + " <span class='rulesText'>" + opts.rulesText + "</span></td><td>&#160;</td><td><div class='ui-add-last ui-state-default ui-corner-all'><span class='ui-icon ui-icon-plusthick'></span></div></td></tr></tfoot></table>");
  240. /* end easier-to-minify code */
  241. var jRow = jQ.find("tr.sf");
  242. var jFields = jRow.find("td.fields");
  243. var jOps = jRow.find("td.ops");
  244. var jData = jRow.find("td.data");
  245. // generate the defaults
  246. var default_ops_html = "";
  247. jQuery.each(opts.operators, function() { default_ops_html += buildOpt(this.op, this.text); });
  248. default_ops_html = buildSel("default", default_ops_html, true);
  249. jOps.append(default_ops_html);
  250. var default_data_html = "<input type='text' class='default' style='display:none;' />";
  251. jData.append(default_data_html);
  252. // generate the field list as a string
  253. var fields_html = "";
  254. var has_custom_ops = false;
  255. var has_custom_data = false;
  256. jQuery.each(fields, function(i) {
  257. var field_num = i;
  258. fields_html += buildOpt(this.itemval, this.text);
  259. // add custom ops if they exist
  260. if (this.ops != null) {
  261. has_custom_ops = true;
  262. var custom_ops = "";
  263. jQuery.each(this.ops, function() { custom_ops += buildOpt(this.op, this.text); });
  264. custom_ops = buildSel("field" + field_num, custom_ops, true);
  265. jOps.append(custom_ops);
  266. }
  267. // add custom data if it is given
  268. if (this.dataUrl != null) {
  269. if (i > highest_late_setup) highest_late_setup = i;
  270. has_custom_data = true;
  271. var dEvents = this.dataEvents;
  272. var iEvent = this.dataInit;
  273. var bs = this.buildSelect;
  274. jQuery.ajax(jQuery.extend({
  275. url : this.dataUrl,
  276. complete: function(data) {
  277. var $d;
  278. if(bs != null) $d =jQuery("<div />").append(bs(data));
  279. else $d = jQuery("<div />").append(data.responseText);
  280. $d.find("select").addClass("field" + field_num).hide();
  281. jData.append($d.html());
  282. if (iEvent) initData(".field" + i, iEvent);
  283. if (dEvents) bindDataEvents(".field" + i, dEvents);
  284. if (i == highest_late_setup) { // change should get called no more than twice when this searchFilter is constructed
  285. jQ.find("tr.sf td.fields select[name='field']").change();
  286. }
  287. }
  288. },opts.ajaxSelectOptions));
  289. } else if (this.dataValues != null) {
  290. has_custom_data = true;
  291. var custom_data = "";
  292. jQuery.each(this.dataValues, function() { custom_data += buildOpt(this.value, this.text); });
  293. custom_data = buildSel("field" + field_num, custom_data, true);
  294. jData.append(custom_data);
  295. } else if (this.dataEvents != null || this.dataInit != null) {
  296. has_custom_data = true;
  297. var custom_data = "<input type='text' class='field" + field_num + "' />";
  298. jData.append(custom_data);
  299. }
  300. // attach events to data if they exist
  301. if (this.dataInit != null && i != highest_late_setup)
  302. initData(".field" + i, this.dataInit);
  303. if (this.dataEvents != null && i != highest_late_setup)
  304. bindDataEvents(".field" + i, this.dataEvents);
  305. });
  306. fields_html = "<select name='field'>" + fields_html + "</select>";
  307. jFields.append(fields_html);
  308. // setup the field select with an on-change event if there are custom ops or data
  309. var jFSelect = jFields.find("select[name='field']");
  310. if (has_custom_ops) jFSelect.change(function(e) {
  311. var index = e.target.selectedIndex;
  312. var td = jQuery(e.target).parents("tr.sf").find("td.ops");
  313. td.find("select").removeAttr("name").hide(); // disown and hide all elements
  314. var jElem = td.find(".field" + index);
  315. if (jElem[0] == null) jElem = td.find(".default"); // if there's not an element for that field, use the default one
  316. jElem.attr("name", "op").show();
  317. return false;
  318. });
  319. else jOps.find(".default").attr("name", "op").show();
  320. if (has_custom_data) jFSelect.change(function(e) {
  321. var index = e.target.selectedIndex;
  322. var td = jQuery(e.target).parents("tr.sf").find("td.data");
  323. td.find("select,input").removeClass("vdata").hide(); // disown and hide all elements
  324. var jElem = td.find(".field" + index);
  325. if (jElem[0] == null) jElem = td.find(".default"); // if there's not an element for that field, use the default one
  326. jElem.show().addClass("vdata");
  327. return false;
  328. });
  329. else jData.find(".default").show().addClass("vdata");
  330. // go ahead and call the change event and setup the ops and data values
  331. if (has_custom_ops || has_custom_data) jFSelect.change();
  332. // bind events
  333. jQ.find(".ui-state-default").hover(hover, hover).mousedown(active).mouseup(active); // add hover/active effects to all buttons
  334. jQ.find(".ui-closer").click(function(e) {
  335. opts.onClose(jQuery(jQ.selector));
  336. return false;
  337. });
  338. jQ.find(".ui-del").click(function(e) {
  339. var row = jQuery(e.target).parents(".sf");
  340. if (row.siblings(".sf").length > 0) { // doesn't remove if there's only one filter left
  341. if (opts.datepickerFix === true && jQuery.fn.datepicker !== undefined)
  342. row.find(".hasDatepicker").datepicker("destroy"); // clean up datepicker's $.data mess
  343. row.remove(); // also unbinds
  344. } else { // resets the filter if it's the last one
  345. row.find("select[name='field']")[0].selectedIndex = 0;
  346. row.find("select[name='op']")[0].selectedIndex = 0;
  347. row.find(".data input").val(""); // blank all input values
  348. row.find(".data select").each(function() { this.selectedIndex = 0; }); // select first option on all selects
  349. row.find("select[name='field']").change(function(event){event.stopPropagation();}); // trigger any change events
  350. }
  351. return false;
  352. });
  353. jQ.find(".ui-add").click(function(e) {
  354. var row = jQuery(e.target).parents(".sf");
  355. var newRow = row.clone(true).insertAfter(row);
  356. newRow.find(".ui-state-default").removeClass("ui-state-hover ui-state-active");
  357. if (opts.clone) {
  358. newRow.find("select[name='field']")[0].selectedIndex = row.find("select[name='field']")[0].selectedIndex;
  359. var stupid_browser = (newRow.find("select[name='op']")[0] == null); // true for IE6
  360. if (!stupid_browser)
  361. newRow.find("select[name='op']").focus()[0].selectedIndex = row.find("select[name='op']")[0].selectedIndex;
  362. var jElem = newRow.find("select.vdata");
  363. if (jElem[0] != null) // select doesn't copy it's selected index when cloned
  364. jElem[0].selectedIndex = row.find("select.vdata")[0].selectedIndex;
  365. } else {
  366. newRow.find(".data input").val(""); // blank all input values
  367. newRow.find("select[name='field']").focus();
  368. }
  369. if (opts.datepickerFix === true && jQuery.fn.datepicker !== undefined) { // using $.data to associate data with document elements is Not Good
  370. row.find(".hasDatepicker").each(function() {
  371. var settings = jQuery.data(this, "datepicker").settings;
  372. newRow.find("#" + this.id).unbind().removeAttr("id").removeClass("hasDatepicker").datepicker(settings);
  373. });
  374. }
  375. newRow.find("select[name='field']").change(function(event){event.stopPropagation();} );
  376. return false;
  377. });
  378. jQ.find(".ui-search").click(function(e) {
  379. var ui = jQuery(jQ.selector); // pointer to search box wrapper element
  380. var ruleGroup;
  381. var group_op = ui.find("select[name='groupOp'] :selected").val(); // puls "AND" or "OR"
  382. if (!opts.stringResult) {
  383. ruleGroup = {
  384. groupOp: group_op,
  385. rules: []
  386. };
  387. } else {
  388. ruleGroup = "{\"groupOp\":\"" + group_op + "\",\"rules\":[";
  389. }
  390. ui.find(".sf").each(function(i) {
  391. var tField = jQuery(this).find("select[name='field'] :selected").val();
  392. var tOp = jQuery(this).find("select[name='op'] :selected").val();
  393. var tData = jQuery(this).find("input.vdata,select.vdata :selected").val();
  394. tData += "";
  395. if (!opts.stringResult) {
  396. ruleGroup.rules.push({
  397. field: tField,
  398. op: tOp,
  399. data: tData
  400. });
  401. } else {
  402. tData = tData.replace(/\\/g,'\\\\').replace(/\"/g,'\\"');
  403. if (i > 0) ruleGroup += ",";
  404. ruleGroup += "{\"field\":\"" + tField + "\",";
  405. ruleGroup += "\"op\":\"" + tOp + "\",";
  406. ruleGroup += "\"data\":\"" + tData + "\"}";
  407. }
  408. });
  409. if (opts.stringResult) ruleGroup += "]}";
  410. opts.onSearch(ruleGroup);
  411. return false;
  412. });
  413. jQ.find(".ui-reset").click(function(e,op) {
  414. var ui = jQuery(jQ.selector);
  415. ui.find(".ui-del").click(); // removes all filters, resets the last one
  416. ui.find("select[name='groupOp']")[0].selectedIndex = 0; // changes the op back to the default one
  417. opts.onReset(op);
  418. return false;
  419. });
  420. jQ.find(".ui-add-last").click(function() {
  421. var row = jQuery(jQ.selector + " .sf:last");
  422. var newRow = row.clone(true).insertAfter(row);
  423. newRow.find(".ui-state-default").removeClass("ui-state-hover ui-state-active");
  424. newRow.find(".data input").val(""); // blank all input values
  425. newRow.find("select[name='field']").focus();
  426. if (opts.datepickerFix === true && jQuery.fn.datepicker !== undefined) { // using $.data to associate data with document elements is Not Good
  427. row.find(".hasDatepicker").each(function() {
  428. var settings = jQuery.data(this, "datepicker").settings;
  429. newRow.find("#" + this.id).unbind().removeAttr("id").removeClass("hasDatepicker").datepicker(settings);
  430. });
  431. }
  432. newRow.find("select[name='field']").change(function(event){event.stopPropagation();});
  433. return false;
  434. });
  435. this.setGroupOp = function(setting) {
  436. /* a "setter" for groupping argument.
  437. * ("AND" or "OR")
  438. *
  439. * Inputs:
  440. * setting - a string
  441. *
  442. * Returns:
  443. * Does not return anything. May add success / failure reporting in future versions.
  444. *
  445. * author: Daniel Dotsenko (dotsa@hotmail.com)
  446. */
  447. selDOMobj = jQ.find("select[name='groupOp']")[0];
  448. var indexmap = {}, l = selDOMobj.options.length, i;
  449. for (i=0; i<l; i++) {
  450. indexmap[selDOMobj.options[i].value] = i;
  451. }
  452. selDOMobj.selectedIndex = indexmap[setting];
  453. jQuery(selDOMobj).change(function(event){event.stopPropagation();});
  454. };
  455. this.setFilter = function(settings) {
  456. /* a "setter" for an arbitrary SearchFilter's filter line.
  457. * designed to abstract the DOM manipulations required to infer
  458. * a particular filter is a fit to the search box.
  459. *
  460. * Inputs:
  461. * settings - an "object" (dictionary)
  462. * index (optional*) (to be implemented in the future) : signed integer index (from top to bottom per DOM) of the filter line to fill.
  463. * Negative integers (rooted in -1 and lower) denote position of the line from the bottom.
  464. * sfref (optional*) : DOM object referencing individual '.sf' (normally a TR element) to be populated. (optional)
  465. * filter (mandatory) : object (dictionary) of form {'field':'field_value','op':'op_value','data':'data value'}
  466. *
  467. * * It is mandatory to have either index or sfref defined.
  468. *
  469. * Returns:
  470. * Does not return anything. May add success / failure reporting in future versions.
  471. *
  472. * author: Daniel Dotsenko (dotsa@hotmail.com)
  473. */
  474. var o = settings['sfref'], filter = settings['filter'];
  475. // setting up valueindexmap that we will need to manipulate SELECT elements.
  476. var fields = [], i, j , l, lj, li,
  477. valueindexmap = {};
  478. // example of valueindexmap:
  479. // {'field1':{'index':0,'ops':{'eq':0,'ne':1}},'fieldX':{'index':1,'ops':{'eq':0,'ne':1},'data':{'true':0,'false':1}}},
  480. // if data is undefined it's a INPUT field. If defined, it's SELECT
  481. selDOMobj = o.find("select[name='field']")[0];
  482. for (i=0, l=selDOMobj.options.length; i<l; i++) {
  483. valueindexmap[selDOMobj.options[i].value] = {'index':i,'ops':{}};
  484. fields.push(selDOMobj.options[i].value);
  485. }
  486. for (i=0, li=fields.length; i < li; i++) {
  487. selDOMobj = o.find(".ops > select[class='field"+i+"']")[0];
  488. if (selDOMobj) {
  489. for (j=0, lj=selDOMobj.options.length; j<lj; j++) {
  490. valueindexmap[fields[i]]['ops'][selDOMobj.options[j].value] = j;
  491. }
  492. }
  493. selDOMobj = o.find(".data > select[class='field"+i+"']")[0];
  494. if (selDOMobj) {
  495. valueindexmap[fields[i]]['data'] = {}; // this setting is the flag that 'data' is contained in a SELECT
  496. for (j=0, lj=selDOMobj.options.length; j<lj; j++) {
  497. valueindexmap[fields[i]]['data'][selDOMobj.options[j].value] = j;
  498. }
  499. }
  500. } // done populating valueindexmap
  501. // preparsing the index values for SELECT elements.
  502. var fieldvalue, fieldindex, opindex, datavalue, dataindex;
  503. fieldvalue = filter['field'];
  504. if (valueindexmap[fieldvalue]) {
  505. fieldindex = valueindexmap[fieldvalue]['index'];
  506. }
  507. if (fieldindex != null) {
  508. opindex = valueindexmap[fieldvalue]['ops'][filter['op']];
  509. if(opindex === undefined) {
  510. for(i=0,li=options.operators.length; i<li;i++) {
  511. if(options.operators[i].op == filter.op ){
  512. opindex = i;
  513. break;
  514. }
  515. }
  516. }
  517. datavalue = filter['data'];
  518. if (valueindexmap[fieldvalue]['data'] == null) {
  519. dataindex = -1; // 'data' is not SELECT, Making the var 'defined'
  520. } else {
  521. dataindex = valueindexmap[fieldvalue]['data'][datavalue]; // 'undefined' may come from here.
  522. }
  523. }
  524. // only if values for 'field' and 'op' and 'data' are 'found' in mapping...
  525. if (fieldindex != null && opindex != null && dataindex != null) {
  526. o.find("select[name='field']")[0].selectedIndex = fieldindex;
  527. o.find("select[name='field']").change();
  528. o.find("select[name='op']")[0].selectedIndex = opindex;
  529. o.find("input.vdata").val(datavalue); // if jquery does not find any INPUT, it does not set any. This means we deal with SELECT
  530. o = o.find("select.vdata")[0];
  531. if (o) {
  532. o.selectedIndex = dataindex;
  533. }
  534. return true
  535. } else {
  536. return false
  537. }
  538. }; // end of this.setFilter fn
  539. } // end of if fields != null
  540. }
  541. return new SearchFilter(this, fields, options);
  542. };
  543. jQuery.fn.searchFilter.version = '1.2.9';
  544. /* This property contains the default options */
  545. jQuery.fn.searchFilter.defaults = {
  546. /*
  547. * PROPERTY
  548. * TYPE: boolean
  549. * DESCRIPTION: clone a row if it is added from an existing row
  550. * when false, any new added rows will be blank.
  551. */
  552. clone: true,
  553. /*
  554. * PROPERTY
  555. * TYPE: boolean
  556. * DESCRIPTION: current version of datepicker uses a data store,
  557. * which is incompatible with $().clone(true)
  558. */
  559. datepickerFix: true,
  560. /*
  561. * FUNCTION
  562. * DESCRIPTION: the function that will be called when the user clicks Reset
  563. * INPUT TYPE: JS object if stringResult is false, otherwise is JSON string
  564. */
  565. onReset: function(data) { alert("Reset Clicked. Data Returned: " + data) },
  566. /*
  567. * FUNCTION
  568. * DESCRIPTION: the function that will be called when the user clicks Search
  569. * INPUT TYPE: JS object if stringResult is false, otherwise is JSON string
  570. */
  571. onSearch: function(data) { alert("Search Clicked. Data Returned: " + data) },
  572. /*
  573. * FUNCTION
  574. * DESCRIPTION: the function that will be called when the user clicks the Closer icon
  575. * or the close() function is called
  576. * if left null, it simply does a .hide() on the searchFilter
  577. * INPUT TYPE: a jQuery object for the searchFilter
  578. */
  579. onClose: function(jElem) { jElem.hide(); },
  580. /*
  581. * PROPERTY
  582. * TYPE: array of objects, each object has the properties op and text
  583. * DESCRIPTION: the selectable operators that are applied between rules
  584. * e.g. for {op:"AND", text:"all"}
  585. * the search filter box will say: match all rules
  586. * the server should interpret this as putting the AND op between each rule:
  587. * rule1 AND rule2 AND rule3
  588. * text will be the option text, and op will be the option value
  589. */
  590. groupOps: [
  591. { op: "AND", text: "all" },
  592. { op: "OR", text: "any" }
  593. ],
  594. /*
  595. * PROPERTY
  596. * TYPE: array of objects, each object has the properties op and text
  597. * DESCRIPTION: the operators that will appear as drop-down options
  598. * text will be the option text, and op will be the option value
  599. */
  600. operators: [
  601. { op: "eq", text: "is equal to" },
  602. { op: "ne", text: "is not equal to" },
  603. { op: "lt", text: "is less than" },
  604. { op: "le", text: "is less or equal to" },
  605. { op: "gt", text: "is greater than" },
  606. { op: "ge", text: "is greater or equal to" },
  607. { op: "in", text: "is in" },
  608. { op: "ni", text: "is not in" },
  609. { op: "bw", text: "begins with" },
  610. { op: "bn", text: "does not begin with" },
  611. { op: "ew", text: "ends with" },
  612. { op: "en", text: "does not end with" },
  613. { op: "cn", text: "contains" },
  614. { op: "nc", text: "does not contain" }
  615. ],
  616. /*
  617. * PROPERTY
  618. * TYPE: string
  619. * DESCRIPTION: part of the phrase: _match_ ANY/ALL rules
  620. */
  621. matchText: "match",
  622. /*
  623. * PROPERTY
  624. * TYPE: string
  625. * DESCRIPTION: part of the phrase: match ANY/ALL _rules_
  626. */
  627. rulesText: "rules",
  628. /*
  629. * PROPERTY
  630. * TYPE: string
  631. * DESCRIPTION: the text that will be displayed in the reset button
  632. */
  633. resetText: "Reset",
  634. /*
  635. * PROPERTY
  636. * TYPE: string
  637. * DESCRIPTION: the text that will be displayed in the search button
  638. */
  639. searchText: "Search",
  640. /*
  641. * PROPERTY
  642. * TYPE: boolean
  643. * DESCRIPTION: a flag that, when set, will make the onSearch and onReset return strings instead of objects
  644. */
  645. stringResult: true,
  646. /*
  647. * PROPERTY
  648. * TYPE: string
  649. * DESCRIPTION: the title of the searchFilter window
  650. */
  651. windowTitle: "Search Rules",
  652. /*
  653. * PROPERTY
  654. * TYPE: object
  655. * DESCRIPTION: options to extend the ajax request
  656. */
  657. ajaxSelectOptions : {}
  658. }; /* end of searchFilter */