AngularJS + ag-grid: sticky/remembered selections with virtual paging/infinite scrolling - javascript

In an AngularJS application, I have an ag-grid that uses virtual paging/infinite scrolling to lazy-load rows from a dataset that is too large to show at once. I have turned on check-box selection in the first column, so that the user should be able to select individual rows for arbitrary application-specific actions.
The AngularJS application uses ui-router to control multiple views. So, building on the virtual-paging example with "sorting & filtering", with constructed data about Olympic winners, from the ag-grid documentation, I've further extended the code a bit. From index.html:
<body ng-controller="MainController" class="container">
<div ui-view="contents"></div>
</body>
and the following ui-router states:
myapp.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("example.page1")
$stateProvider
.state('example', {
abstract: true,
views: {
contents: {
template: '<div ui-view="example"></div>'
}
}
})
.state('example.page1', {
url: '/page1',
views: {
example: {
templateUrl: 'page1.html'
}
}
})
.state('example.page2', {
url: '/page2',
views: {
example: {
template: 'Go back to the <a ui-sref="example.page1">example grid</a>.'
}
}
});
});
where page1.html looks like the following:
<div ng-controller="GridController">
<div ag-grid="gridOptions" class="ag-fresh" style="height: 250px;"></div>
</div>
<div>
<h3>Selected rows:</h3>
<ul class="list-inline">
<li ng-repeat="row in currentSelection track by row.id">
<a ng-click="remove(row)">
<div class="badge">#{{ row.id }}, {{ row.athlete }}</div>
</a>
</li>
</ul>
</div>
<p>Go to <a ui-sref="example.page2">the other page</a>.</p>
What I want to accomplish:
That selections made in the ag-grid is remembered (sticky) when scrolling a (virtual) page out of view and back again, so that a user can select multiple rows on separate pages.
That the remembered selections are available outside the grid, and support adding and removing selections (as intended by the ng-click="remove(row)" in page1.html, shown above).
That the selections should be remembered when switching from the view with the ag-grid to another one, and back again.
(Optional) That the selections are remembered for the user's session.
How can I accomplish this?

I've created a working example of this can be implemented.
First of all, we'll write a AngularJS service, selectionService to keep track of the selections:
function _emptyArray(array) {
while (array.length) {
array.pop();
}
}
function _updateSharedArray(target, source) {
_emptyArray(target);
_.each(source, function _addActivity(activity) {
target.push(activity);
});
}
myapp.factory('selectionService', function ($rootScope, $window) {
var _collections = {},
_storage = $window.sessionStorage,
_prefix = 'selectionService';
angular.element($window).on('storage', _updateOnStorageChange);
function _persistCollection(collection, data) {
_storage.setItem(_prefix + ':' + collection, angular.toJson(data));
}
function _loadCollection(collection) {
var item = _storage.getItem(_prefix + ':' + collection);
return item !== null ? angular.fromJson(item) : item;
}
function _updateOnStorageChange(event) {
var item = event.originalEvent.newValue;
var keyParts = event.originalEvent.key.split(':');
if (keyParts.length < 2 || keyParts[0] !== _prefix) {
return;
}
var collection = keyParts[1];
_updateSharedArray(_getCollection(collection), angular.fromJson(item));
_broadcastUpdate(collection);
}
function _broadcastUpdate(collection) {
$rootScope.$emit(_service.getUpdatedSignal(collection));
}
function _afterUpdate(collection, selected) {
_persistCollection(collection, selected);
_broadcastUpdate(collection);
}
function _getCollection(collection) {
if (!_.has(_collections, collection)) {
var data = _loadCollection(collection);
// Holds reference to a shared array. Only mutate, don't replace it.
_collections[collection] = data !== null ? data : [];
}
return _collections[collection];
}
function _add(item, path, collection) {
// Add `item` to `collection` where item will be identified by `path`.
// For example, path could be 'id', 'row_id', 'data.athlete_id',
// whatever fits the row data being added.
var selected = _getCollection(collection);
if (!_.any(selected, path, _.get(item, path))) {
selected.push(item);
}
_afterUpdate(collection, selected);
}
function _remove(item, path, collection) {
// Remove `item` from `collection`, where item is identified by `path`,
// just like in _add().
var selected = _getCollection(collection);
_.remove(selected, path, _.get(item, path));
_afterUpdate(collection, selected);
}
function _getUpdatedSignal(collection) {
return 'selectionService:updated:' + collection;
}
function _updateInGridSelections(gridApi, path, collection) {
var selectedInGrid = gridApi.getSelectedNodes(),
currentlySelected = _getCollection(collection),
gridPath = 'data.' + path;
_.each(selectedInGrid, function (node) {
if (!_.any(currentlySelected, path, _.get(node, gridPath))) {
// The following suppressEvents=true flag is ignored for now, but a
// fixing pull request is waiting at ag-grid GitHub.
gridApi.deselectNode(node, true);
}
});
var selectedIdsInGrid = _.pluck(selectedInGrid, gridPath),
currentlySelectedIds = _.pluck(currentlySelected, path),
missingIdsInGrid = _.difference(currentlySelectedIds, selectedIdsInGrid);
if (missingIdsInGrid.length > 0) {
// We're trying to avoid the following loop, since it seems horrible to
// have to loop through all the nodes only to select some. I wish there
// was a way to select nodes/rows based on an id.
var i;
gridApi.forEachNode(function (node) {
i = _.indexOf(missingIdsInGrid, _.get(node, gridPath));
if (i >= 0) {
// multi=true, suppressEvents=true:
gridApi.selectNode(node, true, true);
missingIdsInGrid.splice(i, 1); // Reduce haystack.
if (!missingIdsInGrid.length) {
// I'd love for `forEachNode` to support breaking the loop here.
}
}
});
}
}
var _service = {
getCollection: _getCollection,
add: _add,
remove: _remove,
getUpdatedSignal: _getUpdatedSignal,
updateInGridSelections: _updateInGridSelections
};
return _service;
});
The selectionService service allows adding and removing arbitrary objects to separate collections, identified by collection, a name you find suitable. This way the same service can be used for remembering selections in multiple ag-grid instances. Each object will be identified using a path parameter. The path is used to retrieve the unique identifier using lodash's get function.
Furthermore, the service uses sessionStorage to persist the selections during the user's whole tab/browser session. This might be overkill; we could have just relied on the service to keep track of the selections since it will only get instantiated once. This can of course be modified to your needs.
Then there were the changes that had to be done to the GridController. First of all the columnDefs entry for the first column had to be changed slightly
var columnDefs = [
{
headerName: "#",
width: 60,
field: 'id', // <-- Now we use a generated row ID.
checkboxSelection: true,
suppressSorting: true,
suppressMenu: true
}, …
where the new, generated row ID is generated once the data has been retrieved from the remote server
// Add row ids.
for (var i = 0; i < allOfTheData.length; i++) {
var item = allOfTheData[i];
item.id = 'm' + i;
}
(The 'm' in the ID was included just to make sure I didn't confused that ID with other IDs used by ag-grid.)
Next, the necessary changes to gridOptions were to add
{
…,
onRowSelected: rowSelected,
onRowDeselected: rowDeselected,
onBeforeFilterChanged: clearSelections,
onBeforeSortChanged: clearSelections,
…
}
Were the different handlers are quite straight forward, communicating with the selectionService
function rowSelected(event) {
selectionService.add(event.node.data, 'id', 'page-1');
}
function rowDeselected(event) {
selectionService.remove(event.node.data, 'id', 'page-1');
}
function clearSelections(event) {
$scope.gridOptions.api.deselectAll();
}
Now, the GridController needs to handle updates signalled by the selectionService too
$scope.$on('$destroy',
$rootScope.$on(selectionService.getUpdatedSignal('page-1'),
updateSelections));
and
function updateSelections() {
selectionService.updateInGridSelections($scope.gridOptions.api, 'id', 'page-1');
}
calls selectionService.updateInGridSelections which will update the in-grid selections of the grid in question. That was the most cumbersome function to write. For example, if a selection has been added externally (outside the grid), then we'll have to perform a forEachNode run, even if we know all the necessary nodes have already been selected in-grid; there's no way to exit that loop early.
Finally, another crux was to clear and reapply the selections before and after, respectively, when the filters or sort orders are changed, or when new data is retrieved from the server (which is only simulated in the demo). The solution was to include a call to updateSelections after the params.successCallback inside the getRows handler
params.successCallback(rowsThisPage, lastRow);
updateSelections();
Now, the most puzzling findings during the implementation of this solution was that the ag-grid API grid options onAfterFilterChanged and onAfterSortChanged couldn't be used for reapplying the selections because they trigger before the (remote) data has finished loading.

Related

Ext JS 4.2.1 - grid with paging - checkbox state lost

I have an ExtJS grid that has a PagingToolbar for (remote) paging, and a checkbox column defined as:
{
dataIndex : 'selected',
xtype: 'checkcolumn',
width : 60
}
However, if I check a box and then page forwards and backwards, the checkbox state is not saved - all the checkboxes are unchecked.
I guess since the store only contains data for the current page (from the server), I need some way of storing the state for rows that are not in the current page of data, and then reinstating the checkboxes when I return to that page.
Is there a best practice, or example of storing the checkbox state in the store while paging?
Well, that's so low-level work that no one has yet thought to make a "best practice" for it. E.g.
beforeload:function(store) {
if(!store.checkedItems) store.checkedItems = [];
store.each(function(item) {
store.checkedItems[item.get("Id")] = item.get("selected");
});
},
load:function(store) {
if(!store.checkedItems) store.checkedItems = [];
store.each(function(item) {
item.set("selected",store.checkedItems[item.get("Id")]);
});
}
or
beforeload:function(store) {
if(!store.checkedItems) store.checkedItems = [];
store.each(function(item) {
store.checkedItems[item.get("Id")] = {selected: item.get("selected") }; // extensible if you want to keep more than one checkbox...
});
},
load:function(store) {
if(!store.checkedItems) store.checkedItems = [];
store.each(function(item) {
item.set(store.checkedItems[item.get("Id")]);
});
}

Render angular directives in Selectize.js items

I am using angular-selectize to use Selectize.js in my angular project.
To use custom items in Selectize.js selector, I am using Selectize.js' render option:
render: {
item: function(item, escape) {
var avatar = '<div>' +
'<span avatars="\'' + escape(item._id) +'\'" class="avatars">' +
'</span>' +
escape(item.nick) +
'</div>';
var compiledAvatar = $compile(avatar)($rootScope);
$timeout();
return compiledAvatar.html();
},
where avatars is a custom directive with asychronous behaviour
The problem is that the render.item function expects an HTML string as an output but:
There is no way of returning a rendered or "$compileed" HTML string in a synchronous way as expected by render.item method.
I do not know how to render that item's elements afterwards when they have already been added to the DOM.
Note that although $compile is called, returned string would not be the expected compiled result but the string before compilation due to the asynchronous nature of $compile.
One idea is to use DOM manipulation which is not the most recommended Angular way, but I got it working on this plunker. and a second one with custom directive and randomized data to simulate your compiled avatar.
To simulate your asynchronous call, I use ngResource. My render function returns a string "<div class='compiledavatar'>Temporary Avatar</div>" with a special class markup compiledavatar. For a second or two, you will see Temporary Avatar as you select an element. When the ngResource calls finishes I look for the element with class compiledavatar and then replace the html with what I downloaded. Here is the full code:
var app = angular.module('plunker', ['selectize', 'ngResource']);
app.controller('MainCtrl', function($scope, $resource, $document) {
var vm = this;
vm.name = 'World';
vm.$resource = $resource;
vm.myModel = 1;
vm.$document = $document;
vm.myOptions = [{
id: 1,
title: 'Spectrometer'
}, {
id: 2,
title: 'Star Chart'
}, {
id: 3,
title: 'Laser Pointer'
}];
vm.myConfig = {
create: true,
valueField: 'id',
labelField: 'title',
delimiter: '|',
placeholder: 'Pick something',
onInitialize: function(selectize) {
// receives the selectize object as an argument
},
render: {
item: function(item, escape) {
var label = item.title;
var caption = item.id;
var Stub = vm.$resource('mydata', {});
// This simulates your asynchronous call
Stub.get().$promise.then(function(s) {
var result = document.getElementsByClassName("compiledavatar")
angular.element(result).html(s.compiledAvatar);
// Once the work is done, remove the class so next time this element wont be changed
// Remove class
var elems = document.querySelectorAll(".compiledavatar");
[].forEach.call(elems, function(el) {
el.className = el.className.replace(/compiledavatar/, "");
});
});
return "<div class='compiledavatar'>Temporary Avatar</div>"
}
},
// maxItems: 1
};
});
To simulate the JSON API I just created a file in plunker mydata:
{
"compiledAvatar": "<div><span style='display: block; color: black; font-size: 14px;'>an avatar</span></div>"
}
Of course your compiled function should return you something different every calls. Me it gives me the same to demonstrate the principle.
In addition, if your dynamic code is an Agular directive, here is a second plunker with a custom directive and randomized data so you can better see the solution:
The data include a custom directive my-customer:
[{
"compiledAvatar": "<div><span style='display: block; color: black; font-size: 14px;'>an avatar #1 <my-customer></my-customer></span></div>"
},
{
"compiledAvatar": "<div><span style='display: block; color: black; font-size: 14px;'>an avatar #2 <my-customer></my-customer></span></div>"
},
(...)
The directive is defined as:
app.directive('myCustomer', function() {
return {
template: '<div>and a custom directive</div>'
};
});
And the main difference in the app is that you have to add $compile when replacing the HTML and the text should show An avatar #(number) and a custom directive. I get an array of json value and use a simple random to pick a value. Once the HTML is replaced I remove the class, so next time only the last added element will be changed.
Stub.query().$promise.then(function(s) {
var index = Math.floor(Math.random() * 10);
var result = document.getElementsByClassName("compiledavatar")
angular.element(result).html($compile(s[index].compiledAvatar)($scope));
// Remove class
var elems = document.querySelectorAll(".compiledavatar");
[].forEach.call(elems, function(el) {
el.className = el.className.replace(/compiledavatar/, "");
});
});
Also, I looked at selectize library and you cant return a promise... as it does a html.replace on the value returned by render. This is why I went to the route of a temporary string with a class to retrieve later and update.
Let me know if that helps.
This answer is based in the helpful answer by #gregori with the following differences:
Take into account Selectize.js' Render Cache. The standard behaviour of Selectize.js is that the items are cached as returned by the render function, and not with the modifications we have done to them. After adding and deleting some elements, the cached and not the modified version would be displayed if we do not update the render cache acordingly.
Using random id's to identify the elements to select to be manipulated from DOM.
Using watchers to know when the compilation has been done
First, we define a method to modify the selectize.js render cache:
scope.selectorCacheUpdate = function(key, value, type){
var cached = selectize.renderCache[type][key];
// update cached element
var newValue = angular.element(cached).html(value);
selectize.renderCache[type][key] = newValue[0].outerHTML;
return newValue.html();
};
Then, the render function is defined as follows:
function renderAvatar(item, escape, type){
// Random id used to identify the element
var randomId = Math.floor(Math.random() * 0x10000000).toString(16);
var avatar =
'<div id="' + randomId + '">' +
'<span customAvatarTemplate ...></span>' +
...
'</div>';
var compiled = $compile(avatar)($rootScope);
// watcher to see when the element has been compiled
var destroyWatch = $rootScope.$watch(
function (){
return compiled[0].outerHTML;
},
function (newValue, oldValue){
if(newValue !== oldValue){
var elem = angular.element(document.getElementById(randomId));
var rendered = elem.scope().selectorCacheUpdate(item._id, compiled.html(), type);
// Update DOM element
elem.html(rendered);
destroyWatch();
}
}
);
});
return avatar;
}
Note: The key for the render cache is the valueField of the selectize items, in this case, _id
Finally, we add this function as a selectize render function in the selectize configuration object:
config = {
...
render: {
item: function(i,e){
return renderAvatar(i, e, 'item');
},
option: function(i,e){
return renderAvatar(i, e, 'option');
}
},
...
}
For more details, see how this solution has been added to the application that motivated this question: https://github.com/P2Pvalue/teem/commit/968a437e58c5f1e70e80cc6aa77f5aefd76ba8e3.

Kendo UI Web - MultiSelect: select an option more than once

I'm currently facing a problem with the Kendo UI MultiSelect widget for selecting an option more than once. For example, in the image below I want to select Schindler's List again after selecting The Dark Knight, but unfortunately the MultiSelect widget behaves more like a set than an ordered list, i.e. repetitive selection is not allowed. Is there actually a proper way to achieve this? Any workarounds?
That's the intended behavior of the multi-select control and there is no simple way to make it do what you want using the available configuration options. Possible workarounds are ...
Creating a custom multi-select widget
Something like this should work (note that I haven't tested this much - it lets you add multiples and keeps the filter intact though):
(function ($) {
var ui = kendo.ui,
MultiSelect = ui.MultiSelect;
var originalRender = MultiSelect.fn._render;
var originalSelected = MultiSelect.fn._selected;
var CustomMultiSelect = MultiSelect.extend({
init: function (element, options) {
var that = this;
MultiSelect.fn.init.call(that, element, options);
},
options: {
name: 'CustomMultiSelect'
},
_select: function (li) {
var that = this,
values = that._values,
dataItem,
idx;
if (!that._allowSelection()) {
return;
}
if (!isNaN(li)) {
idx = li;
} else {
idx = li.data("idx");
}
that.element[0].children[idx].selected = true;
dataItem = that.dataSource.view()[idx];
that.tagList.append(that.tagTemplate(dataItem));
that._dataItems.push(dataItem);
values.push(that._dataValue(dataItem));
that.currentTag(null);
that._placeholder();
if (that._state === "filter") {
that._state = "accept";
}
console.log(this.dataSource.view());
},
_render: function (data) {
// swapping out this._selected keeps filtering functional
this._selected = dummy;
return originalRender.call(this, data);
this._selected = originalSelected;
}
});
function dummy() { return null; }
ui.plugin(CustomMultiSelect);
})(jQuery);
Demo here.
Using a dropdown list
Use a simple dropdown list (or ComboBox) and bind the select event to append to your list (which you have to create manually).
For example:
var mySelectedList = [];
$("#dropdownlist").kendoDropDownList({
select: function (e) {
var item = e.item;
var text = item.text();
// store your selected item in the list
mySelectedList.push({
text: text
});
// update the displayed list
$("#myOrderedList").append("<li>" + text + "</li>");
}
});
Then you could bind clicks on those list elements to remove elements from the list. The disadvantage of that is that it requires more work to make it look "pretty" (you have to create and combine your own HTML, css, images etc.).

angularj ui grid searching

I'm looking for tutorial or example on how to implement a simple input text for searching
in the grid.
My attempt (but ng-keyup require angularjs > 1.1.3 and I've got
1.0.7)
<input type="text" ng-keyup="mySearch()" ng-model="searchText">
$scope.getPagedDataAsync = function (pageSize, page, searchText) {
setTimeout(function () {
var data;
if (searchText) {
var ft = searchText.toLowerCase();
$http.get('largeLoad.json?q='+encodeURIComponent(ft)).success(function (largeLoad) {
$scope.setPagingData(largeLoad,page,pageSize);
});
} else {
$http.get('largeLoad.json').success(function (largeLoad) {
$scope.setPagingData(largeLoad,page,pageSize);
});
}
}, 100);
};
$scope.mySearch = function(){
console.log($scope.searchText);
$scope.getPagedDataAsync($scope.pagingOptions.pageSize, $scope.pagingOptions.currentPage,$scope.searchText);
}
Bye
NB its a fake request against a json file just to make the example.
Update: I'm using ng-grid-1.3.2
Basically to solve this problem I think you can use a solution similar to what I've done below where I'm just watching the property of the model for changes and firing a function to do the filtering on the data set at that point.
The HTML for the text input
<input type="text" placeholder="Type to filter" ng-model="gardenModel.externalFilterText"/>
The JavaScript that filters the data set (also included the part I had a watch on a service to update the data in the first place too or if the data is refreshed to reapply the filter).
//This function is called every time the data is updated from the service or the filter text changes
$scope.filterGardens = function(filterText) {
//Basically everything in this function is custom filtering specific
//to the data set I was looking at if you want something closer to the
//real implementation you'll probably have to dig through the source (I believe they separated the search filter code into it's own file in the original project)
//Creating a temporary array so changes don't cause a bunch of firing of watchers
var tempToShow = [];
//doing case insensitive search so lower case the filter text
filterText = filterText.toLowerCase();
//If the filter text is blank just use the whole data set
if(!filterText || filterText == "")
{
$scope.gardenModel.shownGardens = $scope.gardenModel.gardens;
return;
}
//step through each entry in the main list and add any gardens that match
for (var i = 0; i < $scope.gardenModel.gardens.length; i++) {
var curEntry = $scope.gardenModel.gardens[i];
var curGarden = curEntry.curGarden;
if(curGarden["Garden Name"] && curGarden["Garden Name"].answer.toString().toLowerCase().indexOf(filterText)!=-1)
tempToShow.push(curEntry);
else if(curGarden["Address"] && curGarden["Address"].answer.toString().toLowerCase().indexOf(filterText)!=-1)
tempToShow.push(curEntry);
else if(curGarden["Ownership"] && curGarden["Ownership"].answer.toString().toLowerCase().indexOf(filterText)!=-1)
tempToShow.push(curEntry);
else if(curGarden.gardenId && curGarden.gardenId == filterText)
tempToShow.push(curEntry);
};
$scope.gardenModel.shownGardens = tempToShow;
}
//Watch for any changes to the filter text (this is bound to the input in the HTML)
$scope.$watch('gardenModel.externalFilterText', function(value) {
$scope.filterGardens(value);
});
//Watch for any changes on the service (this way if addition/edit are made and
//refresh happens in the service things stay up to date in this view, and the filter stays)
$scope.$watch( function () { return gardenService.gardens; }, function ( gardens ) {
$scope.gardenModel.gardens = gardens;
$scope.filterGardens($scope.gardenModel.externalFilterText);
});
Edit Cleaned up the code formatting a bit and added some comments.

Backbone.js Form Values Remembered in History

I'm new to the whole Backbone (and I'm actually not much of a JavaScript programmer yet), and I'm running into issues with a filter form.
Basically I have a database that my backbone collection gets populated from. On the page, there is a filter section where I can checkmark things such as: "Jobstatus: Running, Completed, Failed, etc..." Or input such as "User name: test0." All that works great until I started paginating my results.
I have a router set up to handle #page/:page. This works as well, say I go to page 2 with no filters checked, and then on page 2 I check some filters (which right now sets my results back to page 1 by using navigate(page/1)), when I hit my back button, I go back to page 2 (since that is where my no filter results were last on), but my filter box is still checked.
So if there are 10 rows per page and I have 14 results with no filters. I go to page 2 to see results 11-14, check a filter that returns me to page 1 with a total of 5 results, and then i hit back. I am now on page 2 of those filtered results seeing rows 11-5 of 5...
Is there anyway for the history to remember the form checkboxes and other inputs? So if I hit back in the situation I describe, it goes to page 2, but with the form filters removes as they were prior.
I was thinking I'd have to use routes for every value on the form... but hoping there is a better way of doing this (hopefully without rewriting the entire code as well).
Any help is appreciated.
I have two main views for input: one tied to the filter form, one for the pagination selection. The filter form view calls another view for the results (which is ties to a collection of rows).
Here is the filter form view:
var FilterForm = Backbone.View.extend({
events: {
"submit": "refreshData",
"change input": "refreshData"
},
initialize: function () {
this.refreshData();
},
refreshData: function () {
Backbone.history.navigate('page/1');
pageNumberModel.set('pageNumber', 1);
this.newPage();
},
newPage: function () {
var pageNumber = pageNumberModel.get('pageNumber');
var rowsPerPage = pageNumberModel.get('rowsPerPage');
var startRow = ((pageNumber * rowsPerPage) - rowsPerPage) + 1;
var endRow = startRow + (rowsPerPage - 1);
$('#startrow').val(startRow);
$('#endrow').val(endRow);
var inputData = this.$el.serializeArray();
var inputDatareduced = _(inputData).reduce(function (acc, field) {
acc[field.name] = field.value;
return acc;
});
$.get("Database.aspx", inputData, function (outputData) {
jobQueueRows.set($.parseJSON($.parseJSON(outputData)['JobQueue']));
jobQueueRowsView.render();
pageNumberModel.set($.parseJSON($.parseJSON(outputData)['Pagination']));
pageNumberView.render();
rowNumberView.render();
});
}
});
I would recommend creating a filterModel for your FilterForm view and using that to hold the state. Here is a reference implementation.
FilterModel:
var FilterModel = Backbone.Model.extend({
initialize: function(){
if( !this.get('filters'))
this.set({filters: new Array()});
},
applyFilter: function(key){
var filters = this.get("filters");
set("filters", _.union(filters, key));
return this;
},
removeFilter: function(key){
var filters = this.get("filters");
set("filters", _.without(filters, key));
return this;
},
defaults: {
fooFilters: [
{key: "size", label: "Filter By Size"},
{key: "color", label: "Past Week"}]
}
});
Bind a handler in your view to update the filter model:
filterClicked: function (e) {
var filter = $(e.target);
if(filter.is(':checked'))
this.model.applyFilter(filter.attr("id"));
else
this.model.removeFilter(filter.attr("id"));
},
Modify your filter template to apply the models state to your checkboxes when rendering:
<ul id="fooFilters" class="foo-filter-list">
<% _.each(fooFilters, function(f) { %>
<li>
<label for="<%=f.key%>"><%=f.name%></label>
<input type="checkbox" name="" value="" id="<%=f.key%>" <%if($.inArray(f.key, filters) === -1 {%>checked<%}%>/>
</li>
<% }); %>
</ul>

Categories

Resources