From c4e984a37e4ac3573b2c4197954757b4dbe26954 Mon Sep 17 00:00:00 2001 From: "Kimberlee I. Model" Date: Tue, 3 Sep 2019 07:07:47 -0400 Subject: [PATCH 1/6] Split target/property strings around 'single quotes' and began small refactor --- dist/no.min.js | 2 +- no.js | 202 ++++++++++++++++++++++++++++------------ package-lock.json | 233 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 5 +- test/no.test.js | 61 ++++++++++++ 5 files changed, 442 insertions(+), 61 deletions(-) create mode 100644 package-lock.json create mode 100644 test/no.test.js diff --git a/dist/no.min.js b/dist/no.min.js index 90b7737..9460e31 100644 --- a/dist/no.min.js +++ b/dist/no.min.js @@ -1 +1 @@ -!function(){function e(e){this.js(e)}e.prototype.js=function(e){e=e||"html";var t=this,r=!1;document.querySelector(e).querySelectorAll("[no-js]").forEach(function(e){Object.keys(e.attributes).forEach(function(n){var o=e.attributes[n];if(0===o.name.indexOf("on--")){var a=!0;console.warn('Deprecation warning: using double dashes "--" are deprecated. Use a single dash "-" instead.')}else{if(0!==o.name.indexOf("on-"))return;var a=!1}var p=o.name.split(a?"--":"-"),i=o.value.split(" "),s=p[1],u=p[2],c=p[3];if(p.length>3&&"self"===p[p.length-1]){var l=e;r=!0}else{r=!1;var l=i[0]}var y,v=c;if("reset"!==u){var d=t._getPropertyValueIndex(c,r);y=i.slice(d).join(" "),"attribute"===c&&(v=i[d-1])}var f={action:u,target:l,sourceElement:e,propertyOrEventType:c,propertyValue:y,propertyName:v};e.addEventListener(s,function(e){t._handler(f)})})})},e.prototype._handler=function(e){var t="string"==typeof e.target?document.querySelectorAll(e.target):[e.target];t.forEach(function(t){return"trigger"===e.action&&"function"==typeof t[e.propertyOrEventType]?void t[e.propertyOrEventType]():void("class"===e.propertyOrEventType?"set"===e.action?t.className=e.propertyValue:"switch"==e.action?(t.classList.remove(e.propertyValue),e.sourceElement.classList.add(e.propertyValue)):t.classList[e.action](e.propertyValue):"attribute"===e.propertyOrEventType||"id"===e.propertyOrEventType?"remove"===e.action?t.removeAttribute(e.propertyName):"add"!==e.action&&"set"!=e.action||t.setAttribute(e.propertyName,e.propertyValue):"dom"===e.propertyOrEventType&&"remove"===e.action?t.remove():"value"===e.propertyOrEventType?"set"===e.action?t.value=e.propertyValue:"reset"===e.action&&(t.value=null):"text"===e.propertyOrEventType&&"set"===e.action&&(t.innerText=e.propertyValue))})},e.prototype._getPropertyValueIndex=function(e,t){var r="attribute"===e?2:1;return t?r-1:r},document.addEventListener("DOMContentLoaded",function(){window.no=new e})}(); \ No newline at end of file +!function(){function e(e){this.js(e)}e.prototype._splitParams=function(e){for(var t=[],r="",n=!1,o=!1,a=!1,p=!1,i=!0,s=0;s4&&"self"===p[p.length-1]?(s=e,o=!0):(o=!1,s=i[0]);var y,v=u;if("reset"!==c){var d=t._getPropertyValueIndex(u,o);y=i.slice(d).join(" "),"attribute"===u&&(v=i[d-1])}var m={action:c,target:s,sourceElement:e,propertyOrEventType:u,propertyValue:y,propertyName:v};e.addEventListener(l,function(e){t._handler(m)})})},e.prototype.js=function(e){e=e||"html";var t=this;document.querySelector(e).querySelectorAll("[no-js]").forEach(function(e){t._processRegular(e)})},e.prototype._getPropertyValueIndex=function(e,t){var r="attribute"===e?2:1;return t?r-1:r},e.prototype._handler=function(e){("string"==typeof e.target?document.querySelectorAll(e.target):[e.target]).forEach(function(t){if("trigger"===e.action&&"function"==typeof t[e.propertyOrEventType])return void t[e.propertyOrEventType]();"class"===e.propertyOrEventType?"set"===e.action?t.className=e.propertyValue:"switch"==e.action?(t.classList.remove(e.propertyValue),e.sourceElement.classList.add(e.propertyValue)):t.classList[e.action](e.propertyValue):"attribute"===e.propertyOrEventType||"id"===e.propertyOrEventType?"remove"===e.action?t.removeAttribute(e.propertyName):"add"!==e.action&&"set"!=e.action||t.setAttribute(e.propertyName,e.propertyValue):"dom"===e.propertyOrEventType&&"remove"===e.action?t.remove():"value"===e.propertyOrEventType?"set"===e.action?t.value=e.propertyValue:"reset"===e.action&&(t.value=null):"text"===e.propertyOrEventType&&"set"===e.action&&(t.innerText=e.propertyValue)})},document.addEventListener("DOMContentLoaded",function(){window.no=new e})}(); \ No newline at end of file diff --git a/no.js b/no.js index 2f1a442..e5fe9ed 100644 --- a/no.js +++ b/no.js @@ -9,7 +9,8 @@ * * Supported propertyTypes: * attribute: -* on-[eventType]-add||set-attribute="[target] [attributeName] [attributeValue]" +* on-[eventType]-add-attribute="[target] [attributeName] [attributeValue]" +* on-[eventType]-set-attribute="[target] [attributeName] [attributeValue]" * on-[eventType]-remove-attribute="[target] [attributeName]" * class: * on-[eventType]-add-class="[target] [className]" @@ -18,7 +19,8 @@ * on-[eventType]-toggle-class="[target] [className]" * on-[eventType]-switch-class="[target] [className]" * id: -* on-[eventType]-add||set-id="[target] [idValue]" +* on-[eventType]-add-id="[target] [idValue]" +* on-[eventType]-set-id="[target] [idValue]" * on-[eventType]-remove-id="[target]" * dom: * on-[eventType]-remove-dom="[target]" @@ -39,63 +41,152 @@ this.js(dom); } - NoJS.prototype.js = function (dom) { - dom = dom || 'html'; - var this_ = this; - var isSelf = false; - document.querySelector(dom).querySelectorAll('[no-js]').forEach(function(el) { - Object.keys(el.attributes).forEach(function(prop) { - var attr = el.attributes[prop]; - - // to enable support for single and double dashes. - // note the order of condition checking is important. - if (attr.name.indexOf('on--') === 0) { - var doubleDash = true; - console.warn('Deprecation warning: using double dashes "--" are deprecated. Use a single dash "-" instead.') - } else if (attr.name.indexOf('on-') === 0) { - var doubleDash = false; + /** + * Can handle spaces, single quotation, and escaped "\', \\, \ " chars. + */ + NoJS.prototype._splitParams = function(param) { + var ret = []; + var str = ""; + var inQuote = false; + var prevSpace = false; + var prevQuote = false; + var prevEsc = false; + var first = true; + for(var i = 0; i < param.length; i++) { + var chr = param.charAt(i); + if((first || prevSpace || !inQuote) && !prevEsc && chr === '\'') { + if(!first && !prevSpace) { + console.warn("possibly malformed parameter string: \"" + param + "\""); + } + // start recording a quoted string. + inQuote = true; + first = false; + prevSpace = false; + prevQuote = false; + } else if(inQuote && !prevEsc && chr === '\'') { + // end recording a quoted string + inQuote = false; + prevQuote = true; + ret.push(str); + str = ""; + } else if(!inQuote && !prevEsc && /\s/.exec(chr)) { + // matched a space between params. + if(!first && !prevSpace && !prevQuote) { + ret.push(str); + str = ""; + } // else multiple spaces in a row or leading space. + prevSpace = true; + prevQuote = false; + } else if(!prevEsc && '\\' === chr) { + prevEsc = true; + first = false; + prevSpace = false; + prevQuote = false; + }else if(prevEsc) { + if('\'' === chr || '\\' === chr || /\s/.exec(chr)) { + str += chr; } else { - return; + console.warn("possibly malformed parameter string: \"" + param + "\""); } + prevEsc = false; + first = false; + prevSpace = false; + prevQuote = false; + } else { + first = false; + prevSpace = false; + prevQuote = false; + str += chr; + } + } - var signatureParts = attr.name.split(doubleDash ? '--' : '-'); - var paramValues = attr.value.split(' '); + if(inQuote) { + console.warn("possibly malformed parameter string: \"" + param + "\""); + } - var eventType = signatureParts[1], action = signatureParts[2], propertyOrEventType = signatureParts[3]; + if(!prevQuote && !prevSpace && !first) { + ret.push(str); + } // else last param was inserted by close quote, or it has trailing space. - if (signatureParts.length > 3 && signatureParts[signatureParts.length -1] === 'self') { - var target = el; - isSelf = true; - } else { - isSelf = false; - var target = paramValues[0]; - } + return ret; + } - var propertyName = propertyOrEventType; - var propertyValue; - if (action !== 'reset') { - var index = this_._getPropertyValueIndex(propertyOrEventType, isSelf); - // join space containing values that might have been split. - propertyValue = paramValues.slice(index).join(' '); - if (propertyOrEventType === 'attribute') { - propertyName = paramValues[index - 1]; - } - } + NoJS.prototype._processRegular = function(elt) { + var this_ = this; + Object.keys(elt.attributes).forEach(function(prop) { + var attr = elt.attributes[prop]; + var isSelf = false; + + // to enable support for single and double dashes. + // note the order of condition checking is important. + if (attr.name.indexOf('on--') === 0) { + var doubleDash = true; + console.warn('Deprecation warning: using double dashes "--" are deprecated. Use a single dash "-" instead.') + } else if (attr.name.indexOf('on-') === 0) { + var doubleDash = false; + } else { + return; + } + + var signatureParts = attr.name.split(doubleDash ? '--' : '-'); + var paramValues = this_._splitParams(attr.value); + + if(signatureParts.length < 4) { + console.warn("invalid signature: \"" + attr.name + "\""); + return; + } + var eventType = signatureParts[1]; + var action = signatureParts[2]; + var propertyOrEventType = signatureParts[3]; + + var target; + if (signatureParts.length > 4 && signatureParts[signatureParts.length -1] === 'self') { + target = elt; + isSelf = true; + } else { + isSelf = false; + target = paramValues[0]; + } - var options = { - action: action, - target: target, - sourceElement: el, - propertyOrEventType: propertyOrEventType, - propertyValue: propertyValue, - propertyName: propertyName + var propertyName = propertyOrEventType; + var propertyValue; + if (action !== 'reset') { + var index = this_._getPropertyValueIndex(propertyOrEventType, isSelf); + // join space containing values that might have been split. + propertyValue = paramValues.slice(index).join(' '); + if (propertyOrEventType === 'attribute') { + propertyName = paramValues[index - 1]; } + } - el.addEventListener(eventType, function(e) { - this_._handler(options); - }) - }) - }) + var options = { + action: action, + target: target, + sourceElement: elt, + propertyOrEventType: propertyOrEventType, + propertyValue: propertyValue, + propertyName: propertyName + }; + + elt.addEventListener(eventType, function(e) { + this_._handler(options); + }); + }); + } + + NoJS.prototype.js = function (dom) { + dom = dom || 'html'; + var this_ = this; + document.querySelector(dom).querySelectorAll('[no-js]').forEach(function(el) { + this_._processRegular(el); + }); + } + + NoJS.prototype._getPropertyValueIndex = function (propertyOrEventType, isSelf) { + // if props type is attribute, the signature takes the form + // on-[evtType]-[action]-attribute = "target propertyName propertyValue" + var index = propertyOrEventType === 'attribute' ? 2 : 1; + return isSelf ? index - 1 : index; } NoJS.prototype._handler = function (options) { @@ -145,14 +236,7 @@ }) } - NoJS.prototype._getPropertyValueIndex = function (propertyOrEventType, isSelf) { - // if props type is attribute, the signature takes the form - // on-[evtType]-[action]-attribute = "target propertyName propertyValue" - var index = propertyOrEventType === 'attribute' ? 2 : 1; - return isSelf ? index - 1 : index; - } - document.addEventListener('DOMContentLoaded', function() { window.no = new NoJS(); - }) -})() \ No newline at end of file + }); +})() diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..003ebca --- /dev/null +++ b/package-lock.json @@ -0,0 +1,233 @@ +{ + "name": "nojs", + "version": "0.1.3", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true, + "requires": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "jasmine": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.4.0.tgz", + "integrity": "sha512-sR9b4n+fnBFDEd7VS2el2DeHgKcPiMVn44rtKFumq9q7P/t8WrxsVIZPob4UDdgcDNCwyDqwxCt4k9TDRmjPoQ==", + "requires": { + "glob": "^7.1.3", + "jasmine-core": "~3.4.0" + } + }, + "jasmine-core": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.4.0.tgz", + "integrity": "sha512-HU/YxV4i6GcmiH4duATwAbJQMlE0MsDIR5XmSVxURxKHn3aGAdbY1/ZJFmVRbKtnLwIxxMJD7gYaPsypcbYimg==" + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "requires": { + "align-text": "^0.1.1" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true, + "optional": true + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + } + } +} diff --git a/package.json b/package.json index bd3c3b7..9843898 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Library that helps minimize js you have to write", "main": "no.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", + "test": "jasmine test/*.test.js", "uglify": "uglifyjs --compress --mangle -o dist/no.min.js -- no.js" }, "repository": { @@ -29,5 +29,8 @@ "homepage": "https://github.com/ifedapoolarewaju/nojs#readme", "devDependencies": { "uglify-js": "^2.7.5" + }, + "dependencies": { + "jasmine": "^3.4.0" } } diff --git a/test/no.test.js b/test/no.test.js new file mode 100644 index 0000000..7928d32 --- /dev/null +++ b/test/no.test.js @@ -0,0 +1,61 @@ +global.document = {}; +global.window = {}; + +describe("no", function() { + document.querySelectorAll = function(selector) { return []; }; + document.querySelector = function(selector) { + var ret = {}; + ret.querySelectorAll = function(selector) { return []; }; + return ret; + }; + document.addEventListener = function(evt, act) { + act(); + }; + + var nojs = require("../no.js"); + var no = window.no; + + it("can split parameters with spaces", function() { + expect(no._splitParams("")).toEqual([ ]); + expect(no._splitParams(" ")).toEqual([ ]); + expect(no._splitParams("a")).toEqual(["a"]); + expect(no._splitParams("a b")).toEqual(["a", "b"]); + expect(no._splitParams("a b c")).toEqual(["a", "b", "c"]); + }); + + it("can split parameters with spaces and escapes", function() { + expect(no._splitParams("\\ ")).toEqual([" "]); + expect(no._splitParams("\\\\")).toEqual(["\\"]); + expect(no._splitParams("\\ ")).toEqual([" "]); + }); + + it("can split quoted parameters", function() { + expect(no._splitParams("\'a\'")).toEqual(["a"]); + expect(no._splitParams("\'a\' \'b\'")).toEqual(["a", "b"]); + expect(no._splitParams("\'a\' \'b\' \'c\'")).toEqual(["a", "b", "c"]); + }); + + it("can split quoted parameters with spaces and thingsin them", function() { + expect(no._splitParams("\'a b c\'")).toEqual(["a b c"]); + expect(no._splitParams("\'-a-\' \'+b+\'")).toEqual(["-a-", "+b+"]); + expect(no._splitParams("\' $a!>* \' \'b c \' \'c:d:e \'")).toEqual([" $a!>* ", "b c ", "c:d:e "]); + }); + + it("can split mixed quoted and unquoted", function() { + expect(no._splitParams("a \'b\'")).toEqual(["a", "b"]); + expect(no._splitParams("\'a\' b")).toEqual(["a", "b"]); + expect(no._splitParams("a \'b\' c")).toEqual(["a", "b", "c"]); + expect(no._splitParams("\'a\' b \'c\'")).toEqual(["a", "b", "c"]); + }); + + it("can have escapes in quoted", function() { + expect(no._splitParams("\'\\\'\\ \\\\\'")).toEqual(["\' \\"]); + expect(no._splitParams("\'\\\'\' \'\\ \' \'\\\\\'")).toEqual(["\'", " ", "\\"]); + expect(no._splitParams("\'a\\\'b\\ c\\\\\d'")).toEqual(["a\'b c\\d"]); + expect(no._splitParams("\'a\\\'b\' \'c\\ d\' \'e\\\\f\'")).toEqual(["a\'b", "c d", "e\\f"]); + }); + + it("can mix escapes with quotes and nonquotes", function() { + expect(no._splitParams("\'a \\ b\' c\\'\\ \'d\'")).toEqual(["a b", "c\' ", "d"]); + }); +}); From 9403769c93950598d329a66f61335b1b81a65131 Mon Sep 17 00:00:00 2001 From: "Kimberlee I. Model" Date: Sat, 7 Sep 2019 21:30:17 -0400 Subject: [PATCH 2/6] WIP: quite a large refactor towards modularizing actions --- no.js | 261 ++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 143 insertions(+), 118 deletions(-) diff --git a/no.js b/no.js index e5fe9ed..d94bf4d 100644 --- a/no.js +++ b/no.js @@ -36,6 +36,8 @@ * on-[eventType]-trigger-scrollIntoView="[target]" * */ +"use strict"; + (function() { function NoJS (dom) { this.js(dom); @@ -44,16 +46,16 @@ /** * Can handle spaces, single quotation, and escaped "\', \\, \ " chars. */ - NoJS.prototype._splitParams = function(param) { - var ret = []; - var str = ""; - var inQuote = false; - var prevSpace = false; - var prevQuote = false; - var prevEsc = false; - var first = true; - for(var i = 0; i < param.length; i++) { - var chr = param.charAt(i); + NoJS.prototype.splitParams = function(param) { + let ret = []; + let str = ""; + let inQuote = false; + let prevSpace = false; + let prevQuote = false; + let prevEsc = false; + let first = true; + for(let i = 0; i < param.length; i++) { + let chr = param.charAt(i); if((first || prevSpace || !inQuote) && !prevEsc && chr === '\'') { if(!first && !prevSpace) { console.warn("possibly malformed parameter string: \"" + param + "\""); @@ -109,133 +111,156 @@ } // else last param was inserted by close quote, or it has trailing space. return ret; - } + }; - NoJS.prototype._processRegular = function(elt) { - var this_ = this; - Object.keys(elt.attributes).forEach(function(prop) { - var attr = elt.attributes[prop]; - var isSelf = false; + NoJS.prototype.processTrigger = function(keys, vals) { + let trigger = {}; + trigger.eventType = keys.shift(); + if(keys[0] === "timeout") { + keys.shift(); + trigger.timeout = {}; + trigger.timeout.time = parseInt(vals.shift()); + trigger.timeout.count = parseInt(vals.shift()); + } else { trigger.timeout = null; } + return trigger; + }; - // to enable support for single and double dashes. - // note the order of condition checking is important. - if (attr.name.indexOf('on--') === 0) { - var doubleDash = true; - console.warn('Deprecation warning: using double dashes "--" are deprecated. Use a single dash "-" instead.') - } else if (attr.name.indexOf('on-') === 0) { - var doubleDash = false; - } else { - return; - } + NoJS.prototype.processAction = function(keys, vals, elt) { + let action = {}; + action.invalid = false; + action.actionType = keys.shift(); + let property = keys.shift(); + if((action.actionType === "add" && (property === "class" + || property === "attribute" || property === "id")) + || (action.actionType === "set" && (property === "attribute" + || property === "class" || property === "id" + || property === "value" || property === "text")) + || (action.actionType === "remove" && ( property === "attribute" + || property === "class" || property === "id" + || property === "dom")) + || (action.actionType === "reset" && property === "value") + || ((action.actionType === "toggle" || action.actionType === "switch") + && property === "class")) + { + action.propertyType = property; + } else { action.propertyType = property; action.invalid = true; } - var signatureParts = attr.name.split(doubleDash ? '--' : '-'); - var paramValues = this_._splitParams(attr.value); + action.isSelf = keys[keys.length - 1] === "self"; - if(signatureParts.length < 4) { - console.warn("invalid signature: \"" + attr.name + "\""); - return; + if(action.isSelf) { + action.target = elt; + } else { + action.target = vals.shift(); + } + + hasMore = lst => { if(!lst.length > 0) { action.invalid = true; } }; + + if(action.propertyType === "class") { + hasMore(vals); + action.className = vals.shift(); + } else if(action.propertyType === "id") { + action.propertyType = "attribute"; + action.attributeName = "id"; + if(action.actionType !== "remove") { + hasMore(vals); + action.attributeValue = vals.shift(); } - var eventType = signatureParts[1]; - var action = signatureParts[2]; - var propertyOrEventType = signatureParts[3]; - - var target; - if (signatureParts.length > 4 && signatureParts[signatureParts.length -1] === 'self') { - target = elt; - isSelf = true; - } else { - isSelf = false; - target = paramValues[0]; + } else if(action.propertyType === "attribute") { + hasMore(vals); + action.attributeName = vals.shift(); + if(action.actionType !== "remove") { + hasMore(vals); + action.attributeValue = vals.shift(); } - - var propertyName = propertyOrEventType; - var propertyValue; - if (action !== 'reset') { - var index = this_._getPropertyValueIndex(propertyOrEventType, isSelf); - // join space containing values that might have been split. - propertyValue = paramValues.slice(index).join(' '); - if (propertyOrEventType === 'attribute') { - propertyName = paramValues[index - 1]; - } + } else if(action.propertyType === "value") { + if(action.actionType !== "reset") { + hasMore(vals); + action.value = vals.shift(); } + } else if(action.propertyType === "text") { + hasMore(vals); + action.text = vals.shift(); + } + return action; + }; - var options = { - action: action, - target: target, - sourceElement: elt, - propertyOrEventType: propertyOrEventType, - propertyValue: propertyValue, - propertyName: propertyName - }; - - elt.addEventListener(eventType, function(e) { - this_._handler(options); - }); - }); - } - - NoJS.prototype.js = function (dom) { - dom = dom || 'html'; - var this_ = this; - document.querySelector(dom).querySelectorAll('[no-js]').forEach(function(el) { - this_._processRegular(el); - }); - } - - NoJS.prototype._getPropertyValueIndex = function (propertyOrEventType, isSelf) { - // if props type is attribute, the signature takes the form - // on-[evtType]-[action]-attribute = "target propertyName propertyValue" - var index = propertyOrEventType === 'attribute' ? 2 : 1; - return isSelf ? index - 1 : index; - } - - NoJS.prototype._handler = function (options) { - var targets = typeof options.target === "string" ? document.querySelectorAll(options.target) : [options.target]; - targets.forEach(function(el) { - if (options.action === 'trigger' && typeof el[options.propertyOrEventType] === 'function') { - el[options.propertyOrEventType](); - return; + NoJS.prototype.processListener = function(trig, act) { + if(trig.timeout === null) { + if(trig.eventType === "immediately") { + // timeout 0 to happen after all other listeners are installed. + setTimeout(() => { this.apply(null, act); }, 0); + } else { + elt.addEventListener(trigger.eventType, evt => { + this.apply(evt, act); + }); } - - if (options.propertyOrEventType === 'class') { - if (options.action === 'set') { - el.className = options.propertyValue; - } else if (options.action == 'switch') { - // @todo add and remove based on the class presence - // during time of action - el.classList.remove(options.propertyValue); - options.sourceElement.classList.add(options.propertyValue); - } else { - el.classList[options.action](options.propertyValue); - } + } else if(trig.timeout.count < 0) { + if(trig.eventType === "immediately") { + setInterval(() => { this.apply(null, act); }, trig.timeout.time); + } else { + elt.addEventListener(trigger.eventType, evt => { + setInterval(() => { this.apply(evt, act); }, nums); + }); } - - else if (options.propertyOrEventType === 'attribute' || options.propertyOrEventType === 'id') { - if (options.action === 'remove') { - el.removeAttribute(options.propertyName); - } else if (options.action === 'add' || options.action == 'set') { - el.setAttribute(options.propertyName, options.propertyValue); + } else if(trig.timeout.count > 0) { + let countDown = trig.timeout.count; + let countDownTimer = () => { + this.apply(null, act); + if(countDown > 0) { + countDown--; + setTimeout(countDownTimer, trig.timeout.time); } + }; + if(trig.eventType === "immediately") { + setTimeout(countDownTimer, trig.timeout.time); + } else { + elt.addEventListener(trig.eventType, () => { + setTimeout(countDownTimer, trig.timeout.time); + }); } + } // else if trig.timeout.count === 0 do nothing. + }; - else if (options.propertyOrEventType === 'dom' && options.action === 'remove') { - el.remove(); - } + NoJS.prototype._processElement = function(elt) { + // returns a list of actions to be added to the element. + let ret = []; + Object.keys(elt.attributes).forEach(prop => { + let attr = elt.attributes[prop]; - else if (options.propertyOrEventType === 'value') { - if (options.action === 'set') { - el.value = options.propertyValue; - } else if (options.action === 'reset') { - el.value = null; - } + // to enable support for single and double dashes. + // note the order of condition checking is important. + let doubleDash = false; + if (attr.name.indexOf('on--') === 0) { + doubleDash = true; + console.warn('Deprecation warning: using double dashes "--" are deprecated. Use a single dash "-" instead.') + } else if (attr.name.indexOf('on-') !== 0) { + // This is a regular HTML attribute, no action required. + return; } - else if (options.propertyOrEventType === 'text' && options.action === 'set') { - el.innerText = options.propertyValue; + let signatureParts = attr.name.split(doubleDash ? '--' : '-'); + let paramValues = this_._splitParams(attr.value); + + let trigger = this.processTrigger(signatureParts, paramValues); + let action = this.processAction(signatureParts, paramValues, elt); + + if(action.invalid || (trigger.timeout != null && + (isNaN(trigger.timeout.time) || isNaN(trigger.timeout.count)))) { + console.warn("invalid no-js attribute \"" + attr.name + "\"=\"" + + attr.value + "\""); + } else { + this.processListener(trigger, action); } - }) + }); } + NoJS.prototype.js = function (dom) { + dom = dom || 'html'; + document.querySelector(dom).querySelectorAll('[no-js]').forEach(el => { + this.processRegular(el); + }); + }; + document.addEventListener('DOMContentLoaded', function() { window.no = new NoJS(); }); From 33e9404397b38488a046546586874397c1f6c538 Mon Sep 17 00:00:00 2001 From: "Kimberlee I. Model" Date: Sun, 8 Sep 2019 03:11:03 -0400 Subject: [PATCH 3/6] refactored code for open/close extensibility by adding action types to a list. --- .gitignore | 1 + dist/no.min.js | 2 +- no.js | 346 +++++++++++++++++++++++++++++----------------- package-lock.json | 27 +++- package.json | 10 +- test.html | 101 ++++++++++++++ test/no.test.js | 58 ++++---- 7 files changed, 381 insertions(+), 164 deletions(-) create mode 100644 test.html diff --git a/.gitignore b/.gitignore index da7f48c..b72d4a9 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,4 @@ jspm_packages .node_repl_history .DS_Store +.*.swp diff --git a/dist/no.min.js b/dist/no.min.js index 9460e31..bd6b505 100644 --- a/dist/no.min.js +++ b/dist/no.min.js @@ -1 +1 @@ -!function(){function e(e){this.js(e)}e.prototype._splitParams=function(e){for(var t=[],r="",n=!1,o=!1,a=!1,p=!1,i=!0,s=0;s4&&"self"===p[p.length-1]?(s=e,o=!0):(o=!1,s=i[0]);var y,v=u;if("reset"!==c){var d=t._getPropertyValueIndex(u,o);y=i.slice(d).join(" "),"attribute"===u&&(v=i[d-1])}var m={action:c,target:s,sourceElement:e,propertyOrEventType:u,propertyValue:y,propertyName:v};e.addEventListener(l,function(e){t._handler(m)})})},e.prototype.js=function(e){e=e||"html";var t=this;document.querySelector(e).querySelectorAll("[no-js]").forEach(function(e){t._processRegular(e)})},e.prototype._getPropertyValueIndex=function(e,t){var r="attribute"===e?2:1;return t?r-1:r},e.prototype._handler=function(e){("string"==typeof e.target?document.querySelectorAll(e.target):[e.target]).forEach(function(t){if("trigger"===e.action&&"function"==typeof t[e.propertyOrEventType])return void t[e.propertyOrEventType]();"class"===e.propertyOrEventType?"set"===e.action?t.className=e.propertyValue:"switch"==e.action?(t.classList.remove(e.propertyValue),e.sourceElement.classList.add(e.propertyValue)):t.classList[e.action](e.propertyValue):"attribute"===e.propertyOrEventType||"id"===e.propertyOrEventType?"remove"===e.action?t.removeAttribute(e.propertyName):"add"!==e.action&&"set"!=e.action||t.setAttribute(e.propertyName,e.propertyValue):"dom"===e.propertyOrEventType&&"remove"===e.action?t.remove():"value"===e.propertyOrEventType?"set"===e.action?t.value=e.propertyValue:"reset"===e.action&&(t.value=null):"text"===e.propertyOrEventType&&"set"===e.action&&(t.innerText=e.propertyValue)})},document.addEventListener("DOMContentLoaded",function(){window.no=new e})}(); \ No newline at end of file +"use strict";!function(){function e(e){this.targetTypes={}}function t(e){no.targetTypes[e]={},no.targetTypes[e].process=function(e,t){("trigger"!==e.actionType||t.length>0)&&(e.invalid=!0)},no.targetTypes[e].apply=function(t,n,i){i[e]()}}e.prototype.splitParams=function(e){for(var t=[],n="",i=!1,a=!1,o=!1,s=!1,r=!0,c=0;c0){var s=e.timeout.count;o=function(t){s-- >0&&(a(t),setTimeout(o,e.timeout.time))}}else o=function(t){setInterval(function(){a(t)},e.timeout.time)};"immediately"===e.eventType?setTimeout(function(){a(null)},0):n.addEventListener(e.eventType,o)}},e.prototype.processElement=function(e){var t=this;Object.keys(e.attributes).forEach(function(n){var i=e.attributes[n],a=!1;if(0===i.name.indexOf("on--"))a=!0,console.warn('Deprecation warning: using double dashes "--" are deprecated. Use a single dash "-" instead.');else if(0!==i.name.indexOf("on-"))return;var o=i.name.split(a?"--":"-");o.shift();var s=t.splitParams(i.value);"invalid"===s&&console.warn("invalid no-js parameter "+i.name+'="'+i.value+'"');var r=t.processTrigger(o,s),c=t.processAction(o,s,e);c.invalid||r.invalid?console.warn("invalid no-js attribute "+i.name+'="'+i.value+'"'):t.processListener(r,c,e)})},e.prototype.js=function(e){var t=this;e=e||"html",document.querySelector(e).querySelectorAll("[no-js]").forEach(function(e){t.processElement(e)})},window.no=new e,no.targetTypes.attribute={},no.targetTypes.attribute.process=function(e,t){"add"===e.actionType||"set"===e.actionType?t.length>=2?(e.attributeName=t.shift(),e.attributeValue=t.join(" ")):e.invalid=!0:"remove"===e.actionType&&1===t.length?e.attributeName=t.shift():e.invalid=!0},no.targetTypes.attribute.apply=function(e,t,n){"remove"===t.actionType?n.removeAttribute(t.attributeName):n.setAttribute(t.attributeName,t.attributeValue)},no.targetTypes.class={},no.targetTypes.class.process=function(e,t){"add"===e.actionType||"set"===e.actionType||"remove"===e.actionType||"toggle"===e.actionType||"switch"===e.actionType?e.classNames=t:e.invalid=!0},no.targetTypes.class.apply=function(e,t,n){"set"===t.actionType?n.className=t.classNames.join(" "):"switch"===t.actionType?t.classNames.forEach(function(e){n.classList.remove(e),t.sourceElement.classList.add(e)}):t.classNames.forEach(function(e){n.classList[t.actionType](e)})},no.targetTypes.id={},no.targetTypes.id.process=function(e,t){e.targetType="attribute",t.unshift("id"),no.targetTypes.attribute.process(e,t)},no.targetTypes.dom={},no.targetTypes.dom.process=function(e,t){"remove"!==e.actionType&&0!=t.length&&e.invalid},no.targetTypes.dom.apply=function(e,t,n){"remove"===t.actionType&&n.remove()},no.targetTypes.value={},no.targetTypes.value.process=function(e,t){"set"===e.actionType?e.value=t.join(" "):"reset"===e.actionType&&0===t.length||(e.invalid=!0)},no.targetTypes.value.apply=function(e,t,n){"set"===t.actionType?n.value=t.value:"reset"===t.actionType&&(n.value=null)},no.targetTypes.text={},no.targetTypes.text.process=function(e,t){"set"===e.actionType?e.text=t.join(" "):e.invalid=!0},no.targetTypes.text.apply=function(e,t,n){"set"===t.actionType&&(n.innerText=t.text)},t("click"),t("focus"),t("blur"),t("scrollIntoView"),document.addEventListener("DOMContentLoaded",function(){no.js()})}(); \ No newline at end of file diff --git a/no.js b/no.js index d94bf4d..60cef78 100644 --- a/no.js +++ b/no.js @@ -9,8 +9,7 @@ * * Supported propertyTypes: * attribute: -* on-[eventType]-add-attribute="[target] [attributeName] [attributeValue]" -* on-[eventType]-set-attribute="[target] [attributeName] [attributeValue]" +* on-[eventType]-add||set-attribute="[target] [attributeName] [attributeValue]" * on-[eventType]-remove-attribute="[target] [attributeName]" * class: * on-[eventType]-add-class="[target] [className]" @@ -19,8 +18,7 @@ * on-[eventType]-toggle-class="[target] [className]" * on-[eventType]-switch-class="[target] [className]" * id: -* on-[eventType]-add-id="[target] [idValue]" -* on-[eventType]-set-id="[target] [idValue]" +* on-[eventType]-add||set-id="[target] [idValue]" * on-[eventType]-remove-id="[target]" * dom: * on-[eventType]-remove-dom="[target]" @@ -40,38 +38,47 @@ (function() { function NoJS (dom) { - this.js(dom); + this.targetTypes = {}; } /** * Can handle spaces, single quotation, and escaped "\', \\, \ " chars. */ NoJS.prototype.splitParams = function(param) { - let ret = []; - let str = ""; - let inQuote = false; - let prevSpace = false; - let prevQuote = false; - let prevEsc = false; - let first = true; - for(let i = 0; i < param.length; i++) { - let chr = param.charAt(i); - if((first || prevSpace || !inQuote) && !prevEsc && chr === '\'') { + var ret = []; + var str = ""; + var inQuote = false; + var prevSpace = false; + var prevQuote = false; + var prevEsc = false; + var first = true; + for(var i = 0; i < param.length; i++) { + var chr = param.charAt(i); + if(prevEsc) { + if('\'' === chr || '\\' === chr || /\s/.exec(chr)) { + str += chr; + } else { + return "invalid"; + } + prevEsc = false; + first = false; + prevSpace = false; + prevQuote = false; + } else if((first || prevSpace || !inQuote) && chr === '\'') { if(!first && !prevSpace) { - console.warn("possibly malformed parameter string: \"" + param + "\""); + return "invalid"; } // start recording a quoted string. inQuote = true; first = false; prevSpace = false; - prevQuote = false; - } else if(inQuote && !prevEsc && chr === '\'') { + } else if(inQuote && chr === '\'') { // end recording a quoted string inQuote = false; prevQuote = true; ret.push(str); str = ""; - } else if(!inQuote && !prevEsc && /\s/.exec(chr)) { + } else if(!inQuote && /\s/.exec(chr)) { // matched a space between params. if(!first && !prevSpace && !prevQuote) { ret.push(str); @@ -79,22 +86,13 @@ } // else multiple spaces in a row or leading space. prevSpace = true; prevQuote = false; - } else if(!prevEsc && '\\' === chr) { + } else if('\\' === chr) { prevEsc = true; first = false; prevSpace = false; prevQuote = false; - }else if(prevEsc) { - if('\'' === chr || '\\' === chr || /\s/.exec(chr)) { - str += chr; - } else { - console.warn("possibly malformed parameter string: \"" + param + "\""); - } - prevEsc = false; - first = false; - prevSpace = false; - prevQuote = false; } else { + if(prevQuote) { return "invalid"; } first = false; prevSpace = false; prevQuote = false; @@ -103,7 +101,7 @@ } if(inQuote) { - console.warn("possibly malformed parameter string: \"" + param + "\""); + return "invalid"; } if(!prevQuote && !prevSpace && !first) { @@ -114,122 +112,91 @@ }; NoJS.prototype.processTrigger = function(keys, vals) { - let trigger = {}; + var trigger = {}; trigger.eventType = keys.shift(); + trigger.invalid = false; if(keys[0] === "timeout") { keys.shift(); trigger.timeout = {}; trigger.timeout.time = parseInt(vals.shift()); trigger.timeout.count = parseInt(vals.shift()); + if(isNaN(trigger.timeout.time) || isNaN(trigger.timeout.count)) { + trigger.invalid = true; + } } else { trigger.timeout = null; } return trigger; }; NoJS.prototype.processAction = function(keys, vals, elt) { - let action = {}; - action.invalid = false; + var action = {}; action.actionType = keys.shift(); - let property = keys.shift(); - if((action.actionType === "add" && (property === "class" - || property === "attribute" || property === "id")) - || (action.actionType === "set" && (property === "attribute" - || property === "class" || property === "id" - || property === "value" || property === "text")) - || (action.actionType === "remove" && ( property === "attribute" - || property === "class" || property === "id" - || property === "dom")) - || (action.actionType === "reset" && property === "value") - || ((action.actionType === "toggle" || action.actionType === "switch") - && property === "class")) - { - action.propertyType = property; - } else { action.propertyType = property; action.invalid = true; } - + action.targetType = keys.shift(); + action.sourceElement = elt; action.isSelf = keys[keys.length - 1] === "self"; - if(action.isSelf) { - action.target = elt; - } else { - action.target = vals.shift(); - } + if(action.isSelf) { action.target = elt; } + else { action.target = vals.shift(); } - hasMore = lst => { if(!lst.length > 0) { action.invalid = true; } }; + action.invalid = false; - if(action.propertyType === "class") { - hasMore(vals); - action.className = vals.shift(); - } else if(action.propertyType === "id") { - action.propertyType = "attribute"; - action.attributeName = "id"; - if(action.actionType !== "remove") { - hasMore(vals); - action.attributeValue = vals.shift(); - } - } else if(action.propertyType === "attribute") { - hasMore(vals); - action.attributeName = vals.shift(); - if(action.actionType !== "remove") { - hasMore(vals); - action.attributeValue = vals.shift(); - } - } else if(action.propertyType === "value") { - if(action.actionType !== "reset") { - hasMore(vals); - action.value = vals.shift(); - } - } else if(action.propertyType === "text") { - hasMore(vals); - action.text = vals.shift(); + var targetType = this.targetTypes[action.targetType]; + if(action.actionType == null || action.targetType == null + || targetType == null || action.target == null) { + action.invalid = true; + } else { + targetType.process(action, vals); } + return action; }; - NoJS.prototype.processListener = function(trig, act) { - if(trig.timeout === null) { - if(trig.eventType === "immediately") { - // timeout 0 to happen after all other listeners are installed. - setTimeout(() => { this.apply(null, act); }, 0); - } else { - elt.addEventListener(trigger.eventType, evt => { - this.apply(evt, act); - }); - } - } else if(trig.timeout.count < 0) { - if(trig.eventType === "immediately") { - setInterval(() => { this.apply(null, act); }, trig.timeout.time); - } else { - elt.addEventListener(trigger.eventType, evt => { - setInterval(() => { this.apply(evt, act); }, nums); - }); - } - } else if(trig.timeout.count > 0) { - let countDown = trig.timeout.count; - let countDownTimer = () => { - this.apply(null, act); - if(countDown > 0) { - countDown--; - setTimeout(countDownTimer, trig.timeout.time); + NoJS.prototype.processListener = function(trig, act, elt) { + var targetType = this.targetTypes[act.targetType]; + if(targetType != null) { + var apply = function(evt) { + if(act.isSelf) { targetType.apply(evt, act, act.target); } + else { + var tgts = document.querySelectorAll(act.target); + tgts.forEach(function (target) { + targetType.apply(evt, act, target); + }); } }; + var listener = null; + + if(trig.timeout == null) { listener = apply; } + else if(trig.timeout.count > 0) { + var countDown = trig.timeout.count; + listener = function(evnt) { + if(countDown-- > 0) { + apply(evnt); + setTimeout(listener, trig.timeout.time); + } + }; + } else { + listener = function(evnt) { + setInterval(function() { apply(evnt); }, trig.timeout.time); + } + } + if(trig.eventType === "immediately") { - setTimeout(countDownTimer, trig.timeout.time); + // apply it after all the listeners are installed. + setTimeout(function() { apply(null) }, 0); } else { - elt.addEventListener(trig.eventType, () => { - setTimeout(countDownTimer, trig.timeout.time); - }); + elt.addEventListener(trig.eventType, listener); } - } // else if trig.timeout.count === 0 do nothing. + } }; - NoJS.prototype._processElement = function(elt) { + NoJS.prototype.processElement = function(elt) { // returns a list of actions to be added to the element. - let ret = []; - Object.keys(elt.attributes).forEach(prop => { - let attr = elt.attributes[prop]; + var this_ = this; + Object.keys(elt.attributes).forEach(function(prop) { + var attr = elt.attributes[prop]; // to enable support for single and double dashes. // note the order of condition checking is important. - let doubleDash = false; + var doubleDash = false; if (attr.name.indexOf('on--') === 0) { doubleDash = true; console.warn('Deprecation warning: using double dashes "--" are deprecated. Use a single dash "-" instead.') @@ -238,30 +205,151 @@ return; } - let signatureParts = attr.name.split(doubleDash ? '--' : '-'); - let paramValues = this_._splitParams(attr.value); + var signatureParts = attr.name.split(doubleDash ? '--' : '-'); + signatureParts.shift(); + var paramValues = this_.splitParams(attr.value); - let trigger = this.processTrigger(signatureParts, paramValues); - let action = this.processAction(signatureParts, paramValues, elt); + if(paramValues === "invalid") { + console.warn("invalid no-js parameter " + attr.name + "=\"" + + attr.value + "\""); + } + + var trigger = this_.processTrigger(signatureParts, paramValues); + var action = this_.processAction(signatureParts, paramValues, elt); - if(action.invalid || (trigger.timeout != null && - (isNaN(trigger.timeout.time) || isNaN(trigger.timeout.count)))) { - console.warn("invalid no-js attribute \"" + attr.name + "\"=\"" + if(action.invalid || trigger.invalid) { + console.warn("invalid no-js attribute " + attr.name + "=\"" + attr.value + "\""); } else { - this.processListener(trigger, action); + this_.processListener(trigger, action, elt); } }); - } + }; NoJS.prototype.js = function (dom) { + var this_ = this; dom = dom || 'html'; - document.querySelector(dom).querySelectorAll('[no-js]').forEach(el => { - this.processRegular(el); + document.querySelector(dom).querySelectorAll('[no-js]') + .forEach(function(el) { + this_.processElement(el); }); }; + window.no = new NoJS(); + + no.targetTypes.attribute = {}; + no.targetTypes.attribute.process = function(action, values) { + if(action.actionType === "add" || action.actionType === "set") { + if(values.length >= 2) { + action.attributeName = values.shift(); + action.attributeValue = values.join(" "); + } else { action.invalid = true; } + } else if(action.actionType === "remove") { + if(values.length === 1) { + action.attributeName = values.shift(); + } else { action.invalid = true; } + } else { action.invalid = true; } + }; + no.targetTypes.attribute.apply = function(evnt, action, target) { + if(action.actionType === "remove") { + target.removeAttribute(action.attributeName); + } else { + target.setAttribute(action.attributeName, action.attributeValue); + } + } + + no.targetTypes["class"] = {}; + no.targetTypes["class"].process = function(action, values) { + if(action.actionType === "add" || action.actionType === "set" + || action.actionType === "remove" || action.actionType === "toggle" + || action.actionType === "switch") { + action.classNames = values; + } else { action.invalid = true; } + } + no.targetTypes["class"].apply = function(evnt, action, target) { + if(action.actionType === "set") { + target.className = action.classNames.join(" "); + } else if(action.actionType === "switch") { + // @todo add and remove based on the class presence + // during time of action + action.classNames.forEach(function(name) { + target.classList.remove(name); + action.sourceElement.classList.add(name); + }); + } else { + action.classNames.forEach(function(name) { + target.classList[action.actionType](name); + }); + } + } + + no.targetTypes.id = {}; + no.targetTypes.id.process = function(action, values) { + action.targetType = "attribute"; + values.unshift("id"); + no.targetTypes.attribute.process(action, values); + } + + no.targetTypes.dom = {}; + no.targetTypes.dom.process = function(action, values) { + if(action.actionType !== "remove" && values.length != 0) { + action.invalid; + } + } + no.targetTypes.dom.apply = function(evnt, action, target) { + if(action.actionType === "remove") { + target.remove(); + } + } + + no.targetTypes.value = {}; + no.targetTypes.value.process = function(action, values) { + if(action.actionType === "set") { + action.value = values.join(" "); + } else if(action.actionType !== "reset" || values.length !== 0) { + action.invalid = true; + } + } + no.targetTypes.value.apply = function(evnt, action, target) { + if(action.actionType === "set") { + target.value = action.value; + } else if(action.actionType === "reset") { + target.value = null; + } + } + + no.targetTypes.text = {}; + no.targetTypes.text.process = function(action, values) { + if(action.actionType === "set") { + action.text = values.join(" "); + } else { + action.invalid = true; + } + } + no.targetTypes.text.apply = function(evnt, action, target) { + if(action.actionType === "set") { + target.innerText = action.text; + } + } + + function addTrigger(type) { + no.targetTypes[type] = {}; + no.targetTypes[type].process = function(action, values) { + if(action.actionType !== "trigger" || values.length > 0) { + action.invalid = true; + } + } + no.targetTypes[type].apply = function(evnt, action, target) { + target[type](); + } + } + + addTrigger("click"); + addTrigger("focus"); + addTrigger("blur"); + addTrigger("scrollIntoView"); + document.addEventListener('DOMContentLoaded', function() { - window.no = new NoJS(); + no.js(); }); -})() +})(); diff --git a/package-lock.json b/package-lock.json index 003ebca..523755c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,12 +18,14 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -59,7 +61,8 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, "decamelize": { "version": "1.2.0", @@ -70,12 +73,14 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true }, "glob": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -89,6 +94,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -97,7 +103,8 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "is-buffer": { "version": "1.1.6", @@ -109,6 +116,7 @@ "version": "3.4.0", "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.4.0.tgz", "integrity": "sha512-sR9b4n+fnBFDEd7VS2el2DeHgKcPiMVn44rtKFumq9q7P/t8WrxsVIZPob4UDdgcDNCwyDqwxCt4k9TDRmjPoQ==", + "dev": true, "requires": { "glob": "^7.1.3", "jasmine-core": "~3.4.0" @@ -117,7 +125,8 @@ "jasmine-core": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.4.0.tgz", - "integrity": "sha512-HU/YxV4i6GcmiH4duATwAbJQMlE0MsDIR5XmSVxURxKHn3aGAdbY1/ZJFmVRbKtnLwIxxMJD7gYaPsypcbYimg==" + "integrity": "sha512-HU/YxV4i6GcmiH4duATwAbJQMlE0MsDIR5XmSVxURxKHn3aGAdbY1/ZJFmVRbKtnLwIxxMJD7gYaPsypcbYimg==", + "dev": true }, "kind-of": { "version": "3.2.2", @@ -144,6 +153,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -152,6 +162,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1" } @@ -159,7 +170,8 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true }, "repeat-string": { "version": "1.6.1", @@ -215,7 +227,8 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "yargs": { "version": "3.10.0", diff --git a/package.json b/package.json index 9843898..d73c3e0 100644 --- a/package.json +++ b/package.json @@ -23,14 +23,18 @@ ], "author": "Ifedapo .A. Olarewaju", "license": "MIT", + "contributors": [ + { + "name": "Kimberlee Model", + "url": "www.redbow.kim/blog/" + } + ], "bugs": { "url": "https://github.com/ifedapoolarewaju/nojs/issues" }, "homepage": "https://github.com/ifedapoolarewaju/nojs#readme", "devDependencies": { - "uglify-js": "^2.7.5" - }, - "dependencies": { + "uglify-js": "^2.7.5", "jasmine": "^3.4.0" } } diff --git a/test.html b/test.html new file mode 100644 index 0000000..e022817 --- /dev/null +++ b/test.html @@ -0,0 +1,101 @@ + + + + + + + + + + + +

Attributes

+ (add and set are equivelant) + + + + change my color +

Classes

+ + + + + change my classes +

Select class

+
    +
  • thing 1
  • +
  • thing 2
  • +
  • thing 3
  • +
  • thing 4
  • +
+

Id

+ + + change my color +

Remove an element

+ + Remove me +

Set Text

+ + + Set my text +

Value

+ + + + +

triggers

+ + + +
+ + + + + + + diff --git a/test/no.test.js b/test/no.test.js index 7928d32..c77cbec 100644 --- a/test/no.test.js +++ b/test/no.test.js @@ -1,5 +1,5 @@ global.document = {}; -global.window = {}; +global.window = global; describe("no", function() { document.querySelectorAll = function(selector) { return []; }; @@ -16,46 +16,56 @@ describe("no", function() { var no = window.no; it("can split parameters with spaces", function() { - expect(no._splitParams("")).toEqual([ ]); - expect(no._splitParams(" ")).toEqual([ ]); - expect(no._splitParams("a")).toEqual(["a"]); - expect(no._splitParams("a b")).toEqual(["a", "b"]); - expect(no._splitParams("a b c")).toEqual(["a", "b", "c"]); + expect(no.splitParams("")).toEqual([ ]); + expect(no.splitParams(" ")).toEqual([ ]); + expect(no.splitParams("a")).toEqual(["a"]); + expect(no.splitParams("a b")).toEqual(["a", "b"]); + expect(no.splitParams("a b c")).toEqual(["a", "b", "c"]); }); it("can split parameters with spaces and escapes", function() { - expect(no._splitParams("\\ ")).toEqual([" "]); - expect(no._splitParams("\\\\")).toEqual(["\\"]); - expect(no._splitParams("\\ ")).toEqual([" "]); + expect(no.splitParams("\\ ")).toEqual([" "]); + expect(no.splitParams("\\\\")).toEqual(["\\"]); + expect(no.splitParams("\\ ")).toEqual([" "]); }); it("can split quoted parameters", function() { - expect(no._splitParams("\'a\'")).toEqual(["a"]); - expect(no._splitParams("\'a\' \'b\'")).toEqual(["a", "b"]); - expect(no._splitParams("\'a\' \'b\' \'c\'")).toEqual(["a", "b", "c"]); + expect(no.splitParams("\'a\'")).toEqual(["a"]); + expect(no.splitParams("\'a\' \'b\'")).toEqual(["a", "b"]); + expect(no.splitParams("\'a\' \'b\' \'c\'")).toEqual(["a", "b", "c"]); }); it("can split quoted parameters with spaces and thingsin them", function() { - expect(no._splitParams("\'a b c\'")).toEqual(["a b c"]); - expect(no._splitParams("\'-a-\' \'+b+\'")).toEqual(["-a-", "+b+"]); - expect(no._splitParams("\' $a!>* \' \'b c \' \'c:d:e \'")).toEqual([" $a!>* ", "b c ", "c:d:e "]); + expect(no.splitParams("\'a b c\'")).toEqual(["a b c"]); + expect(no.splitParams("\'-a-\' \'+b+\'")).toEqual(["-a-", "+b+"]); + expect(no.splitParams("\' $a!>* \' \'b c \' \'c:d:e \'")).toEqual([" $a!>* ", "b c ", "c:d:e "]); }); it("can split mixed quoted and unquoted", function() { - expect(no._splitParams("a \'b\'")).toEqual(["a", "b"]); - expect(no._splitParams("\'a\' b")).toEqual(["a", "b"]); - expect(no._splitParams("a \'b\' c")).toEqual(["a", "b", "c"]); - expect(no._splitParams("\'a\' b \'c\'")).toEqual(["a", "b", "c"]); + expect(no.splitParams("a \'b\'")).toEqual(["a", "b"]); + expect(no.splitParams("\'a\' b")).toEqual(["a", "b"]); + expect(no.splitParams("a \'b\' c")).toEqual(["a", "b", "c"]); + expect(no.splitParams("\'a\' b \'c\'")).toEqual(["a", "b", "c"]); }); it("can have escapes in quoted", function() { - expect(no._splitParams("\'\\\'\\ \\\\\'")).toEqual(["\' \\"]); - expect(no._splitParams("\'\\\'\' \'\\ \' \'\\\\\'")).toEqual(["\'", " ", "\\"]); - expect(no._splitParams("\'a\\\'b\\ c\\\\\d'")).toEqual(["a\'b c\\d"]); - expect(no._splitParams("\'a\\\'b\' \'c\\ d\' \'e\\\\f\'")).toEqual(["a\'b", "c d", "e\\f"]); + expect(no.splitParams("\'\\\'\\ \\\\\'")).toEqual(["\' \\"]); + expect(no.splitParams("\'\\\'\' \'\\ \' \'\\\\\'")).toEqual(["\'", " ", "\\"]); + expect(no.splitParams("\'a\\\'b\\ c\\\\\d'")).toEqual(["a\'b c\\d"]); + expect(no.splitParams("\'a\\\'b\' \'c\\ d\' \'e\\\\f\'")).toEqual(["a\'b", "c d", "e\\f"]); }); it("can mix escapes with quotes and nonquotes", function() { - expect(no._splitParams("\'a \\ b\' c\\'\\ \'d\'")).toEqual(["a b", "c\' ", "d"]); + expect(no.splitParams("\'a \\ b\' c\\'\\ \'d\'")).toEqual(["a b", "c\' ", "d"]); }); + + it("detects errors ", function() { + // no space between quoted strings. + expect(no.splitParams("\'a\'\'b\'")).toEqual("invalid"); + // unclosed string + expect(no.splitParams("\'a")).toEqual("invalid"); + // illegal escapes + expect(no.splitParams("\\n")).toEqual("invalid"); + }); + }); From 421f349fe2929a471cf2fe139fc3337faeb6bca8 Mon Sep 17 00:00:00 2001 From: "Kimberlee I. Model" Date: Sun, 8 Sep 2019 03:35:29 -0400 Subject: [PATCH 4/6] fixed the trigger-scrollIntoView action --- no.js | 9 +++++---- test.html | 7 +++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/no.js b/no.js index 60cef78..e8777d5 100644 --- a/no.js +++ b/no.js @@ -130,7 +130,7 @@ NoJS.prototype.processAction = function(keys, vals, elt) { var action = {}; action.actionType = keys.shift(); - action.targetType = keys.shift(); + action.targetType = keys.shift().toLowerCase(); action.sourceElement = elt; action.isSelf = keys[keys.length - 1] === "self"; @@ -333,13 +333,14 @@ } function addTrigger(type) { - no.targetTypes[type] = {}; - no.targetTypes[type].process = function(action, values) { + var ltype = type.toLowerCase(); + no.targetTypes[ltype] = {}; + no.targetTypes[ltype].process = function(action, values) { if(action.actionType !== "trigger" || values.length > 0) { action.invalid = true; } } - no.targetTypes[type].apply = function(evnt, action, target) { + no.targetTypes[ltype].apply = function(evnt, action, target) { target[type](); } } diff --git a/test.html b/test.html index e022817..ee933e3 100644 --- a/test.html +++ b/test.html @@ -3,8 +3,7 @@ - - +