lustre-labs / lustre

A Gleam web framework for building HTML templates, single page applications, and real-time server components.
https://hexdocs.pm/lustre
MIT License
948 stars 66 forks source link

🎉 Fragment in client implementation (#16) #99

Closed JScearcy closed 5 months ago

JScearcy commented 5 months ago

Address or start to address #16 Add client support for fragments in vdom

View used for testing #### This is just extending the view from the counter ``` // VIEW ------------------------------------------------------------------------ fn count_fragment(count: Int) { let count_1 = int.to_string(count + 1) let count_2 = int.to_string(count + 2) let count_3 = int.to_string(count + 3) element.fragment([ html.p([], [element.text(count_1)]), html.p([], [element.text(count_2)]), html.p([], [element.text(count_3)]), ]) } fn text_fragment(count: String) { element.fragment([ html.p([], [element.text("text fragment 1")]), html.p([], [element.text("text fragment 2")]), html.p([], [element.text("text fragment 3")]), html.div([], [ html.p([], [element.text("skip nested fragment")]), element.fragment([ html.p([], [element.text("skip nested fragment"), element.text(count)]), html.p([], [element.text("skip nested fragment 2")]), html.p([], [element.text("skip nested fragment 3")]), ]), ]), ]) } fn view(model: Model) -> Element(Msg) { let styles = [#("width", "100vw"), #("height", "100vh"), #("padding", "1rem")] let count = int.to_string(model) element.fragment([ element.fragment([]), ui.centre( [attribute.style(styles)], ui.stack([], [ element.fragment([count_fragment(model), text_fragment(count)]), element.fragment([ ui.button([event.on_click(Incr)], [element.text("+")]), ]), html.p([attribute.style([#("text-align", "center")])], [ element.text(count), ]), element.fragment([html.p([], [element.text("Fragment text")])]), html.p([attribute.style([])], [element.text(count)]), ]), ), element.fragment([]), html.div([], [html.p([], [element.text("top level fragment second child")])]), element.fragment([ html.div([], [ html.p([], [ element.text("top level fragment nested fragment second child"), ]), ]), ]), element.fragment([]), ]) } ```
Output of view used for testing (after incrementing) ```

26

27

28

text fragment 1

text fragment 2

text fragment 3

skip nested fragment

skip nested fragment25

skip nested fragment 2

skip nested fragment 3

25

Fragment text

25

top level fragment second child

top level fragment nested fragment second child

```
View used for testing #### This is just extending the view from the counter with keys ``` // VIEW ------------------------------------------------------------------------ type Person { Person(name: String) } fn person_li() -> Element(msg) { let person_list = [ Person("Test Person"), Person("Person Test"), Person("Person Person"), ] let person_lis = list.map(person_list, fn(person) { case person { Person(name) -> html.li([], [element.text("Person: "), element.text(name)]) } }) element.fragment(person_lis) } fn key_list() -> List(#(String, String)) { [ #("daf8235a-e7f3-4ce2-86aa-6eb1c41f12de", "first keyed"), #("12fbb6c3-ab8c-48db-90ce-e0119dc60f9e", "second keyed"), #("79f4ef78-c2f9-4ffa-9b57-1144639933c2", "third keyed"), ] } fn fragment_key_list() -> List(#(String, String)) { [ #("73f77eac-2495-4a05-90e9-fd0d68fc4891", "first fragment keyed"), #("da50a19f-a22e-4b51-bdba-2274f32127c1", "second fragment keyed"), #("03ce7794-ecee-40c2-9d2f-4366997cd0d1", "third fragment keyed"), ] } fn fragment_key_list_second() -> List(#(String, String)) { [ #("73f77eac-2495-4a05-90e9-fd0d68fc4892", "first second fragment keyed"), #("da50a19f-a22e-4b51-bdba-2274f32127c2", "second second fragment keyed"), #("03ce7794-ecee-40c2-9d2f-4366997cd0d2", "third second fragment keyed"), ] } fn keyed_nodes() { let key_list_tuple = list.map(key_list(), fn(val) { let #(key, label) = val #(key, html.li([], [element.text(label)])) }) element.keyed(html.ul([], _), key_list_tuple) } fn fragment_keyed_nodes() { let key_list_tuple = list.map(fragment_key_list(), fn(val) { let #(key, label) = val let element = html.li([], [element.text(label)]) #(key, element) }) element.keyed(element.fragment, key_list_tuple) } fn fragment_second_keyed_nodes() { let key_list_tuple = list.map(fragment_key_list_second(), fn(val) { let #(key, label) = val let element = html.li([], [element.text(label)]) #(key, element) }) element.keyed(element.fragment, key_list_tuple) } fn view(model: Model) -> Element(Msg) { let styles = [#("width", "100vw"), #("height", "100vh"), #("padding", "1rem")] let count = int.to_string(model) element.fragment([ ui.centre( [attribute.style(styles)], ui.stack([], [ keyed_nodes(), html.p([], [element.text("------")]), element.keyed(html.ul([], _), [ #("a", fragment_keyed_nodes()), #("b", html.li([], [element.text("fragment key sibling b")])), #("c", html.li([], [element.text("fragment key sibling c")])), #("d", fragment_second_keyed_nodes()), #("e", person_li()), ]), ui.button([event.on_click(Incr)], [element.text("+")]), html.p([attribute.style([#("text-align", "center")])], [ element.text(count), ]), html.p([attribute.style([])], [element.text(count)]), ]), ), html.div([], [html.p([], [element.text("top level fragment second child")])]), element.fragment([ html.div([], [ html.p([], [ element.text("top level fragment nested fragment second child"), ]), ]), ]), ]) } ```
Output of view used for testing (after incrementing) ```
  • first keyed
  • second keyed
  • third keyed

------

  • first fragment keyed
  • second fragment keyed
  • third fragment keyed
  • fragment key sibling b
  • fragment key sibling c
  • first second fragment keyed
  • second second fragment keyed
  • third second fragment keyed
  • Person: Test Person
  • Person: Person Test
  • Person: Person Person

35

35

top level fragment second child

top level fragment nested fragment second child

```
JScearcy commented 5 months ago

This should be ok for review - going to bring up a discussion about testing to validate all behavior for any changes

JScearcy commented 5 months ago

Could we add some snapshot tests to this to check the static HTML rendering. It's about the only thing we have tests for so we may as well keep that up.

I added two new snapshot tests - one smoke test for patch, and a render which should validate that a fragments children are rendered in the correct order

hayleigh-dot-dev commented 5 months ago

Oh heck yeah, let's get this thing in! Thank you so much for this work, it's going to be really valuable. 💕