matthewp / corset

Declarative data bindings, bring your own backend.

Home Page:https://corset.dev/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Child to parent bindings

matthewp opened this issue · comments

Discussed this elsewhere, but custom functions could be made to work with pretty easily. I could see something like this:

registerCustomFunction('--resolve', class {
  constructor() {
    this.state = 'pending';
    this.value = null;
    this.reason = null;
  }
  check([val], { rebind }) {
    Promise.resolve(val).then(resolvedValue => {
      this.value = resolvedValue;
      this.state = 'resolved';
    }).catch(err => {
      this.state = 'rejected';
      this.reason = err;
    });
  }
  call([]) {
    return { value: this.value, state: this.state, reason: this.reason };
  }
});

Which you could then use like this:

sheet`
  .page {
    --promise: --resolve(${Promise.resolve(1)});
    --state: get(var(--promise), state);
    class-toggle[--state]: true;
  }

  .page.pending {
    text: "Waiting";
  }

  .page.rejected {
    --reason: get(var(--promise), reason);
    text: "Oops, there was a problem: " var(--reason);
  }

  .page.resolved {
    --value: get(var(--promise), value);
    text: "We got: " var(--value);
  }
`;

dropping this idea in here from our convo yesterday related to the relationships between elements with behaviors; if I understood what you said correctly a moment ago, the idea above and this one are related at a deeper level in the implementation

omni-present behaviored element relationships


3 steps:

  1. In the parent sheet:
    --var: listen(--foreign-var, select(".element-with-behavior"));
    resolves with the value of --foreign-var from the already behavior-bound element returned by select()

  2. child behavior could have a dispatch fn in the context obj to dispatch --vars

  3. when listen captures in the parent/recipient, call that parent's rebind()

And 3 notes:

  • a. Because we used select() in item 1, this implies pulling data up from a child and should be the recommended use. However, it doesn't prevent a dev from doing this instead if the need comes up, even if it's not something we necessarily want to promote because it could get spaghetti-y:
    --var: listen(--foreign-var, ${domElWithBehaviorFromAnywhere});
    This would replace the need for the idea of "portal" components because the relationship to data is omni-present. If the concept of a portal component is something the dev chooses to use in their implementation anyway (to avoid spaghetti code through convention), this would allow them to build one.

  • b. the 3rd param of listen() could be a fallback like "loading..." or anything

  • c. This differs from the current var() inheritance in a specific and useful way that would be good to document (I don't remember the details but it boiled down to being able to chose one or the other as a way to choose the timing of rebind() updates)

Another idea I've been looking at is inspired by the CSS toggle spec and looks like this:

mount(root, class {
  constructor(props, { stores }) {
    // Can save stores here if you want
  }
  bind(props, { stores }) {
    return sheet`
      #app {
        --app: "testing";
        store-root: app;
      }
      .child {
        store-set: app name "Wilbur";
      }
      .child button {
        event[some-event]: ${() => stores.get('app')?.set('name', 'Anne')};
      }
      .sibling {
        text: store-get(app, name);
      }
      #counter {
        /** Can pass a store to a child behavior */
        behavior: mount(counter, app);
      }
    `;
  }
});

Bikeshedding on the name:

  • store - As shown above, aligns with stores in other frameworks like Svelte.
  • state - Maybe most obvious, but I kind of want to reserve that word for a future FSM feature.
  • local - Like local keyword in some programming languages.
  • map - It's literally a Map, just be direct?

I went with the stores solution. It's available in 0.9.0 (a non-breaking change) and is documented here: https://corset.dev/learn/stores/