diff --git a/src/bind-attributes.js b/src/bind-attributes.js
new file mode 100644
index 0000000..38516da
--- /dev/null
+++ b/src/bind-attributes.js
@@ -0,0 +1,46 @@
+import HTMLHandler from 'https://dev.jspm.io/parse5';
+import {nanoid} from 'https://deno.land/x/nanoid@v3.0.0/mod.ts';
+
+export function parseAttributeBindings({HTML = '', CSS = '', JS = ''}) {
+ const parsedHTML = HTMLHandler.parseFragment(HTML);
+
+ function _parse(node) {
+ if (node.nodeName.startsWith('#') && node.nodeName !== '#document-fragment') {
+ return node;
+ }
+
+ node.childNodes = node.childNodes?.map(_parse);
+
+ if (!node.attrs) {
+ return node;
+ }
+
+ node.attrs = node.attrs.map(attr => {
+ const {name, value} = attr;
+
+ if (name.startsWith('{') && name.endsWith('}')) {
+ const cleanName = name.slice(1, -1);
+ const id = nanoid(5);
+ const eventSlug = `handle-${cleanName}-${id}`;
+
+ JS += '\n\n' + `
+function __bind__attr__${cleanName}__${id}() {
+ document.getElementById('${eventSlug}').setAttribute('${cleanName}', ${value || cleanName});
+}
+ `.trim();
+
+ return ({name: 'id', value: eventSlug});
+ }
+
+ return attr;
+ });
+
+ return node;
+ }
+
+ const out = _parse(parsedHTML);
+
+ HTML = HTMLHandler.serialize(out);
+
+ return {HTML, CSS, JS};
+}
diff --git a/src/events.js b/src/events.js
index 12dca09..0ecc974 100644
--- a/src/events.js
+++ b/src/events.js
@@ -20,7 +20,7 @@ export function parseEvents({HTML, CSS, JS}) {
if (name.startsWith('on')) {
console.log('Got event!!');
- const eventSlug = `handle-${name.slice(2)}-${attr.value.replace(/[[\](){}'"]*/gi, '')}-${nanoid(5)}`;
+ const eventSlug = `handle-${name.slice(2)}-${attr.value.replace(/[[\](){}'"\s;+]*/gi, '')}-${nanoid(5)}`;
JS += '\n\n';
JS += `
diff --git a/src/index.js b/src/index.js
index ec7984f..b3424b8 100644
--- a/src/index.js
+++ b/src/index.js
@@ -2,6 +2,7 @@ import {split} from './split.js';
import {resolve} from './resolve.js';
import {makeReactive} from './react.js';
import {parseEvents} from './events.js';
+import {parseAttributeBindings} from './bind-attributes.js';
/*
* Parse a component
@@ -15,7 +16,7 @@ import {parseEvents} from './events.js';
* @return {string} code.JS - The parsed JS code
*/
export function parse(code = '', options = {wrapInHTML: true, HTMLTemplate: null}) {
- let {HTML, CSS, JS} = makeReactive(parseEvents(resolve(split(code, true))));
+ let {HTML, CSS, JS} = makeReactive(parseAttributeBindings(parseEvents(resolve(split(code, true)))));
if (options.wrapInHTML) {
if (options.HTMLTemplate) {
@@ -41,3 +42,15 @@ ${HTML.split('\n').map(line => '\t' + line).join('\n')}
return {HTML, CSS, JS};
}
+let out = parse(`
+
+
+`);
+
+console.log(out.HTML);
+console.log();
+console.log();
+console.log(out.JS);
diff --git a/src/react.js b/src/react.js
index ce05ab0..470de7b 100644
--- a/src/react.js
+++ b/src/react.js
@@ -6,19 +6,19 @@ const {builders: b} = types;
let deps = {};
let depsInverted = false;
let HTML = '';
+let ast = {};
const finalReactiveCalls = [];
export function makeReactive({HTML: HTMLIn, CSS, JS}) {
HTML = HTMLIn;
- const ast = parse(JS);
+ ast = parse(JS);
visit(ast, {
visitUpdateExpression: visitUpdate,
visitVariableDeclaration: visitVariable
});
- // It works. Don't touch
visit(ast, {
visitVariableDeclaration: secondVisitVariable
});
@@ -46,6 +46,7 @@ function visitVariable(path) {
visit(declaration.init, {
visitIdentifier(path) {
+ // O(h no)
deps[declaration.id.name].push(path.node.name);
this.traverse(path);
}
@@ -84,7 +85,12 @@ function secondVisitVariable(path) {
) : b.emptyStatement(),
hasBinding(HTML, declaration.id.name) ?
- parse(`document.getElementById('__bind__${declaration.id.name}').innerText = ${declaration.id.name}`).program.body[0] : b.emptyStatement(), // I'm lazy
+ buildBinding(declaration.id.name) : b.emptyStatement(),
+
+ hasAttributeBinding(declaration.id.name) ?
+ b.expressionStatement(
+ b.callExpression(b.identifier(hasAttributeBinding(declaration.id.name)), [])
+ ) : b.emptyStatement(),
...(deps[declaration.id.name] || []).map(
dep => b.expressionStatement(
@@ -161,10 +167,46 @@ function bindConsts(path) {
const reactiveDeclarators = [];
for (const declaration of path.node.declarations) {
- HTML = directlyBind(HTML, declaration.id.name, eval(print(declaration.init).code));
+ HTML = directlyBind(HTML, declaration.id.name,
+ eval(print(declaration.init).code)); // TODO no eval
}
path.replace(path.node, ...reactiveDeclarators);
return false;
}
+
+function buildBinding(name) {
+ return b.expressionStatement(
+ b.assignmentExpression(
+ '=',
+ b.memberExpression(
+ b.callExpression(
+ b.memberExpression(
+ b.identifier('document'),
+ b.identifier('getElementById')
+ ),
+ [b.literal('__bind__' + name)]
+ ),
+ b.identifier('innerText')
+ ),
+ b.identifier(name)
+ )
+ );
+}
+
+function hasAttributeBinding(name) {
+ let value = '';
+
+ visit(ast, {
+ visitFunctionDeclaration(path) {
+ if (path.node.id.name.startsWith(`__bind__attr__${name}__`)) {
+ value = path.node.id.name;
+ }
+
+ return false; // There will never be a nested fn we need
+ }
+ });
+
+ return value;
+}