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

Missing documentation for passing objects as attributes #23

Open
vinyll opened this issue Nov 21, 2021 · 11 comments
Open

Missing documentation for passing objects as attributes #23

vinyll opened this issue Nov 21, 2021 · 11 comments
Labels
enhancement New feature or request

Comments

@vinyll
Copy link
Member

vinyll commented Nov 21, 2021

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 vinyll added the enhancement New feature or request label Nov 21, 2021
@vinyll
Copy link
Member Author

vinyll commented Nov 21, 2021

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

@mark-russ
Copy link
Contributor

mark-russ commented Nov 21, 2021

@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
Copy link
Member Author

vinyll commented Nov 21, 2021

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
Copy link
Contributor

mark-russ commented Nov 21, 2021

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
Copy link
Member Author

vinyll commented Aug 13, 2022

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
Copy link

mlbiche commented Mar 1, 2023

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

@vinyll
Copy link
Member Author

vinyll commented Nov 5, 2023

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
Copy link

dgrelaud commented Dec 5, 2023

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:

  • Parent code:
<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>
  • child-component:
<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
Copy link
Member Author

vinyll commented Dec 5, 2023

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
Copy link

dgrelaud commented Dec 6, 2023

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
Copy link
Member Author

vinyll commented Dec 6, 2023

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…

dgrelaud added a commit to carboneio/lego that referenced this issue Dec 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants