2 * # angular-elastic-builder
3 * ## Angular Module for building an Elasticsearch Query
6 * @link https://github.com/dncrews/angular-elastic-builder.git
8 * @author Dan Crews <crewsd@gmail.com>
12 * angular-elastic-builder
16 * Angular Module for building an Elasticsearch query
22 angular.module('angular-elastic-builder', [
29 * angular-elastic-builder
31 * /src/directives/BuilderDirective.js
33 * Angular Directive for injecting a query builder form.
39 angular.module('angular-elastic-builder')
40 .directive('elasticBuilder', [
41 'elasticQueryService',
43 function EB(elasticQueryService) {
47 data: '=elasticBuilder',
50 templateUrl: 'angular-elastic-builder/BuilderDirective.html',
52 link: function(scope) {
53 var data = scope.data;
58 * Removes either Group or Rule
60 scope.removeChild = function(idx) {
61 scope.filters.splice(idx, 1);
67 scope.addRule = function() {
68 scope.filters.push({});
72 * Adds a Group of Rules
74 scope.addGroup = function() {
83 * Any time "outside forces" change the query, they should tell us so via
86 scope.$watch('data.needsUpdate', function(curr) {
91 scope.filters = elasticQueryService.toFilters(data.query, scope.data.fields);
92 scope.data.needsUpdate = false;
96 * Changes on the page update the Query
98 scope.$watch('filters', function(curr) {
103 data.query = elasticQueryService.toQuery(scope.filters, scope.data.fields);
114 * angular-elastic-builder
116 * /src/directives/Chooser.js
118 * This file is to help recursively, to decide whether to show a group or rule
124 var app = angular.module('angular-elastic-builder');
126 app.directive('elasticBuilderChooser', [
130 function elasticBuilderChooser(RH, groupClassHelper) {
135 item: '=elasticBuilderChooser',
139 templateUrl: 'angular-elastic-builder/ChooserDirective.html',
141 compile: function (element) {
142 return RH.compile(element, function(scope, el, attrs) {
143 var depth = scope.depth = (+ attrs.depth)
146 scope.getGroupClassName = function() {
148 if (item.type === 'group') level++;
150 return groupClassHelper(level);
162 * angular-elastic-builder
164 * /src/directives/Group.js
170 var app = angular.module('angular-elastic-builder');
172 app.directive('elasticBuilderGroup', [
176 function elasticBuilderGroup(RH, groupClassHelper) {
181 group: '=elasticBuilderGroup',
185 templateUrl: 'angular-elastic-builder/GroupDirective.html',
187 compile: function(element) {
188 return RH.compile(element, function(scope, el, attrs) {
189 var depth = scope.depth = (+ attrs.depth);
190 var group = scope.group;
192 scope.addRule = function() {
193 group.rules.push({});
195 scope.addGroup = function() {
203 scope.removeChild = function(idx) {
204 group.rules.splice(idx, 1);
207 scope.getGroupClassName = function() {
208 return groupClassHelper(depth + 1);
220 * angular-elastic-builder
222 * /src/directives/Rule.js
228 var app = angular.module('angular-elastic-builder');
230 app.directive('elasticBuilderRule', [
232 function elasticBuilderRule() {
236 rule: '=elasticBuilderRule',
240 templateUrl: 'angular-elastic-builder/RuleDirective.html',
242 link: function(scope) {
243 scope.getType = function() {
244 var fields = scope.elasticFields;
245 var field = scope.rule.field;
247 if (!fields || !field) {
251 if (fields[field].subType === 'boolean') {
255 return fields[field].type;
266 * angular-elastic-builder
268 * /src/directives/RuleTypes.js
270 * Determines which Rule type should be displayed
276 var app = angular.module('angular-elastic-builder');
278 app.directive('elasticType', [
283 type: '=elasticType',
288 template: '<ng-include src="getTemplateUrl()" />',
290 link: function(scope) {
291 scope.getTemplateUrl = function() {
292 var type = scope.type;
295 type = type.charAt(0).toUpperCase() + type.slice(1);
297 return 'angular-elastic-builder/types/' + type + '.html';
300 // This is a weird hack to make sure these are numbers
301 scope.booleans = [ 'False', 'True' ];
302 scope.booleansOrder = [ 'True', 'False' ];
304 scope.inputNeeded = function() {
315 return ~needs.indexOf(scope.rule.subType);
325 (function(angular) {"use strict"; angular.module("angular-elastic-builder").run(["$templateCache", function($templateCache) {$templateCache.put("angular-elastic-builder/BuilderDirective.html","<!-- <div class=\"elastic-builder\">\r\n <div class=\"filter-panels\">\r\n <div class=\"list-group form-inline\">\r\n <div\r\n data-ng-repeat=\"filter in filters\"\r\n data-elastic-builder-chooser=\"filter\"\r\n data-elastic-fields=\"data.fields\"\r\n data-on-remove=\"removeChild($index)\"\r\n data-depth=\"0\"></div>\r\n <div class=\"list-group-item actions\">\r\n <a class=\"btn btn-xs btn-primary\" title=\"Add Rule\" data-ng-click=\"addRule()\">\r\n <i class=\"fa fa-plus\"></i>\r\n </a>\r\n <a class=\"btn btn-xs btn-primary\" title=\"Add Group\" data-ng-click=\"addGroup()\">\r\n <i class=\"fa fa-list\"></i>\r\n </a>\r\n </div>\r\n </div>\r\n </div>\r\n</div> -->\r\n\r\n<!-- <div class=\"elastic-builder\">\r\n <div class=\"filter-panels\"> -->\r\n <div class=\"list-group form-inline list-group-item-custom\" ng-switch=\"data.type\">\r\n <div\r\n data-ng-repeat=\"filter in filters\"\r\n data-elastic-builder-chooser=\"filter\"\r\n data-elastic-fields=\"data.fields\"\r\n data-on-remove=\"removeChild($index)\"\r\n data-depth=\"0\">\r\n </div>\r\n <div class=\"list-group-item actions list-group-item-custom\" ng-switch-when=\"condition\">\r\n <a href=\"#\" class=\"btn green btn-sm\" data-ng-click=\"addGroup()\">\r\n <i class=\"icon-plus\"></i> {{ \'APPLICATION_ADD_GROUP\' | translate }}\r\n </a>\r\n </div>\r\n <div class=\"list-group-item actions list-group-item-custom\" ng-switch-when=\"action\">\r\n <a href=\"#\" class=\"btn green btn-sm\" data-ng-click=\"addRule()\">\r\n <i class=\"icon-plus\"></i> {{ \'APPLICATION_ADD_ACTION\' | translate }}\r\n </a>\r\n </div>\r\n <div class=\"list-group-item actions list-group-item-custom\" ng-switch-default>\r\n <a href=\"#\" class=\"btn green btn-sm\" data-ng-click=\"addRule()\">\r\n <i class=\"icon-plus\"></i> {{ \'APPLICATION_ADD_CONDITION\' | translate }}\r\n </a>\r\n <a href=\"#\" class=\"btn green btn-sm\" data-ng-click=\"addGroup()\">\r\n <i class=\"icon-plus\"></i> {{ \'APPLICATION_ADD_GROUP\' | translate }}\r\n </a>\r\n </div>\r\n </div>\r\n <!-- </div>\r\n</div> -->\r\n");
326 $templateCache.put("angular-elastic-builder/ChooserDirective.html","<!-- <div\r\n class=\"list-group-item elastic-builder-chooser\"\r\n data-ng-class=\"getGroupClassName()\">\r\n\r\n <div data-ng-if=\"item.type === \'group\'\"\r\n data-elastic-builder-group=\"item\"\r\n data-depth=\"{{ depth }}\"\r\n data-elastic-fields=\"elasticFields\"\r\n data-on-remove=\"onRemove()\"></div>\r\n\r\n <div data-ng-if=\"item.type !== \'group\'\"\r\n data-elastic-builder-rule=\"item\"\r\n data-elastic-fields=\"elasticFields\"\r\n data-on-remove=\"onRemove()\"></div>\r\n\r\n</div> -->\r\n\r\n\r\n<div class=\"list-group-item elastic-builder-chooser list-group-item-custom\" data-ng-class=\"getGroupClassName()\">\r\n <div data-ng-if=\"item.type === \'group\'\"\r\n data-elastic-builder-group=\"item\"\r\n data-depth=\"{{ depth }}\"\r\n data-elastic-fields=\"elasticFields\"\r\n data-on-remove=\"onRemove()\"></div>\r\n <div data-ng-if=\"item.type !== \'group\'\"\r\n data-elastic-builder-rule=\"item\"\r\n data-elastic-fields=\"elasticFields\"\r\n data-on-remove=\"onRemove()\"></div>\r\n</div>\r\n");
327 $templateCache.put("angular-elastic-builder/GroupDirective.html","<!-- <div class=\"elastic-builder-group\">\r\n <h5>If\r\n <select data-ng-model=\"group.subType\" class=\"form-control\">\r\n <option value=\"and\">all</option>\r\n <option value=\"or\">any</option>\r\n </select>\r\n of these conditions are met\r\n </h5>\r\n <div\r\n data-ng-repeat=\"rule in group.rules\"\r\n data-elastic-builder-chooser=\"rule\"\r\n data-elastic-fields=\"elasticFields\"\r\n data-depth=\"{{ +depth + 1 }}\"\r\n data-on-remove=\"removeChild($index)\"></div>\r\n\r\n <div class=\"list-group-item actions\" data-ng-class=\"getGroupClassName()\">\r\n <a class=\"btn btn-xs btn-primary\" title=\"Add Sub-Rule\" data-ng-click=\"addRule()\">\r\n <i class=\"fa fa-plus\"></i>\r\n </a>\r\n <a class=\"btn btn-xs btn-primary\" title=\"Add Sub-Group\" data-ng-click=\"addGroup()\">\r\n <i class=\"fa fa-list\"></i>\r\n </a>\r\n </div>\r\n\r\n <a class=\"btn btn-xs btn-danger remover\" data-ng-click=\"onRemove()\">\r\n <i class=\"fa fa-minus\"></i>\r\n </a>\r\n</div> -->\r\n\r\n\r\n<!-- BEGIN Portlet PORTLET-->\r\n<div class=\"portlet light\">\r\n <div class=\"portlet-title\">\r\n <div class=\"caption\" data-ng-class=\"{\'font-blue-hoki\': data.type === \'condition\', \'font-red-flamingo\': data.type === \'action\'}\">\r\n <!-- <i class=\"icon-people font-green-sharp\"></i> -->\r\n <h5><span class=\"caption-subject\">{{ \'APPLICATION_IF\' | translate }}</span>\r\n <select data-ng-model=\"group.subType\" class=\"form-control\">\r\n <option value=\"and\"><strong>{{ \'APPLICATION_ALL\' | translate | lowercase }}</strong></option>\r\n <option value=\"or\"><strong>{{ \'APPLICATION_ANY\' | translate | lowercase }}</strong></option>\r\n </select>\r\n <span class=\"caption-subject\">{{group.subType == \'and\' ? \'MESSAGE_CODITIONS_MET_ALL\' : \'MESSAGE_CODITIONS_MET_ANY\' | translate}}</span>\r\n </h5>\r\n </div>\r\n </div>\r\n <div class=\"portlet-body\">\r\n <div class=\"elastic-builder-group\">\r\n <div\r\n data-ng-repeat=\"rule in group.rules\"\r\n data-elastic-builder-chooser=\"rule\"\r\n data-elastic-fields=\"elasticFields\"\r\n data-depth=\"{{ +depth + 1 }}\"\r\n data-on-remove=\"removeChild($index)\"></div>\r\n\r\n <div class=\"list-group-item actions list-group-item-custom\" data-ng-class=\"getGroupClassName()\">\r\n <a href=\"#\" class=\"btn green btn-sm\" data-ng-click=\"addRule()\">\r\n <i class=\"icon-plus\"></i> {{ \'APPLICATION_ADD_CONDITION\' | translate }}\r\n </a>\r\n <a class=\"btn red btn-sm remover\" data-ng-click=\"onRemove()\">\r\n <i class=\"icon-trash\"></i> {{ \'APPLICATION_REMOVE_GROUP\' | translate }}\r\n </a>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n<!-- END Portlet PORTLET-->\r\n");
328 $templateCache.put("angular-elastic-builder/RuleDirective.html","<!-- <div class=\"elastic-builder-rule\">\r\n <select class=\"form-control\" data-ng-model=\"rule.field\" data-ng-options=\"key as key for (key, value) in elasticFields\"></select>\r\n\r\n <span data-elastic-type=\"getType()\" data-rule=\"rule\" data-guide=\"elasticFields[rule.field]\"></span>\r\n\r\n <a class=\"btn btn-xs btn-danger remover\" data-ng-click=\"onRemove()\">\r\n <i class=\"fa fa-minus\"></i>\r\n </a>\r\n\r\n</div> -->\r\n<!-- <div class=\"elastic-builder-rule\"> -->\r\n <!-- <select class=\"form-control\" data-ng-model=\"rule.field\" data-ng-options=\"key as key for (key, value) in elasticFields\"></select> -->\n {{elasticFields[rule.field].action.label | translate}}\n <select class=\"form-control\" data-ng-model=\"rule.field\" >\n <option ng-repeat=\"option in elasticFields\" value=\"{{option.value}}\">{{ option.label | translate }}</option>\n </select>\n <!-- <ui-select class=\"form-control\" data-ng-model=\"rule.field\" theme=\"bootstrap\">\r\n <ui-select-match placeholder=\"{{\'APPLICATION_CONDITION\' | translate}}\">{{$translate.instant($select.selected.name)}}</ui-select-match>\r\n <ui-select-choices repeat=\"value.value as key for (key, value) in elasticFields | filter: $select.search\">\r\n {{$translate.instant(key.name)}}\r\n </ui-select-choices>\r\n </ui-select> -->\r\n\r\n <span data-elastic-type=\"getType()\" data-rule=\"rule\" data-guide=\"elasticFields[rule.field]\"></span>\r\n\r\n <!-- <a class=\"btn btn-xs btn-danger remover\" data-ng-click=\"onRemove()\">\r\n <i class=\"fa fa-minus\"></i>\r\n </a> -->\r\n\r\n <a class=\"btn remover\" data-ng-click=\"onRemove()\">\r\n <i class=\"icon-trash\"></i>\r\n </a>\r\n\r\n<!-- </div> -->\r\n");
329 $templateCache.put("angular-elastic-builder/types/Boolean.html","<span class=\"boolean-rule\">\r\n Equals\r\n\r\n <!-- This is a weird hack to make sure these are numbers -->\r\n <select\r\n data-ng-model=\"rule.value\"\r\n class=\"form-control\"\r\n data-ng-options=\"booleans.indexOf(choice) as choice for choice in booleansOrder\">\r\n </select>\r\n</span>\r\n");
330 $templateCache.put("angular-elastic-builder/types/Date.html","<span class=\"date-rule\">\r\n <select data-ng-model=\"rule.subType\" class=\"form-control\">\r\n\r\n <optgroup label=\"Generic\">\r\n <option value=\"exists\">Exists</option>\r\n <option value=\"notExists\">! Exists</option>\r\n </optgroup>\r\n </select>\r\n\r\n</span>\r\n");
331 $templateCache.put("angular-elastic-builder/types/Multi.html","<!-- <span class=\"multi-rule\">\r\n <span data-ng-repeat=\"choice in guide.choices\">\r\n <label class=\"checkbox\">\r\n <input type=\"checkbox\" data-ng-model=\"rule.values[choice]\">\r\n {{ choice }}\r\n </label>\r\n </span>\r\n</span> -->\r\n\r\n<span class=\"multi-rule\">\r\n {{guide.action.midterm | translate}}\r\n <!-- Range Fields -->\r\n <!-- <select data-ng-model=\"rule.values\" class=\"form-control\"> -->\r\n <select data-ng-model=\"rule.value\" class=\"form-control\">\r\n <!-- <optgroup label=\"Numeral\"> -->\r\n <option data-ng-repeat=\"(key, value) in guide.choices\" value=\"{{value}}\">{{key}}</option>\r\n <!-- </optgroup> -->\r\n\r\n <!-- <optgroup label=\"Generic\">\r\n <option value=\"exists\">Exists</option>\r\n <option value=\"notExists\">! Exists</option>\r\n </optgroup> -->\r\n </select>\r\n\r\n <!-- <input data-ng-if=\"inputNeeded()\"\r\n class=\"form-control\"\r\n data-ng-model=\"rule.value\"\r\n type=\"number\"\r\n min=\"{{ guide.minimum }}\"\r\n max=\"{{ guide.maximum }}\"> -->\r\n</span>\r\n");
332 $templateCache.put("angular-elastic-builder/types/Multiterm.html","<span class=\"elastic-multiterm\">\r\n <select data-ng-model=\"rule.subType\" class=\"form-control\">\r\n <option value=\"equals\">{{ \'APPLICATION_EQUALS_TO\' | translate }}</option>\r\n <option value=\"notEquals\">{{ \'APPLICATION_NOT_EQUALS_TO\' | translate }}</option>\r\n </select>\r\n <select data-ng-model=\"rule.value\" class=\"form-control\">\r\n <option data-ng-repeat=\"(key, value) in guide.choices\" value=\"{{value}}\">{{value | capitalize}}</option>\r\n </select>\r\n</span>\r\n");
333 $templateCache.put("angular-elastic-builder/types/Number.html","<span class=\"number-rule\">\r\n <select data-ng-model=\"rule.subType\" class=\"form-control\">\r\n <!-- <optgroup label=\"Numeral\"> -->\r\n <option value=\"equals\">=</option>\r\n <option value=\"gt\">></option>\r\n <option value=\"gte\">≥</option>\r\n <option value=\"lt\"><</option>\r\n <option value=\"lte\">≤</option>\r\n <!-- </optgroup> -->\r\n\r\n <!-- <optgroup label=\"Generic\"> -->\r\n <!-- <option value=\"exists\">Exists</option>\r\n <option value=\"notExists\">! Exists</option> -->\r\n <!-- </optgroup> -->\r\n </select>\r\n\r\n <!-- Range Fields -->\r\n <input data-ng-if=\"inputNeeded()\"\r\n class=\"form-control\"\r\n data-ng-model=\"rule.value\"\r\n type=\"number\"\r\n min=\"{{ guide.minimum }}\"\r\n max=\"{{ guide.maximum }}\">\r\n\r\n <!-- <span class=\"help-block\">{{rule.help}}</span> -->\r\n\r\n</span>\r\n");
334 $templateCache.put("angular-elastic-builder/types/Term.html","<!-- <span class=\"elastic-term\">\r\n <select data-ng-model=\"rule.subType\" class=\"form-control\"> -->\r\n <!-- Term Options -->\r\n <!-- <optgroup label=\"Text\">\r\n <option value=\"equals\">Equals</option>\r\n <option value=\"notEquals\">! Equals</option>\r\n </optgroup> -->\r\n\r\n <!-- Generic Options -->\r\n <!-- <optgroup label=\"Generic\">\r\n <option value=\"exists\">Exists</option>\r\n <option value=\"notExists\">! Exists</option>\r\n </optgroup>\r\n\r\n </select>\r\n <input\r\n data-ng-if=\"inputNeeded()\"\r\n class=\"form-control\"\r\n data-ng-model=\"rule.value\"\r\n type=\"text\">\r\n</span> -->\r\n\r\n\r\n<span class=\"elastic-term\">\r\n <select data-ng-model=\"rule.subType\" class=\"form-control\">\r\n <!-- Term Options -->\r\n <!-- <optgroup label=\"Text\"> -->\r\n <option value=\"equals\">{{ \'APPLICATION_EQUALS_TO\' | translate }}</option>\r\n <option value=\"notEquals\">{{ \'APPLICATION_NOT_EQUALS_TO\' | translate }}</option>\r\n <!-- </optgroup> -->\r\n\r\n <!-- Generic Options -->\r\n <!-- <optgroup label=\"Generic\">\r\n <option value=\"exists\">Exists</option>\r\n <option value=\"notExists\">! Exists</option>\r\n </optgroup> -->\r\n\r\n </select>\r\n <input\r\n data-ng-if=\"inputNeeded()\"\r\n class=\"form-control input-medium\"\r\n data-ng-model=\"rule.value\"\r\n type=\"text\">\r\n</span>\r\n");}]);})(window.angular);
336 * angular-elastic-builder
338 * /src/services/GroupClassHelper.js
340 * This keeps all of the groups colored correctly
346 angular.module('angular-elastic-builder')
347 .factory('groupClassHelper', function groupClassHelper() {
349 return function(level) {
352 // 'list-group-item-info',
353 // 'list-group-item-success',
354 // 'list-group-item-warning',
355 // 'list-group-item-danger',
358 return levels[level % levels.length];
365 * angular-elastic-builder
367 * /src/services/QueryService.js
369 * This file is used to convert filters into queries, and vice versa
375 angular.module('angular-elastic-builder')
376 .factory('elasticQueryService', [
380 toFilters: toFilters,
386 function toFilters(query, fieldMap) {
387 var filters = query.map(parseQueryGroup.bind(query, fieldMap));
391 function toQuery(filters, fieldMap) {
392 var query = filters.map(parseFilterGroup.bind(filters, fieldMap)).filter(function(item) {
398 function parseQueryGroup(fieldMap, group, truthy) {
399 if (truthy !== false) {
403 var key = Object.keys(group)[0],
409 type = typeMap[key] || 'item',
410 obj = getFilterTemplate(type);
415 obj.rules = group[key].map(parseQueryGroup.bind(group, fieldMap));
420 obj.field = group[key].field;
423 missing: 'notExists',
429 obj.field = Object.keys(group[key])[0];
430 var fieldData = fieldMap[Object.keys(group[key])[0]];
432 if (fieldData.type === 'multi') {
433 var vals = group[key][obj.field];
434 if (typeof vals === 'string') {
437 // MY CUSTOM MODIFICATION
438 obj.value = group[key][obj.field];
439 // obj.values = fieldData.choices.reduce(function(prev, choice) {
440 // prev[choice] = truthy === (group[key][obj.field].indexOf(choice) > -1);
444 obj.subType = truthy ? 'equals' : 'notEquals';
445 obj.value = group[key][obj.field];
447 if (typeof obj.value === 'number') {
448 obj.subType = 'boolean';
453 obj.field = Object.keys(group[key])[0];
454 obj.subType = Object.keys(group[key][obj.field])[0];
455 obj.value = group[key][obj.field][obj.subType];
458 obj = parseQueryGroup(fieldMap, group[key].filter, false);
461 obj.field = Object.keys(group[key])[0];
468 function parseFilterGroup(fieldMap, group) {
470 if (group.type === 'group') {
471 obj[group.subType] = group.rules.map(parseFilterGroup.bind(group, fieldMap)).filter(function(item) {
477 var fieldName = group.field;
478 var fieldData = fieldMap[fieldName];
484 switch (fieldData.type) {
487 if (fieldData.subType === 'boolean') {
488 group.subType = 'boolean';
491 if (!group.subType) {
494 switch (group.subType) {
497 if (group.value === undefined) {
501 obj.term[fieldName] = group.value;
504 if (group.value === undefined) {
512 obj.not.filter.term[fieldName] = group.value;
525 throw new Error('unexpected subtype ' + group.subType);
531 obj.range[fieldName] = {};
532 obj.range[fieldName][group.subType] = group.value;
536 if (group.subType === 'exists') {
540 } else if (group.subType === 'notExists') {
545 throw new Error('unexpected subtype');
552 obj.terms[fieldName] = group.value;
553 // obj.terms[fieldName] = Object.keys(group.values || {}).reduce(function(prev, key) {
554 // if (group.values[key]) prev.push(key);
561 throw new Error('unexpected type');
567 function getFilterTemplate(type) {
586 return angular.copy(templates[type]);