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

browser.template.Template should NOT render the outer HTML #2240

Open
Tracked by #2250
denis-migdal opened this issue Sep 20, 2023 · 9 comments
Open
Tracked by #2250

browser.template.Template should NOT render the outer HTML #2240

denis-migdal opened this issue Sep 20, 2023 · 9 comments

Comments

@denis-migdal
Copy link
Contributor

denis-migdal commented Sep 20, 2023

Hi,

Currently, browser.template.Template is rendering the outer HTML.
However, this creates bugs and the need for dirty hacks :

content = TEMPLATE.clone().content

# 1. Template can't work on documentFragment or on any element that hasn't a father (e.g. newly created element not yet inserted into the DOM/another element).
# "self.parentElement is not defined"
h4ck = html.DIV()
h4ck <= content
elem = html.DIV( h4ck )

parameters = {k[5:] : v for k, v in this.attrs.items() if k.startswith("data-")}
Template(h4ck).render(**parameters)

children = h4ck.childNodes

# 2. can't use Template directly on a custom element, else we might have an infinite loop
# cf https://github.com/brython-dev/brython/issues/2236
this <= children

browser.template.Template should instead :

  1. make a copy of the template element.
  2. render the inner HTML of the current element, using the inner HTML of the copied template element.
  3. (optionally?) render the attributes, one by one, of the current element using the attributes of the copied template element.

This would solve issue #2236 as well as preventing the little dirty h4ck 1 to 2.

Cordially,

@denis-migdal
Copy link
Contributor Author

Another bug found in Template : due to the dirty behavior of browsers with table related tag, we need to put them in a table before giving them to a Template :

content = TEMPLATE.clone().content
			
h4ck = tagtype()
h4ck <= content

elem = None

if tagtype in [html.TD, html.TR, html.TBODY, html.TFOOT, html.THEAD]:

  cur_elem = h4ck
  tag = tagtype
  if tag == html.TD:
	  tag = html.TR
	  cur_elem = tag( cur_elem )
	  if elem == None:
		  elem = cur_elem
  if tag == html.TR:
	  tag = html.TBODY
	  cur_elem = tag( cur_elem )
	  if elem == None:
		  elem = cur_elem
  
  cur_elem = html.TABLE( cur_elem )
  if elem == None:
	  elem = cur_elem
else:
  elem = html.DIV( h4ck )

parameters = {k[5:] : v for k, v in this.attrs.items() if k.startswith("data-")}
Template(h4ck).render(**parameters)

children = elem.children[0].childNodes

this <= children

It could be great if Template either :

  • Template print a warning in the console if a table related element is given without being in a table (and maybe some information in the documentation).
  • Template create the parents (if they don't exists) before rendering, then removing them once rendered ?

@denis-migdal
Copy link
Contributor Author

denis-migdal commented Sep 27, 2023

Okay, found another issue :

If no substitutions are done, the result is a text containing only line breaks.

content = self._TEMPLATE.clone().content
			
h4ck = html.DIV() if tagtype == DOMNode else tagtype()
h4ck <= content
                    # ....
elem = html.DIV( h4ck )

parameters = {k[5:] : v for k, v in self.attrs.items() if k.startswith("data-")}
Template(h4ck).render(**parameters)

children = elem.children[0].childNodes
if len(children) == 1 and children[0].text.count('\n') == len( children[0].text ):
     children = h4ck.childNodes

self <= children

@denis-migdal
Copy link
Contributor Author

Found a new bug :
If we pass a body to Template we get the following error message :

Traceback (most recent call last):
  File "http://localhost:3000/components/components.py", line 100, in <module>
    g.generate({"e": 44}) # remplace the element by a new one.
      ^^^^^^^^^^^^^^^^^^^
  File "http://localhost:3000/components/components.py", line 50, in generate
    Template(h4ck).render(**values)
                   ^^^^^^^^^^^^^^^^
  File "VFS.browser.template.py", line 306, in render
    for element in self.element.select("*[b-on]"):
                                ^^^^^^^^^^^^^^^^^
TypeError: DOMNode object doesn't support selection by selector brython.js:14610:56

@denis-migdal
Copy link
Contributor Author

denis-migdal commented Sep 28, 2023

I also wrote a new class that should be safer than Template (?) :

Usage :

<div id='test_a' data-test="{e}"><span>{e}</span></div>
<div id='test_b' data-test="{e}"><span>{e}</span></div>

<div data-test={e}>
	<div id='test_c' data-test="{e}"><span>{e}</span></div>
</div>
t = document.querySelector('#test_a');
g = Generator(target=t)
g.generate({"e": 42}) # change the children + attrs

t = document.querySelector('#test_b');
g = Generator(content=[t])
print( g.generate({"e": 43}) ) # create new elements

t = document.querySelector('#test_c');
g = Generator(target=t.parentElement, content=[t])
g.generate({"e": 44}) # replace the elements by new ones (don't change attrs)

Code :

class Generator:

	def __init__(self, target=None, content=None):

		self.alsoAttrs = content == None

		if content == None:
			content = target.childNodes

		content = [c.clone() for c in content]
		self.target = target

		self.template = target.clone() if target != None else html.DIV()
		self.template.replaceChildren(*content)

	def generate(self, values):

		h4ck = self.template.clone()

		tagtype = h4ck.tagName
			
		elem = None
		if tagtype in [html.TD, html.TR, html.TBODY, html.TFOOT, html.THEAD]:

			cur_elem = h4ck
			tag = tagtype
			if tag == html.TD:
				tag = html.TR
				cur_elem = tag( cur_elem )
				if elem == None:
					elem = cur_elem
			if tag == html.TR:
				tag = html.TBODY
				cur_elem = tag( cur_elem )
				if elem == None:
					elem = cur_elem

			cur_elem = html.TABLE( cur_elem )
			if elem == None:
				elem = cur_elem
		else:
			elem = html.DIV( h4ck )
			
			Template(h4ck).render(**values)

			
			children = elem.children[0].childNodes
			if len(children) == 1 and children[0].text.count('\n') == len( children[0].text ):
				children = h4ck.childNodes

		if self.target == None:
			return children

		if self.alsoAttrs:
			for attrname in h4ck.attrs:
				self.target.attrs[attrname] = h4ck.attrs[attrname].format(**values)

		self.target.replaceChildren( *children )

		return self.target

@CookiePLMonster
Copy link

CookiePLMonster commented Mar 24, 2024

Are you having any luck rendering the arguments from a cloned template, even using this hack? I tried my best to replicate it, but variables defined like

<template id="footnote-template">
<li id="#fn:{id}" role="doc-endnote"><p>{note}&nbsp;<a href="#fnref:{id}" class="reversefootnote" role="doc-backlink">&larrhk;</a></p></li>
</template>

are just not being substituted with a Template(hack).render(id='x', note='y') call.

@denis-migdal
Copy link
Contributor Author

Are you having any luck rendering the arguments from a cloned template, even using this hack? I tried my best to replicate it, but variables defined like

I worked for me. Maybe a bug has been introduced in the new Brython version ?

TBF, now I use my own implementation of WebComponent : BLISS.

@CookiePLMonster
Copy link

I worked for me. Maybe a bug has been introduced in the new Brython version ?

The following outputs un-rendered HTML for me, which means I get a footnote saying {note}.

Brython:

newFootnote = document['footnote-template'].clone().content
h4ck = html.DIV()
h4ck <= newFootnote
elem = html.DIV(h4ck)

Template(h4ck).render(id='test', note='Testing')
document['output-footnotes'] <= h4ck.childNodes

HTML:

<template id="footnote-template">
<li id="#fn:{id}" role="doc-endnote"><p>{note}&nbsp;<a href="#fnref:{id}" class="reversefootnote" role="doc-backlink">&larrhk;</a></p></li>
</template>

Not sure if what I am doing is supported at all - ideally, I'd like to define templates of my elements in HTML and have Brython output them however I see fit, on demand.

@denis-migdal
Copy link
Contributor Author

denis-migdal commented Mar 25, 2024

You missed 2 lines :

newFootnote = document['footnote-template'].clone().content

# h4ck
h4ck = html.DIV()
h4ck <= newFootnote

elem = html.DIV(h4ck)
Template(h4ck).render(id='test', note='Testing')

# get result :
children = elem.children[0].childNodes
if len(children) == 1 and children[0].text.count('\n') == len( children[0].text ):
	children = h4ck.childNodes

document['output-footnotes'] <= children

Indeed, sometimes the result is in elem, other times it is in h4ck.
You can try to print them both to see which one contains your result.

After, it is possible that this h4ck doesn't work on the newest versions of Brython.

@CookiePLMonster
Copy link

CookiePLMonster commented Mar 25, 2024

You missed 2 lines :

Awesome, this works, thank you! I also found out that just specifying document['output-footnotes'] <= elem.childNodes also works.

This is much cleaner than manually building HTML from Brython, like I did before:

        note = html.P('No effect.' + chr(160) + html.A(chr(0x21A9), href='#fnref:no-effect', Class='reversefootnote', role='doc-backlink')
                                + chr(160) + html.A(chr(0x21A9) + html.SUP('2'), href='#fnref:no-effect:1', Class='reversefootnote', role='doc-backlink'))
        document['output-footnotes'] <= html.LI(note, id='#fn:no-effect', role='doc-endnote')

IMO in an ideal scenario, Brython would "support" using HTML5 templates like:

document['output-footnotes'] <= Template('footnote-template').instantiateNew(arg1=foo, arg2=bar)

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

No branches or pull requests

2 participants