Backbone.Facetr is an utility which enables filtering of Backbone collections through facets. It can be used to implement faceted search, which follows the faceted classification system.
It works flawlessly up to 2500 items; computation starts getting slower with 5000 - 10000 items. This is however an early version; optimizations may improve performance in future realeases.
Production bundles can be found in the dist folder. Facetr is distributed via bower and npm. Both AMD and CommonJS are supported.
npm install backbone.facetr --save
bower install Backbone.Facetr --save
git clone [email protected]:arillo/Backbone.Facetr.git # clone repo
cd Backbone.Facetr # navigate to local repo
npm install -g grunt-cli # install grunt globally
npm install # install dependencies
grunt # build to dist folder
Include in your code using any of the following methods
define('yourmodule', ['backbone.facetr'], function(Facetr){
// ...
});
var Facetr = require('backbone.facetr');
<!-- deps -->
<script src="path/to/libs/backbone.js"></script>
<script src="path/to/libs/underscore.js"></script>
<!-- facetr -->
<script src="path/to/libs/backbone.facetr.js"></script>
// create a collection with few test items
var collection = new Backbone.Collection([
{
'Name' : {
'FirstName' : 'Bob',
'LastName' : 'Smith'
},
'Age' : 20,
'Country' : 'Australia',
'Hobbies' : ['fishing','painting','playing the ukulele'],
'Profession': 'manager'
},
{
'Name' : {
'FirstName' : 'Otto',
'LastName' : 'Von Braun'
},
'Age' : 35,
'Country' : 'New Zealand',
'Hobbies' : ['drawing', 'painting', 'shopping'],
'Profession': 'team manager'
},
{
'Name' : {
'FirstName' : 'Sarah',
'LastName' : 'Smith'
},
'Age' : 28,
'Country' : 'Ireland',
'Hobbies' : ['shopping','painting'],
'Profession': 'project manager'
}
]);
Facetr(collection).facet('Name.LastName').value('Smith'); // collection contains 'Sarah Smith' and 'Bob Smith'
Facetr(collection).facet('Hobbies').value('shopping'); // contains only 'Sarah Smith'
Facetr(collection).facet('Name.LastName').removeValue('Smith'); // contains 'Sarah Smith' and 'Otto Von Braun'
// removes all facet values and restores original collection content
Facetr(collection).clearValues();
// if chaining is not your cup of tea, the following is equivalent to the above code
var facetCollection = Facetr(collection); // returns a FacetCollection object
var lastNameFacet = facetCollection.facet('Name.LastName'); // returns a Facet object
var hobbiesFacet = facetCollection.facet('Hobbies');
lastNameFacet.value('Smith'); // returns a FacetExp object
hobbiesFacet.value('shopping');
lastNameFacet.removeValue('Smith');
// read the API Reference section for more
// most examples will use the above collection reference to illustrate functionalities
Syntax: PropertyName{1}(.PropertyName)*
In the context of Facetr, Dot Notation refers to the syntax used to define facets on a collection. Using the Facetr Dot Notation it is possible to define facets on properties of a Model, as well as on properties of its properties.
For example, consider the following model:
var model = new Backbone.Model({
'Name' : {
'FirstName' : 'John',
'LastName' : ['Smith','White']
},
'City' : 'London',
'Age' : 45,
'FamilyMembers' : [
{ 'Name' : 'Robert' },
{ 'Name' : 'Margaret' }
]
});
To add a facet on property 'FirstName' the following expression in Dot Notation syntax can be used: 'Name.FirstName'.
Facetr(collection).facet('Name.FirstName');
Theoretically there is no depth limit for a Dot Notation expression (e.g. PropertyName1.PropertyName2...PropertyNameN), the only limitation being the common sense.
A facet can only be added on a Backbone Model or object property having any of the following value types:
- string | number | boolean
- Array of strings
- Array of numbers
- Array of booleans
- Array of objects
- Array of Backbone models
It cannot be added on properties having the following value / composite types:
- object
- Backbone Collection
- Array of arrays
- Array of Backbone collections
.. and in any other case not mentioned in the list of allowed value types
Facetr(collection).facet('City'); // valid, City is a string
Facetr(collection).facet('Age'); // valid, Age is a Number
Facetr(collection).facet('Name.LastName'); // valid, LastName is an Array of strings
Facetr(collection).facet('FamilyMembers.Name'); // valid, takes value of Name of each object in array FamilyMembers
Facetr(collection).facet('Name'); // error, Name is an object
// etc..
Facets filtering can be combined using logical operators 'or' and 'and', both between different facets (external operator, see FacetCollection.facet method) and between values of one facet (internal operator, see Facet.value method and FacetExp).
// create a facet with default external operator 'and'
var facetCountry = Facetr(collection).facet('Country');
// or set the external operator explicitly
var facetLastName = Facetr(collection).facet('Name.LastName', 'and');
// when filtering, such operator will be used to combine filters of the two facets
facetCountry.value('Australia');
facetLastName.value('Smith');
// collection contains all models with Country equal 'Australia' and LastName equal 'Smith'
// add a value with default internal operator 'or'
facetCountry.value('Australia');
// or set the internal operator explicitly
facetCountry.value('Ireland', 'or');
// it is possible to use chaining syntax to combine values
facetCountry.value('Australia').or('Ireland');
// collection contains all models with Country equal either 'Australia' or 'Ireland'
Initialize a Collection to be used with Facetr. The first building block of any Facetr expression. Returns the created FacetCollection instance for method chaining. An id can be associated with the collection.
Example
Facetr(collection);
// or also
Facetr(collection, 'myCollection');
// which enables the following syntax
Facetr('myCollection') === Facetr(collection); // true
Adds a Facet on the given collection using the property refered to by the Dot Notation expression (see Dot Notation section for more details). Valid external operator values are: 'or' and 'and' (default is 'and'). Returns the created Facet instance to allow method chaining. Triggers a facet event with the facetName passed to the callback, unless true is passed as last parameter.
Example
Facetr(collection).on('facet', function(facetName) {
console.log('added facet', facetName);
});
// add facet on property 'Age' using default operator ('and')
Facetr(collection).facet('Age');
// add facet on property 'LastName' of object 'Name' using 'or' operator
Facetr(collection).facet('Name.LastName', 'or');
// console output would be
// added facet Age
// added facet Name.LastName
Returns an array containing objects representing the status of the facets added to the collection. Useful for rendering out facets lists. Each object in the array is the result of invoking toJSON on each Facet (see Facet documentation below for the Facet.toJSON method).
Example
// create a collection with two models
var collection = new Backbone.Collection([
{
'Name' : {
'FirstName' : 'Bob',
'LastName' : 'Smith'
},
'Age' : 20
},
{
'Name' : {
'FirstName' : 'Otto',
'LastName' : 'Von Braun'
},
'Age' : 35
}
]);
Facetr(collection).facet('Name.FirstName').label('First Name');
Facetr(collection).facet('Name.FirstName').value('Bob');
var json = Facetr(collection).toJSON();
// value of json will be an array of Facet data object with the following format:
//
// [
// {
// data : {
// extOperator : 'and',
// intOperator : 'or',
// label : 'First Name',
// name : 'Name.FirstName',
// selected : true,
// sort : {
// by : 'value',
// direction : 'asc'
// }
// customData : {}
// },
// values : [
// {
// active : true,
// activeCount : 1,
// count : 1,
// value : 'Bob'
// },
// {
// active : false,
// activeCount : 0,
// count : 1,
// value : 'Otto'
// }
// ]
// }
// ]
Removes all the facets added to the collection and unfilters it accordingly. Use this method to reset the original models in the collection. Triggers a clear event, unless true is passed as parameter.
Example
Facet(collection).on('clear', function() {
console.log('All facets were removed');
});
Facet(collection).clear();
// console output:
// All facets were removed
Removes all the facets and the facetrid added on the collection id upon Facetr(collection) initialization. Resets the original items in the collection.
Example
Facetr(collection).remove()
Sorts the collection according to the given attribute name. By default ascendent sort is used. See asc() and desc() methods below to define sort direction. Triggers sort event unless true is passed as last parameter. This method automatically recognizes string, numeric or date values.
Example
Facetr(collection).on('sort', function(attr, dir) {
console.log('Sorting by ' + attr + ' ' + dir);
});
Facetr(collection).sortBy('Age');
// console output:
// Sorting by Age asc
Sorts the collection in ascendent order by the attribute selected using sortBy method. If sortBy was not invoked before, this method has no effect. Triggers sort event unless true is passed as parameter.
Example
Facetr(collection).sortBy('Age').asc();
Sorts the collection in descendent order by the attribute selected using sortBy method. If sortBy was not invoked before, this method has no effect. Triggers sort event unless true is passed as parameter.
Example
Facetr(collection).sortBy('Age').desc();
Adds a filter which is used to filter the collection by testing each model against it. Triggers Backbone.Collection reset unless true is passed as last parameter. Multiple filters can be added as long as they have different names. Adding two filters with the same name will result in the first being overwritten by the second.
Example
Facetr(collection).addFilter('AgeFilter', function(model) {
return model.get('Age') >= 20 && model.get('Age') < 60;
});
Removes the filter with the given name from the collection and unfilters it accordingly. Triggers reset unless true is passed as last parameter.
Example
Facetr(collection).removeFilter('AgeFilter');
Removes all the filters previously added to the collection and unfilters it accordingly. Triggers reset unless true is passed as parameter.
Example
Facetr(collection).clearFilters();
Removes all the currently selected values from all the facets, bringing the collection to its initial state. Triggers a clearValues event unless true is passed as parameter.
Example
Facetr(collection).clearValues();
Sometimes it is convinient to give the facets list a predefined order. This method can be used to achieve this by passing an array of facet names which corresponds to the order to be given to the facets in the list. Triggers a 'facetsOrder' event with the facetNames array passed to the event handler, unless true is given as last parameter.
Example
Facetr(collection).facet('Age');
Facetr(collection).facet('Name.FirstName');
// Facetr(collection).toJSON() has facet 'Age' at index 0 and 'Name.FirstName' at index 1
Facetr(collection).facetsOrder(['Name.FirstName', 'Age']);
// Facetr(collection).toJSON() has facet 'Name.FirstName' at index 0 and 'Age' at index 1
Returns the reference to the Backbone.Collection. Useful for cases where the reference is needed but it was declared in another scope and is no more accessible.
Example
Facetr(collection).collection() === collection; // true
Returns the length of the collection before any faceted filtering was applied.
Example
var collection = new Backbone.Collection([
{
Name : 'John'
Age : 19
},
{
Name : 'Sarah',
Age : 35
}
]);
collection.length() // 2
Facetr(collection).facet('Age').value(35);
collection.length(); // 1
Facetr(collection).origLength(); // 2
Returns an array containing all the Facet instances created on this FacetCollection.
Returns an object representation of the current state of the Facetr collection which can be used to reload the same state in future using the initFromSettingsJSON method.
Example
Facetr(collection).facet('Name.FirstName').label('First Name');
Facetr(collection).sortBy('Name.FirstName').asc();
Facetr(collection).facet('Name.FirstName').value('Bob');
var json = Facetr(collection).settingsJSON();
// value of json will be the following:
//
// {
// sort : {
// by : "Name.FirstName",
// dir : "asc"
// },
// facets : [
// {
// attr : "Name.FirstName",
// eop : "and",
// iop : "or",
// vals : [ "Bob" ]
// }
// ]
// }
Initializes the Facetr collection using a settings object generated using the settingsJSON method. Triggers an "initFromSettingsJSON" event.
Adds a value to the facet. This will result in the collection being filtered by { FacetName : 'Value' }. Valid internal operator values are: 'or' and 'and' (default is 'or'). Triggers a 'filter' event on the FacetCollection passing facetName and facetValue to the handler and a 'value' event on the Facet passing the facetValue to the handler, unless true is passed as last parameter.
Example
Facetr(collection).on('filter', function(facetName, facetValue) {
console.log('filtered by '+ facetName + ' with value equal ' + facetValue);
});
var facet = Facetr(collection).facet('Name.FirstName');
facet.on('value', function(facetValue){
console.log('the following value was added to the facet: ' + facetValue);
});
facet.value('Bob');
// console output: "filtered by Name.FirstName with value Bob"
// collection contains only models with FirstName = 'Bob'
Removes the given value from the facet and resets the collection to the state previous of the filtering caused by the removed value. Triggers a 'unfilter' event on the FacetCollection passing facetName and facetValue to the handler and a 'removeValue' event on the Facet passing the facetValue to the handler, unless true is passed as last parameter.
Example
Facetr(collection).on('unfilter', function(facetName, facetValue) {
console.log('unfiltered by '+ facetName + ' with value equal ' + facetValue);
});
var facet = Facetr(collection).facet('Name.FirstName');
facet.on('removeValue', function(value){
console.log('the following value was removed from the facet: ' + value);
});
facet.removeValue('Bob');
// console output: "unfiltered by Name.FirstName with value Bob"
// collection contains again also models with FirstName = 'Bob'
Returns an object representation of the current facet data and values. Useful for rendering the facet to the page.
Example
// create a collection with two models
var collection = new Backbone.Collection([
{
'Name' : {
'FirstName' : 'Bob',
'LastName' : 'Smith'
},
'Age' : 20
},
{
'Name' : {
'FirstName' : 'Otto',
'LastName' : 'Von Braun'
},
'Age' : 35
}
]);
Facetr(collection).facet('Name.FirstName').label('First Name');
Facetr(collection).facet('Name.FirstName').value('Bob');
var json = Facetr(collection).facet('Name.FirstName').toJSON();
// json is equal to:
//
// {
// data : {
// extOperator : 'and',
// intOperator : 'or',
// label : 'First Name',
// name : 'Name.FirstName',
// selected : true,
// sort : {
// by : 'value',
// direction : 'asc'
// }
// customData : {}
// },
// values : [
// {
// active : true,
// activeCount : 1,
// count : 1,
// value : 'Bob'
// },
// {
// active : false,
// activeCount : 0,
// count : 1,
// value : 'Otto'
// }
// ]
// }
Use this method to set a human readable label for the facet. This can be used when rendering the facet on the page.
Example
Facetr(collection).facet('Name.FirstName').label('First Name');
Facetr(collection).facet('Name.FirstName').toJSON();
// the property data.label has value 'First Name'
// while the property data.name stays 'Name.FirstName'
Sorts the facet values by their count. The count is the number of models in the original collection with an attribute having as name the facet name and value the given value.
Example
// we use the colleciton defined in Basic Usage section
var facet = Facetr(collection).facet('Hobbies');
facet.value('painting'); // painting count = 3
facet.value('drawing'); // drawing count = 1
// facet.toJSON().data.values[0] = 'drawing'
// facet.toJSON().data.values[1] = 'painting'
facet.sortByCount();
// facet.toJSON().data.values[0] = 'painting'
// facet.toJSON().data.values[1] = 'drawing'
Sorts the facet values by their active count. The active count is the number of models in the current filtered collection with an attribute having as name the facet name and value the given value.
Example
var facet = Facetr(collection).facet('Hobbies');
facet.value('fishing');
facet.value('shopping');
// fishing active count = 1
// shopping active count = 2
// facet.toJSON().data.values[0] = 'fishing'
// facet.toJSON().data.values[1] = 'shopping'
facet.sortByActiveCount();
// facet.toJSON().data.values[0] = 'shopping'
// facet.toJSON().data.values[1] = 'fishing'
Sorts the facet values by their value. This is the default sort.
Sets the direction of the values sort to ascendent.
Example
Facetr(collection).facet('Name.FirstName').asc();
Sets the direction of the values sort to descendant.
Example
Facetr(collection).facet('Name.FirstName').sortByCount().desc();
Removes the facet and all its values and unfilters the collection accordingly. It triggers a "removeFacet" event on the facet collection, unless true is passed as parameter.
var facetCollection = Facetr(collection);
var facet = facetCollection.facet('Age');
facetCollection.on('removeFacet', function(facetName){
console.log('removed facet', facetName);
});
facet.remove();
// console output would be
// removed facet Age
Unselects all the values from the facet. It triggers a 'clear' event unless true is passed as parameter.
This method can be used to add arbitrary data to pass to the templates. Data added using this method is included in the object returned by the toJSON() method in the data.customData property. To retrieve previously set data, just pass the key parameter without any value.
Example
var facet = Facetr('myCollection').facet('Hobbies').customData('sublabel', 'Available hobbies');
// facet.customData('sublabel') returns 'Available hobbies'
// facet.toJSON().data.customData = { sublabel : 'Available hobbies' }
Returns true if any value is selected from this facet, false otherwise.
Example
var facet = Facetr('myCollection').facet('Hobbies');
facet.value('fishing');
// facet.isSelected() returns true
facet.clear(); // remove all selected values
// facet.isSelected() returns false
This method creates a hierarchical representation of facet values, based on the settings object given as parameter. Once this method is used, the facet becomes a "hierarchical" facet. You can then use the facet as you usually would, with the following differences:
- the data section of the object resulting from invoking the toJSON method will also contain a "hierarchical" property with value true and a "groupedValues" array, with the following format:
groupedValues
[
{
value: "manager",
label: "Manager",
active: false,
activeCount: 7,
count: 7,
groups: [
{
value: "team manager",
label: "Team Manager",
active: false,
activeCount: 5,
count: 5,
groups: [
{ ... other groups values ... }
]
},
{
value: "manager",
label: "Manager",
active: false,
activeCount: 5,
count: 5 // no groups property means that this value has no children groups
},
]
}
]
-
when a value from the facet is selected, also the models resulting from selecting its descendents values are included in the result set
-
the count includes both the count of the value itself and that of its descendants values
The hierarchySettings argument is an array with different "hierarchical value" objects, where it is possible to specify the hierarchy of values along with a label. See the Example below to understand the hierarchySettings format.
Example
var hierarchySettings = [
{
value: "manager",
label: "Manager",
groups: [
{
value: "project manager",
label: "Project Manager",
groups: [
{
value: "team manager",
label: "Team Manager"
}
]
}
]
}
];
var profession = Facetr(collection).facet('Profession').hierarchy(hierarchySettings);
var json = profession.toJSON();
// value of json variable would be:
//
// {
// "data": {
// "name":"Profession",
// "hierarchical":true,
// "label":"Profession",
// "extOperator":"and",
// "intOperator":"or",
// "sort":{
// "by":"activeCount",
// "direction":"desc"
// },
// "selected":false,
// "customData":{}
// },
// "values": [ ... stays as the normal facets ... ],
// "groupedValues":[
// {
// "value":"manager",
// "label":"Manager",
// "active":false,
// "activeCount":3,
// "count":3,
// "groups":[
// {
// "value":"project manager",
// "label":"Project Manager",
// "active":false,
// "activeCount":2,
// "count":2,
// "groups":[
// {
// "value":"team manager",
// "label":"Team Manager",
// "active":false,
// "activeCount":1,
// "count":1
// }
// ]
// }
// ]
// }
// ]
// }
FacetExp objects are returned from Facet value and removeValue methods. They can be used to coincisely define multiple values on a facet, using different operators.
Example
Facetr(collection).facet('Age').value(12, 'and');
Facetr(collection).facet('Age').value(15, 'or');
Facetr(collection).facet('Age').value(39, 'and');
// can also be expressed with the following syntax
Facetr(collection).facet('Age').value(12, 'and').or(15).and(39);
Equivalent to facet.value('Value', 'and'), but can be used for FacetExp chains.
Equivalent to facet.value('Value', 'or'), but can be used for FacetExp chains.
This section contains a list of websites / projects using Facetr.
Backbone.Facetr may be freely distributed under the MIT license.
Copyright (C) 2012 Arillo GmbH http://arillo.net
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.