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 by pyro.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 value

  • Call self.render_pyroes (with the value generated by self.pyroes_) to render underneath ul

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_ (inside self.pyro_) generates a value it is put inside the input field as the current value

  • When 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.