UNPKG

13.4 kBJavaScriptView Raw
1/**
2 * Destroys the builder
3 * @fires QueryBuilder.beforeDestroy
4 */
5QueryBuilder.prototype.destroy = function() {
6 /**
7 * Before the {@link QueryBuilder#destroy} method
8 * @event beforeDestroy
9 * @memberof QueryBuilder
10 */
11 this.trigger('beforeDestroy');
12
13 if (this.status.generated_id) {
14 this.$el.removeAttr('id');
15 }
16
17 this.clear();
18 this.model = null;
19
20 this.$el
21 .off('.queryBuilder')
22 .removeClass('query-builder')
23 .removeData('queryBuilder');
24
25 delete this.$el[0].queryBuilder;
26};
27
28/**
29 * Clear all rules and resets the root group
30 * @fires QueryBuilder.beforeReset
31 * @fires QueryBuilder.afterReset
32 */
33QueryBuilder.prototype.reset = function() {
34 /**
35 * Before the {@link QueryBuilder#reset} method, can be prevented
36 * @event beforeReset
37 * @memberof QueryBuilder
38 */
39 var e = this.trigger('beforeReset');
40 if (e.isDefaultPrevented()) {
41 return;
42 }
43
44 this.status.group_id = 1;
45 this.status.rule_id = 0;
46
47 this.model.root.empty();
48
49 this.addRule(this.model.root);
50
51 /**
52 * After the {@link QueryBuilder#reset} method
53 * @event afterReset
54 * @memberof QueryBuilder
55 */
56 this.trigger('afterReset');
57};
58
59/**
60 * Clears all rules and removes the root group
61 * @fires QueryBuilder.beforeClear
62 * @fires QueryBuilder.afterClear
63 */
64QueryBuilder.prototype.clear = function() {
65 /**
66 * Before the {@link QueryBuilder#clear} method, can be prevented
67 * @event beforeClear
68 * @memberof QueryBuilder
69 */
70 var e = this.trigger('beforeClear');
71 if (e.isDefaultPrevented()) {
72 return;
73 }
74
75 this.status.group_id = 0;
76 this.status.rule_id = 0;
77
78 if (this.model.root) {
79 this.model.root.drop();
80 this.model.root = null;
81 }
82
83 /**
84 * After the {@link QueryBuilder#clear} method
85 * @event afterClear
86 * @memberof QueryBuilder
87 */
88 this.trigger('afterClear');
89};
90
91/**
92 * Modifies the builder configuration.<br>
93 * Only options defined in QueryBuilder.modifiable_options are modifiable
94 * @param {object} options
95 */
96QueryBuilder.prototype.setOptions = function(options) {
97 $.each(options, function(opt, value) {
98 if (QueryBuilder.modifiable_options.indexOf(opt) !== -1) {
99 this.settings[opt] = value;
100 }
101 }.bind(this));
102};
103
104/**
105 * Returns the model associated to a DOM object, or the root model
106 * @param {jQuery} [target]
107 * @returns {Node}
108 */
109QueryBuilder.prototype.getModel = function(target) {
110 if (!target) {
111 return this.model.root;
112 }
113 else if (target instanceof Node) {
114 return target;
115 }
116 else {
117 return $(target).data('queryBuilderModel');
118 }
119};
120
121/**
122 * Validates the whole builder
123 * @param {object} [options]
124 * @param {boolean} [options.skip_empty=false] - skips validating rules that have no filter selected
125 * @returns {boolean}
126 * @fires QueryBuilder.changer:validate
127 */
128QueryBuilder.prototype.validate = function(options) {
129 options = $.extend({
130 skip_empty: false
131 }, options);
132
133 this.clearErrors();
134
135 var self = this;
136
137 var valid = (function parse(group) {
138 var done = 0;
139 var errors = 0;
140
141 group.each(function(rule) {
142 if (!rule.filter && options.skip_empty) {
143 return;
144 }
145
146 if (!rule.filter) {
147 self.triggerValidationError(rule, 'no_filter', null);
148 errors++;
149 return;
150 }
151
152 if (!rule.operator) {
153 self.triggerValidationError(rule, 'no_operator', null);
154 errors++;
155 return;
156 }
157
158 if (rule.operator.nb_inputs !== 0) {
159 var valid = self.validateValue(rule, rule.value);
160
161 if (valid !== true) {
162 self.triggerValidationError(rule, valid, rule.value);
163 errors++;
164 return;
165 }
166 }
167
168 done++;
169
170 }, function(group) {
171 var res = parse(group);
172 if (res === true) {
173 done++;
174 }
175 else if (res === false) {
176 errors++;
177 }
178 });
179
180 if (errors > 0) {
181 return false;
182 }
183 else if (done === 0 && !group.isRoot() && options.skip_empty) {
184 return null;
185 }
186 else if (done === 0 && (!self.settings.allow_empty || !group.isRoot())) {
187 self.triggerValidationError(group, 'empty_group', null);
188 return false;
189 }
190
191 return true;
192
193 }(this.model.root));
194
195 /**
196 * Modifies the result of the {@link QueryBuilder#validate} method
197 * @event changer:validate
198 * @memberof QueryBuilder
199 * @param {boolean} valid
200 * @returns {boolean}
201 */
202 return this.change('validate', valid);
203};
204
205/**
206 * Gets an object representing current rules
207 * @param {object} [options]
208 * @param {boolean|string} [options.get_flags=false] - export flags, true: only changes from default flags or 'all'
209 * @param {boolean} [options.allow_invalid=false] - returns rules even if they are invalid
210 * @param {boolean} [options.skip_empty=false] - remove rules that have no filter selected
211 * @returns {object}
212 * @fires QueryBuilder.changer:ruleToJson
213 * @fires QueryBuilder.changer:groupToJson
214 * @fires QueryBuilder.changer:getRules
215 */
216QueryBuilder.prototype.getRules = function(options) {
217 options = $.extend({
218 get_flags: false,
219 allow_invalid: false,
220 skip_empty: false
221 }, options);
222
223 var valid = this.validate(options);
224 if (!valid && !options.allow_invalid) {
225 return null;
226 }
227
228 var self = this;
229
230 var out = (function parse(group) {
231 var groupData = {
232 condition: group.condition,
233 rules: []
234 };
235
236 if (group.data) {
237 groupData.data = $.extendext(true, 'replace', {}, group.data);
238 }
239
240 if (options.get_flags) {
241 var flags = self.getGroupFlags(group.flags, options.get_flags === 'all');
242 if (!$.isEmptyObject(flags)) {
243 groupData.flags = flags;
244 }
245 }
246
247 group.each(function(rule) {
248 if (!rule.filter && options.skip_empty) {
249 return;
250 }
251
252 var value = null;
253 if (!rule.operator || rule.operator.nb_inputs !== 0) {
254 value = rule.value;
255 }
256
257 var ruleData = {
258 id: rule.filter ? rule.filter.id : null,
259 field: rule.filter ? rule.filter.field : null,
260 type: rule.filter ? rule.filter.type : null,
261 input: rule.filter ? rule.filter.input : null,
262 operator: rule.operator ? rule.operator.type : null,
263 value: value
264 };
265
266 if (rule.filter && rule.filter.data || rule.data) {
267 ruleData.data = $.extendext(true, 'replace', {}, rule.filter.data, rule.data);
268 }
269
270 if (options.get_flags) {
271 var flags = self.getRuleFlags(rule.flags, options.get_flags === 'all');
272 if (!$.isEmptyObject(flags)) {
273 ruleData.flags = flags;
274 }
275 }
276
277 /**
278 * Modifies the JSON generated from a Rule object
279 * @event changer:ruleToJson
280 * @memberof QueryBuilder
281 * @param {object} json
282 * @param {Rule} rule
283 * @returns {object}
284 */
285 groupData.rules.push(self.change('ruleToJson', ruleData, rule));
286
287 }, function(model) {
288 var data = parse(model);
289 if (data.rules.length !== 0 || !options.skip_empty) {
290 groupData.rules.push(data);
291 }
292 }, this);
293
294 /**
295 * Modifies the JSON generated from a Group object
296 * @event changer:groupToJson
297 * @memberof QueryBuilder
298 * @param {object} json
299 * @param {Group} group
300 * @returns {object}
301 */
302 return self.change('groupToJson', groupData, group);
303
304 }(this.model.root));
305
306 out.valid = valid;
307
308 /**
309 * Modifies the result of the {@link QueryBuilder#getRules} method
310 * @event changer:getRules
311 * @memberof QueryBuilder
312 * @param {object} json
313 * @returns {object}
314 */
315 return this.change('getRules', out);
316};
317
318/**
319 * Sets rules from object
320 * @param {object} data
321 * @param {object} [options]
322 * @param {boolean} [options.allow_invalid=false] - silent-fail if the data are invalid
323 * @throws RulesError, UndefinedConditionError
324 * @fires QueryBuilder.changer:setRules
325 * @fires QueryBuilder.changer:jsonToRule
326 * @fires QueryBuilder.changer:jsonToGroup
327 * @fires QueryBuilder.afterSetRules
328 */
329QueryBuilder.prototype.setRules = function(data, options) {
330 options = $.extend({
331 allow_invalid: false
332 }, options);
333
334 if ($.isArray(data)) {
335 data = {
336 condition: this.settings.default_condition,
337 rules: data
338 };
339 }
340
341 if (!data || !data.rules || (data.rules.length === 0 && !this.settings.allow_empty)) {
342 Utils.error('RulesParse', 'Incorrect data object passed');
343 }
344
345 this.clear();
346 this.setRoot(false, data.data, this.parseGroupFlags(data));
347 this.applyGroupFlags(this.model.root);
348
349 /**
350 * Modifies data before the {@link QueryBuilder#setRules} method
351 * @event changer:setRules
352 * @memberof QueryBuilder
353 * @param {object} json
354 * @param {object} options
355 * @returns {object}
356 */
357 data = this.change('setRules', data, options);
358
359 var self = this;
360
361 (function add(data, group) {
362 if (group === null) {
363 return;
364 }
365
366 if (data.condition === undefined) {
367 data.condition = self.settings.default_condition;
368 }
369 else if (self.settings.conditions.indexOf(data.condition) == -1) {
370 Utils.error(!options.allow_invalid, 'UndefinedCondition', 'Invalid condition "{0}"', data.condition);
371 data.condition = self.settings.default_condition;
372 }
373
374 group.condition = data.condition;
375
376 data.rules.forEach(function(item) {
377 var model;
378
379 if (item.rules !== undefined) {
380 if (self.settings.allow_groups !== -1 && self.settings.allow_groups < group.level) {
381 Utils.error(!options.allow_invalid, 'RulesParse', 'No more than {0} groups are allowed', self.settings.allow_groups);
382 self.reset();
383 }
384 else {
385 model = self.addGroup(group, false, item.data, self.parseGroupFlags(item));
386 if (model === null) {
387 return;
388 }
389
390 self.applyGroupFlags(model);
391
392 add(item, model);
393 }
394 }
395 else {
396 if (!item.empty) {
397 if (item.id === undefined) {
398 Utils.error(!options.allow_invalid, 'RulesParse', 'Missing rule field id');
399 item.empty = true;
400 }
401 if (item.operator === undefined) {
402 item.operator = 'equal';
403 }
404 }
405
406 model = self.addRule(group, item.data, self.parseRuleFlags(item));
407 if (model === null) {
408 return;
409 }
410
411 if (!item.empty) {
412 model.filter = self.getFilterById(item.id, !options.allow_invalid);
413
414 if (model.filter) {
415 model.operator = self.getOperatorByType(item.operator, !options.allow_invalid);
416
417 if (!model.operator) {
418 model.operator = self.getOperators(model.filter)[0];
419 }
420
421 if (model.operator && model.operator.nb_inputs !== 0 && item.value !== undefined) {
422 model.value = item.value;
423 }
424 }
425 }
426
427 self.applyRuleFlags(model);
428
429 /**
430 * Modifies the Rule object generated from the JSON
431 * @event changer:jsonToRule
432 * @memberof QueryBuilder
433 * @param {Rule} rule
434 * @param {object} json
435 * @returns {Rule} the same rule
436 */
437 if (self.change('jsonToRule', model, item) != model) {
438 Utils.error('RulesParse', 'Plugin tried to change rule reference');
439 }
440 }
441 });
442
443 /**
444 * Modifies the Group object generated from the JSON
445 * @event changer:jsonToGroup
446 * @memberof QueryBuilder
447 * @param {Group} group
448 * @param {object} json
449 * @returns {Group} the same group
450 */
451 if (self.change('jsonToGroup', group, data) != group) {
452 Utils.error('RulesParse', 'Plugin tried to change group reference');
453 }
454
455 }(data, this.model.root));
456
457 /**
458 * After the {@link QueryBuilder#setRules} method
459 * @event afterSetRules
460 * @memberof QueryBuilder
461 */
462 this.trigger('afterSetRules');
463};