zfben_rails_assets 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,716 @@
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
+
110
+ jQuery.fn.searchFilter = function(fields, options) {
111
+
112
+ function SearchFilter(jQ, fields, options) {
113
+
114
+
115
+ //---------------------------------------------------------------
116
+ // PUBLIC VARS
117
+ //---------------------------------------------------------------
118
+
119
+ this.$ = jQ; // makes the jQuery object available as .$ from the return value
120
+
121
+
122
+ //---------------------------------------------------------------
123
+ // PUBLIC FUNCTIONS
124
+ //---------------------------------------------------------------
125
+
126
+ this.add = function(i) {
127
+ if (i == null) jQ.find(".ui-add-last").click();
128
+ else jQ.find(".sf:eq(" + i + ") .ui-add").click();
129
+ return this;
130
+ };
131
+
132
+ this.del = function(i) {
133
+ if (i == null) jQ.find(".sf:last .ui-del").click();
134
+ else jQ.find(".sf:eq(" + i + ") .ui-del").click();
135
+ return this;
136
+ };
137
+
138
+ this.search = function(e) {
139
+ jQ.find(".ui-search").click();
140
+ return this;
141
+ };
142
+
143
+ this.reset = function(o) {
144
+ if(o===undefined) o = false;
145
+ jQ.find(".ui-reset").trigger('click',[o]);
146
+ return this;
147
+ };
148
+
149
+ this.close = function() {
150
+ jQ.find(".ui-closer").click();
151
+ return this;
152
+ };
153
+
154
+
155
+
156
+ //---------------------------------------------------------------
157
+ // "CONSTRUCTOR" (in air quotes)
158
+ //---------------------------------------------------------------
159
+
160
+ if (fields != null) { // type coercion matches undefined as well as null
161
+
162
+
163
+ //---------------------------------------------------------------
164
+ // UTILITY FUNCTIONS
165
+ //---------------------------------------------------------------
166
+
167
+ function hover() {
168
+ jQuery(this).toggleClass("ui-state-hover");
169
+ return false;
170
+ }
171
+
172
+ function active(e) {
173
+ jQuery(this).toggleClass("ui-state-active", (e.type == "mousedown"));
174
+ return false;
175
+ }
176
+
177
+ function buildOpt(value, text) {
178
+ return "<option value='" + value + "'>" + text + "</option>";
179
+ }
180
+
181
+ function buildSel(className, options, isHidden) {
182
+ return "<select class='" + className + "'" + (isHidden ? " style='display:none;'" : "") + ">" + options + "</select>";
183
+ }
184
+
185
+ function initData(selector, fn) {
186
+ var jElem = jQ.find("tr.sf td.data " + selector);
187
+ if (jElem[0] != null)
188
+ fn(jElem);
189
+ }
190
+
191
+ function bindDataEvents(selector, events) {
192
+ var jElem = jQ.find("tr.sf td.data " + selector);
193
+ if (jElem[0] != null) {
194
+ jQuery.each(events, function() {
195
+ if (this.data != null)
196
+ jElem.bind(this.type, this.data, this.fn);
197
+ else
198
+ jElem.bind(this.type, this.fn);
199
+ });
200
+ }
201
+ }
202
+
203
+
204
+ //---------------------------------------------------------------
205
+ // SUPER IMPORTANT PRIVATE VARS
206
+ //---------------------------------------------------------------
207
+
208
+ // copies jQuery.fn.searchFilter.defaults.options properties onto an empty object, then options onto that
209
+ var opts = jQuery.extend({}, jQuery.fn.searchFilter.defaults, options);
210
+
211
+ // this is keeps track of the last asynchronous setup
212
+ var highest_late_setup = -1;
213
+
214
+
215
+ //---------------------------------------------------------------
216
+ // CREATION PROCESS STARTS
217
+ //---------------------------------------------------------------
218
+
219
+ // generate the global ops
220
+ var gOps_html = "";
221
+ jQuery.each(opts.groupOps, function() { gOps_html += buildOpt(this.op, this.text); });
222
+ gOps_html = "<select name='groupOp'>" + gOps_html + "</select>";
223
+
224
+ /* original content - doesn't minify very well
225
+ jQ
226
+ .html("") // clear any old content
227
+ .addClass("ui-searchFilter") // add classes
228
+ .append( // add content
229
+ "\
230
+ <div class='ui-widget-overlay' style='z-index: -1'>&nbsp;</div>\
231
+ <table class='ui-widget-content ui-corner-all'>\
232
+ <thead>\
233
+ <tr>\
234
+ <td colspan='5' class='ui-widget-header ui-corner-all' style='line-height: 18px;'>\
235
+ <div class='ui-closer ui-state-default ui-corner-all ui-helper-clearfix' style='float: right;'>\
236
+ <span class='ui-icon ui-icon-close'></span>\
237
+ </div>\
238
+ " + opts.windowTitle + "\
239
+ </td>\
240
+ </tr>\
241
+ </thead>\
242
+ <tbody>\
243
+ <tr class='sf'>\
244
+ <td class='fields'></td>\
245
+ <td class='ops'></td>\
246
+ <td class='data'></td>\
247
+ <td><div class='ui-del ui-state-default ui-corner-all'><span class='ui-icon ui-icon-minus'></span></div></td>\
248
+ <td><div class='ui-add ui-state-default ui-corner-all'><span class='ui-icon ui-icon-plus'></span></div></td>\
249
+ </tr>\
250
+ <tr>\
251
+ <td colspan='5' class='divider'><div>&nbsp;</div></td>\
252
+ </tr>\
253
+ </tbody>\
254
+ <tfoot>\
255
+ <tr>\
256
+ <td colspan='3'>\
257
+ <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>\
258
+ <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>\
259
+ <span class='matchText'>" + opts.matchText + "</span> \
260
+ " + gOps_html + " \
261
+ <span class='rulesText'>" + opts.rulesText + "</span>\
262
+ </td>\
263
+ <td>&nbsp;</td>\
264
+ <td><div class='ui-add-last ui-state-default ui-corner-all'><span class='ui-icon ui-icon-plusthick'></span></div></td>\
265
+ </tr>\
266
+ </tfoot>\
267
+ </table>\
268
+ ");
269
+ /* end hard-to-minify code */
270
+ /* begin easier to minify code */
271
+ 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>");
272
+ /* end easier-to-minify code */
273
+
274
+ var jRow = jQ.find("tr.sf");
275
+ var jFields = jRow.find("td.fields");
276
+ var jOps = jRow.find("td.ops");
277
+ var jData = jRow.find("td.data");
278
+
279
+ // generate the defaults
280
+ var default_ops_html = "";
281
+ jQuery.each(opts.operators, function() { default_ops_html += buildOpt(this.op, this.text); });
282
+ default_ops_html = buildSel("default", default_ops_html, true);
283
+ jOps.append(default_ops_html);
284
+ var default_data_html = "<input type='text' class='default' style='display:none;' />";
285
+ jData.append(default_data_html);
286
+
287
+ // generate the field list as a string
288
+ var fields_html = "";
289
+ var has_custom_ops = false;
290
+ var has_custom_data = false;
291
+ jQuery.each(fields, function(i) {
292
+ var field_num = i;
293
+ fields_html += buildOpt(this.itemval, this.text);
294
+ // add custom ops if they exist
295
+ if (this.ops != null) {
296
+ has_custom_ops = true;
297
+ var custom_ops = "";
298
+ jQuery.each(this.ops, function() { custom_ops += buildOpt(this.op, this.text); });
299
+ custom_ops = buildSel("field" + field_num, custom_ops, true);
300
+ jOps.append(custom_ops);
301
+ }
302
+ // add custom data if it is given
303
+ if (this.dataUrl != null) {
304
+ if (i > highest_late_setup) highest_late_setup = i;
305
+ has_custom_data = true;
306
+ var dEvents = this.dataEvents;
307
+ var iEvent = this.dataInit;
308
+ var bs = this.buildSelect;
309
+ jQuery.ajax(jQuery.extend({
310
+ url : this.dataUrl,
311
+ complete: function(data) {
312
+ var $d;
313
+ if(bs != null) $d =jQuery("<div />").append(bs(data));
314
+ else $d = jQuery("<div />").append(data.responseText);
315
+ $d.find("select").addClass("field" + field_num).hide();
316
+ jData.append($d.html());
317
+ if (iEvent) initData(".field" + i, iEvent);
318
+ if (dEvents) bindDataEvents(".field" + i, dEvents);
319
+ if (i == highest_late_setup) { // change should get called no more than twice when this searchFilter is constructed
320
+ jQ.find("tr.sf td.fields select[name='field']").change();
321
+ }
322
+ }
323
+ },opts.ajaxSelectOptions));
324
+ } else if (this.dataValues != null) {
325
+ has_custom_data = true;
326
+ var custom_data = "";
327
+ jQuery.each(this.dataValues, function() { custom_data += buildOpt(this.value, this.text); });
328
+ custom_data = buildSel("field" + field_num, custom_data, true);
329
+ jData.append(custom_data);
330
+ } else if (this.dataEvents != null || this.dataInit != null) {
331
+ has_custom_data = true;
332
+ var custom_data = "<input type='text' class='field" + field_num + "' />";
333
+ jData.append(custom_data);
334
+ }
335
+ // attach events to data if they exist
336
+ if (this.dataInit != null && i != highest_late_setup)
337
+ initData(".field" + i, this.dataInit);
338
+ if (this.dataEvents != null && i != highest_late_setup)
339
+ bindDataEvents(".field" + i, this.dataEvents);
340
+ });
341
+ fields_html = "<select name='field'>" + fields_html + "</select>";
342
+ jFields.append(fields_html);
343
+
344
+ // setup the field select with an on-change event if there are custom ops or data
345
+ var jFSelect = jFields.find("select[name='field']");
346
+ if (has_custom_ops) jFSelect.change(function(e) {
347
+ var index = e.target.selectedIndex;
348
+ var td = jQuery(e.target).parents("tr.sf").find("td.ops");
349
+ td.find("select").removeAttr("name").hide(); // disown and hide all elements
350
+ var jElem = td.find(".field" + index);
351
+ if (jElem[0] == null) jElem = td.find(".default"); // if there's not an element for that field, use the default one
352
+ jElem.attr("name", "op").show();
353
+ return false;
354
+ });
355
+ else jOps.find(".default").attr("name", "op").show();
356
+ if (has_custom_data) jFSelect.change(function(e) {
357
+ var index = e.target.selectedIndex;
358
+ var td = jQuery(e.target).parents("tr.sf").find("td.data");
359
+ td.find("select,input").removeClass("vdata").hide(); // disown and hide all elements
360
+ var jElem = td.find(".field" + index);
361
+ if (jElem[0] == null) jElem = td.find(".default"); // if there's not an element for that field, use the default one
362
+ jElem.show().addClass("vdata");
363
+ return false;
364
+ });
365
+ else jData.find(".default").show().addClass("vdata");
366
+ // go ahead and call the change event and setup the ops and data values
367
+ if (has_custom_ops || has_custom_data) jFSelect.change();
368
+
369
+ // bind events
370
+ jQ.find(".ui-state-default").hover(hover, hover).mousedown(active).mouseup(active); // add hover/active effects to all buttons
371
+ jQ.find(".ui-closer").click(function(e) {
372
+ opts.onClose(jQuery(jQ.selector));
373
+ return false;
374
+ });
375
+ jQ.find(".ui-del").click(function(e) {
376
+ var row = jQuery(e.target).parents(".sf");
377
+ if (row.siblings(".sf").length > 0) { // doesn't remove if there's only one filter left
378
+ if (opts.datepickerFix === true && jQuery.fn.datepicker !== undefined)
379
+ row.find(".hasDatepicker").datepicker("destroy"); // clean up datepicker's $.data mess
380
+ row.remove(); // also unbinds
381
+ } else { // resets the filter if it's the last one
382
+ row.find("select[name='field']")[0].selectedIndex = 0;
383
+ row.find("select[name='op']")[0].selectedIndex = 0;
384
+ row.find(".data input").val(""); // blank all input values
385
+ row.find(".data select").each(function() { this.selectedIndex = 0; }); // select first option on all selects
386
+ row.find("select[name='field']").change(function(event){event.stopPropagation();}); // trigger any change events
387
+ }
388
+ return false;
389
+ });
390
+ jQ.find(".ui-add").click(function(e) {
391
+ var row = jQuery(e.target).parents(".sf");
392
+ var newRow = row.clone(true).insertAfter(row);
393
+ newRow.find(".ui-state-default").removeClass("ui-state-hover ui-state-active");
394
+ if (opts.clone) {
395
+ newRow.find("select[name='field']")[0].selectedIndex = row.find("select[name='field']")[0].selectedIndex;
396
+ var stupid_browser = (newRow.find("select[name='op']")[0] == null); // true for IE6
397
+ if (!stupid_browser)
398
+ newRow.find("select[name='op']").focus()[0].selectedIndex = row.find("select[name='op']")[0].selectedIndex;
399
+ var jElem = newRow.find("select.vdata");
400
+ if (jElem[0] != null) // select doesn't copy it's selected index when cloned
401
+ jElem[0].selectedIndex = row.find("select.vdata")[0].selectedIndex;
402
+ } else {
403
+ newRow.find(".data input").val(""); // blank all input values
404
+ newRow.find("select[name='field']").focus();
405
+ }
406
+ if (opts.datepickerFix === true && jQuery.fn.datepicker !== undefined) { // using $.data to associate data with document elements is Not Good
407
+ row.find(".hasDatepicker").each(function() {
408
+ var settings = jQuery.data(this, "datepicker").settings;
409
+ newRow.find("#" + this.id).unbind().removeAttr("id").removeClass("hasDatepicker").datepicker(settings);
410
+ });
411
+ }
412
+ newRow.find("select[name='field']").change(function(event){event.stopPropagation();} );
413
+ return false;
414
+ });
415
+ jQ.find(".ui-search").click(function(e) {
416
+ var ui = jQuery(jQ.selector); // pointer to search box wrapper element
417
+ var ruleGroup;
418
+ var group_op = ui.find("select[name='groupOp'] :selected").val(); // puls "AND" or "OR"
419
+ if (!opts.stringResult) {
420
+ ruleGroup = {
421
+ groupOp: group_op,
422
+ rules: []
423
+ };
424
+ } else {
425
+ ruleGroup = "{\"groupOp\":\"" + group_op + "\",\"rules\":[";
426
+ }
427
+ ui.find(".sf").each(function(i) {
428
+ var tField = jQuery(this).find("select[name='field'] :selected").val();
429
+ var tOp = jQuery(this).find("select[name='op'] :selected").val();
430
+ var tData = jQuery(this).find("input.vdata,select.vdata :selected").val();
431
+ tData += "";
432
+ if (!opts.stringResult) {
433
+ ruleGroup.rules.push({
434
+ field: tField,
435
+ op: tOp,
436
+ data: tData
437
+ });
438
+ } else {
439
+ tData = tData.replace(/\\/g,'\\\\').replace(/\"/g,'\\"');
440
+ if (i > 0) ruleGroup += ",";
441
+ ruleGroup += "{\"field\":\"" + tField + "\",";
442
+ ruleGroup += "\"op\":\"" + tOp + "\",";
443
+ ruleGroup += "\"data\":\"" + tData + "\"}";
444
+ }
445
+ });
446
+ if (opts.stringResult) ruleGroup += "]}";
447
+ opts.onSearch(ruleGroup);
448
+ return false;
449
+ });
450
+ jQ.find(".ui-reset").click(function(e,op) {
451
+ var ui = jQuery(jQ.selector);
452
+ ui.find(".ui-del").click(); // removes all filters, resets the last one
453
+ ui.find("select[name='groupOp']")[0].selectedIndex = 0; // changes the op back to the default one
454
+ opts.onReset(op);
455
+ return false;
456
+ });
457
+ jQ.find(".ui-add-last").click(function() {
458
+ var row = jQuery(jQ.selector + " .sf:last");
459
+ var newRow = row.clone(true).insertAfter(row);
460
+ newRow.find(".ui-state-default").removeClass("ui-state-hover ui-state-active");
461
+ newRow.find(".data input").val(""); // blank all input values
462
+ newRow.find("select[name='field']").focus();
463
+ if (opts.datepickerFix === true && jQuery.fn.datepicker !== undefined) { // using $.data to associate data with document elements is Not Good
464
+ row.find(".hasDatepicker").each(function() {
465
+ var settings = jQuery.data(this, "datepicker").settings;
466
+ newRow.find("#" + this.id).unbind().removeAttr("id").removeClass("hasDatepicker").datepicker(settings);
467
+ });
468
+ }
469
+ newRow.find("select[name='field']").change(function(event){event.stopPropagation();});
470
+ return false;
471
+ });
472
+
473
+ this.setGroupOp = function(setting) {
474
+ /* a "setter" for groupping argument.
475
+ * ("AND" or "OR")
476
+ *
477
+ * Inputs:
478
+ * setting - a string
479
+ *
480
+ * Returns:
481
+ * Does not return anything. May add success / failure reporting in future versions.
482
+ *
483
+ * author: Daniel Dotsenko (dotsa@hotmail.com)
484
+ */
485
+ selDOMobj = jQ.find("select[name='groupOp']")[0];
486
+ var indexmap = {}, l = selDOMobj.options.length, i;
487
+ for (i=0; i<l; i++) {
488
+ indexmap[selDOMobj.options[i].value] = i;
489
+ }
490
+ selDOMobj.selectedIndex = indexmap[setting];
491
+ jQuery(selDOMobj).change(function(event){event.stopPropagation();});
492
+ };
493
+
494
+ this.setFilter = function(settings) {
495
+ /* a "setter" for an arbitrary SearchFilter's filter line.
496
+ * designed to abstract the DOM manipulations required to infer
497
+ * a particular filter is a fit to the search box.
498
+ *
499
+ * Inputs:
500
+ * settings - an "object" (dictionary)
501
+ * index (optional*) (to be implemented in the future) : signed integer index (from top to bottom per DOM) of the filter line to fill.
502
+ * Negative integers (rooted in -1 and lower) denote position of the line from the bottom.
503
+ * sfref (optional*) : DOM object referencing individual '.sf' (normally a TR element) to be populated. (optional)
504
+ * filter (mandatory) : object (dictionary) of form {'field':'field_value','op':'op_value','data':'data value'}
505
+ *
506
+ * * It is mandatory to have either index or sfref defined.
507
+ *
508
+ * Returns:
509
+ * Does not return anything. May add success / failure reporting in future versions.
510
+ *
511
+ * author: Daniel Dotsenko (dotsa@hotmail.com)
512
+ */
513
+
514
+ var o = settings['sfref'], filter = settings['filter'];
515
+
516
+ // setting up valueindexmap that we will need to manipulate SELECT elements.
517
+ var fields = [], i, j , l, lj, li,
518
+ valueindexmap = {};
519
+ // example of valueindexmap:
520
+ // {'field1':{'index':0,'ops':{'eq':0,'ne':1}},'fieldX':{'index':1,'ops':{'eq':0,'ne':1},'data':{'true':0,'false':1}}},
521
+ // if data is undefined it's a INPUT field. If defined, it's SELECT
522
+ selDOMobj = o.find("select[name='field']")[0];
523
+ for (i=0, l=selDOMobj.options.length; i<l; i++) {
524
+ valueindexmap[selDOMobj.options[i].value] = {'index':i,'ops':{}};
525
+ fields.push(selDOMobj.options[i].value);
526
+ }
527
+ for (i=0, li=fields.length; i < li; i++) {
528
+ selDOMobj = o.find(".ops > select[class='field"+i+"']")[0];
529
+ if (selDOMobj) {
530
+ for (j=0, lj=selDOMobj.options.length; j<lj; j++) {
531
+ valueindexmap[fields[i]]['ops'][selDOMobj.options[j].value] = j;
532
+ }
533
+ }
534
+ selDOMobj = o.find(".data > select[class='field"+i+"']")[0];
535
+ if (selDOMobj) {
536
+ valueindexmap[fields[i]]['data'] = {}; // this setting is the flag that 'data' is contained in a SELECT
537
+ for (j=0, lj=selDOMobj.options.length; j<lj; j++) {
538
+ valueindexmap[fields[i]]['data'][selDOMobj.options[j].value] = j;
539
+ }
540
+ }
541
+ } // done populating valueindexmap
542
+
543
+ // preparsing the index values for SELECT elements.
544
+ var fieldvalue, fieldindex, opindex, datavalue, dataindex;
545
+ fieldvalue = filter['field'];
546
+ if (valueindexmap[fieldvalue]) {
547
+ fieldindex = valueindexmap[fieldvalue]['index'];
548
+ }
549
+ if (fieldindex != null) {
550
+ opindex = valueindexmap[fieldvalue]['ops'][filter['op']];
551
+ if(opindex === undefined) {
552
+ for(i=0,li=options.operators.length; i<li;i++) {
553
+ if(options.operators[i].op == filter.op ){
554
+ opindex = i;
555
+ break;
556
+ }
557
+ }
558
+ }
559
+ datavalue = filter['data'];
560
+ if (valueindexmap[fieldvalue]['data'] == null) {
561
+ dataindex = -1; // 'data' is not SELECT, Making the var 'defined'
562
+ } else {
563
+ dataindex = valueindexmap[fieldvalue]['data'][datavalue]; // 'undefined' may come from here.
564
+ }
565
+ }
566
+ // only if values for 'field' and 'op' and 'data' are 'found' in mapping...
567
+ if (fieldindex != null && opindex != null && dataindex != null) {
568
+ o.find("select[name='field']")[0].selectedIndex = fieldindex;
569
+ o.find("select[name='field']").change();
570
+ o.find("select[name='op']")[0].selectedIndex = opindex;
571
+ o.find("input.vdata").val(datavalue); // if jquery does not find any INPUT, it does not set any. This means we deal with SELECT
572
+ o = o.find("select.vdata")[0];
573
+ if (o) {
574
+ o.selectedIndex = dataindex;
575
+ }
576
+ return true
577
+ } else {
578
+ return false
579
+ }
580
+ }; // end of this.setFilter fn
581
+ } // end of if fields != null
582
+ }
583
+ return new SearchFilter(this, fields, options);
584
+ };
585
+
586
+ jQuery.fn.searchFilter.version = '1.2.9';
587
+
588
+ /* This property contains the default options */
589
+ jQuery.fn.searchFilter.defaults = {
590
+
591
+ /*
592
+ * PROPERTY
593
+ * TYPE: boolean
594
+ * DESCRIPTION: clone a row if it is added from an existing row
595
+ * when false, any new added rows will be blank.
596
+ */
597
+ clone: true,
598
+
599
+ /*
600
+ * PROPERTY
601
+ * TYPE: boolean
602
+ * DESCRIPTION: current version of datepicker uses a data store,
603
+ * which is incompatible with $().clone(true)
604
+ */
605
+ datepickerFix: true,
606
+
607
+ /*
608
+ * FUNCTION
609
+ * DESCRIPTION: the function that will be called when the user clicks Reset
610
+ * INPUT TYPE: JS object if stringResult is false, otherwise is JSON string
611
+ */
612
+ onReset: function(data) { alert("Reset Clicked. Data Returned: " + data) },
613
+
614
+ /*
615
+ * FUNCTION
616
+ * DESCRIPTION: the function that will be called when the user clicks Search
617
+ * INPUT TYPE: JS object if stringResult is false, otherwise is JSON string
618
+ */
619
+ onSearch: function(data) { alert("Search Clicked. Data Returned: " + data) },
620
+
621
+ /*
622
+ * FUNCTION
623
+ * DESCRIPTION: the function that will be called when the user clicks the Closer icon
624
+ * or the close() function is called
625
+ * if left null, it simply does a .hide() on the searchFilter
626
+ * INPUT TYPE: a jQuery object for the searchFilter
627
+ */
628
+ onClose: function(jElem) { jElem.hide(); },
629
+
630
+ /*
631
+ * PROPERTY
632
+ * TYPE: array of objects, each object has the properties op and text
633
+ * DESCRIPTION: the selectable operators that are applied between rules
634
+ * e.g. for {op:"AND", text:"all"}
635
+ * the search filter box will say: match all rules
636
+ * the server should interpret this as putting the AND op between each rule:
637
+ * rule1 AND rule2 AND rule3
638
+ * text will be the option text, and op will be the option value
639
+ */
640
+ groupOps: [
641
+ { op: "AND", text: "all" },
642
+ { op: "OR", text: "any" }
643
+ ],
644
+
645
+
646
+ /*
647
+ * PROPERTY
648
+ * TYPE: array of objects, each object has the properties op and text
649
+ * DESCRIPTION: the operators that will appear as drop-down options
650
+ * text will be the option text, and op will be the option value
651
+ */
652
+ operators: [
653
+ { op: "eq", text: "is equal to" },
654
+ { op: "ne", text: "is not equal to" },
655
+ { op: "lt", text: "is less than" },
656
+ { op: "le", text: "is less or equal to" },
657
+ { op: "gt", text: "is greater than" },
658
+ { op: "ge", text: "is greater or equal to" },
659
+ { op: "in", text: "is in" },
660
+ { op: "ni", text: "is not in" },
661
+ { op: "bw", text: "begins with" },
662
+ { op: "bn", text: "does not begin with" },
663
+ { op: "ew", text: "ends with" },
664
+ { op: "en", text: "does not end with" },
665
+ { op: "cn", text: "contains" },
666
+ { op: "nc", text: "does not contain" }
667
+ ],
668
+
669
+ /*
670
+ * PROPERTY
671
+ * TYPE: string
672
+ * DESCRIPTION: part of the phrase: _match_ ANY/ALL rules
673
+ */
674
+ matchText: "match",
675
+
676
+ /*
677
+ * PROPERTY
678
+ * TYPE: string
679
+ * DESCRIPTION: part of the phrase: match ANY/ALL _rules_
680
+ */
681
+ rulesText: "rules",
682
+
683
+ /*
684
+ * PROPERTY
685
+ * TYPE: string
686
+ * DESCRIPTION: the text that will be displayed in the reset button
687
+ */
688
+ resetText: "Reset",
689
+
690
+ /*
691
+ * PROPERTY
692
+ * TYPE: string
693
+ * DESCRIPTION: the text that will be displayed in the search button
694
+ */
695
+ searchText: "Search",
696
+
697
+ /*
698
+ * PROPERTY
699
+ * TYPE: boolean
700
+ * DESCRIPTION: a flag that, when set, will make the onSearch and onReset return strings instead of objects
701
+ */
702
+ stringResult: true,
703
+
704
+ /*
705
+ * PROPERTY
706
+ * TYPE: string
707
+ * DESCRIPTION: the title of the searchFilter window
708
+ */
709
+ windowTitle: "Search Rules",
710
+ /*
711
+ * PROPERTY
712
+ * TYPE: object
713
+ * DESCRIPTION: options to extend the ajax request
714
+ */
715
+ ajaxSelectOptions : {}
716
+ }; /* end of searchFilter */