diff --git a/.gitignore b/.gitignore index 10d09b5..c5a0c24 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ npm-debug.log .idea test/domassist.test.dist.js dist +*js.map diff --git a/lib/idStorage.js b/lib/idStorage.js new file mode 100644 index 0000000..23f3106 --- /dev/null +++ b/lib/idStorage.js @@ -0,0 +1,61 @@ +const storage = new Map(); + +const idStorage = { + areSimilar(a, b) { + const everyKey = f => Object.keys(a).every(f); + const type = ({}).toString + .call(a) + .match(/\[object (\w+)\]/)[1]; + + switch (type) { + case 'Array': + return a.length === b.length && + everyKey(k => idStorage.areSimilar(a.sort()[k], b.sort()[k])); + case 'Object': + return Object.keys(a).length === Object.keys(b).length && + everyKey(k => idStorage.areSimilar(a[k], b[k])); + default: + return a === b; + } + }, + + add(data) { + const id = idStorage.generateID(); + storage.set(id, data); + + return id; + }, + + generateID() { + const h = n => (n | 0).toString(16); + const s = n => h((Math.random() * (1 << (n << 2))) ^ Date.now()).slice(-n); + + return [ + s(4) + s(4), s(4), + `4${s(3)}`, + h(8 | (Math.random() * 4)) + s(3), + Date.now().toString(16).slice(-10) + s(2) + ].join('-'); + }, + + removeFromID(id) { + const val = storage.get(id); + storage.delete(id); + + return val; + }, + + removeFromValue(value) { + let found = null; + + storage.forEach((val, key) => { + if (!found && idStorage.areSimilar(val, value)) { + found = key; + } + }); + + return idStorage.removeFromID(found); + } +}; + +export default idStorage; diff --git a/lib/modify.js b/lib/modify.js index 2a89fc7..48e48a2 100644 --- a/lib/modify.js +++ b/lib/modify.js @@ -6,9 +6,7 @@ import on from './on'; import styles from './styles'; function bindEvents(el, events) { - Object.keys(events).forEach((event) => { - on(el, event, events[event]); - }); + return Object.keys(events).map(event => on(el, event, events[event])); } function modify(selector, params) { @@ -25,11 +23,12 @@ function modify(selector, params) { 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 bindEvents(el, params[param]); } + modules[param](el, params[param]); } }); diff --git a/lib/off.js b/lib/off.js index a859db8..980fafd 100644 --- a/lib/off.js +++ b/lib/off.js @@ -1,22 +1,33 @@ import find from './find'; +import idStorage from './idStorage'; -function off(selector, event) { +function off(selector, event, handler, capture = false) { if (Array.isArray(selector)) { - selector.forEach((item) => off(item, event)); - } - if (!window._domassistevents) { - window._domassistevents = {}; + selector.forEach((item) => off(item, event, handler, capture)); } - const data = window._domassistevents[`_${event}`]; + // ID + if (typeof selector === 'string' && typeof event === 'undefined') { + const data = idStorage.removeFromID(selector); + + if (!data) { + return; + } - if (!data) { - return; + return off(data.el, data.event, data.handler, data.capture); } + const el = find(selector); + if (el.length) { el.forEach((item) => { - item.removeEventListener(event, data.cb, data.capture); + item.removeEventListener(event, handler); + idStorage.removeFromValue({ + el: item, + event, + handler, + capture + }); }); } } diff --git a/lib/on.js b/lib/on.js index 713ade5..a83dc77 100644 --- a/lib/on.js +++ b/lib/on.js @@ -1,27 +1,33 @@ import find from './find'; +import idStorage from './idStorage'; -function on(selector, event, cb, capture = false) { +function on(selector, event, handler, capture = false) { if (Array.isArray(selector)) { - selector.forEach((item) => on(item, event, cb, capture)); - return; + return Array.prototype.concat.apply([], + selector.map(item => on(item, event, handler, capture))); } - const data = { - cb, - capture - }; - - if (!window._domassistevents) { - window._domassistevents = {}; - } - - window._domassistevents[`_${event}`] = data; const el = find(selector); + let ids = null; + if (el.length) { - el.forEach((item) => { - item.addEventListener(event, cb, capture); + ids = el.map(item => { + item.addEventListener(event, handler); + + return idStorage.add({ + el: item, + event, + handler, + capture + }); }); + + if (ids.length === 1) { + ids = ids[0]; + } } + + return ids; } export default on; diff --git a/lib/once.js b/lib/once.js index 6582f68..5069cbd 100644 --- a/lib/once.js +++ b/lib/once.js @@ -1,10 +1,24 @@ import on from './on'; import off from './off'; +import find from './find'; -function once(el, event, run, capture = false) { - on(el, event, e => { - off(el, event); - run(e); +function once(selector, event, handler, capture = false) { + if (Array.isArray(selector)) { + selector.forEach(item => once(item, event, handler, capture)); + return; + } + + const el = find(selector); + + if (el.length > 1) { + // Need to get the event id for each element so once runs properly + once(el, event, handler, capture); + return; + } + + const eventId = on(el, event, e => { + off(eventId); + handler(e); }, capture); } diff --git a/package.json b/package.json index 7686366..2f51348 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "devDependencies": { "eslint-config-firstandthird": "3.2.0", "eslint-plugin-import": "^2.1.0", + "map-polyfill": "0.0.1", "phantomjs-prebuilt": "^2.1.14", "scriptkit": "0.2.0", "static-server": "^2.0.3", diff --git a/test/domassist.test.js b/test/domassist.test.js index 1c372dc..7fef05c 100644 --- a/test/domassist.test.js +++ b/test/domassist.test.js @@ -1,5 +1,6 @@ /* eslint no-console: 0 */ +import '../node_modules/map-polyfill/dist/map.min'; import domassist from '../domassist'; import test from 'tape-rollup'; import { teardown } from './setup'; @@ -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,17 +128,15 @@ test('Events - once', assert => { `; const link = domassist.findOne('a', el); - const pos = link.getBoundingClientRect(); let clicks = 0; - domassist.once(link, 'click', e => { 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 +148,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/index.html b/test/index.html new file mode 100644 index 0000000..cb44594 --- /dev/null +++ b/test/index.html @@ -0,0 +1,13 @@ + + + + + + +