Filtering lists
Often, a simple SELECT * FROM <table> is too basic for a proper admin. This is
why Monad supports list filters. These allow you to pass extra parameters to
the resource query, e.g. {"someStatus":"1"}.
Defining a filter
Just place some HTML in your list template - usually between the header and
the table, but wherever really. Bind the form elements' ng-models to keys
on $ctrl.filter and the default moListController will pick them up!
If you need to use your own controller, you'll have to implement this yourself of course.
An example:
<mo-list-header ...></mo-list-header>
<form ng-submit="$ctrl.applyFilter()"><fieldset>
<label>
<input ng-model="$ctrl.filter.deleted"
ng-true-value="1"
ng-false-value="0"
type="checkbox">
Show only deleted items
</label>
<button type="submit">Apply</button>
</fieldset></form>
<mo-list-table ...></mo-list-table>
Depending on your $resource implementation, this could result in a query such
as /some/url?deleted=1.
On re-filtering, Monad jumps back to page 1 since it has no way of knowing beforehand the current page will still be available after the new filter is applied. (TODO: use Manager.count so we CAN know this?)
Default filter
To apply default values to a filter ("initial state"), simply add it as a binding to your list component:
// ...
template: '<my-list ... filter="{deleted: 1}"></my-list>',
bindings: { ... , filter: '<'}
// ...
Auto-filtering
Manually calling applyFilter() makes perfect sense when your filter is a text
input ("search"), but for checkboxes or radios you might want to automatically
refresh the list when anything changes. Simply bind $ctrl.applyFilter() to an
ng-change directive on the input(s) in question in that case.
Pagination
The most ubiquitous "filter" is of course pagination. This is done seperately
from the "normal" filters mentioned above, since one would also want to be able
to paginate filtered lists! And besides, it's customary to place a filter
above the found rows and pagination below them. But to visualize in database
terms: a filter is a WHERE clause, whereas pagination are LIMIT/OFFSET
options.
To be able to paginate, you need to tell Monad the total number of items in a
list. How you do that (or if it's even possible with your API...) is not up to
Monad. Monki supports an (optionally filtered) API call to /count/ for
this; other APIs could implement their own logic.
But, assuming you've defined a count property on your list controller, it's
stock (Angular) Bootstrap from there:
<div class="text-center" ng-if="$ctrl.count > 10">
<uib-pagination total-items="$ctrl.count" ng-model="$ctrl.page" boundary-links="true" max-size="10"></uib-pagination>
</div>
To define such a property, one would extend the module definition like so:
// The route template:
template: '<my-module-list resource="$resolve.resource" count="$resolve.count.data"></my-module-list>'
// The resolves:
{
resource: ['moResource', moResource => moResource('/api/url/')],
count: ['$http', $http => $http.get('/api/url/for/count/')]
}
// The component:
app.component('myModuleList', {
bindings: {resource: '<', count: '<'},
// Further component definitions.
});
The property can either come from a resolve or be something on a custom controller - whatever your preference is.
Note that count.data syntax in the template binding; resolves using
$http get passed the "raw" success data, which is an object incluing headers
and the retrieved data on a data property. Of course you could also use
e.g. a $q promise to directly pass the count, and maybe your API returns
additional data in that call (so would refer to count.data.someProperty).
Any moListController can optionally receive a pageSize property (via
directive injection, e.g. <my-list-component page-size="10">). This defaults
to 10 which is reasonable in most cases, but this way you can override it in
specific cases.
Don't forget to add pageSize to your component's bindings in the definition.
Generically extending resources for filtering
To extend a common count (or other...) implementation for your API, simply use
a custom resource factory that extends moResource. Sample implementation:
app.factory('resource', ['moResource', function (moResource) {
return function (url, paramDefaults, actions, options) {
paramDefaults = paramDefaults || {};
actions = actions || {};
options = options || {};
actions.count = {
method: 'get',
url: url.replace(/\/:id\/$/, '/') + 'count/'
};
var resource = moResource(url, paramDefaults, actions, options);
// Monki expects limit/order/offset in an "options" JSON object, so
// let's generically rewrite the `query` method too:
let query = resource.query;
resource.query = function (parameters, success, error) {
var newparameters = {filter: {}, options: {}};
for (let i in parameters) {
if (i == 'filter') {
newparameters.filter = parameters[i];
} else {
newparameters.options[i] = parameters[i];
}
}
return query.call(resource, newparameters, success, error);
};
return resource;
};
}]);
If your admin talks to multiple APIs (say wordpress and facebook), just add
and inject extensions in the components where you need them:
app.factory('wordpressResource', ['moResource', ...]);
app.factory('facebookResource', ['moResource', ...]);