@momsfriendlydevco/angular-ui-query-builder
Advanced tools
Comparing version 1.1.8 to 1.2.0
@@ -13,3 +13,3 @@ 'use strict'; | ||
}, | ||
template: '\n\t\t<div class="ui-query-builder">\n\t\t\t<ui-query-builder-branch\n\t\t\t\tbranch="$ctrl.query"\n\t\t\t\tspec="$ctrl.spec"\n\t\t\t></ui-query-builder-branch>\n\t\t</div>\n\t', | ||
template: '\n\t\t<div class="ui-query-builder">\n\t\t\t<ui-query-builder-branch\n\t\t\t\tbranch="$ctrl.query"\n\t\t\t\tspec="$ctrl.spec"\n\t\t\t></ui-query-builder-branch>\n\n\t\t\t<!-- Meta field: sort {{{ -->\n\t\t\t<div class="query-container">\n\t\t\t\t<!-- Root branch display {{{ -->\n\t\t\t\t<div class="query-stem"><div></div></div>\n\t\t\t\t<!-- }}} -->\n\t\t\t\t<!-- Path component {{{ -->\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn-group btn-block">\n\t\t\t\t\t\t<a class="btn btn-1 btn-block">\n\t\t\t\t\t\t\tSort by\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\t<!-- }}} -->\n\t\t\t\t<div class="query-stem"><div></div></div>\n\t\t\t\t<!-- Query operand component {{{ -->\n\t\t\t\t<div class="query-block btn-group">\n\t\t\t\t\t<div class="btn btn-block btn-2">\n\t\t\t\t\t\t<input ng-model="$ctrl.query.sort" type="text" class="form-control"/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<!-- }}} -->\n\t\t\t</div>\n\t\t\t<!-- }}} -->\n\t\t\t<!-- Meta field: limit {{{ -->\n\t\t\t<div class="query-container">\n\t\t\t\t<!-- Root branch display {{{ -->\n\t\t\t\t<div class="query-stem"><div></div></div>\n\t\t\t\t<!-- }}} -->\n\t\t\t\t<!-- Path component {{{ -->\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn-group btn-block">\n\t\t\t\t\t\t<a class="btn btn-1 btn-block">\n\t\t\t\t\t\t\tLimited to\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\t<!-- }}} -->\n\t\t\t\t<div class="query-stem"><div></div></div>\n\t\t\t\t<!-- Query operand component {{{ -->\n\t\t\t\t<div class="query-block btn-group">\n\t\t\t\t\t<div class="btn btn-block btn-2">\n\t\t\t\t\t\t<input ng-model="$ctrl.query.limit" type="number" class="form-control"/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div class="query-stem"><div></div></div>\n\t\t\t\t<div class="query-block btn-group">\n\t\t\t\t\t<div class="btn btn-block btn-1">\n\t\t\t\t\t\tSkipping\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div class="query-stem"><div></div></div>\n\t\t\t\t<div class="query-block btn-group">\n\t\t\t\t\t<div class="btn btn-block btn-2">\n\t\t\t\t\t\t<input ng-model="$ctrl.query.skip" type="number" class="form-control"/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<!-- }}} -->\n\t\t\t</div>\n\t\t\t<!-- }}} -->\n\n\t\t</div>\n\t', | ||
controller: ['$scope', function controller($scope) { | ||
@@ -47,3 +47,3 @@ var $ctrl = this; | ||
}, | ||
template: '\n\t\t<div ng-repeat="leaf in $ctrl.properties track by leaf.id" ng-switch="leaf.spec.type" ng-repeat-emit="uiQueryQueryRepaint" class="query-container">\n\t\t\t<!-- Root branch display {{{ -->\n\t\t\t<div class="query-stem"><div></div></div>\n\t\t\t<!-- }}} -->\n\t\t\t<!-- Path component {{{ -->\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<div ng-click="$ctrl.remove(leaf.id); $event.stopPropagation()" class="btn btn-trash btn-danger btn-xs pull-left">\n\t\t\t\t\t\t\t<i class="fa fa-times"></i>\n\t\t\t\t\t\t</div>\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<div ng-show="leaf.valueOperand" class="query-stem"><div></div></div>\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\')">After</a></li>\n\t\t\t\t\t\t<li ng-if="leaf.spec.type == \'date\'"><a ng-click="$ctrl.setWrapper(leaf, \'$lt\')">Before</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<div ng-show="leaf.valueOperand" class="query-stem"><div></div></div>\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" 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-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<div class="query-container">\n\t\t\t<div class="query-stem"><div></div></div>\n\t\t\t<div class="query-block btn-group">\n\t\t\t\t<a ng-click="$ctrl.add()" class="btn btn-lg btn-add btn-default">\n\t\t\t\t\t<i class="fa fa-fw fa-plus fa-lg"></i>\n\t\t\t\t</a>\n\t\t\t</div>\n\t\t</div>\n\t\t<!-- }}} -->\n\t', | ||
template: '\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-container">\n\t\t\t<div class="query-stem"><div></div></div>\n\t\t\t<!-- Path component {{{ -->\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<div ng-click="$ctrl.remove(leaf.id); $event.stopPropagation()" class="btn btn-trash btn-danger btn-xs pull-left">\n\t\t\t\t\t\t\t<i class="fa fa-times"></i>\n\t\t\t\t\t\t</div>\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<div ng-show="leaf.valueOperand" class="query-stem"><div></div></div>\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<div ng-show="leaf.valueOperand" class="query-stem"><div></div></div>\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" 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<div class="query-container">\n\t\t\t<div class="query-stem"><div></div></div>\n\t\t\t<div class="query-block btn-group">\n\t\t\t\t<a ng-click="$ctrl.add()" class="btn btn-lg btn-add btn-default">\n\t\t\t\t\t<i class="fa fa-fw fa-plus fa-lg"></i>\n\t\t\t\t</a>\n\t\t\t</div>\n\t\t</div>\n\t\t<!-- }}} -->\n\t', | ||
controller: ['$element', '$scope', function controller($element, $scope) { | ||
@@ -92,2 +92,6 @@ var $ctrl = this; | ||
textFalse: 'Disabled' | ||
}, | ||
date: { | ||
title: 'Is exactly', | ||
type: 'date' | ||
} | ||
@@ -100,3 +104,3 @@ }, { | ||
export: function _export(leaf) { | ||
return leaf.valueEdit; | ||
return { $ne: leaf.valueEdit }; | ||
}, | ||
@@ -112,2 +116,6 @@ base: { | ||
textFalse: 'Disabled' | ||
}, | ||
date: { | ||
title: 'Is not exactly', | ||
type: 'date' | ||
} | ||
@@ -149,2 +157,6 @@ }, { | ||
type: 'number' | ||
}, | ||
date: { | ||
title: 'Is after', | ||
type: 'date' | ||
} | ||
@@ -162,2 +174,6 @@ }, { | ||
type: 'number' | ||
}, | ||
date: { | ||
title: 'Is at least', | ||
type: 'date' | ||
} | ||
@@ -175,2 +191,6 @@ }, { | ||
type: 'number' | ||
}, | ||
date: { | ||
title: 'Is before', | ||
type: 'date' | ||
} | ||
@@ -188,2 +208,6 @@ }, { | ||
type: 'number' | ||
}, | ||
date: { | ||
title: 'Is at most', | ||
type: 'date' | ||
} | ||
@@ -202,3 +226,3 @@ }, { | ||
textTrue: 'Has a value', | ||
textFalse: 'Has no value' | ||
textFalse: 'Has a value' // This isn't technically right but its right next to a disabled checkbox so it makes sense in context | ||
} | ||
@@ -249,16 +273,11 @@ }, { | ||
return _($ctrl.branch).map(function (v, k) { | ||
var wrappingKey = _.isObject(v) ? _(v).keys().first() : '$eq'; | ||
var firstKeyVal = _.isObject(v) && _.size(v) > 0 ? _(v).map().first() : undefined; | ||
var newBranch = { | ||
return { | ||
id: k, | ||
value: v, | ||
valueEdit: firstKeyVal || v, | ||
valueOperand: wrappingKey, | ||
isMeta: k.startsWith('$'), | ||
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]) | ||
}; | ||
return newBranch; | ||
}).sortBy(function (p) { | ||
@@ -305,15 +324,37 @@ return p.isMeta ? 'Z' + p.id : 'A' + p.id; | ||
$ctrl.setWrapper = function (leaf, type) { | ||
var newValue = {}; | ||
if (_.isObject(leaf.value) && _.size(leaf.value) == 1) { | ||
// Unwrap object value | ||
newValue[type] = _(leaf.value).values().first(); | ||
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 { | ||
// Preseve value | ||
newValue[type] = leaf.valueEdit; | ||
// 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); | ||
} | ||
leaf.valueOperand = type; | ||
leaf.value = newValue; | ||
leaf.valueEdit = _.isObject(newValue[type]) && _.size(newValue[type]) ? newValue[type] : newValue; | ||
$ctrl.setValue(leaf); | ||
// Set the upstream model value | ||
$ctrl.exportBranch(); | ||
}; | ||
@@ -331,3 +372,3 @@ | ||
leaf.value = $ctrl.operandsByID[leaf.valueOperand].setter(newValue); | ||
leaf.valueEdit = _.isObject(leaf.value) && _.size(leaf.value) ? _(leaf.value).map().first() : leaf.value; | ||
leaf.valueEdit = $ctrl.getFlatValue(leaf.value); | ||
@@ -363,2 +404,29 @@ // Set the upstream model 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 { | ||
// No idea how to convert - just return an empty string | ||
console.warn('Given up trying to flatten input value', input); | ||
return input; | ||
} | ||
}; | ||
// }}} | ||
@@ -425,2 +493,161 @@ | ||
}]); | ||
// }}} | ||
angular.module('angular-ui-query-builder') | ||
// Table decorator {{{ | ||
.service('qbTableSettings', function () { | ||
return { | ||
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' | ||
} | ||
}; | ||
}) | ||
/** | ||
* Directive applied to a table element to indicate that we should manage that table via angular-ui-query | ||
* @param {Object} qbTable The query object to modify | ||
* @emits qbTableQueryChange Emitted to child elements as (e, query) when the query object changes | ||
*/ | ||
.directive('qbTable', function () { | ||
return { | ||
scope: { | ||
qbTable: '=' | ||
}, | ||
restrict: 'AC', | ||
controller: ['$scope', 'qbTableSettings', function controller($scope, qbTableSettings) { | ||
var $ctrl = this; | ||
$ctrl.query = $scope.qbTable; // Copy into $ctrl so children can access it / $watch it | ||
$ctrl.setField = function (field, value) { | ||
if (value == undefined) { | ||
// Remove from query | ||
delete $ctrl.query[field]; | ||
return; | ||
} | ||
switch (field) { | ||
case 'sort': | ||
if ($ctrl.query.sort === value) { | ||
// If already sorting by field switch the sort direction | ||
$ctrl.query.sort = '-' + value; | ||
} else if ($ctrl.query.sort === '-' + value) { | ||
// If reverse sorting switch the right way up again | ||
$ctrl.query.sort = value; | ||
} else { | ||
// Just set the sorting | ||
$ctrl.query.sort = value; | ||
} | ||
break; | ||
default: | ||
$scope.qbTable[field] = value; | ||
} | ||
}; | ||
}] | ||
}; | ||
}) | ||
/** | ||
* Directive for header elements to add angular-ui-query functionality | ||
* @param {Object} ^qbTable.qbTable The query Object to mutate | ||
* @param {string} qbCol The field to operate on | ||
* @param {string} [sortable=q] Indicates that the column should switch to being sorted if the user clicks on it, if a value is specified that is used instead of `q` as the sort field | ||
*/ | ||
.directive('qbCol', function () { | ||
return { | ||
scope: { | ||
qbCol: '@', // The field to operate on | ||
sortable: '@' | ||
}, | ||
require: '^qbTable', | ||
restrict: 'A', | ||
transclude: true, | ||
controller: ['$attrs', '$scope', 'qbTableSettings', function controller($attrs, $scope, qbTableSettings) { | ||
var $ctrl = this; | ||
$scope.qbTableSettings = qbTableSettings; | ||
// Sort functionality {{{ | ||
$scope.canSort = false; // True if either sortable has a specific value or is at least present | ||
$scope.isSorted = false; // False, 'asc', 'desc' | ||
$ctrl.$onInit = function () { | ||
$scope.canSort = $scope.sortable || $attrs.sortable === ''; | ||
}; | ||
$scope.$watch('qbTable.query.sort', function (sorter) { | ||
var sortField = $scope.sortable || $scope.q; | ||
if (!sorter) { | ||
$scope.isSorted = false; | ||
} else if (angular.isArray(sorter) && sorter.some(function (i) { | ||
return i == sortField; | ||
}) || sorter == sortField) { | ||
$scope.isSorted = 'asc'; | ||
} else if (angular.isArray(sorter) && sorter.some(function (i) { | ||
return i == '-' + sortField; | ||
}) || sorter == '-' + sortField) { | ||
$scope.isSorted = 'desc'; | ||
} else { | ||
$scope.isSorted = false; | ||
} | ||
}); | ||
$scope.toggleSort = function () { | ||
if ($scope.sortable) { | ||
$scope.qbTable.setField('sort', $scope.sortable); | ||
} else if ($scope.q && $attrs.sortable === '') { | ||
// Has attribute but no value - assume main key if we have one | ||
$scope.qbTable.setField('sort', $scope.q); | ||
} | ||
}; | ||
// }}} | ||
}], | ||
link: function link(scope, element, attrs, parentScope) { | ||
scope.qbTable = parentScope; | ||
}, | ||
template: '\n\t\t<ng-transclude></ng-transclude>\n\t\t<a ng-if="canSort" ng-click="toggleSort()" class="pull-right">\n\t\t\t<i class="{{\n\t\t\t\tisSorted == \'asc\' ? qbTableSettings.icons.sortAsc\n\t\t\t\t: isSorted == \'desc\' ? qbTableSettings.icons.sortDesc\n\t\t\t\t: qbTableSettings.icons.sortNone\n\t\t\t}}"></i>\n\t\t</a>\n\t' | ||
}; | ||
}) | ||
/** | ||
* Directive to add table pagination | ||
* @param {Object} ^qbTable.qbTable The query Object to mutate | ||
*/ | ||
.directive('qbPagination', function () { | ||
return { | ||
scope: {}, | ||
require: '^qbTable', | ||
restrict: 'EA', | ||
controller: ['$attrs', '$scope', 'qbTableSettings', function controller($attrs, $scope, qbTableSettings) { | ||
var $ctrl = this; | ||
$scope.qbTableSettings = qbTableSettings; | ||
$scope.canPrev = true; | ||
$scope.canNext = true; | ||
$scope.$watchGroup(['qbTable.query.limit', 'qbTable.query.skip'], function (sorter) { | ||
$scope.canPrev = $scope.qbTable.query.skip > 0; | ||
$scope.canNext = !$scope.total || $scope.qbTable.query.skip + $scope.qbTable.query.limit < $scope.total; | ||
}); | ||
$scope.navPageRelative = function (pageRelative) { | ||
if (pageRelative == -1) { | ||
$scope.qbTable.setField('skip', Math.min(($scope.qbTable.query.skip || 0) - ($scope.qbTable.query.limit || 10), 0)); | ||
} else if (pageRelative == 1) { | ||
$scope.qbTable.setField('skip', ($scope.qbTable.query.skip || 0) + ($scope.qbTable.query.limit || 10), 0); | ||
} else { | ||
throw new Error('Unsupported page move: ' + pageRelative); | ||
} | ||
}; | ||
}], | ||
link: function link(scope, element, attrs, parentScope) { | ||
scope.qbTable = parentScope; | ||
}, | ||
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<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' | ||
}; | ||
}); | ||
// }}} |
@@ -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}angular.module("angular-ui-query-builder",[]).component("uiQueryBuilder",{bindings:{query:"=",spec:"<"},template:'\n\t\t<div class="ui-query-builder">\n\t\t\t<ui-query-builder-branch\n\t\t\t\tbranch="$ctrl.query"\n\t\t\t\tspec="$ctrl.spec"\n\t\t\t></ui-query-builder-branch>\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<div ng-repeat="leaf in $ctrl.properties track by leaf.id" ng-switch="leaf.spec.type" ng-repeat-emit="uiQueryQueryRepaint" class="query-container">\n\t\t\t\x3c!-- Root branch display {{{ --\x3e\n\t\t\t<div class="query-stem"><div></div></div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Path component {{{ --\x3e\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<div ng-click="$ctrl.remove(leaf.id); $event.stopPropagation()" class="btn btn-trash btn-danger btn-xs pull-left">\n\t\t\t\t\t\t\t<i class="fa fa-times"></i>\n\t\t\t\t\t\t</div>\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<div ng-show="leaf.valueOperand" class="query-stem"><div></div></div>\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\')">After</a></li>\n\t\t\t\t\t\t<li ng-if="leaf.spec.type == \'date\'"><a ng-click="$ctrl.setWrapper(leaf, \'$lt\')">Before</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<div ng-show="leaf.valueOperand" class="query-stem"><div></div></div>\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" 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-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<div class="query-container">\n\t\t\t<div class="query-stem"><div></div></div>\n\t\t\t<div class="query-block btn-group">\n\t\t\t\t<a ng-click="$ctrl.add()" class="btn btn-lg btn-add btn-default">\n\t\t\t\t\t<i class="fa fa-fw fa-plus fa-lg"></i>\n\t\t\t\t</a>\n\t\t\t</div>\n\t\t</div>\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"}},{id:"$ne",setter:function(t){return{$ne:t}},export:function(t){return t.valueEdit},base:{title:"Is not",type:"string"},boolean:{title:"Is not",type:"boolean",textTrue:"Enabled",textFalse:"Disabled"}},{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"}},{id:"$gte",setter:function(t){return{$gte:t}},export:function(t){return{$gte:t.value.$gte}},base:{title:"Above or equals",type:"number"}},{id:"$lt",setter:function(t){return{$lt:t}},export:function(t){return{$lt:t.value.$lt}},base:{title:"Below",type:"number"}},{id:"$lte",setter:function(t){return{$lt:t}},export:function(t){return{$lte:t.value.$lte}},base:{title:"Below or equals",type:"number"}},{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 no 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,a){return n.spec[a]?n.spec[a]:"$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,a){var r=_.isObject(t)?_(t).keys().first():"$eq";return{id:a,value:t,valueEdit:(_.isObject(t)&&_.size(t)>0?_(t).map().first():void 0)||t,valueOperand:r,isMeta:a.startsWith("$"),spec:n.getSpec(a,t,a),path:e.concat([a])}}).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){var a={};_.isObject(t.value)&&1==_.size(t.value)?a[e]=_(t.value).values().first():a[e]=t.valueEdit,t.valueOperand=e,t.value=a,t.valueEdit=_.isObject(a[e])&&_.size(a[e])?a[e]:a,n.setValue(t)},n.setValue=function(t,e){var a=_.isUndefined(e)?t.valueEdit:e;t.value=n.operandsByID[t.valueOperand].setter(a),t.valueEdit=_.isObject(t.value)&&_.size(t.value)?_(t.value).map().first():t.value,n.exportBranch()},n.setValueIncluded=function(t,e,n){var a=_(t.value).keys().first();if(!a)throw new Error("Tried to set array inclusion on non wrapped key: "+t.value);var r=t.value[a].includes(e);n&&!r?t.value[a].push(e):!n&&r&&(t.value[a]=t.value[a].filter(function(t){return t!=e})),t.value[a].sort(),t.valueEdit=_.isObject(t.value)&&_.size(t.value)?_(t.value).map().first():t.value},n.add=function(){if(!n.properties.some(function(t){return!t.id})){n.properties.push({});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 a=e.valueEdit.includes(t.id);return n?!a:a})}}).directive("ngRepeatEmit",["$rootScope","$timeout",function(t,e){return{restrict:"A",link:function(t,n,a){!0===t.$last&&e(function(){return t.$emit(a.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}angular.module("angular-ui-query-builder",[]).component("uiQueryBuilder",{bindings:{query:"=",spec:"<"},template:'\n\t\t<div class="ui-query-builder">\n\t\t\t<ui-query-builder-branch\n\t\t\t\tbranch="$ctrl.query"\n\t\t\t\tspec="$ctrl.spec"\n\t\t\t></ui-query-builder-branch>\n\n\t\t\t\x3c!-- Meta field: sort {{{ --\x3e\n\t\t\t<div class="query-container">\n\t\t\t\t\x3c!-- Root branch display {{{ --\x3e\n\t\t\t\t<div class="query-stem"><div></div></div>\n\t\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\t\x3c!-- Path component {{{ --\x3e\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn-group btn-block">\n\t\t\t\t\t\t<a class="btn btn-1 btn-block">\n\t\t\t\t\t\t\tSort by\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\t\x3c!-- }}} --\x3e\n\t\t\t\t<div class="query-stem"><div></div></div>\n\t\t\t\t\x3c!-- Query operand component {{{ --\x3e\n\t\t\t\t<div class="query-block btn-group">\n\t\t\t\t\t<div class="btn btn-block btn-2">\n\t\t\t\t\t\t<input ng-model="$ctrl.query.sort" type="text" class="form-control"/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t\x3c!-- }}} --\x3e\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\x3c!-- Meta field: limit {{{ --\x3e\n\t\t\t<div class="query-container">\n\t\t\t\t\x3c!-- Root branch display {{{ --\x3e\n\t\t\t\t<div class="query-stem"><div></div></div>\n\t\t\t\t\x3c!-- }}} --\x3e\n\t\t\t\t\x3c!-- Path component {{{ --\x3e\n\t\t\t\t<div class="query-block">\n\t\t\t\t\t<div class="btn-group btn-block">\n\t\t\t\t\t\t<a class="btn btn-1 btn-block">\n\t\t\t\t\t\t\tLimited to\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\t\x3c!-- }}} --\x3e\n\t\t\t\t<div class="query-stem"><div></div></div>\n\t\t\t\t\x3c!-- Query operand component {{{ --\x3e\n\t\t\t\t<div class="query-block btn-group">\n\t\t\t\t\t<div class="btn btn-block btn-2">\n\t\t\t\t\t\t<input ng-model="$ctrl.query.limit" type="number" class="form-control"/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div class="query-stem"><div></div></div>\n\t\t\t\t<div class="query-block btn-group">\n\t\t\t\t\t<div class="btn btn-block btn-1">\n\t\t\t\t\t\tSkipping\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div class="query-stem"><div></div></div>\n\t\t\t\t<div class="query-block btn-group">\n\t\t\t\t\t<div class="btn btn-block btn-2">\n\t\t\t\t\t\t<input ng-model="$ctrl.query.skip" type="number" class="form-control"/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t\x3c!-- }}} --\x3e\n\t\t\t</div>\n\t\t\t\x3c!-- }}} --\x3e\n\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<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-container">\n\t\t\t<div class="query-stem"><div></div></div>\n\t\t\t\x3c!-- Path component {{{ --\x3e\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<div ng-click="$ctrl.remove(leaf.id); $event.stopPropagation()" class="btn btn-trash btn-danger btn-xs pull-left">\n\t\t\t\t\t\t\t<i class="fa fa-times"></i>\n\t\t\t\t\t\t</div>\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<div ng-show="leaf.valueOperand" class="query-stem"><div></div></div>\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<div ng-show="leaf.valueOperand" class="query-stem"><div></div></div>\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" 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<div class="query-container">\n\t\t\t<div class="query-stem"><div></div></div>\n\t\t\t<div class="query-block btn-group">\n\t\t\t\t<a ng-click="$ctrl.add()" class="btn btn-lg btn-add btn-default">\n\t\t\t\t\t<i class="fa fa-fw fa-plus fa-lg"></i>\n\t\t\t\t</a>\n\t\t\t</div>\n\t\t</div>\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,a){return n.spec[a]?n.spec[a]:"$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,a){return{id:a,value:t,valueEdit:n.getFlatValue(t),valueOperand:_.isObject(t)?_(t).keys().first():"$eq",isMeta:a.startsWith("$")||["sort","skip","limit"].includes(a),spec:n.getSpec(a,t,a),path:e.concat([a])}}).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 a=n.getFlatValue(t.value);t.valueOperand=e,t.valueEdit=a,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 a=_.isUndefined(e)?t.valueEdit:e;t.value=n.operandsByID[t.valueOperand].setter(a),t.valueEdit=n.getFlatValue(t.value),n.exportBranch()},n.setValueIncluded=function(t,e,n){var a=_(t.value).keys().first();if(!a)throw new Error("Tried to set array inclusion on non wrapped key: "+t.value);var l=t.value[a].includes(e);n&&!l?t.value[a].push(e):!n&&l&&(t.value[a]=t.value[a].filter(function(t){return t!=e})),t.value[a].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():(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({});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 a=e.valueEdit.includes(t.id);return n?!a:a})}}).directive("ngRepeatEmit",["$rootScope","$timeout",function(t,e){return{restrict:"A",link:function(t,n,a){!0===t.$last&&e(function(){return t.$emit(a.ngRepeatEmit)})}}}]),angular.module("angular-ui-query-builder").service("qbTableSettings",function(){return{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"}}}).directive("qbTable",function(){return{scope:{qbTable:"="},restrict:"AC",controller:["$scope","qbTableSettings",function(t,e){var n=this;n.query=t.qbTable,n.setField=function(e,a){if(void 0!=a)switch(e){case"sort":n.query.sort===a?n.query.sort="-"+a:(n.query.sort,n.query.sort=a);break;default:t.qbTable[e]=a}else delete n.query[e]}}]}}).directive("qbCol",function(){return{scope:{qbCol:"@",sortable:"@"},require:"^qbTable",restrict:"A",transclude:!0,controller:["$attrs","$scope","qbTableSettings",function(t,e,n){var a=this;e.qbTableSettings=n,e.canSort=!1,e.isSorted=!1,a.$onInit=function(){e.canSort=e.sortable||""===t.sortable},e.$watch("qbTable.query.sort",function(t){var n=e.sortable||e.q;t?angular.isArray(t)&&t.some(function(t){return t==n})||t==n?e.isSorted="asc":angular.isArray(t)&&t.some(function(t){return t=="-"+n})||t=="-"+n?e.isSorted="desc":e.isSorted=!1:e.isSorted=!1}),e.toggleSort=function(){e.sortable?e.qbTable.setField("sort",e.sortable):e.q&&""===t.sortable&&e.qbTable.setField("sort",e.q)}}],link:function(t,e,n,a){t.qbTable=a},template:'\n\t\t<ng-transclude></ng-transclude>\n\t\t<a ng-if="canSort" ng-click="toggleSort()" class="pull-right">\n\t\t\t<i class="{{\n\t\t\t\tisSorted == \'asc\' ? qbTableSettings.icons.sortAsc\n\t\t\t\t: isSorted == \'desc\' ? qbTableSettings.icons.sortDesc\n\t\t\t\t: qbTableSettings.icons.sortNone\n\t\t\t}}"></i>\n\t\t</a>\n\t'}}).directive("qbPagination",function(){return{scope:{},require:"^qbTable",restrict:"EA",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,a){t.qbTable=a},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<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'}}); |
var _ = require('lodash'); | ||
var babel = require('gulp-babel'); | ||
var concat = require('gulp-concat'); | ||
var cleanCSS = require('gulp-clean-css'); | ||
@@ -9,3 +10,2 @@ var ghPages = require('gulp-gh-pages'); | ||
var nodemon = require('gulp-nodemon'); | ||
var rename = require('gulp-rename'); | ||
var rimraf = require('rimraf'); | ||
@@ -47,4 +47,9 @@ var uglify = require('gulp-uglify'); | ||
gulp.task('js', ()=> | ||
gulp.src('./src/angular-ui-query-builder.js') | ||
gulp.task('js', ['js:all', 'js:core', 'js:tables']); | ||
gulp.task('js:all', ()=> | ||
gulp.src([ | ||
'./src/angular-ui-query-builder-core.js', | ||
'./src/angular-ui-query-builder-tables.js', | ||
]) | ||
.pipe(plumber({ | ||
@@ -57,3 +62,3 @@ errorHandler: function(err) { | ||
})) | ||
.pipe(rename('angular-ui-query-builder.js')) | ||
.pipe(concat('angular-ui-query-builder.js')) | ||
.pipe(babel({ | ||
@@ -64,3 +69,3 @@ presets: ['es2015'], | ||
.pipe(gulp.dest('./dist')) | ||
.pipe(rename('angular-ui-query-builder.min.js')) | ||
.pipe(concat('angular-ui-query-builder.min.js')) | ||
.pipe(uglify()) | ||
@@ -70,7 +75,52 @@ .pipe(gulp.dest('./dist')) | ||
gulp.task('css', ()=> | ||
gulp.src('./src/angular-ui-query-builder.css') | ||
.pipe(rename('angular-ui-query-builder.css')) | ||
gulp.task('js:core', ()=> | ||
gulp.src('./src/angular-ui-query-builder-core.js') | ||
.pipe(plumber({ | ||
errorHandler: function(err) { | ||
gutil.log(gutil.colors.red('ERROR DURING JS BUILD')); | ||
process.stdout.write(err.stack); | ||
this.emit('end'); | ||
}, | ||
})) | ||
.pipe(concat('angular-ui-query-builder-core.js')) | ||
.pipe(babel({ | ||
presets: ['es2015'], | ||
plugins: ['angularjs-annotate'], | ||
})) | ||
.pipe(gulp.dest('./dist')) | ||
.pipe(rename('angular-ui-query-builder.min.css')) | ||
.pipe(concat('angular-ui-query-builder-core.min.js')) | ||
.pipe(uglify()) | ||
.pipe(gulp.dest('./dist')) | ||
); | ||
gulp.task('js:tables', ()=> | ||
gulp.src('./src/angular-ui-query-builder-tables.js') | ||
.pipe(plumber({ | ||
errorHandler: function(err) { | ||
gutil.log(gutil.colors.red('ERROR DURING JS BUILD')); | ||
process.stdout.write(err.stack); | ||
this.emit('end'); | ||
}, | ||
})) | ||
.pipe(concat('angular-ui-query-builder-tables.js')) | ||
.pipe(babel({ | ||
presets: ['es2015'], | ||
plugins: ['angularjs-annotate'], | ||
})) | ||
.pipe(gulp.dest('./dist')) | ||
.pipe(concat('angular-ui-query-builder-tables.min.js')) | ||
.pipe(uglify()) | ||
.pipe(gulp.dest('./dist')) | ||
); | ||
gulp.task('css', ['css:all', 'css:core', 'css:tables']); | ||
gulp.task('css:all', ()=> | ||
gulp.src([ | ||
'./src/angular-ui-query-builder-core.css', | ||
'./src/angular-ui-query-builder-tables.css', | ||
]) | ||
.pipe(concat('angular-ui-query-builder.css')) | ||
.pipe(gulp.dest('./dist')) | ||
.pipe(concat('angular-ui-query-builder.min.css')) | ||
.pipe(cleanCSS()) | ||
@@ -80,2 +130,20 @@ .pipe(gulp.dest('./dist')) | ||
gulp.task('css:core', ()=> | ||
gulp.src('./src/angular-ui-query-builder-core.css') | ||
.pipe(concat('angular-ui-query-builder-core.css')) | ||
.pipe(gulp.dest('./dist')) | ||
.pipe(concat('angular-ui-query-builder-core.min.css')) | ||
.pipe(cleanCSS()) | ||
.pipe(gulp.dest('./dist')) | ||
); | ||
gulp.task('css:tables', ()=> | ||
gulp.src('./src/angular-ui-query-builder-tables.css') | ||
.pipe(concat('angular-ui-query-builder-tables.css')) | ||
.pipe(gulp.dest('./dist')) | ||
.pipe(concat('angular-ui-query-builder-tables.min.css')) | ||
.pipe(cleanCSS()) | ||
.pipe(gulp.dest('./dist')) | ||
); | ||
gulp.task('gh-pages', ['build'], function() { | ||
@@ -101,3 +169,3 @@ rimraf.sync('./gh-pages'); | ||
], {base: __dirname}) | ||
.pipe(rename(function(path) { | ||
.pipe(concat(function(path) { | ||
if (path.dirname == 'demo') { // Move all demo files into root | ||
@@ -104,0 +172,0 @@ path.dirname = '.'; |
{ | ||
"name": "@momsfriendlydevco/angular-ui-query-builder", | ||
"version": "1.1.8", | ||
"version": "1.2.0", | ||
"description": "MongoDB format query-builder UI component for Angular", | ||
@@ -24,2 +24,3 @@ "main": "dist/angular-ui-query-builder.js", | ||
"express": "^4.16.1", | ||
"faker": "^4.1.0", | ||
"font-awesome": "^4.7.0", | ||
@@ -29,6 +30,6 @@ "gulp": "^3.9.1", | ||
"gulp-clean-css": "^3.9.0", | ||
"gulp-concat": "^2.6.1", | ||
"gulp-gh-pages": "^0.5.4", | ||
"gulp-nodemon": "^2.2.1", | ||
"gulp-plumber": "^1.1.0", | ||
"gulp-rename": "^1.2.2", | ||
"gulp-uglify": "^3.0.0", | ||
@@ -43,4 +44,5 @@ "gulp-util": "^3.0.8", | ||
"jquery": "^3.2.1", | ||
"lodash": "^4.17.4" | ||
"lodash": "^4.17.4", | ||
"moment": "^2.20.1" | ||
} | ||
} |
@@ -39,2 +39,85 @@ angular-ui-query-builder | ||
API | ||
==== | ||
ui-query-builder (directive) | ||
---------------------------- | ||
Simply create a query object and link it up to the directive. | ||
In a controller: | ||
```javascript | ||
$scope.mySpec = { | ||
_id: {type: 'objectId'}, | ||
name: {type: 'string'}, | ||
email: {type: 'string'}, | ||
status: {type: 'string', enum: ['pending', 'active', 'deleted']}, | ||
}; | ||
$scope.myQuery = { | ||
status: 'active', // Assumes you have a status field | ||
limit: 10, | ||
skip: 0, | ||
}; | ||
``` | ||
In a HTML template: | ||
```html | ||
<ui-query-builder query="$ctrl.myQuery" spec="$ctrl.mySpec"></ui-query-builder> | ||
``` | ||
... or see the [Demo](https://momsfriendlydevco.github.io/angular-ui-query-builder). | ||
The ui-query-builder directive takes the following parameters: | ||
| Parameter | Type | Description | | ||
|-----------|--------|---------------------------------------------------------------------------------------| | ||
| `query` | Object | The current query, this object will be mutated into / from a MongoDB compatible query | | ||
| `spec` | Object | A base specification of field types to use when providing the UI | | ||
qb-table, qb-col, qb-pagination (directives) | ||
-------------------------------------------- | ||
If using either the full JS release (`angular-ui-query-builder.js`) or the table add-on (`angular-ui-query-builder-tables.js`) additional functionality is provided for Tables including column setup, pagination and other functionality. | ||
To use: | ||
1. Add the `qb-table` directive to the table header with a pointer to the query object to mutate | ||
2. (Optional) Add the `qb-col` directive to any table column to extend, include attributes like `sortable` to add that functionality | ||
3. (Optional) Add the `qb-pagination` directive into the table footer to add pagination functionality | ||
For example: | ||
```html | ||
<table class="table table-bordered table-striped table-hover" qb-table="query"> | ||
<thead> | ||
<tr> | ||
<th qb-col="name" sortable>Name</th> | ||
<th qb-col="username" sortable>Username</th> | ||
<th qb-col="email" sortable>Email</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
<tr ng-repeat="row in data track by row.id"> | ||
<td>{{row.name}}</td> | ||
<td>{{row.username}}</td> | ||
<td>{{row.email}}</td> | ||
</tr> | ||
</tbody> | ||
<tfoot> | ||
<tr> | ||
<td colspan="3"> | ||
<qb-pagination></qb-pagination> | ||
</td> | ||
</tr> | ||
</tfoot> | ||
</table> | ||
``` | ||
For a more complex example see the [demo](https://momsfriendlydevco.github.io/angular-ui-query-builder). | ||
TODO | ||
@@ -46,8 +129,8 @@ ==== | ||
* [ ] Compound queries - `$or` / `$and` | ||
* [ ] Automatically moving from a static string (`$eq` condition) to a multiple choice enum (`$in`) when a comma is used in a string | ||
* [x] Automatically moving from a static string (`$eq` condition) to a multiple choice enum (`$in`) when a comma is used in a string | ||
* [ ] Convert string ENUMs to a `$in` type automatically | ||
* [ ] Number filtering - above, below, between | ||
* [ ] Date support - date selector, before, after | ||
* [x] Date support - date selector, before, after | ||
* [ ] Nicer syntax support for `$regexp` | ||
* [ ] Support for `$length` | ||
* [ ] Nicer multi level path support |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses eval() which is a dangerous function. This prevents the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
1352231
41
5547
135
5
17
1
1
3
+ Addedmoment@^2.20.1
+ Addedmoment@2.30.1(transitive)