backbone-query

A lightweight (3KB minified) utility for Backbone projects, that works in the Browser and on the Server.
Adds the ability to search for models with a Query API similar to
MongoDB
Please report any bugs, feature requests in the issue tracker.
Pull requests are welcome!
Usage
Client Side Installation:
To install, include the js/backbone-query.min.js
file in your HTML page, after Backbone and it's dependencies.
Then extend your collections from Backbone.QueryCollection rather than from Backbone.Collection.
Server side (node.js) installation
You can install with NPM: npm install backbone-query
Then simply require in your project: QueryCollection = require("backbone-query").QueryCollection
Your collections will now have two new methods: query
and where
. Both methods accept 2 arguments -
a query object and an options object. The query
method returns an array of models, but the where
method
returns a new collection and is therefore useful where you would like to chain multiple collection
methods / where queries. The following are some basic examples:
MyCollection.query({
featured:true,
likes: {$gt:10}
});
MyCollection.query(
{tags: { $any: ["coffeescript", "backbone", "mvc"]}},
{sortBy: "likes", order: "desc", limit:10, page:2, cache:true}
);
MyCollection.query({
$and:{
title: {$like: "news"},
likes: {$gt: 10}
},
$or:{
featured: true,
category:{$in:["code","programming","javascript"]}
}
});
Or if CoffeeScript is your thing (the source is written in CoffeeScript), try this:
MyCollection.query
$and:
likes: $lt: 15
$or:
content: $like: "news"
featured: $exists: true
$not:
colors: $contains: "yellow"
Another CoffeeScript example, this time using where
rather than query
query =
$likes: $lt: 10
$downloads: $gt: 20
MyCollection.where(query).my_custom_collection_method()
Query API
$equal
Performs a strict equality test using ===
. If no operator is provided and the query value isn't a regex then $equal
is assumed.
MyCollection.query({ title:"Test" });
MyCollection.query({ title: {$equal:"Test"} });
$contains
Assumes that the model property is an array and searches for the query value in the array
MyCollection.query({ colors: {$contains: "red"} });
$ne
"Not equal", the opposite of $equal, returns all models which don't have the query value
MyCollection.query({ title: {$ne:"Test"} });
$lt, $lte, $gt, $gte
These conditional operators can be used for greater than and less than comparisons in queries
MyCollection.query({ likes: {$lt:10} });
MyCollection.query({ likes: {$lte:10} });
MyCollection.query({ likes: {$gt:10} });
MyCollection.query({ likes: {$gte:10} });
$between
To check if a value is in-between 2 query values use the $between operator and supply an array with the min and max value
MyCollection.query({ likes: {$between:[5,15} });
$in
An array of possible values can be supplied using $in, a model will be returned if any of the supplied values is matched
MyCollection.query({ title: {$in:["About", "Home", "Contact"] } });
$nin
"Not in", the opposite of $in. A model will be returned if none of the supplied values is matched
MyCollection.query({ title: {$nin:["About", "Home", "Contact"] } });
$all
Assumes the model property is an array and only returns models where all supplied values are matched.
MyCollection.query({ colors: {$all:["red", "yellow"] } });
$any
Assumes the model property is an array and returns models where any of the supplied values are matched.
MyCollection.query({ colors: {$any:["red", "yellow"] } });
$size
Assumes the model property has a length (i.e. is either an array or a string).
Only returns models the model property's length matches the supplied values
MyCollection.query({ colors: {$size:2 } });
$exists or $has
Checks for the existence of an attribute. Can be supplied either true or false.
MyCollection.query({ title: {$exists: true } });
MyCollection.query({ title: {$has: false } });
$like
Assumes the model attribute is a string and checks if the supplied query value is a substring of the property.
Uses indexOf rather than regex for performance reasons
MyCollection.query({ title: {$like: "Test" } });
$likeI
The same as above but performs a case insensitive search using indexOf and toLowerCase (still faster than Regex)
MyCollection.query({ title: {$likeI: "Test" } });
$regex
Checks if the model attribute matches the supplied regular expression. The regex query can be supplied without the $regex
keyword
MyCollection.query({ content: {$regex: /coffeescript/gi } });
MyCollection.query({ content: /coffeescript/gi });
$cb
A callback function can be supplied as a test. The callback will receive the attribute and should return either true or false.
this
will be set to the current model, this can help with tests against computed properties
MyCollection.query({ title: {$cb: function(attr){ return attr.charAt(0) === "c";}} });
MyCollection.query({ computed_test: {$cb: function(){ return this.computed_property() > 10;}} });
For callbacks that use this
rather than the model attribute, the key name supplied is arbitrary and has no
effect on the results. If the only test you were performing was like the above test it would make more sense
to simply use MyCollection.filter
. However if you are performing other tests or are using the paging / sorting /
caching options of backbone query, then this functionality is useful.
Combined Queries
Multiple queries can be combined together. By default all supplied queries must be matched $and
. However it is possible
to specify either $or
, $nor
, $not
to implement alternate logic.
$and
MyCollection.query({ $and: { title: {$like: "News"}, likes: {$gt: 10}}});
MyCollection.query({ title: {$like: "News"}, likes: {$gt: 10} });
$or
MyCollection.query({ $or: { title: {$like: "News"}, likes: {$gt: 10}}});
$nor
The opposite of $or
MyCollection.query({ $nor: { title: {$like: "News"}, likes: {$gt: 10}}});
$not
The opposite of $and
MyCollection.query({ $not: { title: {$like: "News"}, likes: {$gt: 10}}});
Compound Queries
It is possible to use multiple combined queries, for example searching for models that have a specific title attribute,
and either a category of "abc" or a tag of "xyz"
MyCollection.query({
$and: { title: {$like: "News"}},
$or: {likes: {$gt: 10}, color:{$contains:"red"}}
});
Sorting
Optional sortBy
and order
attributes can be supplied as part of an options object.
sortBy
can either be a model key or a callback function which will be called with each model in the array.
MyCollection.query({title: {$like: "News"}}, {sortBy: "likes"});
MyCollection.query({title: {$like: "News"}}, {sortBy: "likes", order:"desc"});
MyCollection.query(
{title: {$like: "News"}},
{sortBy: function(model){ return model.get("title").charAt(1);}}
);
Paging
To return only a subset of the results paging properties can be supplied as part of an options object.
A limit
property must be supplied and optionally a offset
or a page
property can be supplied.
MyCollection.query({likes:{$gt:10}}, {limit:10});
MyCollection.query({likes:{$gt:10}}, {limit:10, offset:5});
MyCollection.query({likes:{$gt:10}}, {limit:10, page:2});
When using the paging functionality, you will normally need to know the number of pages so that you can render
the correct interface for the user. Backbone Query can send the number of pages of results to a supplied callback.
The callback should be passed as a pager
property on the options object. This callback will also receive the sliced
models as a second variable.
Here is a coffeescript example of a simple paging setup using the pager callback option:
class MyView extends Backbone.View
initialize: ->
@template = -> #templating setup here
events:
"click .page": "change_page"
query_collection: (page = 1) ->
#Collection should be passed in when the view is instantiated
@collection.query {category:"javascript"}, {limit:5, page:page, pager:@render_pages}
change_page: (e) =>
page_number = $(e.target).data('page_number')
@query_collection page_number
render_pages: (total_pages, results) =>
content = @template results
pages = [1..total_pages]
nav = """
<nav>
<span>Total Pages: #{total_pages}</span>
"""
for page in pages
nav += "<a href='#' data-page_number='#{page}'>#{page}</a>"
nav += "</nav>"
@$el.html content + nav
render: => @query_collection()
Caching Results
To enable caching set the cache flag to true in the options object. This can greatly improve performance when paging
through results as the unpaged results will be saved. This options is not enabled by default as if models are changed,
added to, or removed from the collection, then the query cache will be out of date. If you know
that your data is static and won't change then caching can be enabled without any problems.
If your data is dynamic (as in most Backbone Apps) then a helper cache reset method is provided:
reset_query_cache
. This method should be bound to your collections change, add and remove events
(depending on how your data can be changed).
Cache will be saved in a _query_cache
property on each collection where a cache query is performed.
MyCollection.query({likes:{$gt:10}}, {limit:10, page:1, cache:true});
MyCollection.query({likes:{$gt:10}}, {limit:10, page:2, cache:true});
var MyCollection = Backbone.QueryCollection.extend({
initialize: function(){
this.bind("change", this.reset_query_cache, this);
}
});
Author
Dave Tonge - davidgtonge