UNPKG

26.5 kBJavaScriptView Raw
1/**
2 * Checks the configuration of each filter
3 * @param {QueryBuilder.Filter[]} filters
4 * @returns {QueryBuilder.Filter[]}
5 * @throws ConfigError
6 */
7QueryBuilder.prototype.checkFilters = function(filters) {
8 var definedFilters = [];
9
10 if (!filters || filters.length === 0) {
11 Utils.error('Config', 'Missing filters list');
12 }
13
14 filters.forEach(function(filter, i) {
15 if (!filter.id) {
16 Utils.error('Config', 'Missing filter {0} id', i);
17 }
18 if (definedFilters.indexOf(filter.id) != -1) {
19 Utils.error('Config', 'Filter "{0}" already defined', filter.id);
20 }
21 definedFilters.push(filter.id);
22
23 if (!filter.type) {
24 filter.type = 'string';
25 }
26 else if (!QueryBuilder.types[filter.type]) {
27 Utils.error('Config', 'Invalid type "{0}"', filter.type);
28 }
29
30 if (!filter.input) {
31 filter.input = QueryBuilder.types[filter.type] === 'number' ? 'number' : 'text';
32 }
33 else if (typeof filter.input != 'function' && QueryBuilder.inputs.indexOf(filter.input) == -1) {
34 Utils.error('Config', 'Invalid input "{0}"', filter.input);
35 }
36
37 if (filter.operators) {
38 filter.operators.forEach(function(operator) {
39 if (typeof operator != 'string') {
40 Utils.error('Config', 'Filter operators must be global operators types (string)');
41 }
42 });
43 }
44
45 if (!filter.field) {
46 filter.field = filter.id;
47 }
48 if (!filter.label) {
49 filter.label = filter.field;
50 }
51
52 if (!filter.optgroup) {
53 filter.optgroup = null;
54 }
55 else {
56 this.status.has_optgroup = true;
57
58 // register optgroup if needed
59 if (!this.settings.optgroups[filter.optgroup]) {
60 this.settings.optgroups[filter.optgroup] = filter.optgroup;
61 }
62 }
63
64 switch (filter.input) {
65 case 'radio':
66 case 'checkbox':
67 if (!filter.values || filter.values.length < 1) {
68 Utils.error('Config', 'Missing filter "{0}" values', filter.id);
69 }
70 break;
71
72 case 'select':
73 if (filter.placeholder) {
74 if (filter.placeholder_value === undefined) {
75 filter.placeholder_value = -1;
76 }
77 Utils.iterateOptions(filter.values, function(key) {
78 if (key == filter.placeholder_value) {
79 Utils.error('Config', 'Placeholder of filter "{0}" overlaps with one of its values', filter.id);
80 }
81 });
82 }
83 break;
84 }
85 }, this);
86
87 if (this.settings.sort_filters) {
88 if (typeof this.settings.sort_filters == 'function') {
89 filters.sort(this.settings.sort_filters);
90 }
91 else {
92 var self = this;
93 filters.sort(function(a, b) {
94 return self.translate(a.label).localeCompare(self.translate(b.label));
95 });
96 }
97 }
98
99 if (this.status.has_optgroup) {
100 filters = Utils.groupSort(filters, 'optgroup');
101 }
102
103 return filters;
104};
105
106/**
107 * Checks the configuration of each operator
108 * @param {QueryBuilder.Operator[]} operators
109 * @returns {QueryBuilder.Operator[]}
110 * @throws ConfigError
111 */
112QueryBuilder.prototype.checkOperators = function(operators) {
113 var definedOperators = [];
114
115 operators.forEach(function(operator, i) {
116 if (typeof operator == 'string') {
117 if (!QueryBuilder.OPERATORS[operator]) {
118 Utils.error('Config', 'Unknown operator "{0}"', operator);
119 }
120
121 operators[i] = operator = $.extendext(true, 'replace', {}, QueryBuilder.OPERATORS[operator]);
122 }
123 else {
124 if (!operator.type) {
125 Utils.error('Config', 'Missing "type" for operator {0}', i);
126 }
127
128 if (QueryBuilder.OPERATORS[operator.type]) {
129 operators[i] = operator = $.extendext(true, 'replace', {}, QueryBuilder.OPERATORS[operator.type], operator);
130 }
131
132 if (operator.nb_inputs === undefined || operator.apply_to === undefined) {
133 Utils.error('Config', 'Missing "nb_inputs" and/or "apply_to" for operator "{0}"', operator.type);
134 }
135 }
136
137 if (definedOperators.indexOf(operator.type) != -1) {
138 Utils.error('Config', 'Operator "{0}" already defined', operator.type);
139 }
140 definedOperators.push(operator.type);
141
142 if (!operator.optgroup) {
143 operator.optgroup = null;
144 }
145 else {
146 this.status.has_operator_optgroup = true;
147
148 // register optgroup if needed
149 if (!this.settings.optgroups[operator.optgroup]) {
150 this.settings.optgroups[operator.optgroup] = operator.optgroup;
151 }
152 }
153 }, this);
154
155 if (this.status.has_operator_optgroup) {
156 operators = Utils.groupSort(operators, 'optgroup');
157 }
158
159 return operators;
160};
161
162/**
163 * Adds all events listeners to the builder
164 * @private
165 */
166QueryBuilder.prototype.bindEvents = function() {
167 var self = this;
168 var Selectors = QueryBuilder.selectors;
169
170 // group condition change
171 this.$el.on('change.queryBuilder', Selectors.group_condition, function() {
172 if ($(this).is(':checked')) {
173 var $group = $(this).closest(Selectors.group_container);
174 self.getModel($group).condition = $(this).val();
175 }
176 });
177
178 // rule filter change
179 this.$el.on('change.queryBuilder', Selectors.rule_filter, function() {
180 var $rule = $(this).closest(Selectors.rule_container);
181 self.getModel($rule).filter = self.getFilterById($(this).val());
182 });
183
184 // rule operator change
185 this.$el.on('change.queryBuilder', Selectors.rule_operator, function() {
186 var $rule = $(this).closest(Selectors.rule_container);
187 self.getModel($rule).operator = self.getOperatorByType($(this).val());
188 });
189
190 // add rule button
191 this.$el.on('click.queryBuilder', Selectors.add_rule, function() {
192 var $group = $(this).closest(Selectors.group_container);
193 self.addRule(self.getModel($group));
194 });
195
196 // delete rule button
197 this.$el.on('click.queryBuilder', Selectors.delete_rule, function() {
198 var $rule = $(this).closest(Selectors.rule_container);
199 self.deleteRule(self.getModel($rule));
200 });
201
202 if (this.settings.allow_groups !== 0) {
203 // add group button
204 this.$el.on('click.queryBuilder', Selectors.add_group, function() {
205 var $group = $(this).closest(Selectors.group_container);
206 self.addGroup(self.getModel($group));
207 });
208
209 // delete group button
210 this.$el.on('click.queryBuilder', Selectors.delete_group, function() {
211 var $group = $(this).closest(Selectors.group_container);
212 self.deleteGroup(self.getModel($group));
213 });
214 }
215
216 // model events
217 this.model.on({
218 'drop': function(e, node) {
219 node.$el.remove();
220 self.refreshGroupsConditions();
221 },
222 'add': function(e, parent, node, index) {
223 if (index === 0) {
224 node.$el.prependTo(parent.$el.find('>' + QueryBuilder.selectors.rules_list));
225 }
226 else {
227 node.$el.insertAfter(parent.rules[index - 1].$el);
228 }
229 self.refreshGroupsConditions();
230 },
231 'move': function(e, node, group, index) {
232 node.$el.detach();
233
234 if (index === 0) {
235 node.$el.prependTo(group.$el.find('>' + QueryBuilder.selectors.rules_list));
236 }
237 else {
238 node.$el.insertAfter(group.rules[index - 1].$el);
239 }
240 self.refreshGroupsConditions();
241 },
242 'update': function(e, node, field, value, oldValue) {
243 if (node instanceof Rule) {
244 switch (field) {
245 case 'error':
246 self.updateError(node);
247 break;
248
249 case 'flags':
250 self.applyRuleFlags(node);
251 break;
252
253 case 'filter':
254 self.updateRuleFilter(node, oldValue);
255 break;
256
257 case 'operator':
258 self.updateRuleOperator(node, oldValue);
259 break;
260
261 case 'value':
262 self.updateRuleValue(node);
263 break;
264 }
265 }
266 else {
267 switch (field) {
268 case 'error':
269 self.updateError(node);
270 break;
271
272 case 'flags':
273 self.applyGroupFlags(node);
274 break;
275
276 case 'condition':
277 self.updateGroupCondition(node);
278 break;
279 }
280 }
281 }
282 });
283};
284
285/**
286 * Creates the root group
287 * @param {boolean} [addRule=true] - adds a default empty rule
288 * @param {object} [data] - group custom data
289 * @param {object} [flags] - flags to apply to the group
290 * @returns {Group} root group
291 * @fires QueryBuilder.afterAddGroup
292 */
293QueryBuilder.prototype.setRoot = function(addRule, data, flags) {
294 addRule = (addRule === undefined || addRule === true);
295
296 var group_id = this.nextGroupId();
297 var $group = $(this.getGroupTemplate(group_id, 1));
298
299 this.$el.append($group);
300 this.model.root = new Group(null, $group);
301 this.model.root.model = this.model;
302
303 this.model.root.data = data;
304 this.model.root.__.flags = $.extend({}, this.settings.default_group_flags, flags);
305
306 this.trigger('afterAddGroup', this.model.root);
307
308 this.model.root.condition = this.settings.default_condition;
309
310 if (addRule) {
311 this.addRule(this.model.root);
312 }
313
314 return this.model.root;
315};
316
317/**
318 * Adds a new group
319 * @param {Group} parent
320 * @param {boolean} [addRule=true] - adds a default empty rule
321 * @param {object} [data] - group custom data
322 * @param {object} [flags] - flags to apply to the group
323 * @returns {Group}
324 * @fires QueryBuilder.beforeAddGroup
325 * @fires QueryBuilder.afterAddGroup
326 */
327QueryBuilder.prototype.addGroup = function(parent, addRule, data, flags) {
328 addRule = (addRule === undefined || addRule === true);
329
330 var level = parent.level + 1;
331
332 /**
333 * Just before adding a group, can be prevented.
334 * @event beforeAddGroup
335 * @memberof QueryBuilder
336 * @param {Group} parent
337 * @param {boolean} addRule - if an empty rule will be added in the group
338 * @param {int} level - nesting level of the group, 1 is the root group
339 */
340 var e = this.trigger('beforeAddGroup', parent, addRule, level);
341 if (e.isDefaultPrevented()) {
342 return null;
343 }
344
345 var group_id = this.nextGroupId();
346 var $group = $(this.getGroupTemplate(group_id, level));
347 var model = parent.addGroup($group);
348
349 model.data = data;
350 model.__.flags = $.extend({}, this.settings.default_group_flags, flags);
351
352 /**
353 * Just after adding a group
354 * @event afterAddGroup
355 * @memberof QueryBuilder
356 * @param {Group} group
357 */
358 this.trigger('afterAddGroup', model);
359
360 model.condition = this.settings.default_condition;
361
362 if (addRule) {
363 this.addRule(model);
364 }
365
366 return model;
367};
368
369/**
370 * Tries to delete a group. The group is not deleted if at least one rule is flagged `no_delete`.
371 * @param {Group} group
372 * @returns {boolean} if the group has been deleted
373 * @fires QueryBuilder.beforeDeleteGroup
374 * @fires QueryBuilder.afterDeleteGroup
375 */
376QueryBuilder.prototype.deleteGroup = function(group) {
377 if (group.isRoot()) {
378 return false;
379 }
380
381 /**
382 * Just before deleting a group, can be prevented
383 * @event beforeDeleteGroup
384 * @memberof QueryBuilder
385 * @param {Group} parent
386 */
387 var e = this.trigger('beforeDeleteGroup', group);
388 if (e.isDefaultPrevented()) {
389 return false;
390 }
391
392 var del = true;
393
394 group.each('reverse', function(rule) {
395 del &= this.deleteRule(rule);
396 }, function(group) {
397 del &= this.deleteGroup(group);
398 }, this);
399
400 if (del) {
401 group.drop();
402
403 /**
404 * Just after deleting a group
405 * @event afterDeleteGroup
406 * @memberof QueryBuilder
407 */
408 this.trigger('afterDeleteGroup');
409 }
410
411 return del;
412};
413
414/**
415 * Performs actions when a group's condition changes
416 * @param {Group} group
417 * @fires QueryBuilder.afterUpdateGroupCondition
418 * @private
419 */
420QueryBuilder.prototype.updateGroupCondition = function(group) {
421 group.$el.find('>' + QueryBuilder.selectors.group_condition).each(function() {
422 var $this = $(this);
423 $this.prop('checked', $this.val() === group.condition);
424 $this.parent().toggleClass('active', $this.val() === group.condition);
425 });
426
427 /**
428 * After the group condition has been modified
429 * @event afterUpdateGroupCondition
430 * @memberof QueryBuilder
431 * @param {Group} group
432 */
433 this.trigger('afterUpdateGroupCondition', group);
434};
435
436/**
437 * Updates the visibility of conditions based on number of rules inside each group
438 * @private
439 */
440QueryBuilder.prototype.refreshGroupsConditions = function() {
441 (function walk(group) {
442 if (!group.flags || (group.flags && !group.flags.condition_readonly)) {
443 group.$el.find('>' + QueryBuilder.selectors.group_condition).prop('disabled', group.rules.length <= 1)
444 .parent().toggleClass('disabled', group.rules.length <= 1);
445 }
446
447 group.each(null, function(group) {
448 walk(group);
449 }, this);
450 }(this.model.root));
451};
452
453/**
454 * Adds a new rule
455 * @param {Group} parent
456 * @param {object} [data] - rule custom data
457 * @param {object} [flags] - flags to apply to the rule
458 * @returns {Rule}
459 * @fires QueryBuilder.beforeAddRule
460 * @fires QueryBuilder.afterAddRule
461 * @fires QueryBuilder.changer:getDefaultFilter
462 */
463QueryBuilder.prototype.addRule = function(parent, data, flags) {
464 /**
465 * Just before adding a rule, can be prevented
466 * @event beforeAddRule
467 * @memberof QueryBuilder
468 * @param {Group} parent
469 */
470 var e = this.trigger('beforeAddRule', parent);
471 if (e.isDefaultPrevented()) {
472 return null;
473 }
474
475 var rule_id = this.nextRuleId();
476 var $rule = $(this.getRuleTemplate(rule_id));
477 var model = parent.addRule($rule);
478
479 if (data !== undefined) {
480 model.data = data;
481 }
482
483 model.__.flags = $.extend({}, this.settings.default_rule_flags, flags);
484
485 /**
486 * Just after adding a rule
487 * @event afterAddRule
488 * @memberof QueryBuilder
489 * @param {Rule} rule
490 */
491 this.trigger('afterAddRule', model);
492
493 this.createRuleFilters(model);
494
495 if (this.settings.default_filter || !this.settings.display_empty_filter) {
496 /**
497 * Modifies the default filter for a rule
498 * @event changer:getDefaultFilter
499 * @memberof QueryBuilder
500 * @param {QueryBuilder.Filter} filter
501 * @param {Rule} rule
502 * @returns {QueryBuilder.Filter}
503 */
504 model.filter = this.change('getDefaultFilter',
505 this.getFilterById(this.settings.default_filter || this.filters[0].id),
506 model
507 );
508 }
509
510 return model;
511};
512
513/**
514 * Tries to delete a rule
515 * @param {Rule} rule
516 * @returns {boolean} if the rule has been deleted
517 * @fires QueryBuilder.beforeDeleteRule
518 * @fires QueryBuilder.afterDeleteRule
519 */
520QueryBuilder.prototype.deleteRule = function(rule) {
521 if (rule.flags.no_delete) {
522 return false;
523 }
524
525 /**
526 * Just before deleting a rule, can be prevented
527 * @event beforeDeleteRule
528 * @memberof QueryBuilder
529 * @param {Rule} rule
530 */
531 var e = this.trigger('beforeDeleteRule', rule);
532 if (e.isDefaultPrevented()) {
533 return false;
534 }
535
536 rule.drop();
537
538 /**
539 * Just after deleting a rule
540 * @event afterDeleteRule
541 * @memberof QueryBuilder
542 */
543 this.trigger('afterDeleteRule');
544
545 return true;
546};
547
548/**
549 * Creates the filters for a rule
550 * @param {Rule} rule
551 * @fires QueryBuilder.changer:getRuleFilters
552 * @fires QueryBuilder.afterCreateRuleFilters
553 * @private
554 */
555QueryBuilder.prototype.createRuleFilters = function(rule) {
556 /**
557 * Modifies the list a filters available for a rule
558 * @event changer:getRuleFilters
559 * @memberof QueryBuilder
560 * @param {QueryBuilder.Filter[]} filters
561 * @param {Rule} rule
562 * @returns {QueryBuilder.Filter[]}
563 */
564 var filters = this.change('getRuleFilters', this.filters, rule);
565 var $filterSelect = $(this.getRuleFilterSelect(rule, filters));
566
567 rule.$el.find(QueryBuilder.selectors.filter_container).html($filterSelect);
568
569 /**
570 * After creating the dropdown for filters
571 * @event afterCreateRuleFilters
572 * @memberof QueryBuilder
573 * @param {Rule} rule
574 */
575 this.trigger('afterCreateRuleFilters', rule);
576};
577
578/**
579 * Creates the operators for a rule and init the rule operator
580 * @param {Rule} rule
581 * @fires QueryBuilder.afterCreateRuleOperators
582 * @private
583 */
584QueryBuilder.prototype.createRuleOperators = function(rule) {
585 var $operatorContainer = rule.$el.find(QueryBuilder.selectors.operator_container).empty();
586
587 if (!rule.filter) {
588 return;
589 }
590
591 var operators = this.getOperators(rule.filter);
592 var $operatorSelect = $(this.getRuleOperatorSelect(rule, operators));
593
594 $operatorContainer.html($operatorSelect);
595
596 // set the operator without triggering update event
597 rule.__.operator = operators[0];
598
599 /**
600 * After creating the dropdown for operators
601 * @event afterCreateRuleOperators
602 * @memberof QueryBuilder
603 * @param {Rule} rule
604 * @param {QueryBuilder.Operator[]} operators - allowed operators for this rule
605 */
606 this.trigger('afterCreateRuleOperators', rule, operators);
607};
608
609/**
610 * Creates the main input for a rule
611 * @param {Rule} rule
612 * @fires QueryBuilder.afterCreateRuleInput
613 * @private
614 */
615QueryBuilder.prototype.createRuleInput = function(rule) {
616 var $valueContainer = rule.$el.find(QueryBuilder.selectors.value_container).empty();
617
618 rule.__.value = undefined;
619
620 if (!rule.filter || !rule.operator || rule.operator.nb_inputs === 0) {
621 return;
622 }
623
624 var self = this;
625 var $inputs = $();
626 var filter = rule.filter;
627
628 for (var i = 0; i < rule.operator.nb_inputs; i++) {
629 var $ruleInput = $(this.getRuleInput(rule, i));
630 if (i > 0) $valueContainer.append(this.settings.inputs_separator);
631 $valueContainer.append($ruleInput);
632 $inputs = $inputs.add($ruleInput);
633 }
634
635 $valueContainer.show();
636
637 $inputs.on('change ' + (filter.input_event || ''), function() {
638 if (!this._updating_input) {
639 rule._updating_value = true;
640 rule.value = self.getRuleInputValue(rule);
641 rule._updating_value = false;
642 }
643 });
644
645 if (filter.plugin) {
646 $inputs[filter.plugin](filter.plugin_config || {});
647 }
648
649 /**
650 * After creating the input for a rule and initializing optional plugin
651 * @event afterCreateRuleInput
652 * @memberof QueryBuilder
653 * @param {Rule} rule
654 */
655 this.trigger('afterCreateRuleInput', rule);
656
657 if (filter.default_value !== undefined) {
658 rule.value = filter.default_value;
659 }
660 else {
661 rule._updating_value = true;
662 rule.value = self.getRuleInputValue(rule);
663 rule._updating_value = false;
664 }
665};
666
667/**
668 * Performs action when a rule's filter changes
669 * @param {Rule} rule
670 * @param {object} previousFilter
671 * @fires QueryBuilder.afterUpdateRuleFilter
672 * @private
673 */
674QueryBuilder.prototype.updateRuleFilter = function(rule, previousFilter) {
675 this.createRuleOperators(rule);
676 this.createRuleInput(rule);
677
678 rule.$el.find(QueryBuilder.selectors.rule_filter).val(rule.filter ? rule.filter.id : '-1');
679
680 // clear rule data if the filter changed
681 if (previousFilter && rule.filter && previousFilter.id !== rule.filter.id) {
682 rule.data = undefined;
683 }
684
685 /**
686 * After the filter has been updated and the operators and input re-created
687 * @event afterUpdateRuleFilter
688 * @memberof QueryBuilder
689 * @param {Rule} rule
690 */
691 this.trigger('afterUpdateRuleFilter', rule);
692};
693
694/**
695 * Performs actions when a rule's operator changes
696 * @param {Rule} rule
697 * @param {object} previousOperator
698 * @fires QueryBuilder.afterUpdateRuleOperator
699 * @private
700 */
701QueryBuilder.prototype.updateRuleOperator = function(rule, previousOperator) {
702 var $valueContainer = rule.$el.find(QueryBuilder.selectors.value_container);
703
704 if (!rule.operator || rule.operator.nb_inputs === 0) {
705 $valueContainer.hide();
706
707 rule.__.value = undefined;
708 }
709 else {
710 $valueContainer.show();
711
712 if ($valueContainer.is(':empty') || !previousOperator ||
713 rule.operator.nb_inputs !== previousOperator.nb_inputs ||
714 rule.operator.optgroup !== previousOperator.optgroup
715 ) {
716 this.createRuleInput(rule);
717 }
718 }
719
720 if (rule.operator) {
721 rule.$el.find(QueryBuilder.selectors.rule_operator).val(rule.operator.type);
722 }
723
724 /**
725 * After the operator has been updated and the input optionally re-created
726 * @event afterUpdateRuleOperator
727 * @memberof QueryBuilder
728 * @param {Rule} rule
729 */
730 this.trigger('afterUpdateRuleOperator', rule);
731
732 this.updateRuleValue(rule);
733};
734
735/**
736 * Performs actions when rule's value changes
737 * @param {Rule} rule
738 * @fires QueryBuilder.afterUpdateRuleValue
739 * @private
740 */
741QueryBuilder.prototype.updateRuleValue = function(rule) {
742 if (!rule._updating_value) {
743 this.setRuleInputValue(rule, rule.value);
744 }
745
746 /**
747 * After the rule value has been modified
748 * @event afterUpdateRuleValue
749 * @memberof QueryBuilder
750 * @param {Rule} rule
751 */
752 this.trigger('afterUpdateRuleValue', rule);
753};
754
755/**
756 * Changes a rule's properties depending on its flags
757 * @param {Rule} rule
758 * @fires QueryBuilder.afterApplyRuleFlags
759 * @private
760 */
761QueryBuilder.prototype.applyRuleFlags = function(rule) {
762 var flags = rule.flags;
763 var Selectors = QueryBuilder.selectors;
764
765 if (flags.filter_readonly) {
766 rule.$el.find(Selectors.rule_filter).prop('disabled', true);
767 }
768 if (flags.operator_readonly) {
769 rule.$el.find(Selectors.rule_operator).prop('disabled', true);
770 }
771 if (flags.value_readonly) {
772 rule.$el.find(Selectors.rule_value).prop('disabled', true);
773 }
774 if (flags.no_delete) {
775 rule.$el.find(Selectors.delete_rule).remove();
776 }
777
778 /**
779 * After rule's flags has been applied
780 * @event afterApplyRuleFlags
781 * @memberof QueryBuilder
782 * @param {Rule} rule
783 */
784 this.trigger('afterApplyRuleFlags', rule);
785};
786
787/**
788 * Changes group's properties depending on its flags
789 * @param {Group} group
790 * @fires QueryBuilder.afterApplyGroupFlags
791 * @private
792 */
793QueryBuilder.prototype.applyGroupFlags = function(group) {
794 var flags = group.flags;
795 var Selectors = QueryBuilder.selectors;
796
797 if (flags.condition_readonly) {
798 group.$el.find('>' + Selectors.group_condition).prop('disabled', true)
799 .parent().addClass('readonly');
800 }
801 if (flags.no_add_rule) {
802 group.$el.find(Selectors.add_rule).remove();
803 }
804 if (flags.no_add_group) {
805 group.$el.find(Selectors.add_group).remove();
806 }
807 if (flags.no_delete) {
808 group.$el.find(Selectors.delete_group).remove();
809 }
810
811 /**
812 * After group's flags has been applied
813 * @event afterApplyGroupFlags
814 * @memberof QueryBuilder
815 * @param {Group} group
816 */
817 this.trigger('afterApplyGroupFlags', group);
818};
819
820/**
821 * Clears all errors markers
822 * @param {Node} [node] default is root Group
823 */
824QueryBuilder.prototype.clearErrors = function(node) {
825 node = node || this.model.root;
826
827 if (!node) {
828 return;
829 }
830
831 node.error = null;
832
833 if (node instanceof Group) {
834 node.each(function(rule) {
835 rule.error = null;
836 }, function(group) {
837 this.clearErrors(group);
838 }, this);
839 }
840};
841
842/**
843 * Adds/Removes error on a Rule or Group
844 * @param {Node} node
845 * @fires QueryBuilder.changer:displayError
846 * @private
847 */
848QueryBuilder.prototype.updateError = function(node) {
849 if (this.settings.display_errors) {
850 if (node.error === null) {
851 node.$el.removeClass('has-error');
852 }
853 else {
854 var errorMessage = this.translate('errors', node.error[0]);
855 errorMessage = Utils.fmt(errorMessage, node.error.slice(1));
856
857 /**
858 * Modifies an error message before display
859 * @event changer:displayError
860 * @memberof QueryBuilder
861 * @param {string} errorMessage - the error message (translated and formatted)
862 * @param {array} error - the raw error array (error code and optional arguments)
863 * @param {Node} node
864 * @returns {string}
865 */
866 errorMessage = this.change('displayError', errorMessage, node.error, node);
867
868 node.$el.addClass('has-error')
869 .find(QueryBuilder.selectors.error_container).eq(0)
870 .attr('title', errorMessage);
871 }
872 }
873};
874
875/**
876 * Triggers a validation error event
877 * @param {Node} node
878 * @param {string|array} error
879 * @param {*} value
880 * @fires QueryBuilder.validationError
881 * @private
882 */
883QueryBuilder.prototype.triggerValidationError = function(node, error, value) {
884 if (!$.isArray(error)) {
885 error = [error];
886 }
887
888 /**
889 * Fired when a validation error occurred, can be prevented
890 * @event validationError
891 * @memberof QueryBuilder
892 * @param {Node} node
893 * @param {string} error
894 * @param {*} value
895 */
896 var e = this.trigger('validationError', node, error, value);
897 if (!e.isDefaultPrevented()) {
898 node.error = error;
899 }
900};