Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@momsfriendlydevco/angular-ui-query-builder

Package Overview
Dependencies
Maintainers
3
Versions
33
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@momsfriendlydevco/angular-ui-query-builder - npm Package Compare versions

Comparing version 1.2.9 to 1.2.10

579

dist/angular-ui-query-builder-core.js

@@ -7,3 +7,6 @@ 'use strict';

angular.module('angular-ui-query-builder', []).service('QueryBuilder', function () {
angular.module('angular-ui-query-builder', [])
// Service: QueryBuilder {{{
.service('QueryBuilder', function () {
var QueryBuilder = this;

@@ -28,2 +31,31 @@

/**
* List of additional properties that we support but need special treatment
* @var {Object} Each key is the property name with additional details in the Object value
* @param {string} [type='hidden'] How to handle each property within the UI
* @param {boolean} [canDelete=true] Disable deletion on the field
* @param {*} [...] Other inherited properties (see QueryBuilder.queryToArray) for examples
*/
QueryBuilder.metaProperties = {
limit: {
type: 'keyVal',
actions: [{ id: '$eq', title: 'Equals' }],
action: '$eq',
canDelete: true
},
populate: { type: 'hidden' },
skip: {
type: 'keyVal',
actions: [{ id: '$eq', title: 'Equals' }],
action: '$eq',
canDelete: true
},
sort: {
type: 'keyVal',
actions: [{ id: '$eq', title: 'Equals' }],
action: '$eq',
canDelete: false
}
};
/**
* Returns a queryList collection from a query object

@@ -40,3 +72,4 @@ * @param {Object} query The raw MongoDB / Sift object to transform from an object into a collection

var maps = spec[k] // Maps onto a spec path
|| k == '$and' || k == '$or';
|| k == '$and' || k == '$or' || QueryBuilder.metaProperties[k]; // is a meta directive
if (!maps) console.warn('query-builder', 'Incomming query path', k, 'Does not map to anything in spec', spec);

@@ -86,2 +119,12 @@ return !!maps;

};
} else if (QueryBuilder.metaProperties[k]) {
// Is a meta property
return Object.assign({
path: k,
title: _.startCase(k),
value: v,
type: 'hidden',
action: '$hidden',
actions: actions
}, QueryBuilder.metaProperties[k]);
} else if (firstKey == '$exists') {

@@ -96,3 +139,3 @@ return {

};
} else if (s.type == 'string' && _.isArray(s.enum)) {
} else if (s.type == 'string' && _.isArray(s.enum) && s.enum.length) {
return {

@@ -113,4 +156,4 @@ path: k,

type: s.type == 'string' ? 'string' : s.type == 'number' ? 'number' : s.type == 'date' ? 'date' : 'string',
action: firstKey,
value: s.type == 'date' ? moment(firstValue).format('YYYY-MM-DD') // Convert date objects back to strings
action: '$eq',
value: s.type == 'date' ? moment(firstValue).toDate() // Convert date string weirdness into real dates
: firstValue,

@@ -153,2 +196,5 @@ actions: actions

});
case 'keyVal':
case 'hidden':
return ql.value;
default:

@@ -163,3 +209,5 @@ console.warn('Unknown type to convert:', ql.type);

})
// }}}
// Component: uiQueryBuilder {{{
/**

@@ -183,10 +231,11 @@ * Master query builder component

$ctrl.qbQuery;
$scope.$watchGroup(['$ctrl.query', '$ctrl.spec'], function () {
var initUnwatch = $scope.$watchGroup(['$ctrl.query', '$ctrl.spec'], function () {
if (!$ctrl.spec || !$ctrl.query) return; // Not yet got everything we need
$ctrl.qbSpec = QueryBuilder.cleanSpec($ctrl.spec);
$ctrl.qbQuery = QueryBuilder.queryToArray($ctrl.query, $ctrl.qbSpec);
initUnwatch(); // Release the watcher so we don't get stuck in a loop
});
// }}}
/**

@@ -253,3 +302,5 @@ * Emitted by lower elements to inform the main builder that something has changed

})
// }}}
// Component: uiQueryBuilderGroup {{{
/**

@@ -265,8 +316,14 @@ * Query builder element that holds a collection of queries - an array

},
template: '\n\t\t<div ng-repeat="row in $ctrl.qbGroup">\n\t\t\t<ui-query-builder-row\n\t\t\t\tqb-item="row"\n\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t></ui-query-builder-row>\n\t\t</div>\n\t',
template: '\n\t\t<div ng-repeat="row in $ctrl.qbGroup | filter:$ctrl.qbGroupFilter" meta-key="{{row.path}}">\n\t\t\t<ui-query-builder-row\n\t\t\t\tqb-item="row"\n\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t></ui-query-builder-row>\n\t\t</div>\n\t\t<div class="query-row">\n\t\t\t<div class="query-container">\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<button ng-click="$ctrl.add()" type="button" class="btn-add"></button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t',
controller: ['$scope', 'QueryBuilder', function controller($scope, QueryBuilder) {
var $ctrl = this;
$ctrl.qbGroupFilter = function (item) {
return item.type != 'hidden';
};
}]
})
// }}}
// Component: uiQueryBuilderRow {{{
/**

@@ -291,5 +348,7 @@ * Individual line-item for a query row

}],
template: '\n\t\t<div ng-switch="$ctrl.qbItem.type">\n\t\t\t<!-- $and / $or condition {{{ -->\n\t\t\t<div ng-switch-when="binaryGroup" class="query-row">\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-1 btn-block">\n\t\t\t\t\t\t{{$ctrl.qbItem.title}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div ng-repeat="conditional in $ctrl.qbItem.children" class="query-container clearfix">\n\t\t\t\t\t<ui-query-builder-group\n\t\t\t\t\t\tqb-group="conditional"\n\t\t\t\t\t\tqb-spec="$ctrl.spec"\n\t\t\t\t\t></ui-query-builder-group>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<!-- }}} -->\n\t\t\t<!-- String {{{ -->\n\t\t\t<div ng-switch-when="string" class="query-row">\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-1 btn-block">\n\t\t\t\t\t\t{{$ctrl.qbItem.title}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-3 btn-block">\n\t\t\t\t\t\t<input ng-value="$ctrl.qbItem.value" type="text" class="form-control"/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<!-- }}} -->\n\t\t\t<!-- Enum {{{ -->\n\t\t\t<div ng-switch-when="enum" class="query-row">\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-1 btn-block">\n\t\t\t\t\t\t{{$ctrl.qbItem.title}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t\t<ui-query-builder-block-menu-multiple\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="3"\n\t\t\t\t\tselected="$ctrl.qbItem.value"\n\t\t\t\t\toptions="$ctrl.qbItem.enum"\n\t\t\t\t></ui-query-builder-block-menu-multiple>\n\t\t\t</div>\n\t\t\t<!-- }}} -->\n\t\t\t<!-- Date {{{ -->\n\t\t\t<div ng-switch-when="date" class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-path\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\tselected="$ctrl.qbItem.path"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-path>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-3 btn-block">\n\t\t\t\t\t\t<input ng-value="$ctrl.qbItem.value" type="date" class="form-control"/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<!-- }}} -->\n\t\t\t<!-- Number {{{ -->\n\t\t\t<div ng-switch-when="number" class="query-row">\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-1 btn-block">\n\t\t\t\t\t\t{{$ctrl.qbItem.title}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-3 btn-block">\n\t\t\t\t\t\t<input ng-value="$ctrl.qbItem.value" type="number" class="form-control"/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<!-- }}} -->\n\t\t\t<!-- Exists {{{ -->\n\t\t\t<div ng-switch-when="exists" class="query-row">\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-1 btn-block">\n\t\t\t\t\t\t{{$ctrl.qbItem.title}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t</div>\n\t\t\t<!-- }}} -->\n\t\t\t<!-- Search {{{ -->\n\t\t\t<div ng-switch-when="search" class="query-row">\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-1 btn-block">\n\t\t\t\t\t\t{{$ctrl.qbItem.title}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-2 btn-block">\n\t\t\t\t\t\t<input ng-value="$ctrl.qbItem.value" ng-keyup="$ctrl.setChanged()" type="text" class="form-control"/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<!-- }}} -->\n\t\t\t<!-- Unknown {{{ -->\n\t\t\t<div ng-switch-default class="query-row">\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-warning btn-block">\n\t\t\t\t\t\t{{$ctrl.qbItem.title}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-warning btn-block">\n\t\t\t\t\t\tUnknown handler: {{$ctrl.qbItem.type}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<!-- }}} -->\n\t\t\t<!-- Add button {{{\n\t\t\t<div class="query-row">\n\t\t\t\t<div class="query-block btn-group">\n\t\t\t\t\t<a ng-click="$ctrl.add()" class="btn btn-add"></a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t}}} -->\n\t\t</div>\n\t'
template: '\n\t\t<div ng-switch="$ctrl.qbItem.type">\n\t\t\t<!-- $and / $or condition {{{ -->\n\t\t\t<div ng-switch-when="binaryGroup" class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-1 btn-block">\n\t\t\t\t\t\t{{$ctrl.qbItem.title}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div ng-repeat="conditional in $ctrl.qbItem.children" class="query-container clearfix">\n\t\t\t\t\t<ui-query-builder-group\n\t\t\t\t\t\tqb-group="conditional"\n\t\t\t\t\t\tqb-spec="$ctrl.spec"\n\t\t\t\t\t></ui-query-builder-group>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<!-- }}} -->\n\t\t\t<!-- String {{{ -->\n\t\t\t<div ng-switch-when="string" class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-path\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\tselected="$ctrl.qbItem.path"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-path>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-3 btn-block">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tng-model="$ctrl.qbItem.value"\n\t\t\t\t\t\t\tng-change="$ctrl.setChanged()"\n\t\t\t\t\t\t\ttype="text"\n\t\t\t\t\t\t\tclass="form-control"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<!-- }}} -->\n\t\t\t<!-- Enum {{{ -->\n\t\t\t<div ng-switch-when="enum" class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-path\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\tselected="$ctrl.qbItem.path"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-path>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t\t<ui-query-builder-block-menu-multiple\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="3"\n\t\t\t\t\tselected="$ctrl.qbItem.value"\n\t\t\t\t\toptions="$ctrl.qbItem.enum"\n\t\t\t\t></ui-query-builder-block-menu-multiple>\n\t\t\t</div>\n\t\t\t<!-- }}} -->\n\t\t\t<!-- Date {{{ -->\n\t\t\t<div ng-switch-when="date" class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-path\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\tselected="$ctrl.qbItem.path"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-path>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-3 btn-block">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tng-model="$ctrl.qbItem.value"\n\t\t\t\t\t\t\tng-change="$ctrl.setChanged()"\n\t\t\t\t\t\t\ttype="date"\n\t\t\t\t\t\t\tclass="form-control"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<!-- }}} -->\n\t\t\t<!-- Number {{{ -->\n\t\t\t<div ng-switch-when="number" class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-path\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\tselected="$ctrl.qbItem.path"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-path>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-3 btn-block">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tng-value="$ctrl.qbItem.value"\n\t\t\t\t\t\t\tng-changed="$ctrl.setChanged()"\n\t\t\t\t\t\t\ttype="number"\n\t\t\t\t\t\t\tclass="form-control"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<!-- }}} -->\n\t\t\t<!-- Exists {{{ -->\n\t\t\t<div ng-switch-when="exists" class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-path\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\tselected="$ctrl.qbItem.path"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-path>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t</div>\n\t\t\t<!-- }}} -->\n\t\t\t<!-- Search {{{ -->\n\t\t\t<div ng-switch-when="search" class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-path\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\tselected="$ctrl.qbItem.path"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-path>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-2 btn-block">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tng-model="$ctrl.qbItem.value"\n\t\t\t\t\t\t\tng-change="$ctrl.setChanged()"\n\t\t\t\t\t\t\ttype="text"\n\t\t\t\t\t\t\tclass="form-control"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<!-- }}} -->\n\t\t\t<!-- keyVal (Only title + value) {{{ -->\n\t\t\t<div ng-switch-when="keyVal" class="query-row">\n\t\t\t\t<a ng-if="$ctrl.qbItem.canDelete === undefined || $ctrl.qbItem.canDelete" ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-block\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\ttitle="$ctrl.qbItem.title"\n\t\t\t\t></ui-query-builder-block>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-2 btn-block">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tng-model="$ctrl.qbItem.value"\n\t\t\t\t\t\t\tng-change="$ctrl.setChanged()"\n\t\t\t\t\t\t\ttype="text"\n\t\t\t\t\t\t\tclass="form-control"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<!-- }}} -->\n\t\t\t<!-- Unknown {{{ -->\n\t\t\t<div ng-switch-default class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-path\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\tselected="$ctrl.qbItem.path"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-path>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-warning btn-block">\n\t\t\t\t\t\tUnknown handler: {{$ctrl.qbItem.type}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<!-- }}} -->\n\t\t</div>\n\t'
})
// }}}
// Component: uiQueryBuilderPath {{{
/**

@@ -331,4 +390,24 @@ * Component for drawing a path selection component

})
// }}}
// Component: uiQueryBuilderBlock {{{
/**
* Component for drawing a Block with no-interactivity
* @param {number} level The level of button we are drawing
* @param {string} title The title of the block to display
*/
.component('uiQueryBuilderBlock', {
bindings: {
level: '<',
title: '<'
},
controller: ['$scope', function controller($scope) {
var $ctrl = this;
}],
template: '\n\t\t<a class="btn btn-block btn-{{$ctrl.level}}">\n\t\t\t{{$ctrl.title}}\n\t\t</a>\n\t'
})
// }}}
// Component: uiQueryBuilderBlockMenu {{{
/**
* Component for drawing a Block as a dropdown list of options

@@ -360,5 +439,7 @@ * @param {number} level The level of button we are drawing

}],
template: '\n\t\t<a class="btn btn-block btn-{{$ctrl.level}} dropdown-toggle" data-toggle="dropdown"> {{$ctrl.selectedOption.title}} <i class="fa fa-caret-down"></i></a>\n\t\t<ul class="dropdown-menu pull-right">\n\t\t\t<li ng-repeat="option in $ctrl.options track by option.id"><a ng-click="$ctrl.setSelected(option)">{{option.title}}</a></li>\n\t\t</ul>\n\t'
template: '\n\t\t<a class="btn btn-block btn-{{$ctrl.level}} dropdown-toggle" data-toggle="dropdown">\n\t\t\t{{$ctrl.selectedOption.title}}\n\t\t\t<i class="fa fa-caret-down"></i>\n\t\t</a>\n\t\t<ul class="dropdown-menu pull-right">\n\t\t\t<li ng-repeat="option in $ctrl.options track by option.id"><a ng-click="$ctrl.setSelected(option)">{{option.title}}</a></li>\n\t\t</ul>\n\t'
})
// }}}
// Component: uiQueryBuilderBlockMenuMultiple {{{
/**

@@ -406,479 +487,3 @@ * Component for drawing a Block as a dropdown list of multiple-select options

template: '\n\t\t<a class="btn btn-block btn-{{$ctrl.level}} dropdown-toggle" data-toggle="dropdown">\n\t\t\t<span ng-repeat="item in $ctrl.selectedOptions track by item.id" class="pill">\n\t\t\t\t{{item.title}}\n\t\t\t</span>\n\t\t\t<i class="fa fa-caret-down"></i></a>\n\t\t</a>\n\t\t<ul class="dropdown-menu pull-right">\n\t\t\t<li ng-repeat="option in $ctrl.options track by option.id">\n\t\t\t\t<a ng-click="$ctrl.toggle(option)">\n\t\t\t\t\t<i class="fa fa-fw" ng-class="option.selected ? \'fa-check-square-o\' : \'fa-square-o\'"></i>\n\t\t\t\t\t{{option.title}}\n\t\t\t\t</a>\n\t\t\t</li>\n\t\t</ul>\n\t'
})
// Main widget {{{
.component('uiQueryBuilderOLD', {
bindings: {
query: '=',
spec: '<'
},
template: '\n\t\t<div class="ui-query-builder clearfix">\n\t\t\t<div class="query-container">\n\t\t\t\t<!-- Meta field: sort {{{ -->\n\t\t\t\t<div class="query-row">\n\t\t\t\t\t<!-- Path component {{{ -->\n\t\t\t\t\t<div class="query-block">\n\t\t\t\t\t\t<div class="btn-group btn-block">\n\t\t\t\t\t\t\t<a class="btn btn-1 btn-block">\n\t\t\t\t\t\t\t\tSort by\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<!-- }}} -->\n\t\t\t\t\t<!-- Query operand component {{{ -->\n\t\t\t\t\t<div class="query-block btn-group">\n\t\t\t\t\t\t<div class="btn btn-block btn-2">\n\t\t\t\t\t\t\t<input ng-model="$ctrl.query.sort" type="text" class="form-control"/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<!-- }}} -->\n\t\t\t\t</div>\n\t\t\t\t<!-- }}} -->\n\t\t\t\t<!-- Meta field: limit {{{ -->\n\t\t\t\t<div class="query-row">\n\t\t\t\t\t<!-- Path component {{{ -->\n\t\t\t\t\t<div class="query-block">\n\t\t\t\t\t\t<div class="btn-group btn-block">\n\t\t\t\t\t\t\t<a class="btn btn-1 btn-block">\n\t\t\t\t\t\t\t\tLimited to\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<!-- }}} -->\n\t\t\t\t\t<!-- Query operand component {{{ -->\n\t\t\t\t\t<div class="query-block btn-group">\n\t\t\t\t\t\t<div class="btn btn-block btn-2">\n\t\t\t\t\t\t\t<input ng-model="$ctrl.query.limit" type="number" class="form-control"/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class="query-block btn-group">\n\t\t\t\t\t\t<div class="btn btn-block btn-1">\n\t\t\t\t\t\t\tSkipping\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class="query-block btn-group">\n\t\t\t\t\t\t<div class="btn btn-block btn-2">\n\t\t\t\t\t\t\t<input ng-model="$ctrl.query.skip" type="number" class="form-control"/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<!-- }}} -->\n\t\t\t\t</div>\n\t\t\t\t<!-- }}} -->\n\t\t\t\t<div class="query-row">\n\t\t\t\t\t<div class="query-block">\n\t\t\t\t\t\t<!-- FIXME: Need branch title -->\n\t\t\t\t\t</div>\n\t\t\t\t\t<ui-query-builder-branch\n\t\t\t\t\t\tclass="query-container"\n\t\t\t\t\t\tbranch="$ctrl.query"\n\t\t\t\t\t\tspec="$ctrl.spec"\n\t\t\t\t\t></ui-query-builder-branch>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t',
controller: ['$scope', function controller($scope) {
var $ctrl = this;
// Clean up incomming spec {{{
$scope.$watch('$ctrl.spec', function () {
_.forEach($ctrl.spec, function (v, k) {
if (!v.title) v.title = _.startCase(k); // Create a title from the key if its omitted
if (v.enum && _.isArray(v.enum)) {
// Ensure enums are aways collections
v.enum = _(v.enum).map(function (e) {
return _.isString(e) ? { id: e, title: _.startCase(e) } : e;
}).sortBy('title').value();
}
});
});
// }}}
}]
})
// }}}
// Branch widget {{{
/**
* Display a branch
* This is a seperate component in order to allow recursion
* @param {Object} branch The branch to display (passed from the main widget or recursively from this one)
* @param {Object} spec The specification passed from the parent
*/
.component('uiQueryBuilderBranch', {
bindings: {
branch: '=',
spec: '<'
},
template: '\n\t\t<!-- AND blocks {{{ -->\n\t\t<div ng-repeat="leaf in $ctrl.properties | filter:{isMeta:true,id:\'$and\'} track by leaf.id" ng-switch="leaf.spec.type" ng-repeat-emit="uiQueryQueryRepaint" class="query-row">\n\t\t\t<div ng-repeat="choiceLeaf in leaf.value">\n\t\t\t\t<ui-query-builder-branch\n\t\t\t\t\tbranch="choiceLeaf"\n\t\t\t\t\tspec="$ctrl.spec"\n\t\t\t\t></ui-query-builder-branch>\n\t\t\t</div>\n\t\t</div>\n\t\t<!-- }}} -->\n\t\t<!-- OR blocks {{{ -->\n\t\t<div ng-repeat="leaf in $ctrl.properties | filter:{isMeta:true,id:\'$or\'} track by leaf.id" ng-switch="leaf.spec.type" ng-repeat-emit="uiQueryQueryRepaint" class="query-row">\n\t\t\t<div ng-repeat="choiceLeaf in leaf.value">\n\t\t\t\t<ui-query-builder-branch\n\t\t\t\t\tbranch="choiceLeaf"\n\t\t\t\t\tspec="$ctrl.spec"\n\t\t\t\t></ui-query-builder-branch>\n\t\t\t</div>\n\t\t</div>\n\t\t<!-- }}} -->\n\t\t<!-- Main fields {{{ -->\n\t\t<div ng-repeat="leaf in $ctrl.properties | filter:{isMeta:false} track by leaf.id" ng-switch="leaf.spec.type" ng-repeat-emit="uiQueryQueryRepaint" class="query-row">\n\t\t\t<!-- Path component {{{ -->\n\t\t\t<button ng-click="$ctrl.remove(leaf.id); $event.stopPropagation()" class="btn btn-trash btn-danger" type="button"></button>\n\t\t\t<div class="query-block">\n\t\t\t\t<div class="btn-group btn-block" ng-class="{new: !leaf.id}">\n\t\t\t\t\t<a class="btn btn-1 btn-block dropdown-toggle" data-toggle="dropdown">\n\t\t\t\t\t\t{{$ctrl.spec[leaf.id].title || \'Select...\'}}\n\t\t\t\t\t\t<i class="fa fa-caret-down"></i>\n\t\t\t\t\t</a>\n\t\t\t\t\t<ul class="dropdown-menu pull-right">\n\t\t\t\t\t\t<li ng-repeat="(key, val) in $ctrl.spec track by key" ng-class="key == leaf.id && \'active\'">\n\t\t\t\t\t\t\t<a ng-click="$ctrl.setField(leaf, key)">\n\t\t\t\t\t\t\t\t{{$ctrl.spec[key].title}}\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<!-- }}} -->\n\t\t\t<!-- Query type component {{{ -->\n\t\t\t<div ng-show="leaf.valueOperand" class="query-block">\n\t\t\t\t<div class="btn-group btn-block">\n\t\t\t\t\t<a class="btn btn-2 btn-block dropdown-toggle" data-toggle="dropdown">\n\t\t\t\t\t\t{{($ctrl.operandsByID[leaf.valueOperand][leaf.spec.type] || $ctrl.operandsByID[leaf.valueOperand].base).title}}\n\t\t\t\t\t\t<i class="fa fa-caret-down"></i>\n\t\t\t\t\t</a>\n\t\t\t\t\t<ul class="dropdown-menu pull-right">\n\t\t\t\t\t\t<li><a ng-click="$ctrl.setWrapper(leaf, \'$eq\')">Is</a></li>\n\t\t\t\t\t\t<li><a ng-click="$ctrl.setWrapper(leaf, \'$ne\')">Is not</a></li>\n\t\t\t\t\t\t<li><a ng-click="$ctrl.setWrapper(leaf, \'$in\')">One of</a></li>\n\t\t\t\t\t\t<li><a ng-click="$ctrl.setWrapper(leaf, \'$nin\')">Not one of</a></li>\n\t\t\t\t\t\t<li ng-if="leaf.spec.type == \'number\'"><a ng-click="$ctrl.setWrapper(leaf, \'$gt\')">Above</a></li>\n\t\t\t\t\t\t<li ng-if="leaf.spec.type == \'number\'"><a ng-click="$ctrl.setWrapper(leaf, \'$lt\')">Below</a></li>\n\t\t\t\t\t\t<li ng-if="leaf.spec.type == \'date\'"><a ng-click="$ctrl.setWrapper(leaf, \'$gt\')">Is after</a></li>\n\t\t\t\t\t\t<li ng-if="leaf.spec.type == \'date\'"><a ng-click="$ctrl.setWrapper(leaf, \'$gte\')">Is at least</a></li>\n\t\t\t\t\t\t<li ng-if="leaf.spec.type == \'date\'"><a ng-click="$ctrl.setWrapper(leaf, \'$lt\')">Is before</a></li>\n\t\t\t\t\t\t<li ng-if="leaf.spec.type == \'date\'"><a ng-click="$ctrl.setWrapper(leaf, \'$lte\')">Is at most</a></li>\n\t\t\t\t\t\t<li><a ng-click="$ctrl.setWrapper(leaf, \'$exists\')">Has a value</a></li>\n\t\t\t\t\t</ul>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<!-- }}} -->\n\t\t\t<!-- Query operand component {{{ -->\n\t\t\t<div ng-show="leaf.valueOperand" class="query-block btn-group" ng-switch="(operandConfig = $ctrl.operandsByID[leaf.valueOperand][leaf.spec.type] || $ctrl.operandsByID[leaf.valueOperand].base).type">\n\t\t\t\t<div ng-switch-when="string" class="btn btn-block btn-3">\n\t\t\t\t\t<input ng-model="leaf.valueEdit" ng-change="$ctrl.setValue(leaf)" type="text" class="form-control"/>\n\t\t\t\t</div>\n\t\t\t\t<div ng-switch-when="array" class="btn btn-block btn-3 btn-group">\n\t\t\t\t\t<div class="btn-fill text-left dropdown-toggle" data-toggle="dropdown">\n\t\t\t\t\t\t<span class="pill" ng-repeat="item in $ctrl.spec[leaf.id].enum | uiQueryBuilderFilterSelected:leaf track by item.id">\n\t\t\t\t\t\t\t{{item.title}}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span ng-if="!leaf.valueEdit.length">...</span>\n\t\t\t\t\t\t<i class="fa fa-caret-down"></i>\n\t\t\t\t\t</div>\n\t\t\t\t\t<ul class="dropdown-menu pull-right">\n\t\t\t\t\t\t<li ng-repeat="item in $ctrl.spec[leaf.id].enum | uiQueryBuilderFilterSelected:leaf:false track by item.id">\n\t\t\t\t\t\t\t<a ng-click="$ctrl.setValueIncluded(leaf, item.id, false)">\n\t\t\t\t\t\t\t\t<i class="fa fa-fw fa-check-square text-primary"></i>\n\t\t\t\t\t\t\t\t{{item.title}}\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t\t<li ng-repeat="item in $ctrl.spec[leaf.id].enum | uiQueryBuilderFilterSelected:leaf:true track by item.id">\n\t\t\t\t\t\t\t<a ng-click="$ctrl.setValueIncluded(leaf, item.id, true)">\n\t\t\t\t\t\t\t\t<i class="fa fa-fw fa-square-o text-primary"></i>\n\t\t\t\t\t\t\t\t{{item.title}}\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</div>\n\t\t\t\t<div ng-switch-when="boolean" class="btn btn-block btn-3" ng-click="$ctrl.setValue(leaf, !leaf.valueEdit)">\n\t\t\t\t\t<i class="fa fa-fw" ng-class="leaf.valueEdit ? \'fa-check-square-o\' : \'fa-square-o\'"></i>\n\t\t\t\t\t{{leaf.valueEdit ? operandConfig.textTrue : operandConfig.textFalse}}\n\t\t\t\t</div>\n\t\t\t\t<div ng-switch-when="date" class="btn btn-block btn-3">\n\t\t\t\t\t<input ng-model="leaf.valueEdit" ng-change="$ctrl.setValue(leaf)" type="date" class="form-control"/>\n\t\t\t\t</div>\n\t\t\t\t<div ng-switch-default class="btn btn-block btn-3">\n\t\t\t\t\tUnknown operand: <code>{{leaf.valueOperand}}</code>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<!-- }}} -->\n\t\t</div>\n\t\t<!-- Add button {{{ -->\n\t\t<button ng-click="$ctrl.add()" class="btn btn-add btn-success" type="button"></button>\n\t\t<!-- }}} -->\n\t',
controller: ['$element', '$scope', function controller($element, $scope) {
var $ctrl = this;
// Operands {{{
/**
* An array of all supported wrapping operands
* These usually correspond to the 'dollar function' wrapper in Mongo. e.g. $eq =~ equals
* Each item has an `id` and a `base` setup with an optional override for specific types
* @var array
*/
$ctrl.operands = [
/*
{
id: String, // The operand matching leaf.valueOperand
setter: Function(v), // Function used to convert the value into something compatible with the operand
base: {
title: String, // The human title of the operand
type: String, // How to display the operand value to the user (generally matches to standard scalar values)
},
string: { // Specific override for the string type (optional)
...
},
number: { // Specific override for the number type (optional)
...
},
*/
{
id: '$eq',
setter: function setter(v) {
return { $eq: v };
},
export: function _export(leaf) {
return leaf.valueEdit;
},
base: {
title: 'Is',
type: 'string'
},
boolean: {
title: 'Is',
type: 'boolean',
textTrue: 'Enabled',
textFalse: 'Disabled'
},
date: {
title: 'Is exactly',
type: 'date'
}
}, {
id: '$ne',
setter: function setter(v) {
return { $ne: v };
},
export: function _export(leaf) {
return { $ne: leaf.valueEdit };
},
base: {
title: 'Is not',
type: 'string'
},
boolean: {
title: 'Is not',
type: 'boolean',
textTrue: 'Enabled',
textFalse: 'Disabled'
},
date: {
title: 'Is not exactly',
type: 'date'
}
}, {
id: '$in',
setter: function setter(v) {
return { $in: _.isArray(v) ? v.split(/\s*,\s*/) : [v] };
},
export: function _export(leaf) {
return { $in: leaf.value.$in };
},
base: {
title: 'One of',
type: 'array'
}
}, {
id: '$nin',
setter: function setter(v) {
return { $nin: _.isArray(v) ? v.split(/\s*,\s*/) : [v] };
},
export: function _export(leaf) {
return { $nin: leaf.value.$nin };
},
base: {
title: 'Not one of',
type: 'array'
}
}, {
id: '$gt',
setter: function setter(v) {
return { $gt: v };
},
export: function _export(leaf) {
return { $gt: leaf.value.$gt };
},
base: {
title: 'Above',
type: 'number'
},
date: {
title: 'Is after',
type: 'date'
}
}, {
id: '$gte',
setter: function setter(v) {
return { $gte: v };
},
export: function _export(leaf) {
return { $gte: leaf.value.$gte };
},
base: {
title: 'Above or equals',
type: 'number'
},
date: {
title: 'Is at least',
type: 'date'
}
}, {
id: '$lt',
setter: function setter(v) {
return { $lt: v };
},
export: function _export(leaf) {
return { $lt: leaf.value.$lt };
},
base: {
title: 'Below',
type: 'number'
},
date: {
title: 'Is before',
type: 'date'
}
}, {
id: '$lte',
setter: function setter(v) {
return { $lt: v };
},
export: function _export(leaf) {
return { $lte: leaf.value.$lte };
},
base: {
title: 'Below or equals',
type: 'number'
},
date: {
title: 'Is at most',
type: 'date'
}
}, {
id: '$exists',
setter: function setter(v) {
return { $exists: !!v };
},
export: function _export(leaf) {
return { $exists: leaf.value.$exists };
},
base: {
title: 'Has a value',
type: 'boolean',
textTrue: 'Has a value',
textFalse: 'Has a value' // This isn't technically right but its right next to a disabled checkbox so it makes sense in context
}
}, {
id: '$regexp',
setter: function setter(v) {
return { $regexp: v };
},
export: function _export(leaf) {
return { $regexp: leaf.value.$regexp };
},
base: {
title: 'Matches',
type: 'string'
}
}];
$ctrl.operandsByID = _.mapKeys($ctrl.operands, 'id');
// }}}
// $ctrl.getSpec() {{{
$ctrl.getSpec = function (key, val, path) {
// Spec present {{{
if ($ctrl.spec[path]) {
return $ctrl.spec[path];
// }}}
// Meta parent types {{{
} else if (key == '$and' || key == '$or') {
return _defineProperty({ type: 'group' }, 'type', key);
// }}}
// Guessing {{{
} else if (_.isString(val)) {
return { type: 'string' };
} else if (_.isNumber(val)) {
return { type: 'number' };
// }}}
// Fallback {{{
} else {
return { type: 'string' };
}
// }}}
};
// }}}
// $ctrl.translateBranch() {{{
$ctrl.translateBranch = function (branch) {
var pathSegments = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
return _($ctrl.branch).map(function (v, k) {
return {
id: k,
value: v,
valueEdit: $ctrl.getFlatValue(v),
valueOperand: _.isObject(v) ? _(v).keys().first() : '$eq',
isMeta: ('' + k).startsWith('$') || ['sort', 'skip', 'limit'].includes(k),
spec: $ctrl.getSpec(k, v, k),
path: pathSegments.concat([k])
};
}).sortBy(function (p) {
return p.isMeta ? 'Z' + p.id : 'A' + p.id;
}) // Force meta items to the end
.value();
};
// }}}
// $ctrl.exportBranch() {{{
/**
* Export the local $ctrl.properties branch back into the upstream branch
*/
$ctrl.exportBranch = function () {
$ctrl.branch = _($ctrl.properties).mapKeys(function (b) {
return b.id;
}).mapValues(function (b) {
return $ctrl.operandsByID[b.valueOperand].export(b);
}).value();
};
// }}}
// Convert branch -> properties {{{
// We have to do this to sort appropriately and allow iteration over dollar prefixed keys
$ctrl.properties;
$scope.$watchGroup(['$ctrl.branch', '$ctrl.spec'], function () {
if (!$ctrl.branch || !$ctrl.spec) return; // Not yet ready
$ctrl.properties = $ctrl.translateBranch($ctrl.branch);
});
// }}}
// Branch interaction {{{
$ctrl.setField = function (leaf, field) {
leaf.id = field;
leaf.path = [field];
leaf.value = undefined;
leaf.valueEdit = undefined;
leaf.valueOperand = '$eq';
leaf.spec = $ctrl.spec[field];
$ctrl.setValue(leaf);
};
$ctrl.setWrapper = function (leaf, type) {
if (leaf.valueOperand == '$eq' && type == '$ne') {
// Negate
leaf.valueOperand = '$ne';
leaf.valueEdit = $ctrl.getFlatValue(leaf.value);
leaf.value = { $ne: leaf.valueEdit };
} else if (leaf.valueOperand == '$ne' && type == '$eq') {
leaf.valueOperand = '$eq';
leaf.valueEdit = $ctrl.getFlatValue(leaf.value);
leaf.value = { $eq: leaf.valueEdit };
} else if (leaf.valueOperand == '$in' && type == '$eq') {
// Flatten array into scalar
leaf.valueOperand = '$eq';
leaf.value = leaf.valueEdit = $ctrl.getFlatValue(leaf.value);
} else if ((leaf.valueOperand == '$eq' || leaf.valueOperand === undefined) && type == '$in') {
// Roll scalar into array
leaf.valueOperand = '$in';
leaf.valueEdit = $ctrl.getFlatValue(leaf.value);
leaf.value = { $in: [leaf.valueEdit] };
} else if (type == '$exists') {
// Convert anything to exists - force it to be a boolean
leaf.valueOperand = '$exists';
leaf.valueEdit = true;
leaf.value = { $exists: leaf.valueEdit };
} else {
// Unknown swapping - convert to an object with one key
console.log('UNHANDLED TYPE CONVERT:', leaf.type, '=>', type);
var newValue = $ctrl.getFlatValue(leaf.value);
leaf.valueOperand = type;
leaf.valueEdit = newValue;
leaf.value = _defineProperty({}, leaf.valueOperand, leaf.valueEdit);
}
// Set the upstream model value
$ctrl.exportBranch();
};
/**
* Set the value of a leaf
* @param {Object} leaf The leaf to change the value of
* @param {*} [value] Optional value to set, if omitted the bound leaf.valueEdit will be used
*/
$ctrl.setValue = function (leaf, value) {
var newValue = _.isUndefined(value) ? leaf.valueEdit : value;
// Run via operand setter
leaf.value = $ctrl.operandsByID[leaf.valueOperand].setter(newValue);
leaf.valueEdit = $ctrl.getFlatValue(leaf.value);
// Set the upstream model value
$ctrl.exportBranch();
};
// }}}
// Utility functions {{{
/**
* Set whether the specified value is included in the leaf array of values
* @param {Object} leaf The leaf to change the value of
* @param {string} value The value to toggle to inclusion of
* @param {boolean} included Whether the value is included
*/
$ctrl.setValueIncluded = function (leaf, value, included) {
var wrapperKey = _(leaf.value).keys().first();
if (!wrapperKey) throw new Error('Tried to set array inclusion on non wrapped key: ' + leaf.value);
var isIncluded = leaf.value[wrapperKey].includes(value);
if (included && !isIncluded) {
leaf.value[wrapperKey].push(value);
} else if (!included && isIncluded) {
leaf.value[wrapperKey] = leaf.value[wrapperKey].filter(function (i) {
return i != value;
});
}
leaf.value[wrapperKey].sort();
leaf.valueEdit = _.isObject(leaf.value) && _.size(leaf.value) ? _(leaf.value).map().first() : leaf.value;
};
/**
* Return the 'flat' value of a Mongo expression
* This will always return the closest thing we have to a scalar primative
* @param {Object|string} input The input expression to flatten
* @returns {string|number} The nearest thing we can evaluate to a primative (or an empty string)
*
* @example
* $ctrl.getFlatValue('foo') //= 'foo'
* @example
* $ctrl.getFlatValue({$eq: 'bar'}) //= 'bar'
* @example
* $ctrl.getFlatValue({$in: ['quz', 'qux']}) //= 'quz'
*/
$ctrl.getFlatValue = function (input) {
if (_.isString(input) || _.isNumber(input) || _.isBoolean(input) || _.isDate(input)) {
// Already a primative
return input;
} else if (_.isObject(input) && _.size(input) == 1) {
// Unwrap object value from object
return _(input).values().first();
} else if (_.isObject(input) && input.$regexp) {
// RegExps - we can savely ignore the options object and guess at the expression
return '/' + _.trim(input.$regexp, '/') + '/' + input.options;
} else {
// No idea how to convert - just return an empty string
console.warn('Given up trying to flatten input value', input);
return input;
}
};
// }}}
// Branch CRUD {{{
$ctrl.add = function () {
if ($ctrl.properties.some(function (p) {
return !p.id;
})) return; // Check there are no new items currently in the process of being added
$ctrl.properties.push({ isMeta: false });
// Wait for the page to redraw then force the dropdown to open
// Yes I know this is a weird work around but we have to wait for the DOM to settle for some reason before we can add the `open` class - MC 2017-10-03
var eventUnbind = $scope.$on('uiQueryQueryRepaint', function () {
$element.find('.query-block > .new').addClass('open');
});
};
$ctrl.remove = function (id) {
$ctrl.properties = $ctrl.properties.filter(function (p) {
return p.id != id;
});
$ctrl.exportBranch();
};
// }}}
}]
})
/**
* Simple query which takes an array of possible selections and returns only those that are present within the leaf.valueEdit array
* This is used to display selected items in an array
* @param {array} items The array to filter
* @param {Object} leaf The leaf node to filter against
* @param {boolean} [invert=false] Whether to invert the result
* @returns {array} The filtered items array
*/
.filter('uiQueryBuilderFilterSelected', function () {
return function (items, leaf, invert) {
if (!items) return;
return items.filter(function (i) {
var doesInclude = leaf.valueEdit.includes(i.id);
return invert ? !doesInclude : doesInclude;
});
};
})
/**
* Fire a $scope.$emit() with the given message when an ng-repeat render finishes
* @param {string} message The message to emit to this element scope upwards
* @example
* <div ng-repeat="widget in widgets" ng-repeat-emit="finished"></div>
*/
.directive('ngRepeatEmit', ['$rootScope', '$timeout', function ($rootScope, $timeout) {
return {
restrict: 'A',
link: function link(scope, elem, attr) {
if (scope.$last === true) $timeout(function () {
return scope.$emit(attr.ngRepeatEmit);
});
}
};
}]);
});
// }}}

2

dist/angular-ui-query-builder-core.min.js

@@ -1,1 +0,1 @@

"use strict";function _defineProperty(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};angular.module("angular-ui-query-builder",[]).service("QueryBuilder",function(){var t=this;t.cleanSpec=function(t){return _(t).mapValues(function(t,e){return{type:t.type,enum:_(t.enum).map(function(t){return _.isString(t)?{id:t,title:_.startCase(t)}:t}).sortBy("title").value()}}).value()},t.queryToArray=function(e,n){var i=[{id:"$eq",title:"Equals"},{id:"$neq",title:"Doesnt equal"},{id:"$lt",title:"Is less than"},{id:"$lte",title:"Is equal to or less than"},{id:"$gt",title:"Is greater than"},{id:"$gte",title:"Is equal or greater than"},{id:"$in",title:"Is one of"},{id:"$nin",title:"Is not one of"},{id:"$exists",title:"Has a value"},{id:"$nexists",title:"Does not have a value"}];return _(e).pickBy(function(t,e){var i=n[e]||"$and"==e||"$or"==e;return i||console.warn("query-builder","Incomming query path",e,"Does not map to anything in spec",n),!!i}).map(function(e,r){var l=n[r],a=_.isObject(e)&&_(e).keys().first(),c=_.isObject(e)?_(e).values().first():e;return"$or"==r&&e.every(function(t){return _.isObject(t)&&1==_.keys(t).length})&&e.map(function(t){return _.chain(t).first().values().first().keys().find(function(t){return"$regexp"==t}).value()}).length==e.length?{path:r,type:"search",title:"Search",value:_.chain(e).first().values().first().get("$regexp").value(),fields:_(e).map(function(t){return _.keys(t)}).flatten().value(),actions:i}:"$and"==r||"$or"==r?(_.isArray(e)||(console.warn("query-builder","Query path",r,"is a meta key",e,"but is not an array!","Given",void 0===e?"undefined":_typeof(e)),e=[]),{path:r,type:"binaryGroup",title:"$and"==r?"AND":"$or"==r?"OR":"UNKNOWN",condition:r.replace(/\$/,""),children:e.map(function(e){return t.queryToArray(e,n)}),actions:i}):"$exists"==a?{path:r,title:e.title||_.startCase(r),value:!!e,type:"exists",action:"$exists",actions:i}:"string"==l.type&&_.isArray(l.enum)?{path:r,title:e.title||_.startCase(r),type:"enum",action:e.$in?"$in":e.$nin?"$nin":l.enum.length?"$in":"$eq",enum:l.enum,value:e.$in?e.$in:e.$nin?e.$nin:l.enum.length&&!_.isArray(e)?[e]:e,actions:i}:{path:r,title:e.title||_.startCase(r),type:"string"==l.type?"string":"number"==l.type?"number":"date"==l.type?"date":"string",action:a,value:"date"==l.type?moment(c).format("YYYY-MM-DD"):c,actions:i}}).value()},t.arrayToQuery=function(t){return function(t){return _(t).mapKeys(function(t){return t.path}).mapValues(function(t){switch(t.type){case"string":case"number":case"date":return"$eq"==t.action?t.value:_defineProperty({},t.action,t.value);case"enum":return _defineProperty({},t.action,t.value);case"exists":return{$exists:"$exists"==t.action};case"search":return t.fields.map(function(e){return _defineProperty({},e,{$regexp:t.value,options:"i"})});default:console.warn("Unknown type to convert:",t.type)}}).value()}(t)}}).component("uiQueryBuilder",{bindings:{query:"=",spec:"<"},template:'\n\t\t<div class="ui-query-builder">\n\t\t\t<div class="query-container">\n\t\t\t\t<ui-query-builder-group\n\t\t\t\t\tqb-group="$ctrl.qbQuery"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-group>\n\t\t\t</div>\n\t\t</div>\n\t',controller:["$scope","$timeout","QueryBuilder",function(t,e,n){var i=this;i.qbSpec,i.qbQuery,t.$watchGroup(["$ctrl.query","$ctrl.spec"],function(){i.spec&&i.query&&(i.qbSpec=n.cleanSpec(i.spec),i.qbQuery=n.queryToArray(i.query,i.qbSpec))}),t.$on("queryBuilder.change",function(t,r){return e(function(){r&&(i.query=r,i.qbQuery=n.queryToArray(i.query,i.qbSpec)),i.query=n.arrayToQuery(i.qbQuery)})}),t.$on("queryBuilder.pathAction.drop",function(t,e){i.qbQuery=i.qbQuery.filter(function(t){return t.path!=e}),i.query=n.arrayToQuery(i.qbQuery)}),t.$on("queryBuilder.pathAction.swap",function(e,r,l){i.qbQuery=i.qbQuery.filter(function(t){return t.path!=r}),i.query=n.arrayToQuery(i.qbQuery),t.$emit("queryBuilder.pathAction.add",l)}),t.$on("queryBuilder.pathAction.add",function(t,e){i.query[e]="",i.qbQuery=n.queryToArray(i.query,i.qbSpec),i.query=n.arrayToQuery(i.qbQuery)})}]}).component("uiQueryBuilderGroup",{bindings:{qbGroup:"=",qbSpec:"<"},template:'\n\t\t<div ng-repeat="row in $ctrl.qbGroup">\n\t\t\t<ui-query-builder-row\n\t\t\t\tqb-item="row"\n\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t></ui-query-builder-row>\n\t\t</div>\n\t',controller:["$scope","QueryBuilder",function(t,e){}]}).component("uiQueryBuilderRow",{bindings:{qbItem:"=",qbSpec:"<"},controller:["$scope","QueryBuilder",function(t,e){var n=this;n.delete=function(e){return t.$emit("queryBuilder.pathAction.drop",e)},n.setChanged=function(){return t.$emit("queryBuilder.change")}}],template:'\n\t\t<div ng-switch="$ctrl.qbItem.type">\n\t\t\t\x3c!-- $and / $or condition {{{ --\x3e\n\t\t\t<div ng-switch-when="binaryGroup" class="query-row">\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-1 btn-block">\n\t\t\t\t\t\t{{$ctrl.qbItem.title}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div ng-repeat="conditional in $ctrl.qbItem.children" class="query-container clearfix">\n\t\t\t\t\t<ui-query-builder-group\n\t\t\t\t\t\tqb-group="conditional"\n\t\t\t\t\t\tqb-spec="$ctrl.spec"\n\t\t\t\t\t></ui-query-builder-group>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- String {{{ --\x3e\n\t\t\t<div ng-switch-when="string" class="query-row">\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-1 btn-block">\n\t\t\t\t\t\t{{$ctrl.qbItem.title}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-3 btn-block">\n\t\t\t\t\t\t<input ng-value="$ctrl.qbItem.value" type="text" class="form-control"/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Enum {{{ --\x3e\n\t\t\t<div ng-switch-when="enum" class="query-row">\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-1 btn-block">\n\t\t\t\t\t\t{{$ctrl.qbItem.title}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t\t<ui-query-builder-block-menu-multiple\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="3"\n\t\t\t\t\tselected="$ctrl.qbItem.value"\n\t\t\t\t\toptions="$ctrl.qbItem.enum"\n\t\t\t\t></ui-query-builder-block-menu-multiple>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Date {{{ --\x3e\n\t\t\t<div ng-switch-when="date" class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-path\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\tselected="$ctrl.qbItem.path"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-path>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-3 btn-block">\n\t\t\t\t\t\t<input ng-value="$ctrl.qbItem.value" type="date" class="form-control"/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Number {{{ --\x3e\n\t\t\t<div ng-switch-when="number" class="query-row">\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-1 btn-block">\n\t\t\t\t\t\t{{$ctrl.qbItem.title}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-3 btn-block">\n\t\t\t\t\t\t<input ng-value="$ctrl.qbItem.value" type="number" class="form-control"/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Exists {{{ --\x3e\n\t\t\t<div ng-switch-when="exists" class="query-row">\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-1 btn-block">\n\t\t\t\t\t\t{{$ctrl.qbItem.title}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Search {{{ --\x3e\n\t\t\t<div ng-switch-when="search" class="query-row">\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-1 btn-block">\n\t\t\t\t\t\t{{$ctrl.qbItem.title}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-2 btn-block">\n\t\t\t\t\t\t<input ng-value="$ctrl.qbItem.value" ng-keyup="$ctrl.setChanged()" type="text" class="form-control"/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Unknown {{{ --\x3e\n\t\t\t<div ng-switch-default class="query-row">\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-warning btn-block">\n\t\t\t\t\t\t{{$ctrl.qbItem.title}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-warning btn-block">\n\t\t\t\t\t\tUnknown handler: {{$ctrl.qbItem.type}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Add button {{{\n\t\t\t<div class="query-row">\n\t\t\t\t<div class="query-block btn-group">\n\t\t\t\t\t<a ng-click="$ctrl.add()" class="btn btn-add"></a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t}}} --\x3e\n\t\t</div>\n\t'}).component("uiQueryBuilderPath",{bindings:{level:"<",selected:"<",qbSpec:"<"},controller:["$scope",function(t){var e=this;e.setSelected=function(n){return t.$emit("queryBuilder.pathAction.swap",e.selected,n)},e.options,e.$onInit=function(){e.options=_.map(e.qbSpec,function(t,e){return Object.assign({},{path:e,title:_.startCase(e)},t)}),e.selectedOption=e.options.find(function(t){return t.path==e.selected})}}],template:'\n\t\t<a class="btn btn-block btn-{{$ctrl.level}} dropdown-toggle" data-toggle="dropdown">\n\t\t\t{{$ctrl.selectedOption.title}}\n\t\t\t<i class="fa fa-caret-down"></i>\n\t\t</a>\n\t\t<ul class="dropdown-menu pull-right">\n\t\t\t<li ng-repeat="path in $ctrl.options track by path.path"><a ng-click="$ctrl.setSelected(path.path)">{{path.title}}</a></li>\n\t\t</ul>\n\t'}).component("uiQueryBuilderBlockMenu",{bindings:{level:"<",options:"<",selected:"="},controller:["$scope",function(t){var e=this;e.setSelected=function(n){e.selected=n.id,t.$emit("queryBuilder.change")},e.selectedOption,t.$watchGroup(["$ctrl.options","$ctrl.selected"],function(){e.selectedOption=e.options.find(function(t){return t.id==e.selected})})}],template:'\n\t\t<a class="btn btn-block btn-{{$ctrl.level}} dropdown-toggle" data-toggle="dropdown"> {{$ctrl.selectedOption.title}} <i class="fa fa-caret-down"></i></a>\n\t\t<ul class="dropdown-menu pull-right">\n\t\t\t<li ng-repeat="option in $ctrl.options track by option.id"><a ng-click="$ctrl.setSelected(option)">{{option.title}}</a></li>\n\t\t</ul>\n\t'}).component("uiQueryBuilderBlockMenuMultiple",{bindings:{level:"<",options:"<",selected:"="},controller:["$scope",function(t){var e=this;e.toggle=function(n){e.selected||(e.selected=[]),e.selected.includes(n.id)?e.selected=e.selected.filter(function(t){return t!=n.id}):e.selected.push(n.id),t.$emit("queryBuilder.change")},e.selectedOptions,t.$watch("$ctrl.selected",function(){e.selectedOptions=e.options.filter(function(t){return(e.selected||[]).includes(t.id)}),e.options.forEach(function(t){return t.selected=e.selectedOptions.some(function(e){return e.id==t.id})})},!0)}],template:'\n\t\t<a class="btn btn-block btn-{{$ctrl.level}} dropdown-toggle" data-toggle="dropdown">\n\t\t\t<span ng-repeat="item in $ctrl.selectedOptions track by item.id" class="pill">\n\t\t\t\t{{item.title}}\n\t\t\t</span>\n\t\t\t<i class="fa fa-caret-down"></i></a>\n\t\t</a>\n\t\t<ul class="dropdown-menu pull-right">\n\t\t\t<li ng-repeat="option in $ctrl.options track by option.id">\n\t\t\t\t<a ng-click="$ctrl.toggle(option)">\n\t\t\t\t\t<i class="fa fa-fw" ng-class="option.selected ? \'fa-check-square-o\' : \'fa-square-o\'"></i>\n\t\t\t\t\t{{option.title}}\n\t\t\t\t</a>\n\t\t\t</li>\n\t\t</ul>\n\t'}).component("uiQueryBuilderOLD",{bindings:{query:"=",spec:"<"},template:'\n\t\t<div class="ui-query-builder clearfix">\n\t\t\t<div class="query-container">\n\t\t\t\t\x3c!-- Meta field: sort {{{ --\x3e\n\t\t\t\t<div class="query-row">\n\t\t\t\t\t\x3c!-- Path component {{{ --\x3e\n\t\t\t\t\t<div class="query-block">\n\t\t\t\t\t\t<div class="btn-group btn-block">\n\t\t\t\t\t\t\t<a class="btn btn-1 btn-block">\n\t\t\t\t\t\t\t\tSort by\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\t\t\x3c!-- Query operand component {{{ --\x3e\n\t\t\t\t\t<div class="query-block btn-group">\n\t\t\t\t\t\t<div class="btn btn-block btn-2">\n\t\t\t\t\t\t\t<input ng-model="$ctrl.query.sort" type="text" class="form-control"/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\t</div>\n\t\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\t\x3c!-- Meta field: limit {{{ --\x3e\n\t\t\t\t<div class="query-row">\n\t\t\t\t\t\x3c!-- Path component {{{ --\x3e\n\t\t\t\t\t<div class="query-block">\n\t\t\t\t\t\t<div class="btn-group btn-block">\n\t\t\t\t\t\t\t<a class="btn btn-1 btn-block">\n\t\t\t\t\t\t\t\tLimited to\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\t\t\x3c!-- Query operand component {{{ --\x3e\n\t\t\t\t\t<div class="query-block btn-group">\n\t\t\t\t\t\t<div class="btn btn-block btn-2">\n\t\t\t\t\t\t\t<input ng-model="$ctrl.query.limit" type="number" class="form-control"/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class="query-block btn-group">\n\t\t\t\t\t\t<div class="btn btn-block btn-1">\n\t\t\t\t\t\t\tSkipping\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class="query-block btn-group">\n\t\t\t\t\t\t<div class="btn btn-block btn-2">\n\t\t\t\t\t\t\t<input ng-model="$ctrl.query.skip" type="number" class="form-control"/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\t</div>\n\t\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\t<div class="query-row">\n\t\t\t\t\t<div class="query-block">\n\t\t\t\t\t\t\x3c!-- FIXME: Need branch title --\x3e\n\t\t\t\t\t</div>\n\t\t\t\t\t<ui-query-builder-branch\n\t\t\t\t\t\tclass="query-container"\n\t\t\t\t\t\tbranch="$ctrl.query"\n\t\t\t\t\t\tspec="$ctrl.spec"\n\t\t\t\t\t></ui-query-builder-branch>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t',controller:["$scope",function(t){var e=this;t.$watch("$ctrl.spec",function(){_.forEach(e.spec,function(t,e){t.title||(t.title=_.startCase(e)),t.enum&&_.isArray(t.enum)&&(t.enum=_(t.enum).map(function(t){return _.isString(t)?{id:t,title:_.startCase(t)}:t}).sortBy("title").value())})})}]}).component("uiQueryBuilderBranch",{bindings:{branch:"=",spec:"<"},template:'\n\t\t\x3c!-- AND blocks {{{ --\x3e\n\t\t<div ng-repeat="leaf in $ctrl.properties | filter:{isMeta:true,id:\'$and\'} track by leaf.id" ng-switch="leaf.spec.type" ng-repeat-emit="uiQueryQueryRepaint" class="query-row">\n\t\t\t<div ng-repeat="choiceLeaf in leaf.value">\n\t\t\t\t<ui-query-builder-branch\n\t\t\t\t\tbranch="choiceLeaf"\n\t\t\t\t\tspec="$ctrl.spec"\n\t\t\t\t></ui-query-builder-branch>\n\t\t\t</div>\n\t\t</div>\n\t\t\x3c!-- }}} --\x3e\n\t\t\x3c!-- OR blocks {{{ --\x3e\n\t\t<div ng-repeat="leaf in $ctrl.properties | filter:{isMeta:true,id:\'$or\'} track by leaf.id" ng-switch="leaf.spec.type" ng-repeat-emit="uiQueryQueryRepaint" class="query-row">\n\t\t\t<div ng-repeat="choiceLeaf in leaf.value">\n\t\t\t\t<ui-query-builder-branch\n\t\t\t\t\tbranch="choiceLeaf"\n\t\t\t\t\tspec="$ctrl.spec"\n\t\t\t\t></ui-query-builder-branch>\n\t\t\t</div>\n\t\t</div>\n\t\t\x3c!-- }}} --\x3e\n\t\t\x3c!-- Main fields {{{ --\x3e\n\t\t<div ng-repeat="leaf in $ctrl.properties | filter:{isMeta:false} track by leaf.id" ng-switch="leaf.spec.type" ng-repeat-emit="uiQueryQueryRepaint" class="query-row">\n\t\t\t\x3c!-- Path component {{{ --\x3e\n\t\t\t<button ng-click="$ctrl.remove(leaf.id); $event.stopPropagation()" class="btn btn-trash btn-danger" type="button"></button>\n\t\t\t<div class="query-block">\n\t\t\t\t<div class="btn-group btn-block" ng-class="{new: !leaf.id}">\n\t\t\t\t\t<a class="btn btn-1 btn-block dropdown-toggle" data-toggle="dropdown">\n\t\t\t\t\t\t{{$ctrl.spec[leaf.id].title || \'Select...\'}}\n\t\t\t\t\t\t<i class="fa fa-caret-down"></i>\n\t\t\t\t\t</a>\n\t\t\t\t\t<ul class="dropdown-menu pull-right">\n\t\t\t\t\t\t<li ng-repeat="(key, val) in $ctrl.spec track by key" ng-class="key == leaf.id && \'active\'">\n\t\t\t\t\t\t\t<a ng-click="$ctrl.setField(leaf, key)">\n\t\t\t\t\t\t\t\t{{$ctrl.spec[key].title}}\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Query type component {{{ --\x3e\n\t\t\t<div ng-show="leaf.valueOperand" class="query-block">\n\t\t\t\t<div class="btn-group btn-block">\n\t\t\t\t\t<a class="btn btn-2 btn-block dropdown-toggle" data-toggle="dropdown">\n\t\t\t\t\t\t{{($ctrl.operandsByID[leaf.valueOperand][leaf.spec.type] || $ctrl.operandsByID[leaf.valueOperand].base).title}}\n\t\t\t\t\t\t<i class="fa fa-caret-down"></i>\n\t\t\t\t\t</a>\n\t\t\t\t\t<ul class="dropdown-menu pull-right">\n\t\t\t\t\t\t<li><a ng-click="$ctrl.setWrapper(leaf, \'$eq\')">Is</a></li>\n\t\t\t\t\t\t<li><a ng-click="$ctrl.setWrapper(leaf, \'$ne\')">Is not</a></li>\n\t\t\t\t\t\t<li><a ng-click="$ctrl.setWrapper(leaf, \'$in\')">One of</a></li>\n\t\t\t\t\t\t<li><a ng-click="$ctrl.setWrapper(leaf, \'$nin\')">Not one of</a></li>\n\t\t\t\t\t\t<li ng-if="leaf.spec.type == \'number\'"><a ng-click="$ctrl.setWrapper(leaf, \'$gt\')">Above</a></li>\n\t\t\t\t\t\t<li ng-if="leaf.spec.type == \'number\'"><a ng-click="$ctrl.setWrapper(leaf, \'$lt\')">Below</a></li>\n\t\t\t\t\t\t<li ng-if="leaf.spec.type == \'date\'"><a ng-click="$ctrl.setWrapper(leaf, \'$gt\')">Is after</a></li>\n\t\t\t\t\t\t<li ng-if="leaf.spec.type == \'date\'"><a ng-click="$ctrl.setWrapper(leaf, \'$gte\')">Is at least</a></li>\n\t\t\t\t\t\t<li ng-if="leaf.spec.type == \'date\'"><a ng-click="$ctrl.setWrapper(leaf, \'$lt\')">Is before</a></li>\n\t\t\t\t\t\t<li ng-if="leaf.spec.type == \'date\'"><a ng-click="$ctrl.setWrapper(leaf, \'$lte\')">Is at most</a></li>\n\t\t\t\t\t\t<li><a ng-click="$ctrl.setWrapper(leaf, \'$exists\')">Has a value</a></li>\n\t\t\t\t\t</ul>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Query operand component {{{ --\x3e\n\t\t\t<div ng-show="leaf.valueOperand" class="query-block btn-group" ng-switch="(operandConfig = $ctrl.operandsByID[leaf.valueOperand][leaf.spec.type] || $ctrl.operandsByID[leaf.valueOperand].base).type">\n\t\t\t\t<div ng-switch-when="string" class="btn btn-block btn-3">\n\t\t\t\t\t<input ng-model="leaf.valueEdit" ng-change="$ctrl.setValue(leaf)" type="text" class="form-control"/>\n\t\t\t\t</div>\n\t\t\t\t<div ng-switch-when="array" class="btn btn-block btn-3 btn-group">\n\t\t\t\t\t<div class="btn-fill text-left dropdown-toggle" data-toggle="dropdown">\n\t\t\t\t\t\t<span class="pill" ng-repeat="item in $ctrl.spec[leaf.id].enum | uiQueryBuilderFilterSelected:leaf track by item.id">\n\t\t\t\t\t\t\t{{item.title}}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span ng-if="!leaf.valueEdit.length">...</span>\n\t\t\t\t\t\t<i class="fa fa-caret-down"></i>\n\t\t\t\t\t</div>\n\t\t\t\t\t<ul class="dropdown-menu pull-right">\n\t\t\t\t\t\t<li ng-repeat="item in $ctrl.spec[leaf.id].enum | uiQueryBuilderFilterSelected:leaf:false track by item.id">\n\t\t\t\t\t\t\t<a ng-click="$ctrl.setValueIncluded(leaf, item.id, false)">\n\t\t\t\t\t\t\t\t<i class="fa fa-fw fa-check-square text-primary"></i>\n\t\t\t\t\t\t\t\t{{item.title}}\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t\t<li ng-repeat="item in $ctrl.spec[leaf.id].enum | uiQueryBuilderFilterSelected:leaf:true track by item.id">\n\t\t\t\t\t\t\t<a ng-click="$ctrl.setValueIncluded(leaf, item.id, true)">\n\t\t\t\t\t\t\t\t<i class="fa fa-fw fa-square-o text-primary"></i>\n\t\t\t\t\t\t\t\t{{item.title}}\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</div>\n\t\t\t\t<div ng-switch-when="boolean" class="btn btn-block btn-3" ng-click="$ctrl.setValue(leaf, !leaf.valueEdit)">\n\t\t\t\t\t<i class="fa fa-fw" ng-class="leaf.valueEdit ? \'fa-check-square-o\' : \'fa-square-o\'"></i>\n\t\t\t\t\t{{leaf.valueEdit ? operandConfig.textTrue : operandConfig.textFalse}}\n\t\t\t\t</div>\n\t\t\t\t<div ng-switch-when="date" class="btn btn-block btn-3">\n\t\t\t\t\t<input ng-model="leaf.valueEdit" ng-change="$ctrl.setValue(leaf)" type="date" class="form-control"/>\n\t\t\t\t</div>\n\t\t\t\t<div ng-switch-default class="btn btn-block btn-3">\n\t\t\t\t\tUnknown operand: <code>{{leaf.valueOperand}}</code>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t</div>\n\t\t\x3c!-- Add button {{{ --\x3e\n\t\t<button ng-click="$ctrl.add()" class="btn btn-add btn-success" type="button"></button>\n\t\t\x3c!-- }}} --\x3e\n\t',controller:["$element","$scope",function(t,e){var n=this;n.operands=[{id:"$eq",setter:function(t){return{$eq:t}},export:function(t){return t.valueEdit},base:{title:"Is",type:"string"},boolean:{title:"Is",type:"boolean",textTrue:"Enabled",textFalse:"Disabled"},date:{title:"Is exactly",type:"date"}},{id:"$ne",setter:function(t){return{$ne:t}},export:function(t){return{$ne:t.valueEdit}},base:{title:"Is not",type:"string"},boolean:{title:"Is not",type:"boolean",textTrue:"Enabled",textFalse:"Disabled"},date:{title:"Is not exactly",type:"date"}},{id:"$in",setter:function(t){return{$in:_.isArray(t)?t.split(/\s*,\s*/):[t]}},export:function(t){return{$in:t.value.$in}},base:{title:"One of",type:"array"}},{id:"$nin",setter:function(t){return{$nin:_.isArray(t)?t.split(/\s*,\s*/):[t]}},export:function(t){return{$nin:t.value.$nin}},base:{title:"Not one of",type:"array"}},{id:"$gt",setter:function(t){return{$gt:t}},export:function(t){return{$gt:t.value.$gt}},base:{title:"Above",type:"number"},date:{title:"Is after",type:"date"}},{id:"$gte",setter:function(t){return{$gte:t}},export:function(t){return{$gte:t.value.$gte}},base:{title:"Above or equals",type:"number"},date:{title:"Is at least",type:"date"}},{id:"$lt",setter:function(t){return{$lt:t}},export:function(t){return{$lt:t.value.$lt}},base:{title:"Below",type:"number"},date:{title:"Is before",type:"date"}},{id:"$lte",setter:function(t){return{$lt:t}},export:function(t){return{$lte:t.value.$lte}},base:{title:"Below or equals",type:"number"},date:{title:"Is at most",type:"date"}},{id:"$exists",setter:function(t){return{$exists:!!t}},export:function(t){return{$exists:t.value.$exists}},base:{title:"Has a value",type:"boolean",textTrue:"Has a value",textFalse:"Has a value"}},{id:"$regexp",setter:function(t){return{$regexp:t}},export:function(t){return{$regexp:t.value.$regexp}},base:{title:"Matches",type:"string"}}],n.operandsByID=_.mapKeys(n.operands,"id"),n.getSpec=function(t,e,i){return n.spec[i]?n.spec[i]:"$and"==t||"$or"==t?_defineProperty({type:"group"},"type",t):_.isString(e)?{type:"string"}:_.isNumber(e)?{type:"number"}:{type:"string"}},n.translateBranch=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[];return _(n.branch).map(function(t,i){return{id:i,value:t,valueEdit:n.getFlatValue(t),valueOperand:_.isObject(t)?_(t).keys().first():"$eq",isMeta:(""+i).startsWith("$")||["sort","skip","limit"].includes(i),spec:n.getSpec(i,t,i),path:e.concat([i])}}).sortBy(function(t){return t.isMeta?"Z"+t.id:"A"+t.id}).value()},n.exportBranch=function(){n.branch=_(n.properties).mapKeys(function(t){return t.id}).mapValues(function(t){return n.operandsByID[t.valueOperand].export(t)}).value()},n.properties,e.$watchGroup(["$ctrl.branch","$ctrl.spec"],function(){n.branch&&n.spec&&(n.properties=n.translateBranch(n.branch))}),n.setField=function(t,e){t.id=e,t.path=[e],t.value=void 0,t.valueEdit=void 0,t.valueOperand="$eq",t.spec=n.spec[e],n.setValue(t)},n.setWrapper=function(t,e){if("$eq"==t.valueOperand&&"$ne"==e)t.valueOperand="$ne",t.valueEdit=n.getFlatValue(t.value),t.value={$ne:t.valueEdit};else if("$ne"==t.valueOperand&&"$eq"==e)t.valueOperand="$eq",t.valueEdit=n.getFlatValue(t.value),t.value={$eq:t.valueEdit};else if("$in"==t.valueOperand&&"$eq"==e)t.valueOperand="$eq",t.value=t.valueEdit=n.getFlatValue(t.value);else if("$eq"!=t.valueOperand&&void 0!==t.valueOperand||"$in"!=e)if("$exists"==e)t.valueOperand="$exists",t.valueEdit=!0,t.value={$exists:t.valueEdit};else{console.log("UNHANDLED TYPE CONVERT:",t.type,"=>",e);var i=n.getFlatValue(t.value);t.valueOperand=e,t.valueEdit=i,t.value=_defineProperty({},t.valueOperand,t.valueEdit)}else t.valueOperand="$in",t.valueEdit=n.getFlatValue(t.value),t.value={$in:[t.valueEdit]};n.exportBranch()},n.setValue=function(t,e){var i=_.isUndefined(e)?t.valueEdit:e;t.value=n.operandsByID[t.valueOperand].setter(i),t.valueEdit=n.getFlatValue(t.value),n.exportBranch()},n.setValueIncluded=function(t,e,n){var i=_(t.value).keys().first();if(!i)throw new Error("Tried to set array inclusion on non wrapped key: "+t.value);var r=t.value[i].includes(e);n&&!r?t.value[i].push(e):!n&&r&&(t.value[i]=t.value[i].filter(function(t){return t!=e})),t.value[i].sort(),t.valueEdit=_.isObject(t.value)&&_.size(t.value)?_(t.value).map().first():t.value},n.getFlatValue=function(t){return _.isString(t)||_.isNumber(t)||_.isBoolean(t)||_.isDate(t)?t:_.isObject(t)&&1==_.size(t)?_(t).values().first():_.isObject(t)&&t.$regexp?"/"+_.trim(t.$regexp,"/")+"/"+t.options:(console.warn("Given up trying to flatten input value",t),t)},n.add=function(){if(!n.properties.some(function(t){return!t.id})){n.properties.push({isMeta:!1});e.$on("uiQueryQueryRepaint",function(){t.find(".query-block > .new").addClass("open")})}},n.remove=function(t){n.properties=n.properties.filter(function(e){return e.id!=t}),n.exportBranch()}}]}).filter("uiQueryBuilderFilterSelected",function(){return function(t,e,n){if(t)return t.filter(function(t){var i=e.valueEdit.includes(t.id);return n?!i:i})}}).directive("ngRepeatEmit",["$rootScope","$timeout",function(t,e){return{restrict:"A",link:function(t,n,i){!0===t.$last&&e(function(){return t.$emit(i.ngRepeatEmit)})}}}]);
"use strict";function _defineProperty(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};angular.module("angular-ui-query-builder",[]).service("QueryBuilder",function(){var t=this;t.cleanSpec=function(t){return _(t).mapValues(function(t,e){return{type:t.type,enum:_(t.enum).map(function(t){return _.isString(t)?{id:t,title:_.startCase(t)}:t}).sortBy("title").value()}}).value()},t.metaProperties={limit:{type:"keyVal",actions:[{id:"$eq",title:"Equals"}],action:"$eq",canDelete:!0},populate:{type:"hidden"},skip:{type:"keyVal",actions:[{id:"$eq",title:"Equals"}],action:"$eq",canDelete:!0},sort:{type:"keyVal",actions:[{id:"$eq",title:"Equals"}],action:"$eq",canDelete:!1}},t.queryToArray=function(e,n){var r=[{id:"$eq",title:"Equals"},{id:"$neq",title:"Doesnt equal"},{id:"$lt",title:"Is less than"},{id:"$lte",title:"Is equal to or less than"},{id:"$gt",title:"Is greater than"},{id:"$gte",title:"Is equal or greater than"},{id:"$in",title:"Is one of"},{id:"$nin",title:"Is not one of"},{id:"$exists",title:"Has a value"},{id:"$nexists",title:"Does not have a value"}];return _(e).pickBy(function(e,r){var l=n[r]||"$and"==r||"$or"==r||t.metaProperties[r];return l||console.warn("query-builder","Incomming query path",r,"Does not map to anything in spec",n),!!l}).map(function(e,l){var i=n[l],c=_.isObject(e)&&_(e).keys().first(),u=_.isObject(e)?_(e).values().first():e;return"$or"==l&&e.every(function(t){return _.isObject(t)&&1==_.keys(t).length})&&e.map(function(t){return _.chain(t).first().values().first().keys().find(function(t){return"$regexp"==t}).value()}).length==e.length?{path:l,type:"search",title:"Search",value:_.chain(e).first().values().first().get("$regexp").value(),fields:_(e).map(function(t){return _.keys(t)}).flatten().value(),actions:r}:"$and"==l||"$or"==l?(_.isArray(e)||(console.warn("query-builder","Query path",l,"is a meta key",e,"but is not an array!","Given",void 0===e?"undefined":_typeof(e)),e=[]),{path:l,type:"binaryGroup",title:"$and"==l?"AND":"$or"==l?"OR":"UNKNOWN",condition:l.replace(/\$/,""),children:e.map(function(e){return t.queryToArray(e,n)}),actions:r}):t.metaProperties[l]?Object.assign({path:l,title:_.startCase(l),value:e,type:"hidden",action:"$hidden",actions:r},t.metaProperties[l]):"$exists"==c?{path:l,title:e.title||_.startCase(l),value:!!e,type:"exists",action:"$exists",actions:r}:"string"==i.type&&_.isArray(i.enum)&&i.enum.length?{path:l,title:e.title||_.startCase(l),type:"enum",action:e.$in?"$in":e.$nin?"$nin":i.enum.length?"$in":"$eq",enum:i.enum,value:e.$in?e.$in:e.$nin?e.$nin:i.enum.length&&!_.isArray(e)?[e]:e,actions:r}:{path:l,title:e.title||_.startCase(l),type:"string"==i.type?"string":"number"==i.type?"number":"date"==i.type?"date":"string",action:"$eq",value:"date"==i.type?moment(u).toDate():u,actions:r}}).value()},t.arrayToQuery=function(t){return function(t){return _(t).mapKeys(function(t){return t.path}).mapValues(function(t){switch(t.type){case"string":case"number":case"date":return"$eq"==t.action?t.value:_defineProperty({},t.action,t.value);case"enum":return _defineProperty({},t.action,t.value);case"exists":return{$exists:"$exists"==t.action};case"search":return t.fields.map(function(e){return _defineProperty({},e,{$regexp:t.value,options:"i"})});case"keyVal":case"hidden":return t.value;default:console.warn("Unknown type to convert:",t.type)}}).value()}(t)}}).component("uiQueryBuilder",{bindings:{query:"=",spec:"<"},template:'\n\t\t<div class="ui-query-builder">\n\t\t\t<div class="query-container">\n\t\t\t\t<ui-query-builder-group\n\t\t\t\t\tqb-group="$ctrl.qbQuery"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-group>\n\t\t\t</div>\n\t\t</div>\n\t',controller:["$scope","$timeout","QueryBuilder",function(t,e,n){var r=this;r.qbSpec,r.qbQuery;var l=t.$watchGroup(["$ctrl.query","$ctrl.spec"],function(){r.spec&&r.query&&(r.qbSpec=n.cleanSpec(r.spec),r.qbQuery=n.queryToArray(r.query,r.qbSpec),l())});t.$on("queryBuilder.change",function(t,l){return e(function(){l&&(r.query=l,r.qbQuery=n.queryToArray(r.query,r.qbSpec)),r.query=n.arrayToQuery(r.qbQuery)})}),t.$on("queryBuilder.pathAction.drop",function(t,e){r.qbQuery=r.qbQuery.filter(function(t){return t.path!=e}),r.query=n.arrayToQuery(r.qbQuery)}),t.$on("queryBuilder.pathAction.swap",function(e,l,i){r.qbQuery=r.qbQuery.filter(function(t){return t.path!=l}),r.query=n.arrayToQuery(r.qbQuery),t.$emit("queryBuilder.pathAction.add",i)}),t.$on("queryBuilder.pathAction.add",function(t,e){r.query[e]="",r.qbQuery=n.queryToArray(r.query,r.qbSpec),r.query=n.arrayToQuery(r.qbQuery)})}]}).component("uiQueryBuilderGroup",{bindings:{qbGroup:"=",qbSpec:"<"},template:'\n\t\t<div ng-repeat="row in $ctrl.qbGroup | filter:$ctrl.qbGroupFilter" meta-key="{{row.path}}">\n\t\t\t<ui-query-builder-row\n\t\t\t\tqb-item="row"\n\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t></ui-query-builder-row>\n\t\t</div>\n\t\t<div class="query-row">\n\t\t\t<div class="query-container">\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<button ng-click="$ctrl.add()" type="button" class="btn-add"></button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t',controller:["$scope","QueryBuilder",function(t,e){this.qbGroupFilter=function(t){return"hidden"!=t.type}}]}).component("uiQueryBuilderRow",{bindings:{qbItem:"=",qbSpec:"<"},controller:["$scope","QueryBuilder",function(t,e){var n=this;n.delete=function(e){return t.$emit("queryBuilder.pathAction.drop",e)},n.setChanged=function(){return t.$emit("queryBuilder.change")}}],template:'\n\t\t<div ng-switch="$ctrl.qbItem.type">\n\t\t\t\x3c!-- $and / $or condition {{{ --\x3e\n\t\t\t<div ng-switch-when="binaryGroup" class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-1 btn-block">\n\t\t\t\t\t\t{{$ctrl.qbItem.title}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div ng-repeat="conditional in $ctrl.qbItem.children" class="query-container clearfix">\n\t\t\t\t\t<ui-query-builder-group\n\t\t\t\t\t\tqb-group="conditional"\n\t\t\t\t\t\tqb-spec="$ctrl.spec"\n\t\t\t\t\t></ui-query-builder-group>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- String {{{ --\x3e\n\t\t\t<div ng-switch-when="string" class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-path\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\tselected="$ctrl.qbItem.path"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-path>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-3 btn-block">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tng-model="$ctrl.qbItem.value"\n\t\t\t\t\t\t\tng-change="$ctrl.setChanged()"\n\t\t\t\t\t\t\ttype="text"\n\t\t\t\t\t\t\tclass="form-control"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Enum {{{ --\x3e\n\t\t\t<div ng-switch-when="enum" class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-path\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\tselected="$ctrl.qbItem.path"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-path>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t\t<ui-query-builder-block-menu-multiple\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="3"\n\t\t\t\t\tselected="$ctrl.qbItem.value"\n\t\t\t\t\toptions="$ctrl.qbItem.enum"\n\t\t\t\t></ui-query-builder-block-menu-multiple>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Date {{{ --\x3e\n\t\t\t<div ng-switch-when="date" class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-path\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\tselected="$ctrl.qbItem.path"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-path>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-3 btn-block">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tng-model="$ctrl.qbItem.value"\n\t\t\t\t\t\t\tng-change="$ctrl.setChanged()"\n\t\t\t\t\t\t\ttype="date"\n\t\t\t\t\t\t\tclass="form-control"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Number {{{ --\x3e\n\t\t\t<div ng-switch-when="number" class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-path\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\tselected="$ctrl.qbItem.path"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-path>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-3 btn-block">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tng-value="$ctrl.qbItem.value"\n\t\t\t\t\t\t\tng-changed="$ctrl.setChanged()"\n\t\t\t\t\t\t\ttype="number"\n\t\t\t\t\t\t\tclass="form-control"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Exists {{{ --\x3e\n\t\t\t<div ng-switch-when="exists" class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-path\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\tselected="$ctrl.qbItem.path"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-path>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Search {{{ --\x3e\n\t\t\t<div ng-switch-when="search" class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-path\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\tselected="$ctrl.qbItem.path"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-path>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-2 btn-block">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tng-model="$ctrl.qbItem.value"\n\t\t\t\t\t\t\tng-change="$ctrl.setChanged()"\n\t\t\t\t\t\t\ttype="text"\n\t\t\t\t\t\t\tclass="form-control"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- keyVal (Only title + value) {{{ --\x3e\n\t\t\t<div ng-switch-when="keyVal" class="query-row">\n\t\t\t\t<a ng-if="$ctrl.qbItem.canDelete === undefined || $ctrl.qbItem.canDelete" ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-block\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\ttitle="$ctrl.qbItem.title"\n\t\t\t\t></ui-query-builder-block>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-2 btn-block">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tng-model="$ctrl.qbItem.value"\n\t\t\t\t\t\t\tng-change="$ctrl.setChanged()"\n\t\t\t\t\t\t\ttype="text"\n\t\t\t\t\t\t\tclass="form-control"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Unknown {{{ --\x3e\n\t\t\t<div ng-switch-default class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-path\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\tselected="$ctrl.qbItem.path"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-path>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-warning btn-block">\n\t\t\t\t\t\tUnknown handler: {{$ctrl.qbItem.type}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t</div>\n\t'}).component("uiQueryBuilderPath",{bindings:{level:"<",selected:"<",qbSpec:"<"},controller:["$scope",function(t){var e=this;e.setSelected=function(n){return t.$emit("queryBuilder.pathAction.swap",e.selected,n)},e.options,e.$onInit=function(){e.options=_.map(e.qbSpec,function(t,e){return Object.assign({},{path:e,title:_.startCase(e)},t)}),e.selectedOption=e.options.find(function(t){return t.path==e.selected})}}],template:'\n\t\t<a class="btn btn-block btn-{{$ctrl.level}} dropdown-toggle" data-toggle="dropdown">\n\t\t\t{{$ctrl.selectedOption.title}}\n\t\t\t<i class="fa fa-caret-down"></i>\n\t\t</a>\n\t\t<ul class="dropdown-menu pull-right">\n\t\t\t<li ng-repeat="path in $ctrl.options track by path.path"><a ng-click="$ctrl.setSelected(path.path)">{{path.title}}</a></li>\n\t\t</ul>\n\t'}).component("uiQueryBuilderBlock",{bindings:{level:"<",title:"<"},controller:["$scope",function(t){}],template:'\n\t\t<a class="btn btn-block btn-{{$ctrl.level}}">\n\t\t\t{{$ctrl.title}}\n\t\t</a>\n\t'}).component("uiQueryBuilderBlockMenu",{bindings:{level:"<",options:"<",selected:"="},controller:["$scope",function(t){var e=this;e.setSelected=function(n){e.selected=n.id,t.$emit("queryBuilder.change")},e.selectedOption,t.$watchGroup(["$ctrl.options","$ctrl.selected"],function(){e.selectedOption=e.options.find(function(t){return t.id==e.selected})})}],template:'\n\t\t<a class="btn btn-block btn-{{$ctrl.level}} dropdown-toggle" data-toggle="dropdown">\n\t\t\t{{$ctrl.selectedOption.title}}\n\t\t\t<i class="fa fa-caret-down"></i>\n\t\t</a>\n\t\t<ul class="dropdown-menu pull-right">\n\t\t\t<li ng-repeat="option in $ctrl.options track by option.id"><a ng-click="$ctrl.setSelected(option)">{{option.title}}</a></li>\n\t\t</ul>\n\t'}).component("uiQueryBuilderBlockMenuMultiple",{bindings:{level:"<",options:"<",selected:"="},controller:["$scope",function(t){var e=this;e.toggle=function(n){e.selected||(e.selected=[]),e.selected.includes(n.id)?e.selected=e.selected.filter(function(t){return t!=n.id}):e.selected.push(n.id),t.$emit("queryBuilder.change")},e.selectedOptions,t.$watch("$ctrl.selected",function(){e.selectedOptions=e.options.filter(function(t){return(e.selected||[]).includes(t.id)}),e.options.forEach(function(t){return t.selected=e.selectedOptions.some(function(e){return e.id==t.id})})},!0)}],template:'\n\t\t<a class="btn btn-block btn-{{$ctrl.level}} dropdown-toggle" data-toggle="dropdown">\n\t\t\t<span ng-repeat="item in $ctrl.selectedOptions track by item.id" class="pill">\n\t\t\t\t{{item.title}}\n\t\t\t</span>\n\t\t\t<i class="fa fa-caret-down"></i></a>\n\t\t</a>\n\t\t<ul class="dropdown-menu pull-right">\n\t\t\t<li ng-repeat="option in $ctrl.options track by option.id">\n\t\t\t\t<a ng-click="$ctrl.toggle(option)">\n\t\t\t\t\t<i class="fa fa-fw" ng-class="option.selected ? \'fa-check-square-o\' : \'fa-square-o\'"></i>\n\t\t\t\t\t{{option.title}}\n\t\t\t\t</a>\n\t\t\t</li>\n\t\t</ul>\n\t'});

@@ -1,1 +0,1 @@

"use strict";function _defineProperty(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};angular.module("angular-ui-query-builder",[]).service("QueryBuilder",function(){var t=this;t.cleanSpec=function(t){return _(t).mapValues(function(t,e){return{type:t.type,enum:_(t.enum).map(function(t){return _.isString(t)?{id:t,title:_.startCase(t)}:t}).sortBy("title").value()}}).value()},t.queryToArray=function(e,n){var i=[{id:"$eq",title:"Equals"},{id:"$neq",title:"Doesnt equal"},{id:"$lt",title:"Is less than"},{id:"$lte",title:"Is equal to or less than"},{id:"$gt",title:"Is greater than"},{id:"$gte",title:"Is equal or greater than"},{id:"$in",title:"Is one of"},{id:"$nin",title:"Is not one of"},{id:"$exists",title:"Has a value"},{id:"$nexists",title:"Does not have a value"}];return _(e).pickBy(function(t,e){var i=n[e]||"$and"==e||"$or"==e;return i||console.warn("query-builder","Incomming query path",e,"Does not map to anything in spec",n),!!i}).map(function(e,l){var r=n[l],a=_.isObject(e)&&_(e).keys().first(),s=_.isObject(e)?_(e).values().first():e;return"$or"==l&&e.every(function(t){return _.isObject(t)&&1==_.keys(t).length})&&e.map(function(t){return _.chain(t).first().values().first().keys().find(function(t){return"$regexp"==t}).value()}).length==e.length?{path:l,type:"search",title:"Search",value:_.chain(e).first().values().first().get("$regexp").value(),fields:_(e).map(function(t){return _.keys(t)}).flatten().value(),actions:i}:"$and"==l||"$or"==l?(_.isArray(e)||(console.warn("query-builder","Query path",l,"is a meta key",e,"but is not an array!","Given",void 0===e?"undefined":_typeof(e)),e=[]),{path:l,type:"binaryGroup",title:"$and"==l?"AND":"$or"==l?"OR":"UNKNOWN",condition:l.replace(/\$/,""),children:e.map(function(e){return t.queryToArray(e,n)}),actions:i}):"$exists"==a?{path:l,title:e.title||_.startCase(l),value:!!e,type:"exists",action:"$exists",actions:i}:"string"==r.type&&_.isArray(r.enum)?{path:l,title:e.title||_.startCase(l),type:"enum",action:e.$in?"$in":e.$nin?"$nin":r.enum.length?"$in":"$eq",enum:r.enum,value:e.$in?e.$in:e.$nin?e.$nin:r.enum.length&&!_.isArray(e)?[e]:e,actions:i}:{path:l,title:e.title||_.startCase(l),type:"string"==r.type?"string":"number"==r.type?"number":"date"==r.type?"date":"string",action:a,value:"date"==r.type?moment(s).format("YYYY-MM-DD"):s,actions:i}}).value()},t.arrayToQuery=function(t){return function(t){return _(t).mapKeys(function(t){return t.path}).mapValues(function(t){switch(t.type){case"string":case"number":case"date":return"$eq"==t.action?t.value:_defineProperty({},t.action,t.value);case"enum":return _defineProperty({},t.action,t.value);case"exists":return{$exists:"$exists"==t.action};case"search":return t.fields.map(function(e){return _defineProperty({},e,{$regexp:t.value,options:"i"})});default:console.warn("Unknown type to convert:",t.type)}}).value()}(t)}}).component("uiQueryBuilder",{bindings:{query:"=",spec:"<"},template:'\n\t\t<div class="ui-query-builder">\n\t\t\t<div class="query-container">\n\t\t\t\t<ui-query-builder-group\n\t\t\t\t\tqb-group="$ctrl.qbQuery"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-group>\n\t\t\t</div>\n\t\t</div>\n\t',controller:["$scope","$timeout","QueryBuilder",function(t,e,n){var i=this;i.qbSpec,i.qbQuery,t.$watchGroup(["$ctrl.query","$ctrl.spec"],function(){i.spec&&i.query&&(i.qbSpec=n.cleanSpec(i.spec),i.qbQuery=n.queryToArray(i.query,i.qbSpec))}),t.$on("queryBuilder.change",function(t,l){return e(function(){l&&(i.query=l,i.qbQuery=n.queryToArray(i.query,i.qbSpec)),i.query=n.arrayToQuery(i.qbQuery)})}),t.$on("queryBuilder.pathAction.drop",function(t,e){i.qbQuery=i.qbQuery.filter(function(t){return t.path!=e}),i.query=n.arrayToQuery(i.qbQuery)}),t.$on("queryBuilder.pathAction.swap",function(e,l,r){i.qbQuery=i.qbQuery.filter(function(t){return t.path!=l}),i.query=n.arrayToQuery(i.qbQuery),t.$emit("queryBuilder.pathAction.add",r)}),t.$on("queryBuilder.pathAction.add",function(t,e){i.query[e]="",i.qbQuery=n.queryToArray(i.query,i.qbSpec),i.query=n.arrayToQuery(i.qbQuery)})}]}).component("uiQueryBuilderGroup",{bindings:{qbGroup:"=",qbSpec:"<"},template:'\n\t\t<div ng-repeat="row in $ctrl.qbGroup">\n\t\t\t<ui-query-builder-row\n\t\t\t\tqb-item="row"\n\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t></ui-query-builder-row>\n\t\t</div>\n\t',controller:["$scope","QueryBuilder",function(t,e){}]}).component("uiQueryBuilderRow",{bindings:{qbItem:"=",qbSpec:"<"},controller:["$scope","QueryBuilder",function(t,e){var n=this;n.delete=function(e){return t.$emit("queryBuilder.pathAction.drop",e)},n.setChanged=function(){return t.$emit("queryBuilder.change")}}],template:'\n\t\t<div ng-switch="$ctrl.qbItem.type">\n\t\t\t\x3c!-- $and / $or condition {{{ --\x3e\n\t\t\t<div ng-switch-when="binaryGroup" class="query-row">\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-1 btn-block">\n\t\t\t\t\t\t{{$ctrl.qbItem.title}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div ng-repeat="conditional in $ctrl.qbItem.children" class="query-container clearfix">\n\t\t\t\t\t<ui-query-builder-group\n\t\t\t\t\t\tqb-group="conditional"\n\t\t\t\t\t\tqb-spec="$ctrl.spec"\n\t\t\t\t\t></ui-query-builder-group>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- String {{{ --\x3e\n\t\t\t<div ng-switch-when="string" class="query-row">\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-1 btn-block">\n\t\t\t\t\t\t{{$ctrl.qbItem.title}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-3 btn-block">\n\t\t\t\t\t\t<input ng-value="$ctrl.qbItem.value" type="text" class="form-control"/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Enum {{{ --\x3e\n\t\t\t<div ng-switch-when="enum" class="query-row">\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-1 btn-block">\n\t\t\t\t\t\t{{$ctrl.qbItem.title}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t\t<ui-query-builder-block-menu-multiple\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="3"\n\t\t\t\t\tselected="$ctrl.qbItem.value"\n\t\t\t\t\toptions="$ctrl.qbItem.enum"\n\t\t\t\t></ui-query-builder-block-menu-multiple>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Date {{{ --\x3e\n\t\t\t<div ng-switch-when="date" class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-path\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\tselected="$ctrl.qbItem.path"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-path>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-3 btn-block">\n\t\t\t\t\t\t<input ng-value="$ctrl.qbItem.value" type="date" class="form-control"/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Number {{{ --\x3e\n\t\t\t<div ng-switch-when="number" class="query-row">\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-1 btn-block">\n\t\t\t\t\t\t{{$ctrl.qbItem.title}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-3 btn-block">\n\t\t\t\t\t\t<input ng-value="$ctrl.qbItem.value" type="number" class="form-control"/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Exists {{{ --\x3e\n\t\t\t<div ng-switch-when="exists" class="query-row">\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-1 btn-block">\n\t\t\t\t\t\t{{$ctrl.qbItem.title}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Search {{{ --\x3e\n\t\t\t<div ng-switch-when="search" class="query-row">\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-1 btn-block">\n\t\t\t\t\t\t{{$ctrl.qbItem.title}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-2 btn-block">\n\t\t\t\t\t\t<input ng-value="$ctrl.qbItem.value" ng-keyup="$ctrl.setChanged()" type="text" class="form-control"/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Unknown {{{ --\x3e\n\t\t\t<div ng-switch-default class="query-row">\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-warning btn-block">\n\t\t\t\t\t\t{{$ctrl.qbItem.title}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-warning btn-block">\n\t\t\t\t\t\tUnknown handler: {{$ctrl.qbItem.type}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Add button {{{\n\t\t\t<div class="query-row">\n\t\t\t\t<div class="query-block btn-group">\n\t\t\t\t\t<a ng-click="$ctrl.add()" class="btn btn-add"></a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t}}} --\x3e\n\t\t</div>\n\t'}).component("uiQueryBuilderPath",{bindings:{level:"<",selected:"<",qbSpec:"<"},controller:["$scope",function(t){var e=this;e.setSelected=function(n){return t.$emit("queryBuilder.pathAction.swap",e.selected,n)},e.options,e.$onInit=function(){e.options=_.map(e.qbSpec,function(t,e){return Object.assign({},{path:e,title:_.startCase(e)},t)}),e.selectedOption=e.options.find(function(t){return t.path==e.selected})}}],template:'\n\t\t<a class="btn btn-block btn-{{$ctrl.level}} dropdown-toggle" data-toggle="dropdown">\n\t\t\t{{$ctrl.selectedOption.title}}\n\t\t\t<i class="fa fa-caret-down"></i>\n\t\t</a>\n\t\t<ul class="dropdown-menu pull-right">\n\t\t\t<li ng-repeat="path in $ctrl.options track by path.path"><a ng-click="$ctrl.setSelected(path.path)">{{path.title}}</a></li>\n\t\t</ul>\n\t'}).component("uiQueryBuilderBlockMenu",{bindings:{level:"<",options:"<",selected:"="},controller:["$scope",function(t){var e=this;e.setSelected=function(n){e.selected=n.id,t.$emit("queryBuilder.change")},e.selectedOption,t.$watchGroup(["$ctrl.options","$ctrl.selected"],function(){e.selectedOption=e.options.find(function(t){return t.id==e.selected})})}],template:'\n\t\t<a class="btn btn-block btn-{{$ctrl.level}} dropdown-toggle" data-toggle="dropdown"> {{$ctrl.selectedOption.title}} <i class="fa fa-caret-down"></i></a>\n\t\t<ul class="dropdown-menu pull-right">\n\t\t\t<li ng-repeat="option in $ctrl.options track by option.id"><a ng-click="$ctrl.setSelected(option)">{{option.title}}</a></li>\n\t\t</ul>\n\t'}).component("uiQueryBuilderBlockMenuMultiple",{bindings:{level:"<",options:"<",selected:"="},controller:["$scope",function(t){var e=this;e.toggle=function(n){e.selected||(e.selected=[]),e.selected.includes(n.id)?e.selected=e.selected.filter(function(t){return t!=n.id}):e.selected.push(n.id),t.$emit("queryBuilder.change")},e.selectedOptions,t.$watch("$ctrl.selected",function(){e.selectedOptions=e.options.filter(function(t){return(e.selected||[]).includes(t.id)}),e.options.forEach(function(t){return t.selected=e.selectedOptions.some(function(e){return e.id==t.id})})},!0)}],template:'\n\t\t<a class="btn btn-block btn-{{$ctrl.level}} dropdown-toggle" data-toggle="dropdown">\n\t\t\t<span ng-repeat="item in $ctrl.selectedOptions track by item.id" class="pill">\n\t\t\t\t{{item.title}}\n\t\t\t</span>\n\t\t\t<i class="fa fa-caret-down"></i></a>\n\t\t</a>\n\t\t<ul class="dropdown-menu pull-right">\n\t\t\t<li ng-repeat="option in $ctrl.options track by option.id">\n\t\t\t\t<a ng-click="$ctrl.toggle(option)">\n\t\t\t\t\t<i class="fa fa-fw" ng-class="option.selected ? \'fa-check-square-o\' : \'fa-square-o\'"></i>\n\t\t\t\t\t{{option.title}}\n\t\t\t\t</a>\n\t\t\t</li>\n\t\t</ul>\n\t'}).component("uiQueryBuilderOLD",{bindings:{query:"=",spec:"<"},template:'\n\t\t<div class="ui-query-builder clearfix">\n\t\t\t<div class="query-container">\n\t\t\t\t\x3c!-- Meta field: sort {{{ --\x3e\n\t\t\t\t<div class="query-row">\n\t\t\t\t\t\x3c!-- Path component {{{ --\x3e\n\t\t\t\t\t<div class="query-block">\n\t\t\t\t\t\t<div class="btn-group btn-block">\n\t\t\t\t\t\t\t<a class="btn btn-1 btn-block">\n\t\t\t\t\t\t\t\tSort by\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\t\t\x3c!-- Query operand component {{{ --\x3e\n\t\t\t\t\t<div class="query-block btn-group">\n\t\t\t\t\t\t<div class="btn btn-block btn-2">\n\t\t\t\t\t\t\t<input ng-model="$ctrl.query.sort" type="text" class="form-control"/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\t</div>\n\t\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\t\x3c!-- Meta field: limit {{{ --\x3e\n\t\t\t\t<div class="query-row">\n\t\t\t\t\t\x3c!-- Path component {{{ --\x3e\n\t\t\t\t\t<div class="query-block">\n\t\t\t\t\t\t<div class="btn-group btn-block">\n\t\t\t\t\t\t\t<a class="btn btn-1 btn-block">\n\t\t\t\t\t\t\t\tLimited to\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\t\t\x3c!-- Query operand component {{{ --\x3e\n\t\t\t\t\t<div class="query-block btn-group">\n\t\t\t\t\t\t<div class="btn btn-block btn-2">\n\t\t\t\t\t\t\t<input ng-model="$ctrl.query.limit" type="number" class="form-control"/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class="query-block btn-group">\n\t\t\t\t\t\t<div class="btn btn-block btn-1">\n\t\t\t\t\t\t\tSkipping\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class="query-block btn-group">\n\t\t\t\t\t\t<div class="btn btn-block btn-2">\n\t\t\t\t\t\t\t<input ng-model="$ctrl.query.skip" type="number" class="form-control"/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\t</div>\n\t\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\t<div class="query-row">\n\t\t\t\t\t<div class="query-block">\n\t\t\t\t\t\t\x3c!-- FIXME: Need branch title --\x3e\n\t\t\t\t\t</div>\n\t\t\t\t\t<ui-query-builder-branch\n\t\t\t\t\t\tclass="query-container"\n\t\t\t\t\t\tbranch="$ctrl.query"\n\t\t\t\t\t\tspec="$ctrl.spec"\n\t\t\t\t\t></ui-query-builder-branch>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t',controller:["$scope",function(t){var e=this;t.$watch("$ctrl.spec",function(){_.forEach(e.spec,function(t,e){t.title||(t.title=_.startCase(e)),t.enum&&_.isArray(t.enum)&&(t.enum=_(t.enum).map(function(t){return _.isString(t)?{id:t,title:_.startCase(t)}:t}).sortBy("title").value())})})}]}).component("uiQueryBuilderBranch",{bindings:{branch:"=",spec:"<"},template:'\n\t\t\x3c!-- AND blocks {{{ --\x3e\n\t\t<div ng-repeat="leaf in $ctrl.properties | filter:{isMeta:true,id:\'$and\'} track by leaf.id" ng-switch="leaf.spec.type" ng-repeat-emit="uiQueryQueryRepaint" class="query-row">\n\t\t\t<div ng-repeat="choiceLeaf in leaf.value">\n\t\t\t\t<ui-query-builder-branch\n\t\t\t\t\tbranch="choiceLeaf"\n\t\t\t\t\tspec="$ctrl.spec"\n\t\t\t\t></ui-query-builder-branch>\n\t\t\t</div>\n\t\t</div>\n\t\t\x3c!-- }}} --\x3e\n\t\t\x3c!-- OR blocks {{{ --\x3e\n\t\t<div ng-repeat="leaf in $ctrl.properties | filter:{isMeta:true,id:\'$or\'} track by leaf.id" ng-switch="leaf.spec.type" ng-repeat-emit="uiQueryQueryRepaint" class="query-row">\n\t\t\t<div ng-repeat="choiceLeaf in leaf.value">\n\t\t\t\t<ui-query-builder-branch\n\t\t\t\t\tbranch="choiceLeaf"\n\t\t\t\t\tspec="$ctrl.spec"\n\t\t\t\t></ui-query-builder-branch>\n\t\t\t</div>\n\t\t</div>\n\t\t\x3c!-- }}} --\x3e\n\t\t\x3c!-- Main fields {{{ --\x3e\n\t\t<div ng-repeat="leaf in $ctrl.properties | filter:{isMeta:false} track by leaf.id" ng-switch="leaf.spec.type" ng-repeat-emit="uiQueryQueryRepaint" class="query-row">\n\t\t\t\x3c!-- Path component {{{ --\x3e\n\t\t\t<button ng-click="$ctrl.remove(leaf.id); $event.stopPropagation()" class="btn btn-trash btn-danger" type="button"></button>\n\t\t\t<div class="query-block">\n\t\t\t\t<div class="btn-group btn-block" ng-class="{new: !leaf.id}">\n\t\t\t\t\t<a class="btn btn-1 btn-block dropdown-toggle" data-toggle="dropdown">\n\t\t\t\t\t\t{{$ctrl.spec[leaf.id].title || \'Select...\'}}\n\t\t\t\t\t\t<i class="fa fa-caret-down"></i>\n\t\t\t\t\t</a>\n\t\t\t\t\t<ul class="dropdown-menu pull-right">\n\t\t\t\t\t\t<li ng-repeat="(key, val) in $ctrl.spec track by key" ng-class="key == leaf.id && \'active\'">\n\t\t\t\t\t\t\t<a ng-click="$ctrl.setField(leaf, key)">\n\t\t\t\t\t\t\t\t{{$ctrl.spec[key].title}}\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Query type component {{{ --\x3e\n\t\t\t<div ng-show="leaf.valueOperand" class="query-block">\n\t\t\t\t<div class="btn-group btn-block">\n\t\t\t\t\t<a class="btn btn-2 btn-block dropdown-toggle" data-toggle="dropdown">\n\t\t\t\t\t\t{{($ctrl.operandsByID[leaf.valueOperand][leaf.spec.type] || $ctrl.operandsByID[leaf.valueOperand].base).title}}\n\t\t\t\t\t\t<i class="fa fa-caret-down"></i>\n\t\t\t\t\t</a>\n\t\t\t\t\t<ul class="dropdown-menu pull-right">\n\t\t\t\t\t\t<li><a ng-click="$ctrl.setWrapper(leaf, \'$eq\')">Is</a></li>\n\t\t\t\t\t\t<li><a ng-click="$ctrl.setWrapper(leaf, \'$ne\')">Is not</a></li>\n\t\t\t\t\t\t<li><a ng-click="$ctrl.setWrapper(leaf, \'$in\')">One of</a></li>\n\t\t\t\t\t\t<li><a ng-click="$ctrl.setWrapper(leaf, \'$nin\')">Not one of</a></li>\n\t\t\t\t\t\t<li ng-if="leaf.spec.type == \'number\'"><a ng-click="$ctrl.setWrapper(leaf, \'$gt\')">Above</a></li>\n\t\t\t\t\t\t<li ng-if="leaf.spec.type == \'number\'"><a ng-click="$ctrl.setWrapper(leaf, \'$lt\')">Below</a></li>\n\t\t\t\t\t\t<li ng-if="leaf.spec.type == \'date\'"><a ng-click="$ctrl.setWrapper(leaf, \'$gt\')">Is after</a></li>\n\t\t\t\t\t\t<li ng-if="leaf.spec.type == \'date\'"><a ng-click="$ctrl.setWrapper(leaf, \'$gte\')">Is at least</a></li>\n\t\t\t\t\t\t<li ng-if="leaf.spec.type == \'date\'"><a ng-click="$ctrl.setWrapper(leaf, \'$lt\')">Is before</a></li>\n\t\t\t\t\t\t<li ng-if="leaf.spec.type == \'date\'"><a ng-click="$ctrl.setWrapper(leaf, \'$lte\')">Is at most</a></li>\n\t\t\t\t\t\t<li><a ng-click="$ctrl.setWrapper(leaf, \'$exists\')">Has a value</a></li>\n\t\t\t\t\t</ul>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Query operand component {{{ --\x3e\n\t\t\t<div ng-show="leaf.valueOperand" class="query-block btn-group" ng-switch="(operandConfig = $ctrl.operandsByID[leaf.valueOperand][leaf.spec.type] || $ctrl.operandsByID[leaf.valueOperand].base).type">\n\t\t\t\t<div ng-switch-when="string" class="btn btn-block btn-3">\n\t\t\t\t\t<input ng-model="leaf.valueEdit" ng-change="$ctrl.setValue(leaf)" type="text" class="form-control"/>\n\t\t\t\t</div>\n\t\t\t\t<div ng-switch-when="array" class="btn btn-block btn-3 btn-group">\n\t\t\t\t\t<div class="btn-fill text-left dropdown-toggle" data-toggle="dropdown">\n\t\t\t\t\t\t<span class="pill" ng-repeat="item in $ctrl.spec[leaf.id].enum | uiQueryBuilderFilterSelected:leaf track by item.id">\n\t\t\t\t\t\t\t{{item.title}}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span ng-if="!leaf.valueEdit.length">...</span>\n\t\t\t\t\t\t<i class="fa fa-caret-down"></i>\n\t\t\t\t\t</div>\n\t\t\t\t\t<ul class="dropdown-menu pull-right">\n\t\t\t\t\t\t<li ng-repeat="item in $ctrl.spec[leaf.id].enum | uiQueryBuilderFilterSelected:leaf:false track by item.id">\n\t\t\t\t\t\t\t<a ng-click="$ctrl.setValueIncluded(leaf, item.id, false)">\n\t\t\t\t\t\t\t\t<i class="fa fa-fw fa-check-square text-primary"></i>\n\t\t\t\t\t\t\t\t{{item.title}}\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t\t<li ng-repeat="item in $ctrl.spec[leaf.id].enum | uiQueryBuilderFilterSelected:leaf:true track by item.id">\n\t\t\t\t\t\t\t<a ng-click="$ctrl.setValueIncluded(leaf, item.id, true)">\n\t\t\t\t\t\t\t\t<i class="fa fa-fw fa-square-o text-primary"></i>\n\t\t\t\t\t\t\t\t{{item.title}}\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</div>\n\t\t\t\t<div ng-switch-when="boolean" class="btn btn-block btn-3" ng-click="$ctrl.setValue(leaf, !leaf.valueEdit)">\n\t\t\t\t\t<i class="fa fa-fw" ng-class="leaf.valueEdit ? \'fa-check-square-o\' : \'fa-square-o\'"></i>\n\t\t\t\t\t{{leaf.valueEdit ? operandConfig.textTrue : operandConfig.textFalse}}\n\t\t\t\t</div>\n\t\t\t\t<div ng-switch-when="date" class="btn btn-block btn-3">\n\t\t\t\t\t<input ng-model="leaf.valueEdit" ng-change="$ctrl.setValue(leaf)" type="date" class="form-control"/>\n\t\t\t\t</div>\n\t\t\t\t<div ng-switch-default class="btn btn-block btn-3">\n\t\t\t\t\tUnknown operand: <code>{{leaf.valueOperand}}</code>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t</div>\n\t\t\x3c!-- Add button {{{ --\x3e\n\t\t<button ng-click="$ctrl.add()" class="btn btn-add btn-success" type="button"></button>\n\t\t\x3c!-- }}} --\x3e\n\t',controller:["$element","$scope",function(t,e){var n=this;n.operands=[{id:"$eq",setter:function(t){return{$eq:t}},export:function(t){return t.valueEdit},base:{title:"Is",type:"string"},boolean:{title:"Is",type:"boolean",textTrue:"Enabled",textFalse:"Disabled"},date:{title:"Is exactly",type:"date"}},{id:"$ne",setter:function(t){return{$ne:t}},export:function(t){return{$ne:t.valueEdit}},base:{title:"Is not",type:"string"},boolean:{title:"Is not",type:"boolean",textTrue:"Enabled",textFalse:"Disabled"},date:{title:"Is not exactly",type:"date"}},{id:"$in",setter:function(t){return{$in:_.isArray(t)?t.split(/\s*,\s*/):[t]}},export:function(t){return{$in:t.value.$in}},base:{title:"One of",type:"array"}},{id:"$nin",setter:function(t){return{$nin:_.isArray(t)?t.split(/\s*,\s*/):[t]}},export:function(t){return{$nin:t.value.$nin}},base:{title:"Not one of",type:"array"}},{id:"$gt",setter:function(t){return{$gt:t}},export:function(t){return{$gt:t.value.$gt}},base:{title:"Above",type:"number"},date:{title:"Is after",type:"date"}},{id:"$gte",setter:function(t){return{$gte:t}},export:function(t){return{$gte:t.value.$gte}},base:{title:"Above or equals",type:"number"},date:{title:"Is at least",type:"date"}},{id:"$lt",setter:function(t){return{$lt:t}},export:function(t){return{$lt:t.value.$lt}},base:{title:"Below",type:"number"},date:{title:"Is before",type:"date"}},{id:"$lte",setter:function(t){return{$lt:t}},export:function(t){return{$lte:t.value.$lte}},base:{title:"Below or equals",type:"number"},date:{title:"Is at most",type:"date"}},{id:"$exists",setter:function(t){return{$exists:!!t}},export:function(t){return{$exists:t.value.$exists}},base:{title:"Has a value",type:"boolean",textTrue:"Has a value",textFalse:"Has a value"}},{id:"$regexp",setter:function(t){return{$regexp:t}},export:function(t){return{$regexp:t.value.$regexp}},base:{title:"Matches",type:"string"}}],n.operandsByID=_.mapKeys(n.operands,"id"),n.getSpec=function(t,e,i){return n.spec[i]?n.spec[i]:"$and"==t||"$or"==t?_defineProperty({type:"group"},"type",t):_.isString(e)?{type:"string"}:_.isNumber(e)?{type:"number"}:{type:"string"}},n.translateBranch=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[];return _(n.branch).map(function(t,i){return{id:i,value:t,valueEdit:n.getFlatValue(t),valueOperand:_.isObject(t)?_(t).keys().first():"$eq",isMeta:(""+i).startsWith("$")||["sort","skip","limit"].includes(i),spec:n.getSpec(i,t,i),path:e.concat([i])}}).sortBy(function(t){return t.isMeta?"Z"+t.id:"A"+t.id}).value()},n.exportBranch=function(){n.branch=_(n.properties).mapKeys(function(t){return t.id}).mapValues(function(t){return n.operandsByID[t.valueOperand].export(t)}).value()},n.properties,e.$watchGroup(["$ctrl.branch","$ctrl.spec"],function(){n.branch&&n.spec&&(n.properties=n.translateBranch(n.branch))}),n.setField=function(t,e){t.id=e,t.path=[e],t.value=void 0,t.valueEdit=void 0,t.valueOperand="$eq",t.spec=n.spec[e],n.setValue(t)},n.setWrapper=function(t,e){if("$eq"==t.valueOperand&&"$ne"==e)t.valueOperand="$ne",t.valueEdit=n.getFlatValue(t.value),t.value={$ne:t.valueEdit};else if("$ne"==t.valueOperand&&"$eq"==e)t.valueOperand="$eq",t.valueEdit=n.getFlatValue(t.value),t.value={$eq:t.valueEdit};else if("$in"==t.valueOperand&&"$eq"==e)t.valueOperand="$eq",t.value=t.valueEdit=n.getFlatValue(t.value);else if("$eq"!=t.valueOperand&&void 0!==t.valueOperand||"$in"!=e)if("$exists"==e)t.valueOperand="$exists",t.valueEdit=!0,t.value={$exists:t.valueEdit};else{console.log("UNHANDLED TYPE CONVERT:",t.type,"=>",e);var i=n.getFlatValue(t.value);t.valueOperand=e,t.valueEdit=i,t.value=_defineProperty({},t.valueOperand,t.valueEdit)}else t.valueOperand="$in",t.valueEdit=n.getFlatValue(t.value),t.value={$in:[t.valueEdit]};n.exportBranch()},n.setValue=function(t,e){var i=_.isUndefined(e)?t.valueEdit:e;t.value=n.operandsByID[t.valueOperand].setter(i),t.valueEdit=n.getFlatValue(t.value),n.exportBranch()},n.setValueIncluded=function(t,e,n){var i=_(t.value).keys().first();if(!i)throw new Error("Tried to set array inclusion on non wrapped key: "+t.value);var l=t.value[i].includes(e);n&&!l?t.value[i].push(e):!n&&l&&(t.value[i]=t.value[i].filter(function(t){return t!=e})),t.value[i].sort(),t.valueEdit=_.isObject(t.value)&&_.size(t.value)?_(t.value).map().first():t.value},n.getFlatValue=function(t){return _.isString(t)||_.isNumber(t)||_.isBoolean(t)||_.isDate(t)?t:_.isObject(t)&&1==_.size(t)?_(t).values().first():_.isObject(t)&&t.$regexp?"/"+_.trim(t.$regexp,"/")+"/"+t.options:(console.warn("Given up trying to flatten input value",t),t)},n.add=function(){if(!n.properties.some(function(t){return!t.id})){n.properties.push({isMeta:!1});e.$on("uiQueryQueryRepaint",function(){t.find(".query-block > .new").addClass("open")})}},n.remove=function(t){n.properties=n.properties.filter(function(e){return e.id!=t}),n.exportBranch()}}]}).filter("uiQueryBuilderFilterSelected",function(){return function(t,e,n){if(t)return t.filter(function(t){var i=e.valueEdit.includes(t.id);return n?!i:i})}}).directive("ngRepeatEmit",["$rootScope","$timeout",function(t,e){return{restrict:"A",link:function(t,n,i){!0===t.$last&&e(function(){return t.$emit(i.ngRepeatEmit)})}}}]),angular.module("angular-ui-query-builder").provider("qbTableSettings",function(){var t=this;return t.icons={sortNone:"fa fa-fw fa-sort text-muted",sortAsc:"fa fa-fw fa-sort-alpha-asc text-primary",sortDesc:"fa fa-fw fa-sort-alpha-desc text-primary"},t.export={defaults:{format:"xlsx"},formats:[{id:"xlsx",title:"Excel (XLSX)"},{id:"csv",title:"CSV"},{id:"json",title:"JSON"},{id:"html",title:"HTML (display in browser)"}],questions:[]},t.$get=function(){return t},t}).service("qbTableUtilities",function(){return{getSynopsis:function(t){var e=_.keys(t).filter(function(t){return!["sort","skip","limit","select"].includes(t)});return[e.length?e.length+" filters":"All records",t.sort?t.sort.startsWith("-")?"sorted by "+t.sort.substr(1)+" (reverse order)":"sorted by "+t.sort:null,t.limit?"limited to "+t.limit+" rows":null,t.offset?"starting at record "+t.skip:null,t.select?"selecting only "+t.select.length+" columns":null].filter(function(t){return t}).join(", ")},find:function(t,e){var n,i=_.isFunction(e)?e:_.matches(e);return!!function t(e,l){return i(e,l.slice(l.length-1))?(n=l,!0):_.isArray(e)?e.some(function(e,n){return t(e,l.concat(n))}):_.isObject(e)?_.some(e,function(e,n){return t(e,l.concat(n))}):void 0}(t,[])&&n},escapeRegExp:function(t){return String(t).replace(/(\W)/g,"\\$1")},unescapeRegExp:function(t){return String(t).replace(/\\(\W)/g,"$1")}}}).directive("qbTable",function(){return{scope:{qbTable:"=?",stickyThead:"<?",stickyTfoot:"<?"},restrict:"AC",controller:["$attrs","$element","$scope","qbTableSettings",function(t,e,n,i){var l=this;l.query=n.qbTable,l.$broadcast=function(t){for(var e=arguments.length,i=Array(e>1?e-1:0),l=1;l<e;l++)i[l-1]=arguments[l];return n.$broadcast.apply(n,[t].concat(i))},l.$on=function(t,e){return n.$on(t,e)},l.setField=function(t,e){if(void 0!=e)switch(t){case"sort":l.query.sort===e?l.query.sort="-"+e:(l.query.sort,l.query.sort=e);break;default:n.qbTable[t]=e}else delete l.query[t]},e.addClass("qb-table"),n.$watch("stickyThead",function(){return e.toggleClass("qb-sticky-thead",n.stickyThead||""===t.stickyThead)}),n.$watch("stickyTfoot",function(){return e.toggleClass("qb-sticky-tfoot",n.stickyTfoot||""===t.stickyTfoot)})}]}}).directive("qbCol",function(){return{scope:{qbCol:"@",sortable:"@"},require:"^qbTable",restrict:"A",transclude:!0,controller:["$attrs","$element","$scope","qbTableSettings",function(t,e,n,i){var l=this;n.qbTableSettings=i;var r=n.$watchGroup(["qbTable","sortable"],function(){""!==t.sortable||n.qbTable||console.warn("Added qb-col + sortable onto element",e,"but no qb-table query has been assigned on the table element!"),r()});n.canSort=!1,n.isSorted=!1,l.$onInit=function(){n.canSort=n.sortable||""===t.sortable,e.toggleClass("sortable",n.canSort)},n.$watch("qbTable.query.sort",function(t){var e=n.sortable||n.qbCol;t?angular.isArray(t)&&t.some(function(t){return t==e})||t==e?n.isSorted="asc":angular.isArray(t)&&t.some(function(t){return t=="-"+e})||t=="-"+e?n.isSorted="desc":n.isSorted=!1:n.isSorted=!1}),n.toggleSort=function(){n.sortable?n.qbTable.setField("sort",n.sortable):n.qbCol&&""===t.sortable&&n.qbTable.setField("sort",n.qbCol)},e.addClass("qb-col")}],link:function(t,e,n,i){t.qbTable=i},template:'\n\t\t<div class="qb-col-wrapper">\n\t\t\t<ng-transclude></ng-transclude>\n\t\t\t<a ng-if="canSort" ng-click="toggleSort()" class="qb-col-right">\n\t\t\t\t<i class="{{\n\t\t\t\t\tisSorted == \'asc\' ? qbTableSettings.icons.sortAsc\n\t\t\t\t\t: isSorted == \'desc\' ? qbTableSettings.icons.sortDesc\n\t\t\t\t\t: qbTableSettings.icons.sortNone\n\t\t\t\t}}"></i>\n\t\t\t</a>\n\t\t</div>\n\t'}}).directive("qbCell",function(){return{scope:{selector:"=?",onSelect:"&?"},require:"^qbTable",restrict:"A",transclude:!0,controller:["$attrs","$element","$scope","$timeout","qbTableSettings",function(t,e,n,i,l){n.qbTableSettings=l,n.isMeta=e.parents("thead").length>0,n.isMeta&&i(function(){return n.qbTable.$on("qbTableCellSelect",function(){var t=[];n.qbTable.$broadcast("qbTableCellSelectStatus",t),n.metaStatus=t.every(function(t){return t})?"all":t.some(function(t){return t})?"some":"none"})}),n.isSelector="selector"in t,n.$watch("selector",function(){n.isSelector&&e.toggleClass("selector",n.isSelector),n.isSelector&&!n.isMeta&&e.parents("tr").toggleClass("selected",!!n.selector)}),n.isSelector&&!n.isMeta&&e.on("click",function(t){return n.$apply(function(){n.selector=!n.selector,n.onSelect&&n.onSelect({value:n.selector}),n.qbTable.$broadcast("qbTableCellSelect")})}),n.metaSelect=function(t){return n.qbTable.$broadcast("qbTableCellSelectMeta",t)},n.isSelector&&!n.isMeta&&i(function(){n.qbTable.$on("qbTableCellSelectMeta",function(t,e){switch(e){case"all":n.selector=!0;break;case"invert":n.selector=!n.selector;break;case"none":n.selector=!1;break;default:throw new Error("Unknown selection type: "+e)}n.qbTable.$broadcast("qbTableCellSelect")}),n.qbTable.$on("qbTableCellSelectStatus",function(t,e){return e.push(n.selector)})}),e.addClass("qb-cell")}],link:function(t,e,n,i){t.qbTable=i},template:'\n\t\t<ng-transclude></ng-transclude>\n\t\t<div ng-if="isSelector && isMeta" class="btn-group">\n\t\t\t<a class="btn btn-default dropdown-toggle" data-toggle="dropdown">\n\t\t\t\t<i class="fa fa-lg fa-fw" ng-class="metaStatus == \'all\' ? \'fa-check-square-o text-primary\' : metaStatus == \'some\' ? \'fa-minus-square-o\' : \'fa-square-o\'"></i>\n\t\t\t\t<i class="fa fa-caret-down"></i>\n\t\t\t</a>\n\t\t\t<ul class="dropdown-menu">\n\t\t\t\t<li><a ng-click="metaSelect(\'all\')">All</a></li>\n\t\t\t\t<li><a ng-click="metaSelect(\'invert\')">Invert</a></li>\n\t\t\t\t<li><a ng-click="metaSelect(\'none\')">None</a></li>\n\t\t\t</ul>\n\t\t</div>\n\t\t<div ng-if="isSelector && !isMeta">\n\t\t\t<i class="fa fa-lg fa-fw" ng-class="selector ? \'fa-check-square-o\' : \'fa-square-o\'"></i>\n\t\t</div>\n\t'}}).directive("qbPagination",function(){return{scope:{},require:"^qbTable",restrict:"EA",transclude:!0,controller:["$attrs","$scope","qbTableSettings",function(t,e,n){e.qbTableSettings=n,e.canPrev=!0,e.canNext=!0,e.$watchGroup(["qbTable.query.limit","qbTable.query.skip"],function(t){e.canPrev=e.qbTable.query.skip>0,e.canNext=!e.total||e.qbTable.query.skip+e.qbTable.query.limit<e.total}),e.navPageRelative=function(t){if(-1==t)e.qbTable.setField("skip",Math.min((e.qbTable.query.skip||0)-(e.qbTable.query.limit||10),0));else{if(1!=t)throw new Error("Unsupported page move: "+t);e.qbTable.setField("skip",(e.qbTable.query.skip||0)+(e.qbTable.query.limit||10),0)}}}],link:function(t,e,n,i){t.qbTable=i},template:'\n\t\t<nav>\n\t\t\t<ul class="pager">\n\t\t\t\t<li ng-class="canPrev ? \'\' : \'disabled\'" class="previous"><a ng-click="navPageRelative(-1)"><i class="fa fa-arrow-left"></i></a></li>\n\t\t\t\t<ng-transclude class="text-center"></ng-transclude>\n\t\t\t\t<li ng-class="canNext ? \'\' : \'disabled\'" class="next"><a ng-click="navPageRelative(1)"><i class="fa fa-arrow-right"></i></a></li>\n\t\t\t</ul>\n\t\t</nav>\n\t'}}).directive("qbExport",function(){return{scope:{query:"<",spec:"<",url:"@"},transclude:!0,restrict:"EA",controller:["$element","$httpParamSerializer","$scope","$timeout","$window","qbTableSettings","qbTableUtilities",function(t,e,n,i,l,r,a){n.qbTableSettings=r,n.settings={},n.isShowing=!1,n.exportPrompt=function(){n.settings=angular.extend(angular.copy(r.export.defaults),{query:_(n.query).omitBy(function(t,e){return["skip","limit"].includes(e)}).value(),columns:_.map(n.spec,function(t,e){return t.id=e,t.title=_.startCase(e),t.selected=!0,t}),questions:_(r.export.questions).mapKeys(function(t){return t.id}).mapValues(function(t){return t.default}).value()}),t.find(".modal").on("show.bs.modal",function(){return i(function(){return n.isShowing=!0})}).on("hidden.bs.modal",function(){return i(function(){return n.isShowing=!1})}).modal("show")},n.exportExecute=function(){var t=angular.extend(n.settings.query,{select:n.settings.columns.filter(function(t){return t.selected}).map(function(t){return t.id}),format:n.settings.format},n.settings.questions);l.open(n.url+"?"+e(t))},n.querySynopsis,n.$watchGroup(["isShowing","settings.query"],function(){n.isShowing&&(n.querySynopsis=a.getSynopsis(n.settings.query))}),n.columnSynopsis,n.$watchGroup(["isShowing",function(){return _.get(n.settings,"columns",[]).map(function(t){return t.id+"="+t.selected}).join("&")}],function(){n.isShowing&&(n.columnSynopsis=n.settings.columns.filter(function(t){return t.selected}).length+" columns")})}],template:'\n\t\t<div class="modal fade">\n\t\t\t<div class="modal-dialog modal-lg">\n\t\t\t\t<div ng-if="isShowing" class="modal-content">\n\t\t\t\t\t<div class="modal-header">\n\t\t\t\t\t\t<a class="close" data-dismiss="modal"><i class="fa fa-times"></i></a>\n\t\t\t\t\t\t<h4 class="modal-title">Export</h4>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class="modal-body form-horizontal">\n\t\t\t\t\t\t<div class="form-group">\n\t\t\t\t\t\t\t<label class="col-sm-3 control-label">Output format</label>\n\t\t\t\t\t\t\t<div class="col-sm-9">\n\t\t\t\t\t\t\t\t<select ng-model="settings.format" class="form-control">\n\t\t\t\t\t\t\t\t\t<option ng-repeat="format in qbTableSettings.export.formats track by format.id" value="{{format.id}}">{{format.title}}</option>\n\t\t\t\t\t\t\t\t</select>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class="form-group">\n\t\t\t\t\t\t\t<label class="col-sm-3 control-label">Criteria</label>\n\t\t\t\t\t\t\t<div class="col-sm-9">\n\t\t\t\t\t\t\t\t<div class="panel-group" id="qb-export-criteria-{{$id}}">\n\t\t\t\t\t\t\t\t\t<div class="panel panel-default">\n\t\t\t\t\t\t\t\t\t\t<div class="panel-heading">\n\t\t\t\t\t\t\t\t\t\t\t<h4 class="panel-title">\n\t\t\t\t\t\t\t\t\t\t\t\t<a data-toggle="collapse" data-target="#qb-export-criteria-{{$id}}-query" data-parent="#qb-export-criteria-{{$id}}" class="btn-block collapsed">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{{querySynopsis}}\n\t\t\t\t\t\t\t\t\t\t\t\t\t<i class="fa fa-caret-right pull-right"></i>\n\t\t\t\t\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t\t\t\t\t</h4>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t<div id="qb-export-criteria-{{$id}}-query" class="panel-collapse collapse container">\n\t\t\t\t\t\t\t\t\t\t\t<ui-query-builder\n\t\t\t\t\t\t\t\t\t\t\t\tquery="settings.query"\n\t\t\t\t\t\t\t\t\t\t\t\tspec="spec"\n\t\t\t\t\t\t\t\t\t\t\t></ui-query-builder>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class="form-group">\n\t\t\t\t\t\t\t<label class="col-sm-3 control-label">Columns</label>\n\t\t\t\t\t\t\t<div class="col-sm-9">\n\t\t\t\t\t\t\t\t<div class="panel-group" id="qb-export-columns-{{$id}}">\n\t\t\t\t\t\t\t\t\t<div class="panel panel-default">\n\t\t\t\t\t\t\t\t\t\t<div class="panel-heading">\n\t\t\t\t\t\t\t\t\t\t\t<h4 class="panel-title">\n\t\t\t\t\t\t\t\t\t\t\t\t<a data-toggle="collapse" data-target="#qb-export-columns-{{$id}}-columns" data-parent="#qb-export-columns-{{$id}}" class="btn-block collapsed">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{{columnSynopsis}}\n\t\t\t\t\t\t\t\t\t\t\t\t\t<i class="fa fa-caret-right pull-right"></i>\n\t\t\t\t\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t\t\t\t\t</h4>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t<div id="qb-export-columns-{{$id}}-columns" class="panel-collapse collapse row">\n\t\t\t\t\t\t\t\t\t\t\t<div class="col-xs-12">\n\t\t\t\t\t\t\t\t\t\t\t\t<table qb-table class="table table-hover">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<thead>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<th qb-cell selector></th>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<th>Column</th>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</thead>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<tr ng-repeat="col in settings.columns track by col.id">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<td qb-cell selector="col.selected"></td>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<td>{{col.title}}</td>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t\t\t\t\t\t\t</table>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div ng-repeat="question in qbTableSettings.export.questions track by question.id" class="form-group">\n\t\t\t\t\t\t\t<label class="col-sm-3 control-label">{{question.title}}</label>\n\t\t\t\t\t\t\t<div ng-switch="question.type" class="col-sm-9">\n\t\t\t\t\t\t\t\t<div ng-switch-when="text">\n\t\t\t\t\t\t\t\t\t<input type="text" ng-model="settings.questions[question.id]" class="form-control"/>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<div ng-switch-default>\n\t\t\t\t\t\t\t\t\t<div class="alert alert-danger">\n\t\t\t\t\t\t\t\t\t\tUnknown question type: "{{question.type}}"\n\t\t\t\t\t\t\t\t\t\t<pre>{{question | json}}</pre>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<div ng-if="question.help" class="help-block">{{question.help}}</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class="modal-footer">\n\t\t\t\t\t\t<div class="pull-left">\n\t\t\t\t\t\t\t<a class="btn btn-danger" data-dismiss="modal">Cancel</a>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class="pull-right">\n\t\t\t\t\t\t\t<a ng-click="exportExecute()" class="btn btn-primary" data-dismiss="modal">Export</a>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t\t<ng-transclude>\n\t\t\t<a ng-click="exportPrompt()" class="btn btn-default">Export...</a>\n\t\t</ng-transclude>\n\t'}}).directive("qbModal",function(){return{scope:{query:"=",spec:"<",title:"@?",onRefresh:"&?",binding:"@?"},transclude:!0,restrict:"A",controller:["$element","$scope",function(t,e){var n=this;n.isShown=!1,n.rebind=function(){t.one("click",function(){t.find(".qb-modal").one("hide.bs.modal",function(){n.isShown=!1}).one("hidden.bs.modal",function(){n.rebind()}).modal("show")})},e.submit=function(){angular.isFunction(n.onRefresh)&&n.onRefresh({query:e.queryCopy,spec:e.spec}),e.binding&&"complete"!=e.binding||(e.query=e.queryCopy),t.find(".qb-modal").modal("hide")},n.$onInit=function(){e.queryCopy="live"==e.binding?e.query:angular.copy(e.query)},n.rebind()}],template:'\n\t\t<ng-transclude></ng-transclude>\n\t\t<div class="qb-modal modal fade">\n\t\t\t<div class="modal-dialog modal-lg">\n\t\t\t\t<div class="modal-content">\n\t\t\t\t\t<div class="modal-header">\n\t\t\t\t\t\t<a class="close" data-dismiss="modal"><i class="fa fa-times"></i></a>\n\t\t\t\t\t\t<h4 class="modal-title">{{title || \'Edit Filter\'}}</h4>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class="modal-body">\n\t\t\t\t\t\t<ui-query-builder\n\t\t\t\t\t\t\tquery="queryCopy"\n\t\t\t\t\t\t\tspec="spec"\n\t\t\t\t\t\t></ui-query-builder>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class="modal-footer">\n\t\t\t\t\t\t<div class="pull-left">\n\t\t\t\t\t\t\t<a class="btn btn-danger" data-dismiss="modal">Cancel</a>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class="pull-right">\n\t\t\t\t\t\t\t<a ng-click="submit()" class="btn btn-success">Refresh</a>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t'}}).directive("qbSearch",function(){return{scope:{query:"=",spec:"<",onRefresh:"&?",useIndexes:"@?"},restrict:"AE",transclude:!0,controller:["$scope","$rootScope","$timeout","qbTableUtilities",function(t,e,n,i){var l=this;t.search="",t.submit=function(){if(!t.search)return t.clear();var n={$comment:"search",$or:_(t.spec).pickBy(function(t){return"string"==t.type}).mapValues(function(e,n){return[{$regexp:i.escapeRegExp(t.search),options:"i"}]}).value()},r=i.find(t.query,{$comment:"search"}),a=angular.copy(t.query);if(r&&_.isEqual(r,["$comment"]))a=n;else if(r&&"$and"==r[0])_.set(a,r,n);else if(_.isEqual(_.keys(a),["$and"]))a.$and.push(n);else if(_.isObject(a)){var s=l.useIndexes||"auto";"auto"==s&&(s=_.keys(t.spec).some(function(e){return"_id"!=e&&t.spec[e].index})?"stringIndexed":"string"),a.$or=_(t.spec).pickBy(function(t,e){if("_id"==e)return!1;switch(s){case"all":return!0;case"string":return"string"==t.type;case"stringIndexed":return"string"==t.type&&t.index;default:throw new Error('Unknown field selection method: "'+s+'"')}}).map(function(e,n){return _defineProperty({},n,{$regexp:i.escapeRegExp(t.search),options:"i"})}).value()}else console.warn("Unable to place search query",n,"within complex query",a);e.$broadcast("queryBuilder.change",a),angular.isFunction(l.onRefresh)&&l.onRefresh({query:a}),("complete"==l.binding||angular.isUndefined(l.binding))&&(t.query=a)},t.clear=function(){var e=i.find(t.query,{$comment:"search"});e&&_.isEqual(e,["$comment"])?t.query={}:e&&"$and"==e[0]?t.query=t.query.$and.find(function(t,e){return"search"!=t.$comment}):e?_.unset(t.query,e):console.warn("Unable to clear search query within complex query",t.query)},t.check=function(){try{t.search=_.chain(t.query).get("$or").first().values().first().get("$regexp").thru(function(t){return i.unescapeRegExp(t||"")}).value()}catch(e){t.search=""}},l.$onInit=function(){return t.check()}}],template:'\n\t\t<ng-transclude>\n\t\t\t<form ng-submit="submit()" class="form-inline">\n\t\t\t\t<div class="form-group">\n\t\t\t\t\t<div class="input-group">\n\t\t\t\t\t\t<input ng-blur="submit()" type="text" ng-model="search" class="form-control"/>\n\t\t\t\t\t\t<a ng-click="submit()" class="btn btn-default input-group-addon">\n\t\t\t\t\t\t\t<i class="fa fa-search"/>\n\t\t\t\t\t\t</a>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</ng-transclude>\n\t'}});
"use strict";function _defineProperty(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};angular.module("angular-ui-query-builder",[]).service("QueryBuilder",function(){var t=this;t.cleanSpec=function(t){return _(t).mapValues(function(t,e){return{type:t.type,enum:_(t.enum).map(function(t){return _.isString(t)?{id:t,title:_.startCase(t)}:t}).sortBy("title").value()}}).value()},t.metaProperties={limit:{type:"keyVal",actions:[{id:"$eq",title:"Equals"}],action:"$eq",canDelete:!0},populate:{type:"hidden"},skip:{type:"keyVal",actions:[{id:"$eq",title:"Equals"}],action:"$eq",canDelete:!0},sort:{type:"keyVal",actions:[{id:"$eq",title:"Equals"}],action:"$eq",canDelete:!1}},t.queryToArray=function(e,n){var l=[{id:"$eq",title:"Equals"},{id:"$neq",title:"Doesnt equal"},{id:"$lt",title:"Is less than"},{id:"$lte",title:"Is equal to or less than"},{id:"$gt",title:"Is greater than"},{id:"$gte",title:"Is equal or greater than"},{id:"$in",title:"Is one of"},{id:"$nin",title:"Is not one of"},{id:"$exists",title:"Has a value"},{id:"$nexists",title:"Does not have a value"}];return _(e).pickBy(function(e,l){var i=n[l]||"$and"==l||"$or"==l||t.metaProperties[l];return i||console.warn("query-builder","Incomming query path",l,"Does not map to anything in spec",n),!!i}).map(function(e,i){var r=n[i],a=_.isObject(e)&&_(e).keys().first(),s=_.isObject(e)?_(e).values().first():e;return"$or"==i&&e.every(function(t){return _.isObject(t)&&1==_.keys(t).length})&&e.map(function(t){return _.chain(t).first().values().first().keys().find(function(t){return"$regexp"==t}).value()}).length==e.length?{path:i,type:"search",title:"Search",value:_.chain(e).first().values().first().get("$regexp").value(),fields:_(e).map(function(t){return _.keys(t)}).flatten().value(),actions:l}:"$and"==i||"$or"==i?(_.isArray(e)||(console.warn("query-builder","Query path",i,"is a meta key",e,"but is not an array!","Given",void 0===e?"undefined":_typeof(e)),e=[]),{path:i,type:"binaryGroup",title:"$and"==i?"AND":"$or"==i?"OR":"UNKNOWN",condition:i.replace(/\$/,""),children:e.map(function(e){return t.queryToArray(e,n)}),actions:l}):t.metaProperties[i]?Object.assign({path:i,title:_.startCase(i),value:e,type:"hidden",action:"$hidden",actions:l},t.metaProperties[i]):"$exists"==a?{path:i,title:e.title||_.startCase(i),value:!!e,type:"exists",action:"$exists",actions:l}:"string"==r.type&&_.isArray(r.enum)&&r.enum.length?{path:i,title:e.title||_.startCase(i),type:"enum",action:e.$in?"$in":e.$nin?"$nin":r.enum.length?"$in":"$eq",enum:r.enum,value:e.$in?e.$in:e.$nin?e.$nin:r.enum.length&&!_.isArray(e)?[e]:e,actions:l}:{path:i,title:e.title||_.startCase(i),type:"string"==r.type?"string":"number"==r.type?"number":"date"==r.type?"date":"string",action:"$eq",value:"date"==r.type?moment(s).toDate():s,actions:l}}).value()},t.arrayToQuery=function(t){return function(t){return _(t).mapKeys(function(t){return t.path}).mapValues(function(t){switch(t.type){case"string":case"number":case"date":return"$eq"==t.action?t.value:_defineProperty({},t.action,t.value);case"enum":return _defineProperty({},t.action,t.value);case"exists":return{$exists:"$exists"==t.action};case"search":return t.fields.map(function(e){return _defineProperty({},e,{$regexp:t.value,options:"i"})});case"keyVal":case"hidden":return t.value;default:console.warn("Unknown type to convert:",t.type)}}).value()}(t)}}).component("uiQueryBuilder",{bindings:{query:"=",spec:"<"},template:'\n\t\t<div class="ui-query-builder">\n\t\t\t<div class="query-container">\n\t\t\t\t<ui-query-builder-group\n\t\t\t\t\tqb-group="$ctrl.qbQuery"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-group>\n\t\t\t</div>\n\t\t</div>\n\t',controller:["$scope","$timeout","QueryBuilder",function(t,e,n){var l=this;l.qbSpec,l.qbQuery;var i=t.$watchGroup(["$ctrl.query","$ctrl.spec"],function(){l.spec&&l.query&&(l.qbSpec=n.cleanSpec(l.spec),l.qbQuery=n.queryToArray(l.query,l.qbSpec),i())});t.$on("queryBuilder.change",function(t,i){return e(function(){i&&(l.query=i,l.qbQuery=n.queryToArray(l.query,l.qbSpec)),l.query=n.arrayToQuery(l.qbQuery)})}),t.$on("queryBuilder.pathAction.drop",function(t,e){l.qbQuery=l.qbQuery.filter(function(t){return t.path!=e}),l.query=n.arrayToQuery(l.qbQuery)}),t.$on("queryBuilder.pathAction.swap",function(e,i,r){l.qbQuery=l.qbQuery.filter(function(t){return t.path!=i}),l.query=n.arrayToQuery(l.qbQuery),t.$emit("queryBuilder.pathAction.add",r)}),t.$on("queryBuilder.pathAction.add",function(t,e){l.query[e]="",l.qbQuery=n.queryToArray(l.query,l.qbSpec),l.query=n.arrayToQuery(l.qbQuery)})}]}).component("uiQueryBuilderGroup",{bindings:{qbGroup:"=",qbSpec:"<"},template:'\n\t\t<div ng-repeat="row in $ctrl.qbGroup | filter:$ctrl.qbGroupFilter" meta-key="{{row.path}}">\n\t\t\t<ui-query-builder-row\n\t\t\t\tqb-item="row"\n\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t></ui-query-builder-row>\n\t\t</div>\n\t\t<div class="query-row">\n\t\t\t<div class="query-container">\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<button ng-click="$ctrl.add()" type="button" class="btn-add"></button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t',controller:["$scope","QueryBuilder",function(t,e){this.qbGroupFilter=function(t){return"hidden"!=t.type}}]}).component("uiQueryBuilderRow",{bindings:{qbItem:"=",qbSpec:"<"},controller:["$scope","QueryBuilder",function(t,e){var n=this;n.delete=function(e){return t.$emit("queryBuilder.pathAction.drop",e)},n.setChanged=function(){return t.$emit("queryBuilder.change")}}],template:'\n\t\t<div ng-switch="$ctrl.qbItem.type">\n\t\t\t\x3c!-- $and / $or condition {{{ --\x3e\n\t\t\t<div ng-switch-when="binaryGroup" class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-1 btn-block">\n\t\t\t\t\t\t{{$ctrl.qbItem.title}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div ng-repeat="conditional in $ctrl.qbItem.children" class="query-container clearfix">\n\t\t\t\t\t<ui-query-builder-group\n\t\t\t\t\t\tqb-group="conditional"\n\t\t\t\t\t\tqb-spec="$ctrl.spec"\n\t\t\t\t\t></ui-query-builder-group>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- String {{{ --\x3e\n\t\t\t<div ng-switch-when="string" class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-path\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\tselected="$ctrl.qbItem.path"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-path>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-3 btn-block">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tng-model="$ctrl.qbItem.value"\n\t\t\t\t\t\t\tng-change="$ctrl.setChanged()"\n\t\t\t\t\t\t\ttype="text"\n\t\t\t\t\t\t\tclass="form-control"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Enum {{{ --\x3e\n\t\t\t<div ng-switch-when="enum" class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-path\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\tselected="$ctrl.qbItem.path"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-path>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t\t<ui-query-builder-block-menu-multiple\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="3"\n\t\t\t\t\tselected="$ctrl.qbItem.value"\n\t\t\t\t\toptions="$ctrl.qbItem.enum"\n\t\t\t\t></ui-query-builder-block-menu-multiple>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Date {{{ --\x3e\n\t\t\t<div ng-switch-when="date" class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-path\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\tselected="$ctrl.qbItem.path"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-path>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-3 btn-block">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tng-model="$ctrl.qbItem.value"\n\t\t\t\t\t\t\tng-change="$ctrl.setChanged()"\n\t\t\t\t\t\t\ttype="date"\n\t\t\t\t\t\t\tclass="form-control"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Number {{{ --\x3e\n\t\t\t<div ng-switch-when="number" class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-path\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\tselected="$ctrl.qbItem.path"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-path>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-3 btn-block">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tng-value="$ctrl.qbItem.value"\n\t\t\t\t\t\t\tng-changed="$ctrl.setChanged()"\n\t\t\t\t\t\t\ttype="number"\n\t\t\t\t\t\t\tclass="form-control"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Exists {{{ --\x3e\n\t\t\t<div ng-switch-when="exists" class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-path\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\tselected="$ctrl.qbItem.path"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-path>\n\t\t\t\t<ui-query-builder-block-menu\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="2"\n\t\t\t\t\tselected="$ctrl.qbItem.action"\n\t\t\t\t\toptions="$ctrl.qbItem.actions"\n\t\t\t\t></ui-query-builder-block-menu>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Search {{{ --\x3e\n\t\t\t<div ng-switch-when="search" class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-path\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\tselected="$ctrl.qbItem.path"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-path>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-2 btn-block">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tng-model="$ctrl.qbItem.value"\n\t\t\t\t\t\t\tng-change="$ctrl.setChanged()"\n\t\t\t\t\t\t\ttype="text"\n\t\t\t\t\t\t\tclass="form-control"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- keyVal (Only title + value) {{{ --\x3e\n\t\t\t<div ng-switch-when="keyVal" class="query-row">\n\t\t\t\t<a ng-if="$ctrl.qbItem.canDelete === undefined || $ctrl.qbItem.canDelete" ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-block\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\ttitle="$ctrl.qbItem.title"\n\t\t\t\t></ui-query-builder-block>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-2 btn-block">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tng-model="$ctrl.qbItem.value"\n\t\t\t\t\t\t\tng-change="$ctrl.setChanged()"\n\t\t\t\t\t\t\ttype="text"\n\t\t\t\t\t\t\tclass="form-control"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Unknown {{{ --\x3e\n\t\t\t<div ng-switch-default class="query-row">\n\t\t\t\t<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>\n\t\t\t\t<ui-query-builder-path\n\t\t\t\t\tclass="query-block"\n\t\t\t\t\tlevel="1"\n\t\t\t\t\tselected="$ctrl.qbItem.path"\n\t\t\t\t\tqb-spec="$ctrl.qbSpec"\n\t\t\t\t></ui-query-builder-path>\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn btn-warning btn-block">\n\t\t\t\t\t\tUnknown handler: {{$ctrl.qbItem.type}}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t</div>\n\t'}).component("uiQueryBuilderPath",{bindings:{level:"<",selected:"<",qbSpec:"<"},controller:["$scope",function(t){var e=this;e.setSelected=function(n){return t.$emit("queryBuilder.pathAction.swap",e.selected,n)},e.options,e.$onInit=function(){e.options=_.map(e.qbSpec,function(t,e){return Object.assign({},{path:e,title:_.startCase(e)},t)}),e.selectedOption=e.options.find(function(t){return t.path==e.selected})}}],template:'\n\t\t<a class="btn btn-block btn-{{$ctrl.level}} dropdown-toggle" data-toggle="dropdown">\n\t\t\t{{$ctrl.selectedOption.title}}\n\t\t\t<i class="fa fa-caret-down"></i>\n\t\t</a>\n\t\t<ul class="dropdown-menu pull-right">\n\t\t\t<li ng-repeat="path in $ctrl.options track by path.path"><a ng-click="$ctrl.setSelected(path.path)">{{path.title}}</a></li>\n\t\t</ul>\n\t'}).component("uiQueryBuilderBlock",{bindings:{level:"<",title:"<"},controller:["$scope",function(t){}],template:'\n\t\t<a class="btn btn-block btn-{{$ctrl.level}}">\n\t\t\t{{$ctrl.title}}\n\t\t</a>\n\t'}).component("uiQueryBuilderBlockMenu",{bindings:{level:"<",options:"<",selected:"="},controller:["$scope",function(t){var e=this;e.setSelected=function(n){e.selected=n.id,t.$emit("queryBuilder.change")},e.selectedOption,t.$watchGroup(["$ctrl.options","$ctrl.selected"],function(){e.selectedOption=e.options.find(function(t){return t.id==e.selected})})}],template:'\n\t\t<a class="btn btn-block btn-{{$ctrl.level}} dropdown-toggle" data-toggle="dropdown">\n\t\t\t{{$ctrl.selectedOption.title}}\n\t\t\t<i class="fa fa-caret-down"></i>\n\t\t</a>\n\t\t<ul class="dropdown-menu pull-right">\n\t\t\t<li ng-repeat="option in $ctrl.options track by option.id"><a ng-click="$ctrl.setSelected(option)">{{option.title}}</a></li>\n\t\t</ul>\n\t'}).component("uiQueryBuilderBlockMenuMultiple",{bindings:{level:"<",options:"<",selected:"="},controller:["$scope",function(t){var e=this;e.toggle=function(n){e.selected||(e.selected=[]),e.selected.includes(n.id)?e.selected=e.selected.filter(function(t){return t!=n.id}):e.selected.push(n.id),t.$emit("queryBuilder.change")},e.selectedOptions,t.$watch("$ctrl.selected",function(){e.selectedOptions=e.options.filter(function(t){return(e.selected||[]).includes(t.id)}),e.options.forEach(function(t){return t.selected=e.selectedOptions.some(function(e){return e.id==t.id})})},!0)}],template:'\n\t\t<a class="btn btn-block btn-{{$ctrl.level}} dropdown-toggle" data-toggle="dropdown">\n\t\t\t<span ng-repeat="item in $ctrl.selectedOptions track by item.id" class="pill">\n\t\t\t\t{{item.title}}\n\t\t\t</span>\n\t\t\t<i class="fa fa-caret-down"></i></a>\n\t\t</a>\n\t\t<ul class="dropdown-menu pull-right">\n\t\t\t<li ng-repeat="option in $ctrl.options track by option.id">\n\t\t\t\t<a ng-click="$ctrl.toggle(option)">\n\t\t\t\t\t<i class="fa fa-fw" ng-class="option.selected ? \'fa-check-square-o\' : \'fa-square-o\'"></i>\n\t\t\t\t\t{{option.title}}\n\t\t\t\t</a>\n\t\t\t</li>\n\t\t</ul>\n\t'}),angular.module("angular-ui-query-builder").provider("qbTableSettings",function(){var t=this;return t.icons={sortNone:"fa fa-fw fa-sort text-muted",sortAsc:"fa fa-fw fa-sort-alpha-asc text-primary",sortDesc:"fa fa-fw fa-sort-alpha-desc text-primary"},t.export={defaults:{format:"xlsx"},formats:[{id:"xlsx",title:"Excel (XLSX)"},{id:"csv",title:"CSV"},{id:"json",title:"JSON"},{id:"html",title:"HTML (display in browser)"}],questions:[]},t.$get=function(){return t},t}).service("qbTableUtilities",function(){return{getSynopsis:function(t){var e=_.keys(t).filter(function(t){return!["sort","skip","limit","select"].includes(t)});return[e.length?e.length+" filters":"All records",t.sort?t.sort.startsWith("-")?"sorted by "+t.sort.substr(1)+" (reverse order)":"sorted by "+t.sort:null,t.limit?"limited to "+t.limit+" rows":null,t.offset?"starting at record "+t.skip:null,t.select?"selecting only "+t.select.length+" columns":null].filter(function(t){return t}).join(", ")},find:function(t,e){var n,l=_.isFunction(e)?e:_.matches(e);return!!function t(e,i){return l(e,i.slice(i.length-1))?(n=i,!0):_.isArray(e)?e.some(function(e,n){return t(e,i.concat(n))}):_.isObject(e)?_.some(e,function(e,n){return t(e,i.concat(n))}):void 0}(t,[])&&n},escapeRegExp:function(t){return String(t).replace(/(\W)/g,"\\$1")},unescapeRegExp:function(t){return String(t).replace(/\\(\W)/g,"$1")}}}).directive("qbTable",function(){return{scope:{qbTable:"=?",stickyThead:"<?",stickyTfoot:"<?"},restrict:"AC",controller:["$attrs","$element","$scope","qbTableSettings",function(t,e,n,l){var i=this;i.query=n.qbTable,i.$broadcast=function(t){for(var e=arguments.length,l=Array(e>1?e-1:0),i=1;i<e;i++)l[i-1]=arguments[i];return n.$broadcast.apply(n,[t].concat(l))},i.$on=function(t,e){return n.$on(t,e)},i.setField=function(t,e){if(void 0!=e)switch(t){case"sort":i.query.sort===e?i.query.sort="-"+e:(i.query.sort,i.query.sort=e);break;default:n.qbTable[t]=e}else delete i.query[t]},e.addClass("qb-table"),n.$watch("stickyThead",function(){return e.toggleClass("qb-sticky-thead",n.stickyThead||""===t.stickyThead)}),n.$watch("stickyTfoot",function(){return e.toggleClass("qb-sticky-tfoot",n.stickyTfoot||""===t.stickyTfoot)})}]}}).directive("qbCol",function(){return{scope:{qbCol:"@",sortable:"@"},require:"^qbTable",restrict:"A",transclude:!0,controller:["$attrs","$element","$scope","qbTableSettings",function(t,e,n,l){var i=this;n.qbTableSettings=l;var r=n.$watchGroup(["qbTable","sortable"],function(){""!==t.sortable||n.qbTable||console.warn("Added qb-col + sortable onto element",e,"but no qb-table query has been assigned on the table element!"),r()});n.canSort=!1,n.isSorted=!1,i.$onInit=function(){n.canSort=n.sortable||""===t.sortable,e.toggleClass("sortable",n.canSort)},n.$watch("qbTable.query.sort",function(t){var e=n.sortable||n.qbCol;t?angular.isArray(t)&&t.some(function(t){return t==e})||t==e?n.isSorted="asc":angular.isArray(t)&&t.some(function(t){return t=="-"+e})||t=="-"+e?n.isSorted="desc":n.isSorted=!1:n.isSorted=!1}),n.toggleSort=function(){n.sortable?n.qbTable.setField("sort",n.sortable):n.qbCol&&""===t.sortable&&n.qbTable.setField("sort",n.qbCol)},e.addClass("qb-col")}],link:function(t,e,n,l){t.qbTable=l},template:'\n\t\t<div class="qb-col-wrapper">\n\t\t\t<ng-transclude></ng-transclude>\n\t\t\t<a ng-if="canSort" ng-click="toggleSort()" class="qb-col-right">\n\t\t\t\t<i class="{{\n\t\t\t\t\tisSorted == \'asc\' ? qbTableSettings.icons.sortAsc\n\t\t\t\t\t: isSorted == \'desc\' ? qbTableSettings.icons.sortDesc\n\t\t\t\t\t: qbTableSettings.icons.sortNone\n\t\t\t\t}}"></i>\n\t\t\t</a>\n\t\t</div>\n\t'}}).directive("qbCell",function(){return{scope:{selector:"=?",onSelect:"&?"},require:"^qbTable",restrict:"A",transclude:!0,controller:["$attrs","$element","$scope","$timeout","qbTableSettings",function(t,e,n,l,i){n.qbTableSettings=i,n.isMeta=e.parents("thead").length>0,n.isMeta&&l(function(){return n.qbTable.$on("qbTableCellSelect",function(){var t=[];n.qbTable.$broadcast("qbTableCellSelectStatus",t),n.metaStatus=t.every(function(t){return t})?"all":t.some(function(t){return t})?"some":"none"})}),n.isSelector="selector"in t,n.$watch("selector",function(){n.isSelector&&e.toggleClass("selector",n.isSelector),n.isSelector&&!n.isMeta&&e.parents("tr").toggleClass("selected",!!n.selector)}),n.isSelector&&!n.isMeta&&e.on("click",function(t){return n.$apply(function(){n.selector=!n.selector,n.onSelect&&n.onSelect({value:n.selector}),n.qbTable.$broadcast("qbTableCellSelect")})}),n.metaSelect=function(t){return n.qbTable.$broadcast("qbTableCellSelectMeta",t)},n.isSelector&&!n.isMeta&&l(function(){n.qbTable.$on("qbTableCellSelectMeta",function(t,e){switch(e){case"all":n.selector=!0;break;case"invert":n.selector=!n.selector;break;case"none":n.selector=!1;break;default:throw new Error("Unknown selection type: "+e)}n.qbTable.$broadcast("qbTableCellSelect")}),n.qbTable.$on("qbTableCellSelectStatus",function(t,e){return e.push(n.selector)})}),e.addClass("qb-cell")}],link:function(t,e,n,l){t.qbTable=l},template:'\n\t\t<ng-transclude></ng-transclude>\n\t\t<div ng-if="isSelector && isMeta" class="btn-group">\n\t\t\t<a class="btn btn-default dropdown-toggle" data-toggle="dropdown">\n\t\t\t\t<i class="fa fa-lg fa-fw" ng-class="metaStatus == \'all\' ? \'fa-check-square-o text-primary\' : metaStatus == \'some\' ? \'fa-minus-square-o\' : \'fa-square-o\'"></i>\n\t\t\t\t<i class="fa fa-caret-down"></i>\n\t\t\t</a>\n\t\t\t<ul class="dropdown-menu">\n\t\t\t\t<li><a ng-click="metaSelect(\'all\')">All</a></li>\n\t\t\t\t<li><a ng-click="metaSelect(\'invert\')">Invert</a></li>\n\t\t\t\t<li><a ng-click="metaSelect(\'none\')">None</a></li>\n\t\t\t</ul>\n\t\t</div>\n\t\t<div ng-if="isSelector && !isMeta">\n\t\t\t<i class="fa fa-lg fa-fw" ng-class="selector ? \'fa-check-square-o\' : \'fa-square-o\'"></i>\n\t\t</div>\n\t'}}).directive("qbPagination",function(){return{scope:{},require:"^qbTable",restrict:"EA",transclude:!0,controller:["$attrs","$scope","qbTableSettings",function(t,e,n){e.qbTableSettings=n,e.canPrev=!0,e.canNext=!0,e.$watchGroup(["qbTable.query.limit","qbTable.query.skip"],function(t){e.canPrev=e.qbTable.query.skip>0,e.canNext=!e.total||e.qbTable.query.skip+e.qbTable.query.limit<e.total}),e.navPageRelative=function(t){if(-1==t)e.qbTable.setField("skip",Math.min((e.qbTable.query.skip||0)-(e.qbTable.query.limit||10),0));else{if(1!=t)throw new Error("Unsupported page move: "+t);e.qbTable.setField("skip",(e.qbTable.query.skip||0)+(e.qbTable.query.limit||10),0)}}}],link:function(t,e,n,l){t.qbTable=l},template:'\n\t\t<nav>\n\t\t\t<ul class="pager">\n\t\t\t\t<li ng-class="canPrev ? \'\' : \'disabled\'" class="previous"><a ng-click="navPageRelative(-1)"><i class="fa fa-arrow-left"></i></a></li>\n\t\t\t\t<ng-transclude class="text-center"></ng-transclude>\n\t\t\t\t<li ng-class="canNext ? \'\' : \'disabled\'" class="next"><a ng-click="navPageRelative(1)"><i class="fa fa-arrow-right"></i></a></li>\n\t\t\t</ul>\n\t\t</nav>\n\t'}}).directive("qbExport",function(){return{scope:{query:"<",spec:"<",url:"@"},transclude:!0,restrict:"EA",controller:["$element","$httpParamSerializer","$scope","$timeout","$window","qbTableSettings","qbTableUtilities",function(t,e,n,l,i,r,a){n.qbTableSettings=r,n.settings={},n.isShowing=!1,n.exportPrompt=function(){n.settings=angular.extend(angular.copy(r.export.defaults),{query:_(n.query).omitBy(function(t,e){return["skip","limit"].includes(e)}).value(),columns:_.map(n.spec,function(t,e){return t.id=e,t.title=_.startCase(e),t.selected=!0,t}),questions:_(r.export.questions).mapKeys(function(t){return t.id}).mapValues(function(t){return t.default}).value()}),t.find(".modal").on("show.bs.modal",function(){return l(function(){return n.isShowing=!0})}).on("hidden.bs.modal",function(){return l(function(){return n.isShowing=!1})}).modal("show")},n.exportExecute=function(){var t=angular.extend(n.settings.query,{select:n.settings.columns.filter(function(t){return t.selected}).map(function(t){return t.id}),format:n.settings.format},n.settings.questions);i.open(n.url+"?"+e(t))},n.querySynopsis,n.$watchGroup(["isShowing","settings.query"],function(){n.isShowing&&(n.querySynopsis=a.getSynopsis(n.settings.query))}),n.columnSynopsis,n.$watchGroup(["isShowing",function(){return _.get(n.settings,"columns",[]).map(function(t){return t.id+"="+t.selected}).join("&")}],function(){n.isShowing&&(n.columnSynopsis=n.settings.columns.filter(function(t){return t.selected}).length+" columns")})}],template:'\n\t\t<div class="modal fade">\n\t\t\t<div class="modal-dialog modal-lg">\n\t\t\t\t<div ng-if="isShowing" class="modal-content">\n\t\t\t\t\t<div class="modal-header">\n\t\t\t\t\t\t<a class="close" data-dismiss="modal"><i class="fa fa-times"></i></a>\n\t\t\t\t\t\t<h4 class="modal-title">Export</h4>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class="modal-body form-horizontal">\n\t\t\t\t\t\t<div class="form-group">\n\t\t\t\t\t\t\t<label class="col-sm-3 control-label">Output format</label>\n\t\t\t\t\t\t\t<div class="col-sm-9">\n\t\t\t\t\t\t\t\t<select ng-model="settings.format" class="form-control">\n\t\t\t\t\t\t\t\t\t<option ng-repeat="format in qbTableSettings.export.formats track by format.id" value="{{format.id}}">{{format.title}}</option>\n\t\t\t\t\t\t\t\t</select>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class="form-group">\n\t\t\t\t\t\t\t<label class="col-sm-3 control-label">Criteria</label>\n\t\t\t\t\t\t\t<div class="col-sm-9">\n\t\t\t\t\t\t\t\t<div class="panel-group" id="qb-export-criteria-{{$id}}">\n\t\t\t\t\t\t\t\t\t<div class="panel panel-default">\n\t\t\t\t\t\t\t\t\t\t<div class="panel-heading">\n\t\t\t\t\t\t\t\t\t\t\t<h4 class="panel-title">\n\t\t\t\t\t\t\t\t\t\t\t\t<a data-toggle="collapse" data-target="#qb-export-criteria-{{$id}}-query" data-parent="#qb-export-criteria-{{$id}}" class="btn-block collapsed">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{{querySynopsis}}\n\t\t\t\t\t\t\t\t\t\t\t\t\t<i class="fa fa-caret-right pull-right"></i>\n\t\t\t\t\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t\t\t\t\t</h4>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t<div id="qb-export-criteria-{{$id}}-query" class="panel-collapse collapse container">\n\t\t\t\t\t\t\t\t\t\t\t<ui-query-builder\n\t\t\t\t\t\t\t\t\t\t\t\tquery="settings.query"\n\t\t\t\t\t\t\t\t\t\t\t\tspec="spec"\n\t\t\t\t\t\t\t\t\t\t\t></ui-query-builder>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class="form-group">\n\t\t\t\t\t\t\t<label class="col-sm-3 control-label">Columns</label>\n\t\t\t\t\t\t\t<div class="col-sm-9">\n\t\t\t\t\t\t\t\t<div class="panel-group" id="qb-export-columns-{{$id}}">\n\t\t\t\t\t\t\t\t\t<div class="panel panel-default">\n\t\t\t\t\t\t\t\t\t\t<div class="panel-heading">\n\t\t\t\t\t\t\t\t\t\t\t<h4 class="panel-title">\n\t\t\t\t\t\t\t\t\t\t\t\t<a data-toggle="collapse" data-target="#qb-export-columns-{{$id}}-columns" data-parent="#qb-export-columns-{{$id}}" class="btn-block collapsed">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{{columnSynopsis}}\n\t\t\t\t\t\t\t\t\t\t\t\t\t<i class="fa fa-caret-right pull-right"></i>\n\t\t\t\t\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t\t\t\t\t</h4>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t<div id="qb-export-columns-{{$id}}-columns" class="panel-collapse collapse row">\n\t\t\t\t\t\t\t\t\t\t\t<div class="col-xs-12">\n\t\t\t\t\t\t\t\t\t\t\t\t<table qb-table class="table table-hover">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<thead>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<th qb-cell selector></th>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<th>Column</th>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</thead>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<tr ng-repeat="col in settings.columns track by col.id">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<td qb-cell selector="col.selected"></td>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<td>{{col.title}}</td>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t\t\t\t\t\t\t</table>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div ng-repeat="question in qbTableSettings.export.questions track by question.id" class="form-group">\n\t\t\t\t\t\t\t<label class="col-sm-3 control-label">{{question.title}}</label>\n\t\t\t\t\t\t\t<div ng-switch="question.type" class="col-sm-9">\n\t\t\t\t\t\t\t\t<div ng-switch-when="text">\n\t\t\t\t\t\t\t\t\t<input type="text" ng-model="settings.questions[question.id]" class="form-control"/>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<div ng-switch-default>\n\t\t\t\t\t\t\t\t\t<div class="alert alert-danger">\n\t\t\t\t\t\t\t\t\t\tUnknown question type: "{{question.type}}"\n\t\t\t\t\t\t\t\t\t\t<pre>{{question | json}}</pre>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<div ng-if="question.help" class="help-block">{{question.help}}</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class="modal-footer">\n\t\t\t\t\t\t<div class="pull-left">\n\t\t\t\t\t\t\t<a class="btn btn-danger" data-dismiss="modal">Cancel</a>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class="pull-right">\n\t\t\t\t\t\t\t<a ng-click="exportExecute()" class="btn btn-primary" data-dismiss="modal">Export</a>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t\t<ng-transclude>\n\t\t\t<a ng-click="exportPrompt()" class="btn btn-default">Export...</a>\n\t\t</ng-transclude>\n\t'}}).directive("qbModal",function(){return{scope:{query:"=",spec:"<",title:"@?",onRefresh:"&?",binding:"@?"},transclude:!0,restrict:"A",controller:["$element","$scope",function(t,e){var n=this;n.isShown=!1,n.rebind=function(){t.one("click",function(){t.find(".qb-modal").one("hide.bs.modal",function(){n.isShown=!1}).one("hidden.bs.modal",function(){n.rebind()}).modal("show")})},e.submit=function(){angular.isFunction(n.onRefresh)&&n.onRefresh({query:e.queryCopy,spec:e.spec}),e.binding&&"complete"!=e.binding||(e.query=e.queryCopy),t.find(".qb-modal").modal("hide")},n.$onInit=function(){e.queryCopy="live"==e.binding?e.query:angular.copy(e.query)},n.rebind()}],template:'\n\t\t<ng-transclude></ng-transclude>\n\t\t<div class="qb-modal modal fade">\n\t\t\t<div class="modal-dialog modal-lg">\n\t\t\t\t<div class="modal-content">\n\t\t\t\t\t<div class="modal-header">\n\t\t\t\t\t\t<a class="close" data-dismiss="modal"><i class="fa fa-times"></i></a>\n\t\t\t\t\t\t<h4 class="modal-title">{{title || \'Edit Filter\'}}</h4>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class="modal-body">\n\t\t\t\t\t\t<ui-query-builder\n\t\t\t\t\t\t\tquery="queryCopy"\n\t\t\t\t\t\t\tspec="spec"\n\t\t\t\t\t\t></ui-query-builder>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class="modal-footer">\n\t\t\t\t\t\t<div class="pull-left">\n\t\t\t\t\t\t\t<a class="btn btn-danger" data-dismiss="modal">Cancel</a>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class="pull-right">\n\t\t\t\t\t\t\t<a ng-click="submit()" class="btn btn-success">Refresh</a>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t'}}).directive("qbSearch",function(){return{scope:{query:"=",spec:"<",onRefresh:"&?",useIndexes:"@?"},restrict:"AE",transclude:!0,controller:["$scope","$rootScope","$timeout","qbTableUtilities",function(t,e,n,l){var i=this;t.search="",t.submit=function(){if(!t.search)return t.clear();var n={$comment:"search",$or:_(t.spec).pickBy(function(t){return"string"==t.type}).mapValues(function(e,n){return[{$regexp:l.escapeRegExp(t.search),options:"i"}]}).value()},r=l.find(t.query,{$comment:"search"}),a=angular.copy(t.query);if(r&&_.isEqual(r,["$comment"]))a=n;else if(r&&"$and"==r[0])_.set(a,r,n);else if(_.isEqual(_.keys(a),["$and"]))a.$and.push(n);else if(_.isObject(a)){var s=i.useIndexes||"auto";"auto"==s&&(s=_.keys(t.spec).some(function(e){return"_id"!=e&&t.spec[e].index})?"stringIndexed":"string"),a.$or=_(t.spec).pickBy(function(t,e){if("_id"==e)return!1;switch(s){case"all":return!0;case"string":return"string"==t.type;case"stringIndexed":return"string"==t.type&&t.index;default:throw new Error('Unknown field selection method: "'+s+'"')}}).map(function(e,n){return _defineProperty({},n,{$regexp:l.escapeRegExp(t.search),options:"i"})}).value()}else console.warn("Unable to place search query",n,"within complex query",a);e.$broadcast("queryBuilder.change",a),angular.isFunction(i.onRefresh)&&i.onRefresh({query:a}),("complete"==i.binding||angular.isUndefined(i.binding))&&(t.query=a)},t.clear=function(){var e=l.find(t.query,{$comment:"search"});e&&_.isEqual(e,["$comment"])?t.query={}:e&&"$and"==e[0]?t.query=t.query.$and.find(function(t,e){return"search"!=t.$comment}):e?_.unset(t.query,e):console.warn("Unable to clear search query within complex query",t.query)},t.check=function(){try{t.search=_.chain(t.query).get("$or").first().values().first().get("$regexp").thru(function(t){return l.unescapeRegExp(t||"")}).value()}catch(e){t.search=""}},i.$onInit=function(){return t.check()}}],template:'\n\t\t<ng-transclude>\n\t\t\t<form ng-submit="submit()" class="form-inline">\n\t\t\t\t<div class="form-group">\n\t\t\t\t\t<div class="input-group">\n\t\t\t\t\t\t<input ng-blur="submit()" type="text" ng-model="search" class="form-control"/>\n\t\t\t\t\t\t<a ng-click="submit()" class="btn btn-default input-group-addon">\n\t\t\t\t\t\t\t<i class="fa fa-search"/>\n\t\t\t\t\t\t</a>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</ng-transclude>\n\t'}});
{
"name": "@momsfriendlydevco/angular-ui-query-builder",
"version": "1.2.9",
"version": "1.2.10",
"description": "MongoDB format query-builder UI component for Angular",

@@ -5,0 +5,0 @@ "main": "src/utilities.js",

angular.module('angular-ui-query-builder',[])
// Service: QueryBuilder {{{
.service('QueryBuilder', function() {

@@ -23,3 +24,33 @@ var QueryBuilder = this;

/**
* List of additional properties that we support but need special treatment
* @var {Object} Each key is the property name with additional details in the Object value
* @param {string} [type='hidden'] How to handle each property within the UI
* @param {boolean} [canDelete=true] Disable deletion on the field
* @param {*} [...] Other inherited properties (see QueryBuilder.queryToArray) for examples
*/
QueryBuilder.metaProperties = {
limit: {
type: 'keyVal',
actions: [{id: '$eq', title: 'Equals'}],
action: '$eq',
canDelete: true,
},
populate: {type: 'hidden'},
skip: {
type: 'keyVal',
actions: [{id: '$eq', title: 'Equals'}],
action: '$eq',
canDelete: true,
},
sort: {
type: 'keyVal',
actions: [{id: '$eq', title: 'Equals'}],
action: '$eq',
canDelete: false,
},
};
/**
* Returns a queryList collection from a query object

@@ -47,5 +78,8 @@ * @param {Object} query The raw MongoDB / Sift object to transform from an object into a collection

.pickBy((v, k) => {
var maps = spec[k] // Maps onto a spec path
var maps =
spec[k] // Maps onto a spec path
|| k == '$and'
|| k == '$or';
|| k == '$or'
|| QueryBuilder.metaProperties[k] // is a meta directive
if (!maps) console.warn('query-builder', 'Incomming query path', k, 'Does not map to anything in spec', spec);

@@ -99,2 +133,11 @@ return !!maps;

};
} else if (QueryBuilder.metaProperties[k]) { // Is a meta property
return Object.assign({
path: k,
title: _.startCase(k),
value: v,
type: 'hidden',
action: '$hidden',
actions,
}, QueryBuilder.metaProperties[k]);
} else if (firstKey == '$exists') {

@@ -109,3 +152,3 @@ return {

};
} else if (s.type == 'string' && _.isArray(s.enum)) {
} else if (s.type == 'string' && _.isArray(s.enum) && s.enum.length) {
return {

@@ -137,5 +180,5 @@ path: k,

: 'string',
action: firstKey,
action: '$eq',
value:
s.type == 'date' ? moment(firstValue).format('YYYY-MM-DD') // Convert date objects back to strings
s.type == 'date' ? moment(firstValue).toDate() // Convert date string weirdness into real dates
: firstValue,

@@ -180,2 +223,5 @@ actions,

}));
case 'keyVal':
case 'hidden':
return ql.value;
default:

@@ -190,4 +236,5 @@ console.warn('Unknown type to convert:', ql.type);

})
// }}}
// Component: uiQueryBuilder {{{
/**

@@ -220,10 +267,11 @@ * Master query builder component

$ctrl.qbQuery;
$scope.$watchGroup(['$ctrl.query', '$ctrl.spec'], ()=> {
var initUnwatch = $scope.$watchGroup(['$ctrl.query', '$ctrl.spec'], ()=> {
if (!$ctrl.spec || !$ctrl.query) return; // Not yet got everything we need
$ctrl.qbSpec = QueryBuilder.cleanSpec($ctrl.spec);
$ctrl.qbQuery = QueryBuilder.queryToArray($ctrl.query, $ctrl.qbSpec);
initUnwatch(); // Release the watcher so we don't get stuck in a loop
});
// }}}
/**

@@ -285,4 +333,5 @@ * Emitted by lower elements to inform the main builder that something has changed

})
// }}}
// Component: uiQueryBuilderGroup {{{
/**

@@ -299,3 +348,3 @@ * Query builder element that holds a collection of queries - an array

template: `
<div ng-repeat="row in $ctrl.qbGroup">
<div ng-repeat="row in $ctrl.qbGroup | filter:$ctrl.qbGroupFilter" meta-key="{{row.path}}">
<ui-query-builder-row

@@ -306,9 +355,19 @@ qb-item="row"

</div>
<div class="query-row">
<div class="query-container">
<div class="query-block">
<button ng-click="$ctrl.add()" type="button" class="btn-add"></button>
</div>
</div>
</div>
`,
controller: function($scope, QueryBuilder) {
var $ctrl = this;
$ctrl.qbGroupFilter = item => item.type != 'hidden';
},
})
// }}}
// Component: uiQueryBuilderRow {{{
/**

@@ -333,2 +392,3 @@ * Individual line-item for a query row

<div ng-switch-when="binaryGroup" class="query-row">
<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>
<div class="query-block">

@@ -349,7 +409,9 @@ <div class="btn btn-1 btn-block">

<div ng-switch-when="string" class="query-row">
<div class="query-block">
<div class="btn btn-1 btn-block">
{{$ctrl.qbItem.title}}
</div>
</div>
<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>
<ui-query-builder-path
class="query-block"
level="1"
selected="$ctrl.qbItem.path"
qb-spec="$ctrl.qbSpec"
></ui-query-builder-path>
<ui-query-builder-block-menu

@@ -363,3 +425,8 @@ class="query-block"

<div class="btn btn-3 btn-block">
<input ng-value="$ctrl.qbItem.value" type="text" class="form-control"/>
<input
ng-model="$ctrl.qbItem.value"
ng-change="$ctrl.setChanged()"
type="text"
class="form-control"
/>
</div>

@@ -371,7 +438,9 @@ </div>

<div ng-switch-when="enum" class="query-row">
<div class="query-block">
<div class="btn btn-1 btn-block">
{{$ctrl.qbItem.title}}
</div>
</div>
<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>
<ui-query-builder-path
class="query-block"
level="1"
selected="$ctrl.qbItem.path"
qb-spec="$ctrl.qbSpec"
></ui-query-builder-path>
<ui-query-builder-block-menu

@@ -408,3 +477,8 @@ class="query-block"

<div class="btn btn-3 btn-block">
<input ng-value="$ctrl.qbItem.value" type="date" class="form-control"/>
<input
ng-model="$ctrl.qbItem.value"
ng-change="$ctrl.setChanged()"
type="date"
class="form-control"
/>
</div>

@@ -416,7 +490,9 @@ </div>

<div ng-switch-when="number" class="query-row">
<div class="query-block">
<div class="btn btn-1 btn-block">
{{$ctrl.qbItem.title}}
</div>
</div>
<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>
<ui-query-builder-path
class="query-block"
level="1"
selected="$ctrl.qbItem.path"
qb-spec="$ctrl.qbSpec"
></ui-query-builder-path>
<ui-query-builder-block-menu

@@ -430,3 +506,8 @@ class="query-block"

<div class="btn btn-3 btn-block">
<input ng-value="$ctrl.qbItem.value" type="number" class="form-control"/>
<input
ng-value="$ctrl.qbItem.value"
ng-changed="$ctrl.setChanged()"
type="number"
class="form-control"
/>
</div>

@@ -438,7 +519,9 @@ </div>

<div ng-switch-when="exists" class="query-row">
<div class="query-block">
<div class="btn btn-1 btn-block">
{{$ctrl.qbItem.title}}
</div>
</div>
<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>
<ui-query-builder-path
class="query-block"
level="1"
selected="$ctrl.qbItem.path"
qb-spec="$ctrl.qbSpec"
></ui-query-builder-path>
<ui-query-builder-block-menu

@@ -454,10 +537,37 @@ class="query-block"

<div ng-switch-when="search" class="query-row">
<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>
<ui-query-builder-path
class="query-block"
level="1"
selected="$ctrl.qbItem.path"
qb-spec="$ctrl.qbSpec"
></ui-query-builder-path>
<div class="query-block">
<div class="btn btn-1 btn-block">
{{$ctrl.qbItem.title}}
<div class="btn btn-2 btn-block">
<input
ng-model="$ctrl.qbItem.value"
ng-change="$ctrl.setChanged()"
type="text"
class="form-control"
/>
</div>
</div>
</div>
<!-- }}} -->
<!-- keyVal (Only title + value) {{{ -->
<div ng-switch-when="keyVal" class="query-row">
<a ng-if="$ctrl.qbItem.canDelete === undefined || $ctrl.qbItem.canDelete" ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>
<ui-query-builder-block
class="query-block"
level="1"
title="$ctrl.qbItem.title"
></ui-query-builder-block>
<div class="query-block">
<div class="btn btn-2 btn-block">
<input ng-value="$ctrl.qbItem.value" ng-keyup="$ctrl.setChanged()" type="text" class="form-control"/>
<input
ng-model="$ctrl.qbItem.value"
ng-change="$ctrl.setChanged()"
type="text"
class="form-control"
/>
</div>

@@ -469,9 +579,11 @@ </div>

<div ng-switch-default class="query-row">
<a ng-click="$ctrl.delete($ctrl.qbItem.path)" class="btn-trash"></a>
<ui-query-builder-path
class="query-block"
level="1"
selected="$ctrl.qbItem.path"
qb-spec="$ctrl.qbSpec"
></ui-query-builder-path>
<div class="query-block">
<div class="btn btn-warning btn-block">
{{$ctrl.qbItem.title}}
</div>
</div>
<div class="query-block">
<div class="btn btn-warning btn-block">
Unknown handler: {{$ctrl.qbItem.type}}

@@ -482,14 +594,8 @@ </div>

<!-- }}} -->
<!-- Add button {{{
<div class="query-row">
<div class="query-block btn-group">
<a ng-click="$ctrl.add()" class="btn btn-add"></a>
</div>
</div>
}}} -->
</div>
`,
})
// }}}
// Component: uiQueryBuilderPath {{{
/**

@@ -533,4 +639,27 @@ * Component for drawing a path selection component

})
// }}}
// Component: uiQueryBuilderBlock {{{
/**
* Component for drawing a Block with no-interactivity
* @param {number} level The level of button we are drawing
* @param {string} title The title of the block to display
*/
.component('uiQueryBuilderBlock', {
bindings: {
level: '<',
title: '<',
},
controller: function($scope) {
var $ctrl = this;
},
template: `
<a class="btn btn-block btn-{{$ctrl.level}}">
{{$ctrl.title}}
</a>
`,
})
// }}}
// Component: uiQueryBuilderBlockMenu {{{
/**

@@ -562,3 +691,6 @@ * Component for drawing a Block as a dropdown list of options

template: `
<a class="btn btn-block btn-{{$ctrl.level}} dropdown-toggle" data-toggle="dropdown"> {{$ctrl.selectedOption.title}} <i class="fa fa-caret-down"></i></a>
<a class="btn btn-block btn-{{$ctrl.level}} dropdown-toggle" data-toggle="dropdown">
{{$ctrl.selectedOption.title}}
<i class="fa fa-caret-down"></i>
</a>
<ul class="dropdown-menu pull-right">

@@ -569,4 +701,5 @@ <li ng-repeat="option in $ctrl.options track by option.id"><a ng-click="$ctrl.setSelected(option)">{{option.title}}</a></li>

})
// }}}
// Component: uiQueryBuilderBlockMenuMultiple {{{
/**

@@ -623,603 +756,2 @@ * Component for drawing a Block as a dropdown list of multiple-select options

})
// Main widget {{{
.component('uiQueryBuilderOLD', {
bindings: {
query: '=',
spec: '<',
},
template: `
<div class="ui-query-builder clearfix">
<div class="query-container">
<!-- Meta field: sort {{{ -->
<div class="query-row">
<!-- Path component {{{ -->
<div class="query-block">
<div class="btn-group btn-block">
<a class="btn btn-1 btn-block">
Sort by
</a>
</div>
</div>
<!-- }}} -->
<!-- Query operand component {{{ -->
<div class="query-block btn-group">
<div class="btn btn-block btn-2">
<input ng-model="$ctrl.query.sort" type="text" class="form-control"/>
</div>
</div>
<!-- }}} -->
</div>
<!-- }}} -->
<!-- Meta field: limit {{{ -->
<div class="query-row">
<!-- Path component {{{ -->
<div class="query-block">
<div class="btn-group btn-block">
<a class="btn btn-1 btn-block">
Limited to
</a>
</div>
</div>
<!-- }}} -->
<!-- Query operand component {{{ -->
<div class="query-block btn-group">
<div class="btn btn-block btn-2">
<input ng-model="$ctrl.query.limit" type="number" class="form-control"/>
</div>
</div>
<div class="query-block btn-group">
<div class="btn btn-block btn-1">
Skipping
</div>
</div>
<div class="query-block btn-group">
<div class="btn btn-block btn-2">
<input ng-model="$ctrl.query.skip" type="number" class="form-control"/>
</div>
</div>
<!-- }}} -->
</div>
<!-- }}} -->
<div class="query-row">
<div class="query-block">
<!-- FIXME: Need branch title -->
</div>
<ui-query-builder-branch
class="query-container"
branch="$ctrl.query"
spec="$ctrl.spec"
></ui-query-builder-branch>
</div>
</div>
</div>
`,
controller: function($scope) {
var $ctrl = this;
// Clean up incomming spec {{{
$scope.$watch('$ctrl.spec', ()=> {
_.forEach($ctrl.spec, (v, k) => {
if (!v.title) v.title = _.startCase(k); // Create a title from the key if its omitted
if (v.enum && _.isArray(v.enum)) { // Ensure enums are aways collections
v.enum = _(v.enum)
.map(e => _.isString(e) ? {id: e, title: _.startCase(e)} :e)
.sortBy('title')
.value();
}
})
});
// }}}
},
})
// }}}
// Branch widget {{{
/**
* Display a branch
* This is a seperate component in order to allow recursion
* @param {Object} branch The branch to display (passed from the main widget or recursively from this one)
* @param {Object} spec The specification passed from the parent
*/
.component('uiQueryBuilderBranch', {
bindings: {
branch: '=',
spec: '<',
},
template: `
<!-- AND blocks {{{ -->
<div ng-repeat="leaf in $ctrl.properties | filter:{isMeta:true,id:'$and'} track by leaf.id" ng-switch="leaf.spec.type" ng-repeat-emit="uiQueryQueryRepaint" class="query-row">
<div ng-repeat="choiceLeaf in leaf.value">
<ui-query-builder-branch
branch="choiceLeaf"
spec="$ctrl.spec"
></ui-query-builder-branch>
</div>
</div>
<!-- }}} -->
<!-- OR blocks {{{ -->
<div ng-repeat="leaf in $ctrl.properties | filter:{isMeta:true,id:'$or'} track by leaf.id" ng-switch="leaf.spec.type" ng-repeat-emit="uiQueryQueryRepaint" class="query-row">
<div ng-repeat="choiceLeaf in leaf.value">
<ui-query-builder-branch
branch="choiceLeaf"
spec="$ctrl.spec"
></ui-query-builder-branch>
</div>
</div>
<!-- }}} -->
<!-- Main fields {{{ -->
<div ng-repeat="leaf in $ctrl.properties | filter:{isMeta:false} track by leaf.id" ng-switch="leaf.spec.type" ng-repeat-emit="uiQueryQueryRepaint" class="query-row">
<!-- Path component {{{ -->
<button ng-click="$ctrl.remove(leaf.id); $event.stopPropagation()" class="btn btn-trash btn-danger" type="button"></button>
<div class="query-block">
<div class="btn-group btn-block" ng-class="{new: !leaf.id}">
<a class="btn btn-1 btn-block dropdown-toggle" data-toggle="dropdown">
{{$ctrl.spec[leaf.id].title || 'Select...'}}
<i class="fa fa-caret-down"></i>
</a>
<ul class="dropdown-menu pull-right">
<li ng-repeat="(key, val) in $ctrl.spec track by key" ng-class="key == leaf.id && 'active'">
<a ng-click="$ctrl.setField(leaf, key)">
{{$ctrl.spec[key].title}}
</a>
</li>
</ul>
</div>
</div>
<!-- }}} -->
<!-- Query type component {{{ -->
<div ng-show="leaf.valueOperand" class="query-block">
<div class="btn-group btn-block">
<a class="btn btn-2 btn-block dropdown-toggle" data-toggle="dropdown">
{{($ctrl.operandsByID[leaf.valueOperand][leaf.spec.type] || $ctrl.operandsByID[leaf.valueOperand].base).title}}
<i class="fa fa-caret-down"></i>
</a>
<ul class="dropdown-menu pull-right">
<li><a ng-click="$ctrl.setWrapper(leaf, '$eq')">Is</a></li>
<li><a ng-click="$ctrl.setWrapper(leaf, '$ne')">Is not</a></li>
<li><a ng-click="$ctrl.setWrapper(leaf, '$in')">One of</a></li>
<li><a ng-click="$ctrl.setWrapper(leaf, '$nin')">Not one of</a></li>
<li ng-if="leaf.spec.type == 'number'"><a ng-click="$ctrl.setWrapper(leaf, '$gt')">Above</a></li>
<li ng-if="leaf.spec.type == 'number'"><a ng-click="$ctrl.setWrapper(leaf, '$lt')">Below</a></li>
<li ng-if="leaf.spec.type == 'date'"><a ng-click="$ctrl.setWrapper(leaf, '$gt')">Is after</a></li>
<li ng-if="leaf.spec.type == 'date'"><a ng-click="$ctrl.setWrapper(leaf, '$gte')">Is at least</a></li>
<li ng-if="leaf.spec.type == 'date'"><a ng-click="$ctrl.setWrapper(leaf, '$lt')">Is before</a></li>
<li ng-if="leaf.spec.type == 'date'"><a ng-click="$ctrl.setWrapper(leaf, '$lte')">Is at most</a></li>
<li><a ng-click="$ctrl.setWrapper(leaf, '$exists')">Has a value</a></li>
</ul>
</div>
</div>
<!-- }}} -->
<!-- Query operand component {{{ -->
<div ng-show="leaf.valueOperand" class="query-block btn-group" ng-switch="(operandConfig = $ctrl.operandsByID[leaf.valueOperand][leaf.spec.type] || $ctrl.operandsByID[leaf.valueOperand].base).type">
<div ng-switch-when="string" class="btn btn-block btn-3">
<input ng-model="leaf.valueEdit" ng-change="$ctrl.setValue(leaf)" type="text" class="form-control"/>
</div>
<div ng-switch-when="array" class="btn btn-block btn-3 btn-group">
<div class="btn-fill text-left dropdown-toggle" data-toggle="dropdown">
<span class="pill" ng-repeat="item in $ctrl.spec[leaf.id].enum | uiQueryBuilderFilterSelected:leaf track by item.id">
{{item.title}}
</span>
<span ng-if="!leaf.valueEdit.length">...</span>
<i class="fa fa-caret-down"></i>
</div>
<ul class="dropdown-menu pull-right">
<li ng-repeat="item in $ctrl.spec[leaf.id].enum | uiQueryBuilderFilterSelected:leaf:false track by item.id">
<a ng-click="$ctrl.setValueIncluded(leaf, item.id, false)">
<i class="fa fa-fw fa-check-square text-primary"></i>
{{item.title}}
</a>
</li>
<li ng-repeat="item in $ctrl.spec[leaf.id].enum | uiQueryBuilderFilterSelected:leaf:true track by item.id">
<a ng-click="$ctrl.setValueIncluded(leaf, item.id, true)">
<i class="fa fa-fw fa-square-o text-primary"></i>
{{item.title}}
</a>
</li>
</ul>
</div>
<div ng-switch-when="boolean" class="btn btn-block btn-3" ng-click="$ctrl.setValue(leaf, !leaf.valueEdit)">
<i class="fa fa-fw" ng-class="leaf.valueEdit ? 'fa-check-square-o' : 'fa-square-o'"></i>
{{leaf.valueEdit ? operandConfig.textTrue : operandConfig.textFalse}}
</div>
<div ng-switch-when="date" class="btn btn-block btn-3">
<input ng-model="leaf.valueEdit" ng-change="$ctrl.setValue(leaf)" type="date" class="form-control"/>
</div>
<div ng-switch-default class="btn btn-block btn-3">
Unknown operand: <code>{{leaf.valueOperand}}</code>
</div>
</div>
<!-- }}} -->
</div>
<!-- Add button {{{ -->
<button ng-click="$ctrl.add()" class="btn btn-add btn-success" type="button"></button>
<!-- }}} -->
`,
controller: function($element, $scope) {
var $ctrl = this;
// Operands {{{
/**
* An array of all supported wrapping operands
* These usually correspond to the 'dollar function' wrapper in Mongo. e.g. $eq =~ equals
* Each item has an `id` and a `base` setup with an optional override for specific types
* @var array
*/
$ctrl.operands = [
/*
{
id: String, // The operand matching leaf.valueOperand
setter: Function(v), // Function used to convert the value into something compatible with the operand
base: {
title: String, // The human title of the operand
type: String, // How to display the operand value to the user (generally matches to standard scalar values)
},
string: { // Specific override for the string type (optional)
...
},
number: { // Specific override for the number type (optional)
...
},
*/
{
id: '$eq',
setter: v => ({$eq: v}),
export: leaf => leaf.valueEdit,
base: {
title: 'Is',
type: 'string',
},
boolean: {
title: 'Is',
type: 'boolean',
textTrue: 'Enabled',
textFalse: 'Disabled',
},
date: {
title: 'Is exactly',
type: 'date',
},
},
{
id: '$ne',
setter: v => ({$ne: v}),
export: leaf => ({$ne: leaf.valueEdit}),
base: {
title: 'Is not',
type: 'string',
},
boolean: {
title: 'Is not',
type: 'boolean',
textTrue: 'Enabled',
textFalse: 'Disabled',
},
date: {
title: 'Is not exactly',
type: 'date',
},
},
{
id: '$in',
setter: v => ({$in: _.isArray(v) ? v.split(/\s*,\s*/) : [v]}),
export: leaf => ({$in: leaf.value.$in}),
base: {
title: 'One of',
type: 'array',
},
},
{
id: '$nin',
setter: v => ({$nin: _.isArray(v) ? v.split(/\s*,\s*/) : [v]}),
export: leaf => ({$nin: leaf.value.$nin}),
base: {
title: 'Not one of',
type: 'array',
},
},
{
id: '$gt',
setter: v => ({$gt: v}),
export: leaf => ({$gt: leaf.value.$gt}),
base: {
title: 'Above',
type: 'number',
},
date: {
title: 'Is after',
type: 'date',
},
},
{
id: '$gte',
setter: v => ({$gte: v}),
export: leaf => ({$gte: leaf.value.$gte}),
base: {
title: 'Above or equals',
type: 'number',
},
date: {
title: 'Is at least',
type: 'date',
},
},
{
id: '$lt',
setter: v => ({$lt: v}),
export: leaf => ({$lt: leaf.value.$lt}),
base: {
title: 'Below',
type: 'number',
},
date: {
title: 'Is before',
type: 'date',
},
},
{
id: '$lte',
setter: v => ({$lt: v}),
export: leaf => ({$lte: leaf.value.$lte}),
base: {
title: 'Below or equals',
type: 'number',
},
date: {
title: 'Is at most',
type: 'date',
},
},
{
id: '$exists',
setter: v => ({$exists: !!v}),
export: leaf => ({$exists: leaf.value.$exists}),
base: {
title: 'Has a value',
type: 'boolean',
textTrue: 'Has a value',
textFalse: 'Has a value', // This isn't technically right but its right next to a disabled checkbox so it makes sense in context
},
},
{
id: '$regexp',
setter: v => ({$regexp: v}),
export: leaf => ({$regexp: leaf.value.$regexp}),
base: {
title: 'Matches',
type: 'string',
},
},
];
$ctrl.operandsByID = _.mapKeys($ctrl.operands, 'id');
// }}}
// $ctrl.getSpec() {{{
$ctrl.getSpec = (key, val, path) => {
// Spec present {{{
if ($ctrl.spec[path]) {
return $ctrl.spec[path];
// }}}
// Meta parent types {{{
} else if (key == '$and' || key == '$or') {
return {type: 'group', type: key};
// }}}
// Guessing {{{
} else if (_.isString(val)) {
return {type: 'string'};
} else if (_.isNumber(val)) {
return {type: 'number'};
// }}}
// Fallback {{{
} else {
return {type: 'string'};
}
// }}}
};
// }}}
// $ctrl.translateBranch() {{{
$ctrl.translateBranch = (branch, pathSegments = []) =>
_($ctrl.branch)
.map((v, k) => ({
id: k,
value: v,
valueEdit: $ctrl.getFlatValue(v),
valueOperand: _.isObject(v) ? _(v).keys().first() : '$eq',
isMeta: (''+k).startsWith('$') || ['sort', 'skip', 'limit'].includes(k),
spec: $ctrl.getSpec(k, v, k),
path: pathSegments.concat([k]),
}))
.sortBy(p => p.isMeta ? `Z${p.id}` : `A${p.id}`) // Force meta items to the end
.value();
// }}}
// $ctrl.exportBranch() {{{
/**
* Export the local $ctrl.properties branch back into the upstream branch
*/
$ctrl.exportBranch = ()=> {
$ctrl.branch = _($ctrl.properties)
.mapKeys(b => b.id)
.mapValues(b => $ctrl.operandsByID[b.valueOperand].export(b))
.value()
};
// }}}
// Convert branch -> properties {{{
// We have to do this to sort appropriately and allow iteration over dollar prefixed keys
$ctrl.properties;
$scope.$watchGroup(['$ctrl.branch', '$ctrl.spec'], ()=> {
if (!$ctrl.branch || !$ctrl.spec) return; // Not yet ready
$ctrl.properties = $ctrl.translateBranch($ctrl.branch);
});
// }}}
// Branch interaction {{{
$ctrl.setField = (leaf, field) => {
leaf.id = field;
leaf.path = [field];
leaf.value = undefined;
leaf.valueEdit = undefined;
leaf.valueOperand = '$eq';
leaf.spec = $ctrl.spec[field];
$ctrl.setValue(leaf);
};
$ctrl.setWrapper = (leaf, type) => {
if (leaf.valueOperand == '$eq' && type == '$ne') { // Negate
leaf.valueOperand = '$ne';
leaf.valueEdit = $ctrl.getFlatValue(leaf.value);
leaf.value = {$ne: leaf.valueEdit};
} else if (leaf.valueOperand == '$ne' && type == '$eq') {
leaf.valueOperand = '$eq';
leaf.valueEdit = $ctrl.getFlatValue(leaf.value);
leaf.value = {$eq: leaf.valueEdit};
} else if (leaf.valueOperand == '$in' && type == '$eq') { // Flatten array into scalar
leaf.valueOperand = '$eq';
leaf.value = leaf.valueEdit = $ctrl.getFlatValue(leaf.value);
} else if ((leaf.valueOperand == '$eq' || leaf.valueOperand === undefined) && type == '$in') { // Roll scalar into array
leaf.valueOperand = '$in';
leaf.valueEdit = $ctrl.getFlatValue(leaf.value);
leaf.value = {$in: [leaf.valueEdit]};
} else if (type == '$exists') { // Convert anything to exists - force it to be a boolean
leaf.valueOperand = '$exists';
leaf.valueEdit = true;
leaf.value = {$exists: leaf.valueEdit};
} else { // Unknown swapping - convert to an object with one key
console.log('UNHANDLED TYPE CONVERT:', leaf.type, '=>', type);
var newValue = $ctrl.getFlatValue(leaf.value);
leaf.valueOperand = type;
leaf.valueEdit = newValue;
leaf.value = {[leaf.valueOperand]: leaf.valueEdit};
}
// Set the upstream model value
$ctrl.exportBranch();
};
/**
* Set the value of a leaf
* @param {Object} leaf The leaf to change the value of
* @param {*} [value] Optional value to set, if omitted the bound leaf.valueEdit will be used
*/
$ctrl.setValue = (leaf, value) => {
var newValue = _.isUndefined(value) ? leaf.valueEdit : value;
// Run via operand setter
leaf.value = $ctrl.operandsByID[leaf.valueOperand].setter(newValue);
leaf.valueEdit = $ctrl.getFlatValue(leaf.value);
// Set the upstream model value
$ctrl.exportBranch();
};
// }}}
// Utility functions {{{
/**
* Set whether the specified value is included in the leaf array of values
* @param {Object} leaf The leaf to change the value of
* @param {string} value The value to toggle to inclusion of
* @param {boolean} included Whether the value is included
*/
$ctrl.setValueIncluded = (leaf, value, included) => {
var wrapperKey = _(leaf.value).keys().first();
if (!wrapperKey) throw new Error('Tried to set array inclusion on non wrapped key: ' + leaf.value);
var isIncluded = leaf.value[wrapperKey].includes(value);
if (included && !isIncluded) {
leaf.value[wrapperKey].push(value);
} else if (!included && isIncluded) {
leaf.value[wrapperKey] = leaf.value[wrapperKey].filter(i => i != value);
}
leaf.value[wrapperKey].sort();
leaf.valueEdit = _.isObject(leaf.value) && _.size(leaf.value) ? _(leaf.value).map().first() : leaf.value;
};
/**
* Return the 'flat' value of a Mongo expression
* This will always return the closest thing we have to a scalar primative
* @param {Object|string} input The input expression to flatten
* @returns {string|number} The nearest thing we can evaluate to a primative (or an empty string)
*
* @example
* $ctrl.getFlatValue('foo') //= 'foo'
* @example
* $ctrl.getFlatValue({$eq: 'bar'}) //= 'bar'
* @example
* $ctrl.getFlatValue({$in: ['quz', 'qux']}) //= 'quz'
*/
$ctrl.getFlatValue = input => {
if (_.isString(input) || _.isNumber(input) || _.isBoolean(input) || _.isDate(input)) { // Already a primative
return input;
} else if (_.isObject(input) && _.size(input) == 1) { // Unwrap object value from object
return _(input).values().first();
} else if (_.isObject(input) && input.$regexp) { // RegExps - we can savely ignore the options object and guess at the expression
return '/' + _.trim(input.$regexp, '/') + '/' + input.options;
} else { // No idea how to convert - just return an empty string
console.warn('Given up trying to flatten input value', input);
return input;
}
};
// }}}
// Branch CRUD {{{
$ctrl.add = ()=> {
if ($ctrl.properties.some(p => !p.id)) return; // Check there are no new items currently in the process of being added
$ctrl.properties.push({isMeta: false});
// Wait for the page to redraw then force the dropdown to open
// Yes I know this is a weird work around but we have to wait for the DOM to settle for some reason before we can add the `open` class - MC 2017-10-03
var eventUnbind = $scope.$on('uiQueryQueryRepaint', ()=> {
$element.find('.query-block > .new').addClass('open');
});
};
$ctrl.remove = id => {
$ctrl.properties = $ctrl.properties.filter(p => p.id != id);
$ctrl.exportBranch();
};
// }}}
},
})
/**
* Simple query which takes an array of possible selections and returns only those that are present within the leaf.valueEdit array
* This is used to display selected items in an array
* @param {array} items The array to filter
* @param {Object} leaf The leaf node to filter against
* @param {boolean} [invert=false] Whether to invert the result
* @returns {array} The filtered items array
*/
.filter('uiQueryBuilderFilterSelected', function() {
return function(items, leaf, invert) {
if (!items) return;
return items.filter(i => {
var doesInclude = leaf.valueEdit.includes(i.id);
return (invert ? !doesInclude : doesInclude);
});
};
})
/**
* Fire a $scope.$emit() with the given message when an ng-repeat render finishes
* @param {string} message The message to emit to this element scope upwards
* @example
* <div ng-repeat="widget in widgets" ng-repeat-emit="finished"></div>
*/
.directive('ngRepeatEmit', function($rootScope, $timeout) {
return {
restrict: 'A',
link: function (scope, elem, attr) {
if (scope.$last === true) $timeout(()=> scope.$emit(attr.ngRepeatEmit));
},
};
})
// }}}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc