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

WIP: Add support for a 'Fragment' tag #38

Open
wants to merge 1 commit into
base: v2
Choose a base branch
from
Open

Conversation

zdenko
Copy link
Contributor

@zdenko zdenko commented Sep 18, 2021

This PR adds support for a fragments.
Currently Jadelet requires a single root element (e.g., HTML element) in the template.
However, there are cases where HTML root element can be redundant, or might produce invalid HTML.
For example, there might be a case where we would like to output the content in the existing <ul> element.

With this PR, we would be able to use a (reserved) word "Fragment" or a symbol <> in the template string.

var template = """
Fragment
  li 1
  li 2
  li 3
"""

var template = """
<>
  li 1
  li 2
  li 3
"""

@STRd6
Copy link
Owner

STRd6 commented Sep 18, 2021

One challenge with using fragments in that they behave a little strangely when being added to the DOM.

f = document.createDocumentFragment()
f.appendChild(document.createElement('p'))
f.childElementCount // => 1
document.body.appendChild(f)
f.childElementCount // => 0

This means we no longer have a reference to the containing element so dynamic content won't work inside document fragments.


A related feature that I've been thinking about adding is a way to register specific tags to arbitrary handlers. That way someone could register document fragments or other element constructors to take more control over customized behavior.

Something like:

ul
  Item(@click @text)
    span Neat!
Jadelet.register
  Item: ({click, text, children}) ->
    el = document.createElement 'li'
    el.onclick = click;
    el.append(text, children...)

    return el

It needs a bit more thought, especially around observable properties and content but I think there might be something to it.

@zdenko
Copy link
Contributor Author

zdenko commented Sep 19, 2021

This means we no longer have a reference to the containing element so dynamic content won't work inside document fragments.

Can you show me an example of this?
Below is the simple example I've tried by using observable.

<!-- html -->
<div id="app"></div>
<ul id="list"></ul>
o = Jadelet.Observable
t = Jadelet.exec
app = document.getElementById("app")
lst = document.getElementById("list")

tpl1 = t(`ul
  li @a 
  li @b 
  li @c`)

tpl2 = t(`Fragment
  li @a 
  li @b 
  li @c`)

x = o(1)

obj = {
  a: o(() => x() + 1),
  b: o(() => x() + 2),
  c: o(() => x() + 3)
}

app.appendChild(tpl1(obj))
lst.appendChild(tpl2(obj))

/*
html result:

<div id="app">
  <ul>
    <li>2</li>
    <li>3</li>
    <li>4</li>
  </ul>
</div>

<ul id="list">
  <li>2</li>
  <li>3</li>
  <li>4</li>
</ul>
*/


x(5)

/*
html result:
<div id="app">
  <ul>
    <li>6</li>
    <li>7</li>
    <li>8</li>
  </ul>
</div>

<ul id="list">
  <li>6</li>
  <li>7</li>
  <li>8</li>
</ul>
*/

@STRd6
Copy link
Owner

STRd6 commented Sep 19, 2021

Try this one:

tpl = t(`Fragment
  @a
  li @b
  li @c`)

@zdenko
Copy link
Contributor Author

zdenko commented Sep 19, 2021

I've got the following result with the same HTML.

tpl1 = t(`li @d`)

tpl2 = t(`Fragment
  @a
  li @b`)

x = o(1)

objA = {
  d: o(() => x() + 1)
}

obj = {
  a: o(() => tpl1(objA)), 
  b: o(() => x() + 2)
}

lst.appendChild(tpl2(obj))

/*
html result:

<div id="app"></div>

<ul id="list">
  <li>2</li>
  <li>3</li>
</ul>
*/

x(5)

/*
html result:

<div id="app"></div>

<ul id="list">
  <li>6</li>
  <li>7</li>
</ul>
*/

So far, it looks like it's working.
But once I applied the template to the <div> element, funny things happened.

tpl3 = t(`ul
  @a
  li @b`)

app.appendChild(tpl3(obj))

/*
html result:

<div id="app">
  <ul>
    <li>6</li>
    <li>7</li>
  </ul>
</div>

<ul id="list">
  <li>7</li>
</ul>
*/

So, the @a from the template disappeared.
I tried to reverse order, and I first appended the template to the <div> element and then to <ul>, but I got the same result.
It looks like when both templates use the same subtemplate, it "disappears" from the first element.

I changed the HTML a bit to check if this is the issue with the DocumentFragment, but the outcome was the same.

<div id="app"></div>
<div id="app2"></div>
app.appendChild(tpl3(obj))

/*
html result

<div id="app">
  <ul>
    <li>2</li>
    <li>3</li>
  </ul>
</div>

<div id="app2"></div>
*/

app2.appendChild(tpl3(obj))

/*
html result

<div id="app">
  <ul>
    <li>3</li>
  </ul>
</div>

<div id="app2">
  <ul>
    <li>2</li>
    <li>3</li>
  </ul>
</div>
*/

I thought this might be connected to the observable functions, so I tried the code without them, and the result is the same as above.

objA = { d: x() + 1 }
obj = { a: tpl1(objA), b: x() + 2 }

Even without the observables, the behavior stays the same.

objA = { d: 1 }
obj = { a: tpl1(objA), b: 2 }

Did I miss something in my code?

@zdenko
Copy link
Contributor Author

zdenko commented Sep 20, 2021

I'm still not sure about the disappearing subtemplate, but here is the working example.

o = Jadelet.Observable
t = Jadelet.exec
app = document.getElementById("app")
lst = document.getElementById("list")
x = o(1)

function objA() {
  var tpl = t(`li @d`),
      obj = {d: o(() => x() + 1)}
  return tpl(obj)
}

function obj() {
  return {
    a: objA(),
    b: o(() => x() + 2)
  }
}

function elm1() {
  var tpl = t(`ul
  @a
  li @b`)
  return tpl(obj())
}

function elm2() {
  var tpl = t(`Fragment
  @a
  li @b`)
  return tpl(obj())
}

function test() {
  app.appendChild(elm1())
  lst.appendChild(elm2())
}

test()

/*
<div id="app">
  <ul>
    <li>2</li>
    <li>3</li>
  </ul>
</div>

<ul id="list">
  <li>2</li>
  <li>3</li>
</ul>
*/

x(5)

/*
<div id="app">
  <ul>
    <li>6</li>
    <li>7</li>
  </ul>
</div>

<ul id="list">
  <li>6</li>
  <li>7</li>
</ul>
*/

@zdenko zdenko changed the title Add support for a 'Fragment' tag WIP: Add support for a 'Fragment' tag Sep 22, 2021
@STRd6
Copy link
Owner

STRd6 commented Mar 3, 2022

I've been thinking...

There may be a way to do this by instead of tracking the parent node, to track the first child in a fragment and the number of sibling nodes added. This assumes that other nodes aren't inserted in between nodes controlled by Jadelet. In this case wrapping the entire template in a document fragment by default would allow for root level siblings and should still be able to handle reactively inserting/removing.

One additional edge case would be to track the parent/previous sibling where the template is appended to the DOM to handle the case where zero nodes are added, or to insert a comment node as a placeholder.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants