@momsfriendlydevco/angular-ui-query-builder
Advanced tools
Comparing version 1.2.9 to 1.2.10
@@ -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); | ||
}); | ||
} | ||
}; | ||
}]); | ||
}); | ||
// }}} |
@@ -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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1807431
10934