A read-only proxy of a Backbone.Collection that can easily be filtered, sorted, and paginated.
Backbone.Obscura is a read-only proxy of a Backbone.Collection that can easily be filtered, sorted, and paginated, while still implementing all of the read-only Backbone.Collection methods. As the underlying collection is changed the proxy is efficiently kept in sync, taking into account all of the transformations. All transformations can be modified or removed at any time.
You can pass the proxy into a Backbone.View and let Backbone.Obscura take care of the filtering or paginating logic, leaving your view to only re-render itself as the collection changes. This keeps your views simple and DRY. This works particularly well with Marionette's CollectionView and CompositeView.
var proxy = originalCollection;// Set the transformations on the original collectionproxysetPerPage25setSort'age' 'desc'filterByreturn modelget'age' > 17 && modelget'age' < 70;;// Read-only Backbone.Collection functions work on the transformed proxyproxytoJSON;proxypluck'age';proxyat3;proxyfirst;// 'add', 'remove', 'change', 'reset' events are all forwarded for models in the proxyproxyon'add' /* ... */ ;// Pass the proxy to a view that knows how to react to a changing collectionvar view = collection: proxy ;// In another view or a controller, you can modify the state of the filtersif proxyhasNextPageproxynextPage;$'button'on'click'proxyreverseSort;;
The camera obscura is an optical device that projects an image of its surroundings on a screen. In a similar way, we are using a crude projection of the original collection to "draw" our views.
- You want to move pagination, filtering code out of your views
- You have several ways of filtering down an in-memory collection (find-as-you-type, value ranges) that all need to work together
- You have a collection that might be updated via push and need a filtered view to react to this change
- You have a centralized place where you manage your app's data, and need to visualize part of this data
- You want to have an additional view that presents a different view on the same data (Top 5, Latest)
This library is effectively a convenience wrapper around backbone-filtered-collection,
The original libraries are exposed on the
// backbone-filtered-collectionBackboneObscuraFilteredCollection// backbone-sorted-collectionBackboneObscuraSortedCollection// backbone-paginated-collectionBackboneObscuraPaginatedCollection
You can think of
Backbone.Obscura as a composition of these three transforms applied
one after the other.
var collection = /* data */;var filtered = collection;var sorted = filtered;var obscura = sorted;
Initialize a new Obscura collection by passing in the original collection.
var proxy = originalCollection;
You may also optionally pass an options hash. Currently the only supported option is setting the
perPage setting of the paginated transform.
var proxy = originalCollection perPage: 25 ;
Return a reference to the original collection.
Remove all filters, pagination settings, and sorting transforms. Afterwards the collection should be identical to the original collection.
Remove all ties to the superset and stop updating. Will now be garbage collected when it falls out of scope.
Apply a new filter to the set. Takes an optional filter name.
Can be a simple object that defines required key / value pairs.
filteredfilterBy'foo and bar filter' foo: 2 bar: 3 ;
Or the you can pass a filter function instead of a value.
filteredfilterBy'a > 2'return val > 2;;
Or say you wanted to narrow a value down to one of a couple of options:
filteredfilterBy'quality'return _contains 'better' 'best' val;;
Or you can use an arbitrary filter function on the model itself.
filteredfilterBy'age'return modelget'age' > 10 && modelget'age' < 40;;
Remove a previously applied filter. Accepts a filter name.
Removes all applied filters.
If the collections get out of sync (ex: change events have been suppressed) force the collection to refilter all of the models.
Can also be forced to run on one model in particular.
Returns a list of the names of applied filters.
Note: If added a filter with no name, it will show up here as
Given a string, return whether or not that filter is currently applied.
Return the length of the filtered set. This is useful when you have also paginated the collection and want to know how many items are in the unpaginated set.
- nothing or
null, resets the sorting to the same order as the superset
- a string, sorts by a model key
- a function that accepts a model and returns a value
direction must be one of:
"desc". If it's not provided it
will default to
// sort by the 'age' property descendingproxysetSort'age' 'desc';// equivalent to thisproxysetSortreturn modelget'age';'desc';// but we can also do arbitrary computation in the closureproxysetSortreturn someComplicatedCalculationmodel;;// Characters with accents get sorted to the end of the alphabet,// so let's sort based on the unaccented version.proxysetSortreturn removeAccentsmodelget'name';;// Pass nothing as an option to remove all sortingproxysetSort;
Remove all sorting. Equivalent to calling
Reverse the sort. The API is chainable, so this can be called directly
setSort if you want the sort to be descending.
If there is no current sort function then this does nothing.
// Sort by age descendingproxysetSort'age'reverseSort;
Change the number of models displayed per page. This will reset the current page to 0.
Change the page. If the page is less than 0, it will be set to 0. If it is longer than the number of pages, the last page will be selected.
Return the current setting for number of models per page.
Return the current number of pages.
Return the current page. E.G. if this returns 0, you're on the first page.
Returns true if this is not the last page.
Returns true if this is not the first page.
delta pages forwards or backwards (if
delta is negative).
// will move two pages backproxymovePage-2;
Move to the next page. Equivalent to
Move to the previous page. Equivalent to
Move to the first page of the collection. Equivalent to
Move to the last page of the collection. Equivalent to
paginated.setPage(paginated.getNumPages() - 1).
Get rid of any paginated settings.
reset should fire as you expect.
filtered:add - Fired when a new filter is added. Passes the filter name.
filtered:remove - Fired with a filter is removed. Passes the filter name.
filtered:reset - Fired when all of the filters are removed.
sorted:add - Trigger when a sort function is set
sorted:remove - Trigger when a sort function is removed
paginated:change:perPage - Fired whenever the number of models per page is changed. If you
remove the pagination settings,
perPage will be passed as
paginated:change:page - Fired whenever the page is changed.
paginated:change:numPages - Fired whenever the number of pages changes.
obscura:destroy - Fired when the proxy is destroyed
Install with npm, use with Browserify
> npm install backbone.obscura
and in your code
var Obscura = require'backbone.obscura';
Install with Bower:
bower install backbone.obscura
The component can be used as a Common JS module, an AMD module, or a global.
You can include
backbone.obscura.js directly in a script tag. Make
sure that it is loaded after underscore and backbone. It's exported as
<script src="underscore.js"></script><script src="backbone.js"></script><script src="backbone.obscura.js"></script>
Install Node (comes with npm) and Bower.
From the repo root, install the project's development dependencies:
npm installnpm start
To run the tests in Firefox, just once, as CI would:
There are several libraries that offer similar functionality, but none that offered the combination of features that I wanted.
- No need to define filters or sorting on initialization
- Ability to use arbitrary functions for filters or sorting
- Transparency, if no transforms are defined, the proxy should be the same as the original collection
- Ability to add and remove multiple filters
- Easy to use with Browserify, but also easy to throw into an AMD project
If this library doesn't meet your needs, maybe one of the following will:
Photo taken from Flickr, licensed under Creative Commons
Photo taken from Wikimedia, and is in the public domain