From 1c2c01a8a6c53109700e9b4eb039164c675461b4 Mon Sep 17 00:00:00 2001 From: hashchange Date: Mon, 30 Dec 2013 16:00:03 +0100 Subject: [PATCH 01/18] Ignored .idea directory --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e58794b..9d32ed8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ tmp/ ext/ node_modules -_SpecRunner.html \ No newline at end of file +_SpecRunner.html +.idea/ From 301f37f1d98fa336d3b0b5fd8ee76bdf7b2e53e4 Mon Sep 17 00:00:00 2001 From: hashchange Date: Mon, 30 Dec 2013 16:21:52 +0100 Subject: [PATCH 02/18] Added Karma config file --- spec/javascripts/support/karma.conf.js | 72 ++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 spec/javascripts/support/karma.conf.js diff --git a/spec/javascripts/support/karma.conf.js b/spec/javascripts/support/karma.conf.js new file mode 100644 index 0000000..1900128 --- /dev/null +++ b/spec/javascripts/support/karma.conf.js @@ -0,0 +1,72 @@ +// Karma configuration +// Generated on Mon Dec 30 2013 16:14:03 GMT+0100 (CET) + +module.exports = function(config) { + config.set({ + + // base path, that will be used to resolve files and exclude + basePath: '', + + + // frameworks to use + frameworks: ['jasmine'], + + + // list of files / patterns to load in the browser + files: [ + '../../../public/javascripts/underscore.js', + '../../../public/javascripts/backbone.js', + '../../../src/backbone.picky.js', + '../helpers/*.js', + '../*.spec.js' + ], + + + // list of files to exclude + exclude: [ + + ], + + + // test results reporter to use + // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' + reporters: ['progress'], + + + // web server port + port: 9876, + + + // enable / disable colors in the output (reporters and logs) + colors: true, + + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: false, + + + // Start these browsers, currently available: + // - Chrome + // - ChromeCanary + // - Firefox + // - Opera (has to be installed with `npm install karma-opera-launcher`) + // - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`) + // - PhantomJS + // - IE (only Windows; has to be installed with `npm install karma-ie-launcher`) + browsers: ['Firefox'], + + + // If browser does not capture in given timeout [ms], kill it + captureTimeout: 60000, + + + // Continuous Integration mode + // if true, it capture browsers, run tests and exit + singleRun: false + }); +}; From c8b66fa9e691e04e750b0e88a30233b38394e509 Mon Sep 17 00:00:00 2001 From: hashchange Date: Mon, 30 Dec 2013 19:03:14 +0100 Subject: [PATCH 03/18] Fixed #7 "Attribute vs member" in documentation --- readme.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/readme.md b/readme.md index 3386836..04c87e1 100644 --- a/readme.md +++ b/readme.md @@ -27,7 +27,7 @@ Production: [backbone.picky.min.js](https://raw.github.com/derickbailey/backbone This readme file contains basic usage examples and details on the full API, including methods, -attributes and events. +properties and events. ### Annotated Source Code @@ -117,7 +117,7 @@ The following methods are included in the `Selectable` object #### Selectable#select -Select a model, setting the model's `selected` attribute to true and +Select a model, setting the model's `selected` property to true and triggering a "select" event. ```js @@ -132,7 +132,7 @@ myModel.selected; //=> true ``` #### Selectable#deselect -Deselect a model, setting the model's `selected` attribute to false and +Deselect a model, setting the model's `selected` property to false and triggering a "deselected" event. ```js @@ -170,9 +170,9 @@ myModel.toggleSelected(); //=> "I'm selected!" myModel.toggleSelected(); //=> "I'm no longer selected!" ``` -### Selectable Attributes +### Selectable Properties -The following attributes are manipulated by the Selectable object +The following properties are manipulated by the Selectable object #### Selectable#selected @@ -233,7 +233,7 @@ 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` +the collection's `selected` property, and call the model's `select` method to ensure the model knows it has been selected. ```js @@ -256,7 +256,7 @@ 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 +model from the collection's `selected` property, and call the model's `deselect` method to ensure the model knows it has been deselected. ```js @@ -277,9 +277,9 @@ 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. -### SingleSelect Attributes +### SingleSelect Properties -The following attribute is set by the multi-select automatically +The following property is set by the multi-select automatically ### SingleSelect#selected @@ -422,9 +422,9 @@ The following rules are used when toggling: * 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 +### MultiSelect Properties -The following attribute is set by the multi-select automatically +The following property is set by the multi-select automatically ### MultiSelect#selected From 032bcb25d1624ae42c3dfa7409df690c6d255707 Mon Sep 17 00:00:00 2001 From: hashchange Date: Mon, 30 Dec 2013 17:24:16 +0100 Subject: [PATCH 04/18] Added tests for deselectAll --- spec/javascripts/multiSelect.deselect.spec.js | 90 ++++++++++++++++++- 1 file changed, 87 insertions(+), 3 deletions(-) diff --git a/spec/javascripts/multiSelect.deselect.spec.js b/spec/javascripts/multiSelect.deselect.spec.js index 3cf83ee..9cd35ce 100644 --- a/spec/javascripts/multiSelect.deselect.spec.js +++ b/spec/javascripts/multiSelect.deselect.spec.js @@ -15,7 +15,7 @@ describe("multi-select collection deselecting", function(){ } }); - describe("when no models are selected, and deselecting all", function(){ + describe("when no models are selected, and deselecting all (selectNone)", function(){ var m1, m2, collection; beforeEach(function(){ @@ -42,7 +42,34 @@ describe("multi-select collection deselecting", function(){ }); }); - describe("when 1 model is selected, and deselecting all", function(){ + describe("when no models are selected, and deselecting all (deselectAll)", function(){ + var m1, m2, collection; + + beforeEach(function(){ + m1 = new Model(); + m2 = new Model(); + + collection = new Collection([m1, m2]); + spyOn(collection, "trigger").andCallThrough(); + + collection.deselectAll(); + }); + + it("should trigger 'none' selected event", function(){ + expect(collection.trigger).toHaveBeenCalledWith("select:none", collection); + }); + + it("should have a selected count of 0", function(){ + expect(collection.selectedLength).toBe(0); + }); + + it("should not have any models in the selected list", function(){ + var size = _.size(collection.selected); + expect(size).toBe(0); + }); + }); + + describe("when 1 model is selected, and deselecting all (selectNone)", function(){ var m1, m2, collection; beforeEach(function(){ @@ -70,7 +97,35 @@ describe("multi-select collection deselecting", function(){ }); }); - describe("when all models are selected, and deselecting all", function(){ + describe("when 1 model is selected, and deselecting all (deselectAll)", function(){ + var m1, m2, collection; + + beforeEach(function(){ + m1 = new Model(); + m2 = new Model(); + + collection = new Collection([m1, m2]); + m1.select(); + + spyOn(collection, "trigger").andCallThrough(); + collection.deselectAll(); + }); + + it("should trigger 'none' selected event", function(){ + expect(collection.trigger).toHaveBeenCalledWith("select:none", collection); + }); + + it("should have a selected count of 0", function(){ + expect(collection.selectedLength).toBe(0); + }); + + it("should not have any models in the selected list", function(){ + var size = _.size(collection.selected); + expect(size).toBe(0); + }); + }); + + describe("when all models are selected, and deselecting all (selectNone)", function(){ var m1, m2, collection; beforeEach(function(){ @@ -99,4 +154,33 @@ describe("multi-select collection deselecting", function(){ }); }); + describe("when all models are selected, and deselecting all (deselectAll)", function(){ + var m1, m2, collection; + + beforeEach(function(){ + m1 = new Model(); + m2 = new Model(); + + collection = new Collection([m1, m2]); + m1.select(); + m2.select(); + + spyOn(collection, "trigger").andCallThrough(); + collection.deselectAll(); + }); + + it("should trigger 'none' selected event", function(){ + expect(collection.trigger).toHaveBeenCalledWith("select:none", collection); + }); + + it("should have a selected count of 0", function(){ + expect(collection.selectedLength).toBe(0); + }); + + it("should not have any models in the selected list", function(){ + var size = _.size(collection.selected); + expect(size).toBe(0); + }); + }); + }); From 27591be7b4607fb7727934f6896fe86457b5282e Mon Sep 17 00:00:00 2001 From: hashchange Date: Mon, 30 Dec 2013 17:31:48 +0100 Subject: [PATCH 05/18] Fixed #4 by adding deselectAll as an alias deselectAll is more consistent with the rest of the API; selectNone is kept around for compatibility. --- src/backbone.picky.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/backbone.picky.js b/src/backbone.picky.js index ce9d8e5..91ba0ed 100644 --- a/src/backbone.picky.js +++ b/src/backbone.picky.js @@ -44,7 +44,7 @@ Backbone.Picky = (function (Backbone, _) { // Picky.MultiSelect // ----------------- // A mult-select mixin for Backbone.Collection, allowing a collection to - // have multiple items selected, including `selectAll` and `selectNone` + // have multiple items selected, including `selectAll` and `deselectAll` // capabilities. Picky.MultiSelect = function (collection) { @@ -83,18 +83,22 @@ Backbone.Picky = (function (Backbone, _) { }, // Deselect all models in this collection - selectNone: function () { + deselectAll: function () { if (this.selectedLength === 0) { return; } this.each(function (model) { model.deselect(); }); calculateSelectedLength(this); }, - // Toggle select all / none. If some are selected, it + selectNone: function () { + this.deselectAll(); + }, + + // Toggle select all / none. If some are selected, it // will select all. If all are selected, it will select // none. If none are selected, it will select all. toggleSelectAll: function () { if (this.selectedLength === this.length) { - this.selectNone(); + this.deselectAll(); } else { this.selectAll(); } From 661f8911f1f2940d47db45416a2437d2422c5a12 Mon Sep 17 00:00:00 2001 From: Walther Lalk Date: Thu, 8 Aug 2013 15:23:26 +0200 Subject: [PATCH 06/18] Add support for a model which belongs to more than one collection. If a selectable model belongs to more than one selectable collection then only the initial collection will have it's select() method called. Also, if a model is initially added to a non-selectable collection, and then later added to a selectable collection the 2nd collection will not have it's select() method called, and a error will be thrown as the initial collection will not have a select() method. --- src/backbone.picky.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/backbone.picky.js b/src/backbone.picky.js index 91ba0ed..c72f119 100644 --- a/src/backbone.picky.js +++ b/src/backbone.picky.js @@ -10,6 +10,8 @@ Backbone.Picky = (function (Backbone, _) { Picky.SingleSelect = function(collection){ this.collection = collection; + this.collection.listenTo(this.collection, 'selected', this.select); + this.collection.listenTo(this.collection, 'deselected', this.deselect); }; _.extend(Picky.SingleSelect.prototype, { @@ -50,6 +52,9 @@ Backbone.Picky = (function (Backbone, _) { Picky.MultiSelect = function (collection) { this.collection = collection; this.selected = {}; + this.collection.listenTo(this.collection, 'selected', this.select); + this.collection.listenTo(this.collection, 'deselected', this.deselect); + }; _.extend(Picky.MultiSelect.prototype, { @@ -123,10 +128,6 @@ Backbone.Picky = (function (Backbone, _) { this.selected = true; this.trigger("selected", this); - - if (this.collection) { - this.collection.select(this); - } }, // Deselect this model, and tell our @@ -136,10 +137,6 @@ Backbone.Picky = (function (Backbone, _) { this.selected = false; this.trigger("deselected", this); - - if (this.collection) { - this.collection.deselect(this); - } }, // Change selected to the opposite of what From de62bb84aaa4d459ea954533626d83ae5dc623f7 Mon Sep 17 00:00:00 2001 From: hashchange Date: Mon, 30 Dec 2013 23:19:25 +0100 Subject: [PATCH 07/18] Updated Backbone and Underscore libs Backbone 1.0.0, Underscore 1.5.0. Using the listenTo and stopListening methods requires at least Backbone 0.9.9. --- public/javascripts/backbone.js | 1609 +++++++++++++++++++++++++++++- public/javascripts/underscore.js | 1289 +++++++++++++++++++++++- 2 files changed, 2828 insertions(+), 70 deletions(-) diff --git a/public/javascripts/backbone.js b/public/javascripts/backbone.js index c1c0d4f..3512d42 100644 --- a/public/javascripts/backbone.js +++ b/public/javascripts/backbone.js @@ -1,38 +1,1571 @@ -// Backbone.js 0.9.2 - -// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc. -// Backbone may be freely distributed under the MIT license. -// For all details and documentation: -// http://backbonejs.org -(function(){var l=this,y=l.Backbone,z=Array.prototype.slice,A=Array.prototype.splice,g;g="undefined"!==typeof exports?exports:l.Backbone={};g.VERSION="0.9.2";var f=l._;!f&&"undefined"!==typeof require&&(f=require("underscore"));var i=l.jQuery||l.Zepto||l.ender;g.setDomLibrary=function(a){i=a};g.noConflict=function(){l.Backbone=y;return this};g.emulateHTTP=!1;g.emulateJSON=!1;var p=/\s+/,k=g.Events={on:function(a,b,c){var d,e,f,g,j;if(!b)return this;a=a.split(p);for(d=this._callbacks||(this._callbacks= -{});e=a.shift();)f=(j=d[e])?j.tail:{},f.next=g={},f.context=c,f.callback=b,d[e]={tail:g,next:j?j.next:f};return this},off:function(a,b,c){var d,e,h,g,j,q;if(e=this._callbacks){if(!a&&!b&&!c)return delete this._callbacks,this;for(a=a?a.split(p):f.keys(e);d=a.shift();)if(h=e[d],delete e[d],h&&(b||c))for(g=h.tail;(h=h.next)!==g;)if(j=h.callback,q=h.context,b&&j!==b||c&&q!==c)this.on(d,j,q);return this}},trigger:function(a){var b,c,d,e,f,g;if(!(d=this._callbacks))return this;f=d.all;a=a.split(p);for(g= -z.call(arguments,1);b=a.shift();){if(c=d[b])for(e=c.tail;(c=c.next)!==e;)c.callback.apply(c.context||this,g);if(c=f){e=c.tail;for(b=[b].concat(g);(c=c.next)!==e;)c.callback.apply(c.context||this,b)}}return this}};k.bind=k.on;k.unbind=k.off;var o=g.Model=function(a,b){var c;a||(a={});b&&b.parse&&(a=this.parse(a));if(c=n(this,"defaults"))a=f.extend({},c,a);b&&b.collection&&(this.collection=b.collection);this.attributes={};this._escapedAttributes={};this.cid=f.uniqueId("c");this.changed={};this._silent= -{};this._pending={};this.set(a,{silent:!0});this.changed={};this._silent={};this._pending={};this._previousAttributes=f.clone(this.attributes);this.initialize.apply(this,arguments)};f.extend(o.prototype,k,{changed:null,_silent:null,_pending:null,idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},escape:function(a){var b;if(b=this._escapedAttributes[a])return b;b=this.get(a);return this._escapedAttributes[a]=f.escape(null== -b?"":""+b)},has:function(a){return null!=this.get(a)},set:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c||(c={});if(!d)return this;d instanceof o&&(d=d.attributes);if(c.unset)for(e in d)d[e]=void 0;if(!this._validate(d,c))return!1;this.idAttribute in d&&(this.id=d[this.idAttribute]);var b=c.changes={},h=this.attributes,g=this._escapedAttributes,j=this._previousAttributes||{};for(e in d){a=d[e];if(!f.isEqual(h[e],a)||c.unset&&f.has(h,e))delete g[e],(c.silent?this._silent: -b)[e]=!0;c.unset?delete h[e]:h[e]=a;!f.isEqual(j[e],a)||f.has(h,e)!=f.has(j,e)?(this.changed[e]=a,c.silent||(this._pending[e]=!0)):(delete this.changed[e],delete this._pending[e])}c.silent||this.change(c);return this},unset:function(a,b){(b||(b={})).unset=!0;return this.set(a,null,b)},clear:function(a){(a||(a={})).unset=!0;return this.set(f.clone(this.attributes),a)},fetch:function(a){var a=a?f.clone(a):{},b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a))return!1;c&&c(b,d)}; -a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},save:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c=c?f.clone(c):{};if(c.wait){if(!this._validate(d,c))return!1;e=f.clone(this.attributes)}a=f.extend({},c,{silent:!0});if(d&&!this.set(d,c.wait?a:c))return!1;var h=this,i=c.success;c.success=function(a,b,e){b=h.parse(a,e);if(c.wait){delete c.wait;b=f.extend(d||{},b)}if(!h.set(b,c))return false;i?i(h,a):h.trigger("sync",h,a,c)};c.error=g.wrapError(c.error, -h,c);b=this.isNew()?"create":"update";b=(this.sync||g.sync).call(this,b,this,c);c.wait&&this.set(e,a);return b},destroy:function(a){var a=a?f.clone(a):{},b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};if(this.isNew())return d(),!1;a.success=function(e){a.wait&&d();c?c(b,e):b.trigger("sync",b,e,a)};a.error=g.wrapError(a.error,b,a);var e=(this.sync||g.sync).call(this,"delete",this,a);a.wait||d();return e},url:function(){var a=n(this,"urlRoot")||n(this.collection,"url")||t(); -return this.isNew()?a:a+("/"==a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return null==this.id},change:function(a){a||(a={});var b=this._changing;this._changing=!0;for(var c in this._silent)this._pending[c]=!0;var d=f.extend({},a.changes,this._silent);this._silent={};for(c in d)this.trigger("change:"+c,this,this.get(c),a);if(b)return this;for(;!f.isEmpty(this._pending);){this._pending= -{};this.trigger("change",this,a);for(c in this.changed)!this._pending[c]&&!this._silent[c]&&delete this.changed[c];this._previousAttributes=f.clone(this.attributes)}this._changing=!1;return this},hasChanged:function(a){return!arguments.length?!f.isEmpty(this.changed):f.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this.changed):!1;var b,c=!1,d=this._previousAttributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return!arguments.length|| -!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},isValid:function(){return!this.validate(this.attributes)},_validate:function(a,b){if(b.silent||!this.validate)return!0;var a=f.extend({},this.attributes,a),c=this.validate(a,b);if(!c)return!0;b&&b.error?b.error(this,c,b):this.trigger("error",this,c,b);return!1}});var r=g.Collection=function(a,b){b||(b={});b.model&&(this.model=b.model);b.comparator&&(this.comparator=b.comparator); -this._reset();this.initialize.apply(this,arguments);a&&this.reset(a,{silent:!0,parse:b.parse})};f.extend(r.prototype,k,{model:o,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},add:function(a,b){var c,d,e,g,i,j={},k={},l=[];b||(b={});a=f.isArray(a)?a.slice():[a];c=0;for(d=a.length;c=b))this.iframe=i('