Permalink
Browse files

more specs and correcting behaviors on single select

  • Loading branch information...
1 parent b0a7284 commit e4e28103149ac5181652593c8e425ffd31a7eb2b @derickbailey committed May 27, 2012
Showing with 291 additions and 5 deletions.
  1. +182 −1 readme.md
  2. +102 −2 spec/javascripts/singleSelect.spec.js
  3. +7 −2 src/backbone.picky.js
View
183 readme.md
@@ -76,7 +76,7 @@ can, however, implement your own version of these methods.
* **Picky.Selectable:** Creates select / deselect capabilities for a model
* **Picky.MultiSelect:** Allows a collection to know about the selection of multiple models, including select all / deselect all
-* **Picky.SingleSelect: COMING SOON**. Allow a collection to have an exclusively selected model
+* **Picky.SingleSelect:** Allow a collection to have an exclusively selected model
## Picky.Selectable
@@ -181,6 +181,187 @@ Triggers when a model is selected.
Triggers when a model is deselected.
+## Picky.SingleSelect
+
+Creates single-select capabilities for a `Backbone.Collection`, allowing
+a single model to be exclusively selected within the colllection. Selecting
+another model will cause the first one to be deselected.
+
+```js
+var singleSelect = new Backbone.Picky.SingleSelect(myCollection) ;
+```
+
+### Basic Usage
+
+Extend your collection with the `SingleSelect` instance to make your
+collection support exclusive selections directly.
+
+```js
+SelectableModel = Backbone.Model.extend({
+ initialize: function(){
+ var selectable = new Backbone.Picky.Selectable(this);
+ _.extend(this, selectable);
+ }
+});
+
+SingleCollection = Backbone.Collection.extend({
+ model: SelectableModel,
+
+ initialize: function(){
+ var singleSelect = new Backbone.Picky.SingleSelect(this);
+ _.extend(this, singleSelect);
+ }
+});
+```
+
+### SingleSelect Methods
+
+The following methods are provided by the `SingleSelect` object.
+
+#### SingleSelect#select(model)
+
+Select a model. This method will store the selected model in
+the collection's `selected` attribute, and call the model's `select`
+method to ensure the model knows it has been selected.
+
+```js
+myModel = new SelectableModel();
+myCol = new MultiCollection();
+myCol.select(myModel);
+```
+
+Or
+
+```js
+myModel = new SelectableModel();
+myCol = new MultiCollection([myModel]);
+myModel.select();
+```
+
+If the model is already selected, this is a no-op. If a previous model
+is already selected, the previous model will be deselected.
+
+#### SingleSelect#deselect(model)
+
+Deselect the currently selected model. This method will remove the
+model from the collection's `selected` attribute, and call the model's
+`deselect` method to ensure the model knows it has been deselected.
+
+```js
+myModel = new SelectableModel();
+myCol = new MultiCollection();
+myCol.deselect(myModel);
+```
+
+Or
+
+```js
+myModel = new SelectableModel();
+myCol = new MultiCollection();
+myModel.deselect();
+```
+
+If the model is not currently selected, this is a no-op. If you try to
+deselect a model that is not the currently selected model, the actual
+selected model will not be deselected.
+
+#### MultiSelect#selectAll
+
+Select all models in the collection.
+
+```js
+myCol = new MultiCollection();
+
+myCol.selectAll();
+```
+
+Models that are already selected will not be re-selected.
+Models that are not currently selected will be selected.
+The end result will be all models in the collection are
+selected.
+
+#### MultiSelect#deselectAll
+
+Deselect all models in the collection.
+
+```js
+myCol = new MultiCollection();
+
+myCol.deselectAll();
+```
+
+Models that are selected will be deselected.
+Models that are not selected will not be deselected again.
+The end result will be no models in the collection are
+selected.
+
+#### MultiSelect#toggleSelectAll
+
+Toggle selection of all models in the collection:
+
+```js
+myCol = new MultiCollection();
+
+myCol.toggleSelectAll(); // select all models in the collection
+
+myCol.toggleSelectAll(); // de-select all models in the collection
+```
+
+The following rules are used when toggling:
+
+* If no models are selected, select them all
+* If 1 or more models, but less than all models are selected, select them all
+* If all models are selected, deselect them all
+
+### MultiSelect Attributes
+
+The following attribute is set by the multi-select automatically
+
+### MultiSelect#selected
+
+Returns a hash of selected models, keyed from the model `cid`.
+
+```js
+myCol = new MultiCollection();
+myCol.select(model);
+
+myCol.selected;
+
+//=> produces
+// {
+// "c1": (model object here)
+// }
+```
+
+#### MultiSelect#selectedLength
+
+Returns the number of items in the collection that are selected.
+
+```js
+myCol = new MultiCollection();
+myCol.select(model);
+
+myCol.selectedLength; //=> 1
+```
+
+### MultiSelect Events
+
+The following events are triggered by the MultiSelect based on changes
+in selection:
+
+#### "select:all"
+
+Triggered when all models have been selected
+
+#### "select:none"
+
+Triggered when all models have been deselected
+
+#### "select:some"
+
+Triggered when at least 1 model is selected, but less than all models have
+been selected
+
## Picky.MultiSelect
Creates multi-select capabilities for a `Backbone.Collection`, including
@@ -1,4 +1,5 @@
describe("single select collection", function(){
+
var Model = Backbone.Model.extend({
initialize: function(){
var selectable = new Backbone.Picky.Selectable();
@@ -15,7 +16,7 @@ describe("single select collection", function(){
}
});
- describe("when selecting a model", function(){
+ describe("when selecting a model via the model's select", function(){
var model, collection;
beforeEach(function(){
@@ -36,6 +37,32 @@ describe("single select collection", function(){
});
});
+ describe("when selecting a model via the collection's select", function(){
+ var model, collection;
+
+ beforeEach(function(){
+ model = new Model();
+ collection = new Collection([model]);
+
+ spyOn(collection, "trigger").andCallThrough();
+ spyOn(model, "select").andCallThrough();
+
+ collection.select(model);
+ });
+
+ it("should hang on to the currently selected model", function(){
+ expect(collection.selected).toBe(model);
+ });
+
+ it("should trigger a selected event", function(){
+ expect(collection.trigger).toHaveBeenCalledWith("selected", model);
+ });
+
+ it("should tell the model to select itself", function(){
+ expect(model.select).toHaveBeenCalled();
+ });
+ });
+
describe("when selecting a model that is already selected", function(){
var model, collection;
@@ -65,6 +92,7 @@ describe("single select collection", function(){
m1.select();
spyOn(collection, "trigger").andCallThrough();
+ spyOn(m1, "deselect").andCallThrough();
m2.select();
});
@@ -78,7 +106,7 @@ describe("single select collection", function(){
});
it("should deselect the first model", function(){
- expect(m1.selected).toBe(false);
+ expect(m1.deselect).toHaveBeenCalled();
});
it("should fire a deselect event for the first model", function(){
@@ -124,4 +152,76 @@ describe("single select collection", function(){
});
});
+ describe("when one model is selected and deselecting another model through the collection's deselect", function(){
+ var m1, m2, collection;
+
+ beforeEach(function(){
+ m1 = new Model();
+ m2 = new Model();
+ collection = new Collection([m1, m2]);
+ collection.select(m1);
+
+ spyOn(m1, "deselect").andCallThrough();
+ spyOn(collection, "trigger").andCallThrough();
+
+ collection.deselect(m2);
+ });
+
+ it("should still hang on to the currently selected model", function(){
+ expect(collection.selected).toBe(m1);
+ });
+
+ it("should keep the selected model selected", function(){
+ expect(m1.selected).toBe(true);
+ });
+
+ it("should not deselect the selected model", function(){
+ expect(m1.deselect).not.toHaveBeenCalled();
+ });
+
+ it("should not trigger a deselected event for the selected model", function(){
+ expect(collection.trigger).not.toHaveBeenCalledWith("deselected", m1);
+ });
+
+ it("should not trigger a deselected event for the non-selected model", function(){
+ expect(collection.trigger).not.toHaveBeenCalledWith("deselected", m2);
+ });
+ });
+
+ describe("when one model is selected and deselecting another model through the model's deselect", function(){
+ var m1, m2, collection;
+
+ beforeEach(function(){
+ m1 = new Model();
+ m2 = new Model();
+ collection = new Collection([m1, m2]);
+ collection.select(m1);
+
+ spyOn(m1, "deselect").andCallThrough();
+ spyOn(collection, "trigger").andCallThrough();
+
+ m2.deselect();
+ });
+
+ it("should still hang on to the currently selected model", function(){
+ expect(collection.selected).toBe(m1);
+ });
+
+ it("should keep the selected model selected", function(){
+ expect(m1.selected).toBe(true);
+ });
+
+ it("should not deselect the selected model", function(){
+ expect(m1.deselect).not.toHaveBeenCalled();
+ });
+
+ it("should not trigger a deselected event for the selected model", function(){
+ expect(collection.trigger).not.toHaveBeenCalledWith("deselected", m1);
+ });
+
+ it("should not trigger a deselected event for the non-selected model", function(){
+ expect(collection.trigger).not.toHaveBeenCalledWith("deselected", m2);
+ });
+ });
+
});
View
@@ -17,18 +17,23 @@ Backbone.Picky = (function (Backbone, _) {
// Select a model, deselecting any previously
// select model
select: function(model){
- if (this.selected === model) { return; }
+ if (model && this.selected === model) { return; }
this.deselect();
+
this.selected = model;
+ this.selected.select();
this.trigger("selected", model);
},
// Deselect a model, resulting in no model
// being selected
- deselect: function(){
+ deselect: function(model){
if (!this.selected){ return; }
+ model = model || this.selected;
+ if (this.selected !== model){ return; }
+
this.selected.deselect();
this.trigger("deselected", this.selected);
delete this.selected;

0 comments on commit e4e2810

Please sign in to comment.