diff --git a/.gitignore b/.gitignore index 10d09b5..812ee6a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ npm-debug.log .idea test/domassist.test.dist.js dist +*.map +.DS_Store diff --git a/lib/modify.js b/lib/modify.js index 2a89fc7..e048e6b 100644 --- a/lib/modify.js +++ b/lib/modify.js @@ -19,17 +19,18 @@ function modify(selector, params) { addClass, removeClass, html, - events: on, - styles, + styles }; const els = find(selector); if (els.length) { els.forEach((el) => { - Object.keys(params).forEach((param, index) => { + Object.keys(params).forEach(param => { if (param in modules) { if (param === 'events') { bindEvents(el, params[param]); + return; } + modules[param](el, params[param]); } }); diff --git a/lib/off.js b/lib/off.js index a859db8..8eca807 100644 --- a/lib/off.js +++ b/lib/off.js @@ -1,22 +1,70 @@ import find from './find'; +import privData from './privData'; -function off(selector, event) { - if (Array.isArray(selector)) { - selector.forEach((item) => off(item, event)); +function extractFromDict(element, key, handler, justNameSpace) { + const eventDict = privData.get(element, 'events'); + let eventsToUnbind = []; + let type = key; + let namespace = ''; + + // Allowing namespace to continue + if (type.indexOf('.') > 0) { + const tmp = type.split('.'); + + type = tmp.shift(); + namespace = `.${tmp.join('.')}`; + + // Let's remove the namespaced events + extractFromDict(element, namespace, handler, true); } - if (!window._domassistevents) { - window._domassistevents = {}; + + let filtered = eventDict[type]; + + if (filtered) { + if (typeof handler !== 'undefined') { + // Just those that match the handler + filtered = filtered.filter(d => d.cb === handler); + } + + eventsToUnbind = eventsToUnbind.concat( + filtered.map(data => ({ type, data })) + ); + + // Actually removing from the dict + if (typeof handler !== 'undefined') { + eventDict[type] = eventDict[type].filter(d => d.cb !== handler); + } else { + eventDict[type].length = 0; + } + + // Don't keep the key, prevent leaks + if (eventDict[type].length === 0) { + eventDict[type] = undefined; + } + } + + // When unbinding for .namespace need to actually remove the normal events too + if (!justNameSpace && type.indexOf('.') === 0) { + // Flat map + eventsToUnbind = Array.prototype.concat.apply([], + eventsToUnbind.map(({ data }) => extractFromDict(element, data.type, data.cb))); } - const data = window._domassistevents[`_${event}`]; + return eventsToUnbind; +} - if (!data) { - return; +function off(selector, event, handler) { + if (Array.isArray(selector)) { + selector.forEach((item) => off(item, event, handler)); } + const el = find(selector); + if (el.length) { - el.forEach((item) => { - item.removeEventListener(event, data.cb, data.capture); + el.forEach(item => { + extractFromDict(item, event, handler).forEach(({ type, data }) => { + item.removeEventListener(type, data.cb, data.capture); + }); }); } } diff --git a/lib/on.js b/lib/on.js index 713ade5..b70aa1f 100644 --- a/lib/on.js +++ b/lib/on.js @@ -1,4 +1,14 @@ import find from './find'; +import privData from './privData'; + +function storeIntoDict(element, key, data) { + const eventDict = privData.get(element, 'events'); + if (!eventDict[key]) { + eventDict[key] = []; + } + + eventDict[key].push(data); +} function on(selector, event, cb, capture = false) { if (Array.isArray(selector)) { @@ -6,20 +16,33 @@ function on(selector, event, cb, capture = false) { return; } + const el = find(selector); + let type = event; + let storeKeys = [type]; + let namespaces = ''; + if (type.indexOf('.') > -1) { + const tmp = type.split('.'); + + // Not allowing to bind to .ns + if (!tmp[0]) { + return; + } + + type = tmp.shift(); + namespaces = `.${tmp.join('.')}`; + storeKeys = [type, namespaces]; + } + const data = { + type, cb, capture }; - if (!window._domassistevents) { - window._domassistevents = {}; - } - - window._domassistevents[`_${event}`] = data; - const el = find(selector); if (el.length) { - el.forEach((item) => { - item.addEventListener(event, cb, capture); + el.forEach(item => { + storeKeys.forEach(key => storeIntoDict(item, key, data)); + item.addEventListener(type, cb, capture); }); } } diff --git a/lib/privData.js b/lib/privData.js new file mode 100644 index 0000000..7417305 --- /dev/null +++ b/lib/privData.js @@ -0,0 +1,39 @@ +class Data { + constructor() { + this.uuid = `Domassist${`${Math.random()}`.replace(/\D/g, '')}`; + } + + cache(owner) { + let value = owner[this.uuid]; + + if (!value) { + value = {}; + + // If it's a DOM Node + if (owner.nodeType) { + owner[this.uuid] = value; + } else { + Object.defineProperty(owner, this.uuid, { + value, + configurable: true + }); + } + } + + return value; + } + + get(element, key, def = {}) { + const cache = this.cache(element); + let value = cache[key]; + + if (!value) { + cache[key] = def; + value = cache[key]; + } + + return value; + } +} + +export default new Data(); diff --git a/test/domassist.test.js b/test/domassist.test.js index 1c372dc..93dfc36 100644 --- a/test/domassist.test.js +++ b/test/domassist.test.js @@ -3,6 +3,7 @@ import domassist from '../domassist'; import test from 'tape-rollup'; import { teardown } from './setup'; +import './event-namespacing.test'; import './find.test'; import './classes.test'; import './attrs.test'; @@ -14,8 +15,7 @@ import './modify.test'; import './show-hide.test'; import './styles.test'; import './append.test'; - -const page = window.phantom.page; +import TestUtils from './test-utils'; test('ready', assert => { // if you add more assertions update this number @@ -117,9 +117,7 @@ test('Events - delegate', assert => { `; const button = domassist.findOne('button', el); - const pos = button.getBoundingClientRect(); - - page.sendEvent('click', pos.left + pos.width / 2, pos.top + pos.height / 2); + button.click(); }); test('Events - once', assert => { @@ -130,7 +128,6 @@ test('Events - once', assert => { `; const link = domassist.findOne('a', el); - const pos = link.getBoundingClientRect(); let clicks = 0; @@ -138,9 +135,9 @@ test('Events - once', assert => { clicks++; }); - page.sendEvent('click', pos.left + pos.width / 2, pos.top + pos.height / 2); - page.sendEvent('click', pos.left + pos.width / 2, pos.top + pos.height / 2); - page.sendEvent('click', pos.left + pos.width / 2, pos.top + pos.height / 2); + link.click(); + link.click(); + link.click(); setTimeout(() => { assert.equal(clicks, 1, 'Only fired once'); @@ -152,21 +149,21 @@ test('Events - hover', assert => { const el = domassist.findOne('#domassist'); el.innerHTML = ` -
+ `; const box = domassist.findOne('div', el); - const pos = box.getBoundingClientRect(); domassist.hover(box, e => { - assert.ok(e instanceof MouseEvent, 'Enter fired'); + assert.pass('Enter fired'); assert.equal(e.type, 'mouseenter', 'Correct event'); }, e => { - assert.ok(e instanceof MouseEvent, 'Leave fired'); + assert.pass('Leave fired'); assert.equal(e.type, 'mouseleave', 'Correct event'); - assert.end(); }); - page.sendEvent('mousemove', pos.left + pos.width / 2, pos.top + pos.height / 2); - page.sendEvent('mousemove', pos.left + pos.width + 100, pos.top + pos.height + 100); + TestUtils.fireEvent(box, 'mouseenter'); + TestUtils.fireEvent(box, 'mouseleave'); + + assert.end(); }); diff --git a/test/event-namespacing.test.js b/test/event-namespacing.test.js new file mode 100644 index 0000000..3b61eea --- /dev/null +++ b/test/event-namespacing.test.js @@ -0,0 +1,72 @@ +import domassist from '../domassist'; +import test from 'tape-rollup'; +import TestUtils from './test-utils'; + +test('Events - Namespaced should fire', assert => { + const el = domassist.findOne('#domassist'); + assert.plan(1); + el.innerHTML = ` + Click + `; + const link = domassist.findOne('a'); + + domassist.on(link, 'click.domassist', e => { + assert.pass('Event has been fired'); + }); + + link.click(); + assert.end(); +}); + +test('Events - Should be possible to unbind single event and namespace', assert => { + const el = domassist.findOne('#domassist'); + assert.plan(1); + el.innerHTML = ` + Click + `; + + const link = domassist.findOne('a'); + + domassist.on(link, 'click.domassist', () => { + assert.fail('I should not fire'); + }); + + domassist.on(link, 'mouseenter.domassist', () => { + assert.pass('Mouse enter fired normally'); + }); + + domassist.off(link, 'click.domassist'); + + link.click(); + TestUtils.fireEvent(link, 'mouseenter'); + assert.end(); +}); + +test('Events - Should be possible to unbind whole namespace', assert => { + const el = domassist.findOne('#domassist'); + el.innerHTML = ` + Click + `; + + const link = domassist.findOne('a'); + + domassist.on(link, 'click.domassist', () => { + assert.fail('I should not fire'); + }); + + domassist.on(link, 'mouseenter.domassist', () => { + assert.fail('I should not fire'); + }); + + domassist.on(link, 'custom.domassist', () => { + assert.fail('I should not fire'); + }); + + domassist.off(link, '.domassist'); + + link.click(); + TestUtils.fireEvent(link, 'mouseenter'); + TestUtils.fireEvent(link, 'custom'); + assert.pass('No event have fired'); + assert.end(); +}); diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000..cb44594 --- /dev/null +++ b/test/index.html @@ -0,0 +1,13 @@ + + + + + + +