From bdadba8df7e562ccaffa344431936373220bdcc5 Mon Sep 17 00:00:00 2001 From: Iain Shorter Date: Fri, 28 Oct 2016 18:37:07 +0100 Subject: [PATCH 1/9] first implementation pass, no intergration --- src/MutationEvents.js | 170 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 src/MutationEvents.js diff --git a/src/MutationEvents.js b/src/MutationEvents.js new file mode 100644 index 0000000..1eb1045 --- /dev/null +++ b/src/MutationEvents.js @@ -0,0 +1,170 @@ +var observedElements = []; +var timerRef = null; + +// TODO rAF polyfill here + +function setFrameInterval (callback) { + var handler = function () { + if (!ref) + return; + ref = requestAnimationFrame(handler); + callback(); + }; + var ref = requestAnimationFrame(handler); + + return function cancel () { + ref = null; + } +} + +/* + Returns our status obect, which is a snapshot of the element at the + last update. If it does not exist yet, and shouldCreate is set then it + will be created. Will start the timer if it isn't running yet. +*/ +function getStatus (element, shouldCreate) { + var i = 0; + var l = observedElements.length; + var status = null; + + for (; i < l; i++) + { + if (observedElements[i].element === element) { + status = observedElements[i]; + break; + } + } + + if (!status && shouldCreate) { + status = { + element: element, + root: getRoot(element), + parent: element.parentNode, + handlers: { + + } + }; + observedElements.push(status); + start(); + } + + return status; +} + +function addHandler(element, eventName, callback) { + var status = getStatus(element, true); + + if (status[eventName].indexOf(callback) === -1) { + status[eventName].push(callback); + status.handler++; + } +} + +function removeConnectionHandler (element, eventName, callback) { + var status = getStatus(element, false); + + if (status) { + var index = status[eventName].indexOf(callback); + if (index !== -1) { + if (status.handlers.length === 1) { + ignoreElement(element); + } else { + status[eventName].splice(index, 1); + status.handler--; + } + } + } +} + +/* + Get the root element for an element +*/ +function getRoot(element) { + while (element.parentNode) + element = element.parentNode; + + return element; +} + +/* + Executes a collection of handlers, with the element as the context. Can pass + an eventObject for extra information. +*/ +function fireEvent (status, eventName, eventObject) { + var handlers = status.handlers[eventName]; + var element = status.element; + + for (var i = 0, l = handlers.length; i < l; i++) { + handlers[i].call(element, eventObject); + } +} + +/* + Removes an element from the observation list, will stop the timer if its the + last element on the list. +*/ +function ignoreElement (element) { + var i = 0; + var l = observedElements.length; + + for (; i < l; i++) + { + if (observedElements[i].element === element) { + observedElements.splice(i, 1); + break; + } + } + + if (observedElements.length === 0) + stop(); +} + +/* + Checks for modifications to elements on the observation list and calls the + relevent handlers. +*/ +function poll () { + var i = 0; + var l = observedElements.length; + var status = null; + var element = null; + + for (; i < l; i++) { + status = observedElements[i]; + element = status.element; + + if (status.root.contains(element) === false) { + fireEvent(element, status.adoptedHandler); + status.root = getRoot(element); + } + + if (status.parent !== element.parentNode) { + fireEvent(element, status.disconnectHandler); + if (element.parentNode) { + fireEvent(element, status.connectHandler); + } + status.parent = element.parentNode; + } + } +} + +/* + Starts the timer if it isn't running. +*/ +function start () { + if (timerRef) + return; + + timerRef = setFrameInterval(poll); +} + +/* + Stops the timer if it is running. +*/ +function stop () { + if (!timerRef) + return; + + timerRef(); + timerRef = null; +} \ No newline at end of file From c0ca36e9343135f00abbc5710c9e3599738bc7c3 Mon Sep 17 00:00:00 2001 From: Iain Shorter Date: Wed, 2 Nov 2016 18:28:31 +0000 Subject: [PATCH 2/9] Implementation candidate 1 Digest timer modified to use variable interval, either RAF or based on average execution time Moved callbacks to a handlers object within the status object. Added type protection to public methods. Updated getRoot method to return null when no root it present. Updated event names. Added limited recusion to the poll method, to detect changes during event dispatch. --- src/MutationEvents.js | 374 ++++++++++++++++++++++++++---------------- 1 file changed, 231 insertions(+), 143 deletions(-) diff --git a/src/MutationEvents.js b/src/MutationEvents.js index 1eb1045..ae112d2 100644 --- a/src/MutationEvents.js +++ b/src/MutationEvents.js @@ -1,170 +1,258 @@ -var observedElements = []; -var timerRef = null; - -// TODO rAF polyfill here - -function setFrameInterval (callback) { - var handler = function () { - if (!ref) - return; - ref = requestAnimationFrame(handler); - callback(); - }; - var ref = requestAnimationFrame(handler); +(function(){ + var EVENT_NAMES = ["adopt", "connect", "disconnect"]; + var MAX_REPEAT = 10; // iterations + var MIN_UPDATE_INT = 16; // ms + var MAX_UPDATE_INT = 100; // ms - return function cancel () { - ref = null; - } -} + var observedElements = []; + var timerRef = null; -/* - Returns our status obect, which is a snapshot of the element at the - last update. If it does not exist yet, and shouldCreate is set then it - will be created. Will start the timer if it isn't running yet. -*/ -function getStatus (element, shouldCreate) { - var i = 0; - var l = observedElements.length; - var status = null; + var requestCallback = function () { + var executionTime = MIN_UPDATE_INT; + var time = Date.now || function () { + return +new Date; + }; + function clamp (val, min, max) { + return Math.round(Math.min(Math.max(val, min), max)); + } + return requestAnimationFrame || function (callback) { + setTimeout(function () { + var start = time(); + callback(); + executionTime += (time() - start - executionTime) * 0.1; + executionTime = clamp(executionTime, MIN_UPDATE_INT, MAX_UPDATE_INT); + }, executionTime); + } + }(); - for (; i < l; i++) - { - if (observedElements[i].element === element) { - status = observedElements[i]; - break; + /* + To prevent the mutation events using too much of the processing time + the poll uses a variable interval based on the average execution time. + This means that it should only be able to use half the processing time + maximum. MIN_UPDATE_INT and MAX_UPDATE_INT are cap values for the interval. + */ + function setFrameInterval (callback) { + var handler = function () { + if (!ref) + return; + callback(); + ref = requestCallback(handler); + }; + var ref = requestCallback(handler); + return function cancel () { + ref = null; } } - - if (!status && shouldCreate) { - status = { - element: element, - root: getRoot(element), - parent: element.parentNode, - handlers: { - + + /* + Returns our status obect, which is a snapshot of the element at the + last update. If it does not exist yet, and shouldCreate is set then it + will be created. Will start the timer if it isn't running yet. + */ + function getStatus (element, shouldCreate) { + var i = 0; + var l = observedElements.length; + var status = null; + + for (; i < l; i++) + { + if (observedElements[i].element === element) { + status = observedElements[i]; + break; } - }; - observedElements.push(status); - start(); + } + + if (!status && shouldCreate) { + status = { + element: element, + root: getRoot(element), + parent: element.parentNode, + handlers: { + "connect": [], + "disconnect": [], + "adopt": [], + length: 0 + } + }; + observedElements.push(status); + start(); + } + + return status; } - - return status; -} -function addHandler(element, eventName, callback) { - var status = getStatus(element, true); - - if (status[eventName].indexOf(callback) === -1) { - status[eventName].push(callback); - status.handler++; + function addHandler(element, eventName, callback) { + + if (!(element instanceof HTMLElement)) + throw new TypeError("Cannot observer non-HTMLElement based objects"); + if (!(callback instanceof Function)) + throw new TypeError("Callback handler is not a Function"); + if (EVENT_NAMES.indexOf(eventName) === -1) + throw new Error("Unknown mutation event \"" + eventName + "\""); + + var status = getStatus(element, true); + + if (status.handlers[eventName].indexOf(callback) === -1) { + status.handlers[eventName].push(callback); + status.handlers.length++; + } } -} -function removeConnectionHandler (element, eventName, callback) { - var status = getStatus(element, false); - - if (status) { - var index = status[eventName].indexOf(callback); - if (index !== -1) { - if (status.handlers.length === 1) { - ignoreElement(element); - } else { - status[eventName].splice(index, 1); - status.handler--; + function removeHandler (element, eventName, callback) { + + if (!(element instanceof HTMLElement)) + throw new TypeError("Cannot observer non-HTMLElement based objects"); + if (!(callback instanceof Function)) + throw new TypeError("Callback handler is not a Function"); + if (EVENT_NAMES.indexOf(eventName) === -1) + throw new Error("Unknown mutation event \"" + eventName + "\""); + + var status = getStatus(element, false); + + if (status) { + var index = status.handlers[eventName].indexOf(callback); + if (index !== -1) { + if (status.handlers.length === 1) { + ignoreElement(element); + } else { + status.handlers[eventName].splice(index, 1); + status.handlers.length--; + } } } } -} -/* - Get the root element for an element -*/ -function getRoot(element) { - while (element.parentNode) - element = element.parentNode; - - return element; -} - -/* - Executes a collection of handlers, with the element as the context. Can pass - an eventObject for extra information. -*/ -function fireEvent (status, eventName, eventObject) { - var handlers = status.handlers[eventName]; - var element = status.element; - - for (var i = 0, l = handlers.length; i < l; i++) { - handlers[i].call(element, eventObject); + /* + Get the root element for an element + */ + function getRoot(element) { + var root = null; + while (element.parentNode) + root = element = element.parentNode; + + return root; } -} -/* - Removes an element from the observation list, will stop the timer if its the - last element on the list. -*/ -function ignoreElement (element) { - var i = 0; - var l = observedElements.length; - - for (; i < l; i++) - { - if (observedElements[i].element === element) { - observedElements.splice(i, 1); - break; + /* + Executes a collection of handlers, with the element as the context. Can pass + an eventObject for extra information. + */ + function fireEvent (status, eventName, eventObject) { + var handlers = status.handlers[eventName]; + var element = status.element; + + for (var i = 0, l = handlers.length; i < l; i++) { + handlers[i].call(element, eventObject); } } - - if (observedElements.length === 0) - stop(); -} -/* - Checks for modifications to elements on the observation list and calls the - relevent handlers. -*/ -function poll () { - var i = 0; - var l = observedElements.length; - var status = null; - var element = null; - - for (; i < l; i++) { - status = observedElements[i]; - element = status.element; + /* + Removes an element from the observation list, will stop the timer if its the + last element on the list. + */ + function ignoreElement (element) { + var i = 0; + var l = observedElements.length; - if (status.root.contains(element) === false) { - fireEvent(element, status.adoptedHandler); - status.root = getRoot(element); + for (; i < l; i++) + { + if (observedElements[i].element === element) { + observedElements.splice(i, 1); + break; + } } - if (status.parent !== element.parentNode) { - fireEvent(element, status.disconnectHandler); - if (element.parentNode) { - fireEvent(element, status.connectHandler); + if (observedElements.length === 0) + stop(); + } + + /* + Checks for modifications to elements on the observation list and calls the + relevent handlers. + */ + function poll (repeat) { + + repeat = ~~repeat; // should start with 0 + + var i = 0; + var l = observedElements.length; + var status = null; + var element = null; + var didChange = false; + + for (; i < l; i++) { + status = observedElements[i]; + element = status.element; + + // would prefer to use contains here, then only calculate the new root if required + // but when status.root = null we would have to pregenerate the new root for + // comparison here anyway + + var newRoot = getRoot(element); + + if (status.root !== newRoot) { + + fireEvent(status, "adopt", { + type: "adopt", + element: element, + oldRoot: status.root, + newRoot: newRoot + }); + status.root = newRoot; + didChange = true; + } + + if (status.parent !== element.parentNode) { + fireEvent(status, "disconnect", { + type: "disconnect", + element: element, + from: status.parent + }); + if (element.parentNode) { + fireEvent(status, "connect", { + type: "connect", + element: element, + to: element.parentNode + }); + } + status.parent = element.parentNode; + didChange = true; } - status.parent = element.parentNode; + } + + // if we triggered an event, we should repeat to see if it changed anything + // itself. Should also throw if we exceed our maximum level of recursion + if (didChange) { + if (repeat >= MAX_REPEAT) + throw new Error("Observed element mutation exceeded maximum cyclic changes"); + + poll(repeat + 1); } } -} -/* - Starts the timer if it isn't running. -*/ -function start () { - if (timerRef) - return; - - timerRef = setFrameInterval(poll); -} + /* + Starts the timer if it isn't running. + */ + function start () { + if (timerRef) + return; + + timerRef = setFrameInterval(poll); + } -/* - Stops the timer if it is running. -*/ -function stop () { - if (!timerRef) - return; - - timerRef(); - timerRef = null; -} \ No newline at end of file + /* + Stops the timer if it is running. + */ + function stop () { + if (!timerRef) + return; + + timerRef(); + timerRef = null; + } + Doom.addMutationEvent = addHandler; + Doom.removeMutationEvent = removeHandler; + // Doom.expressMutation = function () { + // poll(0); + // }; +}()); \ No newline at end of file From 4be1728712ffd384ea764ef0c0fdaa6ab3555c53 Mon Sep 17 00:00:00 2001 From: Iain Shorter Date: Wed, 2 Nov 2016 18:29:13 +0000 Subject: [PATCH 3/9] Modify and Create attribute binding Bound attributes in createElement and modifyElement to attach mutation events --- src/CreateElement.js | 19 +++++++++++++++++++ src/ModifyElement.js | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/CreateElement.js b/src/CreateElement.js index b727ed8..c6116fd 100644 --- a/src/CreateElement.js +++ b/src/CreateElement.js @@ -55,6 +55,25 @@ function (fn) { return Doom.touch(this, 'touchend', fn); }; + + // mutation events + attributes.onConnected = + attributes.onconnected = + function (fn) { + return Doom.addMutationEvent(this, 'connect', fn); + }; + + attributes.onDisconnected = + attributes.ondisconnected = + function (fn) { + return Doom.addMutationEvent(this, 'disconnect', fn); + }; + + attributes.onAdopted = + attributes.onadopted = + function (fn) { + return Doom.addMutationEvent(this, 'adopt', fn); + }; // element append attributes.parentNode = diff --git a/src/ModifyElement.js b/src/ModifyElement.js index 3029a9b..5a8ac0c 100644 --- a/src/ModifyElement.js +++ b/src/ModifyElement.js @@ -41,6 +41,25 @@ function (fn) { return Doom.touch(this, 'touchend', fn); }; + + // mutation events + attributes.onConnected = + attributes.onconnected = + function (fn) { + return Doom.addMutationEvent(this, 'connect', fn); + }; + + attributes.onDisconnected = + attributes.ondisconnected = + function (fn) { + return Doom.addMutationEvent(this, 'disconnect', fn); + }; + + attributes.onAdopted = + attributes.onadopted = + function (fn) { + return Doom.addMutationEvent(this, 'adopt', fn); + }; // element append attributes.parentNode = From a684cdf6e8ff3d5686c31bf883115328867f9989 Mon Sep 17 00:00:00 2001 From: Iain Shorter Date: Wed, 2 Nov 2016 18:29:32 +0000 Subject: [PATCH 4/9] Updated build script added mutation events to the build scripts --- build.bat | 2 +- build.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.bat b/build.bat index c070738..63081a9 100644 --- a/build.bat +++ b/build.bat @@ -1 +1 @@ -java -jar compiler.jar --language_in ECMASCRIPT5 --output_wrapper "/* Doom.js Library Version: 1.2.0 Copyright (c) 2015 Iain Shorter */window.Doom = {}; (function() {'use strict';%%output%%})();" --js_output_file=build/Doom.js src/Classlist.js src/Promise.js src/Contains.js src/AlloyElement.js src/CreateElement.js src/Delegate.js src/Fetch.js src/ModifyElement.js src/RemoveElement.js src/Search.js src/TouchGestures.js +java -jar compiler.jar --language_in ECMASCRIPT5 --output_wrapper "/* Doom.js Library Version: 1.3.0 Copyright (c) 2015 Iain Shorter */if(!window.Doom){window.Doom = {}; (function() {'use strict';%output%})();}" --js_output_file=build/Doom.js src/Classlist.js src/Promise.js src/Contains.js src/AlloyElement.js src/CreateElement.js src/Delegate.js src/Fetch.js src/ModifyElement.js src/RemoveElement.js src/Search.js src/TouchGestures.js src/MutationEvents.js diff --git a/build.sh b/build.sh index b28f3c1..63081a9 100644 --- a/build.sh +++ b/build.sh @@ -1 +1 @@ -java -jar compiler.jar --language_in ECMASCRIPT5 --output_wrapper "/* Doom.js Library Version: 1.2.0 Copyright (c) 2015 Iain Shorter */if(!window.Doom){window.Doom = {}; (function() {'use strict';%output%})();}" --js_output_file=build/Doom.js src/Classlist.js src/Promise.js src/Contains.js src/AlloyElement.js src/CreateElement.js src/Delegate.js src/Fetch.js src/ModifyElement.js src/RemoveElement.js src/Search.js src/TouchGestures.js +java -jar compiler.jar --language_in ECMASCRIPT5 --output_wrapper "/* Doom.js Library Version: 1.3.0 Copyright (c) 2015 Iain Shorter */if(!window.Doom){window.Doom = {}; (function() {'use strict';%output%})();}" --js_output_file=build/Doom.js src/Classlist.js src/Promise.js src/Contains.js src/AlloyElement.js src/CreateElement.js src/Delegate.js src/Fetch.js src/ModifyElement.js src/RemoveElement.js src/Search.js src/TouchGestures.js src/MutationEvents.js From e5b30f608d73d58e3229be4ef07cbe45eb8e07a4 Mon Sep 17 00:00:00 2001 From: Iain Shorter Date: Wed, 2 Nov 2016 18:30:26 +0000 Subject: [PATCH 5/9] Mutation documentation added doc file for mutation events, content is currently event descriptions from custom elements v1 spec ( good starting point for descriptions ) --- doc/mutation.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 doc/mutation.md diff --git a/doc/mutation.md b/doc/mutation.md new file mode 100644 index 0000000..d9f34d2 --- /dev/null +++ b/doc/mutation.md @@ -0,0 +1,5 @@ +constructor An instance of the element is created or upgraded. Useful for initializing state, settings up event listeners, or creating shadow dom. See the spec for restrictions on what you can do in the constructor. +connectedCallback Called every time the element is inserted into the DOM. Useful for running setup code, such as fetching resources or rendering. Generally, you should try to delay work until this time. +disconnectedCallback Called every time the element is removed from the DOM. Useful for running clean up code (removing event listeners, etc.). +attributeChangedCallback(attrName, oldVal, newVal) An attribute was added, removed, updated, or replaced. Also called for initial values when an element is created by the parser, or upgraded. Note: only attributes listed in the observedAttributes property will receive this callback. +adoptedCallback() The custom element has been moved into a new document (e.g. someone called document.adoptNode(el)). \ No newline at end of file From 70936e4c58261eb98c30bc02e0f8d8c254bed542 Mon Sep 17 00:00:00 2001 From: Iain Shorter Date: Thu, 1 Dec 2016 17:07:07 +0000 Subject: [PATCH 6/9] Fixed escape issue in batch file --- build.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.bat b/build.bat index 63081a9..4d68dd4 100644 --- a/build.bat +++ b/build.bat @@ -1 +1 @@ -java -jar compiler.jar --language_in ECMASCRIPT5 --output_wrapper "/* Doom.js Library Version: 1.3.0 Copyright (c) 2015 Iain Shorter */if(!window.Doom){window.Doom = {}; (function() {'use strict';%output%})();}" --js_output_file=build/Doom.js src/Classlist.js src/Promise.js src/Contains.js src/AlloyElement.js src/CreateElement.js src/Delegate.js src/Fetch.js src/ModifyElement.js src/RemoveElement.js src/Search.js src/TouchGestures.js src/MutationEvents.js +java -jar compiler.jar --language_in ECMASCRIPT5 --output_wrapper "/* Doom.js Library Version: 1.3.0 Copyright (c) 2015 Iain Shorter */if(!window.Doom){window.Doom = {}; (function() {'use strict';%%output%%})();}" --js_output_file=build/Doom.js src/Classlist.js src/Promise.js src/Contains.js src/AlloyElement.js src/CreateElement.js src/Delegate.js src/Fetch.js src/ModifyElement.js src/RemoveElement.js src/Search.js src/TouchGestures.js src/MutationEvents.js From c292f2a9ebc410e6e246917789f266f4a93efc92 Mon Sep 17 00:00:00 2001 From: Iain Shorter Date: Thu, 1 Dec 2016 17:07:22 +0000 Subject: [PATCH 7/9] Rebuilt with mutation events API --- build/Doom.js | 54 +++++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/build/Doom.js b/build/Doom.js index c0b3751..9eb48ed 100644 --- a/build/Doom.js +++ b/build/Doom.js @@ -1,32 +1,36 @@ -/* Doom.js Library Version: 1.2.0 Copyright (c) 2015 Iain Shorter */if(!window.Doom){window.Doom = {}; (function() {'use strict';"indexOf"in Array.prototype||Object.defineProperty(Array.prototype,"indexOf",{value:function(b,a){for(var d=+a||0,e=this.length;dg.status&&200<=g.status?f(g.response):l(g)};g.onerror=function(){l(g)};g.onabort=function(){l(g)};0!==r&&setTimeout(g.abort.bind(g),r);"json"===q?g.send(e.data?JSON.stringify(e.data):null):"url"===q&&g.send(e.data?b(e.data):null)})};Doom.fetch.DEFAULTS=d})();(function(){function b(d){var e=d.element,f,l;if(e)if("string"===typeof e)e=Doom.search(e),d.element=e,b(d);else{if(!1===e instanceof HTMLElement&&e[0]){f=0;for(l=~~e.length;fp.PAN_MINDELTA&&(Math.abs(c.dist_x)>Math.abs(c.dist_y)?(c.direction=0p.SWIPE_MINSPEED&&(Math.abs(c.dist_x)>Math.abs(c.dist_y)?(c.direction=0f.status&&200<=f.status?g(f.response):h(f)};f.onerror=function(){h(f)};f.onabort=function(){h(f)};0!==s&&setTimeout(f.abort.bind(f),s);"json"===r?f.send(e.data?JSON.stringify(e.data):null):"url"===r&&f.send(e.data?b(e.data):null)})};Doom.fetch.DEFAULTS=d})();(function(){function b(d){var e=d.element,g,h;if(e)if("string"===typeof e)e=Doom.search(e),d.element=e,b(d);else{if(!1===e instanceof HTMLElement&&e[0]){g=0;for(h=~~e.length;gl.PAN_MINDELTA&&(Math.abs(c.dist_x)>Math.abs(c.dist_y)?(c.direction=0l.SWIPE_MINSPEED&&(Math.abs(c.dist_x)>Math.abs(c.dist_y)?(c.direction=0 Date: Thu, 1 Dec 2016 17:22:41 +0000 Subject: [PATCH 8/9] Rough test page for domevents --- test/domevents.html | 88 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 test/domevents.html diff --git a/test/domevents.html b/test/domevents.html new file mode 100644 index 0000000..eab56ee --- /dev/null +++ b/test/domevents.html @@ -0,0 +1,88 @@ + + + + + + + \ No newline at end of file From 6539fa46bd2fa4cc8ba2219e03092f3e37dc40ff Mon Sep 17 00:00:00 2001 From: Iain Shorter Date: Thu, 8 Dec 2016 14:33:27 +0000 Subject: [PATCH 9/9] rebuild --- build/Doom.js | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/build/Doom.js b/build/Doom.js index cc72157..3400ef6 100644 --- a/build/Doom.js +++ b/build/Doom.js @@ -1,34 +1,36 @@ /* Doom.js Library Version: 1.3.0 Copyright (c) 2015 Iain Shorter */if(!window.Doom){window.Doom = {}; (function() {'use strict';"indexOf"in Array.prototype||Object.defineProperty(Array.prototype,"indexOf",{value:function(b,a){for(var d=+a||0,e=this.length;df.status&&200<=f.status?g(f.response):h(f)};f.onerror=function(){h(f)};f.onabort=function(){h(f)};0!==s&&setTimeout(f.abort.bind(f),s);"json"===r?f.send(e.data?JSON.stringify(e.data):null):"url"===r&&f.send(e.data?b(e.data):null)})};Doom.fetch.DEFAULTS=d})();(function(){function b(d){var e=d.element,g,h;if(e)if("string"===typeof e)e=Doom.search(e),d.element=e,b(d);else{if(!1===e instanceof HTMLElement&&e[0]){g=0;for(h=~~e.length;gg.status&&200<=g.status?f(g.response):h(g)};g.onerror=function(){h(g)};g.onabort=function(){h(g)};0!==s&&setTimeout(g.abort.bind(g),s);"json"===r?g.send(e.data?JSON.stringify(e.data):null):"url"===r&&g.send(e.data?b(e.data):null)})};Doom.fetch.DEFAULTS=d})();(function(){function b(d){var e=d.element,f,h;if(e)if("string"===typeof e)e=Doom.search(e),d.element=e,b(d);else{if(!1===e instanceof HTMLElement&&e[0]){f=0;for(h=~~e.length;fl.PAN_MINDELTA&&(Math.abs(c.dist_x)>Math.abs(c.dist_y)?(c.direction=0l.SWIPE_MINSPEED&&(Math.abs(c.dist_x)>Math.abs(c.dist_y)?(c.direction=0