Html Super-Nodes¶
AnPyLar extends the powers of the Html Nodes (call them tags if you wish) to make them:
Aware of Observables and its events.
Simplify template formatting.
Integrate them in the routing engine (
routerlink
).Allow some direct programming in html code.
Note
The additional methods are all prefixed with _
to make it clear that
they don’t belong to the standard hierarchy of node methods
Formatting¶
The method applied for it is: _fmt
(we could have named it _format
, but
that takes longer to type)
The method takes regular values and can also take Observables which will force re-formatting with each new value produced, i.e.: the node subscribes to the value of the observable. An example from the Tour of Pyroes:
html.txt(' {name}')._fmt(name=pyro.name_) # obs name_
pyro
has an observable name_
(generated through a binding) and in this
case:
{name}
will be formatted and reformatted with each value delivered bypyro.name_
Notice how the {name}
and later _fmt(name=xxx)
syntax resembles that of
the string method format
in Python. It is the same
When designing AnPyLar the question was if a new templating language had to be designed and the answer was quick: let’s use something which is already built into the language and people know.
The same code above could have been written without names, as in:
html.txt(' {}')._fmt(pyro.name_) # obs name_
Still the same Format Mini-Language present in Python.
Rendering¶
A node (or better said: underneath the node) can be rendered when an observer generates a value.
From the Tour of Pyroes
def render(self, node):
# render under ul in render_pyroes when observable self.pyroes_ fires
with node.select('ul') as ul: # find node where to display the list
ul._render(self.render_pyroes, self.pyroes_)
def render_pyroes(self, pyroes):
for pyro in pyroes:
with html.li() as li: # per-pyro list item
# per-pyro anchor routing path with parameter pyd
with html.a(routerlink=('/detail', {'pyd': pyro.pyd})):
html.span(pyro.pyd, Class='badge') # show pyd as badge
html.txt(' {name}')._fmt(name=pyro.name_) # obs name_
with html.button('x', Class='delete') as b:
# def param avoids closure using last pyro.pyd
def pyro_delete(evt, pyd=pyro.pyd):
evt.stopPropagation() # avoid evt clicking on "a"
self.pyro_delete(pyd)
b._bind.click(pyro_delete) # use "bind" to get event
The first part describes the rendering binding:
ul._render(self.render_pyroes, self.pyroes_)
which can be translated to:
Whenever the observable
self.pyroes_
produces a valueCall
self.render_pyroes
(with the value generated byself.pyroes_
) to render underneathul
It is guaranteed that:
Any previous DOM structure below
ul
will have been unloaded
ul
is selected as the node under which things will be rendered.
Dual Binding Formatting-Value¶
A standard use case is an <input>
field (or <textarea>
), which:
Displays a value
Generates events when the value changes
To support this use case the method _fmtvalue
(or its alias _fmtval
to
type less) can be used.
From the Tour of Pyroes
<div *_display=pyro_.pyd_>
<h2 {name}="pyro_.name_.map(lambda x: x.upper())">{name} Details</h2>
<div><span>pyd: </span><txt [pyro_.pyd_]>{}</txt></div>
<div>
<label>name:
<input *_fmtvalue=pyro_.name_ placeholder="name"/>
</label>
</div>
<button (click)="router.back()", name="cancel">Go back</button>
<button (click)="save()", name="save">Save</button>
</div>
Where the key is in:
<input *_fmtvalue=pyro_.name_ placeholder="name"/>
The supercharged methods can even be used in html code (see Html Programming). In this case this is equivalent to:
with html.input() as i: # create an input field
i._fmtvalue(self.pyro_.name_)
which effectively creates a dual-binding:
When the observable
name_
(insideself.pyro_
) generates a value it is put inside the input field as the current valueWhen the text in the input field changes … it is passed to
self.pyro_.name_
, and the observable will generate an event with the new value
No, there won’t be an endless loop here. The platform works in the background to avoid that even if the description had led you to believe a loop was possible.
Event Binding¶
Nodes can of course already bind to events. The extensions made here allow binding those events using this syntax:
node._bind.click(callback, *args, **kwargs)
which is just like the standard:
node._bind("click", callback, *args, **kwargs)
In this case the callback has to be able to accept the event:
callback(event, *args, **kwargs)
Additionally, there is a _bindx
method, which discards the event before
going to the callback. In this case:
node._bindx.click(callback, *args, **kwargs)
the callback will be called as:
callback(*args, **kwargs)
Classing Styling and Attributing¶
One can also dynamically change the styles and attributes of a node with:
node._style.name(trigger, on_value, off_value)
or:
node._style("name", trigger, on_value, off_value)
The trigger is expected to be an Observable, which will trigger the setting
of the on_value
or off_value
as the value of the style attribute.
The same can be done with node attributes.:
node._attr.name(trigger, on_value, off_value)
or:
node._attr("name", trigger, on_value, off_value)
And with class:
node._class.name(trigger, on_value, off_value)
or:
node._class("name", trigger, on_value, off_value)
Controlling the display¶
Having the ability show/hide a tag (and its sub-tags) is a typical use-case. It can be done also by hooking the display status to an observable:
node._display(trigger, show='', hide='none'):
The trigger is expected to be an Observable, which will trigger the setting
of the show
or hide
as the value of the display attribute.
There is additionally a procedural method to toggle the status:
node._display_toggle(onoff=None)
If called with no arguments (onoff
defaults to None
), the method will
try to switch the display (from hidden to shown and viceversa).
If called with True
it will try to show the node and if called with
False
it will try to hide node.