From c2b6e7c3253214dfa8f04cfe80c32e4f6d13e31b Mon Sep 17 00:00:00 2001 From: paldepind Date: Fri, 15 May 2015 21:57:50 +0200 Subject: [PATCH] Revamp event listeners --- snabbdom.js | 58 +++++++++++++------------------------ test/index.js | 80 +++++++++++++++++---------------------------------- 2 files changed, 46 insertions(+), 92 deletions(-) diff --git a/snabbdom.js b/snabbdom.js index a1b2f79..dc11229 100644 --- a/snabbdom.js +++ b/snabbdom.js @@ -12,7 +12,6 @@ 'use strict'; var isArr = Array.isArray; - function isString(s) { return typeof s === 'string'; } function isPrimitive(s) { return typeof s === 'string' || typeof s === 'number'; } function isUndef(s) { return s === undefined; } @@ -30,8 +29,6 @@ var emptyNode = VNode(undefined, {style: {}, class: {}}, [], undefined); var frag = document.createDocumentFragment(); -var eventSelectors = []; - function h(selector, b, c) { var props = {}, children, tag, text, i; if (arguments.length === 3) { @@ -60,11 +57,12 @@ function h(selector, b, c) { return VNode(tag, props, children, text, undefined); } +function arrInvoker(arr) { + return function() { arr[0](arr[1]); }; +} + function updateProps(elm, oldVnode, vnode) { - var key, val, name, cur, pushed = false, - oldProps = oldVnode.props, props = vnode.props, - selectorChanged = oldProps.className !== props.className || - oldVnode.tag !== vnode.tag; + var key, val, name, cur, old, oldProps = oldVnode.props, props = vnode.props; for (key in props) { val = props[key]; if (key === 'style' || key === 'class') { @@ -74,46 +72,32 @@ function updateProps(elm, oldVnode, vnode) { if (key === 'style') { elm.style[name] = cur; } else { - selectorChanged = true; elm.classList[cur ? 'add' : 'remove'](name); } } } - } else if (key === 'on') { - pushed = true; - eventSelectors.push(props.on); - } else if (key !== 'key' && (key[0] !== 'o' || key[1] !== 'n')) { - elm[key] = val; - } - } - if (selectorChanged) toggleListeners(elm, oldVnode === emptyNode, pushed); - return pushed; -} - -function toggleListeners(elm, justCreated, pushed) { - var i, obj, evSel, parts, start = eventSelectors.length - 1; - for (i = start; 0 <= i; --i) { - obj = eventSelectors[i]; - for (evSel in obj) { - parts = evSel.split(' '); - if (parts.length === 2) { - if (elm.matches(parts[1])) { - elm.addEventListener(parts[0], obj[evSel]); - } else if (!justCreated) { - elm.removeEventListener(parts[0], obj[evSel], false); + } else if (key[0] === 'o' && key[1] === 'n') { + name = key.slice(2); + if (name !== 'insert' && name !== 'remove') { + old = oldProps[key]; + if (isUndef(old)) { + elm.addEventListener(name, isArr(val) ? arrInvoker(val) : val); + } else if (isArr(old)) { + old[0] = val[0]; // Deliberately modify old array since it's + old[1] = val[1]; // captured in closure created with `arrInvoker` } - } else if (justCreated && i === start && pushed) { - elm.addEventListener(parts[0], obj[evSel]); } + } else if (key !== 'key') { + elm[key] = val; } } } function createElm(vnode) { - var elm, children, pushedSelectors; + var elm, children; if (!isUndef(vnode.tag)) { elm = vnode.elm = document.createElement(vnode.tag); - pushedSelectors = updateProps(elm, emptyNode, vnode); + updateProps(elm, emptyNode, vnode); children = vnode.children; if (isArr(children)) { for (var i = 0; i < children.length; ++i) { @@ -122,7 +106,6 @@ function createElm(vnode) { } else if (isPrimitive(vnode.text)) { elm.textContent = vnode.text; } - if (pushedSelectors) eventSelectors.pop(); if (vnode.props.oninsert) vnode.props.oninsert(vnode); } else { elm = vnode.elm = document.createTextNode(vnode.text); @@ -230,14 +213,13 @@ function updateChildren(parentElm, oldCh, newCh) { } function patchVnode(oldVnode, newVnode) { - var elm = newVnode.elm = oldVnode.elm, pushedSelectors; - if (!isUndef(newVnode.props)) pushedSelectors = updateProps(elm, oldVnode, newVnode); + var elm = newVnode.elm = oldVnode.elm; + if (!isUndef(newVnode.props)) updateProps(elm, oldVnode, newVnode); if (isUndef(newVnode.text)) { updateChildren(elm, oldVnode.children, newVnode.children); } else if (oldVnode.text !== newVnode.text) { elm.textContent = newVnode.text; } - if (pushedSelectors) eventSelectors.pop(); return newVnode; } diff --git a/test/index.js b/test/index.js index dc58731..65b1dc3 100644 --- a/test/index.js +++ b/test/index.js @@ -408,82 +408,54 @@ describe('snabbdom', function() { }); }); describe('event handling', function() { - it('attaches click event handler to children', function() { + it('attaches click event handler to element', function() { var result = []; function clicked(ev) { result.push(ev); } - var vnode = h('div', {on: {'click a.me': clicked}}, [ - h('a.me', 'Click me'), - h('span', 'Not me'), + var vnode = h('div', {onclick: clicked}, [ + h('a', 'Click my parent'), ]); var elm = createElm(vnode); - var a = elm.children[0]; - var span = elm.children[1]; - a.click(); - span.click(); - a.click(); - assert.equal(2, result.length); + elm.click(); + assert.equal(1, result.length); }); - it('attaches click event handler self', function() { + it('does not attach new listener', function() { var result = []; - function clicked(ev) { result.push(ev); } - var vnode = h('div', {on: {'click': clicked}}, [ + //function clicked(ev) { result.push(ev); } + var vnode1 = h('div', {onclick: function(ev) { result.push(ev); }}, [ h('a', 'Click my parent'), ]); - var elm = createElm(vnode); - var a = elm.children[0]; + var vnode2 = h('div', {onclick: function(ev) { result.push(ev); }}, [ + h('a', 'Click my parent'), + ]); + var elm = createElm(vnode1); + patch(vnode1, vnode2); elm.click(); assert.equal(1, result.length); }); - it('attaches click event handler to subchilden', function() { + it('does calls handler for function in array', function() { var result = []; function clicked(ev) { result.push(ev); } - function noop() { } - var vnode = h('div', {on: {'click .nothing': noop}}, [ - h('span', {on: {'click a': clicked}}, [h('a', 'Click me')]), - h('span', 'Not me'), + var vnode = h('div', {onclick: [clicked, 1]}, [ + h('a', 'Click my parent'), ]); var elm = createElm(vnode); - var a = elm.children[0].children[0]; - a.click(); - a.click(); - assert.equal(2, result.length); - }); - it('attaches event handler when class is added to child', function() { - var a, result = []; - function clicked(ev) { result.push(ev); } - var vnode1 = h('div', {on: {'click .btn': clicked}}, [ - h('span', [h('a', 'Click me')]), - h('span', 'Not me'), - ]); - var vnode2 = h('div', {on: {'click .btn': clicked}}, [ - h('span', [h('a.btn', 'Click me')]), - h('span', 'Not me'), - ]); - var elm = createElm(vnode1); - a = elm.children[0].children[0]; - a.click(); - patch(vnode1, vnode2); - a = elm.children[0].children[0]; - a.click(); - assert.equal(1, result.length); + elm.click(); + assert.deepEqual(result, [1]); }); - it('removes event handler from children', function() { + it('handles changed value in array', function() { var result = []; function clicked(ev) { result.push(ev); } - var vnode1 = h('div', {on: {'click a.me': clicked}}, [ - h('a.me', 'Click me'), - h('span', 'Not me'), + var vnode1 = h('div', {onclick: [clicked, 1]}, [ + h('a', 'Click my parent'), ]); - var vnode2 = h('div', {on: {'click a.yes.me': clicked}}, [ - h('a.yes', 'Click me'), - h('span', 'Not me'), + var vnode2 = h('div', {onclick: [clicked, 2]}, [ + h('a', 'Click my parent'), ]); var elm = createElm(vnode1); - var a = elm.children[0]; - a.click(); + elm.click(); patch(vnode1, vnode2); - a.click(); - assert.equal(1, result.length); + elm.click(); + assert.deepEqual(result, [1, 2]); }); }); describe('custom events', function() {