git » hoplite.git » commit 959dfd9

Many new stuffs

author Alan Dipert
2021-05-28 06:20:12 UTC
committer Alan Dipert
2021-05-28 06:20:12 UTC
parent 1e4949c0762a65a3632963df18cd797245ea8065

Many new stuffs

el.mjs +78 -16
reactives.mjs +13 -3
todo.mjs +101 -57

diff --git a/el.mjs b/el.mjs
index c6aea31..2089485 100644
--- a/el.mjs
+++ b/el.mjs
@@ -1,19 +1,44 @@
-import { database, view, watch } from './reactives.mjs';
+import { _ } from './datalog.mjs';
+import { Input, Formula, input, database, view, watch } from './reactives.mjs';
+
+const on = new Proxy({}, {
+  get: (obj, name) => Symbol.for(name)
+});
 
 function el(tag, attrs, kids) {
   let el = document.createElement(tag);
-  for (const [k, v] of Object.entries(attrs)) {
-    el.setAttribute(k, v);
+  for (const k of Reflect.ownKeys(attrs)) {
+    let v = attrs[k];
+    if (v instanceof Input || v instanceof Formula) {
+      watch((oldv, newv) => {
+        el.setAttribute(k, newv);
+      })(v);
+    } else if (typeof k === "symbol") { 
+      el.addEventListener(k.description, v)
+    } else {
+      el.setAttribute(k, v);
+    }
   }
   for (const kid of kids) {
-    el.appendChild(kid);
+    if (kid instanceof Input || kid instanceof Formula) {
+      let textNode = document.createTextNode("");
+      watch((oldv, newv) => {
+        textNode.textContent = newv;
+      })(kid);
+      el.appendChild(textNode);
+    } else {
+      el.appendChild(kid);
+    }
   }
   return el;
 }
 
 const $el = new Proxy({}, {
   get: (obj, name) => (attrs = {}, ...kids) => {
-    if (typeof attrs === "string" || attrs instanceof Node) {
+    if (typeof attrs === "string"
+        || attrs instanceof Node
+        || attrs instanceof Input
+        || attrs instanceof Formula) {
       kids.unshift(attrs);
       attrs = {};
     }
@@ -23,26 +48,63 @@ const $el = new Proxy({}, {
   }
 });
 
-// renderChildren lifecycle
-// renderChildren children lifecycle
-// nesting
-function renderChildren(parent, eidView, klass) {
-  let children = new Map();
+function mapEntities(parent, db, eidView, attrs, makeChild) {
+  let pool = [],
+      childEids = new Map();
   watch((added, removed) => {
     for (let [eid] of removed) {
-      parent.removeChild(children.get(eid).el);
-      children.get(eid).destroy();
-      children.delete(eid);
+      let child = [...childEids].filter(([k, v]) => v.value === eid)[0][0];
+      parent.removeChild(child);
     }
     for (let [eid] of added) {
-      children.set(eid, new klass(eid));
-      parent.appendChild(children.get(eid).el)
+      let child;
+      if (pool.length) {
+        child = pool.pop();
+        childEids.get(child).set(eid);
+      } else {
+        let eidCell = input(eid),
+            attrCells = {eid: eidCell};
+        for (let attr of attrs) {
+          attrCells[attr] = input(null);
+          watch((added, removed) => {
+            if (added.size()) {
+              let [[newv]] = added;
+              attrCells[attr].set(newv);
+            } else if (removed.size()) {
+              attrCells[attr].set("");
+            }
+          })(view({
+            find: [_.attrVal],
+            use: [_.eid],
+            where: [[_.eid, attr, _.attrVal]]
+          })(db, eidCell))
+        }
+        child = makeChild(attrCells);
+        childEids.set(child, eidCell);
+      }
+      parent.appendChild(child);
     }
   })(eidView);
   return parent;
 }
 
-export { $el, renderChildren };
+// function renderChildren(parent, eidView, klass) {
+//   let children = new Map();
+//   watch((added, removed) => {
+//     for (let [eid] of removed) {
+//       parent.removeChild(children.get(eid).el);
+//       children.get(eid).destroy();
+//       children.delete(eid);
+//     }
+//     for (let [eid] of added) {
+//       children.set(eid, new klass(eid));
+//       parent.appendChild(children.get(eid).el)
+//     }
+//   })(eidView);
+//   return parent;
+// }
+
+export { $el, on, mapEntities };
 // function renderInto(parent, view, templateFunction) {
 // }
 
diff --git a/reactives.mjs b/reactives.mjs
index fd503ed..4ed91b5 100644
--- a/reactives.mjs
+++ b/reactives.mjs
@@ -222,13 +222,23 @@ class Watch {
     this.callback = callback;
     this.source = source;
     addEdge(depGraph, source, this)
-    this.callback.call(null, this.source.set, new JSONSet());
+    if (this.source instanceof Input
+        || this.source instanceof Formula) {
+      this.callback.call(null, this.source.previousValue, this.source.value)
+    } else {
+      this.callback.call(null, this.source.set, new JSONSet());
+    }
   }
   detach() {
     removeEdge(depGraph, this.source, this);
   }
   update() {
-    this.callback.call(null, this.source.added, this.source.removed)
+    if (this.source instanceof Input
+        || this.source instanceof Formula) {
+      this.callback.call(null, this.source.previousValue, this.source.value)
+    } else {
+      this.callback.call(null, this.source.added, this.source.removed)
+    }
   }
   flush() {
     // NOOP
@@ -255,7 +265,7 @@ function watch(f) {
   return (source) => new Watch(f, source);
 }
 
-export { transaction, input, formula, database, view, watch };
+export { Input, Formula, transaction, input, formula, database, view, watch };
 
 // let A = input(100),
 //     B = input(200),
diff --git a/todo.mjs b/todo.mjs
index e984bb6..8351bf7 100644
--- a/todo.mjs
+++ b/todo.mjs
@@ -1,67 +1,111 @@
 import { _ } from './datalog.mjs';
-import { transaction, database, view, watch } from './reactives.mjs';
-import { $el, renderChildren } from './el.mjs';
-
-let todos = database([
-  [0, "todo/text", "do the thing"],
-  [0, "todo/status", "ready"],
-  [1, "todo/text", "do the other thing"],
-  [1, "todo/status", "ready"],
-  [2, "todo/text", "do that last thing"],
-  [2, "todo/status", "done"]
-]);
-
-class TodoItem {
-  constructor(eid) {
-    this.eid = eid;
-    this.el = $el.li(document.createTextNode(""));
-
-    watch(([[newText]]) => {
-      this.el.firstChild.textContent = newText;
-    })(view({
-      find: [_.text],
-      use: [_.eid],
-      where: [[_.eid, "todo/text", _.text]]
-    })(todos, eid));
-
-    watch(([[newState]]) => {
-      if (newState === "done") {
-        this.el.style = "text-decoration: line-through;";
-      } else {
-        this.el.style = "";
-      }
-    })(view({
-      find: [_.status],
-      use: [_.eid],
-      where: [[_.eid, "todo/status", _.status]]
-    })(todos, eid));
-
-    this.el.addEventListener('click', e => {
-      todos.remove([...todos].filter(([eid]) => eid === this.eid));
-    });
-  }
-  // update([id, text]) {
-  //   this.el.removeChild(this.el.firstChild);
-  //   this.el.appendChild(document.createTextNode(text));
-  // }
-  destroy() {
-    console.log("destroy eid", this.eid);
-    // TODO
-  }
+import { transaction, input, formula, database, view, watch } from './reactives.mjs';
+import { $el, on, mapEntities } from './el.mjs';
+
+function nextEid(db) {
+  return ([...db].map(([eid]) => eid).sort((x, y) => y - x)[0] || 0) + 1; 
 }
 
 function todoList() {
-  return renderChildren(
-    $el.ul(),
-    view({
-      find: [_.id],
-      where: [[_.id, "todo/text", _._]]
-    })(todos),
-    TodoItem
+  let todos = database([
+    [0, "text", "do the thing"],
+    [0, "status", "ready"],
+    [1, "text", "do the other thing"],
+    [1, "status", "ready"],
+    [2, "text", "do that last thing"],
+    [2, "status", "done"]
+  ]);
+  let textInput = $el.input({type: "text"});
+  return $el.div(
+    $el.form({
+      [on.submit]: e => {
+        let newEid = nextEid(todos);
+        todos.add([
+          [nextEid, "text", textInput.value],
+          [nextEid, "status", "ready"]
+        ])
+        e.preventDefault();
+      }
+    },
+      textInput,
+      $el.input({type: "submit", value: "Add"}),
+    ),
+    mapEntities(
+      $el.ul(),
+      todos,
+      view({find: [_.eid], where: [[_.eid, "text", _._]]})(todos),
+      ["text", "status"],
+      ({eid, text, status}) => $el.li(
+        $el.input({
+          type: "button",
+          value: "x",
+          [on.click]: todos.remove([...todos].filter(([x]) => x === eid.value))
+        }),
+        $el.span({
+          style: formula(s => s === "done" ? "text-decoration: line-through" : "")(status),
+          [on.click]: () => transaction(() => {
+            todos.remove([[eid.value, "status", status.value]]);
+            todos.add([[eid.value, "status", status.value === "done" ? "ready" : "done"]]);
+          })
+        }, text)
+      )
+    )
   );
 }
 
-document.body.appendChild(todoList())
+document.addEventListener("DOMContentLoaded", () => {
+  document.body.appendChild(todoList());
+});
+
+// window.todos = todos;
+
+// class TodoItem {
+//   constructor(eid) {
+//     this.eid = eid;
+//     this.el = $el.li(document.createTextNode(""));
+
+//     watch(([[newText]]) => {
+//       this.el.firstChild.textContent = newText;
+//     })(view({
+//       find: [_.text],
+//       use: [_.eid],
+//       where: [[_.eid, "todo/text", _.text]]
+//     })(todos, eid));
+
+//     watch(([[newState]]) => {
+//       if (newState === "done") {
+//         this.el.style = "text-decoration: line-through;";
+//       } else {
+//         this.el.style = "";
+//       }
+//     })(view({
+//       find: [_.status],
+//       use: [_.eid],
+//       where: [[_.eid, "todo/status", _.status]]
+//     })(todos, eid));
+
+//     this.el.addEventListener('click', e => {
+//       todos.remove([...todos].filter(([eid]) => eid === this.eid));
+//     });
+//   }
+//   destroy() {
+//     console.log("destroy eid", this.eid);
+//     // TODO
+//   }
+// }
+
+// function todoList() {
+//   return renderChildren(
+//     $el.ul(),
+//     view({
+//       find: [_.id],
+//       where: [[_.id, "todo/text", _._]]
+//     })(todos),
+//     TodoItem
+//   );
+// }
+
+// document.body.appendChild(todoList())
 
 // let lastId = [...todos.set]
 //     .map(([id]) => id)