From 1dbabdb4c4f857c662780338d0ef3f194a1bfc60 Mon Sep 17 00:00:00 2001 From: carson Date: Thu, 17 Sep 2020 13:09:07 -0600 Subject: [PATCH] release 0.1 prep --- dist/htmx.js | 184 +- dist/htmx.min.js | 2 +- dist/htmx.min.js.gz | Bin 7550 -> 7986 bytes www/js/htmx.js | 184 +- www/test/0.1.0/src/htmx.js | 184 +- www/test/0.1.0/test/attributes/hx-sse.js | 10 +- www/test/0.1.0/test/attributes/hx-target.js | 12 + www/test/0.1.0/test/attributes/hx-ws.js | 8 +- www/test/0.1.0/test/core/ajax.js | 104 +- www/test/0.1.0/test/core/events.js | 62 +- www/test/0.1.0/test/core/internals.js | 16 +- www/test/0.1.0/test/core/regressions.js | 16 + www/test/0.1.0/test/ext/hyperscript.js | 13 + www/test/0.1.0/test/index.html | 17 +- www/test/0.1.0/test/lib/_hyperscript.js | 2088 +++++++++-------- .../test/manual/scroll-test-eventHandler.html | 107 + .../test/manual/scroll-test-startEnd.html | 29 + .../0.1.0/test/manual/sse-multichannel.html | 41 + www/test/0.1.0/test/manual/sse-settle.html | 49 + www/test/0.1.0/test/manual/sse.html | 43 + 20 files changed, 2029 insertions(+), 1140 deletions(-) create mode 100644 www/test/0.1.0/test/manual/scroll-test-eventHandler.html create mode 100644 www/test/0.1.0/test/manual/scroll-test-startEnd.html create mode 100644 www/test/0.1.0/test/manual/sse-multichannel.html create mode 100644 www/test/0.1.0/test/manual/sse-settle.html create mode 100644 www/test/0.1.0/test/manual/sse.html diff --git a/dist/htmx.js b/dist/htmx.js index e9f301aa..831c82ad 100644 --- a/dist/htmx.js +++ b/dist/htmx.js @@ -41,6 +41,7 @@ return (function () { requestClass:'htmx-request', settlingClass:'htmx-settling', swappingClass:'htmx-swapping', + attributesToSwizzle:["class", "style", "width", "height"] }, parseInterval:parseInterval, _:internalEval, @@ -57,6 +58,8 @@ return (function () { return "[hx-" + verb + "], [data-hx-" + verb + "]" }).join(", "); + var windowIsScrolling = false // used by initScrollHandler + //==================================================================== // Utilities //==================================================================== @@ -216,7 +219,7 @@ return (function () { } function splitOnWhitespace(trigger) { - return trigger.split(/\s+/); + return trigger.trim().split(/\s+/); } function mergeObjects(obj1, obj2) { @@ -362,6 +365,8 @@ return (function () { return explicitTarget; } else if (targetStr.indexOf("closest ") === 0) { return closest(elt, targetStr.substr(8)); + } else if (targetStr.indexOf("find ") === 0) { + return find(elt, targetStr.substr(5)); } else { return getDocument().querySelector(targetStr); } @@ -375,15 +380,24 @@ return (function () { } } - var EXCLUDED_ATTRIBUTES = ['id', 'value']; + function shouldSettleAttribute(name) { + var attributesToSwizzle = htmx.config.attributesToSwizzle; + for (var i = 0; i < attributesToSwizzle.length; i++) { + if (name === attributesToSwizzle[i]) { + return true; + } + } + return false; + } + function cloneAttributes(mergeTo, mergeFrom) { forEach(mergeTo.attributes, function (attr) { - if (!mergeFrom.hasAttribute(attr.name) && EXCLUDED_ATTRIBUTES.indexOf(attr.name) === -1) { + if (!mergeFrom.hasAttribute(attr.name) && shouldSettleAttribute(attr.name)) { mergeTo.removeAttribute(attr.name) } }); forEach(mergeFrom.attributes, function (attr) { - if (EXCLUDED_ATTRIBUTES.indexOf(attr.name) === -1) { + if (shouldSettleAttribute(attr.name)) { mergeTo.setAttribute(attr.name, attr.value); } }); @@ -450,12 +464,21 @@ return (function () { function makeAjaxLoadTask(child) { return function () { - processNode(child, true); + processNode(child); processScripts(child); - triggerEvent(child, 'htmx:load', {}); + processFocus(child) + triggerEvent(child, 'htmx:load'); }; } + function processFocus(child) { + var autofocus = "[autofocus]"; + var autoFocusedElt = matches(child, autofocus) ? child : child.querySelector(autofocus) + if (autoFocusedElt != null) { + autoFocusedElt.focus(); + } + } + function insertNodesBefore(parentNode, insertBefore, fragment, settleInfo) { handleAttributes(parentNode, fragment, settleInfo); while(fragment.childNodes.length > 0){ @@ -491,6 +514,7 @@ return (function () { } else { var newElt = eltBeforeNewContent.nextSibling; } + getInternalData(target).replacedWith = newElt; // tuck away so we can fire events on it later while(newElt && newElt !== target) { settleInfo.elts.push(newElt); newElt = newElt.nextSibling; @@ -521,8 +545,10 @@ return (function () { insertNodesBefore(target, firstChild, fragment, settleInfo); if (firstChild) { while (firstChild.nextSibling) { + closeConnections(firstChild.nextSibling) target.removeChild(firstChild.nextSibling); } + closeConnections(firstChild) target.removeChild(firstChild); } } @@ -760,12 +786,18 @@ return (function () { function initScrollHandler() { if (!window['htmxScrollHandler']) { var scrollHandler = function() { - forEach(getDocument().querySelectorAll("[hx-trigger='revealed'],[data-hx-trigger='revealed']"), function (elt) { - maybeReveal(elt); - }); + windowIsScrolling = true }; window['htmxScrollHandler'] = scrollHandler; window.addEventListener("scroll", scrollHandler) + setInterval(function() { + if (windowIsScrolling) { + windowIsScrolling = false; + forEach(getDocument().querySelectorAll("[hx-trigger='revealed'],[data-hx-trigger='revealed']"), function (elt) { + maybeReveal(elt); + }) + } + }, 200); } } @@ -778,9 +810,9 @@ return (function () { } function processWebSocketInfo(elt, nodeData, info) { - var values = info.split(","); + var values = splitOnWhitespace(info); for (var i = 0; i < values.length; i++) { - var value = splitOnWhitespace(values[i]); + var value = values[i].split(/:(.+)/); if (value[0] === "connect") { processWebSocketSource(elt, value[1]); } @@ -791,6 +823,9 @@ return (function () { } function processWebSocketSource(elt, wssSource) { + if (wssSource.indexOf("ws:") !== 0 && wssSource.indexOf("wss:") !== 0) { + wssSource = "wss:" + wssSource; + } var socket = htmx.createWebSocket(wssSource); socket.onerror = function (e) { triggerErrorEvent(elt, "htmx:wsError", {error:e, socket:socket}); @@ -847,20 +882,21 @@ return (function () { } } - function maybeCloseSSESource(elt) { - if (!bodyContains(elt)) { - getInternalData(elt).sseEventSource.close(); - return true; - } - } + //==================================================================== + // Server Sent Events + //==================================================================== function processSSEInfo(elt, nodeData, info) { - var values = info.split(","); + var values = splitOnWhitespace(info); for (var i = 0; i < values.length; i++) { - var value = splitOnWhitespace(values[i]); + var value = values[i].split(/:(.+)/); if (value[0] === "connect") { processSSESource(elt, value[1]); } + + if ((value[0] === "swap")) { + processSSESwap(elt, value[1]) + } } } @@ -873,10 +909,41 @@ return (function () { getInternalData(elt).sseEventSource = source; } + function processSSESwap(elt, sseEventName) { + var sseSourceElt = getClosestMatch(elt, hasEventSource); + if (sseSourceElt) { + var sseEventSource = getInternalData(sseSourceElt).sseEventSource; + var sseListener = function (event) { + if (maybeCloseSSESource(sseSourceElt)) { + sseEventSource.removeEventListener(sseEventName, sseListener); + return; + } + + /////////////////////////// + // TODO: merge this code with AJAX and WebSockets code in the future. + + var response = event.data; + withExtensions(elt, function(extension){ + response = extension.transformResponse(response, null, elt); + }); + + var swapSpec = getSwapSpecification(elt) + var target = getTarget(elt) + var settleInfo = makeSettleInfo(elt); + + selectAndSwap(swapSpec.swapStyle, elt, target, response, settleInfo) + triggerEvent(elt, "htmx:sseMessage", event) + }; + + getInternalData(elt).sseListener = sseListener; + sseEventSource.addEventListener(sseEventName, sseListener); + } else { + triggerErrorEvent(elt, "htmx:noSSESourceError"); + } + } + function processSSETrigger(elt, verb, path, sseEventName) { - var sseSourceElt = getClosestMatch(elt, function (parent) { - return getInternalData(parent).sseEventSource != null; - }); + var sseSourceElt = getClosestMatch(elt, hasEventSource); if (sseSourceElt) { var sseEventSource = getInternalData(sseSourceElt).sseEventSource; var sseListener = function () { @@ -895,6 +962,19 @@ return (function () { } } + function maybeCloseSSESource(elt) { + if (!bodyContains(elt)) { + getInternalData(elt).sseEventSource.close(); + return true; + } + } + + function hasEventSource(node) { + return getInternalData(node).sseEventSource != null; + } + + //==================================================================== + function loadImmediately(elt, verb, path, nodeData, delay) { var load = function(){ if (!nodeData.loaded) { @@ -956,13 +1036,10 @@ return (function () { }); } - function isHyperScriptAvailable() { - return typeof _hyperscript !== "undefined"; - } - function findElementsToProcess(elt) { if (elt.querySelectorAll) { - var results = elt.querySelectorAll(VERB_SELECTOR + ", a, form, [hx-sse], [data-hx-sse], [hx-ws], [data-hx-ws]"); + var results = elt.querySelectorAll(VERB_SELECTOR + ", a, form, [hx-sse], [data-hx-sse], [hx-ws]," + + " [data-hx-ws]"); return results; } else { return []; @@ -974,10 +1051,6 @@ return (function () { if (!nodeData.initialized) { nodeData.initialized = true; - if (isHyperScriptAvailable()) { - _hyperscript.init(elt); - } - if (elt.value) { nodeData.lastValue = elt.value; } @@ -1011,6 +1084,10 @@ return (function () { // Event/Log Support //==================================================================== + function kebabEventName(str) { + return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase(); + } + function makeEvent(eventName, detail) { var evt; if (window.CustomEvent && typeof window.CustomEvent === 'function') { @@ -1062,6 +1139,10 @@ return (function () { triggerEvent(elt, "htmx:error", {errorInfo:detail}) } var eventResult = elt.dispatchEvent(event); + if (eventResult) { + var kebabedEvent = makeEvent(kebabEventName(eventName), event.detail); + eventResult = eventResult && elt.dispatchEvent(kebabedEvent) + } withExtensions(elt, function (extension) { eventResult = eventResult && (extension.onEvent(eventName, event) !== false) }); @@ -1231,6 +1312,9 @@ return (function () { if (shouldInclude(elt)) { var name = getRawAttribute(elt,"name"); var value = elt.value; + if (!!getRawAttribute(elt, 'multiple')) { + value = toArray(elt.querySelectorAll("option:checked")).map(function (e) { return e.value }); + } if (name != null && value != null) { var current = values[name]; if(current) { @@ -1442,6 +1526,30 @@ return (function () { addExpressionVars(parentElt(elt), rawParameters); } + function safelySetHeaderValue(xhr, header, headerValue) { + if (headerValue !== null) { + try { + xhr.setRequestHeader(header, headerValue); + } catch (e) { + // On an exception, try to set the header URI encoded instead + xhr.setRequestHeader(header, encodeURIComponent(headerValue)); + xhr.setRequestHeader(header + "-URI-AutoEncoded", "true"); + } + } + } + + function getResponseURL(xhr) { + // NB: IE11 does not support this stuff + if (xhr.responseURL && typeof(URL) !== "undefined") { + try { + var url = new URL(xhr.responseURL); + return url.pathname + url.search; + } catch (e) { + triggerErrorEvent(getDocument().body, "htmx:badResponseUrl", {url: xhr.responseURL}); + } + } + } + function issueAjaxRequest(elt, verb, path, eventTarget) { var target = getTarget(elt); if (target == null) { @@ -1535,7 +1643,8 @@ return (function () { // request headers for (var header in headers) { if (headers.hasOwnProperty(header)) { - if (headers[header] !== null) xhr.setRequestHeader(header, headers[header]); + var headerValue = headers[header]; + safelySetHeaderValue(xhr, header, headerValue); } } @@ -1553,7 +1662,7 @@ return (function () { if (this.status === 286) { cancelPolling(elt); } - // don't process 'No Content' response + // don't process 'No Content' if (this.status !== 204) { if (!triggerEvent(target, 'htmx:beforeSwap', eventDetail)) return; @@ -1613,7 +1722,7 @@ return (function () { }); // push URL and save new page if (shouldSaveHistory) { - var pathToPush = pushedUrl || getPushUrl(elt) || finalPathForGet || path; + var pathToPush = pushedUrl || getPushUrl(elt) || getResponseURL(xhr) || finalPathForGet || path; pushUrlIntoHistory(pathToPush); triggerEvent(getDocument().body, 'htmx:pushedIntoHistory', {path:pathToPush}); } @@ -1645,8 +1754,9 @@ return (function () { throw e; } finally { removeRequestIndicatorClasses(elt); - triggerEvent(elt, 'htmx:afterRequest', eventDetail); - triggerEvent(elt, 'htmx:afterOnLoad', eventDetail); + var finalElt = getInternalData(elt).replacedWith || elt; + triggerEvent(finalElt, 'htmx:afterRequest', eventDetail); + triggerEvent(finalElt, 'htmx:afterOnLoad', eventDetail); endRequestLock(); } } @@ -1747,7 +1857,7 @@ return (function () { mergeMetaConfig(); insertIndicatorStyles(); var body = getDocument().body; - processNode(body, true); + processNode(body); triggerEvent(body, 'htmx:load', {}); window.onpopstate = function (event) { if (event.state && event.state.htmx) { diff --git a/dist/htmx.min.js b/dist/htmx.min.js index 2cb7dfb7..1a560ce5 100644 --- a/dist/htmx.min.js +++ b/dist/htmx.min.js @@ -1 +1 @@ -(function(e,t){if(typeof define==="function"&&define.amd){define([],t)}else{e.htmx=t()}})(typeof self!=="undefined"?self:this,function(){return function(){"use strict";var w={onLoad:x,process:He,on:F,off:P,trigger:Xe,find:S,findAll:A,closest:M,remove:H,addClass:R,removeClass:k,toggleClass:D,takeClass:I,defineExtension:dt,removeExtension:gt,logAll:b,logger:null,config:{historyEnabled:true,historyCacheSize:10,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:100,includeIndicatorStyles:true,indicatorClass:"htmx-indicator",requestClass:"htmx-request",settlingClass:"htmx-settling",swappingClass:"htmx-swapping"},parseInterval:a,_:e,createEventSource:function(e){return new EventSource(e,{withCredentials:true})},createWebSocket:function(e){return new WebSocket(e,[])}};var t=["get","post","put","delete","patch"];var r=t.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");function a(e){if(e==null||e==="null"||e==="false"||e===""){return null}else if(e.lastIndexOf("ms")===e.length-2){return parseFloat(e.substr(0,e.length-2))}else if(e.lastIndexOf("s")===e.length-1){return parseFloat(e.substr(0,e.length-1))*1e3}else{return parseFloat(e)}}function u(e,t){return e.getAttribute&&e.getAttribute(t)}function l(e,t){return e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function E(e,t){return u(e,t)||u(e,"data-"+t)}function o(e){return e.parentElement}function C(){return document}function s(e,t){if(t(e)){return e}else if(o(e)){return s(o(e),t)}else{return null}}function O(e,t){var r=null;s(e,function(e){return r=E(e,t)});return r}function f(e,t){var r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.webkitMatchesSelector||e.oMatchesSelector;return r&&r.call(e,t)}function n(e){var t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i;var r=t.exec(e);if(r){return r[1].toLowerCase()}else{return""}}function i(e,t){var r=new DOMParser;var n=r.parseFromString(e,"text/html");var i=n.body;while(t>0){t--;i=i.firstChild}if(i==null){i=C().createDocumentFragment()}return i}function c(e){var t=n(e);switch(t){case"thead":case"tbody":case"tfoot":case"colgroup":case"caption":return i(""+e+"
",1);case"col":return i(""+e+"
",2);case"tr":return i(""+e+"
",2);case"td":case"th":return i(""+e+"
",3);default:return i(e,0)}}function h(e,t){return Object.prototype.toString.call(e)==="[object "+t+"]"}function v(e){return h(e,"Function")}function d(e){return h(e,"Object")}function T(e){var t="htmx-internal-data";var r=e[t];if(!r){r=e[t]={}}return r}function g(e){var t=[];if(e){for(var r=0;r=0}function q(e){return C().body.contains(e)}function p(e){return e.split(/\s+/)}function N(e,t){for(var r in t){if(t.hasOwnProperty(r)){e[r]=t[r]}}return e}function y(e){try{return JSON.parse(e)}catch(e){Me(e);return null}}function e(e){return eval(e)}function x(t){var e=w.on("htmx:load",function(e){t(e.detail.elt)});return e}function b(){w.logger=function(e,t,r){if(console){console.log(t,e,r)}}}function S(e,t){if(t){return e.querySelector(t)}else{return C().body.querySelector(e)}}function A(e,t){if(t){return e.querySelectorAll(t)}else{return C().body.querySelectorAll(e)}}function H(e,t){if(t){setTimeout(function(){H(e)},t)}else{e.parentElement.removeChild(e)}}function R(e,t,r){if(r){setTimeout(function(){R(e,t)},r)}else{e.classList.add(t)}}function k(e,t,r){if(r){setTimeout(function(){k(e,t)},r)}else{e.classList.remove(t)}}function D(e,t){e.classList.toggle(t)}function I(e,t){L(e.parentElement.children,function(e){k(e,t)});R(e,t)}function M(e,t){do{if(e==null||f(e,t))return e}while(e=e&&o(e))}function X(e,t,r){if(v(t)){return{target:C().body,event:e,listener:t}}else{return{target:e,event:t,listener:r}}}function F(t,r,n){pt(function(){var e=X(t,r,n);e.target.addEventListener(e.event,e.listener)});var e=v(r);return e?r:n}function P(t,r,n){pt(function(){var e=X(t,r,n);e.target.removeEventListener(e.event,e.listener)});return v(r)?r:n}function j(e){var t=s(e,function(e){return E(e,"hx-target")!==null});if(t){var r=E(t,"hx-target");if(r==="this"){return t}else if(r.indexOf("closest ")===0){return M(e,r.substr(8))}else{return C().querySelector(r)}}else{var n=T(e);if(n.boosted){return C().body}else{return e}}}var _=["id","value"];function z(t,r){L(t.attributes,function(e){if(!r.hasAttribute(e.name)&&_.indexOf(e.name)===-1){t.removeAttribute(e.name)}});L(r.attributes,function(e){if(_.indexOf(e.name)===-1){t.setAttribute(e.name,e.value)}})}function V(e,t){var r=mt(t);for(var n=0;n0){var t=n.querySelector(e.tagName+"[id='"+e.id+"']");if(t&&t!==n){var r=e.cloneNode();z(e,t);i.tasks.push(function(){z(e,r)})}}})}function J(e){return function(){He(e,true);Le(e);Xe(e,"htmx:load",{})}}function G(e,t,r,n){W(e,r,n);while(r.childNodes.length>0){var i=r.firstChild;e.insertBefore(i,t);if(i.nodeType!==Node.TEXT_NODE&&i.nodeType!==Node.COMMENT_NODE){n.tasks.push(J(i))}}}function Y(e){var t=T(e);if(t.webSocket){t.webSocket.close()}if(t.sseEventSource){t.sseEventSource.close()}if(e.children){L(e.children,function(e){Y(e)})}}function K(e,t,r){if(e.tagName==="BODY"){return te(e,t)}else{var n=e.previousSibling;G(o(e),e,t,r);if(n==null){var i=o(e).firstChild}else{var i=n.nextSibling}while(i&&i!==e){r.elts.push(i);i=i.nextSibling}Y(e);o(e).removeChild(e)}}function Q(e,t,r){return G(e,e.firstChild,t,r)}function Z(e,t,r){return G(o(e),e,t,r)}function $(e,t,r){return G(e,null,t,r)}function ee(e,t,r){return G(o(e),e.nextSibling,t,r)}function te(e,t,r){var n=e.firstChild;G(e,n,t,r);if(n){while(n.nextSibling){e.removeChild(n.nextSibling)}e.removeChild(n)}}function re(e,t){var r=O(e,"hx-select");if(r){var n=C().createDocumentFragment();L(t.querySelectorAll(r),function(e){n.appendChild(e)});t=n}return t}function ne(e,t,r,n,i){switch(e){case"none":return;case"outerHTML":K(r,n,i);return;case"afterbegin":Q(r,n,i);return;case"beforebegin":Z(r,n,i);return;case"beforeend":$(r,n,i);return;case"afterend":ee(r,n,i);return;default:var o=mt(t);for(var a=0;aw.config.historyCacheSize){i.shift()}localStorage.setItem("htmx-history-cache",JSON.stringify(i))}function _e(e){var t=y(localStorage.getItem("htmx-history-cache"))||[];for(var r=0;r=200&&this.status<400){Xe(C().body,"htmx:historyCacheMissLoad",i);var e=c(this.response);e=e.querySelector("[hx-history-elt],[data-hx-history-elt]")||e;var t=Pe();var r=lt(t);te(t,e,r);Be(r.tasks);Fe=n}else{ke(C().body,"htmx:historyCacheMissLoadError",i)}};e.send()}function We(e){ze(Fe);e=e||location.pathname+location.search;Xe(C().body,"htmx:historyRestore",{path:e});var t=_e(e);if(t){var r=c(t.content);var n=Pe();var i=lt(n);te(n,r,i);Be(i.tasks);document.title=t.title;window.scrollTo(0,t.scroll);Fe=e}else{Ue(e)}}function Je(e){var t=O(e,"hx-push-url");return t&&t!=="false"||e.tagName==="A"&&T(e).boosted}function Ge(e){var t=O(e,"hx-push-url");return t==="true"||t==="false"?null:t}function Ye(e){Qe(e,"add")}function Ke(e){Qe(e,"remove")}function Qe(e,t){var r=O(e,"hx-indicator");if(r){var n=C().querySelectorAll(r)}else{n=[e]}L(n,function(e){e.classList[t].call(e.classList,w.config.requestClass)})}function Ze(e,t){for(var r=0;r0){r["swapStyle"]=n[0];for(var i=1;i=200&&this.status<400){if(this.status===286){ue(s)}if(this.status!==204){if(!Xe(c,"htmx:beforeSwap",S))return;var u=this.response;Ie(s,function(e){u=e.transformResponse(u,h,s)});if(a){ze()}var l=at(s);c.classList.add(w.config.swappingClass);var e=function(){try{var e=document.activeElement;var t={elt:e,start:e.selectionStart,end:e.selectionEnd};var r=lt(c);ie(l.swapStyle,c,s,u,r);if(!q(t.elt)&&t.elt.id){var n=document.getElementById(t.elt.id);if(t.start&&n.setSelectionRange){n.setSelectionRange(t.start,t.end)}n.focus()}c.classList.remove(w.config.swappingClass);L(r.elts,function(e){if(e.classList){e.classList.add(w.config.settlingClass)}Xe(e,"htmx:afterSwap",S)});if(p){location.hash=p}var i=function(){L(r.tasks,function(e){e.call()});L(r.elts,function(e){if(e.classList){e.classList.remove(w.config.settlingClass)}Xe(e,"htmx:afterSettle",S)});if(a){var e=o||Ge(s)||y||f;Ve(e);Xe(C().body,"htmx:pushedIntoHistory",{path:e})}st(c,r.elts,l)};if(l.settleDelay>0){setTimeout(i,l.settleDelay)}else{i()}}catch(e){ke(s,"htmx:swapError",S);throw e}};if(l.swapDelay>0){setTimeout(e,l.swapDelay)}else{e()}}}else{ke(s,"htmx:responseError",N({error:"Response Status Error Code "+this.status+" from "+f},S))}}catch(e){ke(s,"htmx:onLoadError",N({error:e},S));throw e}finally{Ke(s);Xe(s,"htmx:afterRequest",S);Xe(s,"htmx:afterOnLoad",S);n()}};h.onerror=function(){Ke(s);ke(s,"htmx:afterRequest",S);ke(s,"htmx:sendError",S);n()};if(!Xe(s,"htmx:beforeRequest",S))return n();Ye(s);h.send(e==="get"?null:ut(h,s,v))}var ht={};function vt(){return{onEvent:function(e,t){return true},transformResponse:function(e,t,r){return e},isInlineSwap:function(e){return false},handleSwap:function(e,t,r,n){return false},encodeParameters:function(e,t,r){return null}}}function dt(e,t){ht[e]=N(vt(),t)}function gt(e){delete ht[e]}function mt(e,r){if(e==null){return r}if(r==null){r=[]}var t=E(e,"hx-ext");if(t){L(t.split(","),function(e){e=e.replace(/ /g,"");var t=ht[e];if(t&&r.indexOf(t)<0){r.push(t)}})}return mt(o(e),r)}function pt(e){if(C().readyState!=="loading"){e()}else{C().addEventListener("DOMContentLoaded",e)}}function yt(){if(w.config.includeIndicatorStyles!==false){C().head.insertAdjacentHTML("beforeend","")}}function xt(){var e=C().querySelector('meta[name="htmx-config"]');if(e){return y(e.content)}else{return null}}function bt(){var e=xt();if(e){w.config=N(w.config,e)}}pt(function(){bt();yt();var e=C().body;He(e,true);Xe(e,"htmx:load",{});window.onpopstate=function(e){if(e.state&&e.state.htmx){We()}}});return w}()}); \ No newline at end of file +(function(e,t){if(typeof define==="function"&&define.amd){define([],t)}else{e.htmx=t()}})(typeof self!=="undefined"?self:this,function(){return function(){"use strict";var E={onLoad:b,process:Ie,on:U,off:z,trigger:Pe,find:w,findAll:N,closest:X,remove:H,addClass:k,removeClass:I,toggleClass:D,takeClass:M,defineExtension:bt,removeExtension:St,logAll:S,logger:null,config:{historyEnabled:true,historyCacheSize:10,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:100,includeIndicatorStyles:true,indicatorClass:"htmx-indicator",requestClass:"htmx-request",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",attributesToSwizzle:["class","style","width","height"]},parseInterval:a,_:e,createEventSource:function(e){return new EventSource(e,{withCredentials:true})},createWebSocket:function(e){return new WebSocket(e,[])}};var t=["get","post","put","delete","patch"];var r=t.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");var n=false;function a(e){if(e==null||e==="null"||e==="false"||e===""){return null}else if(e.lastIndexOf("ms")===e.length-2){return parseFloat(e.substr(0,e.length-2))}else if(e.lastIndexOf("s")===e.length-1){return parseFloat(e.substr(0,e.length-1))*1e3}else{return parseFloat(e)}}function u(e,t){return e.getAttribute&&e.getAttribute(t)}function l(e,t){return e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function C(e,t){return u(e,t)||u(e,"data-"+t)}function o(e){return e.parentElement}function L(){return document}function f(e,t){if(t(e)){return e}else if(o(e)){return f(o(e),t)}else{return null}}function O(e,t){var r=null;f(e,function(e){return r=C(e,t)});return r}function s(e,t){var r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.webkitMatchesSelector||e.oMatchesSelector;return r&&r.call(e,t)}function i(e){var t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i;var r=t.exec(e);if(r){return r[1].toLowerCase()}else{return""}}function c(e,t){var r=new DOMParser;var n=r.parseFromString(e,"text/html");var i=n.body;while(t>0){t--;i=i.firstChild}if(i==null){i=L().createDocumentFragment()}return i}function v(e){var t=i(e);switch(t){case"thead":case"tbody":case"tfoot":case"colgroup":case"caption":return c(""+e+"
",1);case"col":return c(""+e+"
",2);case"tr":return c(""+e+"
",2);case"td":case"th":return c(""+e+"
",3);default:return c(e,0)}}function d(e,t){return Object.prototype.toString.call(e)==="[object "+t+"]"}function h(e){return d(e,"Function")}function g(e){return d(e,"Object")}function T(e){var t="htmx-internal-data";var r=e[t];if(!r){r=e[t]={}}return r}function m(e){var t=[];if(e){for(var r=0;r=0}function R(e){return L().body.contains(e)}function y(e){return e.trim().split(/\s+/)}function A(e,t){for(var r in t){if(t.hasOwnProperty(r)){e[r]=t[r]}}return e}function x(e){try{return JSON.parse(e)}catch(e){ze(e);return null}}function e(e){return eval(e)}function b(t){var e=E.on("htmx:load",function(e){t(e.detail.elt)});return e}function S(){E.logger=function(e,t,r){if(console){console.log(t,e,r)}}}function w(e,t){if(t){return e.querySelector(t)}else{return L().body.querySelector(e)}}function N(e,t){if(t){return e.querySelectorAll(t)}else{return L().body.querySelectorAll(e)}}function H(e,t){if(t){setTimeout(function(){H(e)},t)}else{e.parentElement.removeChild(e)}}function k(e,t,r){if(r){setTimeout(function(){k(e,t)},r)}else{e.classList.add(t)}}function I(e,t,r){if(r){setTimeout(function(){I(e,t)},r)}else{e.classList.remove(t)}}function D(e,t){e.classList.toggle(t)}function M(e,t){q(e.parentElement.children,function(e){I(e,t)});k(e,t)}function X(e,t){do{if(e==null||s(e,t))return e}while(e=e&&o(e))}function F(e,t,r){if(h(t)){return{target:L().body,event:e,listener:t}}else{return{target:e,event:t,listener:r}}}function U(t,r,n){Et(function(){var e=F(t,r,n);e.target.addEventListener(e.event,e.listener)});var e=h(r);return e?r:n}function z(t,r,n){Et(function(){var e=F(t,r,n);e.target.removeEventListener(e.event,e.listener)});return h(r)?r:n}function P(e){var t=f(e,function(e){return C(e,"hx-target")!==null});if(t){var r=C(t,"hx-target");if(r==="this"){return t}else if(r.indexOf("closest ")===0){return X(e,r.substr(8))}else if(r.indexOf("find ")===0){return w(e,r.substr(5))}else{return L().querySelector(r)}}else{var n=T(e);if(n.boosted){return L().body}else{return e}}}function j(e){var t=E.config.attributesToSwizzle;for(var r=0;r0){var t=n.querySelector(e.tagName+"[id='"+e.id+"']");if(t&&t!==n){var r=e.cloneNode();V(e,t);i.tasks.push(function(){V(e,r)})}}})}function G(e){return function(){Ie(e);Ne(e);Y(e);Pe(e,"htmx:load")}}function Y(e){var t="[autofocus]";var r=s(e,t)?e:e.querySelector(t);if(r!=null){r.focus()}}function $(e,t,r,n){J(e,r,n);while(r.childNodes.length>0){var i=r.firstChild;e.insertBefore(i,t);if(i.nodeType!==Node.TEXT_NODE&&i.nodeType!==Node.COMMENT_NODE){n.tasks.push(G(i))}}}function K(e){var t=T(e);if(t.webSocket){t.webSocket.close()}if(t.sseEventSource){t.sseEventSource.close()}if(e.children){q(e.children,function(e){K(e)})}}function Z(e,t,r){if(e.tagName==="BODY"){return ne(e,t)}else{var n=e.previousSibling;$(o(e),e,t,r);if(n==null){var i=o(e).firstChild}else{var i=n.nextSibling}T(e).replacedWith=i;while(i&&i!==e){r.elts.push(i);i=i.nextSibling}K(e);o(e).removeChild(e)}}function Q(e,t,r){return $(e,e.firstChild,t,r)}function ee(e,t,r){return $(o(e),e,t,r)}function te(e,t,r){return $(e,null,t,r)}function re(e,t,r){return $(o(e),e.nextSibling,t,r)}function ne(e,t,r){var n=e.firstChild;$(e,n,t,r);if(n){while(n.nextSibling){K(n.nextSibling);e.removeChild(n.nextSibling)}K(n);e.removeChild(n)}}function ie(e,t){var r=O(e,"hx-select");if(r){var n=L().createDocumentFragment();q(t.querySelectorAll(r),function(e){n.appendChild(e)});t=n}return t}function oe(e,t,r,n,i){switch(e){case"none":return;case"outerHTML":Z(r,n,i);return;case"afterbegin":Q(r,n,i);return;case"beforebegin":ee(r,n,i);return;case"beforeend":te(r,n,i);return;case"afterend":re(r,n,i);return;default:var o=wt(t);for(var a=0;aE.config.historyCacheSize){i.shift()}localStorage.setItem("htmx-history-cache",JSON.stringify(i))}function Be(e){var t=x(localStorage.getItem("htmx-history-cache"))||[];for(var r=0;r=200&&this.status<400){Pe(L().body,"htmx:historyCacheMissLoad",i);var e=v(this.response);e=e.querySelector("[hx-history-elt],[data-hx-history-elt]")||e;var t=Ve();var r=vt(t);ne(t,e,r);Ge(r.tasks);je=n}else{Xe(L().body,"htmx:historyCacheMissLoadError",i)}};e.send()}function $e(e){_e(je);e=e||location.pathname+location.search;Pe(L().body,"htmx:historyRestore",{path:e});var t=Be(e);if(t){var r=v(t.content);var n=Ve();var i=vt(n);ne(n,r,i);Ge(i.tasks);document.title=t.title;window.scrollTo(0,t.scroll);je=e}else{Ye(e)}}function Ke(e){var t=O(e,"hx-push-url");return t&&t!=="false"||e.tagName==="A"&&T(e).boosted}function Ze(e){var t=O(e,"hx-push-url");return t==="true"||t==="false"?null:t}function Qe(e){tt(e,"add")}function et(e){tt(e,"remove")}function tt(e,t){var r=O(e,"hx-indicator");if(r){var n=L().querySelectorAll(r)}else{n=[e]}q(n,function(e){e.classList[t].call(e.classList,E.config.requestClass)})}function rt(e,t){for(var r=0;r0){r["swapStyle"]=n[0];for(var i=1;i=200&&this.status<400){if(this.status===286){se(s)}if(this.status!==204){if(!Pe(c,"htmx:beforeSwap",w))return;var u=this.response;Ue(s,function(e){u=e.transformResponse(u,v,s)});if(a){_e()}var l=ft(s);c.classList.add(E.config.swappingClass);var e=function(){try{var e=document.activeElement;var t={elt:e,start:e.selectionStart,end:e.selectionEnd};var r=vt(c);ae(l.swapStyle,c,s,u,r);if(!R(t.elt)&&t.elt.id){var n=document.getElementById(t.elt.id);if(t.start&&n.setSelectionRange){n.setSelectionRange(t.start,t.end)}n.focus()}c.classList.remove(E.config.swappingClass);q(r.elts,function(e){if(e.classList){e.classList.add(E.config.settlingClass)}Pe(e,"htmx:afterSwap",w)});if(p){location.hash=p}var i=function(){q(r.tasks,function(e){e.call()});q(r.elts,function(e){if(e.classList){e.classList.remove(E.config.settlingClass)}Pe(e,"htmx:afterSettle",w)});if(a){var e=o||Ze(s)||mt(v)||y||f;Je(e);Pe(L().body,"htmx:pushedIntoHistory",{path:e})}dt(c,r.elts,l)};if(l.settleDelay>0){setTimeout(i,l.settleDelay)}else{i()}}catch(e){Xe(s,"htmx:swapError",w);throw e}};if(l.swapDelay>0){setTimeout(e,l.swapDelay)}else{e()}}}else{Xe(s,"htmx:responseError",A({error:"Response Status Error Code "+this.status+" from "+f},w))}}catch(e){Xe(s,"htmx:onLoadError",A({error:e},w));throw e}finally{et(s);var t=T(s).replacedWith||s;Pe(t,"htmx:afterRequest",w);Pe(t,"htmx:afterOnLoad",w);n()}};v.onerror=function(){et(s);Xe(s,"htmx:afterRequest",w);Xe(s,"htmx:sendError",w);n()};if(!Pe(s,"htmx:beforeRequest",w))return n();Qe(s);v.send(e==="get"?null:ct(v,s,d))}var yt={};function xt(){return{onEvent:function(e,t){return true},transformResponse:function(e,t,r){return e},isInlineSwap:function(e){return false},handleSwap:function(e,t,r,n){return false},encodeParameters:function(e,t,r){return null}}}function bt(e,t){yt[e]=A(xt(),t)}function St(e){delete yt[e]}function wt(e,r){if(e==null){return r}if(r==null){r=[]}var t=C(e,"hx-ext");if(t){q(t.split(","),function(e){e=e.replace(/ /g,"");var t=yt[e];if(t&&r.indexOf(t)<0){r.push(t)}})}return wt(o(e),r)}function Et(e){if(L().readyState!=="loading"){e()}else{L().addEventListener("DOMContentLoaded",e)}}function Ct(){if(E.config.includeIndicatorStyles!==false){L().head.insertAdjacentHTML("beforeend","")}}function Lt(){var e=L().querySelector('meta[name="htmx-config"]');if(e){return x(e.content)}else{return null}}function Ot(){var e=Lt();if(e){E.config=A(E.config,e)}}Et(function(){Ot();Ct();var e=L().body;Ie(e);Pe(e,"htmx:load",{});window.onpopstate=function(e){if(e.state&&e.state.htmx){$e()}}});return E}()}); \ No newline at end of file diff --git a/dist/htmx.min.js.gz b/dist/htmx.min.js.gz index 81f6f6ae2a975d3d57e642f1eb2e0df6cb36267a..949bb4de539b0ac60451e574b0fd75268d6f9f5c 100644 GIT binary patch literal 7986 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 literal 7550 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%0A8^O^VD_Wf!7^rSbc<8Y~_*0 zN^1bDUiHzxw#t@xE5T5=fpGADZV%?D*&obTzT?ar)gCmL*TE2Y_Xqq7Rai%QYGpK> zsTIal#%rwiYvt9w4}HO=k?wwrG6Gy#H&oCIhY)!M#5ZeBS@M>P50y1#Bh|bzx6%MuR)za4- zYbj(5oJHLOTn^iRlU4c0RkHJ$$K%u-bYm(LhH@BAL^_hcqb5~;3CF&r#wUyDlV7s? zQ?~&pzfz7~OzP0SJc&AcMYp>kDIqKCLgjmwbwzAcGlXkvA_uH!)T@-A{JA-2{Ovo;g{b#NlX> zVw7+defabB@>AWgTb&X@(7#GRa)t`(*gz^mF!rb3tE zG*M(3U;t&W0xKd7>Qrn`VYrN9vPOgnTwpt44Hwc!xTU+gbDFN}XsK27Ls6kO(KTu) zye#1I35T#pW+w4Q6WvgSeIx<=pOdEaw?RihKf|Z?OhoC_!V51r2Zhi89)IESmS`B8 z!w*Q}EiGHYH6D87qHR;#gw+T^k)D)0m^0u*QE2WX?mDUiw_yf0t2lYLx*^)S?wOrBZ!ybt$yPAVt%!O#ja`vHq393IT^J&Y}a_=+bU;gy|83n?&o` zE6e87_Zi#JW_n@69IKEW?HYS#Q<`*bgLz?o=g~cNe}}`Ptn)nj1sV=+Z}af$tbcAT z_#mD>{|+`5HqyHHV6L8?Jqz>;E}R!cgk`@T?{3FVh#KDwuB?v|lxmeKw2z2rL2Gu) zq5)tch=4^egxkP4Cl!-6R`ynh3yl%YLGWU&J0xh&R`Ajr_&s5!HMB6p0+nR3UHB@C z+sld>O$39m`mQlMd6whhrRW;{ z*|b_jbRu_Nrcoi@G>0@Prr|7Kfk^GiT7RuiX{A~qal07 zVzl%mF~T(QsaW>n7U1VX$@teCo3TEzU_G8ZEN;w7Id?^bt`Hs=r>q5*aAgH5&4Lhl zw>u2TmR|I^qt{2Gtb01=fp4Nyt7R-UIF zLo^tP*Y0dM6~UuMNM7^s29Yi5~uw`{0Uw@tI%=lTVa>VJ1_Cc??*@f zPQ0>cuAzF1Yw^em*Wc7+3-j-+%1Sm&l3C4e0#t1N84+`jFb0WtJdT|fEe zcK=7S=chbX4i{=)-NOQT4BKdaLEata9&pFr<>Nrkk@8hFs2jx@d0$4otFE_w!7pNsHL}$2|Sd2^>x}e;w!Qn5~ UQO5eXYY83v2is-1z6VwS04o=c&Hw-a diff --git a/www/js/htmx.js b/www/js/htmx.js index e9f301aa..831c82ad 100644 --- a/www/js/htmx.js +++ b/www/js/htmx.js @@ -41,6 +41,7 @@ return (function () { requestClass:'htmx-request', settlingClass:'htmx-settling', swappingClass:'htmx-swapping', + attributesToSwizzle:["class", "style", "width", "height"] }, parseInterval:parseInterval, _:internalEval, @@ -57,6 +58,8 @@ return (function () { return "[hx-" + verb + "], [data-hx-" + verb + "]" }).join(", "); + var windowIsScrolling = false // used by initScrollHandler + //==================================================================== // Utilities //==================================================================== @@ -216,7 +219,7 @@ return (function () { } function splitOnWhitespace(trigger) { - return trigger.split(/\s+/); + return trigger.trim().split(/\s+/); } function mergeObjects(obj1, obj2) { @@ -362,6 +365,8 @@ return (function () { return explicitTarget; } else if (targetStr.indexOf("closest ") === 0) { return closest(elt, targetStr.substr(8)); + } else if (targetStr.indexOf("find ") === 0) { + return find(elt, targetStr.substr(5)); } else { return getDocument().querySelector(targetStr); } @@ -375,15 +380,24 @@ return (function () { } } - var EXCLUDED_ATTRIBUTES = ['id', 'value']; + function shouldSettleAttribute(name) { + var attributesToSwizzle = htmx.config.attributesToSwizzle; + for (var i = 0; i < attributesToSwizzle.length; i++) { + if (name === attributesToSwizzle[i]) { + return true; + } + } + return false; + } + function cloneAttributes(mergeTo, mergeFrom) { forEach(mergeTo.attributes, function (attr) { - if (!mergeFrom.hasAttribute(attr.name) && EXCLUDED_ATTRIBUTES.indexOf(attr.name) === -1) { + if (!mergeFrom.hasAttribute(attr.name) && shouldSettleAttribute(attr.name)) { mergeTo.removeAttribute(attr.name) } }); forEach(mergeFrom.attributes, function (attr) { - if (EXCLUDED_ATTRIBUTES.indexOf(attr.name) === -1) { + if (shouldSettleAttribute(attr.name)) { mergeTo.setAttribute(attr.name, attr.value); } }); @@ -450,12 +464,21 @@ return (function () { function makeAjaxLoadTask(child) { return function () { - processNode(child, true); + processNode(child); processScripts(child); - triggerEvent(child, 'htmx:load', {}); + processFocus(child) + triggerEvent(child, 'htmx:load'); }; } + function processFocus(child) { + var autofocus = "[autofocus]"; + var autoFocusedElt = matches(child, autofocus) ? child : child.querySelector(autofocus) + if (autoFocusedElt != null) { + autoFocusedElt.focus(); + } + } + function insertNodesBefore(parentNode, insertBefore, fragment, settleInfo) { handleAttributes(parentNode, fragment, settleInfo); while(fragment.childNodes.length > 0){ @@ -491,6 +514,7 @@ return (function () { } else { var newElt = eltBeforeNewContent.nextSibling; } + getInternalData(target).replacedWith = newElt; // tuck away so we can fire events on it later while(newElt && newElt !== target) { settleInfo.elts.push(newElt); newElt = newElt.nextSibling; @@ -521,8 +545,10 @@ return (function () { insertNodesBefore(target, firstChild, fragment, settleInfo); if (firstChild) { while (firstChild.nextSibling) { + closeConnections(firstChild.nextSibling) target.removeChild(firstChild.nextSibling); } + closeConnections(firstChild) target.removeChild(firstChild); } } @@ -760,12 +786,18 @@ return (function () { function initScrollHandler() { if (!window['htmxScrollHandler']) { var scrollHandler = function() { - forEach(getDocument().querySelectorAll("[hx-trigger='revealed'],[data-hx-trigger='revealed']"), function (elt) { - maybeReveal(elt); - }); + windowIsScrolling = true }; window['htmxScrollHandler'] = scrollHandler; window.addEventListener("scroll", scrollHandler) + setInterval(function() { + if (windowIsScrolling) { + windowIsScrolling = false; + forEach(getDocument().querySelectorAll("[hx-trigger='revealed'],[data-hx-trigger='revealed']"), function (elt) { + maybeReveal(elt); + }) + } + }, 200); } } @@ -778,9 +810,9 @@ return (function () { } function processWebSocketInfo(elt, nodeData, info) { - var values = info.split(","); + var values = splitOnWhitespace(info); for (var i = 0; i < values.length; i++) { - var value = splitOnWhitespace(values[i]); + var value = values[i].split(/:(.+)/); if (value[0] === "connect") { processWebSocketSource(elt, value[1]); } @@ -791,6 +823,9 @@ return (function () { } function processWebSocketSource(elt, wssSource) { + if (wssSource.indexOf("ws:") !== 0 && wssSource.indexOf("wss:") !== 0) { + wssSource = "wss:" + wssSource; + } var socket = htmx.createWebSocket(wssSource); socket.onerror = function (e) { triggerErrorEvent(elt, "htmx:wsError", {error:e, socket:socket}); @@ -847,20 +882,21 @@ return (function () { } } - function maybeCloseSSESource(elt) { - if (!bodyContains(elt)) { - getInternalData(elt).sseEventSource.close(); - return true; - } - } + //==================================================================== + // Server Sent Events + //==================================================================== function processSSEInfo(elt, nodeData, info) { - var values = info.split(","); + var values = splitOnWhitespace(info); for (var i = 0; i < values.length; i++) { - var value = splitOnWhitespace(values[i]); + var value = values[i].split(/:(.+)/); if (value[0] === "connect") { processSSESource(elt, value[1]); } + + if ((value[0] === "swap")) { + processSSESwap(elt, value[1]) + } } } @@ -873,10 +909,41 @@ return (function () { getInternalData(elt).sseEventSource = source; } + function processSSESwap(elt, sseEventName) { + var sseSourceElt = getClosestMatch(elt, hasEventSource); + if (sseSourceElt) { + var sseEventSource = getInternalData(sseSourceElt).sseEventSource; + var sseListener = function (event) { + if (maybeCloseSSESource(sseSourceElt)) { + sseEventSource.removeEventListener(sseEventName, sseListener); + return; + } + + /////////////////////////// + // TODO: merge this code with AJAX and WebSockets code in the future. + + var response = event.data; + withExtensions(elt, function(extension){ + response = extension.transformResponse(response, null, elt); + }); + + var swapSpec = getSwapSpecification(elt) + var target = getTarget(elt) + var settleInfo = makeSettleInfo(elt); + + selectAndSwap(swapSpec.swapStyle, elt, target, response, settleInfo) + triggerEvent(elt, "htmx:sseMessage", event) + }; + + getInternalData(elt).sseListener = sseListener; + sseEventSource.addEventListener(sseEventName, sseListener); + } else { + triggerErrorEvent(elt, "htmx:noSSESourceError"); + } + } + function processSSETrigger(elt, verb, path, sseEventName) { - var sseSourceElt = getClosestMatch(elt, function (parent) { - return getInternalData(parent).sseEventSource != null; - }); + var sseSourceElt = getClosestMatch(elt, hasEventSource); if (sseSourceElt) { var sseEventSource = getInternalData(sseSourceElt).sseEventSource; var sseListener = function () { @@ -895,6 +962,19 @@ return (function () { } } + function maybeCloseSSESource(elt) { + if (!bodyContains(elt)) { + getInternalData(elt).sseEventSource.close(); + return true; + } + } + + function hasEventSource(node) { + return getInternalData(node).sseEventSource != null; + } + + //==================================================================== + function loadImmediately(elt, verb, path, nodeData, delay) { var load = function(){ if (!nodeData.loaded) { @@ -956,13 +1036,10 @@ return (function () { }); } - function isHyperScriptAvailable() { - return typeof _hyperscript !== "undefined"; - } - function findElementsToProcess(elt) { if (elt.querySelectorAll) { - var results = elt.querySelectorAll(VERB_SELECTOR + ", a, form, [hx-sse], [data-hx-sse], [hx-ws], [data-hx-ws]"); + var results = elt.querySelectorAll(VERB_SELECTOR + ", a, form, [hx-sse], [data-hx-sse], [hx-ws]," + + " [data-hx-ws]"); return results; } else { return []; @@ -974,10 +1051,6 @@ return (function () { if (!nodeData.initialized) { nodeData.initialized = true; - if (isHyperScriptAvailable()) { - _hyperscript.init(elt); - } - if (elt.value) { nodeData.lastValue = elt.value; } @@ -1011,6 +1084,10 @@ return (function () { // Event/Log Support //==================================================================== + function kebabEventName(str) { + return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase(); + } + function makeEvent(eventName, detail) { var evt; if (window.CustomEvent && typeof window.CustomEvent === 'function') { @@ -1062,6 +1139,10 @@ return (function () { triggerEvent(elt, "htmx:error", {errorInfo:detail}) } var eventResult = elt.dispatchEvent(event); + if (eventResult) { + var kebabedEvent = makeEvent(kebabEventName(eventName), event.detail); + eventResult = eventResult && elt.dispatchEvent(kebabedEvent) + } withExtensions(elt, function (extension) { eventResult = eventResult && (extension.onEvent(eventName, event) !== false) }); @@ -1231,6 +1312,9 @@ return (function () { if (shouldInclude(elt)) { var name = getRawAttribute(elt,"name"); var value = elt.value; + if (!!getRawAttribute(elt, 'multiple')) { + value = toArray(elt.querySelectorAll("option:checked")).map(function (e) { return e.value }); + } if (name != null && value != null) { var current = values[name]; if(current) { @@ -1442,6 +1526,30 @@ return (function () { addExpressionVars(parentElt(elt), rawParameters); } + function safelySetHeaderValue(xhr, header, headerValue) { + if (headerValue !== null) { + try { + xhr.setRequestHeader(header, headerValue); + } catch (e) { + // On an exception, try to set the header URI encoded instead + xhr.setRequestHeader(header, encodeURIComponent(headerValue)); + xhr.setRequestHeader(header + "-URI-AutoEncoded", "true"); + } + } + } + + function getResponseURL(xhr) { + // NB: IE11 does not support this stuff + if (xhr.responseURL && typeof(URL) !== "undefined") { + try { + var url = new URL(xhr.responseURL); + return url.pathname + url.search; + } catch (e) { + triggerErrorEvent(getDocument().body, "htmx:badResponseUrl", {url: xhr.responseURL}); + } + } + } + function issueAjaxRequest(elt, verb, path, eventTarget) { var target = getTarget(elt); if (target == null) { @@ -1535,7 +1643,8 @@ return (function () { // request headers for (var header in headers) { if (headers.hasOwnProperty(header)) { - if (headers[header] !== null) xhr.setRequestHeader(header, headers[header]); + var headerValue = headers[header]; + safelySetHeaderValue(xhr, header, headerValue); } } @@ -1553,7 +1662,7 @@ return (function () { if (this.status === 286) { cancelPolling(elt); } - // don't process 'No Content' response + // don't process 'No Content' if (this.status !== 204) { if (!triggerEvent(target, 'htmx:beforeSwap', eventDetail)) return; @@ -1613,7 +1722,7 @@ return (function () { }); // push URL and save new page if (shouldSaveHistory) { - var pathToPush = pushedUrl || getPushUrl(elt) || finalPathForGet || path; + var pathToPush = pushedUrl || getPushUrl(elt) || getResponseURL(xhr) || finalPathForGet || path; pushUrlIntoHistory(pathToPush); triggerEvent(getDocument().body, 'htmx:pushedIntoHistory', {path:pathToPush}); } @@ -1645,8 +1754,9 @@ return (function () { throw e; } finally { removeRequestIndicatorClasses(elt); - triggerEvent(elt, 'htmx:afterRequest', eventDetail); - triggerEvent(elt, 'htmx:afterOnLoad', eventDetail); + var finalElt = getInternalData(elt).replacedWith || elt; + triggerEvent(finalElt, 'htmx:afterRequest', eventDetail); + triggerEvent(finalElt, 'htmx:afterOnLoad', eventDetail); endRequestLock(); } } @@ -1747,7 +1857,7 @@ return (function () { mergeMetaConfig(); insertIndicatorStyles(); var body = getDocument().body; - processNode(body, true); + processNode(body); triggerEvent(body, 'htmx:load', {}); window.onpopstate = function (event) { if (event.state && event.state.htmx) { diff --git a/www/test/0.1.0/src/htmx.js b/www/test/0.1.0/src/htmx.js index e9f301aa..831c82ad 100644 --- a/www/test/0.1.0/src/htmx.js +++ b/www/test/0.1.0/src/htmx.js @@ -41,6 +41,7 @@ return (function () { requestClass:'htmx-request', settlingClass:'htmx-settling', swappingClass:'htmx-swapping', + attributesToSwizzle:["class", "style", "width", "height"] }, parseInterval:parseInterval, _:internalEval, @@ -57,6 +58,8 @@ return (function () { return "[hx-" + verb + "], [data-hx-" + verb + "]" }).join(", "); + var windowIsScrolling = false // used by initScrollHandler + //==================================================================== // Utilities //==================================================================== @@ -216,7 +219,7 @@ return (function () { } function splitOnWhitespace(trigger) { - return trigger.split(/\s+/); + return trigger.trim().split(/\s+/); } function mergeObjects(obj1, obj2) { @@ -362,6 +365,8 @@ return (function () { return explicitTarget; } else if (targetStr.indexOf("closest ") === 0) { return closest(elt, targetStr.substr(8)); + } else if (targetStr.indexOf("find ") === 0) { + return find(elt, targetStr.substr(5)); } else { return getDocument().querySelector(targetStr); } @@ -375,15 +380,24 @@ return (function () { } } - var EXCLUDED_ATTRIBUTES = ['id', 'value']; + function shouldSettleAttribute(name) { + var attributesToSwizzle = htmx.config.attributesToSwizzle; + for (var i = 0; i < attributesToSwizzle.length; i++) { + if (name === attributesToSwizzle[i]) { + return true; + } + } + return false; + } + function cloneAttributes(mergeTo, mergeFrom) { forEach(mergeTo.attributes, function (attr) { - if (!mergeFrom.hasAttribute(attr.name) && EXCLUDED_ATTRIBUTES.indexOf(attr.name) === -1) { + if (!mergeFrom.hasAttribute(attr.name) && shouldSettleAttribute(attr.name)) { mergeTo.removeAttribute(attr.name) } }); forEach(mergeFrom.attributes, function (attr) { - if (EXCLUDED_ATTRIBUTES.indexOf(attr.name) === -1) { + if (shouldSettleAttribute(attr.name)) { mergeTo.setAttribute(attr.name, attr.value); } }); @@ -450,12 +464,21 @@ return (function () { function makeAjaxLoadTask(child) { return function () { - processNode(child, true); + processNode(child); processScripts(child); - triggerEvent(child, 'htmx:load', {}); + processFocus(child) + triggerEvent(child, 'htmx:load'); }; } + function processFocus(child) { + var autofocus = "[autofocus]"; + var autoFocusedElt = matches(child, autofocus) ? child : child.querySelector(autofocus) + if (autoFocusedElt != null) { + autoFocusedElt.focus(); + } + } + function insertNodesBefore(parentNode, insertBefore, fragment, settleInfo) { handleAttributes(parentNode, fragment, settleInfo); while(fragment.childNodes.length > 0){ @@ -491,6 +514,7 @@ return (function () { } else { var newElt = eltBeforeNewContent.nextSibling; } + getInternalData(target).replacedWith = newElt; // tuck away so we can fire events on it later while(newElt && newElt !== target) { settleInfo.elts.push(newElt); newElt = newElt.nextSibling; @@ -521,8 +545,10 @@ return (function () { insertNodesBefore(target, firstChild, fragment, settleInfo); if (firstChild) { while (firstChild.nextSibling) { + closeConnections(firstChild.nextSibling) target.removeChild(firstChild.nextSibling); } + closeConnections(firstChild) target.removeChild(firstChild); } } @@ -760,12 +786,18 @@ return (function () { function initScrollHandler() { if (!window['htmxScrollHandler']) { var scrollHandler = function() { - forEach(getDocument().querySelectorAll("[hx-trigger='revealed'],[data-hx-trigger='revealed']"), function (elt) { - maybeReveal(elt); - }); + windowIsScrolling = true }; window['htmxScrollHandler'] = scrollHandler; window.addEventListener("scroll", scrollHandler) + setInterval(function() { + if (windowIsScrolling) { + windowIsScrolling = false; + forEach(getDocument().querySelectorAll("[hx-trigger='revealed'],[data-hx-trigger='revealed']"), function (elt) { + maybeReveal(elt); + }) + } + }, 200); } } @@ -778,9 +810,9 @@ return (function () { } function processWebSocketInfo(elt, nodeData, info) { - var values = info.split(","); + var values = splitOnWhitespace(info); for (var i = 0; i < values.length; i++) { - var value = splitOnWhitespace(values[i]); + var value = values[i].split(/:(.+)/); if (value[0] === "connect") { processWebSocketSource(elt, value[1]); } @@ -791,6 +823,9 @@ return (function () { } function processWebSocketSource(elt, wssSource) { + if (wssSource.indexOf("ws:") !== 0 && wssSource.indexOf("wss:") !== 0) { + wssSource = "wss:" + wssSource; + } var socket = htmx.createWebSocket(wssSource); socket.onerror = function (e) { triggerErrorEvent(elt, "htmx:wsError", {error:e, socket:socket}); @@ -847,20 +882,21 @@ return (function () { } } - function maybeCloseSSESource(elt) { - if (!bodyContains(elt)) { - getInternalData(elt).sseEventSource.close(); - return true; - } - } + //==================================================================== + // Server Sent Events + //==================================================================== function processSSEInfo(elt, nodeData, info) { - var values = info.split(","); + var values = splitOnWhitespace(info); for (var i = 0; i < values.length; i++) { - var value = splitOnWhitespace(values[i]); + var value = values[i].split(/:(.+)/); if (value[0] === "connect") { processSSESource(elt, value[1]); } + + if ((value[0] === "swap")) { + processSSESwap(elt, value[1]) + } } } @@ -873,10 +909,41 @@ return (function () { getInternalData(elt).sseEventSource = source; } + function processSSESwap(elt, sseEventName) { + var sseSourceElt = getClosestMatch(elt, hasEventSource); + if (sseSourceElt) { + var sseEventSource = getInternalData(sseSourceElt).sseEventSource; + var sseListener = function (event) { + if (maybeCloseSSESource(sseSourceElt)) { + sseEventSource.removeEventListener(sseEventName, sseListener); + return; + } + + /////////////////////////// + // TODO: merge this code with AJAX and WebSockets code in the future. + + var response = event.data; + withExtensions(elt, function(extension){ + response = extension.transformResponse(response, null, elt); + }); + + var swapSpec = getSwapSpecification(elt) + var target = getTarget(elt) + var settleInfo = makeSettleInfo(elt); + + selectAndSwap(swapSpec.swapStyle, elt, target, response, settleInfo) + triggerEvent(elt, "htmx:sseMessage", event) + }; + + getInternalData(elt).sseListener = sseListener; + sseEventSource.addEventListener(sseEventName, sseListener); + } else { + triggerErrorEvent(elt, "htmx:noSSESourceError"); + } + } + function processSSETrigger(elt, verb, path, sseEventName) { - var sseSourceElt = getClosestMatch(elt, function (parent) { - return getInternalData(parent).sseEventSource != null; - }); + var sseSourceElt = getClosestMatch(elt, hasEventSource); if (sseSourceElt) { var sseEventSource = getInternalData(sseSourceElt).sseEventSource; var sseListener = function () { @@ -895,6 +962,19 @@ return (function () { } } + function maybeCloseSSESource(elt) { + if (!bodyContains(elt)) { + getInternalData(elt).sseEventSource.close(); + return true; + } + } + + function hasEventSource(node) { + return getInternalData(node).sseEventSource != null; + } + + //==================================================================== + function loadImmediately(elt, verb, path, nodeData, delay) { var load = function(){ if (!nodeData.loaded) { @@ -956,13 +1036,10 @@ return (function () { }); } - function isHyperScriptAvailable() { - return typeof _hyperscript !== "undefined"; - } - function findElementsToProcess(elt) { if (elt.querySelectorAll) { - var results = elt.querySelectorAll(VERB_SELECTOR + ", a, form, [hx-sse], [data-hx-sse], [hx-ws], [data-hx-ws]"); + var results = elt.querySelectorAll(VERB_SELECTOR + ", a, form, [hx-sse], [data-hx-sse], [hx-ws]," + + " [data-hx-ws]"); return results; } else { return []; @@ -974,10 +1051,6 @@ return (function () { if (!nodeData.initialized) { nodeData.initialized = true; - if (isHyperScriptAvailable()) { - _hyperscript.init(elt); - } - if (elt.value) { nodeData.lastValue = elt.value; } @@ -1011,6 +1084,10 @@ return (function () { // Event/Log Support //==================================================================== + function kebabEventName(str) { + return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase(); + } + function makeEvent(eventName, detail) { var evt; if (window.CustomEvent && typeof window.CustomEvent === 'function') { @@ -1062,6 +1139,10 @@ return (function () { triggerEvent(elt, "htmx:error", {errorInfo:detail}) } var eventResult = elt.dispatchEvent(event); + if (eventResult) { + var kebabedEvent = makeEvent(kebabEventName(eventName), event.detail); + eventResult = eventResult && elt.dispatchEvent(kebabedEvent) + } withExtensions(elt, function (extension) { eventResult = eventResult && (extension.onEvent(eventName, event) !== false) }); @@ -1231,6 +1312,9 @@ return (function () { if (shouldInclude(elt)) { var name = getRawAttribute(elt,"name"); var value = elt.value; + if (!!getRawAttribute(elt, 'multiple')) { + value = toArray(elt.querySelectorAll("option:checked")).map(function (e) { return e.value }); + } if (name != null && value != null) { var current = values[name]; if(current) { @@ -1442,6 +1526,30 @@ return (function () { addExpressionVars(parentElt(elt), rawParameters); } + function safelySetHeaderValue(xhr, header, headerValue) { + if (headerValue !== null) { + try { + xhr.setRequestHeader(header, headerValue); + } catch (e) { + // On an exception, try to set the header URI encoded instead + xhr.setRequestHeader(header, encodeURIComponent(headerValue)); + xhr.setRequestHeader(header + "-URI-AutoEncoded", "true"); + } + } + } + + function getResponseURL(xhr) { + // NB: IE11 does not support this stuff + if (xhr.responseURL && typeof(URL) !== "undefined") { + try { + var url = new URL(xhr.responseURL); + return url.pathname + url.search; + } catch (e) { + triggerErrorEvent(getDocument().body, "htmx:badResponseUrl", {url: xhr.responseURL}); + } + } + } + function issueAjaxRequest(elt, verb, path, eventTarget) { var target = getTarget(elt); if (target == null) { @@ -1535,7 +1643,8 @@ return (function () { // request headers for (var header in headers) { if (headers.hasOwnProperty(header)) { - if (headers[header] !== null) xhr.setRequestHeader(header, headers[header]); + var headerValue = headers[header]; + safelySetHeaderValue(xhr, header, headerValue); } } @@ -1553,7 +1662,7 @@ return (function () { if (this.status === 286) { cancelPolling(elt); } - // don't process 'No Content' response + // don't process 'No Content' if (this.status !== 204) { if (!triggerEvent(target, 'htmx:beforeSwap', eventDetail)) return; @@ -1613,7 +1722,7 @@ return (function () { }); // push URL and save new page if (shouldSaveHistory) { - var pathToPush = pushedUrl || getPushUrl(elt) || finalPathForGet || path; + var pathToPush = pushedUrl || getPushUrl(elt) || getResponseURL(xhr) || finalPathForGet || path; pushUrlIntoHistory(pathToPush); triggerEvent(getDocument().body, 'htmx:pushedIntoHistory', {path:pathToPush}); } @@ -1645,8 +1754,9 @@ return (function () { throw e; } finally { removeRequestIndicatorClasses(elt); - triggerEvent(elt, 'htmx:afterRequest', eventDetail); - triggerEvent(elt, 'htmx:afterOnLoad', eventDetail); + var finalElt = getInternalData(elt).replacedWith || elt; + triggerEvent(finalElt, 'htmx:afterRequest', eventDetail); + triggerEvent(finalElt, 'htmx:afterOnLoad', eventDetail); endRequestLock(); } } @@ -1747,7 +1857,7 @@ return (function () { mergeMetaConfig(); insertIndicatorStyles(); var body = getDocument().body; - processNode(body, true); + processNode(body); triggerEvent(body, 'htmx:load', {}); window.onpopstate = function (event) { if (event.state && event.state.htmx) { diff --git a/www/test/0.1.0/test/attributes/hx-sse.js b/www/test/0.1.0/test/attributes/hx-sse.js index 84c1ece5..487fc999 100644 --- a/www/test/0.1.0/test/attributes/hx-sse.js +++ b/www/test/0.1.0/test/attributes/hx-sse.js @@ -40,7 +40,7 @@ describe("hx-sse attribute", function() { this.server.respondWith("GET", "/d1", "div1 updated"); this.server.respondWith("GET", "/d2", "div2 updated"); - var div = make('
' + + var div = make('
' + '
div1
' + '
div2
' + '
'); @@ -60,7 +60,7 @@ describe("hx-sse attribute", function() { this.server.respondWith("GET", "/d1", "div1 updated"); - var div = make('
' + + var div = make('
' + '
div1
' + '
'); @@ -81,7 +81,7 @@ describe("hx-sse attribute", function() { this.server.respondWith("GET", "/d1", "div1 updated"); - var div = make('
' + + var div = make('
' + '
div1
'); this.eventSource.sendEvent("foo"); @@ -99,7 +99,7 @@ describe("hx-sse attribute", function() { it('is closed after removal', function () { this.server.respondWith("GET", "/test", "Clicked!"); - var div = make('
' + + var div = make('
' + '
div1
' + '
'); div.click(); @@ -108,7 +108,7 @@ describe("hx-sse attribute", function() { }) it('is closed after removal with no close and activity', function () { - var div = make('
' + + var div = make('
' + '
div1
' + '
'); div.parentElement.removeChild(div); diff --git a/www/test/0.1.0/test/attributes/hx-target.js b/www/test/0.1.0/test/attributes/hx-target.js index d90c2db3..5969c728 100644 --- a/www/test/0.1.0/test/attributes/hx-target.js +++ b/www/test/0.1.0/test/attributes/hx-target.js @@ -47,6 +47,18 @@ describe("hx-target attribute", function(){ this.server.respond(); div1.innerHTML.should.equal("Clicked!"); }); + + it('targets a `find` element properly', function() + { + this.server.respondWith("GET", "/test", "Clicked!"); + var div1 = make('
Click Me!
') + div1.click(); + this.server.respond(); + var span1 = byId("s1") + var span2 = byId("s2") + span1.innerHTML.should.equal("Clicked!"); + span2.innerHTML.should.equal(""); + }); it('targets an inner element properly', function() { diff --git a/www/test/0.1.0/test/attributes/hx-ws.js b/www/test/0.1.0/test/attributes/hx-ws.js index 3dc1c327..de47075e 100644 --- a/www/test/0.1.0/test/attributes/hx-ws.js +++ b/www/test/0.1.0/test/attributes/hx-ws.js @@ -40,14 +40,14 @@ describe("hx-ws attribute", function() { }); it('handles a basic call back', function () { - var div = make('
div1
div2
'); + var div = make('
div1
div2
'); this.socket.write("
replaced
") byId("d1").innerHTML.should.equal("replaced"); byId("d2").innerHTML.should.equal("div2"); }) it('handles a basic send', function () { - var div = make('
div1
'); + var div = make('
div1
'); byId("d1").click(); var lastSent = this.socket.getLastSent(); var data = JSON.parse(lastSent); @@ -56,14 +56,14 @@ describe("hx-ws attribute", function() { it('is closed after removal', function () { this.server.respondWith("GET", "/test", "Clicked!"); - var div = make('
'); + var div = make('
'); div.click(); this.server.respond(); this.socket.wasClosed().should.equal(true) }) it('is closed after removal with no close and activity', function () { - var div = make('
'); + var div = make('
'); div.parentElement.removeChild(div); this.socket.write("
replaced
") this.socket.wasClosed().should.equal(true) diff --git a/www/test/0.1.0/test/core/ajax.js b/www/test/0.1.0/test/core/ajax.js index 08624c76..0f570a86 100644 --- a/www/test/0.1.0/test/core/ajax.js +++ b/www/test/0.1.0/test/core/ajax.js @@ -240,6 +240,36 @@ describe("Core htmx AJAX Tests", function(){ btn.innerHTML.should.equal("Click Me!"); }); + it('handles 304 NOT MODIFIED responses properly', function() + { + this.server.respondWith("GET", "/test-1", [200, {}, "Content for Tab 1"]); + this.server.respondWith("GET", "/test-2", [200, {}, "Content for Tab 2"]); + + var target = make('
') + var btn1 = make(''); + var btn2 = make(''); + + btn1.click(); + target.innerHTML.should.equal(""); + this.server.respond(); + target.innerHTML.should.equal("Content for Tab 1"); + + btn2.click(); + this.server.respond(); + target.innerHTML.should.equal("Content for Tab 2"); + + this.server.respondWith("GET", "/test-1", [304, {}, "Content for Tab 1"]); + this.server.respondWith("GET", "/test-2", [304, {}, "Content for Tab 2"]); + + btn1.click(); + this.server.respond(); + target.innerHTML.should.equal("Content for Tab 1"); + + btn2.click(); + this.server.respond(); + target.innerHTML.should.equal("Content for Tab 2"); + }); + it('handles hx-trigger with non-default value', function() { this.server.respondWith("GET", "/test", "Clicked!"); @@ -323,17 +353,49 @@ describe("Core htmx AJAX Tests", function(){ it('properly settles attributes on interior elements', function(done) { - this.server.respondWith("GET", "/test", "
"); + this.server.respondWith("GET", "/test", "
"); var div = make("
"); div.click(); this.server.respond(); - should.equal(byId("d1").getAttribute("foo"), null); + should.equal(byId("d1").getAttribute("width"), null); setTimeout(function () { - should.equal(byId("d1").getAttribute("foo"), "bar"); + should.equal(byId("d1").getAttribute("width"), "bar"); done(); }, 20); }); + it('properly handles multiple select input', function() + { + var values; + this.server.respondWith("Post", "/test", function (xhr) { + values = getParameters(xhr); + xhr.respond(204, {}, ""); + }); + + var form = make('
' + + '"); + var input = make(""); + input.focus(); + input.click(); + document.activeElement.should.equal(input); + this.server.respond(); + var input2 = byId('i2'); + document.activeElement.should.equal(input2); + }); + + it('autofocus attribute works properly w/ child', function() + { + this.server.respondWith("GET", "/test", "
"); + var input = make(""); + input.focus(); + input.click(); + document.activeElement.should.equal(input); + this.server.respond(); + var input2 = byId('i2'); + document.activeElement.should.equal(input2); + }); + + it('autofocus attribute works properly w/ true value', function() + { + this.server.respondWith("GET", "/test", "
"); + var input = make(""); + input.focus(); + input.click(); + document.activeElement.should.equal(input); + this.server.respond(); + var input2 = byId('i2'); + document.activeElement.should.equal(input2); + }); + }) diff --git a/www/test/0.1.0/test/core/events.js b/www/test/0.1.0/test/core/events.js index bd266850..37218510 100644 --- a/www/test/0.1.0/test/core/events.js +++ b/www/test/0.1.0/test/core/events.js @@ -44,6 +44,25 @@ describe("Core htmx Events", function() { } }); + it("htmx:configRequest is also dispatched in kebab-case", function () { + var handler = htmx.on("htmx:config-request", function (evt) { + evt.detail.parameters['param'] = "true"; + }); + try { + var param = null; + this.server.respondWith("POST", "/test", function (xhr) { + param = getParameters(xhr)['param']; + xhr.respond(200, {}, ""); + }); + var div = make("
"); + div.click(); + this.server.respond(); + param.should.equal("true"); + } finally { + htmx.off("htmx:config-request", handler); + } + }); + it("htmx:configRequest allows attribute removal", function () { var param = "foo"; var handler = htmx.on("htmx:configRequest", function (evt) { @@ -172,23 +191,56 @@ describe("Core htmx Events", function() { } }); - it("htmx:sendError is called after a failed request", function () { + it("htmx:sendError is called after a failed request", function (done) { var called = false; var handler = htmx.on("htmx:sendError", function (evt) { called = true; }); + this.server.restore(); // turn off server mock so connection doesn't work + var div = make(""); + div.click(); + setTimeout(function () { + htmx.off("htmx:sendError", handler); + should.equal(called, true); + done(); + }, 30); + }); + + it("htmx:afterRequest is called when replacing outerHTML", function () { + var called = false; + var handler = htmx.on("htmx:afterRequest", function (evt) { + called = true; + }); try { this.server.respondWith("POST", "/test", function (xhr) { - xhr.respond(200, {}, ""); + xhr.respond(200, {}, ""); }); - var div = make(""); + var div = make(""); div.click(); this.server.respond(); should.equal(called, true); } finally { - htmx.off("htmx:sendError", handler); + htmx.off("htmx:afterRequest", handler); } - }); + }); + + it("htmx:afterOnLoad is called when replacing outerHTML", function () { + var called = false; + var handler = htmx.on("htmx:afterOnLoad", function (evt) { + called = true; + }); + try { + this.server.respondWith("POST", "/test", function (xhr) { + xhr.respond(200, {}, ""); + }); + var div = make(""); + div.click(); + this.server.respond(); + should.equal(called, true); + } finally { + htmx.off("htmx:afterOnLoad", handler); + } + }); }); diff --git a/www/test/0.1.0/test/core/internals.js b/www/test/0.1.0/test/core/internals.js index 9e6f139e..5089daf5 100644 --- a/www/test/0.1.0/test/core/internals.js +++ b/www/test/0.1.0/test/core/internals.js @@ -1,12 +1,4 @@ describe("Core htmx internals Tests", function() { - beforeEach(function () { - this.server = makeServer(); - clearWorkArea(); - }); - afterEach(function () { - this.server.restore(); - clearWorkArea(); - }); it("makeFragment works with janky stuff", function(){ htmx._("makeFragment")("").tagName.should.equal("BODY"); @@ -20,4 +12,12 @@ describe("Core htmx internals Tests", function() { htmx._("makeFragment")("").tagName.should.equal("TBODY"); }) + it("set header works with non-ASCII values", function(){ + var xhr = new XMLHttpRequest(); + xhr.open("GET", "/dummy"); + htmx._("safelySetHeaderValue")(xhr, "Example", "привет"); + // unfortunately I can't test the value :/ + // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest + }) + }); \ No newline at end of file diff --git a/www/test/0.1.0/test/core/regressions.js b/www/test/0.1.0/test/core/regressions.js index 21b0407e..ff36a937 100644 --- a/www/test/0.1.0/test/core/regressions.js +++ b/www/test/0.1.0/test/core/regressions.js @@ -73,4 +73,20 @@ describe("Core htmx Regression Tests", function(){ div.innerText.should.contain("Foo"); }); + it ('@ symbol in attributes does not break requests', function(){ + this.server.respondWith("GET", "/test", "
Foo
"); + var div = make('
Get It
'); + div.click(); + this.server.respond(); + byId("d1").getAttribute('@foo').should.equal('bar'); + }); + + it ('@ symbol in attributes does not break attribute swizzling requests', function(){ + this.server.respondWith("GET", "/test", "
Foo
"); + var div = make('
Foo
'); + div.click(); + this.server.respond(); + byId("d1").getAttribute('@foo').should.equal('bar'); + }); + }) diff --git a/www/test/0.1.0/test/ext/hyperscript.js b/www/test/0.1.0/test/ext/hyperscript.js index 07b93bfd..55bfc252 100644 --- a/www/test/0.1.0/test/ext/hyperscript.js +++ b/www/test/0.1.0/test/ext/hyperscript.js @@ -11,6 +11,7 @@ describe("hyperscript integration", function() { it('can trigger with a custom event', function () { this.server.respondWith("GET", "/test", "Custom Event Sent!"); var btn = make('') + htmx.trigger(btn, "htmx:load"); // have to manually trigger the load event for non-AJAX dynamic content btn.click(); this.server.respond(); btn.innerHTML.should.equal("Custom Event Sent!"); @@ -19,6 +20,7 @@ describe("hyperscript integration", function() { it('can handle htmx driven events', function () { this.server.respondWith("GET", "/test", "Clicked!"); var btn = make('') + htmx.trigger(btn, "htmx:load"); btn.classList.contains("afterSettle").should.equal(false); btn.click(); this.server.respond(); @@ -29,9 +31,20 @@ describe("hyperscript integration", function() { this.server.respondWith("GET", "/test", [404, {}, "Bad request"]); var div = make('
') var btn = make('') + htmx.trigger(btn, "htmx:load"); btn.click(); this.server.respond(); div.innerHTML.should.equal("Response Status Error Code 404 from /test"); }); + it('hyperscript in non-htmx annotated nodes is evaluated', function () { + this.server.respondWith("GET", "/test", "
"); + var btn = make('') + btn.click(); + this.server.respond(); + var newDiv = byId("d1"); + newDiv.click(); + newDiv.innerText.should.equal("Clicked..."); + }); + }); \ No newline at end of file diff --git a/www/test/0.1.0/test/index.html b/www/test/0.1.0/test/index.html index 06102a59..79e1f4a4 100644 --- a/www/test/0.1.0/test/index.html +++ b/www/test/0.1.0/test/index.html @@ -37,7 +37,19 @@ Confirm & Prompt Test
  • - Scroll Test + Scroll Test 1 - Start/End +
  • +
  • + Scroll Test 2 - Event Handler +
  • +
  • + SSE - Multiple Event Sources - Single Event Name +
  • +
  • + SSE - Single Event Source - Multiple Event Names +
  • +
  • + SSE - Settling
  • @@ -89,9 +101,6 @@ - diff --git a/www/test/0.1.0/test/lib/_hyperscript.js b/www/test/0.1.0/test/lib/_hyperscript.js index 0638762f..4e6e40f4 100644 --- a/www/test/0.1.0/test/lib/_hyperscript.js +++ b/www/test/0.1.0/test/lib/_hyperscript.js @@ -11,9 +11,39 @@ return (function () { 'use strict'; - //----------------------------------------------- + //==================================================================== + // Utilities + //==================================================================== + + function mergeObjects(obj1, obj2) { + for (var key in obj2) { + if (obj2.hasOwnProperty(key)) { + obj1[key] = obj2[key]; + } + } + return obj1; + } + + function parseJSON(jString) { + try { + return JSON.parse(jString); + } catch(error) { + logError(error); + return null; + } + } + + function logError(msg) { + if(console.error) { + console.error(msg); + } else if (console.log) { + console.log("ERROR: ", msg); + } + } + + //==================================================================== // Lexer - //----------------------------------------------- + //==================================================================== var _lexer = function () { var OP_TABLE = { '+': 'PLUS', @@ -77,6 +107,10 @@ (c >= 'A' && c <= 'Z'); } + function isIdentifierChar(c) { + return (c === "_" || c === "$"); + } + function makeTokensObject(tokens, consumed, source) { @@ -205,7 +239,7 @@ tokens.push(consumeClassReference()); } else if (!possiblePrecedingSymbol() && currentChar() === "#" && isAlpha(nextChar())) { tokens.push(consumeIdReference()); - } else if (isAlpha(currentChar())) { + } else if (isAlpha(currentChar()) || isIdentifierChar(currentChar())) { tokens.push(consumeIdentifier()); } else if (isNumeric(currentChar())) { tokens.push(consumeNumber()); @@ -273,7 +307,7 @@ function consumeIdentifier() { var identifier = makeToken("IDENTIFIER"); var value = consumeChar(); - while (isAlpha(currentChar())) { + while (isAlpha(currentChar()) || isIdentifierChar(currentChar())) { value += consumeChar(); } identifier.value = value; @@ -369,9 +403,9 @@ } }(); - //----------------------------------------------- + //==================================================================== // Parser - //----------------------------------------------- + //==================================================================== var _parser = function () { var GRAMMAR = {} @@ -440,11 +474,10 @@ } }(); - //----------------------------------------------- + //==================================================================== // Runtime - //----------------------------------------------- + //==================================================================== var _runtime = function () { - var SCRIPT_ATTRIBUTES = ["_", "script", "data-script"]; function matchesSelector(elt, selector) { // noinspection JSUnresolvedVariable @@ -509,9 +542,17 @@ return last; } + var _scriptAttrs = null; + function getScriptAttributes() { + if (_scriptAttrs == null) { + _scriptAttrs = _hyperscript.config.attributes.replace(/ /g,'').split(",") + } + return _scriptAttrs; + } + function getScript(elt) { - for (var i = 0; i < SCRIPT_ATTRIBUTES.length; i++) { - var scriptAttribute = SCRIPT_ATTRIBUTES[i]; + for (var i = 0; i < getScriptAttributes().length; i++) { + var scriptAttribute = getScriptAttributes()[i]; if (elt.hasAttribute && elt.hasAttribute(scriptAttribute)) { return elt.getAttribute(scriptAttribute) } @@ -525,12 +566,8 @@ }); } - function setScriptAttrs(values) { - SCRIPT_ATTRIBUTES = values; - } - function getScriptSelector() { - return SCRIPT_ATTRIBUTES.map(function (attribute) { + return getScriptAttributes().map(function (attribute) { return "[" + attribute + "]"; }).join(", "); } @@ -562,20 +599,45 @@ return eval(evalString).apply(null, args); } + function processNode(elt) { + var selector = _runtime.getScriptSelector(); + if (matchesSelector(elt, selector)) { + initElement(elt); + } + forEach(elt.querySelectorAll(selector), function (elt) { + initElement(elt); + }) + } + function initElement(elt) { - var src = getScript(elt); - if (src) { - var tokens = _lexer.tokenize(src); - var hyperScript = _parser.parseHyperScript(tokens); - var transpiled = _parser.transpile(hyperScript); - if (elt.getAttribute('debug') === "true") { - console.log(transpiled); + var internalData = getInternalData(elt); + if (!internalData.initialized) { + var src = getScript(elt); + if (src) { + internalData.initialized = true; + internalData.script = src; + var tokens = _lexer.tokenize(src); + var hyperScript = _parser.parseHyperScript(tokens); + var transpiled = _parser.transpile(hyperScript); + if (elt.getAttribute('debug') === "true") { + console.log(transpiled); + } + var hyperscriptObj = eval(transpiled); + hyperscriptObj.applyEventListenersTo(elt); } - var hyperscriptObj = eval(transpiled); - hyperscriptObj.applyEventListenersTo(elt); } } + function getInternalData(elt) { + var dataProp = 'hyperscript-internal-data'; + var data = elt[dataProp]; + if (!data) { + data = elt[dataProp] = {}; + } + return data; + } + + function ajax(method, url, callback, data) { var xhr = new XMLHttpRequest(); xhr.onload = function() { @@ -606,1084 +668,1112 @@ matchesSelector: matchesSelector, getScript: getScript, applyEventListeners: applyEventListeners, - setScriptAttrs: setScriptAttrs, - initElement: initElement, + processNode: processNode, evaluate: evaluate, getScriptSelector: getScriptSelector, ajax: ajax, } }(); - //----------------------------------------------- - // Expressions - //----------------------------------------------- - - _parser.addGrammarElement("parenthesized", function (parser, tokens) { - if (tokens.matchOpToken('(')) { - var expr = parser.parseElement("expression", tokens); - tokens.requireOpToken(")"); - return { - type: "parenthesized", - expr: expr, - transpile: function () { - return "(" + parser.transpile(expr) + ")"; - } - } - } - }) - - _parser.addGrammarElement("string", function (parser, tokens) { - var stringToken = tokens.matchTokenType('STRING'); - if (stringToken) { - return { - type: "string", - token: stringToken, - transpile: function () { - if (stringToken.value.indexOf("'") === 0) { - return "'" + stringToken.value + "'"; - } else { - return '"' + stringToken.value + '"'; - } - } - } - } - }) - - _parser.addGrammarElement("nakedString", function (parser, tokens) { - if (tokens.hasMore()) { - var tokenArr = tokens.consumeUntilWhitespace(); - tokens.matchTokenType("WHITESPACE"); - return { - type: "nakedString", - tokens: tokenArr, - transpile: function () { - return "'" + tokenArr.map(function(t){return t.value}).join("") + "'"; - } - } - } - }) - - _parser.addGrammarElement("number", function (parser, tokens) { - var number = tokens.matchTokenType('NUMBER'); - if (number) { - var numberToken = number; - var value = parseFloat(number.value) - return { - type: "number", - value: value, - numberToken: numberToken, - transpile: function () { - return numberToken.value; - } - } - } - }) - - _parser.addGrammarElement("idRef", function (parser, tokens) { - var elementId = tokens.matchTokenType('ID_REF'); - if (elementId) { - return { - type: "idRef", - value: elementId.value.substr(1), - transpile: function () { - return "document.getElementById('" + this.value + "')" - } - }; - } - }) - - _parser.addGrammarElement("classRef", function (parser, tokens) { - var classRef = tokens.matchTokenType('CLASS_REF'); - if (classRef) { - return { - type: "classRef", - value: classRef.value, - className: function () { - return this.value.substr(1); - }, - transpile: function () { - return "document.querySelectorAll('" + this.value + "')" - } - }; - } - }) - - _parser.addGrammarElement("attributeRef", function (parser, tokens) { - if (tokens.matchOpToken("[")) { - var name = tokens.matchTokenType("IDENTIFIER"); - var value = null; - if (tokens.matchOpToken("=")) { - value = parser.parseElement("expression", tokens); - } - tokens.requireOpToken("]"); - return { - type: "attribute_expression", - name: name.value, - value: value, - transpile: function () { - if (this.value) { - return "({name: '" + this.name + "', value: " + parser.transpile(this.value) + "})"; - } else { - return "({name: '" + this.name + "'})"; - } - } - } - } - }) - - _parser.addGrammarElement("objectLiteral", function (parser, tokens) { - if (tokens.matchOpToken("{")) { - var fields = [] - if (!tokens.matchOpToken("}")) { - do { - var name = tokens.requireTokenType("IDENTIFIER"); - tokens.requireOpToken(":"); - var value = parser.parseElement("expression", tokens); - fields.push({name: name, value: value}); - } while (tokens.matchOpToken(",")) - tokens.requireOpToken("}"); - } - return { - type: "objectLiteral", - fields: fields, - transpile: function () { - return "({" + fields.map(function (field) { - return field.name.value + ":" + parser.transpile(field.value) - }).join(", ") + "})"; - } - } - } - - - }) - - _parser.addGrammarElement("namedArgumentList", function (parser, tokens) { - if (tokens.matchOpToken("(")) { - var fields = [] - if (!tokens.matchOpToken(")")) { - do { - var name = tokens.requireTokenType("IDENTIFIER"); - tokens.requireOpToken(":"); - var value = parser.parseElement("expression", tokens); - fields.push({name: name, value: value}); - } while (tokens.matchOpToken(",")) + //==================================================================== + // Grammar + //==================================================================== + { + _parser.addGrammarElement("parenthesized", function (parser, tokens) { + if (tokens.matchOpToken('(')) { + var expr = parser.parseElement("expression", tokens); tokens.requireOpToken(")"); - } - return { - type: "namedArgumentList", - fields: fields, - transpile: function () { - return "({_namedArgList_:true, " + fields.map(function (field) { - return field.name.value + ":" + parser.transpile(field.value) - }).join(", ") + "})"; - } - } - } - - - }) - - _parser.addGrammarElement("symbol", function (parser, tokens) { - var identifier = tokens.matchTokenType('IDENTIFIER'); - if (identifier) { - return { - type: "symbol", - name: identifier.value, - transpile: function () { - return identifier.value; - } - }; - } - }); - - _parser.addGrammarElement("implicitMeTarget", function (parser, tokens) { - return { - type: "implicitMeTarget", - transpile: function () { - return "[me]" - } - }; - }); - - _parser.addGrammarElement("implicitAllTarget", function (parser, tokens) { - return { - type: "implicitAllTarget", - transpile: function () { - return 'document.querySelectorAll("*")'; - } - }; - }); - - _parser.addGrammarElement("millisecondLiteral", function (parser, tokens) { - var number = tokens.requireTokenType(tokens, "NUMBER"); - var factor = 1; - if (tokens.matchToken("s")) { - factor = 1000; - } else if (tokens.matchToken("ms")) { - // do nothing - } - return { - type: "millisecondLiteral", - number: number, - factor: factor, - transpile: function () { - return factor * parseFloat(this.number.value); - } - }; - }); - - _parser.addGrammarElement("boolean", function (parser, tokens) { - var booleanLiteral = tokens.matchToken("true") || tokens.matchToken("false"); - if (booleanLiteral) { - return { - type: "boolean", - transpile: function () { - return booleanLiteral.value; - } - } - } - }); - - _parser.addGrammarElement("null", function (parser, tokens) { - if (tokens.matchToken('null')) { - return { - type: "null", - transpile: function () { - return "null"; - } - } - } - }); - - _parser.addGrammarElement("arrayLiteral", function (parser, tokens) { - if (tokens.matchOpToken('[')) { - var values = []; - if (!tokens.matchOpToken(']')) { - do { - var expr = parser.parseElement("expression", tokens); - if (expr == null) { - parser.raiseParseError(tokens, "Expected an expression"); + return { + type: "parenthesized", + expr: expr, + transpile: function () { + return "(" + parser.transpile(expr) + ")"; } - values.push(expr); - } while(tokens.matchOpToken(",")) + } + } + }) + + _parser.addGrammarElement("string", function (parser, tokens) { + var stringToken = tokens.matchTokenType('STRING'); + if (stringToken) { + return { + type: "string", + token: stringToken, + transpile: function () { + if (stringToken.value.indexOf("'") === 0) { + return "'" + stringToken.value + "'"; + } else { + return '"' + stringToken.value + '"'; + } + } + } + } + }) + + _parser.addGrammarElement("nakedString", function (parser, tokens) { + if (tokens.hasMore()) { + var tokenArr = tokens.consumeUntilWhitespace(); + tokens.matchTokenType("WHITESPACE"); + return { + type: "nakedString", + tokens: tokenArr, + transpile: function () { + return "'" + tokenArr.map(function (t) { + return t.value + }).join("") + "'"; + } + } + } + }) + + _parser.addGrammarElement("number", function (parser, tokens) { + var number = tokens.matchTokenType('NUMBER'); + if (number) { + var numberToken = number; + var value = parseFloat(number.value) + return { + type: "number", + value: value, + numberToken: numberToken, + transpile: function () { + return numberToken.value; + } + } + } + }) + + _parser.addGrammarElement("idRef", function (parser, tokens) { + var elementId = tokens.matchTokenType('ID_REF'); + if (elementId) { + return { + type: "idRef", + value: elementId.value.substr(1), + transpile: function () { + return "document.getElementById('" + this.value + "')" + } + }; + } + }) + + _parser.addGrammarElement("classRef", function (parser, tokens) { + var classRef = tokens.matchTokenType('CLASS_REF'); + if (classRef) { + return { + type: "classRef", + value: classRef.value, + className: function () { + return this.value.substr(1); + }, + transpile: function () { + return "document.querySelectorAll('" + this.value + "')" + } + }; + } + }) + + _parser.addGrammarElement("attributeRef", function (parser, tokens) { + if (tokens.matchOpToken("[")) { + var name = tokens.matchTokenType("IDENTIFIER"); + var value = null; + if (tokens.matchOpToken("=")) { + value = parser.parseElement("expression", tokens); + } tokens.requireOpToken("]"); + return { + type: "attribute_expression", + name: name.value, + value: value, + transpile: function () { + if (this.value) { + return "({name: '" + this.name + "', value: " + parser.transpile(this.value) + "})"; + } else { + return "({name: '" + this.name + "'})"; + } + } + } } + }) + + _parser.addGrammarElement("objectLiteral", function (parser, tokens) { + if (tokens.matchOpToken("{")) { + var fields = [] + if (!tokens.matchOpToken("}")) { + do { + var name = tokens.requireTokenType("IDENTIFIER"); + tokens.requireOpToken(":"); + var value = parser.parseElement("expression", tokens); + fields.push({name: name, value: value}); + } while (tokens.matchOpToken(",")) + tokens.requireOpToken("}"); + } + return { + type: "objectLiteral", + fields: fields, + transpile: function () { + return "({" + fields.map(function (field) { + return field.name.value + ":" + parser.transpile(field.value) + }).join(", ") + "})"; + } + } + } + + + }) + + _parser.addGrammarElement("namedArgumentList", function (parser, tokens) { + if (tokens.matchOpToken("(")) { + var fields = [] + if (!tokens.matchOpToken(")")) { + do { + var name = tokens.requireTokenType("IDENTIFIER"); + tokens.requireOpToken(":"); + var value = parser.parseElement("expression", tokens); + fields.push({name: name, value: value}); + } while (tokens.matchOpToken(",")) + tokens.requireOpToken(")"); + } + return { + type: "namedArgumentList", + fields: fields, + transpile: function () { + return "({_namedArgList_:true, " + fields.map(function (field) { + return field.name.value + ":" + parser.transpile(field.value) + }).join(", ") + "})"; + } + } + } + + + }) + + _parser.addGrammarElement("symbol", function (parser, tokens) { + var identifier = tokens.matchTokenType('IDENTIFIER'); + if (identifier) { + return { + type: "symbol", + name: identifier.value, + transpile: function () { + return identifier.value; + } + }; + } + }); + + _parser.addGrammarElement("implicitMeTarget", function (parser, tokens) { return { - type: "arrayLiteral", - values:values, + type: "implicitMeTarget", transpile: function () { - return "[" + values.map(function(v){ return parser.transpile(v) }).join(", ") + "]"; - } - } - } - }); - - _parser.addGrammarElement("blockLiteral", function (parser, tokens) { - if (tokens.matchOpToken('\\')) { - var args = [] - var arg1 = tokens.matchTokenType("IDENTIFIER"); - if (arg1) { - args.push(arg1); - while (tokens.matchOpToken(",")) { - args.push(tokens.requireTokenType("IDENTIFIER")); - } - } - // TODO compound op token - tokens.requireOpToken("-"); - tokens.requireOpToken(">"); - var expr = parser.parseElement("expression", tokens); - if (expr == null) { - parser.raiseParseError(tokens, "Expected an expression"); - } - return { - type: "blockLiteral", - args: args, - expr: expr, - transpile: function () { - return "function(" + args.map(function (arg) { - return arg.value - }).join(", ") + "){ return " + - parser.transpile(expr) + " }"; - } - } - } - }); - - _parser.addGrammarElement("leaf", function (parser, tokens) { - return parser.parseAnyOf(["parenthesized", "boolean", "null", "string", "number", "idRef", "classRef", "symbol", "propertyRef", "objectLiteral", "arrayLiteral", "blockLiteral"], tokens) - }); - - _parser.addGrammarElement("propertyAccess", function (parser, tokens, root) { - if (tokens.matchOpToken(".")) { - var prop = tokens.requireTokenType("IDENTIFIER"); - var propertyAccess = { - type: "propertyAccess", - root: root, - prop: prop, - transpile: function () { - return parser.transpile(root) + "." + prop.value; + return "[me]" } }; - return _parser.parseElement("indirectExpression", tokens, propertyAccess); - } - }); + }); - _parser.addGrammarElement("functionCall", function (parser, tokens, root) { - if (tokens.matchOpToken("(")) { - var args = []; - do { - args.push(parser.parseElement("expression", tokens)); - } while (tokens.matchOpToken(",")) - tokens.requireOpToken(")"); - var functionCall = { - type: "functionCall", - root: root, - args: args, + _parser.addGrammarElement("implicitAllTarget", function (parser, tokens) { + return { + type: "implicitAllTarget", transpile: function () { - return parser.transpile(root) + "(" + args.map(function (arg) { - return parser.transpile(arg) - }).join(",") + ")" + return 'document.querySelectorAll("*")'; } }; - return _parser.parseElement("indirectExpression", tokens, functionCall); - } - }); + }); - _parser.addGrammarElement("indirectExpression", function (parser, tokens, root) { - var propAccess = parser.parseElement("propertyAccess", tokens, root); - if (propAccess) { - return propAccess; - } - - var functionCall = parser.parseElement("functionCall", tokens, root); - if (functionCall) { - return functionCall; - } - - return root; - }); - - _parser.addGrammarElement("primaryExpression", function (parser, tokens) { - var leaf = parser.parseElement("leaf", tokens); - if (leaf) { - return parser.parseElement("indirectExpression", tokens, leaf); - } - parser.raiseParseError(tokens, "Unexpected value: " + tokens.currentToken().value); - }); - - _parser.addGrammarElement("postfixExpression", function (parser, tokens) { - var root = parser.parseElement("primaryExpression", tokens); - if (tokens.matchOpToken(":")) { - var typeName = tokens.requireTokenType("IDENTIFIER"); - var nullOk = !tokens.matchOpToken("!"); + _parser.addGrammarElement("millisecondLiteral", function (parser, tokens) { + var number = tokens.requireTokenType(tokens, "NUMBER"); + var factor = 1; + if (tokens.matchToken("s")) { + factor = 1000; + } else if (tokens.matchToken("ms")) { + // do nothing + } return { - type: "typeCheck", - typeName: typeName, - root: root, - nullOk: nullOk, + type: "millisecondLiteral", + number: number, + factor: factor, transpile: function () { - return "_hyperscript.runtime.typeCheck(" + parser.transpile(root) + ", '" + typeName.value + "', " + nullOk + ")"; + return factor * parseFloat(this.number.value); + } + }; + }); + + _parser.addGrammarElement("boolean", function (parser, tokens) { + var booleanLiteral = tokens.matchToken("true") || tokens.matchToken("false"); + if (booleanLiteral) { + return { + type: "boolean", + transpile: function () { + return booleanLiteral.value; + } } } - } else { + }); + + _parser.addGrammarElement("null", function (parser, tokens) { + if (tokens.matchToken('null')) { + return { + type: "null", + transpile: function () { + return "null"; + } + } + } + }); + + _parser.addGrammarElement("arrayLiteral", function (parser, tokens) { + if (tokens.matchOpToken('[')) { + var values = []; + if (!tokens.matchOpToken(']')) { + do { + var expr = parser.parseElement("expression", tokens); + if (expr == null) { + parser.raiseParseError(tokens, "Expected an expression"); + } + values.push(expr); + } while (tokens.matchOpToken(",")) + tokens.requireOpToken("]"); + } + return { + type: "arrayLiteral", + values: values, + transpile: function () { + return "[" + values.map(function (v) { + return parser.transpile(v) + }).join(", ") + "]"; + } + } + } + }); + + _parser.addGrammarElement("blockLiteral", function (parser, tokens) { + if (tokens.matchOpToken('\\')) { + var args = [] + var arg1 = tokens.matchTokenType("IDENTIFIER"); + if (arg1) { + args.push(arg1); + while (tokens.matchOpToken(",")) { + args.push(tokens.requireTokenType("IDENTIFIER")); + } + } + // TODO compound op token + tokens.requireOpToken("-"); + tokens.requireOpToken(">"); + var expr = parser.parseElement("expression", tokens); + if (expr == null) { + parser.raiseParseError(tokens, "Expected an expression"); + } + return { + type: "blockLiteral", + args: args, + expr: expr, + transpile: function () { + return "function(" + args.map(function (arg) { + return arg.value + }).join(", ") + "){ return " + + parser.transpile(expr) + " }"; + } + } + } + }); + + _parser.addGrammarElement("leaf", function (parser, tokens) { + return parser.parseAnyOf(["parenthesized", "boolean", "null", "string", "number", "idRef", "classRef", "symbol", "propertyRef", "objectLiteral", "arrayLiteral", "blockLiteral"], tokens) + }); + + _parser.addGrammarElement("propertyAccess", function (parser, tokens, root) { + if (tokens.matchOpToken(".")) { + var prop = tokens.requireTokenType("IDENTIFIER"); + var propertyAccess = { + type: "propertyAccess", + root: root, + prop: prop, + transpile: function () { + return parser.transpile(root) + "." + prop.value; + } + }; + return _parser.parseElement("indirectExpression", tokens, propertyAccess); + } + }); + + _parser.addGrammarElement("functionCall", function (parser, tokens, root) { + if (tokens.matchOpToken("(")) { + var args = []; + if (!tokens.matchOpToken(')')) { + do { + args.push(parser.parseElement("expression", tokens)); + } while (tokens.matchOpToken(",")) + tokens.requireOpToken(")"); + } + var functionCall = { + type: "functionCall", + root: root, + args: args, + transpile: function () { + return parser.transpile(root) + "(" + args.map(function (arg) { + return parser.transpile(arg) + }).join(",") + ")" + } + }; + return _parser.parseElement("indirectExpression", tokens, functionCall); + } + }); + + _parser.addGrammarElement("indirectExpression", function (parser, tokens, root) { + var propAccess = parser.parseElement("propertyAccess", tokens, root); + if (propAccess) { + return propAccess; + } + + var functionCall = parser.parseElement("functionCall", tokens, root); + if (functionCall) { + return functionCall; + } + return root; - } - }); + }); - _parser.addGrammarElement("logicalNot", function (parser, tokens) { - if (tokens.matchToken("not")) { - var root = parser.parseElement("unaryExpression", tokens); - return { - type: "logicalNot", - root: root, - transpile: function () { - return "!" + parser.transpile(root); + _parser.addGrammarElement("primaryExpression", function (parser, tokens) { + var leaf = parser.parseElement("leaf", tokens); + if (leaf) { + return parser.parseElement("indirectExpression", tokens, leaf); + } + parser.raiseParseError(tokens, "Unexpected value: " + tokens.currentToken().value); + }); + + _parser.addGrammarElement("postfixExpression", function (parser, tokens) { + var root = parser.parseElement("primaryExpression", tokens); + if (tokens.matchOpToken(":")) { + var typeName = tokens.requireTokenType("IDENTIFIER"); + var nullOk = !tokens.matchOpToken("!"); + return { + type: "typeCheck", + typeName: typeName, + root: root, + nullOk: nullOk, + transpile: function () { + return "_hyperscript.runtime.typeCheck(" + parser.transpile(root) + ", '" + typeName.value + "', " + nullOk + ")"; + } } - }; - } - }); - - _parser.addGrammarElement("negativeNumber", function (parser, tokens) { - if (tokens.matchOpToken("-")) { - var root = parser.parseElement("unaryExpression", tokens); - return { - type: "negativeNumber", - root: root, - transpile: function () { - return "-" + parser.transpile(root); - } - }; - } - }); - - _parser.addGrammarElement("unaryExpression", function (parser, tokens) { - return parser.parseAnyOf(["logicalNot", "negativeNumber", "postfixExpression"], tokens); - }); - - _parser.addGrammarElement("mathOperator", function (parser, tokens) { - var expr = parser.parseElement("unaryExpression", tokens); - var mathOp, initialMathOp = null; - mathOp = tokens.matchAnyOpToken("+", "-", "*", "/", "%") - while (mathOp) { - initialMathOp = initialMathOp || mathOp; - if(initialMathOp.value !== mathOp.value) { - parser.raiseParseError(tokens, "You must parenthesize math operations with different operators") - } - var rhs = parser.parseElement("unaryExpression", tokens); - expr = { - type: "mathOperator", - operator: mathOp.value, - lhs: expr, - rhs: rhs, - transpile: function () { - return parser.transpile(this.lhs) + " " + this.operator + " " + parser.transpile(this.rhs); - } - } - mathOp = tokens.matchAnyOpToken("+", "-", "*", "/", "%") - } - return expr; - }); - - _parser.addGrammarElement("mathExpression", function (parser, tokens) { - return parser.parseAnyOf(["mathOperator", "unaryExpression"], tokens); - }); - - _parser.addGrammarElement("comparisonOperator", function (parser, tokens) { - var expr = parser.parseElement("mathExpression", tokens); - var comparisonOp, initialComparisonOp = null; - comparisonOp = tokens.matchAnyOpToken("<", ">", "<=", ">=", "==", "===", "!=", "!==") - while (comparisonOp) { - initialComparisonOp = initialComparisonOp || comparisonOp; - if(initialComparisonOp.value !== comparisonOp.value) { - parser.raiseParseError(tokens, "You must parenthesize comparison operations with different operators") - } - var rhs = parser.parseElement("mathExpression", tokens); - expr = { - type: "comparisonOperator", - operator: comparisonOp.value, - lhs: expr, - rhs: rhs, - transpile: function () { - return parser.transpile(this.lhs) + " " + this.operator + " " + parser.transpile(this.rhs); - } - } - comparisonOp = tokens.matchAnyOpToken("<", ">", "<=", ">=", "==", "===", "!=", "!==") - } - return expr; - }); - - _parser.addGrammarElement("comparisonExpression", function (parser, tokens) { - return parser.parseAnyOf(["comparisonOperator", "mathExpression"], tokens); - }); - - _parser.addGrammarElement("logicalOperator", function (parser, tokens) { - var expr = parser.parseElement("comparisonExpression", tokens); - var logicalOp, initialLogicalOp = null; - logicalOp = tokens.matchToken("and") || tokens.matchToken("or"); - while (logicalOp) { - initialLogicalOp = initialLogicalOp || logicalOp; - if(initialLogicalOp.value !== logicalOp.value) { - parser.raiseParseError(tokens, "You must parenthesize logical operations with different operators") - } - var rhs = parser.parseElement("comparisonExpression", tokens); - expr = { - type: "logicalOperator", - operator: logicalOp.value, - lhs: expr, - rhs: rhs, - transpile: function () { - return parser.transpile(this.lhs) + " " + (this.operator === "and" ? " && " : " || ") + " " + parser.transpile(this.rhs); - } - } - logicalOp = tokens.matchToken("and") || tokens.matchToken("or"); - } - return expr; - }); - - _parser.addGrammarElement("logicalExpression", function (parser, tokens) { - return parser.parseAnyOf(["logicalOperator", "mathExpression"], tokens); - }); - - _parser.addGrammarElement("expression", function (parser, tokens) { - return parser.parseElement("logicalExpression", tokens); - }); - - _parser.addGrammarElement("target", function (parser, tokens) { - var root = parser.parseAnyOf(["symbol", "classRef", "idRef"], tokens); - if (root == null) { - parser.raiseParseError(tokens, "Expected a valid target expression"); - } - - var propPath = [] - while (tokens.matchOpToken(".")) { - propPath.push(tokens.requireTokenType("IDENTIFIER").value) - } - - return { - type: "target", - propPath: propPath, - root: root, - transpile: function () { - return "_hyperscript.runtime.evalTarget(" + parser.transpile(root) + ", [" + propPath.map(function (prop) { - return "\"" + prop + "\"" - }).join(", ") + "])"; - } - }; - }); - - _parser.addGrammarElement("command", function (parser, tokens) { - return parser.parseAnyOf(["onCmd", "addCmd", "removeCmd", "toggleCmd", "waitCmd", "sendCmd", "triggerCmd", - "takeCmd", "logCmd", "callCmd", "putCmd", "setCmd", "ifCmd", "ajaxCmd"], tokens); - }) - - _parser.addGrammarElement("commandList", function (parser, tokens) { - var cmd = parser.parseElement("command", tokens); - if (cmd) { - tokens.matchToken("then"); - cmd.next = parser.parseElement("commandList", tokens); - return cmd; - } - }) - - _parser.addGrammarElement("hyperscript", function (parser, tokens) { - var eventListeners = [] - do { - eventListeners.push(parser.parseElement("eventListener", tokens)); - } while (tokens.matchToken("end") && tokens.hasMore()) - if (tokens.hasMore()) { - parser.raiseParseError(tokens); - } - return { - type: "hyperscript", - eventListeners: eventListeners, - transpile: function () { - return "(function(){\n" + - "var eventListeners = []\n" + - eventListeners.map(function (el) { - return " eventListeners.push(" + parser.transpile(el) + ");\n" - }).join("") + - " function applyEventListenersTo(elt) { _hyperscript.runtime.applyEventListeners(this, elt) }\n" + - " return {eventListeners:eventListeners, applyEventListenersTo:applyEventListenersTo}\n" + - "})()" - } - }; - }) - - - _parser.addGrammarElement("eventListener", function (parser, tokens) { - tokens.requireToken("on"); - var on = parser.parseElement("dotOrColonPath", tokens); - if (on == null) { - parser.raiseParseError(tokens, "Expected event name") - } - if (tokens.matchToken("from")) { - var from = parser.parseElement("target", tokens); - if (from == null) { - parser.raiseParseError(tokens, "Expected target value") - } - } else { - var from = parser.parseElement("implicitMeTarget", tokens); - } - - var args = []; - if (tokens.matchOpToken("(")) { - do { - args.push(tokens.requireTokenType('IDENTIFIER')); - } while (tokens.matchOpToken(",")) - tokens.requireOpToken(')') - } - - var start = parser.parseElement("commandList", tokens); - var eventListener = { - type: "eventListener", - on: on, - from: from, - start: start, - transpile: function () { - return "(function(me){" + - "var my = me;\n" + - "_hyperscript.runtime.forEach( " + parser.transpile(from) + ", function(target){\n" + - " target.addEventListener('" + parser.transpile(on) + "', function(event){\n" + - args.map(function(arg){return "var " + arg.value + " = event.detail." + arg.value + ";"}).join("\n") + "\n" + - parser.transpile(start) + - " })\n" + - "})\n" + - "})" - } - }; - return eventListener; - }); - - _parser.addGrammarElement("addCmd", function (parser, tokens) { - if (tokens.matchToken("add")) { - var classRef = parser.parseElement("classRef", tokens); - var attributeRef = null; - if (classRef == null) { - attributeRef = parser.parseElement("attributeRef", tokens); - if (attributeRef == null) { - parser.raiseParseError(tokens, "Expected either a class reference or attribute expression") - } - } - - if (tokens.matchToken("to")) { - var to = parser.parseElement("target", tokens); } else { - var to = parser.parseElement("implicitMeTarget"); + return root; + } + }); + + _parser.addGrammarElement("logicalNot", function (parser, tokens) { + if (tokens.matchToken("not")) { + var root = parser.parseElement("unaryExpression", tokens); + return { + type: "logicalNot", + root: root, + transpile: function () { + return "!" + parser.transpile(root); + } + }; + } + }); + + _parser.addGrammarElement("negativeNumber", function (parser, tokens) { + if (tokens.matchOpToken("-")) { + var root = parser.parseElement("unaryExpression", tokens); + return { + type: "negativeNumber", + root: root, + transpile: function () { + return "-" + parser.transpile(root); + } + }; + } + }); + + _parser.addGrammarElement("unaryExpression", function (parser, tokens) { + return parser.parseAnyOf(["logicalNot", "negativeNumber", "postfixExpression"], tokens); + }); + + _parser.addGrammarElement("mathOperator", function (parser, tokens) { + var expr = parser.parseElement("unaryExpression", tokens); + var mathOp, initialMathOp = null; + mathOp = tokens.matchAnyOpToken("+", "-", "*", "/", "%") + while (mathOp) { + initialMathOp = initialMathOp || mathOp; + if (initialMathOp.value !== mathOp.value) { + parser.raiseParseError(tokens, "You must parenthesize math operations with different operators") + } + var rhs = parser.parseElement("unaryExpression", tokens); + expr = { + type: "mathOperator", + operator: mathOp.value, + lhs: expr, + rhs: rhs, + transpile: function () { + return parser.transpile(this.lhs) + " " + this.operator + " " + parser.transpile(this.rhs); + } + } + mathOp = tokens.matchAnyOpToken("+", "-", "*", "/", "%") + } + return expr; + }); + + _parser.addGrammarElement("mathExpression", function (parser, tokens) { + return parser.parseAnyOf(["mathOperator", "unaryExpression"], tokens); + }); + + _parser.addGrammarElement("comparisonOperator", function (parser, tokens) { + var expr = parser.parseElement("mathExpression", tokens); + var comparisonOp, initialComparisonOp = null; + comparisonOp = tokens.matchAnyOpToken("<", ">", "<=", ">=", "==", "===", "!=", "!==") + while (comparisonOp) { + initialComparisonOp = initialComparisonOp || comparisonOp; + if (initialComparisonOp.value !== comparisonOp.value) { + parser.raiseParseError(tokens, "You must parenthesize comparison operations with different operators") + } + var rhs = parser.parseElement("mathExpression", tokens); + expr = { + type: "comparisonOperator", + operator: comparisonOp.value, + lhs: expr, + rhs: rhs, + transpile: function () { + return parser.transpile(this.lhs) + " " + this.operator + " " + parser.transpile(this.rhs); + } + } + comparisonOp = tokens.matchAnyOpToken("<", ">", "<=", ">=", "==", "===", "!=", "!==") + } + return expr; + }); + + _parser.addGrammarElement("comparisonExpression", function (parser, tokens) { + return parser.parseAnyOf(["comparisonOperator", "mathExpression"], tokens); + }); + + _parser.addGrammarElement("logicalOperator", function (parser, tokens) { + var expr = parser.parseElement("comparisonExpression", tokens); + var logicalOp, initialLogicalOp = null; + logicalOp = tokens.matchToken("and") || tokens.matchToken("or"); + while (logicalOp) { + initialLogicalOp = initialLogicalOp || logicalOp; + if (initialLogicalOp.value !== logicalOp.value) { + parser.raiseParseError(tokens, "You must parenthesize logical operations with different operators") + } + var rhs = parser.parseElement("comparisonExpression", tokens); + expr = { + type: "logicalOperator", + operator: logicalOp.value, + lhs: expr, + rhs: rhs, + transpile: function () { + return parser.transpile(this.lhs) + " " + (this.operator === "and" ? " && " : " || ") + " " + parser.transpile(this.rhs); + } + } + logicalOp = tokens.matchToken("and") || tokens.matchToken("or"); + } + return expr; + }); + + _parser.addGrammarElement("logicalExpression", function (parser, tokens) { + return parser.parseAnyOf(["logicalOperator", "mathExpression"], tokens); + }); + + _parser.addGrammarElement("expression", function (parser, tokens) { + return parser.parseElement("logicalExpression", tokens); + }); + + _parser.addGrammarElement("target", function (parser, tokens) { + var root = parser.parseAnyOf(["symbol", "classRef", "idRef"], tokens); + if (root == null) { + parser.raiseParseError(tokens, "Expected a valid target expression"); + } + + var propPath = [] + while (tokens.matchOpToken(".")) { + propPath.push(tokens.requireTokenType("IDENTIFIER").value) } return { - type: "addCmd", - classRef: classRef, - attributeRef: attributeRef, - to: to, + type: "target", + propPath: propPath, + root: root, transpile: function () { - if (this.classRef) { - return "_hyperscript.runtime.forEach( " + parser.transpile(to) + ", function (target) {" + - " target.classList.add('" + classRef.className() + "')" + - "})"; - } else { - return "_hyperscript.runtime.forEach( " + parser.transpile(to) + ", function (target) {" + - " target.setAttribute('" + attributeRef.name + "', " + parser.transpile(attributeRef) + ".value)" + - "})"; - } + return "_hyperscript.runtime.evalTarget(" + parser.transpile(root) + ", [" + propPath.map(function (prop) { + return "\"" + prop + "\"" + }).join(", ") + "])"; } - } - } - }); + }; + }); - _parser.addGrammarElement("removeCmd", function (parser, tokens) { - if (tokens.matchToken("remove")) { - var classRef = parser.parseElement("classRef", tokens); - var attributeRef = null; - var elementExpr = null; - if (classRef == null) { - attributeRef = parser.parseElement("attributeRef", tokens); - if (attributeRef == null) { - elementExpr = parser.parseElement("expression", tokens) - if (elementExpr == null) { - parser.raiseParseError(tokens, "Expected either a class reference, attribute expression or value expression"); - } + _parser.addGrammarElement("command", function (parser, tokens) { + return parser.parseAnyOf(["onCmd", "addCmd", "removeCmd", "toggleCmd", "waitCmd", "sendCmd", "triggerCmd", + "takeCmd", "logCmd", "callCmd", "putCmd", "setCmd", "ifCmd", "ajaxCmd"], tokens); + }) + + _parser.addGrammarElement("commandList", function (parser, tokens) { + var cmd = parser.parseElement("command", tokens); + if (cmd) { + tokens.matchToken("then"); + cmd.next = parser.parseElement("commandList", tokens); + return cmd; + } + }) + + _parser.addGrammarElement("hyperscript", function (parser, tokens) { + var eventListeners = [] + do { + eventListeners.push(parser.parseElement("eventListener", tokens)); + } while (tokens.matchToken("end") && tokens.hasMore()) + if (tokens.hasMore()) { + parser.raiseParseError(tokens); + } + return { + type: "hyperscript", + eventListeners: eventListeners, + transpile: function () { + return "(function(){\n" + + "var eventListeners = []\n" + + eventListeners.map(function (el) { + return " eventListeners.push(" + parser.transpile(el) + ");\n" + }).join("") + + " function applyEventListenersTo(elt) { _hyperscript.runtime.applyEventListeners(this, elt) }\n" + + " return {eventListeners:eventListeners, applyEventListenersTo:applyEventListenersTo}\n" + + "})()" } + }; + }) + + _parser.addGrammarElement("eventListener", function (parser, tokens) { + tokens.requireToken("on"); + var on = parser.parseElement("dotOrColonPath", tokens); + if (on == null) { + parser.raiseParseError(tokens, "Expected event name") } if (tokens.matchToken("from")) { var from = parser.parseElement("target", tokens); + if (from == null) { + parser.raiseParseError(tokens, "Expected target value") + } } else { - var from = parser.parseElement("implicitMeTarget"); + var from = parser.parseElement("implicitMeTarget", tokens); } - return { - type: "removeCmd", - classRef: classRef, - attributeRef: attributeRef, - elementExpr: elementExpr, + var args = []; + if (tokens.matchOpToken("(")) { + do { + args.push(tokens.requireTokenType('IDENTIFIER')); + } while (tokens.matchOpToken(",")) + tokens.requireOpToken(')') + } + + var start = parser.parseElement("commandList", tokens); + var eventListener = { + type: "eventListener", + on: on, from: from, + start: start, transpile: function () { - if (this.elementExpr) { - return "_hyperscript.runtime.forEach( " + parser.transpile(elementExpr) + ", function (target) {" + - " target.parentElement.removeChild(target)" + - "})"; - } else { + return "(function(me){" + + "var my = me;\n" + + "_hyperscript.runtime.forEach( " + parser.transpile(from) + ", function(target){\n" + + " target.addEventListener('" + parser.transpile(on) + "', function(event){\n" + + args.map(function (arg) { + return "var " + arg.value + " = event.detail." + arg.value + ";" + }).join("\n") + "\n" + + parser.transpile(start) + + " })\n" + + "})\n" + + "})" + } + }; + return eventListener; + }); + + _parser.addGrammarElement("addCmd", function (parser, tokens) { + if (tokens.matchToken("add")) { + var classRef = parser.parseElement("classRef", tokens); + var attributeRef = null; + if (classRef == null) { + attributeRef = parser.parseElement("attributeRef", tokens); + if (attributeRef == null) { + parser.raiseParseError(tokens, "Expected either a class reference or attribute expression") + } + } + + if (tokens.matchToken("to")) { + var to = parser.parseElement("target", tokens); + } else { + var to = parser.parseElement("implicitMeTarget"); + } + + return { + type: "addCmd", + classRef: classRef, + attributeRef: attributeRef, + to: to, + transpile: function () { if (this.classRef) { - return "_hyperscript.runtime.forEach( " + parser.transpile(from) + ", function (target) {" + - " target.classList.remove('" + classRef.className() + "')" + + return "_hyperscript.runtime.forEach( " + parser.transpile(to) + ", function (target) {" + + " target.classList.add('" + classRef.className() + "')" + "})"; } else { - return "_hyperscript.runtime.forEach( " + parser.transpile(from) + ", function (target) {" + - " target.removeAttribute('" + attributeRef.name + "')" + + return "_hyperscript.runtime.forEach( " + parser.transpile(to) + ", function (target) {" + + " target.setAttribute('" + attributeRef.name + "', " + parser.transpile(attributeRef) + ".value)" + "})"; } } } } - } - }); + }); - _parser.addGrammarElement("toggleCmd", function (parser, tokens) { - if (tokens.matchToken("toggle")) { - var classRef = parser.parseElement("classRef", tokens); - var attributeRef = null; - if (classRef == null) { - attributeRef = parser.parseElement("attributeRef", tokens); - if (attributeRef == null) { - parser.raiseParseError(tokens, "Expected either a class reference or attribute expression") + _parser.addGrammarElement("removeCmd", function (parser, tokens) { + if (tokens.matchToken("remove")) { + var classRef = parser.parseElement("classRef", tokens); + var attributeRef = null; + var elementExpr = null; + if (classRef == null) { + attributeRef = parser.parseElement("attributeRef", tokens); + if (attributeRef == null) { + elementExpr = parser.parseElement("expression", tokens) + if (elementExpr == null) { + parser.raiseParseError(tokens, "Expected either a class reference, attribute expression or value expression"); + } + } + } + if (tokens.matchToken("from")) { + var from = parser.parseElement("target", tokens); + } else { + var from = parser.parseElement("implicitMeTarget"); + } + + return { + type: "removeCmd", + classRef: classRef, + attributeRef: attributeRef, + elementExpr: elementExpr, + from: from, + transpile: function () { + if (this.elementExpr) { + return "_hyperscript.runtime.forEach( " + parser.transpile(elementExpr) + ", function (target) {" + + " target.parentElement.removeChild(target)" + + "})"; + } else { + if (this.classRef) { + return "_hyperscript.runtime.forEach( " + parser.transpile(from) + ", function (target) {" + + " target.classList.remove('" + classRef.className() + "')" + + "})"; + } else { + return "_hyperscript.runtime.forEach( " + parser.transpile(from) + ", function (target) {" + + " target.removeAttribute('" + attributeRef.name + "')" + + "})"; + } + } + } } } - if (tokens.matchToken("on")) { - var on = parser.parseElement("target", tokens); - } else { - var on = parser.parseElement("implicitMeTarget"); + }); + + _parser.addGrammarElement("toggleCmd", function (parser, tokens) { + if (tokens.matchToken("toggle")) { + var classRef = parser.parseElement("classRef", tokens); + var attributeRef = null; + if (classRef == null) { + attributeRef = parser.parseElement("attributeRef", tokens); + if (attributeRef == null) { + parser.raiseParseError(tokens, "Expected either a class reference or attribute expression") + } + } + if (tokens.matchToken("on")) { + var on = parser.parseElement("target", tokens); + } else { + var on = parser.parseElement("implicitMeTarget"); + } + return { + type: "toggleCmd", + classRef: classRef, + attributeRef: attributeRef, + on: on, + transpile: function () { + if (this.classRef) { + return "_hyperscript.runtime.forEach( " + parser.transpile(on) + ", function (target) {" + + " target.classList.toggle('" + classRef.className() + "')" + + "})"; + } else { + return "_hyperscript.runtime.forEach( " + parser.transpile(on) + ", function (target) {" + + " if(target.hasAttribute('" + attributeRef.name + "')) {\n" + + " target.removeAttribute('" + attributeRef.name + "');\n" + + " } else { \n" + + " target.setAttribute('" + attributeRef.name + "', " + parser.transpile(attributeRef) + ".value)" + + " }" + + "})"; + } + } + } } - return { - type: "toggleCmd", - classRef: classRef, - attributeRef: attributeRef, - on: on, - transpile: function () { - if (this.classRef) { - return "_hyperscript.runtime.forEach( " + parser.transpile(on) + ", function (target) {" + - " target.classList.toggle('" + classRef.className() + "')" + - "})"; - } else { - return "_hyperscript.runtime.forEach( " + parser.transpile(on) + ", function (target) {" + - " if(target.hasAttribute('" + attributeRef.name + "')) {\n" + - " target.removeAttribute('" + attributeRef.name + "');\n" + - " } else { \n" + - " target.setAttribute('" + attributeRef.name + "', " + parser.transpile(attributeRef) + ".value)" + - " }" + + }) + + _parser.addGrammarElement("waitCmd", function (parser, tokens) { + if (tokens.matchToken("wait")) { + var time = parser.parseElement('millisecondLiteral', tokens); + return { + type: "waitCmd", + time: time, + transpile: function () { + var capturedNext = this.next; + delete this.next; + return "setTimeout(function () { " + parser.transpile(capturedNext) + " }, " + parser.transpile(this.time) + ")"; + } + } + } + }) + + // TODO - colon path needs to eventually become part of ruby-style symbols + _parser.addGrammarElement("dotOrColonPath", function (parser, tokens) { + var root = tokens.matchTokenType("IDENTIFIER"); + if (root) { + var path = [root.value]; + + var separator = tokens.matchOpToken(".") || tokens.matchOpToken(":"); + if (separator) { + do { + path.push(tokens.requireTokenType("IDENTIFIER").value); + } while (tokens.matchOpToken(separator.value)) + } + + return { + type: "dotOrColonPath", + path: path, + transpile: function () { + return path.join(separator ? separator.value : ""); + } + } + } + }); + + _parser.addGrammarElement("sendCmd", function (parser, tokens) { + if (tokens.matchToken("send")) { + + var eventName = parser.parseElement("dotOrColonPath", tokens); + + var details = parser.parseElement("namedArgumentList", tokens); + if (tokens.matchToken("to")) { + var to = parser.parseElement("target", tokens); + } else { + var to = parser.parseElement("implicitMeTarget"); + } + + return { + type: "sendCmd", + eventName: eventName, + details: details, + to: to, + transpile: function () { + return "_hyperscript.runtime.forEach( " + parser.transpile(to) + ", function (target) {" + + " _hyperscript.runtime.triggerEvent(target, '" + parser.transpile(eventName) + "'," + parser.transpile(details, "{}") + ")" + "})"; } } } - } - }) + }) - _parser.addGrammarElement("waitCmd", function (parser, tokens) { - if (tokens.matchToken("wait")) { - var time = parser.parseElement('millisecondLiteral', tokens); - return { - type: "waitCmd", - time: time, - transpile: function () { - var capturedNext = this.next; - delete this.next; - return "setTimeout(function () { " + parser.transpile(capturedNext) + " }, " + parser.transpile(this.time) + ")"; - } - } - } - }) + _parser.addGrammarElement("triggerCmd", function (parser, tokens) { + if (tokens.matchToken("trigger")) { - // TODO - colon path needs to eventually become part of ruby-style symbols - _parser.addGrammarElement("dotOrColonPath", function (parser, tokens) { - var root = tokens.matchTokenType("IDENTIFIER"); - if (root) { - var path = [root.value]; + var eventName = parser.parseElement("dotOrColonPath", tokens); + var details = parser.parseElement("namedArgumentList", tokens); - var separator = tokens.matchOpToken(".") || tokens.matchOpToken(":"); - if (separator) { - do { - path.push(tokens.requireTokenType("IDENTIFIER").value); - } while (tokens.matchOpToken(separator.value)) - } - - return { - type: "dotOrColonPath", - path: path, - transpile: function () { - return path.join(separator ? separator.value : ""); - } - } - } - }); - - _parser.addGrammarElement("sendCmd", function (parser, tokens) { - if (tokens.matchToken("send")) { - - var eventName = parser.parseElement("dotOrColonPath", tokens); - - var details = parser.parseElement("namedArgumentList", tokens); - if (tokens.matchToken("to")) { - var to = parser.parseElement("target", tokens); - } else { - var to = parser.parseElement("implicitMeTarget"); - } - - return { - type: "sendCmd", - eventName: eventName, - details: details, - to: to, - transpile: function () { - return "_hyperscript.runtime.forEach( " + parser.transpile(to) + ", function (target) {" + - " _hyperscript.runtime.triggerEvent(target, '" + parser.transpile(eventName) + "'," + parser.transpile(details, "{}") + ")" + - "})"; - } - } - } - }) - - _parser.addGrammarElement("triggerCmd", function (parser, tokens) { - if (tokens.matchToken("trigger")) { - - var eventName = parser.parseElement("dotOrColonPath", tokens); - var details = parser.parseElement("namedArgumentList", tokens); - - return { - type: "triggerCmd", - eventName: eventName, - details: details, - transpile: function () { - return "_hyperscript.runtime.triggerEvent(me, '" + parser.transpile(eventName) + "'," + parser.transpile(details, "{}") + ");"; - } - } - } - }) - - _parser.addGrammarElement("takeCmd", function (parser, tokens) { - if (tokens.matchToken("take")) { - var classRef = tokens.requireTokenType(tokens, "CLASS_REF"); - - if (tokens.matchToken("from")) { - var from = parser.parseElement("target", tokens); - } else { - var from = parser.parseElement("implicitAllTarget") - } - - if (tokens.matchToken("for")) { - var forElt = parser.parseElement("target", tokens); - } else { - var forElt = parser.parseElement("implicitMeTarget") - } - - return { - type: "takeCmd", - classRef: classRef, - from: from, - forElt: forElt, - transpile: function () { - var clazz = this.classRef.value.substr(1); - return " _hyperscript.runtime.forEach(" + parser.transpile(from) + ", function (target) { target.classList.remove('" + clazz + "') }); " + - "_hyperscript.runtime.forEach( " + parser.transpile(forElt) + ", function (target) {" + - " target.classList.add('" + clazz + "')" + - "})"; - } - } - } - }) - - _parser.addGrammarElement("logCmd", function (parser, tokens) { - if (tokens.matchToken("log")) { - var exprs = [parser.parseElement("expression", tokens)]; - while (tokens.matchOpToken(",")) { - exprs.push(parser.parseElement("expression", tokens)); - } - if (tokens.matchToken("with")) { - var withExpr = parser.parseElement("expression", tokens); - } - return { - type: "logCmd", - exprs: exprs, - withExpr: withExpr, - transpile: function () { - if (withExpr) { - return parser.transpile(withExpr) + "(" + exprs.map(function (expr) { - return parser.transpile(expr) - }).join(", ") + ")"; - } else { - return "console.log(" + exprs.map(function (expr) { - return parser.transpile(expr) - }).join(", ") + ")"; + return { + type: "triggerCmd", + eventName: eventName, + details: details, + transpile: function () { + return "_hyperscript.runtime.triggerEvent(me, '" + parser.transpile(eventName) + "'," + parser.transpile(details, "{}") + ");"; } } - }; - } - }) + } + }) - _parser.addGrammarElement("callCmd", function (parser, tokens) { - if (tokens.matchToken("call") || tokens.matchToken("get")) { - return { - type: "callCmd", - expr: parser.parseElement("expression", tokens), - transpile: function () { - return "var it = " + parser.transpile(this.expr); + _parser.addGrammarElement("takeCmd", function (parser, tokens) { + if (tokens.matchToken("take")) { + var classRef = tokens.requireTokenType(tokens, "CLASS_REF"); + + if (tokens.matchToken("from")) { + var from = parser.parseElement("target", tokens); + } else { + var from = parser.parseElement("implicitAllTarget") + } + + if (tokens.matchToken("for")) { + var forElt = parser.parseElement("target", tokens); + } else { + var forElt = parser.parseElement("implicitMeTarget") + } + + return { + type: "takeCmd", + classRef: classRef, + from: from, + forElt: forElt, + transpile: function () { + var clazz = this.classRef.value.substr(1); + return " _hyperscript.runtime.forEach(" + parser.transpile(from) + ", function (target) { target.classList.remove('" + clazz + "') }); " + + "_hyperscript.runtime.forEach( " + parser.transpile(forElt) + ", function (target) {" + + " target.classList.add('" + clazz + "')" + + "})"; + } } } - } - }) + }) - _parser.addGrammarElement("putCmd", function (parser, tokens) { - if (tokens.matchToken("put")) { - - var value = parser.parseElement("expression", tokens); - - var operation = tokens.matchToken("into") || - tokens.matchToken("before") || - tokens.matchToken("after"); - - if (operation == null && tokens.matchToken("at")) { - operation = tokens.matchToken("start") || - tokens.matchToken("end"); - tokens.requireToken("of"); + _parser.addGrammarElement("logCmd", function (parser, tokens) { + if (tokens.matchToken("log")) { + var exprs = [parser.parseElement("expression", tokens)]; + while (tokens.matchOpToken(",")) { + exprs.push(parser.parseElement("expression", tokens)); + } + if (tokens.matchToken("with")) { + var withExpr = parser.parseElement("expression", tokens); + } + return { + type: "logCmd", + exprs: exprs, + withExpr: withExpr, + transpile: function () { + if (withExpr) { + return parser.transpile(withExpr) + "(" + exprs.map(function (expr) { + return parser.transpile(expr) + }).join(", ") + ")"; + } else { + return "console.log(" + exprs.map(function (expr) { + return parser.transpile(expr) + }).join(", ") + ")"; + } + } + }; } + }) - if (operation == null) { - parser.raiseParseError(tokens, "Expected one of 'into', 'before', 'at start of', 'at end of', 'after'"); + _parser.addGrammarElement("callCmd", function (parser, tokens) { + if (tokens.matchToken("call") || tokens.matchToken("get")) { + return { + type: "callCmd", + expr: parser.parseElement("expression", tokens), + transpile: function () { + return "var it = " + parser.transpile(this.expr); + } + } } - var target = parser.parseElement("target", tokens); + }) - var directWrite = target.propPath.length === 0 && operation.value === "into"; - var symbolWrite = directWrite && target.root.type === "symbol"; - if (directWrite && !symbolWrite) { - parser.raiseParseError(tokens, "Can only put directly into symbols, not references") + _parser.addGrammarElement("putCmd", function (parser, tokens) { + if (tokens.matchToken("put")) { + + var value = parser.parseElement("expression", tokens); + + var operation = tokens.matchToken("into") || + tokens.matchToken("before") || + tokens.matchToken("after"); + + if (operation == null && tokens.matchToken("at")) { + operation = tokens.matchToken("start") || + tokens.matchToken("end"); + tokens.requireToken("of"); + } + + if (operation == null) { + parser.raiseParseError(tokens, "Expected one of 'into', 'before', 'at start of', 'at end of', 'after'"); + } + var target = parser.parseElement("target", tokens); + + var directWrite = target.propPath.length === 0 && operation.value === "into"; + var symbolWrite = directWrite && target.root.type === "symbol"; + if (directWrite && !symbolWrite) { + parser.raiseParseError(tokens, "Can only put directly into symbols, not references") + } + + return { + type: "putCmd", + target: target, + op: operation.value, + symbolWrite: symbolWrite, + value: value, + transpile: function () { + if (this.symbolWrite) { + return "var " + target.root.name + " = " + parser.transpile(value); + } else { + if (this.op === "into") { + var lastProperty = target.propPath.pop(); // steal last property for assignment + return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" + + " target." + lastProperty + "=" + parser.transpile(value) + + "})"; + } else if (this.op === "before") { + return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" + + " target.insertAdjacentHTML('beforebegin', " + parser.transpile(value) + ")" + + "})"; + } else if (this.op === "start") { + return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" + + " target.insertAdjacentHTML('afterbegin', " + parser.transpile(value) + ")" + + "})"; + } else if (this.op === "end") { + return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" + + " target.insertAdjacentHTML('beforeend', " + parser.transpile(value) + ")" + + "})"; + } else if (this.op === "after") { + return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" + + " target.insertAdjacentHTML('afterend', " + parser.transpile(value) + ")" + + "})"; + } + } + } + } } + }) - return { - type: "putCmd", - target: target, - op: operation.value, - symbolWrite: symbolWrite, - value: value, - transpile: function () { - if (this.symbolWrite) { - return "var " + target.root.name + " = " + parser.transpile(value); - } else { - if (this.op === "into") { + _parser.addGrammarElement("setCmd", function (parser, tokens) { + if (tokens.matchToken("set")) { + + var target = parser.parseElement("target", tokens); + + tokens.requireToken("to"); + + var value = parser.parseElement("expression", tokens); + + var directWrite = target.propPath.length === 0; + var symbolWrite = directWrite && target.root.type === "symbol"; + if (directWrite && !symbolWrite) { + parser.raiseParseError(tokens, "Can only put directly into symbols, not references") + } + + return { + type: "setCmd", + target: target, + symbolWrite: symbolWrite, + value: value, + transpile: function () { + if (this.symbolWrite) { + return "var " + target.root.name + " = " + parser.transpile(value); + } else { var lastProperty = target.propPath.pop(); // steal last property for assignment return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" + " target." + lastProperty + "=" + parser.transpile(value) + "})"; - } else if (this.op === "before") { - return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" + - " target.insertAdjacentHTML('beforebegin', " + parser.transpile(value) + ")" + - "})"; - } else if (this.op === "start") { - return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" + - " target.insertAdjacentHTML('afterbegin', " + parser.transpile(value) + ")" + - "})"; - } else if (this.op === "end") { - return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" + - " target.insertAdjacentHTML('beforeend', " + parser.transpile(value) + ")" + - "})"; - } else if (this.op === "after") { - return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" + - " target.insertAdjacentHTML('afterend', " + parser.transpile(value) + ")" + - "})"; } } } } - } - }) + }) - _parser.addGrammarElement("setCmd", function (parser, tokens) { - if (tokens.matchToken("set")) { + _parser.addGrammarElement("ifCmd", function (parser, tokens) { + if (tokens.matchToken("if")) { + var expr = parser.parseElement("expression", tokens); + tokens.matchToken("then"); // optional 'then' + var trueBranch = parser.parseElement("commandList", tokens); + if (tokens.matchToken("else")) { + var falseBranch = parser.parseElement("commandList", tokens); + } + if (tokens.hasMore()) { + tokens.requireToken("end"); + } + return { + type: "ifCmd", + expr: expr, + trueBranch: trueBranch, + falseBranch: falseBranch, + transpile: function () { + return "if(" + parser.transpile(expr) + "){" + "" + parser.transpile(trueBranch) + "}" + + " else {" + parser.transpile(falseBranch, "") + "}" - var target = parser.parseElement("target", tokens); - - tokens.requireToken("to"); - - var value = parser.parseElement("expression", tokens); - - var directWrite = target.propPath.length === 0; - var symbolWrite = directWrite && target.root.type === "symbol"; - if (directWrite && !symbolWrite) { - parser.raiseParseError(tokens, "Can only put directly into symbols, not references") - } - - return { - type: "setCmd", - target: target, - symbolWrite: symbolWrite, - value: value, - transpile: function () { - if (this.symbolWrite) { - return "var " + target.root.name + " = " + parser.transpile(value); - } else { - var lastProperty = target.propPath.pop(); // steal last property for assignment - return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" + - " target." + lastProperty + "=" + parser.transpile(value) + - "})"; } } } - } - }) - - _parser.addGrammarElement("ifCmd", function (parser, tokens) { - if (tokens.matchToken("if")) { - var expr = parser.parseElement("expression", tokens); - tokens.matchToken("then"); // optional 'then' - var trueBranch = parser.parseElement("commandList", tokens); - if (tokens.matchToken("else")) { - var falseBranch = parser.parseElement("commandList", tokens); - } - if (tokens.hasMore()) { - tokens.requireToken("end"); - } - return { - type: "ifCmd", - expr: expr, - trueBranch: trueBranch, - falseBranch: falseBranch, - transpile: function () { - return "if(" + parser.transpile(expr) + "){" + "" + parser.transpile(trueBranch) + "}" + - " else {" + parser.transpile(falseBranch, "") + "}" + }) + _parser.addGrammarElement("ajaxCmd", function (parser, tokens) { + if (tokens.matchToken("ajax")) { + var method = tokens.matchToken("GET") || tokens.matchToken("POST"); + if (method == null) { + parser.raiseParseError(tokens, "Requires either GET or POST"); } - } - } - }) - - _parser.addGrammarElement("ajaxCmd", function (parser, tokens) { - if (tokens.matchToken("ajax")) { - var method = tokens.matchToken("GET") || tokens.matchToken("POST"); - if (method == null) { - parser.raiseParseError(tokens, "Requires either GET or POST"); - } - if (method.value !== "GET") { - if (!tokens.matchToken("to")) { - var data = parser.parseElement("expression", tokens); - tokens.requireToken("to"); + if (method.value !== "GET") { + if (!tokens.matchToken("to")) { + var data = parser.parseElement("expression", tokens); + tokens.requireToken("to"); + } } - } - var url = parser.parseElement("string", tokens); - if (url == null) { - var url = parser.parseElement("nakedString", tokens); - } - - return { - type: "requestCommand", - method: method, - transpile: function () { - var capturedNext = this.next; - delete this.next; - return "_hyperscript.runtime.ajax('" + method.value + "', " + - parser.transpile(url) + ", " + - "function(response, xhr){ " + parser.transpile(capturedNext) + " }," + - parser.transpile(data, "null") + ")"; + var url = parser.parseElement("string", tokens); + if (url == null) { + var url = parser.parseElement("nakedString", tokens); } - }; - } - }) - //----------------------------------------------- - // API - //----------------------------------------------- - - function start(scriptAttrs) { - if (scriptAttrs) { - _runtime.setScriptAttrs(scriptAttrs); - } - var fn = function () { - var elements = document.querySelectorAll(_runtime.getScriptSelector()); - _runtime.forEach(elements, function (elt) { - init(elt); - }) - }; - if (document.readyState !== 'loading') { - fn(); - } else { - document.addEventListener('DOMContentLoaded', fn); - } - return true; + return { + type: "requestCommand", + method: method, + transpile: function () { + var capturedNext = this.next; + delete this.next; + return "_hyperscript.runtime.ajax('" + method.value + "', " + + parser.transpile(url) + ", " + + "function(response, xhr){ " + parser.transpile(capturedNext) + " }," + + parser.transpile(data, "null") + ")"; + } + }; + } + }) } - function init(elt) { - _runtime.initElement(elt); + //==================================================================== + // API + //==================================================================== + + function processNode(elt) { + _runtime.processNode(elt); } function evaluate(str) { return _runtime.evaluate(str); } - return { + //==================================================================== + // Initialization + //==================================================================== + function ready(fn) { + if (document.readyState !== 'loading') { + fn(); + } else { + document.addEventListener('DOMContentLoaded', fn); + } + } + + function getMetaConfig() { + var element = document.querySelector('meta[name="htmx-config"]'); + if (element) { + return parseJSON(element.content); + } else { + return null; + } + } + + function mergeMetaConfig() { + var metaConfig = getMetaConfig(); + if (metaConfig) { + _hyperscript.config = mergeObjects(_hyperscript.config , metaConfig) + } + } + + var _hyperscript = { lexer: _lexer, parser: _parser, runtime: _runtime, evaluate: evaluate, - init: init, - start: start - } + processNode: processNode, + config: { + attributes : "_, script, data-script" + } + }; + + ready(function () { + mergeMetaConfig(); + processNode(document.body); + document.addEventListener("htmx:load", function(evt){ + processNode(evt.detail.elt); + }) + }) + + return _hyperscript; } )() })); \ No newline at end of file diff --git a/www/test/0.1.0/test/manual/scroll-test-eventHandler.html b/www/test/0.1.0/test/manual/scroll-test-eventHandler.html new file mode 100644 index 00000000..9930d97c --- /dev/null +++ b/www/test/0.1.0/test/manual/scroll-test-eventHandler.html @@ -0,0 +1,107 @@ + + + + Test Scroll Event Handler + + + + + + + + + + +

    Scrolling Event Handler Tests

    +

    You should be able to scroll this page at any speed and see HTML fragments loaded into the DIVs "remotely" without any hiccups.

    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + diff --git a/www/test/0.1.0/test/manual/scroll-test-startEnd.html b/www/test/0.1.0/test/manual/scroll-test-startEnd.html new file mode 100644 index 00000000..43b69c33 --- /dev/null +++ b/www/test/0.1.0/test/manual/scroll-test-startEnd.html @@ -0,0 +1,29 @@ + + + + Test Scroll Behavior + + + + + + + +

    Scrolling Start/End Tests

    + +

    End

    +
    + Click To Add Content... +
    +
    +

    Start

    +
    + Click To Add Content... +
    + + + diff --git a/www/test/0.1.0/test/manual/sse-multichannel.html b/www/test/0.1.0/test/manual/sse-multichannel.html new file mode 100644 index 00000000..0308183c --- /dev/null +++ b/www/test/0.1.0/test/manual/sse-multichannel.html @@ -0,0 +1,41 @@ + + + + + + + + + +
    +

    Multiple Listeners. message only

    +
    Waiting for Posts in Event1 channel...
    +
    Waiting for Posts in Event2 channel...
    +
    Waiting for Posts in Event3 channel...
    +
    Waiting for Posts in Event4 channel...
    +
    + + \ No newline at end of file diff --git a/www/test/0.1.0/test/manual/sse-settle.html b/www/test/0.1.0/test/manual/sse-settle.html new file mode 100644 index 00000000..1130bd29 --- /dev/null +++ b/www/test/0.1.0/test/manual/sse-settle.html @@ -0,0 +1,49 @@ + + + + + + + + + +
    +

    Settling Options

    +
    +
    Waiting for Comments...
    +
    Waiting for Comments...
    +
    Waiting for Comments...
    +
    Waiting for Comments...
    +
    Waiting for Comments...
    +
    +
    + + \ No newline at end of file diff --git a/www/test/0.1.0/test/manual/sse.html b/www/test/0.1.0/test/manual/sse.html new file mode 100644 index 00000000..76c2b739 --- /dev/null +++ b/www/test/0.1.0/test/manual/sse.html @@ -0,0 +1,43 @@ + + + + + + + + + +
    +

    Multiple Listeners. message only

    +
    Waiting for Posts...
    +
    Waiting for Comments...
    +
    Waiting for Albums...
    +
    Waiting for ToDos...
    +
    Waiting for Users...
    +
    + + \ No newline at end of file