From b09f8970fdbf245158d1228bde7e52e3cce6fc32 Mon Sep 17 00:00:00 2001 From: carson Date: Sat, 2 May 2020 09:22:09 -0700 Subject: [PATCH] logging infrastructure --- TODO.md | 5 -- src/htmx.js | 161 ++++++++++++++++++++++++++++++++++++++---------- test/headers.js | 1 - test/util.js | 7 +++ test/values.js | 6 -- 5 files changed, 134 insertions(+), 46 deletions(-) diff --git a/TODO.md b/TODO.md index 76d52b66..cc2cdcc7 100644 --- a/TODO.md +++ b/TODO.md @@ -12,7 +12,6 @@ ## TODOS -* 'revealed' event * history support * Implement LRU * Issue GET to restore content if there isn't a copy locally @@ -37,10 +36,6 @@ * sse support -## Maybes - -* hx-error-url - ## Unsupported Intercooler Features * local actions diff --git a/src/htmx.js b/src/htmx.js index af5c295f..eb28d3d5 100644 --- a/src/htmx.js +++ b/src/htmx.js @@ -71,8 +71,16 @@ var HTMx = HTMx || (function () { return range.createContextualFragment(resp); } + function isType(o, type) { + return Object.prototype.toString.call(o) === "[object " + type + "]"; + } + + function isFunction(o) { + return isType(o, "Function"); + } + function isRawObject(o) { - return Object.prototype.toString.call(o) === "[object Object]"; + return isType(o, "Object"); } function getInternalData(elt) { @@ -91,12 +99,20 @@ var HTMx = HTMx || (function () { }); return arr; } + function forEach(arr, func) { for (var i = 0; i < arr.length; i++) { func(arr[i]); } } + function isScrolledIntoView(el) { + var rect = el.getBoundingClientRect(); + var elemTop = rect.top; + var elemBottom = rect.bottom; + return elemTop < window.innerHeight && elemBottom >= 0; + } + //==================================================================== // Node processing //==================================================================== @@ -240,18 +256,6 @@ var HTMx = HTMx || (function () { } } - function triggerEvent(elt, eventName, details) { - details["elt"] = elt; - var event; - if (window.CustomEvent && typeof window.CustomEvent === 'function') { - event = new CustomEvent(eventName, {detail: details}); - } else { - event = getDocument().createEvent('CustomEvent'); - event.initCustomEvent(eventName, true, true, details); - } - return elt.dispatchEvent(event); - } - function handleTrigger(elt, trigger) { if (trigger) { if (trigger.indexOf("{") === 0) { @@ -271,7 +275,6 @@ var HTMx = HTMx || (function () { } } - function getTrigger(elt) { var explicitTrigger = getClosestAttributeValue(elt, 'hx-trigger'); if (explicitTrigger) { @@ -388,6 +391,26 @@ var HTMx = HTMx || (function () { elt.addEventListener(trigger, eventListener); } + function initScrollHandler() { + if (!window['hxScrollHandler']) { + var scrollHandler = function() { + forEach(getDocument().querySelectorAll("[hx-trigger='reveal']"), function (elt) { + maybeReveal(elt); + }); + }; + window['hxScrollHandler'] = scrollHandler; + window.addEventListener("scroll", scrollHandler) + } + } + + function maybeReveal(elt) { + var nodeData = getInternalData(elt); + if (!nodeData.revealed && isScrolledIntoView(elt)) { + nodeData.revealed = true; + issueAjaxRequest(elt, nodeData.verb, nodeData.path); + } + } + function processNode(elt) { var nodeData = getInternalData(elt); if (!nodeData.processed) { @@ -397,8 +420,13 @@ var HTMx = HTMx || (function () { forEach(VERBS, function(verb){ var path = getAttributeValue(elt, 'hx-' + verb); if (path) { + nodeData.path = path; + nodeData.verb = verb; explicitAction = true; - if (trigger === 'load') { + if (trigger === 'revealed') { + initScrollHandler(); + maybeReveal(elt); + } else if (trigger === 'load') { if (!nodeData.loaded) { nodeData.loaded = true; issueAjaxRequest(elt, verb, path); @@ -424,6 +452,63 @@ var HTMx = HTMx || (function () { forEach(elt.children, function(child) { processNode(child) }); } + //==================================================================== + // Event/Log Support + //==================================================================== + + function sendError(elt, eventName, details) { + var errorURL = getClosestAttributeValue(elt, "hx-error-url"); + if (errorURL) { + var xhr = new XMLHttpRequest(); + xhr.open("POST", errorURL); + xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); + xhr.send(JSON.stringify({ "elt": elt.id, "event": eventName, "details" : details })); + } + } + + function makeEvent(eventName, details) { + var evt; + if (window.CustomEvent && typeof window.CustomEvent === 'function') { + evt = new CustomEvent(eventName, {detail: details}); + } else { + evt = getDocument().createEvent('CustomEvent'); + evt.initCustomEvent(eventName, true, true, details); + } + return evt; + } + + function triggerEvent(elt, eventName, details) { + details["elt"] = elt; + var event = makeEvent(eventName, details); + if (HTMx.logger) { + HTMx.logger(elt, eventName, details); + if (eventName.indexOf("Error") > 0) { + sendError(elt, eventName, details); + } + } + var eventResult = elt.dispatchEvent(event); + var allResult = elt.dispatchEvent(makeEvent("all.hx", {elt:elt, originalDetails:details, originalEvent: event})); + return eventResult && allResult; + } + + function addHTMxEventListener(arg1, arg2, arg3) { + var target, event, listener; + if (isFunction(arg1)) { + target = getDocument().body; + event = "all.hx"; + listener = arg1; + } else if (isFunction(arg2)) { + target = getDocument().body; + event = arg1; + listener = arg2; + } else { + target = arg1; + event = arg2; + listener = arg3; + } + return target.addEventListener(event, listener); + } + //==================================================================== // History Support //==================================================================== @@ -585,7 +670,7 @@ var HTMx = HTMx || (function () { // include the closest form processInputValue(processed, values, closest(elt, 'form')); - return Object.keys(values).length === 0 ? null : values; + return values; } function appendParam(returnStr, name, realValue) { @@ -650,7 +735,8 @@ var HTMx = HTMx || (function () { // request type if (verb === 'get') { - xhr.open('GET', path + (inputValues ? "?" + urlEncode(inputValues) : ""), true); + var noValues = Object.keys(inputValues).length === 0; + xhr.open('GET', path + (noValues ? "" : "?" + urlEncode(inputValues)), true); } else { xhr.open('POST', path, true); setHeader(xhr,'Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8', true); @@ -684,7 +770,7 @@ var HTMx = HTMx || (function () { xhr.onload = function () { try { - if(!triggerEvent(elt, 'beforeOnLoad.hx', {xhr:xhr, target:target})) return; + if (!triggerEvent(elt, 'beforeOnLoad.hx', {xhr: xhr, target: target})) return; snapshotForCurrentHistoryEntry(elt, path); var trigger = this.getResponseHeader("X-HX-Trigger"); handleTrigger(elt, trigger); @@ -694,37 +780,43 @@ var HTMx = HTMx || (function () { if (this.status !== 204) { // Success! var resp = this.response; - if(!triggerEvent(elt, 'beforeSwap.hx', {xhr:xhr, target:target})) return; + if (!triggerEvent(elt, 'beforeSwap.hx', {xhr: xhr, target: target})) return; target.classList.add("hx-swapping"); - var doSwap = function(){ - swapResponse(target, elt, resp, function(){ - target.classList.remove("hx-swapping"); - updateCurrentHistoryContent(); - triggerEvent(elt, 'afterSwap.hx', {xhr:xhr, target:target}); - }); - } + var doSwap = function () { + try { + swapResponse(target, elt, resp, function () { + target.classList.remove("hx-swapping"); + updateCurrentHistoryContent(); + triggerEvent(elt, 'afterSwap.hx', {xhr: xhr, target: target}); + }); + } catch (e) { + triggerEvent(elt, 'swapError.hx', {xhr: xhr, response: xhr.response, status: xhr.status, target: target}); + throw e; + } + }; var swapDelayStr = getAttributeValue(elt, "hx-swap-delay"); if (swapDelayStr) { - setTimeout(doSwap(), parseInterval(swapDelayStr)) + setTimeout(doSwap, parseInterval(swapDelayStr)) } else { doSwap(); } } } else { - triggerEvent(elt, 'errorResponse.hx', {xhr:xhr, response: xhr.response, status: xhr.status, target:target}); + triggerEvent(elt, 'responseError.hx', {xhr: xhr, response: xhr.response, status: xhr.status, target: target}); } + } catch (e) { + triggerEvent(elt, 'onLoadError.hx', {xhr: xhr, response: xhr.response, status: xhr.status, target: target}); + throw e; } finally { removeRequestIndicatorClasses(elt); - triggerEvent(elt, 'afterOnLoad.hx', {xhr:xhr, response: xhr.response, status: xhr.status, target:target}); endRequestLock(); + triggerEvent(elt, 'afterOnLoad.hx', {xhr: xhr, response: xhr.response, status: xhr.status, target: target}); } - }; + } xhr.onerror = function () { - removeRequestIndicatorClasses(elt); - triggerEvent(elt, 'onError.hx', {xhr:xhr}); + removeRequestIndicatorClasses(elt);triggerEvent(elt, 'loadError.hx', {xhr:xhr}); endRequestLock(); - }; - + } if(!triggerEvent(elt, 'beforeRequest.hx', {xhr:xhr, values: inputValues, target:target})) return endRequestLock(); addRequestIndicatorClasses(elt); xhr.send(verb === 'get' ? null : urlEncode(inputValues)); @@ -757,6 +849,7 @@ var HTMx = HTMx || (function () { // Public API return { processElement: processNode, + on: addHTMxEventListener, version: "0.0.1", _:internalEval } diff --git a/test/headers.js b/test/headers.js index e2a32d17..c81c91eb 100644 --- a/test/headers.js +++ b/test/headers.js @@ -40,7 +40,6 @@ describe("HTMx AJAX Headers Tests", function() { it("should include the X-HX-Target-Id header", function(){ this.server.respondWith("GET", "/test", function(xhr){ - console.log(xhr.requestHeaders); xhr.requestHeaders['X-HX-Target-Id'].should.equal('d1'); xhr.respond(200, {}, ""); }); diff --git a/test/util.js b/test/util.js index 595120e4..ce61b798 100644 --- a/test/util.js +++ b/test/util.js @@ -1,4 +1,11 @@ /* Test Utilities */ + +HTMx.logger = function(elt, event, data) { + if(console) { + console.log(event, elt, data); + } +} + function byId(id) { return document.getElementById(id); } diff --git a/test/values.js b/test/values.js index d105ce46..2d879b38 100644 --- a/test/values.js +++ b/test/values.js @@ -8,12 +8,6 @@ describe("HTMx Value Handling", function() { clearWorkArea(); }); - it('No values evaluates to null', function () { - var div = make('
'); - var vals = HTMx._('getInputValues')(div); - should.equal(vals, null); - }) - it('Input includes value', function () { var input = make(''); var vals = HTMx._('getInputValues')(input);