matthewp / bram

Web components, live bound templates, in 4kB

Home Page:https://bramjs.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

0.5.0 ideas

matthewp opened this issue · comments

This issue to sketch out ideas for the next version of Bram 0.4 never made it past alpha but I think the next version will be 0.5 anyways.

Will create sub-issues for each of these, for now I just want to get my ideas out. Here's some things I want:

Bram.Element

A goal of 0.4 was to be closer to the standard API. But going that route you really can't have as nice of an API as I think you get from having a super class. Something like:

class MyWidget extends Bram.Element {

}

This is aligned with what Polymer and Skate are doing, and I think it's the right approach now. This raises the question of how to define the template and model now that the user won't be doing it manually. Also how do you decide if the template is inserted into the shadowRoot or directly into the element as children?

static model

One idea is a static model() function that is used to define your model (only if you need to):

class MyWidget extends Bram.Element {
  static model() {
    return {
      foo: 'bar'
    };
  }
}

Which will be set as this.model on the element. Note that a model function is not required since we are using proxys. If you want to target only Proxy-supported browsers you can simply omit this.

static template

I can't think of a better way right now, but since Polymer uses the dom-module idea they don't really have this problem. Skate uses a render() function. I'm thinking:

class MyWidget extends Bram.Element {
  static template() {
    return document.querySelector('#my-template');
  }
}

I think we'll accept here an HTMLTemplateElement, a string that acts as a selector, or a renderer function.

Properties

I've found writing getter/setters manually to be a lot of boilerplate when all you want them to do is set the model. We might want a properties method that allows you to define some properties that automatically sync with the model (and possibly attributes).

I'm a little unsure about this as the boilerplate isn't all that bad.

Events

This one we definitely do want. First in the template, I'm thinking of a form of:

<a href="/foo" onclick="doSomething()">Click me</a>

With on serving as a prefix that says "this is an event". We'll need some way to force attribute usage when there is an attribute that happens to have on as a prefix. Not sure what that should be, maybe $ like <a href="/foo" $oneweirdattr="bar">Click me</a>. I think this is a reasonable compromise.

Secondly with events I definitely do want a way to support the onclick type of properties that most DOM elements already support. So something like:

class MyWidget extends Bram.Element {
  static events() {
    return [ "customevent", "another" ]
  }
}

Extending elements other than HTMLElement

Since Bram.Element will extend HTMLElement there needs to be another way to extend other elements. I think maybe Bram can be used as a function like:

Class MyInput extends Bram(HTMLInputElement) {

}

This feels pretty nice to me.

static get renderMode() {
  return "shadow" | "child"; // renders to shadow or light dom
}

In Bram 0.4 templates are rendered with a special model like so:

class MyWidget extends HTMLElement {
  connectedCallback() {
    let hydrate = Bram.template(document.querySelector("#my-template"));
    let model = Bram.model({ hello: 'world' });

    this.shadowRoot.appendChild(hydrate(model));

    model.hello = 'to you!';
  }
}

The model is a Proxy object that we listen to changes to so that we know to rerender.

This works really well, but with events I think you really want the element to be the scope of the template:

<template>
  <a href="#" onclick="doSomething()">Click me</a>
</template>
class MyWidget extends Bram.Element {
  doSomething() {
    // I get called when the anchor is clicked!
  }
}

So making the element the scope of the template presents a challenge. Namely, how do we know when a property on the element changes? With Proxys we know everything; but this is because we create a new object.

To make this work without create a new model I think we would have to Object.defineProperty every property on the element. Something like:

class MyWidget extends Bram.Element {
  constructor() {
    super();

    this.count = 0;
  }
}

We'd have to wrap this.count so that we know when it changes. I'm a little worried about the performance costs of looping through every property on an element to wrap it.

Another, perhaps easier, option is to create a Bram.model() like before, but add any of the element's functions to that model.

Got this working:

<template id="click-template">
  <button type="button" on-click="inc">Click me</button>

  <h2 class="count">{{count}}</h2>
</template>
class ClickCount extends Bram.Element {
  static template() {
    return document.querySelector('#click-template');
  }

  constructor() {
    super();
    this.count = 0;
  }

  inc() {
    this.count++;
  }
}