git » hoplite.git » commit 34be154

renderChildren

author Alan Dipert
2021-04-21 06:22:14 UTC
committer Alan Dipert
2021-04-21 06:22:14 UTC
parent 880ed9787e94e77bff80e4035571c01631e6065f

renderChildren

datalog.mjs +4 -1
el.js +0 -13
el.mjs +61 -0
index.html +1 -0
reactives.js => reactives.mjs +4 -0
tests.mjs +41 -5
todo.html +10 -0
todo.js +0 -150
todo.mjs +173 -0

diff --git a/datalog.mjs b/datalog.mjs
index 0568d0b..7e77a70 100644
--- a/datalog.mjs
+++ b/datalog.mjs
@@ -64,6 +64,9 @@ class JSONSet {
   size() {
     return Object.keys(this.values).length;
   }
+  toArray() {
+    return [...this];
+  }
 }
 
 class Relation {
@@ -200,7 +203,7 @@ function partialQuery(q) {
   return (...vals) => query(q, ...vals);
 }
 
-export { _, query };
+export { _, query, JSONSet, partialQuery };
 // let db = new JSONSet([
 //   ["sally", "age", 21],
 //   ["fred", "age", 42],
diff --git a/el.js b/el.js
deleted file mode 100644
index 8f6df05..0000000
--- a/el.js
+++ /dev/null
@@ -1,13 +0,0 @@
-function renderInto(parent, view, templateFunction) {
-}
-
-renderInto($el.ol, view({
-  find: [_.id],
-  where: [[_.id _._ _._]]
-}, todos, ([idCell]) => $.li(
-  {class: "todo"},
-  view({
-    find: [_.text],
-    use: [_.id],
-  })
-)))
diff --git a/el.mjs b/el.mjs
new file mode 100644
index 0000000..3e924dc
--- /dev/null
+++ b/el.mjs
@@ -0,0 +1,61 @@
+import { database, view, watch } from './reactives.mjs';
+
+function el(tag, attrs, kids) {
+  let el = document.createElement(tag);
+  for (const [k, v] of Object.entries(attrs)) {
+    el.setAttribute(k, v);
+  }
+  for (const kid of kids) {
+    el.appendChild(kid);
+  }
+  return el;
+}
+
+const $el = new Proxy({}, {
+  get: (obj, name) => (attrs = {}, ...kids) => {
+    if (typeof attrs === "string" || attrs instanceof Node) {
+      kids.unshift(attrs);
+      attrs = {};
+    }
+    return el(name, attrs, kids.map(kid => {
+      return typeof kid === "string" ? document.createTextNode(kid) : kid;
+    }));
+  }
+});
+
+function renderChildren(parent, view, constructor) {
+  let children = new Map();
+  watch((added, removed) => {
+    for (let [key] of removed) {
+      if (children.has(key)) {
+        parent.removeChild(children.get(key).element);
+        children.delete(key);
+      }
+    }
+    for (let tuple of added) {
+      let [key] = tuple;
+      if (!children.has(key)) {
+        children.set(key, constructor(tuple));
+        parent.appendChild(children.get(key).element)
+      } else {
+        children.get(key).update(children.get(key).element, tuple);
+      }
+    }
+  })(view);
+  return parent;
+}
+
+export { $el, renderChildren };
+// function renderInto(parent, view, templateFunction) {
+// }
+
+// renderInto($el.ol, view({
+//   find: [_.id],
+//   where: [[_.id _._ _._]]
+// }, todos, ([idCell]) => $.li(
+//   {class: "todo"},
+//   view({
+//     find: [_.text],
+//     use: [_.id],
+//   })
+// )))
diff --git a/index.html b/index.html
index 05f1c45..0f6f22f 100644
--- a/index.html
+++ b/index.html
@@ -7,6 +7,7 @@
     <script src="qunit-2.9.2.js"></script>
     <script type="module" src="datalog.mjs"></script>
     <script type="module" src="reactives.mjs"></script>
+    <script type="module" src="el.mjs"></script>
     <script type="module" src="tests.mjs"></script>
     <!-- <script src="relcells.js"></script> -->
     <!-- <script src="el.js"></script> -->
diff --git a/reactives.js b/reactives.mjs
similarity index 98%
rename from reactives.js
rename to reactives.mjs
index 7e1d3ef..fd503ed 100644
--- a/reactives.js
+++ b/reactives.mjs
@@ -1,3 +1,5 @@
+import { JSONSet, partialQuery } from './datalog.mjs';
+
 const depGraph = new Map(),
       suspended = new Set();
 let inTransaction = false;
@@ -253,6 +255,8 @@ function watch(f) {
   return (source) => new Watch(f, source);
 }
 
+export { transaction, input, formula, database, view, watch };
+
 // let A = input(100),
 //     B = input(200),
 //     C = formula(console.log)(A, B);
diff --git a/tests.mjs b/tests.mjs
index a3b15ac..4d1dd09 100644
--- a/tests.mjs
+++ b/tests.mjs
@@ -1,10 +1,46 @@
-import { _, query } from './datalog.mjs';
+import { _, query, JSONSet } from './datalog.mjs';
 const { module, test } = QUnit;
 
-const add = (a, b) => a + b;
+QUnit.assert.queryEquals = function(expect, q, ...args) {
+};
 
-module('add', () => {
-  test('should add two numbers', assert => {
-    assert.equal(add(1, 1), 2, '1 + 1 = 2');
+module('datalog', () => {
+
+  let db1 = new JSONSet([
+    ["sally", "age", 21],
+    ["fred", "age", 42],
+    ["ethel", "age", 42],
+    ["fred", "likes", "pizza"],
+    ["sally", "likes", "opera"],
+    ["ethel", "likes", "sushi"]
+  ]);
+
+  let db2 = new JSONSet([
+    ["ethel", "sex", "female"],
+    ["fred", "sex", "male"]
+  ]);
+
+  test('simple query', assert => {
+    assert.deepEqual(
+      query({
+        find: [_.e],
+        use: [[_.db]],
+        where: [
+          [_.db, _.e, "age", 42]
+        ]
+      }, db1).toArray(),
+      [["fred"], ["ethel"]]
+    );
   });
+  test('implicit database', assert => {
+    assert.deepEqual(
+      query({
+        find: [_.e],
+        where: [
+          [_.e, "age", 42]
+        ]
+      }, db1).toArray(),
+      [["fred"], ["ethel"]]
+    );
+  })
 });
diff --git a/todo.html b/todo.html
new file mode 100644
index 0000000..9a1e14b
--- /dev/null
+++ b/todo.html
@@ -0,0 +1,10 @@
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width">
+    <title>hoplite todo</title>
+    <script type="module" src="todo.mjs"></script>
+  </head>
+  <body>
+  </body>
+</html>
diff --git a/todo.js b/todo.js
deleted file mode 100644
index 609fc7e..0000000
--- a/todo.js
+++ /dev/null
@@ -1,150 +0,0 @@
-let todos = new 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"]
-]);
-
-let lastId = [...todos.set]
-    .map(([id]) => id)
-    .sort((a, b) => b - a)
-    .shift();
-
-let transitions = new database([
-  ["ready", "done"],
-  ["done", "ready"]
-]);
-
-function todoRemove(id) {
-  todos.remove([...todos.set].filter(([eid]) => eid === id));
-}
-
-function todoAdd(text) {
-  let id = ++lastId;
-  todos.add([
-    [id, "todo/text", text],
-    [id, "todo/status", "ready"]
-  ]);
-}
-
-function todoToggle(id) {
-  let [[from, to]] = query({
-    find: [_.from, _.to],
-    use: [[_.db1, _.db2], _.id],
-    where: [
-      [_.db1, _.id, "todo/status", _.from],
-      [_.db2, _.from, _.to]
-    ]
-  }, todos.set, transitions.set, id);
-  transaction(() => {
-    todos.remove([[id, "todo/status", from]]);
-    todos.add([[id, "todo/status", to]]);
-  });
-}
-
-let todoIds = view({
-  find: [_.id],
-  where: [[_.id, _._, _._]]
-})(todos);
-
-let subs = new WeakMap();
-
-function makeTodoLi(ul, id) {
-  let txt = document.createTextNode(""),
-      li = document.createElement("li"),
-      deleteButton = document.createElement("button"),
-      viewer = view({
-        find: [_.status, _.text],
-        use: [_.id],
-        where: [
-          [_.id, "todo/status", _.status],
-          [_.id, "todo/text", _.text]
-        ]
-      })(todos, id),
-      updater = watch((added) => {
-        if (added.size()) {
-          let [[status, text]] = added;
-          txt.textContent = text;
-        }
-      })(viewer);
-  li.appendChild(txt);
-  li.dataset.todoId = id;
-  deleteButton.innerText = "delete";
-  li.appendChild(deleteButton);
-  ul.appendChild(li);
-  subs.set(li, [viewer, updater]);
-}
-
-function destroyTodoLi(ul, id) {
-  let li = ul.querySelector(`li[data-todo-id='${id}']`)
-  subs.get(li).forEach(x => x.detach());
-  ul.removeChild(li);
-}
-
-document.addEventListener("DOMContentLoaded", () => {
-  let ul = document.getElementById("todos-ul");
-  watch((added, removed) => {
-    for (let [id] of added) makeTodoLi(ul, id);
-    for (let [id] of removed) destroyTodoLi(ul, id);
-  })(todoIds);
-});
-// function findTodoLi(ul, id) {
-//   for (let li of ul.children) {
-//     if (li.dataset.id == id) {
-//       return li;
-//     }
-//   }
-// }
-
-// let showStatus = input(ANY.VALUE);
-// // let showStatus = input("ready");
-
-// let visibleTodos = view({
-//   find: [_.id, _.status, _.text],
-//   use: [_.status],
-//   where: [
-//     [_.id, "todo/text", _.text],
-//     [_.id, "todo/status", _.status]
-//   ]
-// })(todos, showStatus);
-
-// let todoTexts = view({
-//   find: [_.id, _.text],
-//   where: [[_.id, _._, _.text]]
-// })(visibleTodos);
-
-// let todoStatuses = view({
-//   find: [_.id, _.status],
-//   where: [[_.id, _.status, _._]]
-// })(visibleTodos);
-
-// document.addEventListener("DOMContentLoaded", () => {
-//   let ul = document.getElementById("todos-ul"),
-//       input = document.getElementById("todos-new"),
-//       add = document.getElementById("todos-add");
-//   add.addEventListener("click", () => {
-//     addTodo(input.value);
-//     input.value = "";
-//   });
-//   watch((added, removed) => {
-//     for (let [id, text] of added) {
-//       let li = findTodoLi(ul, id);
-//       if (!li) {
-//         li = document.createElement("li");
-//         li.appendChild(document.createTextNode(text));
-//         li.dataset.id = id;
-//         li.addEventListener("click", () => removeTodo(id));
-//         ul.appendChild(li);
-//       }
-//     }
-//     for (let [id] of removed) {
-//       for (let child of ul.children) {
-//         if (child.dataset.id == id) {
-//           ul.removeChild(child);
-//         }
-//       }
-//     }
-//   })(todoTexts);
-// });
diff --git a/todo.mjs b/todo.mjs
new file mode 100644
index 0000000..caa72b0
--- /dev/null
+++ b/todo.mjs
@@ -0,0 +1,173 @@
+import { _ } from './datalog.mjs';
+import { database, view } 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"]
+]);
+
+function todoList() {
+  return renderChildren(
+    $el.ul(),
+    view({
+      find: [_.id, _.text],
+      where: [[_.id, "todo/text", _.text]]
+    })(todos),
+    ([id, text]) => ({
+      element: $el.li(text),
+      update: (el, [id, text]) => {
+        el.removeChild(el.firstChild);
+        el.appendChild(document.createTextNode(text));
+      }
+    })
+  );
+}
+
+document.body.appendChild(todoList())
+
+// let lastId = [...todos.set]
+//     .map(([id]) => id)
+//     .sort((a, b) => b - a)
+//     .shift();
+
+// let transitions = new database([
+//   ["ready", "done"],
+//   ["done", "ready"]
+// ]);
+
+// function todoRemove(id) {
+//   todos.remove([...todos.set].filter(([eid]) => eid === id));
+// }
+
+// function todoAdd(text) {
+//   let id = ++lastId;
+//   todos.add([
+//     [id, "todo/text", text],
+//     [id, "todo/status", "ready"]
+//   ]);
+// }
+
+// function todoToggle(id) {
+//   let [[from, to]] = query({
+//     find: [_.from, _.to],
+//     use: [[_.db1, _.db2], _.id],
+//     where: [
+//       [_.db1, _.id, "todo/status", _.from],
+//       [_.db2, _.from, _.to]
+//     ]
+//   }, todos.set, transitions.set, id);
+//   transaction(() => {
+//     todos.remove([[id, "todo/status", from]]);
+//     todos.add([[id, "todo/status", to]]);
+//   });
+// }
+
+// let todoIds = view({
+//   find: [_.id],
+//   where: [[_.id, _._, _._]]
+// })(todos);
+
+// let subs = new WeakMap();
+
+// function makeTodoLi(ul, id) {
+//   let txt = document.createTextNode(""),
+//       li = document.createElement("li"),
+//       deleteButton = document.createElement("button"),
+//       viewer = view({
+//         find: [_.status, _.text],
+//         use: [_.id],
+//         where: [
+//           [_.id, "todo/status", _.status],
+//           [_.id, "todo/text", _.text]
+//         ]
+//       })(todos, id),
+//       updater = watch((added) => {
+//         if (added.size()) {
+//           let [[status, text]] = added;
+//           txt.textContent = text;
+//         }
+//       })(viewer);
+//   li.appendChild(txt);
+//   li.dataset.todoId = id;
+//   deleteButton.innerText = "delete";
+//   li.appendChild(deleteButton);
+//   ul.appendChild(li);
+//   subs.set(li, [viewer, updater]);
+// }
+
+// function destroyTodoLi(ul, id) {
+//   let li = ul.querySelector(`li[data-todo-id='${id}']`)
+//   subs.get(li).forEach(x => x.detach());
+//   ul.removeChild(li);
+// }
+
+// document.addEventListener("DOMContentLoaded", () => {
+//   let ul = document.getElementById("todos-ul");
+//   watch((added, removed) => {
+//     for (let [id] of added) makeTodoLi(ul, id);
+//     for (let [id] of removed) destroyTodoLi(ul, id);
+//   })(todoIds);
+// });
+// function findTodoLi(ul, id) {
+//   for (let li of ul.children) {
+//     if (li.dataset.id == id) {
+//       return li;
+//     }
+//   }
+// }
+
+// let showStatus = input(ANY.VALUE);
+// // let showStatus = input("ready");
+
+// let visibleTodos = view({
+//   find: [_.id, _.status, _.text],
+//   use: [_.status],
+//   where: [
+//     [_.id, "todo/text", _.text],
+//     [_.id, "todo/status", _.status]
+//   ]
+// })(todos, showStatus);
+
+// let todoTexts = view({
+//   find: [_.id, _.text],
+//   where: [[_.id, _._, _.text]]
+// })(visibleTodos);
+
+// let todoStatuses = view({
+//   find: [_.id, _.status],
+//   where: [[_.id, _.status, _._]]
+// })(visibleTodos);
+
+// document.addEventListener("DOMContentLoaded", () => {
+//   let ul = document.getElementById("todos-ul"),
+//       input = document.getElementById("todos-new"),
+//       add = document.getElementById("todos-add");
+//   add.addEventListener("click", () => {
+//     addTodo(input.value);
+//     input.value = "";
+//   });
+//   watch((added, removed) => {
+//     for (let [id, text] of added) {
+//       let li = findTodoLi(ul, id);
+//       if (!li) {
+//         li = document.createElement("li");
+//         li.appendChild(document.createTextNode(text));
+//         li.dataset.id = id;
+//         li.addEventListener("click", () => removeTodo(id));
+//         ul.appendChild(li);
+//       }
+//     }
+//     for (let [id] of removed) {
+//       for (let child of ul.children) {
+//         if (child.dataset.id == id) {
+//           ul.removeChild(child);
+//         }
+//       }
+//     }
+//   })(todoTexts);
+// });