So I’ve been working on a project recently where I needed a simple predicate builder. Basically I needed a way to allow users to build a somewhat complex search using a GUI. And since we are using AngularJS on this project, here’s a quick article about how I did it.
Start with a simple AngularJS setup.
angular
.module('predicate', []);
angular
.module('predicate')
.factory('Condition', function() {
function Condition(data) {
if (data) {
this.setData(data);
}
}
Condition.prototype.setData = function(data) {
angular.extend(this, data);
};
Condition.prototype.searchKey = "";
Condition.prototype.searchValue = "";
return Condition;
});
angular
.module('predicate')
.controller('predicate', function ($scope, Condition) {
var defaultSearch = {"searchKey": "id", "searchValue": ""};
$scope.conditions = [new Condition(defaultSearch)];
});
Okay, so what we’ve done so far is stub out a simple AngularJS app, with a factory that builds a Condition object. This will be the model object that we use to hold data from our predicates. We also add a starting predicate so that there’s at least one condition to search by when the user opens the page.
Now, let’s add some HTML:
<body ng-app="predicate">
<div ng-controller="predicate">
<form ng-submit="doSearch()">
<div ng-repeat="condition in conditions">
<select ng-model="condition.searchKey">
<option value="id">ID</option>
<option value="name">Name</option>
<option value="email">Email</option>
</select>
<input type="text" ng-model="condition.searchValue">
<button type="button" ng-show="$last" ng-click="addCondition()">+</button>
<button type="button" ng-show="conditions.length > 1" ng-click="deleteCondition(condition)">-</button>
</div>
<button type="button" ng-click="doSearch()">Search</button>
</form>
</div>
</body>
So we’ve created a simple HTML stub for an AngularJS app. A couple of notes
about what we’re doing here. We only show the delete button if we have more than
one condition because we don’t want to get into a situation where we have no
conditions. We also only show the add button on the last condition, using the
magic variable $last
, which is available inside the ng-repeat
loop.
So if you fire this up, you should see a simple predicate already in place, because you already have one Condition object in the conditions array.
So now we need to make it do something.
angular
.module('predicate')
.controller('predicate', function ($scope, Condition) {
var defaultSearch = {"searchKey": "id", "searchValue": ""};
$scope.conditions = [new Condition(defaultSearch)];
$scope.addCondition = function() {
$scope.conditions.push(new Condition(defaultSearch));
}
$scope.deleteCondition = function(condition) {
$scope.conditions.splice($scope.conditions.indexOf(condition), 1);
}
});
Now, you should be able to add and remove conditions. Pretty neat! But how do we digest the objects we’re creating? Well, let’s do that now.
angular
.module('predicate')
.controller('predicate', function ($scope, Condition) {
var defaultSearch = {"searchKey": "id", "searchValue": ""};
$scope.conditions = [new Condition(defaultSearch)];
$scope.addCondition = function() {
$scope.conditions.push(new Condition(defaultSearch));
}
$scope.deleteCondition = function(condition) {
$scope.conditions.splice($scope.conditions.indexOf(condition), 1);
}
$scope.doSearch = function() {
var searches = {};
$scope.conditions.forEach(function(val) {
if (val.searchValue.length) {
searches[val.searchKey] = val.searchValue;
}
});
if (Object.keys(searches).length == 0) {
return;
}
console.log(searches);
}
});
So here, the new doSearch()
method we added to this scope loops through the
objects and builds a searches object. You can now use this object to query
whatever backend resource you would like. I feed it into $httpParamSerializer
to turn it into a query string for querying an API resource. Right now, we’re
just printing that to the log using console.log()
so you can see what it’s
doing.
So there you have it! Here’s a CodePen that demonstrates the whole thing. Enjoy!