Polight / lego

🚀 Low-Tech Web-Components Made Lightweight & Future-Proof.
https://lego.js.org
MIT License
123 stars 19 forks source link

Missing documentation for passing objects as attributes #23

Open vinyll opened 2 years ago

vinyll commented 2 years ago

Lego supports passing strings, arrays, objects… to children. They should be passed as attributes as so:

<my-child :user="state.user"></my-child>
<script>
  this.init() {
    this.state = { user: {fistname: "John", lastname: "Doe"} }
  }
</script>

Now the <my-child> component can have a state.user that is updated per instance.

-> This should be in the doc

vinyll commented 2 years ago

@mark-russ Regarding your question earlier, would something like this be clear enough or would it need more details?

mark-russ commented 2 years ago

@mark-russ Regarding your question earlier, would something like this be clear enough or would it need more details?

I think this would be immensely helpful!

My knowledge on how frontend frameworks should work is pretty much nonexistent. If I wanted to pass a typed prop (like say, boolean false) from server-to-component, is this where server-side-rendering would come into play?

I would like to use web components from the top-level, passing typed values into them without having to nest them in a main component, which is something that Svelte also seems to lack the capabilities of (Svelte actually has a few interesting quirks because unlike this project, it was not designed with native web-components-first in mind).

vinyll commented 2 years ago

If you use web components in native HTML you can't use advanced features such as <my-child :user="{ firstname: 'John' }"> as HTML does not support : in attributes.

However you could still pass complex object through JS:

some-native-html-page.html

<html>
  <body>
    <my-component my-prop="hey!"></my-component>
   …
  </body>
  <script>
    document.querySelector('my-component').state.user = { firstname: 'John', lastname: 'Doe' }
   // or eventually
   document.querySelector('my-component').setState('user', { firstname: 'John', lastname: 'Doe' })
  </script>
</html>

I haven't tested this but you eventually get the idea of how to get this working.

However if the data your want to inject comes from the server (something like {{ my_json_data }} in your template), you might rather pass some JSON string…

<my-component data="{{ my_json_data }}"></my-component>

Or even using slots might work, and that would be pretty cool:

<my-component>
  {{ my_json_data }}
</my-component>
mark-russ commented 2 years ago

If you use web components in native HTML you can't use advanced features such as <my-child :user="{ firstname: 'John' }"> as HTML does not support : in attributes.

However you could still pass complex object through JS:

some-native-html-page.html

<html>
  <body>
    <my-component my-prop="hey!"></my-component>
   …
  </body>
  <script>
    document.querySelector('my-component').state.user = { firstname: 'John', lastname: 'Doe' }
   // or eventually
   document.querySelector('my-component').setState('user', { firstname: 'John', lastname: 'Doe' })
  </script>
</html>

I haven't tested this but you eventually get the idea of how to get this working.

However if the data your want to inject comes from the server (something like {{ my_json_data }} in your template), you might rather pass some JSON string…

<my-component data="{{ my_json_data }}"></my-component>

Or even using slots might work, and that would be pretty cool:

<my-component>
  {{ my_json_data }}
</my-component>

I will play around with slots! Both of your suggestions seem like good ones. I'll work on figuring out a decent, reusable/convenient way of doing your first suggestion which also seems very viable. Thanks!

vinyll commented 2 years ago

The workaround here would be to wrap the HTML elements in a Lego component and then benefit from the enriched atributes for passing complex objects (:prop).

However when I do need to pass complex objects I do as you suggest, document.querySelector('my-component').setState(myComplexObject).

Another possibility I'm considering is to manipulate a global stateful object inspired from the ContextAPI, but simpler. Brainstorming is required for this point 🤔 💭 🤓

mlbiche commented 1 year ago

I'll try to dive into it in !37 👍

vinyll commented 1 year ago

This topic is probably solved with the Lego Store and a simple real world usage. However this means the doc should show how to:

dgrelaud commented 11 months ago

I had an issue when passing object: [object object] visible in HTML.

Here is what I do to pass object reference directly in the HTML template:

<script>
  export default class extends Lego {
    setup (){
      this.useShadowDOM = false;
    }
    init() {
      this.state = { 
        parentObj : {
          id : 1
        }
      }
    }
    fnToCallFromChild (a) {
      console.log(a);
      console.log(this.state.parentObj);
    }
  }
</script>
<template>
  <child-component my-text="coucou" :my-function="fnToCallFromChild" :my-object="state.parentObj"> </child-componentt>
</template>
<script>
  export default class extends Lego {
    setup (){
      this.useShadowDOM = false;
    }
    init() {
      this.state = { 
        myText     : '',       // passed by copy
        myFunction : () => {}, // passed by reference with :my-function
        myObject   : {}        // passed by reference with :my-object
      }
    }
    onSearch () {
      this.state.myObject.id++;     // modify object reference 
      this.state.myFunction('hey'); // call parent function  
    }
  }
</script>

<template>
  <div>
    <div @click="onSearch">button</div>
    ${state.myText}
  </div>
</template>

It works but it generates this HTML:

<child-component my-text="coucou" my-function="function fnToCallFromChild (a) { console.log(a); }" my-object="[object Object]">
</child-component>

If I understood well, petit-dom calls el.setAttribute(attr, value); in setDOMAttribute of dom.js when generating the Node. It calls setAttribute of Lego component class (which extends HTMLElement). So we can decide if we want to reflect the value to a real attribute in the HTML in Lego code:

I have modified Lego.js like this:

image

With this correction, it reflects the value as an HTML attribute only if it is a string. The visible HTML becomes this:

<child-component my-text="coucou">
</child-component>

I don't know if there is a better way to do this. What do you think about this solution?

vinyll commented 11 months ago

Very good example! 🚀 Passing the reference of the function down to the child so it can call it sounds like a very clean and resilient solution to me as well.

Recently the Store came out and is another way to resolve with a different architecture. With your exemple the fnToCallFromChild would be an action from the Store and the child would simply call it from store.actions.fnToCallFromChild('hey'). This is not a better option and your solution is definitely appropriate, the store is just a central object that offers a common state and actions for instances and other application functions.

Also that's great you have appropriate the Lego core. Component.js is the core web-component function and that outside of petit-dom. I see you compare the value with a string to decide wether or not it should be written as an attribute. That's a very interesting approach and we should double check how it behaves with other types.

dgrelaud commented 11 months ago

Hello,

Thanks for your quick reply. Yes, the store is a great option for other (more complex) scenarios.

Also that's great you have appropriate the Lego core

Yes I love the simplicity of this framework 😍. "Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away" (Antoine de Saint-Exupéry).

I think we could go even further by adding a built-in simple builder to avoid using rollup/parcel/...webpack when we want to assemble all JS components into one compact JS file. And we could compensate the added code by removing some unused code from petit-dom like directives. But that is another topic when I will have more time :)

vinyll commented 11 months ago

Thanks for the feedback, that's an honor to have Saint Exupery quoted for Lego 🙇

adding a built-in simple builder to avoid using rollup/parcel/...webpack when we want to assemble all JS components into one compact JS file

That's a dilema: either build something minimalist or use a heavy bloated builder. I first chose a builder as it was easy (not to be confused with simple) and I suppose that in the future modules will be fully native, so this won't be required at all. Also it's not a strong dependency and can be replace with any other fancy think coming next. That said, I'm very open to simple solutions that could avoid builders.

we could compensate the added code by removing some unused code from petit-dom like directives.

We could cleanup indeed. However the choice here was to take it as is in order to be able to copy paste any further version. However it has not changed since and it's stability and robustness can proove that it's not meant to move, which is a great feature. Open to discussions here too…