WebReflection / hyperHTML

A Fast & Light Virtual DOM Alternative
ISC License
3.07k stars 113 forks source link

Pass data binding through JavaScript Properties #240

Closed ghost closed 6 years ago

ghost commented 6 years ago

I am starting to port a collection of Polymer web components to VanillaJS with ES6 string templates. I have been evaluating hyperHTML for a while and would prefer to move forward with it vs. Lit-Html, but Lit has one significant feature - the extended Lit library can pass data bindings through JavaScript properties in addition to attributes.

Are you considering any future support in hyperHTML to using property binding rather than attribute binding?

sourcegr commented 6 years ago

+1 for such a feature. I, too, would really love to see that.

WebReflection commented 6 years ago

Imagine I don't know anything about lit-html, what is it that you are missing, exactly?

With hyperHTML you can pass data right away without ever interfering with natural/real DOM:

hyper()`<p data=${anythingYouWant_really}>hello data</p>`

is that what you are looking for?

pinguxx commented 6 years ago

by data you mean dataset ?, i dont think data interfiere with it... but i may be wrong... if you have get/set you can re-render the element, or on the attribute change just update based on the name and it will also render, im not sure i get what is missing

ghost commented 6 years ago

There is one easy and simple justification for this. What if I want to use custom elements built by someone that expect me to be able to pass complex data through a JavaScript property other than the data property?

Why would I want to use a template library where custom element developers have to develop their interface to accommodate the template library instead of the other way around?

Lit-Html allows me to bind to JavaScript properties and/or attributes allowing me to use any custom element pretty much written by anyone. To use hyperHtml without this feature would mean that the only custom elements that will support binding complex data will be custom elements written pretty much specifically be used with hyperHtml.

joshgillies commented 6 years ago

@chrismbeckett maybe I'm missing something, but the following suggests that hyperHTML has exactly the behaviour you require:

Handling data

hyperHTML will pass data to an element as properties, as long as the property is defined on the element's prototype. Otherwise it will fallback to passing data as attributes.

https://custom-elements-everywhere.com/#hyperhtml

ghost commented 6 years ago

@joshgillies That would be great if it was true? The documentation on the viperHTML/hyperHTML website suggests that data binding uses attributes, and that if you want to pass a raw JS object down to an element you should use the data property (and that no JSON serialization is required).

https://viperhtml.js.org/hyperhtml/documentation/#essentials-4

joshgillies commented 6 years ago

@chrismbeckett what leads you to believe the statement isn't true? If I'm not mistaken, the custom-elements-everywhere repo has tests highlighting the desired behaviour within hyperHTML. https://github.com/webcomponents/custom-elements-everywhere/blob/master/libraries/hyperhtml/src/advanced-tests.js#L51

WebReflection commented 6 years ago

A post is missing but I'd like to clarify about data. The reason I've chosen data is exactly because developers knows the data- namespace is reserved for them, but data itself is just a regular attribute that W3C will never reserve for anything else so I think it was a safe, and semantic way, to tell developers "hey, if you need to pass data, guess what kind of special attribute I've got for ya".


back to the rest ...

That would be great if it was true?

That is true. The issue is that you took hyperHTML default attribute behavior over regular DOM nodes, where setting directly and randomly properties or objects is meaningless, confusing it with Custom Elements that provides already everything you need.

As example, I use HyperHTMLElement to simplify my life but anyone can simply use any other Custom Element based library or define own components and specify the get/set behavior.

customElements.define(
  'my-own',
  class extends HTMLElement {
    get anything() {
      return this._anything;
    }
    set anything(value) {
      this._anything = value;
      this.textContent = value.text;
    }
  }
);

hyperHTML(document.body)`
<my-own anything=${{text: 'hello world'}}/>`;

You can see the live demo here.

TL;DR the biggest strength of hyperHTML is that does not lock you in, it frees you from dogmas and let you do through the platform everything the platform can do.

jiayihu commented 6 years ago

This also should be in the docs, otherwise, people may mistakenly prefer lit-html because it's clearly shown in the latter docs how to do it.

Allowing to declaratively passing/binding not-primitive values is also one primary concern of anyone, myself included, approaching Web Components and using template literals instead of JSX.

From the docs and some quick-look to other issues, I was also deceived that using data was the only way since I thought that hyperHTML binds using only setAttribute and data was the unique exception to this rule, provided by the lib.

I also took a step forward and already made a version of the Custom Element example where data is passed as object: Codepen.

customElements.define(
  'h-welcome',
  class HyperWelcome extends HTMLElement {
    constructor(...args) {
      super(...args);
      this.html = hyperHTML.bind(this);
    }

    get user() {
      return this._user;
    }

    set user(value) {
      this._user = value;
    }

    connectedCallback() { this.render(); }

    render() {
      return this.html`<h1>Hello, ${this._user.name}</h1>`;
    }
  }
);

hyperHTML.bind(document.getElementById('root'))`
  <h-welcome user=${{ name: 'Sara' }}></h-welcome>
  <h-welcome user=${{ name: 'Cahal' }}></h-welcome>
  <h-welcome user=${{ name: 'Edite' }}></h-welcome>
`;
WebReflection commented 6 years ago

@jiayihu some minor, but very important, suggestion

customElements.define(
  'h-welcome',
  class HyperWelcome extends HTMLElement {
    // watch out, the correct way is the followiong one
    // this would work with any kind of CE polyfill too
    // which is not a hyperHTML issue but a browsers one
    constructor(...args) {
      const self = super(...args);
      self.html = hyperHTML.bind(self);
      return self;
    }

    get user() {
      return this._user;
    }

    // if you want to react to changes through attributes
    // just render right away when such changes happen
    set user(value) {
      this._user = value;
      this.render();
    }

    render() {
      return this.html`<h1>Hello, ${this._user.name}</h1>`;
    }
  }
);

// you don't need the redundant </close-tag>
// when there is no content inside the node
hyperHTML.bind(document.getElementById('root'))`
  <h-welcome user=${{ name: 'Sara' }} />
  <h-welcome user=${{ name: 'Cahal' }} />
  <h-welcome user=${{ name: 'Edite' }} />
`;
jiayihu commented 6 years ago

if you want to react to changes through attributes just render right away when such changes happen

Ops right, I forgot it!

About the rest of comments...well I took the same code from the official example 😅

What do you think however about adding the example to both the documentation Essentials and Examples?

WebReflection commented 6 years ago

PR's welcome there too 😅 https://github.com/viperHTML/viperhtml.github.io