* Implement `hx-vals` which is a JSON-only (and therefore safer) version of `hx-vars`
* Port all internal evals to use `Function` instead

May fix https://github.com/bigskysoftware/htmx/issues/213
This commit is contained in:
carson 2020-11-15 17:54:54 -07:00
parent 2209c4bf6f
commit 5acd554a5c
7 changed files with 197 additions and 29 deletions

99
dist/htmx.js vendored
View File

@ -161,6 +161,8 @@ return (function () {
case "td":
case "th":
return parseHTML("<table><tbody><tr>" + resp + "</tr></tbody></table>", 3);
case "script":
return parseHTML("<div>" + resp + "</div>", 1);
default:
return parseHTML(resp, 0);
}
@ -610,7 +612,19 @@ return (function () {
}
}
var TITLE_FINDER = /<title>([\s\S]+?)<\/title>/im;
function findTitle(content) {
var result = TITLE_FINDER.exec(content);
if (result) {
return result[1];
}
}
function selectAndSwap(swapStyle, target, elt, responseText, settleInfo) {
var title = findTitle(responseText);
if(title) {
window.document.title = title;
}
var fragment = makeFragment(responseText);
if (fragment) {
handleOutOfBandSwaps(fragment, settleInfo);
@ -686,7 +700,7 @@ return (function () {
if (tokens[0] === '[') {
tokens.shift();
var bracketCount = 1;
var conditionalSource = "(function(" + paramName + "){ return (";
var conditionalSource = " return (function(" + paramName + "){ return (";
var last = null;
while (tokens.length > 0) {
var token = tokens[0];
@ -699,7 +713,7 @@ return (function () {
tokens.shift();
conditionalSource += ")})";
try {
var conditionFunction = eval(conditionalSource);
var conditionFunction = Function(conditionalSource)();
conditionFunction.source = conditionalSource;
return conditionFunction;
} catch (e) {
@ -993,9 +1007,11 @@ return (function () {
elt.addEventListener(getTriggerSpecs(elt)[0].trigger, function (evt) {
var headers = getHeaders(elt, webSocketSourceElt, null, elt);
var results = getInputValues(elt, 'post');
var rawParameters = results.values;
var errors = results.errors;
var filteredParameters = filterValues(rawParameters, elt);
var rawParameters = results.values;
var expressionVars = getHXVarsForElement(elt);
var allParameters = mergeObjects(rawParameters, expressionVars);
var filteredParameters = filterValues(allParameters, elt);
filteredParameters['HEADERS'] = headers;
if (errors && errors.length > 0) {
triggerEvent(elt, 'htmx:validation:halted', errors);
@ -1165,9 +1181,14 @@ return (function () {
});
}
function isBoosted() {
return document.querySelector("[hx-boost], [data-hx-boost]");
}
function findElementsToProcess(elt) {
if (elt.querySelectorAll) {
var results = elt.querySelectorAll(VERB_SELECTOR + ", a, form, [hx-sse], [data-hx-sse], [hx-ws]," +
var boostedElts = isBoosted() ? ", a, form" : "";
var results = elt.querySelectorAll(VERB_SELECTOR + boostedElts + ", [hx-sse], [data-hx-sse], [hx-ws]," +
" [data-hx-ws]");
return results;
} else {
@ -1698,22 +1719,41 @@ return (function () {
}
}
function addExpressionVars(elt, rawParameters) {
if (elt == null) {
return;
function getValuesForElement(elt, attr, strToValues, expressionVars) {
if (expressionVars == null) {
expressionVars = {};
}
var attributeValue = getAttributeValue(elt, "hx-vars");
if (elt == null) {
return expressionVars;
}
var attributeValue = getAttributeValue(elt, attr);
if (attributeValue) {
var varsValues = eval("({" + attributeValue + "})");
var str = attributeValue.trim();
if (str.indexOf('{') !== 0) {
str = "{" + str + "}";
}
var varsValues = strToValues(str);
for (var key in varsValues) {
if (varsValues.hasOwnProperty(key)) {
if (rawParameters[key] == null) {
rawParameters[key] = varsValues[key];
if (expressionVars[key] == null) {
expressionVars[key] = varsValues[key];
}
}
}
}
addExpressionVars(parentElt(elt), rawParameters);
return getValuesForElement(parentElt(elt), attr, strToValues, expressionVars);
}
function getHXVarsForElement(elt, expressionVars) {
return getValuesForElement(elt, "hx-vars", function(valueStr){
return Function("return (" + valueStr + ")")()
}, expressionVars);
}
function getHXValsForElement(elt, expressionVars) {
return getValuesForElement(elt, "hx-vars", function(valueStr){
return JSON.parse(expressionVars);
}, expressionVars);
}
function safelySetHeaderValue(xhr, header, headerValue) {
@ -1745,6 +1785,10 @@ return (function () {
}
function issueAjaxRequest(elt, verb, path, eventTarget, triggeringEvent) {
if (!bodyContains(elt)) {
console.log("Body does not contain", elt);
return; // do not issue requests for elements removed from the DOM
}
var target = getTarget(elt);
if (target == null) {
triggerErrorEvent(elt, 'htmx:targetError', {target: getAttributeValue(elt, "hx-target")});
@ -1783,10 +1827,11 @@ return (function () {
var headers = getHeaders(elt, target, promptResponse, eventTarget);
var results = getInputValues(elt, verb);
var rawParameters = results.values;
var errors = results.errors;
addExpressionVars(elt, rawParameters);
var filteredParameters = filterValues(rawParameters, elt);
var rawParameters = results.values;
var expressionVars = getHXVarsForElement(elt);
var allParameters = mergeObjects(rawParameters, expressionVars);
var filteredParameters = filterValues(allParameters, elt);
if (verb !== 'get' && getClosestAttributeValue(elt, "hx-encoding") == null) {
headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
@ -1799,7 +1844,7 @@ return (function () {
var requestConfig = {
parameters: filteredParameters,
unfilteredParameters:rawParameters,
unfilteredParameters: allParameters,
headers:headers,
target:target,
verb:verb,
@ -1863,7 +1908,19 @@ return (function () {
}
if (hasHeader(xhr,/HX-Push:/i)) {
var pushedUrl = this.getResponseHeader("HX-Push");
var pushedUrl = xhr.getResponseHeader("HX-Push");
}
if (hasHeader(xhr, /HX-Redirect:/i)) {
window.location.href = xhr.getResponseHeader("HX-Redirect");
return;
}
if (hasHeader(xhr,/HX-Refresh:/i)) {
if ("true" === xhr.getResponseHeader("HX-Refresh")) {
location.reload();
return;
}
}
var shouldSaveHistory = shouldPush(elt) || pushedUrl;
@ -1876,9 +1933,9 @@ return (function () {
if (this.status !== 204) {
if (!triggerEvent(target, 'htmx:beforeSwap', eventDetail)) return;
var resp = this.response;
var serverResponse = this.response;
withExtensions(elt, function(extension){
resp = extension.transformResponse(resp, xhr, elt);
serverResponse = extension.transformResponse(serverResponse, xhr, elt);
});
// Save current page
@ -1900,7 +1957,7 @@ return (function () {
};
var settleInfo = makeSettleInfo(target);
selectAndSwap(swapSpec.swapStyle, target, elt, resp, settleInfo);
selectAndSwap(swapSpec.swapStyle, target, elt, serverResponse, settleInfo);
if (selectionInfo.elt &&
!bodyContains(selectionInfo.elt) &&

2
dist/htmx.min.js vendored

File diff suppressed because one or more lines are too long

BIN
dist/htmx.min.js.gz vendored

Binary file not shown.

View File

@ -700,7 +700,7 @@ return (function () {
if (tokens[0] === '[') {
tokens.shift();
var bracketCount = 1;
var conditionalSource = "(function(" + paramName + "){ return (";
var conditionalSource = " return (function(" + paramName + "){ return (";
var last = null;
while (tokens.length > 0) {
var token = tokens[0];
@ -713,7 +713,7 @@ return (function () {
tokens.shift();
conditionalSource += ")})";
try {
var conditionFunction = eval(conditionalSource);
var conditionFunction = Function(conditionalSource)();
conditionFunction.source = conditionalSource;
return conditionFunction;
} catch (e) {
@ -1719,13 +1719,20 @@ return (function () {
}
}
function getExpressionVars(elt, expressionVars = []) {
function getValuesForElement(elt, attr, strToValues, expressionVars) {
if (expressionVars == null) {
expressionVars = {};
}
if (elt == null) {
return expressionVars;
}
var attributeValue = getAttributeValue(elt, "hx-vars");
var attributeValue = getAttributeValue(elt, attr);
if (attributeValue) {
var varsValues = eval("({" + attributeValue + "})");
var str = attributeValue.trim();
if (str.indexOf('{') !== 0) {
str = "{" + str + "}";
}
var varsValues = strToValues(str);
for (var key in varsValues) {
if (varsValues.hasOwnProperty(key)) {
if (expressionVars[key] == null) {
@ -1734,7 +1741,23 @@ return (function () {
}
}
}
return getExpressionVars(parentElt(elt), expressionVars);
return getValuesForElement(parentElt(elt), attr, strToValues, expressionVars);
}
function getHXVarsForElement(elt, expressionVars) {
return getValuesForElement(elt, "hx-vars", function(valueStr){
return Function("return (" + valueStr + ")")()
}, expressionVars);
}
function getHXValsForElement(elt, expressionVars) {
return getValuesForElement(elt, "hx-vals", function(valueStr){
return JSON.parse(valueStr);
}, expressionVars);
}
function getExpressionVars(elt) {
return mergeObjects(getHXVarsForElement(elt), getHXValsForElement(elt));
}
function safelySetHeaderValue(xhr, header, headerValue) {

View File

@ -0,0 +1,87 @@
describe("hx-vals attribute", function() {
beforeEach(function () {
this.server = makeServer();
clearWorkArea();
});
afterEach(function () {
this.server.restore();
clearWorkArea();
});
it('basic hx-vals works', function () {
this.server.respondWith("POST", "/vars", function (xhr) {
var params = getParameters(xhr);
params['i1'].should.equal("test");
xhr.respond(200, {}, "Clicked!")
});
var div = make("<div hx-post='/vars' hx-vals='\"i1\":\"test\"'></div>")
div.click();
this.server.respond();
div.innerHTML.should.equal("Clicked!");
});
it('multiple hx-vals works', function () {
this.server.respondWith("POST", "/vars", function (xhr) {
var params = getParameters(xhr);
params['v1'].should.equal("test");
params['v2'].should.equal("42");
xhr.respond(200, {}, "Clicked!")
});
var div = make("<div hx-post='/vars' hx-vals='\"v1\":\"test\", \"v2\":42'></div>")
div.click();
this.server.respond();
div.innerHTML.should.equal("Clicked!");
});
it('hx-vals can be on parents', function () {
this.server.respondWith("POST", "/vars", function (xhr) {
var params = getParameters(xhr);
params['i1'].should.equal("test");
xhr.respond(200, {}, "Clicked!")
});
make("<div hx-vals='\"i1\":\"test\"'><div id='d1' hx-post='/vars'></div></div>");
var div = byId("d1");
div.click();
this.server.respond();
div.innerHTML.should.equal("Clicked!");
});
it('hx-vals can override parents', function () {
this.server.respondWith("POST", "/vars", function (xhr) {
var params = getParameters(xhr);
params['i1'].should.equal("best");
xhr.respond(200, {}, "Clicked!")
});
make("<div hx-vals='\"i1\":\"test\"'><div id='d1' hx-vals='\"i1\":\"best\"' hx-post='/vars'></div></div>");
var div = byId("d1");
div.click();
this.server.respond();
div.innerHTML.should.equal("Clicked!");
});
it('hx-vals overrides inputs', function () {
this.server.respondWith("POST", "/include", function (xhr) {
var params = getParameters(xhr);
params['i1'].should.equal("best");
xhr.respond(200, {}, "Clicked!")
});
var div = make("<div hx-target='this'><input hx-post='/include' hx-vals='\"i1\":\"best\"' hx-trigger='click' id='i1' name='i1' value='test'/></div>")
var input = byId("i1")
input.click();
this.server.respond();
div.innerHTML.should.equal("Clicked!");
});
it('hx-vals overrides hx-vars', function () {
this.server.respondWith("POST", "/vars", function (xhr) {
var params = getParameters(xhr);
params['i1'].should.equal("test");
xhr.respond(200, {}, "Clicked!")
});
var div = make("<div hx-post='/vars' hx-vals='\"i1\":\"test\"' hx-vars='\"i1\":\"best\"'></div>")
div.click();
this.server.respond();
div.innerHTML.should.equal("Clicked!");
});
});

View File

@ -59,7 +59,7 @@ describe("hx-vars attribute", function() {
div.innerHTML.should.equal("Clicked!");
});
it('hx-vars override inputs', function () {
it('hx-vars overrides inputs', function () {
this.server.respondWith("POST", "/include", function (xhr) {
var params = getParameters(xhr);
params['i1'].should.equal("best");

View File

@ -97,6 +97,7 @@
<script src="attributes/hx-swap.js"></script>
<script src="attributes/hx-target.js"></script>
<script src="attributes/hx-trigger.js"></script>
<script src="attributes/hx-vals.js"></script>
<script src="attributes/hx-vars.js"></script>
<script src="attributes/hx-ws.js"></script>