Revamp event listeners

pull/2/head
paldepind 10 years ago
parent 1c509cd653
commit c2b6e7c325

@ -12,7 +12,6 @@
'use strict'; 'use strict';
var isArr = Array.isArray; var isArr = Array.isArray;
function isString(s) { return typeof s === 'string'; } function isString(s) { return typeof s === 'string'; }
function isPrimitive(s) { return typeof s === 'string' || typeof s === 'number'; } function isPrimitive(s) { return typeof s === 'string' || typeof s === 'number'; }
function isUndef(s) { return s === undefined; } function isUndef(s) { return s === undefined; }
@ -30,8 +29,6 @@ var emptyNode = VNode(undefined, {style: {}, class: {}}, [], undefined);
var frag = document.createDocumentFragment(); var frag = document.createDocumentFragment();
var eventSelectors = [];
function h(selector, b, c) { function h(selector, b, c) {
var props = {}, children, tag, text, i; var props = {}, children, tag, text, i;
if (arguments.length === 3) { if (arguments.length === 3) {
@ -60,11 +57,12 @@ function h(selector, b, c) {
return VNode(tag, props, children, text, undefined); return VNode(tag, props, children, text, undefined);
} }
function arrInvoker(arr) {
return function() { arr[0](arr[1]); };
}
function updateProps(elm, oldVnode, vnode) { function updateProps(elm, oldVnode, vnode) {
var key, val, name, cur, pushed = false, var key, val, name, cur, old, oldProps = oldVnode.props, props = vnode.props;
oldProps = oldVnode.props, props = vnode.props,
selectorChanged = oldProps.className !== props.className ||
oldVnode.tag !== vnode.tag;
for (key in props) { for (key in props) {
val = props[key]; val = props[key];
if (key === 'style' || key === 'class') { if (key === 'style' || key === 'class') {
@ -74,46 +72,32 @@ function updateProps(elm, oldVnode, vnode) {
if (key === 'style') { if (key === 'style') {
elm.style[name] = cur; elm.style[name] = cur;
} else { } else {
selectorChanged = true;
elm.classList[cur ? 'add' : 'remove'](name); elm.classList[cur ? 'add' : 'remove'](name);
} }
} }
} }
} else if (key === 'on') { } else if (key[0] === 'o' && key[1] === 'n') {
pushed = true; name = key.slice(2);
eventSelectors.push(props.on); if (name !== 'insert' && name !== 'remove') {
} else if (key !== 'key' && (key[0] !== 'o' || key[1] !== 'n')) { old = oldProps[key];
elm[key] = val; if (isUndef(old)) {
} elm.addEventListener(name, isArr(val) ? arrInvoker(val) : val);
} } else if (isArr(old)) {
if (selectorChanged) toggleListeners(elm, oldVnode === emptyNode, pushed); old[0] = val[0]; // Deliberately modify old array since it's
return pushed; old[1] = val[1]; // captured in closure created with `arrInvoker`
}
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 (justCreated && i === start && pushed) {
elm.addEventListener(parts[0], obj[evSel]);
} }
} else if (key !== 'key') {
elm[key] = val;
} }
} }
} }
function createElm(vnode) { function createElm(vnode) {
var elm, children, pushedSelectors; var elm, children;
if (!isUndef(vnode.tag)) { if (!isUndef(vnode.tag)) {
elm = vnode.elm = document.createElement(vnode.tag); elm = vnode.elm = document.createElement(vnode.tag);
pushedSelectors = updateProps(elm, emptyNode, vnode); updateProps(elm, emptyNode, vnode);
children = vnode.children; children = vnode.children;
if (isArr(children)) { if (isArr(children)) {
for (var i = 0; i < children.length; ++i) { for (var i = 0; i < children.length; ++i) {
@ -122,7 +106,6 @@ function createElm(vnode) {
} else if (isPrimitive(vnode.text)) { } else if (isPrimitive(vnode.text)) {
elm.textContent = vnode.text; elm.textContent = vnode.text;
} }
if (pushedSelectors) eventSelectors.pop();
if (vnode.props.oninsert) vnode.props.oninsert(vnode); if (vnode.props.oninsert) vnode.props.oninsert(vnode);
} else { } else {
elm = vnode.elm = document.createTextNode(vnode.text); elm = vnode.elm = document.createTextNode(vnode.text);
@ -230,14 +213,13 @@ function updateChildren(parentElm, oldCh, newCh) {
} }
function patchVnode(oldVnode, newVnode) { function patchVnode(oldVnode, newVnode) {
var elm = newVnode.elm = oldVnode.elm, pushedSelectors; var elm = newVnode.elm = oldVnode.elm;
if (!isUndef(newVnode.props)) pushedSelectors = updateProps(elm, oldVnode, newVnode); if (!isUndef(newVnode.props)) updateProps(elm, oldVnode, newVnode);
if (isUndef(newVnode.text)) { if (isUndef(newVnode.text)) {
updateChildren(elm, oldVnode.children, newVnode.children); updateChildren(elm, oldVnode.children, newVnode.children);
} else if (oldVnode.text !== newVnode.text) { } else if (oldVnode.text !== newVnode.text) {
elm.textContent = newVnode.text; elm.textContent = newVnode.text;
} }
if (pushedSelectors) eventSelectors.pop();
return newVnode; return newVnode;
} }

@ -408,82 +408,54 @@ describe('snabbdom', function() {
}); });
}); });
describe('event handling', function() { describe('event handling', function() {
it('attaches click event handler to children', function() { it('attaches click event handler to element', function() {
var result = []; var result = [];
function clicked(ev) { result.push(ev); } function clicked(ev) { result.push(ev); }
var vnode = h('div', {on: {'click a.me': clicked}}, [ var vnode = h('div', {onclick: clicked}, [
h('a.me', 'Click me'), h('a', 'Click my parent'),
h('span', 'Not me'),
]); ]);
var elm = createElm(vnode); var elm = createElm(vnode);
var a = elm.children[0]; elm.click();
var span = elm.children[1]; assert.equal(1, result.length);
a.click();
span.click();
a.click();
assert.equal(2, result.length);
}); });
it('attaches click event handler self', function() { it('does not attach new listener', function() {
var result = []; var result = [];
function clicked(ev) { result.push(ev); } //function clicked(ev) { result.push(ev); }
var vnode = h('div', {on: {'click': clicked}}, [ var vnode1 = h('div', {onclick: function(ev) { result.push(ev); }}, [
h('a', 'Click my parent'), h('a', 'Click my parent'),
]); ]);
var elm = createElm(vnode); var vnode2 = h('div', {onclick: function(ev) { result.push(ev); }}, [
var a = elm.children[0]; h('a', 'Click my parent'),
]);
var elm = createElm(vnode1);
patch(vnode1, vnode2);
elm.click(); elm.click();
assert.equal(1, result.length); assert.equal(1, result.length);
}); });
it('attaches click event handler to subchilden', function() { it('does calls handler for function in array', function() {
var result = []; var result = [];
function clicked(ev) { result.push(ev); } function clicked(ev) { result.push(ev); }
function noop() { } var vnode = h('div', {onclick: [clicked, 1]}, [
var vnode = h('div', {on: {'click .nothing': noop}}, [ h('a', 'Click my parent'),
h('span', {on: {'click a': clicked}}, [h('a', 'Click me')]),
h('span', 'Not me'),
]); ]);
var elm = createElm(vnode); var elm = createElm(vnode);
var a = elm.children[0].children[0]; elm.click();
a.click(); assert.deepEqual(result, [1]);
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);
}); });
it('removes event handler from children', function() { it('handles changed value in array', function() {
var result = []; var result = [];
function clicked(ev) { result.push(ev); } function clicked(ev) { result.push(ev); }
var vnode1 = h('div', {on: {'click a.me': clicked}}, [ var vnode1 = h('div', {onclick: [clicked, 1]}, [
h('a.me', 'Click me'), h('a', 'Click my parent'),
h('span', 'Not me'),
]); ]);
var vnode2 = h('div', {on: {'click a.yes.me': clicked}}, [ var vnode2 = h('div', {onclick: [clicked, 2]}, [
h('a.yes', 'Click me'), h('a', 'Click my parent'),
h('span', 'Not me'),
]); ]);
var elm = createElm(vnode1); var elm = createElm(vnode1);
var a = elm.children[0]; elm.click();
a.click();
patch(vnode1, vnode2); patch(vnode1, vnode2);
a.click(); elm.click();
assert.equal(1, result.length); assert.deepEqual(result, [1, 2]);
}); });
}); });
describe('custom events', function() { describe('custom events', function() {

Loading…
Cancel
Save