From baa79ce9ca0691f335cd771c68b8b7967ba98e78 Mon Sep 17 00:00:00 2001
From: Reha Sterbin
Date: Fri, 14 Oct 2016 12:57:08 -0400
Subject: [PATCH 01/44] Began work on adding sections (placeholder commit)
---
src/core.js | 118 ++++++++++++++++++++++++++++++++++++++++++++++-
src/data.js | 8 ++++
src/defaults.js | 36 ++++++++++++---
src/i18n/en.json | 9 +++-
src/model.js | 66 +++++++++++++++++++++++++-
src/template.js | 56 +++++++++++++++++++++-
6 files changed, 281 insertions(+), 12 deletions(-)
diff --git a/src/core.js b/src/core.js
index 5ee8e0ac..fd63b573 100644
--- a/src/core.js
+++ b/src/core.js
@@ -9,9 +9,11 @@ QueryBuilder.prototype.init = function($el, options) {
this.settings = $.extendext(true, 'replace', {}, QueryBuilder.DEFAULTS, options);
this.model = new Model();
this.status = {
+ section_id: 0,
group_id: 0,
rule_id: 0,
generated_id: false,
+ has_sections: false,
has_optgroup: false,
has_operator_oprgroup: false,
id: null,
@@ -28,6 +30,7 @@ QueryBuilder.prototype.init = function($el, options) {
// SETTINGS SHORTCUTS
this.filters = this.settings.filters;
+ this.sections = this.settings.sections;
this.icons = this.settings.icons;
this.operators = this.settings.operators;
this.templates = this.settings.templates;
@@ -60,7 +63,13 @@ QueryBuilder.prototype.init = function($el, options) {
this.$el.addClass('query-builder form-inline');
this.filters = this.checkFilters(this.filters);
+ for (var k in this.sections) {
+ if (this.sections.hasOwnProperty(k)) {
+ this.sections[k].filters = this.checkFilters(this.sections[k].filters);
+ }
+ }
this.operators = this.checkOperators(this.operators);
+
this.bindEvents();
this.initPlugins();
@@ -245,6 +254,14 @@ QueryBuilder.prototype.bindEvents = function() {
}
});
+ // section exists change
+ this.$el.on('change.queryBuilder', Selectors.section_exists, function() {
+ if ($(this).is(':checked')) {
+ var $section = $(this).closest(Selectors.section_exists);
+ Model($section).condition = $(this).val();
+ }
+ });
+
// rule filter change
this.$el.on('change.queryBuilder', Selectors.rule_filter, function() {
var $rule = $(this).closest(Selectors.rule_container);
@@ -283,6 +300,20 @@ QueryBuilder.prototype.bindEvents = function() {
});
}
+ if (this.settings.allow_sections !== 0) {
+ // add section button
+ this.$el.on('click.queryBuilder', Selectors.add_section, function() {
+ var $section = $(this).closest(Selectors.group_container);
+ self.addSection(Model($section));
+ });
+
+ // delete section button
+ this.$el.on('click.queryBuilder', Selectors.delete_section, function() {
+ var $section = $(this).closest(Selectors.section_container);
+ self.deleteSection(Model($section));
+ });
+ }
+
// model events
this.model.on({
'drop': function(e, node) {
@@ -298,6 +329,10 @@ QueryBuilder.prototype.bindEvents = function() {
}
self.refreshGroupsConditions();
},
+ 'set': function(e, node) {
+ node.parent.$el.find('>' + Selectors.section_body).empty().append(node.$el);
+ self.updateSectionExistsFlag(node.section);
+ },
'move': function(e, node, group, index) {
node.$el.detach();
@@ -402,8 +437,14 @@ QueryBuilder.prototype.addGroup = function(parent, addRule, data, flags) {
}
var group_id = this.nextGroupId();
- var $group = $(this.getGroupTemplate(group_id, level));
- var model = parent.addGroup($group);
+ var $group = $(this.getGroupTemplate(group_id, level, parent instanceof Section || parent.section ? true : false));
+ console.log(parent);
+ if (parent instanceof Section) {
+ var model = parent.setGroup($group);
+ } else {
+ var model = parent.addGroup($group);
+ }
+ console.log(model);
model.data = data;
model.flags = $.extend({}, this.settings.default_group_flags, flags);
@@ -480,6 +521,79 @@ QueryBuilder.prototype.refreshGroupsConditions = function() {
}(this.model.root));
};
+//--section
+
+/**
+ * Add a new section
+ * @param parent {Group}
+ * @param addRule {bool,optional} add a default empty rule
+ * @param data {mixed,optional} section custom data
+ * @param flags {object,optional} flags to apply to the section
+ * @return section {Section}
+ */
+QueryBuilder.prototype.addSection = function(parent, addRule, data, flags) {
+ addRule = (addRule === undefined || addRule === true);
+
+ var level = parent.level + 1;
+
+ var e = this.trigger('beforeAddSection', parent, addRule, level);
+ if (e.isDefaultPrevented()) {
+ return null;
+ }
+
+ var section_id = this.nextSectionId();
+ var $section = $(this.getSectionTemplate(section_id, level));
+ var model = parent.addSection($section);
+
+ model.data = data;
+ model.flags = $.extend({}, this.settings.default_section_flags, flags);
+
+ this.trigger('afterAddSection', model);
+
+ model.exists = this.settings.default_exists;
+
+ this.addGroup(model, true, data, flags);
+
+ return model;
+};
+
+/**
+ * Tries to delete a section. The section is not deleted if at least one rule is no_delete.
+ * @param section {Section}
+ * @return {boolean} true if the section has been deleted
+ */
+QueryBuilder.prototype.deleteSection = function(section) {
+ var e = this.trigger('beforeDeleteSection', section);
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+
+ if (!this.deleteGroup(section.group)) {
+ return false;
+ }
+
+ section.drop();
+ this.trigger('afterDeleteSection');
+
+ return true;
+};
+
+/**
+ * Changes the exists setting of a section
+ * @param section {Section}
+ */
+QueryBuilder.prototype.updateSectionExistsFlag = function(section) {
+ section.$el.find('>' + Selectors.section_exists_flag).each(function() {
+ var $this = $(this);
+ $this.prop('checked', $this.val() === section.exists);
+ $this.parent().toggleClass('active', $this.val() === section.exists);
+ });
+
+ this.trigger('afterUpdateSectionExistsFlag', section);
+};
+
+//--section
+
/**
* Add a new rule
* @param parent {Group}
diff --git a/src/data.js b/src/data.js
index 2f1752dc..73f40ec8 100644
--- a/src/data.js
+++ b/src/data.js
@@ -215,6 +215,14 @@ QueryBuilder.prototype.nextRuleId = function() {
return this.status.id + '_rule_' + (this.status.rule_id++);
};
+/**
+ * Returns an incremented section ID
+ * @return {string}
+ */
+QueryBuilder.prototype.nextSectionId = function() {
+ return this.status.id + '_section_' + (this.status.section_id++);
+};
+
/**
* Returns the operators for a filter
* @param filter {string|object} (filter id name or filter object)
diff --git a/src/defaults.js b/src/defaults.js
index fffde6f5..b2dbd84d 100644
--- a/src/defaults.js
+++ b/src/defaults.js
@@ -28,6 +28,7 @@ QueryBuilder.inputs = [
QueryBuilder.modifiable_options = [
'display_errors',
'allow_groups',
+ 'allow_sections',
'allow_empty',
'default_condition',
'default_filter'
@@ -37,6 +38,7 @@ QueryBuilder.modifiable_options = [
* CSS selectors for common components
*/
var Selectors = QueryBuilder.selectors = {
+ section_container: '.rules-section-container',
group_container: '.rules-group-container',
rule_container: '.rule-container',
filter_container: '.rule-filter-container',
@@ -45,13 +47,17 @@ var Selectors = QueryBuilder.selectors = {
error_container: '.error-container',
condition_container: '.rules-group-header .group-conditions',
- rule_header: '.rule-header',
+ section_header: '.rules-section-header',
group_header: '.rules-group-header',
+ rule_header: '.rule-header',
+ section_actions: '.section-actions',
group_actions: '.group-actions',
rule_actions: '.rule-actions',
+ section_body: '.rules-section-body',
rules_list: '.rules-group-body>.rules-list',
+ section_exists_flag: '.rules-section-header [name$=_exists]',
group_condition: '.rules-group-header [name$=_cond]',
rule_filter: '.rule-filter-container [name$=_filter]',
rule_operator: '.rule-operator-container [name$=_operator]',
@@ -60,7 +66,9 @@ var Selectors = QueryBuilder.selectors = {
add_rule: '[data-add=rule]',
delete_rule: '[data-delete=rule]',
add_group: '[data-add=group]',
- delete_group: '[data-delete=group]'
+ delete_group: '[data-delete=group]',
+ add_section: '[data-add=section]',
+ delete_section: '[data-delete=section]'
};
/**
@@ -104,14 +112,18 @@ QueryBuilder.OPERATORS = {
*/
QueryBuilder.DEFAULTS = {
filters: [],
+ sections: [],
plugins: [],
sort_filters: false,
display_errors: true,
+ allow_sections: -1,
allow_groups: -1,
allow_empty: false,
conditions: ['AND', 'OR'],
default_condition: 'AND',
+ exist_options: ['EXISTS', 'DOES NOT EXIST'],
+ default_exists: 'EXISTS',
inputs_separator: ' , ',
select_placeholder: '------',
display_empty_filter: true,
@@ -132,7 +144,15 @@ QueryBuilder.DEFAULTS = {
no_delete: false
},
+ default_section_flags: {
+ exists_readonly: false,
+ no_add_rule: false,
+ no_add_group: false,
+ no_delete: false
+ },
+
templates: {
+ section: null,
group: null,
rule: null,
filterSelect: null,
@@ -166,10 +186,12 @@ QueryBuilder.DEFAULTS = {
],
icons: {
- add_group: 'glyphicon glyphicon-plus-sign',
- add_rule: 'glyphicon glyphicon-plus',
- remove_group: 'glyphicon glyphicon-remove',
- remove_rule: 'glyphicon glyphicon-remove',
- error: 'glyphicon glyphicon-warning-sign'
+ add_section: 'glyphicon glyphicon-plus-sign',
+ add_group: 'glyphicon glyphicon-plus-sign',
+ add_rule: 'glyphicon glyphicon-plus',
+ remove_section: 'glyphicon glyphicon-remove',
+ remove_group: 'glyphicon glyphicon-remove',
+ remove_rule: 'glyphicon glyphicon-remove',
+ error: 'glyphicon glyphicon-warning-sign'
}
};
diff --git a/src/i18n/en.json b/src/i18n/en.json
index b5828139..60c0d07e 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -4,14 +4,21 @@
"add_rule": "Add rule",
"add_group": "Add group",
+ "add_section": "Add section",
"delete_rule": "Delete",
"delete_group": "Delete",
+ "delete_section": "Delete",
"conditions": {
"AND": "AND",
"OR": "OR"
},
+ "exist_options": {
+ "EXISTS": "EXISTS",
+ "NOT EXISTS": "DOES NOT EXIST"
+ },
+
"operators": {
"equal": "equal",
"not_equal": "not equal",
@@ -58,4 +65,4 @@
"boolean_not_valid": "Not a boolean",
"operator_not_multiple": "Operator {0} cannot accept multiple values"
}
-}
\ No newline at end of file
+}
diff --git a/src/model.js b/src/model.js
index f93dc8e3..df02b19d 100644
--- a/src/model.js
+++ b/src/model.js
@@ -221,7 +221,7 @@ Node.prototype._move = function(group, index) {
// GROUP CLASS
// ===============================
/**
- * @param {Group}
+ * @param {Group|Section}
* @param {jQuery}
*/
var Group = function(parent, $el) {
@@ -232,6 +232,7 @@ var Group = function(parent, $el) {
Node.call(this, parent, $el);
this.rules = [];
+ this.section = null;
this.__.condition = null;
};
@@ -281,6 +282,9 @@ Group.prototype._appendNode = function(node, index, trigger) {
this.rules.splice(index, 0, node);
node.parent = this;
+ if (!(node instanceof Section)) {
+ node.section = this.section;
+ }
if (trigger && this.model !== null) {
this.model.trigger('add', node, index);
@@ -309,6 +313,17 @@ Group.prototype.addRule = function($el, index) {
return this._appendNode(new Rule(this, $el), index, true);
};
+/**
+ * Add a Section by jQuery element at specified index
+ * @param {jQuery}
+ * @param {int,optional}
+ * @return {Section} the inserted section
+ */
+Group.prototype.addSection = function($el, index) {
+ console.log($el);
+ return this._appendNode(new Section(this, $el), index, true);
+};
+
/**
* Delete a specific Node
* @param {Node}
@@ -410,6 +425,8 @@ var Rule = function(parent, $el) {
Node.call(this, parent, $el);
+ this.section = null;
+
this.__.filter = null;
this.__.operator = null;
this.__.flags = {};
@@ -421,8 +438,55 @@ Rule.prototype.constructor = Rule;
defineModelProperties(Rule, ['filter', 'operator', 'value']);
+// Section CLASS
+// ===============================
+/**
+ * @param {Section}
+ * @param {jQuery}
+ */
+var Section = function(parent, $el) {
+ if (!(this instanceof Section)) {
+ return new Section(parent, $el);
+ }
+
+ Node.call(this, parent, $el);
+
+ this.group = null;
+ this.__.exists = null;
+};
+
+Section.prototype = Object.create(Node.prototype);
+Section.prototype.constructor = Section;
+
+defineModelProperties(Section, ['exists']);
+
+/**
+ * Set the root group of the section by jQuery element
+ * @param {jQuery}
+ * @return {Group} the new root group
+ */
+Section.prototype.setGroup = function($el) {
+ this.group = new Group(this, $el);
+ this.group.parent = this;
+ this.group.section = this;
+ if (this.model !== null) {
+ this.model.trigger('set', this.group);
+ }
+ return this.group;
+};
+
+/**
+ * Delete self
+ */
+Section.prototype.drop = function() {
+ this.group.empty();
+ Node.prototype.drop.call(this);
+};
+
// EXPORT
// ===============================
QueryBuilder.Group = Group;
QueryBuilder.Rule = Rule;
+QueryBuilder.Section = Section;
+
diff --git a/src/template.js b/src/template.js
index 3a2756e4..b8808303 100644
--- a/src/template.js
+++ b/src/template.js
@@ -10,6 +10,11 @@ QueryBuilder.templates.group = '\
{{= it.lang.add_group }} \
\
{{?}} \
+ {{? !it.in_section }} \
+ \
+ {{?}} \
{{? it.level>1 }} \