From 4ba2eb1eec0e5b6c1bf0e3bd0a0ca1b9ee6adff5 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Sun, 13 Feb 2011 17:03:48 +0000 Subject: [PATCH] "event" binding now correctly handles registering multiple event handlers at once --- build/output/knockout-latest.debug.js | 53 ++++++++++++++------------- build/output/knockout-latest.js | 2 +- spec/defaultBindingsBehaviors.js | 14 +++++-- src/binding/defaultBindings.js | 53 ++++++++++++++------------- 4 files changed, 67 insertions(+), 55 deletions(-) diff --git a/build/output/knockout-latest.debug.js b/build/output/knockout-latest.debug.js index f0f45b765..742b9b491 100644 --- a/build/output/knockout-latest.debug.js +++ b/build/output/knockout-latest.debug.js @@ -1101,32 +1101,35 @@ ko.utils.arrayForEach(eventHandlersWithShortcuts, function(eventName) { ko.bindingHandlers['event'] = { 'init' : function (element, valueAccessor, allBindingsAccessor, viewModel) { var eventsToHandle = valueAccessor() || {}; - for(var eventName in eventsToHandle) { - if (typeof eventName == "string") { - ko.utils.registerEventHandler(element, eventName, function (event) { - var handlerReturnValue; - var handlerFunction = valueAccessor()[eventName]; - var allBindings = allBindingsAccessor(); - - try { - handlerReturnValue = handlerFunction.apply(viewModel, arguments); - } finally { - if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true. - if (event.preventDefault) - event.preventDefault(); - else - event.returnValue = false; + for(var eventNameOutsideClosure in eventsToHandle) { + (function() { + var eventName = eventNameOutsideClosure; // Separate variable to be captured by event handler closure + if (typeof eventName == "string") { + ko.utils.registerEventHandler(element, eventName, function (event) { + var handlerReturnValue; + var handlerFunction = valueAccessor()[eventName]; + var allBindings = allBindingsAccessor(); + + try { + handlerReturnValue = handlerFunction.apply(viewModel, arguments); + } finally { + if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true. + if (event.preventDefault) + event.preventDefault(); + else + event.returnValue = false; + } } - } - - var bubble = allBindings[eventName + 'Bubble'] !== false; - if (!bubble) { - event.cancelBubble = true; - if (event.stopPropagation) - event.stopPropagation(); - } - }); - } + + var bubble = allBindings[eventName + 'Bubble'] !== false; + if (!bubble) { + event.cancelBubble = true; + if (event.stopPropagation) + event.stopPropagation(); + } + }); + } + })(); } } }; diff --git a/build/output/knockout-latest.js b/build/output/knockout-latest.js index 7fb71b940..ccbb3a59c 100644 --- a/build/output/knockout-latest.js +++ b/build/output/knockout-latest.js @@ -36,7 +36,7 @@ q.p=function(){function f(e,g){return e.replace(b,function(h,i){return g[i]})}va h.push(", ");h.push(i+" : function(__ko_value) { "+j+" = __ko_value; }")}}if(h.length>0)e=e+", '_ko_property_writers' : { "+h.join("")+" } ";return e}}}();q.b("ko.jsonExpressionRewriting",q.p);q.b("ko.jsonExpressionRewriting.parseJson",q.p.F);q.b("ko.jsonExpressionRewriting.insertPropertyAccessorsIntoJson",q.p.P);q.c={}; q.Y=function(f,b,c){function d(i){return function(){return h[i]}}function e(){return h}var g=m,h;new q.m(function(){var i;if(!(i=typeof b=="function"?b():b)){var j=f.getAttribute("data-bind");try{var k=" { "+q.p.P(j)+" } ";i=q.a.Ha(k,c===o?window:c)}catch(l){a(Error("Unable to parse binding attribute.\nMessage: "+l+";\nAttribute value: "+j))}}h=i;if(g)for(var n in h)q.c[n]&&typeof q.c[n].init=="function"&&(0,q.c[n].init)(f,d(n),e,c);for(n in h)q.c[n]&&typeof q.c[n].update=="function"&&(0,q.c[n].update)(f, d(n),e,c)},o,{disposeWhen:function(){return!q.a.A(f)}});g=p};q.va=function(f,b){if(b&&b.nodeType==undefined)a(Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node (note: this is a breaking change since KO version 1.05)"));b=b||window.document.body;var c=q.a.Ja(b,"data-bind");q.a.h(c,function(d){q.Y(d,o,f)})};q.b("ko.bindingHandlers",q.c);q.b("ko.applyBindings",q.va); -q.a.h(["click"],function(f){q.c[f]={init:function(b,c,d,e){return q.c.event.init.call(this,b,function(){var g={};g[f]=c();return g},d,e)}}});q.c.event={init:function(f,b,c,d){var e=b()||{},g;for(g in e)typeof g=="string"&&q.a.o(f,g,function(h){var i,j=b()[g],k=c();try{i=j.apply(d,arguments)}finally{if(i!==m)if(h.preventDefault)h.preventDefault();else h.returnValue=p}if(k[g+"Bubble"]===p){h.cancelBubble=m;h.stopPropagation&&h.stopPropagation()}})}}; +q.a.h(["click"],function(f){q.c[f]={init:function(b,c,d,e){return q.c.event.init.call(this,b,function(){var g={};g[f]=c();return g},d,e)}}});q.c.event={init:function(f,b,c,d){var e=b()||{},g;for(g in e)(function(){var h=g;typeof h=="string"&&q.a.o(f,h,function(i){var j,k=b()[h],l=c();try{j=k.apply(d,arguments)}finally{if(j!==m)if(i.preventDefault)i.preventDefault();else i.returnValue=p}if(l[h+"Bubble"]===p){i.cancelBubble=m;i.stopPropagation&&i.stopPropagation()}})})()}}; q.c.submit={init:function(f,b,c,d){if(typeof b()!="function")a(Error("The value for a submit binding must be a function to invoke on submit"));q.a.o(f,"submit",function(e){var g,h=b();try{g=h.call(d,f)}finally{if(g!==m)if(e.preventDefault)e.preventDefault();else e.returnValue=p}})}};q.c.visible={update:function(f,b){var c=q.a.d(b()),d=f.style.display!="none";if(c&&!d)f.style.display="";else if(!c&&d)f.style.display="none"}}; q.c.enable={update:function(f,b){var c=q.a.d(b());if(c&&f.disabled)f.removeAttribute("disabled");else if(!c&&!f.disabled)f.disabled=m}};q.c.disable={update:function(f,b){q.c.enable.update(f,function(){return!q.a.d(b())})}}; q.c.value={init:function(f,b,c){var d=c().valueUpdate||"change",e=p;if(q.a.Wa(d,"after")){e=m;d=d.substring(5)}var g=e?function(h){setTimeout(h,0)}:function(h){h()};q.a.o(f,d,function(){g(function(){var h=b(),i=q.f.k(f);if(q.D(h))h(i);else{h=c();h._ko_property_writers&&h._ko_property_writers.value&&h._ko_property_writers.value(i)}})})},update:function(f,b){var c=q.a.d(b()),d=q.f.k(f),e=c!=d;if(c===0&&d!==0&&d!=="0")e=m;if(e){d=function(){q.f.I(f,c)};d();f.tagName=="SELECT"&&setTimeout(d,0)}if(f.tagName== diff --git a/spec/defaultBindingsBehaviors.js b/spec/defaultBindingsBehaviors.js index 87c9b6327..d0b301228 100755 --- a/spec/defaultBindingsBehaviors.js +++ b/spec/defaultBindingsBehaviors.js @@ -430,11 +430,17 @@ describe('Binding: Event', { before_each: prepareTestNode, 'Should invoke the supplied function when the event occurs, using model as \'this\' param': function () { - var model = { wasCalled: false, doCall: function () { this.wasCalled = true; } }; - testNode.innerHTML = ""; + var model = { + firstWasCalled: false, firstHandler: function () { this.firstWasCalled = true; }, + secondWasCalled: false, secondHandler: function () { this.secondWasCalled = true; } + }; + testNode.innerHTML = ""; ko.applyBindings(model, testNode); - ko.utils.triggerEvent(testNode.childNodes[0], "someEvent"); - value_of(model.wasCalled).should_be(true); + ko.utils.triggerEvent(testNode.childNodes[0], "firstEvent"); + value_of(model.firstWasCalled).should_be(true); + value_of(model.secondWasCalled).should_be(false); + ko.utils.triggerEvent(testNode.childNodes[0], "secondEvent"); + value_of(model.secondWasCalled).should_be(true); }, 'Should prevent default action': function () { diff --git a/src/binding/defaultBindings.js b/src/binding/defaultBindings.js index cdc32a45f..054592464 100755 --- a/src/binding/defaultBindings.js +++ b/src/binding/defaultBindings.js @@ -18,32 +18,35 @@ ko.utils.arrayForEach(eventHandlersWithShortcuts, function(eventName) { ko.bindingHandlers['event'] = { 'init' : function (element, valueAccessor, allBindingsAccessor, viewModel) { var eventsToHandle = valueAccessor() || {}; - for(var eventName in eventsToHandle) { - if (typeof eventName == "string") { - ko.utils.registerEventHandler(element, eventName, function (event) { - var handlerReturnValue; - var handlerFunction = valueAccessor()[eventName]; - var allBindings = allBindingsAccessor(); - - try { - handlerReturnValue = handlerFunction.apply(viewModel, arguments); - } finally { - if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true. - if (event.preventDefault) - event.preventDefault(); - else - event.returnValue = false; + for(var eventNameOutsideClosure in eventsToHandle) { + (function() { + var eventName = eventNameOutsideClosure; // Separate variable to be captured by event handler closure + if (typeof eventName == "string") { + ko.utils.registerEventHandler(element, eventName, function (event) { + var handlerReturnValue; + var handlerFunction = valueAccessor()[eventName]; + var allBindings = allBindingsAccessor(); + + try { + handlerReturnValue = handlerFunction.apply(viewModel, arguments); + } finally { + if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true. + if (event.preventDefault) + event.preventDefault(); + else + event.returnValue = false; + } } - } - - var bubble = allBindings[eventName + 'Bubble'] !== false; - if (!bubble) { - event.cancelBubble = true; - if (event.stopPropagation) - event.stopPropagation(); - } - }); - } + + var bubble = allBindings[eventName + 'Bubble'] !== false; + if (!bubble) { + event.cancelBubble = true; + if (event.stopPropagation) + event.stopPropagation(); + } + }); + } + })(); } } };