hook in tokenizer implementation

This commit is contained in:
carson 2020-10-04 18:26:17 -06:00
parent 586829e097
commit 18220b3283
3 changed files with 106 additions and 61 deletions

View File

@ -366,7 +366,7 @@ return (function () {
} else if (targetStr.indexOf("closest ") === 0) {
return closest(elt, targetStr.substr(8));
} else if (targetStr.indexOf("find ") === 0) {
return find(elt, targetStr.substr(5));
return find(elt, targetStr.substr(5));
} else {
return getDocument().querySelector(targetStr);
}
@ -642,6 +642,7 @@ return (function () {
var SYMBOL_START = /[_$a-zA-Z]/;
var SYMBOL_CONT = /[_$a-zA-Z0-9]/;
var STRINGISH_START = ['"', "'", "/"];
var NOT_WHITESPACE = /[^\s]/;
function tokenizeString(str) {
var tokens = [];
var position = 0;
@ -673,19 +674,20 @@ return (function () {
return tokens;
}
function isPossibleRelativeReference(token, last) {
return SYMBOL_START.exec(token.charAt(0)) &&
token !== "true" &&
token !== "false" &&
token !== "this" &&
last !== ".";
}
function isPossibleRelativeReference(token, last, paramName) {
return SYMBOL_START.exec(token.charAt(0)) &&
token !== "true" &&
token !== "false" &&
token !== "this" &&
token !== paramName &&
last !== ".";
}
function maybeGenerateConditional(tokens) {
function maybeGenerateConditional(tokens, paramName) {
if (tokens[0] === '[') {
tokens.shift();
var bracketCount = 1;
var conditional = "(function(__val){ return (";
var conditional = "(function(" + paramName + "){ return (";
var last = null;
while (tokens.length > 0) {
var token = tokens[0];
@ -696,13 +698,18 @@ return (function () {
conditional = conditional + "true";
}
tokens.shift();
return conditional + ")})";
conditional += ")})";
try {
return eval(conditional);
} catch (e) {
triggerErrorEvent(getDocument(), "htmx:syntax:error", {error:e})
}
}
} else if (token === "[") {
bracketCount++;
}
if (isPossibleRelativeReference(token, last)) {
conditional = conditional += "((__val." + token + ") ? (__val." + token + ") : (" + token + "))";
if (isPossibleRelativeReference(token, last, paramName)) {
conditional += "((" + paramName + "." + token + ") ? (" + paramName + "." + token + ") : (window." + token + "))";
} else {
conditional = conditional + token;
}
@ -711,49 +718,73 @@ return (function () {
}
}
function consumeUntil(tokens, match) {
var result = "";
while (tokens.length > 0 && !tokens[0].match(match)) {
result += tokens.shift();
}
return result;
}
function getTriggerSpecs(elt) {
var explicitTrigger = getAttributeValue(elt, 'hx-trigger');
var triggerSpecs = [];
if (explicitTrigger) {
var triggerSpecs = explicitTrigger.split(',').map(function(triggerString) {
var tokens = splitOnWhitespace(triggerString.trim());
var trigger = tokens[0]; // splitOnWhitespace returns at least one element
if (!trigger)
return null;
if (trigger === "every")
return {trigger: 'every', pollInterval: parseInterval(tokens[1])};
if (trigger.indexOf("sse:") === 0)
return {trigger: 'sse', sseEvent: trigger.substr(4)};
var triggerSpec = {trigger: trigger};
for (var i = 1; i < tokens.length; i++) {
var token = tokens[i].trim();
if (token === "changed") {
triggerSpec.changed = true;
}
if (token === "once") {
triggerSpec.once = true;
}
if (token.indexOf("delay:") === 0) {
triggerSpec.delay = parseInterval(token.substr(6));
}
if (token.indexOf("throttle:") === 0) {
triggerSpec.throttle = parseInterval(token.substr(9));
var tokens = tokenizeString(explicitTrigger);
do {
consumeUntil(tokens, NOT_WHITESPACE);
var initialLength = tokens.length;
var trigger = consumeUntil(tokens, /[,\[\s]/);
if (trigger !== "") {
if (trigger === "every") {
var every = {trigger: 'every'};
consumeUntil(tokens, NOT_WHITESPACE);
every.pollInterval = parseInterval(consumeUntil(tokens, WHITESPACE));
triggerSpecs.push(every);
} else if (trigger.indexOf("sse:") === 0) {
triggerSpecs.push({trigger: 'sse', sseEvent: trigger.substr(4)});
} else {
var triggerSpec = {trigger: trigger};
var eventFilter = maybeGenerateConditional(tokens, "evt");
if (eventFilter) {
triggerSpec.eventFilter = eventFilter;
}
while (tokens.length > 0 && tokens[0] !== ",") {
consumeUntil(tokens, NOT_WHITESPACE)
var token = tokens.shift();
if (token === "changed") {
triggerSpec.changed = true;
} else if (token === "once") {
triggerSpec.once = true;
} else if (token === "delay" && tokens[0] === ":") {
tokens.shift();
triggerSpec.delay = parseInterval(consumeUntil(tokens, WHITESPACE));
} else if (token === "throttle" && tokens[0] === ":") {
tokens.shift();
triggerSpec.throttle = parseInterval(consumeUntil(tokens, WHITESPACE));
} else {
triggerErrorEvent(elt, "htmx:syntax:error", {token:tokens.shift()});
}
}
triggerSpecs.push(triggerSpec);
}
}
return triggerSpec;
}).filter(function(x){ return x !== null });
if (triggerSpecs.length)
return triggerSpecs;
if (tokens.length === initialLength) {
triggerErrorEvent(elt, "htmx:syntax:error", {token:tokens.shift()});
}
consumeUntil(tokens, NOT_WHITESPACE);
} while (tokens[0] === "," && tokens.shift())
}
if (matches(elt, 'form'))
if (triggerSpecs.length > 0) {
return triggerSpecs;
} else if (matches(elt, 'form')) {
return [{trigger: 'submit'}];
if (matches(elt, 'input, textarea, select'))
} else if (matches(elt, 'input, textarea, select')) {
return [{trigger: 'change'}];
return [{trigger: 'click'}];
} else {
return [{trigger: 'click'}];
}
}
function cancelPolling(elt) {
@ -806,6 +837,10 @@ return (function () {
function addEventListener(elt, verb, path, nodeData, triggerSpec, explicitCancel) {
var eventListener = function (evt) {
if (triggerSpec.eventFilter &&
triggerSpec.eventFilter(evt) !== true) {
return;
}
if (ignoreBoostedAnchorCtrlClick(elt, evt)) {
return;
}
@ -972,7 +1007,7 @@ return (function () {
if (value[0] === "connect") {
processSSESource(elt, value[1]);
}
if ((value[0] === "swap")) {
processSSESwap(elt, value[1])
}
@ -1000,12 +1035,12 @@ return (function () {
///////////////////////////
// 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);

View File

@ -89,7 +89,6 @@ describe("hx-trigger attribute", function(){
it('non-default value works w/ data-* prefix', function()
{
this.server.respondWith("GET", "/test", "Clicked!");
var form = make('<form data-hx-get="/test" data-hx-trigger="click">Click Me!</form>');
form.click();
form.innerHTML.should.equal("Click Me!");
@ -113,8 +112,6 @@ describe("hx-trigger attribute", function(){
div.innerHTML.should.equal("Requests: 2");
});
it("parses spec strings", function()
{
var specExamples = {
@ -131,9 +128,7 @@ describe("hx-trigger attribute", function(){
"event1, event2": [{trigger: 'event1'}, {trigger: 'event2'}],
"event1 once, event2 changed": [{trigger: 'event1', once: true}, {trigger: 'event2', changed: true}],
"event1,": [{trigger: 'event1'}],
",event1": [{trigger: 'event1'}],
" ": [{trigger: 'click'}],
",": [{trigger: 'click'}]
}
for (var specString in specExamples) {
@ -157,4 +152,19 @@ describe("hx-trigger attribute", function(){
spec.should.deep.equal([{trigger: 'change'}]);
})
it('filters properly with filter spec', function(){
this.server.respondWith("GET", "/test", "Called!");
var form = make('<form hx-get="/test" hx-trigger="evt[foo]">Not Called</form>');
form.click();
form.innerHTML.should.equal("Not Called");
var event = htmx._("makeEvent")('evt');
form.dispatchEvent(event);
this.server.respond();
form.innerHTML.should.equal("Not Called");
event.foo = true;
form.dispatchEvent(event);
this.server.respond();
form.innerHTML.should.equal("Called!");
})
})

View File

@ -19,16 +19,16 @@ describe("Core htmx tokenizer tests", function(){
it('tokenizes properly', function()
{
tokenizeTest("", []);
tokenizeTest(" ", []);
tokenizeTest(" ", [" ", " "]);
tokenizeTest("(", ["("]);
tokenizeTest("()", ["(", ")"]);
tokenizeTest("(,)", ["(", ",", ")"]);
tokenizeTest(" ( ) ", ["(", ")"]);
tokenizeTest(" && ) ", ["&&", ")"]);
tokenizeTest(" && ) 'asdf'", ["&&", ")", "'asdf'"]);
tokenizeTest(" && ) ',asdf'", ["&&", ")", "',asdf'"]);
tokenizeTest(" ( ) ", [" ", "(", " ", ")", " "]);
tokenizeTest(" && ) ", [" ", "&", "&", " ", ")", " "]);
tokenizeTest(" && ) 'asdf'", [" ", "&", "&", " ", ")", " ", "'asdf'"]);
tokenizeTest(" && ) ',asdf'", [" ", "&", "&", " ", ")", " ", "',asdf'"]);
tokenizeTest('",asdf"', ['",asdf"']);
tokenizeTest('&& ) ",asdf"', ["&&", ")", '",asdf"']);
tokenizeTest('&& ) ",asdf"', ["&", "&", " ", ")", " ", '",asdf"']);
});
it('generates conditionals property', function()