Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Engine rebuild with user—side PhantomComponent class #62

Open
sidiousvic opened this issue Jul 27, 2020 · 1 comment
Open

Engine rebuild with user—side PhantomComponent class #62

sidiousvic opened this issue Jul 27, 2020 · 1 comment
Assignees
Labels
🌋 epic This is is gonna take time and effort 🧪 experiment Experimental feature 👻 phantom This applies to the phantom engine

Comments

@sidiousvic
Copy link
Owner

Have tested this and it works. The result is a much more idiomatic user—side Phantom, similar to class—based React, but with key differences:

User lists children by returning an array from a state() method

 children() {
    return [PhantomChild];
  }

User defines state by returning a state object from a state() method

 state() {
    return { message: "🍕" };
  }

User defines markup by returning a template string from a render() method

In this markup, the user...

  • ...must define a @componentName attribute for wrapping element
  • ...can access child components via this.<childComponentName>
  • ...can access state via this.<propertyInUserDefinedState>
 render() {
    return `
    <div @app>
      ${this.Child}
      <p>${this.message}</p>  
    </div>`;
  }

User obtains a reference for every component from Phantom

export const { App, Child } = PHANTOM(PhantomApp);

User can access and update state via top—level component properties

document.addEventListener("click", toggleEmoji);
function toggleEmoji() {
  if (Child.message === "💜")
    Child.update({
      message: "🍕",
    });
  else
    Child.update({
      message: "💜",
    });
}

Prototype

///////////////////// 🔨 PHANTOMCOMPONENT
class PhantomComponent {
  [x: string]: unknown;
  data: any;
  nest: any;
  id: any;
  constructor() {
    this.data = {};
  }

  appear() {}

  update(data: any) {
    for (const [_k, v] of Object.entries(data)) {
      this[_k] = v;
      this.data[_k] = v;
    }
    this.appear();
  }
}

///////////////////// ⚙️ PHANTOM ENGINE
function PHANTOM(Component: any, parent: any = undefined) {
  injectPHANTOMElement();

  const c = new Component();

  if (parent) c.parent = parent;

  c.name = removePhantomPrefixFromName(c);

  const userDefinedState = c.state();
  c.update(userDefinedState);

  addUserDefinedChildrenToNest(c);

  if (c.nest) {
    const nestedApparitions = generateNestedApparitions(c);
    c.update(nestedApparitions);
  }

  c.appear = () => updateNode(c);

  const userDefinedHTML = c.render();
  const componentNode = generateNode(userDefinedHTML);
  if (!parent) document.body.append(componentNode);

  return { [c.name]: c, ...c.nest };
}

///////////////////// 🧰 UTILITIES
function injectPHANTOMElement() {
  if (!document.querySelector("#PHANTOM")) {
    const PHANTOM = document.createElement("div");
    PHANTOM.id = "PHANTOM";
    document.body.appendChild(PHANTOM);
  }
}
function removePhantomPrefixFromName(c: any) {
  return c.constructor.name.replace("Phantom", "");
}
function addUserDefinedChildrenToNest(c: any) {
  const nest: any = {};
  if (c.children)
    c.children().map((Child: any) => {
      const childInstance = PHANTOM(Child, c);
      for (const [_k, v] of Object.entries(childInstance)) {
        nest[_k] = v;
      }
    });
  c.nest = nest;
}
function generateNestedApparitions(c: any) {
  const nestedApparitions: any = {};
  const nest = c.nest;
  for (const [_k] of Object.entries(nest)) {
    const childComponent = nest[_k];
    const childHtml = childComponent.render();
    nestedApparitions[_k] = childHtml;
  }
  return nestedApparitions;
}
function getElementByPhantomId(phantomId: string) {
  let element: Node | null = null;

  function traverseNode(node: Node) {
    if (node.childNodes) {
      node.childNodes.forEach((childNode: ChildNode) => {
        traverseNode(childNode);
      });
    }
    if ((node as HTMLElement).attributes)
      for (const [_k, v] of Object.entries((node as HTMLElement).attributes)) {
        if (v.name === phantomId) element = node;
      }
  }

  traverseNode(document.body);

  return element;
}
function swapNode(swapIn: ChildNode, swapOut: ChildNode | null) {
  swapOut?.replaceWith(swapIn);
  return swapIn;
}
function parseNodeMap(html: string) {
  return html.replace(/>,/g, ">");
}
function updateNode(c: any) {
  // parse render() as a node
  let html = c.render();
  console.log(html);
  const swapIn = generateNode(html);
  console.log(c.name, getElementByPhantomId(`@${c.name.toLowerCase()}`));
  let swapOut = getElementByPhantomId(`@${c.name.toLowerCase()}`);
  console.log("SWAPIN:::", swapIn, "SWAPOUT:::", swapOut);
  swapNode(swapIn as HTMLElement, swapOut);
}
function generateNode(html: string) {
  html = parseNodeMap(html); // sanitize HTML commas
  let doc = new DOMParser().parseFromString(html, "text/html");
  return doc.body.firstChild as HTMLElement;
}

///////////////////// 💻 USER SIDE
class PhantomChild extends PhantomComponent {
  state() {
    return { message: "💜", hearts: [1, 2, 3] };
  }
  render() {
    return `
    <div @child>
      ${(this.hearts as string[]).map(() => `<p>${this.message}</p>`)}
    </div>`;
  }
}

class PhantomApp extends PhantomComponent {
  children() {
    return [PhantomChild];
  }
  state() {
    return { message: "🍕" };
  }
  render() {
    return `
    <div @app>
      ${this.Child}
      <p>${this.message}</p>
    </div>`;
  }
}

export const { App, Child } = PHANTOM(PhantomApp);

console.log("Components 😈:", App, Child);

document.addEventListener("click", toggleEmoji);
function toggleEmoji() {
  if (Child.message === "💜")
    Child.update({
      message: "🍕",
    });
  else
    Child.update({
      message: "💜",
    });
}
@sidiousvic sidiousvic added 👻 phantom This applies to the phantom engine 🧪 experiment Experimental feature 🌋 epic This is is gonna take time and effort labels Jul 27, 2020
@sidiousvic sidiousvic self-assigned this Jul 27, 2020
@sidiousvic
Copy link
Owner Author

@nayelyrodarte check this baby out when you can! No work needed, just familiarize yourself and don't hesitate to share your opinion or ask any questions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🌋 epic This is is gonna take time and effort 🧪 experiment Experimental feature 👻 phantom This applies to the phantom engine
Projects
None yet
Development

No branches or pull requests

1 participant