From 70d61de028b8d09dbcbcadaaccccc3d41007ba65 Mon Sep 17 00:00:00 2001 From: Yousef Cisco Date: Fri, 16 May 2014 16:34:14 +0100 Subject: [PATCH 1/5] Added handling to update selects automatically after the collection is changed --- backbone.stickit.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/backbone.stickit.js b/backbone.stickit.js index c0e5a2b..b9bbe7c 100644 --- a/backbone.stickit.js +++ b/backbone.stickit.js @@ -556,12 +556,12 @@ // Determine if this option is selected. var isSelected = function() { if (!isMultiple && optionVal != null && fieldVal != null && optionVal === fieldVal) { - return true + return true; } else if (_.isObject(fieldVal) && _.isEqual(optionVal, fieldVal)) { return true; } return false; - } + }; if (isSelected()) { option.prop('selected', true); @@ -593,7 +593,14 @@ } // Support Backbone.Collection and deserialize. - if (optList instanceof Backbone.Collection) optList = optList.toJSON(); + if (optList instanceof Backbone.Collection){ + // Listen to the collection for all events and trigger an update of the select options. + optList.once('all', function() { + var currentVal = getAttr(model, options.observe, options); + applyViewFn.call(this, options.update, $el, currentVal, model, options); + }, this); + optList = optList.toJSON(); + } if (selectConfig.defaultOption) { addSelectOptions(["__default__"], $el); From 5999133db6385c0906a2af4793a30ce40a2b99d0 Mon Sep 17 00:00:00 2001 From: Yousef Cisco Date: Fri, 16 May 2014 16:36:48 +0100 Subject: [PATCH 2/5] Code style fix --- backbone.stickit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backbone.stickit.js b/backbone.stickit.js index b9bbe7c..0795019 100644 --- a/backbone.stickit.js +++ b/backbone.stickit.js @@ -593,7 +593,7 @@ } // Support Backbone.Collection and deserialize. - if (optList instanceof Backbone.Collection){ + if (optList instanceof Backbone.Collection) { // Listen to the collection for all events and trigger an update of the select options. optList.once('all', function() { var currentVal = getAttr(model, options.observe, options); From 8d842866ba13bd6277e6cde14c29c4166fee5d41 Mon Sep 17 00:00:00 2001 From: Yousef Cico Date: Sun, 18 May 2014 18:36:06 +0100 Subject: [PATCH 3/5] Changes from comments Removed event listeners after unstuck is called. Changed it so that the options are re-rendered on add, remove, reset and sort. This meant that I had to add in extra lines to manage the event listeners to ensure that we don't create a huge number of events. --- backbone.stickit.js | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/backbone.stickit.js b/backbone.stickit.js index 0795019..9597223 100644 --- a/backbone.stickit.js +++ b/backbone.stickit.js @@ -594,11 +594,30 @@ // Support Backbone.Collection and deserialize. if (optList instanceof Backbone.Collection) { - // Listen to the collection for all events and trigger an update of the select options. - optList.once('all', function() { + var collection = optList; + var refreshSelectOptions = function() { var currentVal = getAttr(model, options.observe, options); applyViewFn.call(this, options.update, $el, currentVal, model, options); - }, this); + }; + // We need to call this function after unstickit and after an update so we don't end up + // with multiple listeners doing the same thing + var removeCollectionListeners = function() { + collection.off('add remove reset sort', refreshSelectOptions); + }; + // Remove previously set event listeners by triggering a custom event + collection.trigger('stickit:selectRefresh'); + collection.once('stickit:selectRefresh', removeCollectionListeners); + + // Listen to the collection and trigger an update of the select options + collection.on('add remove reset sort', refreshSelectOptions, this); + + // Remove the previous model event listener + model.trigger('stickit:selectRefresh'); + model.once('stickit:selectRefresh', function() { + model.off('stickit:unstuck', removeCollectionListeners); + }); + // Remove collection event listeners once this binding is unstuck + model.once('stickit:unstuck', removeCollectionListeners, this); optList = optList.toJSON(); } From 84a8964d551502db2f86fb33c11c3830cd5fa4a1 Mon Sep 17 00:00:00 2001 From: Yousef Cisco Date: Mon, 19 May 2014 13:37:07 +0100 Subject: [PATCH 4/5] Added tests, updated functionality to remove all event listeners --- backbone.stickit.js | 13 ++++++--- test/bindData.js | 69 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/backbone.stickit.js b/backbone.stickit.js index 9597223..9cce921 100644 --- a/backbone.stickit.js +++ b/backbone.stickit.js @@ -604,20 +604,25 @@ var removeCollectionListeners = function() { collection.off('add remove reset sort', refreshSelectOptions); }; + var removeAllListeners = function() { + removeCollectionListeners(); + collection.off('stickit:selectRefresh'); + model.off('stickit:selectRefresh'); + } // Remove previously set event listeners by triggering a custom event collection.trigger('stickit:selectRefresh'); - collection.once('stickit:selectRefresh', removeCollectionListeners); - + collection.once('stickit:selectRefresh', removeCollectionListeners, this); + // Listen to the collection and trigger an update of the select options collection.on('add remove reset sort', refreshSelectOptions, this); // Remove the previous model event listener model.trigger('stickit:selectRefresh'); model.once('stickit:selectRefresh', function() { - model.off('stickit:unstuck', removeCollectionListeners); + model.off('stickit:unstuck', removeAllListeners); }); // Remove collection event listeners once this binding is unstuck - model.once('stickit:unstuck', removeCollectionListeners, this); + model.once('stickit:unstuck', removeAllListeners, this); optList = optList.toJSON(); } diff --git a/test/bindData.js b/test/bindData.js index 37304a2..c073937 100644 --- a/test/bindData.js +++ b/test/bindData.js @@ -733,6 +733,75 @@ test('bindings:selectOptions:defaultOption:OptGroups', 8, function() { equal(model.get('water'), 'dasina'); }); + test('bindings:selectOptions (Backbone.Collection that changes)', function() { + + var collection = new Backbone.Collection([{id:1,name:'fountain'}, {id:2,name:'evian'}, {id:3,name:'dasina'}]); + model.set({'water':'fountain'}); + view.model = model; + view.templateId = 'jst8'; + view.bindings = { + '#test8': { + observe: 'water', + selectOptions: { + collection: function() { return collection; }, + labelPath: 'name', + valuePath: 'name' + } + } + }; + + $('#qunit-fixture').html(view.render().el); + + equal(getSelectedOption(view.$('#test8')).data('stickit_bind_val'), 'fountain'); + + model.set('water', 'evian'); + equal(getSelectedOption(view.$('#test8')).data('stickit_bind_val'), 'evian'); + + view.$('#test8 option').eq(2).prop('selected', true).trigger('change'); + equal(model.get('water'), 'dasina'); + + // Test that the select options are auto-updated + collection.add({id:4,name:'buxton'}); + equal(view.$('#test8 option').eq(3).data('stickit_bind_val'), 'buxton'); + + var modelEvents = ['stickit:unstuck', 'stickit:selectRefresh']; + var collectionEvents = ['stickit:selectRefresh', 'add', 'remove', 'reset', 'sort']; + + // Test the number of event listeners set against the model and collection + equal(_.filter(model._events, function(event, key) { + return (event.length === 1 && _.contains(modelEvents, key)); + }).length, modelEvents.length); + + equal(_.filter(collection._events, function(event, key) { + return (event.length === 1 && _.contains(collectionEvents, key)); + }).length, collectionEvents.length); + + collection.remove(2); + equal(view.$('#test8 option').length, collection.length); + + collection.reset(); + equal(view.$('#test8 option').length, collection.length); + + // Test the number of event listeners set against the model and collection after changes to the collection + equal(_.filter(model._events, function(event, key) { + return (event.length === 1 && _.contains(modelEvents, key)); + }).length, modelEvents.length); + + equal(_.filter(collection._events, function(event, key) { + return (event.length === 1 && _.contains(collectionEvents, key)); + }).length, collectionEvents.length); + + view.unstickit(); + + // Test that the select options are no longer updated + collection.add([{id:1,name:'fountain'}, {id:2,name:'evian'}, {id:3,name:'dasina'}]); + notEqual(view.$('#test8 option').length, collection.length); + + // Test that all event listeners have been removed after unstickit + ok(_.isEmpty(model._events)); + ok(_.isEmpty(collection._events)); + }); + test('bindings:selectOptions (collection path relative to `this`)', function() { view.collection = new Backbone.Collection([{id:1,name:'fountain'}, {id:2,name:'evian'}, {id:3,name:'dasina'}]); From 4b596e3a4e7959e79dc95e91439b37c02f76f5a3 Mon Sep 17 00:00:00 2001 From: Yousef Cisco Date: Mon, 19 May 2014 13:44:47 +0100 Subject: [PATCH 5/5] Added missing semicolon --- backbone.stickit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backbone.stickit.js b/backbone.stickit.js index 9cce921..800470b 100644 --- a/backbone.stickit.js +++ b/backbone.stickit.js @@ -608,7 +608,7 @@ removeCollectionListeners(); collection.off('stickit:selectRefresh'); model.off('stickit:selectRefresh'); - } + }; // Remove previously set event listeners by triggering a custom event collection.trigger('stickit:selectRefresh'); collection.once('stickit:selectRefresh', removeCollectionListeners, this);