diff --git a/index.js b/index.js index b69c249..d89393a 100644 --- a/index.js +++ b/index.js @@ -1,35 +1,68 @@ /* global MutationObserver */ var document = require('global/document') var window = require('global/window') -var watch = [] +var watch = Object.create(null) +var KEY_ID = 'onloadid' if (window && window.MutationObserver) { var observer = new MutationObserver(function (mutations) { for (var i = 0; i < mutations.length; i++) { - var mutation = mutations[i] - var x, y - for (x = 0; x < mutation.addedNodes.length; x++) { - for (y = 0; y < watch.length; y++) { - if (watch[y][0] === mutation.addedNodes[x]) { - watch[y][1]() - } - } - } - for (x = 0; x < mutation.removedNodes.length; x++) { - for (y = 0; y < watch.length; y++) { - if (watch[y][0] === mutation.removedNodes[x]) { - watch[y][2]() - watch.splice(y, 1) - } - } - } + eachMutation(mutations[i].removedNodes, function (target) { + target[1]() + }) + eachMutation(mutations[i].addedNodes, function (target) { + // TODO: Should queue functions to run, then run them all on setImmediate? + target[0]() + }) } }) observer.observe(document.body, {childList: true, subtree: true}) } -module.exports = function onload (el, l, u) { - l = l || function () {} - u = u || function () {} - watch.push([el, l, u]) +module.exports = function onload (el, on, off) { + on = on || function () {} + off = off || function () {} + var id + if (el.dataset && el.dataset[KEY_ID]) { + id = el.dataset[KEY_ID] + } else { + // TODO: Is there a better way to uniquely identify an element? + var caller = onload.caller.toString() + if (caller) { + id = hash(caller) + } else { + var err = new Error() + var lines = err.stack.split('\n') + for (var i = 0; i < lines.length; i++) { + if (lines[i].indexOf('onload') !== -1) { + id = lines[i + 1] + break + } + } + id = hash(id.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '')) + } + el.dataset[KEY_ID] = id + } + // TODO: Should we allow multiple set per element? + watch[id] = [on, off] +} + +function eachMutation (nodes, fn) { + for (var i = 0; i < nodes.length; i++) { + if (nodes[i].dataset && nodes[i].dataset[KEY_ID] && watch[nodes[i].dataset[KEY_ID]]) { + fn(watch[nodes[i].dataset[KEY_ID]]) + } + if (nodes[i].childNodes.length > 0) { + eachMutation(nodes[i].childNodes, fn) + } + } +} + +function hash (str) { + var res = 5381 + var i = str.length + while (i) { + res = (res * 33) ^ str.charCodeAt(--i) + } + return res >>> 0 } diff --git a/package.json b/package.json index bd829e5..849e3b2 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "standard": "^6.0.7", "tape": "^4.5.0", "testron": "^1.2.0", - "wzrd": "^1.3.1" + "wzrd": "^1.3.1", + "yo-yo": "^1.2.1" } } diff --git a/test.js b/test.js index a2304ee..83ab972 100644 --- a/test.js +++ b/test.js @@ -1,5 +1,6 @@ var onload = require('./') var test = require('tape') +var yo = require('yo-yo') test('onload/onunload', function (t) { t.plan(2) @@ -34,3 +35,128 @@ test('nested', function (t) { e2.appendChild(e3) e2.removeChild(e3) }) + +test('complex', function (t) { + t.plan(4) + var state = [] + + function button () { + var el = yo`` + onload(el, function () { + state.push('on') + }, function () { + state.push('off') + }) + return el + } + + var root = yo`
+ ${button()} +
` + document.body.appendChild(root) + + runops([ + function () { + t.deepEqual(state, ['on'], 'turn on') + state = [] + root = yo.update(root, yo`

${button()}

`) + }, + function () { + t.deepEqual(state, ['off', 'on'], 'turn off/on') + state = [] + root = yo.update(root, yo`

removed

`) + }, + function () { + t.deepEqual(state, ['off'], 'turn off') + state = [] + var btn = button() + root = yo.update(root, yo`

${btn}

`) + root = yo.update(root, yo`

+

Updated
+
${btn}
+

`) + }, + function () { + t.deepEqual(state, ['off', 'on'], 'turn off/on') + } + ], t.end) +}) + +test('complex nested', function (t) { + var state = [] + function button () { + var el = yo`` + onload(el, function () { + state.push('on') + }, function () { + state.push('off') + }) + return el + } + function app (page) { + return yo`
+

Hello

+ ${page} +
` + } + + var root = app(yo`
Loading...
`) + document.body.appendChild(root) + + runops([ + function () { + t.deepEqual(state, [], 'did nothing') + state = [] + root = yo.update(root, app(yo`
+ ${button()} +
`)) + }, + function () { + t.deepEqual(state, ['on'], 'turn on') + state = [] + root = yo.update(root, app(yo`
+

Another Page

+
`)) + }, + function () { + t.deepEqual(state, ['off'], 'turn off') + state = [] + root = yo.update(root, app(yo`
+ ${button()} + ${button()} +
`)) + }, + function () { + t.deepEqual(state, ['on', 'on'], 'turn 2 on') + state = [] + root = yo.update(root, app(yo`
+ ${button()} + ${button()} +
`)) + }, + function () { + t.deepEqual(state, [], 'do nothing') + state = [] + root = yo.update(root, app(yo`
+ ${button()} +

removed

+
`)) + }, + function () { + t.deepEqual(state, ['off'], 'turn 1 off') + } + ], t.end) +}) + +function runops (ops, done) { + function loop () { + var next = ops.shift() + if (next) { + next() + setTimeout(loop, 10) + } else { + done() + } + } + setTimeout(loop, 10) +}