git » hoplite.git » commit 9c5b320

entitiesBy

author Alan Dipert
2021-06-08 05:25:44 UTC
committer Alan Dipert
2021-06-08 05:25:44 UTC
parent 8c2579aa84559b223038f75e05c5e405777df9f2

entitiesBy

el.mjs +103 -29
reactives.mjs +3 -0
todo.mjs +9 -5

diff --git a/el.mjs b/el.mjs
index 1979dfa..93503ba 100644
--- a/el.mjs
+++ b/el.mjs
@@ -1,5 +1,5 @@
 import { _ } from './datalog.mjs';
-import { Input, Formula, input, database, view, watch } from './reactives.mjs';
+import { Input, Formula, input, formula, database, view, watch } from './reactives.mjs';
 
 function el(tag, attrs, kids) {
   let el = document.createElement(tag);
@@ -22,9 +22,8 @@ function el(tag, attrs, kids) {
       })(kid);
       el.appendChild(textNode);
     } else if (kid instanceof EntityNodes) {
-      kid.setCallback(([func, ...args]) => {
-        func.apply(el, args);
-      })
+      // TODO error if any EntityNodes siblings for now
+      kid.bind(el);
     } else {
       el.appendChild(kid);
     }
@@ -77,47 +76,122 @@ function makeAttrCells(db, eidCell) {
   })
 }
 
+function insertSorted(el, nodeToAttrs, sortAttr, node) {
+  if (!el.children.length) {
+    el.appendChild(node);
+  } else if (nodeToAttrs.get(el.children[el.children.length-1])[sortAttr].value <= nodeToAttrs.get(node)[sortAttr].value){
+    el.appendChild(node);
+  } else {
+    for (let i = 0; i < el.children.length; i++) {
+      if (nodeToAttrs.get(el.children[i])[sortAttr].value >= nodeToAttrs.get(node)[sortAttr].value) {
+        el.insertBefore(node, el.children[i]);
+        break;
+      }
+    }
+  }
+}
+
 class EntityNodes {
-  constructor(db, makeNode) {
+  constructor(db, makeNode, sortAttr = input("_eid")) {
     this.db = db;
     this.makeNode = makeNode;
-    this.pool = [];
-    this.nodeToEidCell = new Map();
-    this.eidToNode = new Map();
-    this.bufferedCommands = [];
-    this.callback = command => this.bufferedCommands.push(command);
+    this.sortAttr = sortAttr;
+  }
+  bind(el) {
+    let eidToNode = new Map(),
+        nodeToEidCell = new Map(),
+        nodeToAttrs = new Map(),
+        eidToInsertFormula = new Map(),
+        pool = [];
     watch((added, removed) => {
+      let node;
       for (let [eid] of removed) {
-        let node = this.eidToNode.get(eid);
-        this.eidToNode.delete(eid);
-        this.callback([Node.prototype.removeChild, node]);
+        node = eidToNode.get(eid);
+        el.removeChild(node);
+        eidToNode.delete(eid);
+        eidToInsertFormula.get(eid).detach();
+        eidToInsertFormula.delete(eid);
+        pool.push(node);
       }
       for (let [eid] of added) {
-        let node;
-        if (this.pool.length) {
-          node = this.pool.pop();
+        if (pool.length) {
+          node = pool.pop();
           nodeToEidCell.get(node).set(eid);
+          eidToNode.set(eid, node);
         } else {
           let eidCell = input(eid),
-              attrs = makeAttrCells(db, eidCell);
+              attrs = makeAttrCells(this.db, eidCell);
           node = this.makeNode(attrs);
-          this.eidToNode.set(eid, node);
-          this.nodeToEidCell.set(node, eidCell);
+          eidToNode.set(eid, node);
+          nodeToEidCell.set(node, eidCell);
+          nodeToAttrs.set(node, attrs);
         }
-        this.callback([Node.prototype.appendChild, node]);
+        eidToInsertFormula.set(eid, null);
       }
-    })(view({find: [_.eid], where: [[_.eid]]})(db))
-  }
-  setCallback(f) {
-    this.callback = f;
-    while (this.bufferedCommands.length) {
-      this.callback(this.bufferedCommands.pop());
-    }
+    })(view({find: [_.eid], where: [[_.eid]]})(this.db));
+    watch((oldv, newv) => {
+      for (let [eid, f] of [...eidToInsertFormula]) {
+        if (el.contains(eidToNode.get(eid))) {
+          el.removeChild(eidToNode.get(eid));
+        }
+        f?.detach();
+        eidToInsertFormula.set(eid, formula(attr => {
+          insertSorted(el, nodeToAttrs, newv, eidToNode.get(eid));
+        })(nodeToAttrs.get(eidToNode.get(eid))[newv]));
+      }
+    })(this.sortAttr);
   }
 }
 
+// class EntityNodes {
+//   constructor(db, makeNode) {
+//     this.db = db;
+//     this.makeNode = makeNode;
+//     this.pool = [];
+//     this.nodeToEidCell = new Map();
+//     this.eidToNode = new Map();
+//     this.bufferedCommands = [];
+//     this.callback = command => this.bufferedCommands.push(command);
+//     watch((added, removed) => {
+//       for (let [eid] of removed) {
+//         let node = this.eidToNode.get(eid);
+//         this.eidToNode.delete(eid);
+//         this.callback([Node.prototype.removeChild, node]);
+//       }
+//       for (let [eid] of added) {
+//         let node;
+//         if (this.pool.length) {
+//           node = this.pool.pop();
+//           nodeToEidCell.get(node).set(eid);
+//         } else {
+//           let eidCell = input(eid),
+//               attrs = makeAttrCells(db, eidCell);
+//           node = this.makeNode(attrs);
+//           this.eidToNode.set(eid, node);
+//           this.nodeToEidCell.set(node, eidCell);
+//         }
+//         this.callback([Node.prototype.appendChild, node]);
+//       }
+//     })(view({find: [_.eid], where: [[_.eid]]})(db))
+//   }
+//   setCallback(f) {
+//     this.callback = f;
+//     while (this.bufferedCommands.length) {
+//       this.callback(this.bufferedCommands.pop());
+//     }
+//   }
+// }
+
 function entities(db, makeNode) {
-  return new EntityNodes(db, makeNode);
+  return new EntityNodes(db, makeNode, input("_eid"));
+}
+
+function entitiesBy(db, sortAttr, makeNode) {
+  if (!(sortAttr instanceof Input)
+      && !(sortAttr instanceof Formula )) {
+    sortAttr = input(sortAttr);
+  }
+  return new EntityNodes(db, makeNode, sortAttr)
 }
 
 // function mapEntities(parent, db, eidView, attrs, makeChild) {
@@ -176,7 +250,7 @@ function entities(db, makeNode) {
 //   return parent;
 // }
 
-export { $el, entities };
+export { $el, entities, entitiesBy };
 // function renderInto(parent, view, templateFunction) {
 // }
 
diff --git a/reactives.mjs b/reactives.mjs
index 97a4ac6..06d7280 100644
--- a/reactives.mjs
+++ b/reactives.mjs
@@ -83,6 +83,9 @@ class Formula {
   static isReactiveSource(x) {
     return x instanceof Input || x instanceof Formula;
   }
+  detach() {
+    this.sources.forEach(source => removeEdge(depGraph, source, this));
+  }
   sourceValues() {
     return this.sources.map(x => Formula.isReactiveSource(x) ? x.value : x);
   }
diff --git a/todo.mjs b/todo.mjs
index 91c2460..cd55cf9 100644
--- a/todo.mjs
+++ b/todo.mjs
@@ -1,6 +1,6 @@
 import { query, _, ANY, paths } from './datalog.mjs';
 import { input, formula, database, view } from './reactives.mjs';
-import { $el, entities } from './el.mjs';
+import { $el, entities, entitiesBy } from './el.mjs';
 
 let todos = database([
   [0, "text", "do the thing"],
@@ -22,7 +22,9 @@ let visibleTodos = view({
   ]
 })(todos, showStatus);
 
-let todoList = $el.ul(
+window.by = input("_eid");
+
+let todoList = $el.div(
   $el.select({
     onchange: ({target: {value}}) => {
       showStatus.set(value === "any" ? ANY.VALUE : value);
@@ -32,9 +34,11 @@ let todoList = $el.ul(
     $el.option({value: "ready"}, "Ready"),
     $el.option({value: "done"}, "Done")
   ),
-  entities(visibleTodos, attrs => $el.li(
-    formula((eid, text) => `${eid} - ${text}`)(attrs._eid, attrs.text)
-  ))
+  $el.ul(
+    entitiesBy(visibleTodos, window.by, attrs => $el.li(
+      formula((eid, status, text) => `${eid} - ${status} - ${text}`)(attrs._eid, attrs.status, attrs.text)
+    ))
+  )
 );
 
 document.addEventListener("DOMContentLoaded", () => {