Outline – Example – Setup – Select.Me – Select.One – Select.Many – Guidelines – Labels – Compatibility
Backbone.Select is a set of mixins for Backbone models and collections. Models gain the ability to be selected, and collections handle those selections.
Collections either allow only a single model to be selected, or cater for multiple selections. Events communicate selections and deselections to other parts of your application. If you wish, you can use different types of selections (e.g. "picked" and "rejected") in parallel, with labels.
Models can be part of more than one collection. Backbone.Select will keep selections of shared models in sync and guarantee consistency in even the most complex scenarios.
In a nutshell, Backbone.Select provides you with an almost deceptively simple, but surprisingly powerful mechanism to pick, group or segment items into different buckets. A number of stripped-down demos might help to get you started, and serve as inspiration.
For a superset of Backbone.Select with additional features, have a look at Backbone.Cycle.
- Introductory example
- Dependencies and setup
- Basic model and collection interaction
- Backbone.Select.Me: Making models selectable
- Backbone.Select.One: a single-select collection
- Backbone.Select.Many: a multi-select collection
- Custom labels
- Custom options
- Guidelines
- Compatibility with Backbone's own select method
- Compatibility with Backbone.Picky
- Extension API
- Build process and tests, release notes, credits, copyright, MIT license
Perhaps the best way to explain what Backbone.Select does, before getting into the details, is an example.
// --- (1) Setup ---
var Model = Backbone.Model.extend({
initialize: function () {
// Applies the mixin:
Backbone.Select.Me.applyTo( this );
}
});
// A collection type allowing only one selection at a time
var Collection = Backbone.Collection.extend({
initialize: function ( models, options ) {
// Applies the mixin:
Backbone.Select.One.applyTo( this, models, options );
}
});
var m1 = new Model( {id: "m1"} ),
m2 = new Model( {id: "m2"} ),
m3 = new Model( {id: "m3"} );
var collection = new Collection( [m1, m2, m3] );
// --- (2) Selecting a model ---
collection.select( m1 );
console.log( collection.selected.id ); // prints "m1"
console.log( m1.selected ); // prints true
// --- (3) Events ---
collection.on( "select:one", function ( model ) {
console.log( model.id + " has been selected." );
});
collection.on( "deselect:one", function ( model ) {
console.log( model.id + " has been deselected." );
});
m2.select();
// prints "m1 has been deselected."
// prints "m2 has been selected."
There are a couple of interactive demos you can play around with. The demos are kept simple, and are a good way to explore the various features of Backbone.Select.
- Basic functionality, sharing models among multiple collections: JSBin, Codepen
- Delayed animation. Using custom labels in a Select.One collection: JSBin, Codepen
- Selecting, starring, loving. Using custom labels in a Select.Many collection: JSBin, Codepen
- Focusing on one selected item in a Select.Many collection, using custom labels: JSBin, Codepen
- Focusing on one selected item in a Select.Many collection, using the
exclusive
option: JSBin, Codepen
Backbone and Underscore are the only dependencies. Include backbone.select.js after Backbone.
The stable version of Backbone.Select is available in the dist
directory (dev, prod). If you use Bower, fetch the files with bower install backbone.select
. With npm, it is npm install backbone.select
.
When loaded as a module (e.g. AMD, Node), Backbone.Select does not export a meaningful value. It solely lives in the Backbone namespace.
In theory, a selectable model could be used on its own, but what purpose would it serve? Models are selected in the context of a collection.
So the mixins are used in tandem: Models are augmented with the Backbone.Select.Me mixin, and stored in Backbone.Select.One or Backbone.Select.Many collections. All models in a Backbone.Select collection must have the Backbone.Select.Me mixin applied to them. If you don't take care of that yourself, it happens automatically when models, or sets of raw model data, are added to a Select.One or Select.Many collection.
Selections can be made with model or collection methods. Model and collection will keep each other in sync. Thus, if model
is part of collection
, model.select()
and collection.select( model )
are equivalent.
Adds methods for selecting and deselecting a model. Tracks whether or not the model is selected, and triggers events when the selection changes.
To create a selectable model type, apply the mixin in its initialize
method. Assuming your base type is Backbone.Model
, augment it with
var SelectableModel = Backbone.Model.extend({
initialize: function () {
Backbone.Select.Me.applyTo( this );
}
});
Replace Backbone.Model
in the example above with whatever base type you work with.
The Select.Me applyTo()
signature is:
Backbone.Select.Me.applyTo( model, [options] );
You can pass an options hash as the second parameter to .applyTo()
. Select.Me models recognize the defaultLabel
setup option.
You don't necessarily have to apply the Select.Me mixin to the models yourself. If you don't, it gets applied on the fly when you add models to a Select.One or Select.Many collection.
That works no matter how you add models to a collection. You can pass them in with add()
, set()
, reset()
, or create()
, or during instantiation. It also works when you pass in an array of attribute hashes, or some other data structure which still needs to be parsed (using options.parse
).
Likewise, if you set the collection.model
property to a model class, there is no need to apply the Select.Me mixin in the initialize()
method of that class.
In some cases, you have to apply the Select.Me model mixin yourself, in the initialize()
method of a dedicated model class.
-
If you want to select models even before they become part of a collection, you obviously have to set them up with the Select.Me mixin applied.
-
When the mixin is applied to models automatically while a collection is being created, the options passed to
Backbone.Select.Me.applyTo()
are inherited from the collection. You don't want that to happen if you define adefaultLabel
. You should apply the Select.Me mixin explicitly then, and not rely on automatic application. See below.
The following methods are added to a model by the Select.Me
mixin.
Selects a model. It sets the model's selected
property to true and triggers a "selected"
event.
var model = new SelectableModel( { id: "foo" } );
model.on( "selected", function ( selectedModel ) {
console.log( "I am model " + selectedModel.id + " and just got selected." );
});
model.select(); // => prints "I am model foo and just got selected."
model.selected; // => true
If the model has already been selected, selecting it again does not affect its state, and a "selected"
event is not triggered. A "reselected"
event is triggered instead.
The select
method supports the following options: silent
, label
and exclusive
. The exclusive
option only affects Select.Many collections – see there for more.
The method returns the model, and allows chaining.
Deselects a model. It sets the model's selected
property to false and triggers a "deselected"
event.
var model = new SelectableModel( { id: "foo" } );
model.on( "deselected", function ( deselectedModel ) {
console.log( "I am model " + deselectedModel.id + " and just got deselected." );
});
// A model must be selected for `deselect` to have any effect.
model.select();
model.deselect(); // => prints "I am model foo and just got deselected."
model.selected; // => false
If a model is not selected, deselecting it is a no-op and does not trigger a "deselected"
event.
The deselect
method supports the following options: silent
, label
.
The method returns the model, and allows chaining.
Toggles the selection state between selected and deselected. Calls either the select
or deselect
method, which does the actual work.
var model = new SelectableModel();
model.on( "selected", function () {
console.log( "I am selected." );
});
model.on( "deselected", function () {
console.log( "I am no longer selected." );
});
model.toggleSelected(); // => prints "I am selected."
model.toggleSelected(); // => prints "I am no longer selected."
The toggleSelected
method supports the following options: silent
, label
.
The method returns the model, and allows chaining.
The following property is managed by the Select.Me mixin.
Returns a boolean value indicating whether or not the model is currently selected. May be undefined
instead of false if a model has never been selected.
Use this property read-only, never set it directly. For changing its state, call the appropriate method: .select()
or .deselect()
.
The events listed below are are triggered from Select.Me models. They bubble up to the collection, too.
Events can be prevented from firing when Backbone.Select methods are called with the silent
option, as in
model.select( { silent: true } )
Event handlers receive an options hash as the last parameter. It contains the options which were used for the method call.
A label
property is always part of that hash. It contains the label used for the action, regardless of whether it was set with the label
option or left at its default.
Finally, custom options can be used when invoking any method. They are simply passed through to the event handler, merged into the options hash. See the section on custom options, below.
Event handlers with standard names are invoked automatically. Standard names are onSelect
, onDeselect
, and onReselect
. If these methods exist on the model, they are run without having to be wired up with the event manually.
Triggered when a model has been selected. Event handlers are called with the following arguments: selected model, options.
Runs the onSelect
event handler if the method exists on the model.
Is not triggered when the model had already been selected and is selected again (without any change in status). That results in a "reselected"
event.
Triggered when a model has been deselected. Event handlers are called with the following arguments: deselected model, options.
Runs the onDeselect
event handler if the method exists on the model.
Triggered when a model, which is already selected, is selected again. Event handlers are called with the following arguments: reselected model, options.
Runs the onReselect
event handler if the method exists on the model.
Adds methods for selecting and deselecting models to a collection. Tracks which model is selected, and triggers events when the selection changes.
Allows only a single model to be selected within the collection. Selecting another model will cause the first one to be deselected.
Models in a Backbone.Select.One collection must have the Backbone.Select.Me
mixin applied to them. If you don't take care of that yourself, it happens automatically when models, or sets of raw model data, are added to a Select.One collection.
To create a single-select collection type, apply the mixin in the initialize
method. Assuming your base type is Backbone.Collection
, augment it with
var SelectOneCollection = Backbone.Collection.extend({
initialize: function ( models ) {
Backbone.Select.One.applyTo( this, models );
}
});
Replace Backbone.Collection
in the example above with whatever base type you work with.
If you no longer use a collection, call collection.close()
. See below for more.
The Select.One applyTo()
signature is:
Backbone.Select.One.applyTo( collection, models, [options] );
You can pass an options hash as the third argument to .applyTo()
, as seen in the model sharing example above. Select.One collections recognize the following setup options: defaultLabel
, ignoreLabel
.
You have to provide the models
argument, but it doesn't have to be an array of models. Use whatever has been passed to initialize()
as the models argument. That may indeed be an array of models, but it could also be an array of attribute hashes, or some other structure which still needs to be parsed (using Backbone's options.parse
). The models
argument may even be null
or undefined
. No matter what it is, pass it on. The Backbone.Select mixin needs to examine it, and it can handle it all.
There are differences, though, in what you are able to do next. If full-fledged models are passed to a collection, they can be selected as soon as the Select.One mixin has been applied to the collection. In other words, you can select a model as part of the setup process in initialize()
:
var Collection = Backbone.Collection.extend( {
initialize: function ( models ) {
Backbone.Select.One.applyTo( this, models );
// We can select the first model right here, provided that actual
// models have been passed in. (But we better guard against all the
// other stuff which can be passed to a collection.)
if ( models && models[0] && models[0].select ) models[0].select();
}
} );
However, when raw attribute data is passed in, or data in need of parsing, the corresponding models can only be selected after the instantiation is complete, not in initialize()
. This is a limitation of Backbone itself. Raw data is parsed and converted into models eventually, but only after initialize()
has run its course.
(If you need to make selections like the one above, choosing an item while you create and populate the collection, you are probably better off with the autoSelect
option offered by Backbone.Cycle. Backbone.Cycle is built on top of Backbone.Select.)
If the models don't have the Select.Me mixin applied to them out of the box, it is applied to them as they are processed by the collection mixin.
The following methods are added to a collection by the Select.One
mixin.
Selects a model. It stores the selected model in the selected
property of the collection, and updates the selected
property of the model as well. Both collection and model fire their respective events for a selection.
var model = new SelectableModel();
var collection = new SelectOneCollection( [model] );
collection.select( model );
As mentioned before, calling collection.select( model )
is equivalent in every way to calling model.select()
.
If another model had already been selected, that model is deselected in the process. The events indicating a deselection are also triggered.
Combined select-deselect actions are treated as atomic, so all related events – for the selection as well as the deselection – are fired only after the selection and the deselection have already taken place.
Reselecting the same model again does not affect the state of the model or the collection, and events indicating a selection won't be triggered – no "select:one"
event for the collection, no "selected"
event for the model.
Instead, a "reselect:one"
event (collection) and a "reselected"
event (model) indicate the action.
The select
method supports the following options: silent
, label
.
The method returns the collection, and allows chaining.
Backbone collections have a select
method out of the box, an alias of filter
. It continues to be accessible, based on its different signature. See below.
Deselects a model if it had been selected. It removes the model from the selected
property of the collection, and sets the selected
property of the model to false as well. Both collection and model fire their respective events for a deselection.
var model = new SelectableModel();
var collection = new SelectOneCollection( [model] );
collection.deselect( model );
Again, as mentioned before, calling collection.deselect( model )
is equivalent in every way to calling model.deselect()
.
If the model is not currently selected, this action is a no-op. If you try to deselect a model which is not the currently selected model of the collection, the actual selected model will not be deselected.
You can call deselect
without a model argument. The currently selected model, if any, will be deselected in that case.
The deselect
method supports the following options: silent
, label
.
The method returns the collection, and allows chaining.
The following property is managed by the Select.One mixin.
Returns the model which is currently selected in the collection, or undefined
if no model is selected.
var collection = new SelectOneCollection();
collection.select( model );
collection.selected; // => model
The events listed below are triggered by Backbone.Select.One based on changes of the selection.
Events can be prevented from firing when Backbone.Select methods are called with the silent
option, as in
collection.select( model, { silent: true } )
Event handlers receive an options hash as the last parameter. It contains the options which were used for the method call.
A label
property is always part of that hash. It contains the label used for the action, regardless of whether it was set with the label
option or left at its default.
Another property in the hash might be _externalEvent
. It appears when the selection or deselection was initiated indirectly, e.g. by an add()
or remove()
action, and contains the name of the corresponding event.
Finally, custom options can be used when invoking any method. They are simply passed through to the event handler, merged into the options hash. See the section on custom options, below.
Event handlers with standard names are invoked automatically. Standard names are onSelect
, onDeselect
, and onReselect
. If these methods exist on the collection, they are run without having to be wired up with the event manually.
Triggered when a model has been selected. Event handlers are called with the following arguments: selected model, collection, options.
Runs the onSelect
event handler if the method exists on the collection.
Is not triggered when the model had already been selected and is selected again (without any change in status). That results in a "reselect:one"
event.
Triggered when a model has been deselected. Event handlers are called with the following arguments: deselected model, collection, options.
Runs the onDeselect
event handler if the method exists on the collection.
The event fires when deselect
has been called explicitly, and also when a model is deselected by selecting another one.
Combined select-deselect actions are treated as atomic, so "deselect:one"
fires after both the deselection and the selection have already taken place. That is also the case for any other events related to the transaction, such as the "deselected"
and "selected"
events on the models involved.
Triggered when a model, which is already selected, is selected again. Event handlers are called with the following arguments: reselected model, collection, options.
Runs the onReselect
event handler if the method exists on the collection.
Adds methods for selecting and deselecting models to a collection. Tracks which models are selected, and triggers events when the selection changes.
Backbone.Select.Many allows multiple models to be selected in a collection at the same time.
Models in a Backbone.Select.Many collection must have the Backbone.Select.Me
mixin applied to them. If you don't take care of that yourself, it happens automatically when models, or sets of raw model data, are added to a Select.Many collection.
To create a multi-select collection type, apply the mixin in the initialize
method. Assuming your base type is Backbone.Collection
, augment it with
SelectManyCollection = Backbone.Collection.extend({
initialize: function ( models ) {
Backbone.Select.Many.applyTo( this, models );
}
});
Replace Backbone.Collection
in the example above with whatever base type you work with.
If you no longer use a collection, call collection.close()
. See below for more.
The Select.Many applyTo()
signature is:
Backbone.Select.Many.applyTo( thisCollection, models, [options] );
You can pass an options hash as the third argument to .applyTo()
, as seen in the model sharing example above. Select.Many collections recognize the following setup options: defaultLabel
, ignoreLabel
.
You have to provide the models
argument. Pass on whatever has been passed to initialize()
as the models argument.
If you pass in raw model data while you create the collection – e.g. a set of attribute hashes, or other data still needing to be parsed –, then you can't select a model in initialize()
, or at any other point while creating the collection.
See the corresponding section about the Select.One mixin, above, for more details about the models
argument.
The following methods are added to a collection by the Select.Many
mixin.
Selects a model. It stores the selected model in the selected
list of the collection, and updates the selected
property of the model as well. Both collection and model fire their respective events for a selection.
var model = new SelectableModel();
var collection = new SelectManyCollection( [model] );
collection.select( model );
As mentioned before, calling collection.select( model )
is equivalent in every way to calling model.select()
.
Reselecting the same model again does not affect the state of the model or the collection, and events indicating a selection won't be triggered – no "select:some"
or "select:all"
event for the collection, no "selected"
event for the model.
Instead, a "reselect:any"
event (collection) and a "reselected"
event (model) indicate the action.
The select
method supports the following options: silent
, label
and exclusive
.
The exclusive
option makes a Select.Many collection work like a Select.One collection for just one action. By passing the option { exclusive: true }
to a .select()
call, only the specified model is selected, and all others are deselected.
var collection = new SelectManyCollection( [m1, m2, m3] );
m1.select();
m2.select();
collection.select( m3, { exclusive: true } );
collection.selected; // => { "c3": model3 }
As one would expect, it also works when the model has already been selected before. The model stays selected, but all other models are deselected.
You can also use the exclusive
option when calling .select()
on a model, rather than the collection.
m3.select( { exclusive: true } );
When models are shared among multiple collections, it makes a difference whether you call .select()
with the exclusive
option directly on a model, or on a collection.
In the context of a collection, the effect of the exclusive
option is limited to the collection where the selection took place. Other Select.Many collections don't inherit the "exclusivity" of the model, and don't deselect models unless they, too, are shared with the collection where the call was made. Consider this:
var collectionA = new SelectManyCollection( [m1, m2] );
var collectionB = new SelectManyCollection( [mA, m2] );
m1.select();
mA.select();
collectionA.select( m2, { exclusive: true } );
collectionA.selected; // stores only m2; `exclusive` at work here
collectionB.selected; // stores mA, m2; `exclusive` not inherited
When you use the exclusive
option for a .select()
call on a model, however, its effect spreads to any collection sharing that particular model.
var collectionA = new SelectManyCollection( [m1, m2] );
var collectionB = new SelectManyCollection( [mA, m2] );
m1.select();
mA.select();
m2.select( { exclusive: true } );
collectionA.selected; // stores only m2; `exclusive` at work here
collectionB.selected; // stores only m2; `exclusive` also at work here
Finally, combined select-deselect actions are treated as atomic, so all related events – for the selection as well as the deselections – are fired only after the selection and the deselections have already taken place.
The method returns the collection, and allows chaining.
Backbone collections have a select
method out of the box, an alias of filter
. It continues to be accessible, based on its different signature. See below.
Deselects a model if it had been selected. It removes the model from the selected
list of the collection, and sets the selected
property of the model to false as well. Both collection and model fire their respective events for a deselection.
var model = new SelectableModel();
var collection = new SelectManyCollection( [model] );
collection.deselect( model );
If the model is not currently selected, this action is a no-op.
You can call deselect
without a model argument. All currently selected models, if any, will be deselected in that case. The method acts exactly like deselectAll()
then.
The deselect
method supports the following options: silent
, label
.
The method returns the collection, and allows chaining.
Selects all models in the collection. Fires collection and model events indicating the change, and separate events for any re-selections. See the events section below.
collection.selectAll();
The selectAll
method supports the following options: silent
, label
.
The whole batch of select actions is treated as atomic. All related events – those of the collection as well as those of individual models – are fired only after the selections have been completed.
The method returns the collection, and allows chaining.
Deselects all models in the collection. Fires collection and model events indicating the change. See the events section below.
collection.deselectAll();
The deselectAll
method supports the following options: silent
, label
.
The whole batch of deselect actions is treated as atomic. All related events – those of the collection as well as those of individual models – are fired only after the deselections have been completed.
The method returns the collection, and allows chaining.
Selects all models in the collection which haven't been selected, and deselects those which have been. Fires collection and model events indicating the change. See the events section below.
collection.invertSelection();
The invertSelection
method supports the following options: silent
, label
.
The whole batch of select and deselect actions is treated as atomic. All related events – those of the collection as well as those of individual models – are fired only after the selections and deselections have been completed.
The method returns the collection, and allows chaining.
Selects all models in the collection. If that is already the case and all models are selected, they are deselected instead. Fires collection and model events indicating the change, and separate events for any re-selections. See the events section below.
var collection = new SelectManyCollection( models );
collection.toggleSelectAll(); // selects all models in the collection
collection.toggleSelectAll(); // deselects all models in the collection
The following rules apply when toggling:
- If no models are selected, select them all.
- If some models are selected, but not all of them, select them all.
- If all models are selected, deselect them all.
The toggleSelectAll
method supports the following options: silent
, label
.
The whole batch of select and deselect actions is treated as atomic. All related events – those of the collection as well as those of individual models – are fired only after the selections and deselections have been completed.
The method returns the collection, and allows chaining.
The following properties are managed by the Select.Many mixin.
Returns a hash of the selected models. The cid
of the models serve as the keys. Returns an empty object, {}
, if no models are selected.
var collection = new SelectManyCollection( [model1, model2] );
collection.select( model1 );
collection.selected; // => { "c1": model1 }
// Select an additional model
collection.select( model2 );
collection.selected; // => { "c1": model1, "c2": model2 }
Stores the number of selected items in the collection. Equivalent to calling _.size( collection.selected )
.
var collection = new SelectManyCollection( [model] );
collection.select( model );
collection.selectedLength; // => 1
The events below are triggered by Backbone.Select.Many based on changes of the selection.
Events can be prevented from firing when Backbone.Select methods are called with the silent
option, as in
collection.select( model, { silent: true } )
Select.Many events, with the exception of "reselect:any"
, pass a "diff" hash to event handlers as the first parameter:
{ selected: [...], deselected: [...] }
The selected
array contains models which have been newly selected by the action triggering the event. Likewise, models in the deselected
array have changed their status from selected to deselected.
Event handlers receive an options hash as the last parameter. It contains the options which were used for the method call, such as exclusive
.
A label
property is always part of that hash. It contains the label used for the action, regardless of whether it was set with the label
option or left at its default.
Another property in the hash might be _externalEvent
. It appears when the selection or deselection was initiated indirectly, e.g. by an add()
or remove()
action, and contains the name of the corresponding event.
Finally, custom options can be used when invoking any method. They are simply passed through to the event handler, merged into the options hash. See the section on custom options, below.
Event handlers with standard names are invoked automatically. Standard names are onSelectNone
, onSelectSome
, onSelectAll
and onReselect
. If these methods exist on the collection, they are run without having to be wired up with the event manually.
Triggered when all models have been selected. Event handlers are called with the following arguments: "diff" hash, collection, options.
Runs the onSelectAll
event handler if the method exists on the collection.
Triggered when all models have been deselected. Event handlers are called with the following arguments: "diff" hash, collection, options.
Runs the onSelectNone
event handler if the method exists on the collection.
Triggered when some, but not all, models have been selected. Event handlers are called with the following arguments: "diff" hash, collection, options.
Runs the onSelectSome
event handler if the method exists on the collection.
Triggered when at least one model, which is already selected, is selected again. Event handlers are called with the following arguments: array of reselected models, collection, options.
Runs the onReselect
event handler if the method exists on the collection.
In contrast to the other events, this event fires even if there isn't any change in the resulting selection at all. Note that there is no separate reselect:all event; the re-selection of all items in the collection is also covered by "reselect:any"
.
By default, a selected model is labeled with the property selected
. Select.One and Select.Many collections expose a selected
property, too. You can change these names to ones of your choosing. And if you do, you can use different names alongside each other.
That may look like a trivial bit of syntactic sugar, but it isn't. It adds a whole new layer of flexibility, allowing different types of selections to coexist.
Creating and using a custom label is easy. Just set it in the label
option when selecting or deselecting.
model.select( { label: "starred" } );
model.starred; // => true
model.selected; // => undefined
A selection made with a specific label
acts on that label only, not on any others. The default label, "selected"
, is not involved in the selection above, so model.selected
remains unchanged. Different labels are independent of each other.
That is a key feature of labels. You can use any number of them in a given collection, at the same time. You can also use them across a whole bunch of collections, be they Select.One or Select.Many collections, or both.
But how exactly is a label
used in the context of a collection?
// A Select.One collection
collection.select( model, { label: "starred" } );
collection.starred; // => model
collection.selected; // => undefined
It works the same in a Select.Many collection, but have a look at retrieving the length:
// A Select.Many collection
collection.select( model, { label: "starred" } );
collection.starred; // => { "c1": model } assuming that model.cid = "c1"
collection.selected; // => {}
collection.starredLength; // => 1
collection.selectedLength; // => 0
You already know that selectedLength
stores the size of a selection made with the default label, "selected"
. Likewise, when you make a selection with label: "starred"
, you retrieve its size from the property starredLength
. If you were to use the label "foo"
, you'd have to query fooLength
.
You may have noticed that the collection has a selected
property, and selectedLength
, even though we haven't actually used that label in any call. These properties always exist because "selected"
is the default label. (You can change the default label, though.)
The label in use is also exposed in selection-related events.
Every event handler receives an options hash as the last argument, and that hash has a label
property. It always contains the label name, whether you have passed in a custom label or not.
model.on( "selected", function ( model, options ) {
console.log( "Label: " + options.label );
});
// The event handler receives the custom label
model.select( { label: "starred" } ); // prints "Label: starred"
// The event handler also receives the default label
model.select(); // prints "Label: selected"
The generic events like "selected"
, "select:one"
etc fire regardless of the label you use. Besides, there are more specific events which are namespaced to the label. "selected:starred"
fires when a model is selected using the custom "starred"
label. The somewhat inelegant "selected:selected"
event fires when a model is selected with the default label, "selected"
.
Every event has these namespaced variants. The label is simply appended to the generic event name, in the format :labelName
. For the label "foo"
, event names would be "select:one:foo"
, "deselected:foo"
, "reselect:any:foo"
etc.
As mentioned elsewhere, predefined event handlers like onSelect
are invoked automatically. They respond to generic events like "selected"
and therefore run regardless of the label you use. If you need to differentiate, read the label from the options argument, which is the last argument the handler receives (see above).
Predefined event handlers are only available for generic events, not for their namespaced variants. So if you create a onSelectStarred
method on a model, it will not be invoked automatically when a "selected:starred"
event occurs.
The default label for selections or deselections is "selected"
. You can provide your own name for the default label when you apply the Select.Me, Select.One and Select.Many mixins:
var Collection = Backbone.Collection.extend({
initialize: function ( models ) {
Backbone.Select.One.applyTo( this, models, { defaultLabel: "starred" } ); // <= HERE
}
});
var collection = new Collection( [model] );
// Calling select() without specifying a label
collection.select( model );
collection.starred; // => model
collection.selected; // => undefined
Unless you have a specific dislike for the "selected"
label, you probably won't need that option most of the time. But it becomes essential when you want to ignore the "selected"
label. See the ignoreLabel
setup option, below, for more.
Selectable models need to have the Select.Me mixin applied to them, but you don't necessarily have to take care of that yourself. If the mixin is missing in some models, a Select.One or Select.Many collection applies the mixin automatically when the models are being added to the collection. That is convenient, but it has an undesirable effect when the defaultLabel
option comes into play.
Suppose you create a new collection and pass models to it during instantiation. The Select.One or Select.Many mixin gets applied to the collection, and the models are automatically spruced up with Select.Me mixins. As a part of that process, the model mixins inherit the setup options of the collection mixin. And so, the defaultLabel
of the collection becomes the defaultLabel
of the models as well.
However, models you add to the collection later on, which also have the Select.Me mixin applied on the fly, do not automatically inherit the defaultLabel
of the collection. You have to set the default label in the options of every call to add()
, reset()
or set()
. If you forget, the models in the collection end up with varying default labels.
Stay clear of that mess. If you use a defaultLabel
anywhere, apply the Select.Me mixin to the models explicitly, in the initialize()
method of a dedicated model class. Do not rely on automatic mixin application then.
There may be scenarios when you don't want a collection to respond to selections made with a specific label. Then, you'll need the ignoreLabel
setup option.
Suppose you have a Select.One collection and a Select.Many collection, and both are sharing the same set of models. You want to select models, and also star them (using a custom "starred"
label). Only one model should be selected at a time. But there should be no limit on how many models are starred.
In that scenario, selecting models works out of the box. The models are part of a Select.One collection, so only one model is selected at a time. But unfortunately, the same mechanism is at play when you use the "starred"
label. Starring one model immediately un-stars another one, precisely because they are all in the same Select.One collection.
To make things work and star multiple models, you have to make the Select.One collection ignore the "starred"
label somehow. Then, you'd be able to manage your starred models in the Select.Many collection, without the Select.One collection interfering.
You can provide the name of a label to ignore, or an array of names, when you apply the Select.One and Select.Many mixins.
var Collection = Backbone.Collection.extend({
initialize: function ( models ) {
Backbone.Select.One.applyTo( this, models, { ignoreLabel: "starred" } ); // <= HERE
}
});
var collection = new Collection( [model] );
// Calling select() with the ignored label
collection.select( model, { label: "starred" } );
collection.starred; // => undefined
// Calling select() with other labels continues to work
collection.select( model );
collection.selected; // => model
Ignoring labels only works in the context of a collection. You can't ignore labels on the level of a model. Technical considerations aside, it just wouldn't make sense. Hence, the ignoreLabel
option does not do anything in a Select.Me model setup.
Don't define a label as the default for a collection – which implies that you intend to use it – and then instruct the collection to ignore it. You can't ignore the default label.
That fact could potentially trip you up when you try to make a collection ignore the "selected"
label. In order to ignore "selected"
, you have to define a different default label at the same time.
var Collection = Backbone.Collection.extend({
initialize: function ( models ) {
Backbone.Select.One.applyTo( this, models, {
defaultLabel: "starred",
ignoreLabel: "selected"
} );
}
});
var collection = new Collection( [model] );
// Calling select() with the ignored label
collection.select( model, { label: "selected" } );
collection.selected; // => undefined
With custom options, you can send additional information to event handlers. Just pass an arbitrary, custom option (or a whole bunch of them) to any method. Custom options don't affect the operation of Backbone.Select, but they are passed on to the event handlers as the last argument.
var collection = new SelectOneCollection( [model] );
collection.on( "select:one", function ( model, collection, options ) {
if ( options ) console.log( "Selected while foo=" + options.foo );
});
collection.select( model, { foo: "bar" } ); // prints "Selected while foo=bar"
Options get passed around to all event handlers which are running. In the example above, the event handler is set up for the collection. It will also pick up an option passed to the select
method of the model, for instance.
model.select( { foo: "baz" } ); // prints "Selected while foo=baz"
There are a number of things you should know, or do, to work with selections successfully.
When a collection is no longer in use, call close()
on it. That avoids memory leaks and ensures proper selection handling when models are shared between collections.
So don't just replace a collection like this:
var collection = new SelectableCollection( [model] );
// ... do stuff
collection = new SelectableCollection( [model] ); // WRONG!
Instead, call close()
before you let an obsolete collection fade away into oblivion:
var collection = new SelectableCollection( [model] );
// ... do stuff
collection.close();
collection = new SelectableCollection( [model] ); // now OK
A number of simple rules govern the interaction of collections and models.
-
Models can be part of more than one collection. The collections don't have to be of the same type. A model can be part of single-select and multi-select collections at the same time.
Selections carry over. If you select a model in one collection, it gets selected in all other collections it is part of. Likewise for deselections.
-
The selections in a collection are updated as needed when models are added and removed.
Suppose you have selected a model (or models) in one collection, and then you create another one with these models. The new collection will pick up the selections you have already made.
-
A model loses its
selected
status when it is removed from the last collection holding it.The concept of selecting a model only makes sense in relation to a group, ie a collection. When you remove a model from all collection contexts, its selection has lost its meaning and is cleared. If you reuse the model later on and add it to a collection, you start with a clean slate.
Resetting a collection has the same effect. Suppose you reset a collection, with a set of models which has already been part of the collection before the reset. If the models are not part of another collection, they loose their
selected
status as they are removed, and are deselected by the time they are re-added.However, if the models also part of another collection, they retain their
selected
status even as they are removed, and nothing changes as a result of the reset. -
When adding multiple selected models to a Select.One collection in one go, the last selected model "wins".
Suppose a number of models are selected in a Select.Many collection. You then also add them to a Select.One collection. Only one model can be selected there, so Backbone.Select will deselect all but one of them. And that winner is the last model added to the Select.One collection – it retains its
selected
status.
If you are working with events a lot, knowledge of the finer points can make a difference.
-
Selections and deselections involving a number of collections and models are treated as atomic. Events are delayed until all collections and models have been updated, without select or deselect actions pending.
To illustrate, suppose two models are part of two collections simultaneously: a Select.One collection, and a Select.Many collection. The first of the models is currently selected. Now if you go ahead in the Select.Many collection and select the second model there, it also gets selected in the Select.One collection, which in turn deselects the first model – in the Select.One collection, and eventually also in the Select.Many collection.
Selection-related events do not fire until that process is complete, and all models and collections are in a consistent state.
-
When a selection, or deselection, is made with the
silent
option enabled, selection-related events will be silenced in all of the collections sharing the model. -
When a selected model is added to a collection or removed from it, the collection is updated and the corresponding
"select:*"
event is fired. Event handlers receive an options object with related information. In the options object, the name of the underlying Backbone event,"add"
or"remove"
, is available asoptions._externalEvent
, e.g.{ _externalEvent: "add" }
. -
The
_externalEvent
property only appears in the event of the collection to which models are added, or from which they are removed. The_externalEvent
property does not show up in events of other collections sharing the model, nor in events of the model itself.As the property name suggests,
_externalEvent
communicates the underlying event, not the action which has triggered it. Methods likeset()
,create()
anddestroy()
trigger"add"
and"remove"
events. When such a method is called,_externalEvent
is set to a value of either"add"
or"remove"
as each event happens.The options passed along with a
"select:*"
event also inherit the event options of the underlying Backbone event. -
A
reset()
does not trigger a"select:*"
or"deselect:one"
event in the collection which is reset. The command is meant to suppress individual notifications. It does so for Backbone's own collection events as well, and keeps"add"
and"remove"
events from firing. So for the collection being reset, only a"reset"
event is fired in the end.That silence only affects the collection which is reset, however. And it is limited to collection events. Model events go ahead: the
"selected"
and"deselected"
events of a model fire as usual, and they bubble up to the collection, too. In other collections sharing the models, the"select:*"
and"deselect:one"
events proceed as ever. -
When a selected model is added to a collection while the collection is instantiated, no collection event is fired: no
select:one
event in a Select.One collection, noselect:some
orselect:all
event in a Select.Many collection.Passing models to a collection during instantiation is pretty much the same as a reset. (In fact, internally, Backbone indeed handles it as a silent reset.) So no event is fired in the collection.
-
The selection-related events in a Select.Many collection (and anywhere else, for that matter) fire when a selection or deselection is made. They do not fire merely because the status of the collection changes, e.g. from some models in a collection being selected to all models being selected.
Suppose, for instance, that some models in a Select.Many collection are selected. Then the unselected models are removed from the collection, in one go. All remaining models in the collection are selected now. But that status change has not come about because of a selection. So there is no
"select:all"
event.Put differently, there never is a
select:none
,select:some
, orselect:all
event with a diff object where bothdiff.selected
anddiff.deselected
would be empty.
A set()
call acts as a wrapper around three different actions:
- a
remove()
call, removing all existing models which are not passed in withset()
- a merge operation, updating the attributes of existing models which are passed in with
set()
- an
add()
call, adding models which are not yet part of the collection.
The three actions take place in that exact order: remove()
, merge, add()
. Even though they all happen under the umbrella of a single set()
call, they are independent of each other and fire their own events, as if called individually.
The implications of that process can be surprising.
Consider, for example, a Select.One collection in which existingModel
is selected. You want to update the attributes of that model. At the same time, you want to add newModel
, which is already selected as well – perhaps that happened in another collection.
When you call collection.set( [existingModel, newModel] )
, the existing model gets deselected, and newModel
becomes the selected model in the collection. In a Select.One collection, only one model can be selected, and it may seem that the last model "wins".
And that would indeed be the case if the models were passed in with add()
or reset()
: with these commands, models are processed in order. But not with set()
. If you provide the models in reverse order, and call collection.set( [newModel, existingModel] )
, the last model does not win – newModel
still comes out on top and is the selected one.
That's because the models are processed in separate remove()
, merge, and add()
actions, as described above. After removing any surplus models, the set()
process enters the merge phase first, and updates the existing model, at which point it still is selected. Later, set()
performs an independent add( newModel )
action, which deselects existingModel
.
Because set()
is really just a wrapper around other operations, it fires the events belonging to these operations. The initial, implicit remove()
call is accompanied by remove
events, one for each model removed. Likewise, the add()
action fires add
events.
With each such event, the selections are brought up to date. So each add
or remove
event happens in tandem with a batch of selection events it may have caused.
For instance, the removal of a selected model from a Select.One collection triggers the events remove
and deselect:one
in the collection, and perhaps a deselected
event on the model as well (unless the model is also contained in other collections, and stays selected). Only after these related events have run their course, the next removal takes place – or, eventually, an addition.
In total, set()
can cause a whole series of these event batches, each kicked off by the underlying remove
or add
action.
Out of the box, Backbone collections have their own select
method – an alias of filter
. In your own code, that should not be an issue: just use filter
.
The original select
method of Backbone collections is still available, though, and can be called just as before. Even implementations overriding Backbone's select
remain accessible. That's because the select
method is overloaded. If the first argument in a select
call is a model, the Backbone.Select mixin will handle it. If not, the call is passed up the prototype chain.
That kind of compatibility is crucial for third-party plugins or legacy code. They may rely on Backbone's select, or on their own implementation. Even so, they will continue to work – no modification required.
This component started off as a series of PRs for Backbone.Picky and turned into an independent fork at version 0.2.0. Backbone.Picky itself is no longer developed, but migrating is easy.
Backbone.Select is fully compatible to Backbone.Picky once you have instantiated it. You only have to change the way the mixin is created and applied to an object.
Picky.Selectable was applied like this:
initialize: function () {
var selectable = new Backbone.Picky.Selectable( this );
_.extend( this, selectable );
}
In Backbone.Select, it has become
initialize: function () {
Backbone.Select.Me.applyTo( this );
}
Similarly, the initialization of Picky.SingleSelect
initialize: function () {
var singleSelect = new Backbone.Picky.SingleSelect( this );
_.extend( this, singleSelect );
}
is replaced by
initialize: function ( models ) {
Backbone.Select.One.applyTo( this, models );
}
Picky.MultiSelect is treated the same way. Use Backbone.Select.Many.applyTo( this, models )
.
If you no longer use a collection and are about to discard it, you need to close it first: call collection.close()
. See above.
There are a number of internal properties, flags, events, and configuration settings which nevertheless are part of the public API of Backbone.Select. You should not need to even know about them when you apply the mixins to your own objects. But they can come in handy if you build a component on top of Backbone.Select. (An example of such a component is Backbone.Cycle.)
The API is safeguarded by tests. Introducing a breaking change would entail moving to a new major version of Backbone.Select. In other words, the API is safe to use – never mind it is largely internal.
Use the following properties read-only.
Internal properties are prefixed with _picky
. (The first such property was introduced when Backbone.Select was still a bunch of PRs for Backbone.Picky, hence the name.)
In addition to the properties listed here, you'll find other ones with a _picky
prefix in the source. But the _picky
prefix in itself does not mean that a property is part of the public API. If you encounter a property and don't see it in the list below, it might change or be removed at any time, so don't rely on it (or monitor it closely).
Present in all Backbone.Select entities. Signals if a Backbone.Select mixin has been applied to a Backbone model or collection, and tells its type. Values: "Backbone.Select.Me"
, "Backbone.Select.One"
, "Backbone.Select.Many"
.
Used in all Backbone.Select entities. Contains the name of the default label. Always present, is set to "selected"
unless the label has been changed with the defaultLabel
setup option.
Used in Select.One and Select.Many collections. Contains an array of ignored labels. Always present, contains an empty array if no label has been ignored.
You can pass the flag @bbs:silentLocally: true
in the options of a select()
or deselect()
call. It acts like silent
, except that events are silenced only for the model or collection on which select()
or deselect()
has been called. In addition, events are silenced only for the call itself, not secondary ones.
Let's clarify the effect of the @bbs:silentLocally
flag in practical terms:
- If you use the flag for a call on a collection, it doesn't silence the events fired by the model. It also doesn't silence the events fired by other collections sharing the model.
- If the flag is used on a model, collection events go ahead unsilenced.
- If you select a model in a Select.One collection and it leads to another model being deselected, only the
select:one
event is silenced, but not the (secondary)deselect:one
event. - If you use the flag on a collection, only the collection events themselves are silenced (e.g.
select:one
,deselect:one
for a Select.One collection). The model eventsselected
,deselected
continue to bubble up to the collection and can be observed on it. - However, if you use the flag on a model, the
selected
anddeselected
events aren't just silenced the on the model itself, but also on the collection. They aren't fired on the model, so they don't bubble up.
The @bbs:add:silent
event is fired after an otherwise silent addition. It is only triggered with options.silent
being set.
By the time the event is fired, selections have been updated and the underlying action, which would normally trigger an "add"
event, has run its course.
Event handlers receive the same arguments as for an ordinary "add"
event.
The @bbs:remove:silent
event is fired after an otherwise silent removal. It is only triggered with options.silent
being set.
By the time the event is fired, selections have been updated and the underlying action, which would normally trigger a "remove"
event, has run its course.
Event handlers receive the same arguments as for an ordinary "remove"
event, including options.index
.
In addition, options["@bbs:wasSelected"]
flags if the removed model had been selected. That is useful because the model is no longer selected by the time the event fires (unless it is still part of another collection). The flag fills that information gap.
The status is stored per label. So, to retrieve the status with regard to the selected
label, query
options["@bbs:wasSelected"]["selected"]
The @bbs:reset:silent
event is fired after an otherwise silent reset. It is only triggered with options.silent
being set.
By the time the event is fired, selections have been updated and the underlying action, which would normally trigger a "reset"
event, has run its course.
Event handlers receive the same arguments as for an ordinary "reset"
event, including options.previousModels
.
The applyModelMixin
configuration can be set to a function which applies a custom model mixin to a model.
When raw model data or plain Backbone models are passed to a collection, the Select.Me model mixin is applied to them automatically. In case you want to supply modified versions of the Select.Me mixin at that point, set Backbone.Select.Me.custom.applyModelMixin
to a function which applies a mixin as you see fit.
The applyModelMixin
function can pick variations of the Select.Me
mixin based on the collection (type), or based on an option. The function is called with the following arguments:
model
: the model to which the mixin must be appliedcollection
: the collection to which the model has been addedoptions
: the options which would normally be passed toBackbone.Select.Me.applyTo()
.
By default, Backbone.Select.Me.custom.applyModelMixin
is undefined, and the standard Select.Me
mixin is used. The setting is global. If you change it, the function will be applied throughout your entire application.
If you'd like to fix, customize or otherwise improve the project: here are your tools.
npm sets up the environment for you.
- The only thing you've got to have on your machine (besides Git) is Node.js. Download the installer here.
- Clone the project and open a command prompt in the project directory.
- Run the setup with
npm run setup
. - Make sure the Grunt CLI is installed as a global Node module. If not, or if you are not sure, run
npm install -g grunt-cli
from the command prompt.
Your test and build environment is ready now. If you want to test against specific versions of Backbone, edit bower.json
first.
The test tool chain: Grunt (task runner), Karma (test runner), Jasmine (test framework). But you don't really need to worry about any of this.
A handful of commands manage everything for you:
- Run the tests in a terminal with
grunt test
. - Run the tests in a browser interactively, live-reloading the page when the source or the tests change:
grunt interactive
. - If the live reload bothers you, you can also run the tests in a browser without it:
grunt webtest
. - Run the linter only with
grunt lint
orgrunt hint
. (The linter is part ofgrunt test
as well.) - Build the dist files (also running tests and linter) with
grunt build
, or justgrunt
. - Build continuously on every save with
grunt ci
. - Change the version number throughout the project with
grunt setver --to=1.2.3
. Or just increment the revision withgrunt setver --inc
. (Remember to rebuild the project withgrunt
afterwards.) grunt getver
will quickly tell you which version you are at.
Finally, if need be, you can set up a quick demo page to play with the code. First, edit the files in the demo
directory. Then display demo/index.html
, live-reloading your changes to the code or the page, with grunt demo
. Libraries needed for the demo/playground should go into the Bower dev dependencies – in the project-wide bower.json
– or else be managed by the dedicated bower.json
in the demo directory.
The grunt interactive
and grunt demo
commands spin up a web server, opening up the whole project to access via http. So please be aware of the security implications. You can restrict that access to localhost in Gruntfile.js
if you just use browsers on your machine.
In case anything about the test and build process needs to be changed, have a look at the following config files:
karma.conf.js
(changes to dependencies, additional test frameworks)Gruntfile.js
(changes to the whole process)web-mocha/_index.html
(changes to dependencies, additional test frameworks)
New test files in the spec
directory are picked up automatically, no need to edit the configuration for that.
- Added
@bbs:wasSelected
to the options of the@bbs:remove:silent
event - Added the
Backbone.Select.Me.custom.applyModelMixin
configuration setting
Changes:
- Removed the separate AMD/Node builds in
dist/amd
. Module systems and browser globals are now supported by the same file,dist/backbone.select.js
(or.min.js
) - Removed the
enableModelSharing
option. Model sharing is always enabled now. Remember to call.close()
when you no longer use a collection. - Removed the
_modelSharingEnabled
flag - Renamed the
_silentLocally
flag to@bbs:silentLocally
Other:
- No more restrictions on using the
silent
option when callingadd()
,remove()
orreset()
on a collection. Usesilent
as you please. - Collections are able to process the full set of input types during instantiation, and when calling
add()
,set()
, andreset()
. Select.One and Select.Many collections now accept attribute hashes, raw model data requiringoptions.parse
, and models without the Select.Me mixin applied. Previously, only Select.Me models have been accepted. - Added events
@bbs:add:silent
,@bbs:remove:silent
and@bbs:reset:silent
, notifying other components, like Backbone.Cycle, of silent additions, removals, and resets. - Prefixed all internal events and flags with
@bbs:
, as additional protection against naming collisions with user-defined events and option names. (NB The public_externalEvent
property on the event options object remains unprefixed.)
- Version is exposed in
Backbone.Select.version
- AMD demo allows testing r.js output
- Updated bower.json, package.json for Backbone 1.3.x (#14)
- Added
_silentLocally
flag to public API
- Made all methods support chaining.
- Allowed
deselect()
to be called without a model argument in a Select.Many collection (acts likedeselectAll()
).
- Added
_pickyIgnoredLabels
property to public API
- Added
invertSelection()
- Made events for
selectAll()
,deselectAll()
,toggleSelectAll()
fire only after all individual actions are complete.
- Introduced support for custom labels,
label
option - Added event variants which are namespaced according to label
- Added
defaultLabel
setup option,_pickyDefaultLabel
property - Added
ignoreLabel
setup option - Added
exclusive
option toselect()
in Select.Many, Select.Me
- Fixed miscalculation of selectedLength in Backbone.Select.Many (#6)
- Updated bower.json, package.json for Backbone 1.2.x (#7)
- Fixed compatibility with Underscore 1.7.0
- Switched to plain objects as mixins internally
- Enforced strict mode
- Fleshed out package.json for npm installs
- Restored access to the
select
method of Backbone.Collection by overloading theselect
method.
- Added arguments validation to
applyTo
factory methods. - Various minor bugs fixed.
- Related selections and deselections are treated as a single, atomic transaction. Firing of events is delayed until select and deselect actions have spread across all affected models and collections, without any actions still pending.
- Fixed bug when models were added or removed with
reset
(collection was not correctly registered with models)
- Relaxed dependency requirements in
bower.json
- Moved build to
dist
directory - Added
_pickyType
property to identify mixins in a model or collection - Switched development stack to Bower, Karma, Node web server
- Added demo app, memory-leak test environment in
demo
directory
- Removed obsolete Backbone.Picky files from build
- Forked Backbone.Picky, renaming the project to Backbone.Select
- Renamed components to Select.Me (former Selectable), Select.One (former SingleSelect), Select.Many (former MultiSelect)
- Added
options._externalEvent
, available when the selection in a collection is altered during anadd
orremove
action - Added
applyTo
class methods for setup - Removed support for creating new Backbone.Select objects solely with the constructor
- Event handlers with standard names are invoked automatically if they exist (
onSelect
,onDeselect
,onReselect
,onSelectNone
,onSelectSome
,onSelectAll
) - Options – including arbitrary, custom ones – are passed on to event handlers
- The collection is also passed to event handlers (single-select collection)
- A "diff" hash is passed to select:- event handlers (multi-select collection)
- New events capture when models are re-selected:
reselected
(model),reselect:one
(single-select collection),reselect:any
(multi-select collection) - Multi-select events no longer fire when
selectAll
,deselectAll
actions are a no-op (change in spec) - Added support for sharing models among collections
- Added a
silent
option - Improved events, now firing when model and collection are in a consistent state (Picky issue #18)
- Added
deselectAll
, while keepingselectNone
around as an alias - More comprehensive testing
- Added config file for the Karma test runner
- Renamed
SingleSelect
events from "select" and "deselect" to "select:one" and "deselect:one" - Pass model as argument in select:one / deselect:one events
- Updated the build to use latest grunt and related tools
- Removed reliance on ruby for any part of this project
- Added Picky.SingleSelect
- Fleshed out the specs more
- Initial release of untested code
- Basic "Selectable" mixin for models
- Basic "MultiSelect" mixin for collections
Special credits go to Derick Bailey, who created the original version of this component, Backbone.Picky. I have forked it at version 0.2.0. Backbone.Picky is no longer developed; see the Backbone.Picky Compatibility section if you want to migrate.
Copyright (c) 2014-2024 Michael Heim
Copyright (c) 2013 Derick Bailey, Muted Solutions, LLC
Code in the data provider test helper: (c) 2014 Box, Inc., Apache 2.0 license. See file.
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.