html5 (core library)
Any flare components are entirely established on top of the html5-library.
The html5 library
is flare’s core module and key feature, and manages
access to the browser’s DOM and its items, by implementing a Python
object wrapper class for any HTML-element. Such an element is called
widget. For example, html5.Div()
is the widget representing a
div-element, or html5.A()
a widget representing an a-element.
Widgets can be sub-classed into specialized components, which contain
other widgets and components and interact together.
The document’s body and head can directly be accessed by the static
widgets html5.Head()
and html5.Body()
.
All these widgets are inheriting from an abstract widget wrapper called
html5.Widget
. html5.Widget
is the overall superclass which
contains most of the functions used when working with DOM elements.
Therefore, all widgets are usually handled the same way, except
leaf-type widgets, which may not contain any children.
First steps
When working with native html5-widgets, every widget must be created separately and stacked together in the desired order. This is well known from JavaScript’s createElement-function.
Here’s a little code sample.
from flare import html5
# Creating a new a-widget
a = html5.A()
a["href"] = "https://www.viur.dev" # assign value to href-attribute
a["target"] = "_blank" # assign value to target-attribute
a.addClass("link") # Add style class "link" to element
# Append text node "Hello World" to the a-element
a.appendChild(html5.TextNode("Hello World"))
# Append the a-widget to the body-widget
html5.Body().appendChild(a)
Summarized:
html5.Xyz()
creates an instance of the desired widget. The notation is that the first letter is always in uppercase-order, the rest is hold in lowercase-order, therefore e.g.html5.Textarea()
is used for a textarea.Attributes are accessible via the attribute indexing syntax, like
widget["attribute"]
. There are some special attributes likestyle
ordata
that are providing a dict-like access, sowidget["style"]["border"] = "1px solid red"
is used.Stacking is performed with
widget.appendChild()
. There are also some additional functions for easier element stacking and child modification, these are -widget.prependChild()
to prepend children, -widget.insertBefore()
to insert a child before another child, -widget.removeChild()
to remove a child.To access existing child widgets, use
widget.children(n)
to access the n-th child, or without n to retrieve a list of a children.
Parsing widgets from HTML-code
Above result can also be achieved much faster, by using the build-in html5-parser and renderer.
from flare import *
html5.Body().appendChild(
"<a href='https://www.viur.dev' target='_blank' class='viur'>Hello World</a>"
)
That’s quite simpler, right? This is a very handy feature for prototyping and to quickly integrate new HTML layouts.
Widget.appendChild()
and other, corresponding functions, allow for
an arbitrary number of elements to be added. HTML-code, widgets, text or
even lists or tuples of those can be given, like so
ul = html5.Ul()
ul.appendChild("<li class='is-active'>lol</li>")
ul.prependChild(html5.Li(1337 * 42))
ul.appendChild("<li>me too</li>", html5.Li("and same as I"))
The HTML parser can also do more: When component classes (any class that inherits directly from html5.Widget, like html5.Div or so) are decorated with the html5.tag-decorator, these are automatically made available in the HTML-parser for recognition.
Inheritance is normal
In most cases, both methods shown above are used together where necessary and useful. Especially when creating new components with a custom behavior inside your app, knowledge of both worlds is required.
To create new components, inheriting from existing widgets is usual. If we would like to add our link multiple times within our app, with additional click tracking, we can make it a separate component, like so:
import logging
from flare import *
class Link(html5.A): # inherit Link from html5.A widget
def __init__(self, url, *args, target="_blank", **kwargs):
super().__init__()
self.addClass("link")
self["href"] = url
self["target"] = "_blank"
self.appendChild(*args, **kwargs)
self.sinkEvent("onClick")
def onClick(self, event):
logging.info(f"The link to {self['href']} has been clicked")
html5.Body().appendChild(
# Create a link with text
Link("https://www.viur.dev", "ViUR Framework"),
"<br>",
# Create link with logo
Link("https://www.python.org", """
<img src="https://www.python.org/static/community_logos/python-powered-h-50x65.png"
title="Python Programming Language">
""")
)
In this example, we just made our first custom component: The
Link
-class can be arbitrarily used.
Widget basics
Following sections describe the most widely used functions of the
:class:`html5.Widget
<flare.html5.Widget>`-class which are inherited by any widget or huger
component in flare.
Constructor
All widgets share the same __init__
-function, having the following
signature:
def __init__(self, *args, appendTo=None, style=None, **kwargs)
*args
are any positional arguments that are passed toself.appendChild()
. These can be either other widgets or strings containing HTML-code. Non-container widgets likehtml5.Br()
orhtml5.Hr()
don’t allow anything passed to this parameter, and throw an Exception.appendTo
can be set to another html5.Widget where the constructed widget automatically will be appended to. It substitutes an additionalappendChild()
-call to insert the constructed Widget to the parent.style
allows to specify CSS-classes which are added to the constructed widget using**kwargs
specifies any other parameters that are passed toappendChild()
, like variables.
Insertion and removal
These methods manipulate the DOM and it’s nodes
appendChild()
Appends another html5.Widget as child to the parent element:
self.appendChild("""<ul class='navlist'></ul>""")
self.nav.appendChild("""<li>Navigation Point 1</li>""")
prependChild()
Prepends a new child to the parent element
self.appendChild("""<ul class='navlist'></ul>""")
navpoint2 = self.nav.appendChild("""<li>Navigation Point 2</li>""")
navpoint2.prependChild(("""<li>Navigation Point 1</li>"""))
replaceChild()
Same as appendChild(), but removes the current children of the Widget first.
insertBefore()
Inserts a new child element before the target child element
self.appendChild("""<ul class='navlist'></ul>""")
navpoint = self.nav.appendChild("""<li>Navigation Point 1</li>""")
navpoint3 = self.nav.appendChild("""<li>Navigation Point 3</li>""")
navpoint2 = self.nav.insertBefore("""<li>Navigation Point 2</li>""", navpoint3)
If the child element that the new element is supposed to be inserted before does not exist, the new element is appended to the parent instead.
removeChild(), removeAllChildren()
Either removes one child from the parent element or any available children.
Visibility and usability
Widgets can be switched hidden or disabled. Form elements, for example, might be disabled when a specific condition isn’t met. These functions here help to quickly change visibility and usability of widgets, including their child widgets which are switched recursively.
hide(), show()
Hides or shows a widget on demand.
To check whether a widget is hidden or not, evaluate
widget["hidden"]
. In the HTML-parser, this flag can be set using the
hidden
attribute, e.g. <div hidden>You can't see me.</div>
.
enable(), disable()
Enable or disable the widget in the DOM. Useful for forms and similar UI applications.
To check whether a widget is disabled or not, evaluate
widget["disabled"]
. In the HTML-parser, this flag can be set using
the disabled
attribute, e.g. <div disabled>I'm disabled</div>
.
class-attribute modification
These methods are helpful for adding CSS-classes quickly.
addClass()
Adds a class to the html5.Widget and checks to prevent adding the same class multiple times.
nav = self.appendChild("""<ul></ul>""")
nav.addClass('navlist')
Adding a class multiple times might be wanted and is valid. In this
case, modify the widget’s class
-attribute directly by assigning a
list to it.
removeClass()
Checks if the widget has that class and removes it
nav = self.appendChild("""<ul class='big-red-warning-border-color'></ul>""")
nav.removeClass('big-red-warning-border-color')
toggleClass()
Toggles a class on or off, depending on whether it has the specified class already or not.
hasClass()
Checks if the element has a given class or not. Returns True if class name is found and False otherwise.
nav = self.appendChild("""<ul class='big-red-warning-border-color'></ul>""")
if nav.hasClass('big-red-warning-border-color'):
print("Help! There is a big red border around this element! Remove the class so we can feel safe again")
HTML parser reference
The html5-library built into flare brings its own HTML-parser. Using this parser, any HTML-code can directly be turned into a flare DOM.
Additionally, some nice extensions regarding flare component and widget customization and conditional rendering is supported, as the HTML-renderer automatically creates the DOM from a parsed input and serves as some kind of template processor.
Data-based rendering
Using variables
Any variables provided via kwargs to html5.fromHTML()
can be inserted in attributes or as TextNode-elements with their particular content
when surrounded by {{
and }}
. Inside this notation, full Python expression syntax
is allowed, so that even calculations or concatenations can be done.
html5.Body().appendChild("""
<div class="color-{{ l[1] + 40 }}">{{ d["world"] + "World" * 3 }} and {{ d }}</div>
""", l=[1,2,3], d={"world": "Hello"})
renders into
<div class="color-42">HelloWorldWorldWorld and {'world': 'Hello'}</div>
flare-if, flare-elif, flare-else
The attributes flare-if
, flare-elif
and flare-else
can be
used on all tags for conditional rendering.
This allows for any simple Python expression that evaluates to True or any computed non-boolean value representing True.
html5.Body().appendChild("""
<div>begin</div>
<div flare-if="i <= 10">i is just low</div>
<div flare-elif="i <= 50 and j >=100">i and j have normal values</div>
<div flare-elif="i > 50 and j >= 50">i and j have moderate values</div>
<div flare-else>i and j are something different</div>
<div>end</div>
""", i=50, j=151)
As variables, any arguments given to
html5.fromHTML()
(or related functions) as kwargs
can be used.
html5.parseHTML()
def parseHTML(html: str, debug: bool=False) -> HtmlAst
Parses the provided HTML-code according to the tags registered by
html5.registerTag() or components that use the
@tag
-decorator.
The function returns an abstract syntax tree representation (HtmlAst) of
the HTML-code that can be rendered by html5.fromHTML()
.
html5.fromHTML()
def fromHTML(html: [str, HtmlAst], appendTo: Widget=None, bindTo: Widget=None, debug: bool=False, **kwargs) -> [Widget]
Renders HTML-code or compiled HTML-code (HtmlAst).
appendTo: Defines the Widget where to append the generated widgets to
bindTo: Defines the Widget where to bind widgets using the
[name]
-attribute todebug: Debugging output
**kwargs: Any specified kwargs are available as variables to any expressions.
HTML-code can optionally be pre-compiled with
html5.parseHTML()
, and then executed multiple
times (but with different variables) by fromHTML. This is useful when
generating lists of same elements with only replaced variable data.
@html5.tag
Decorator to register a sub-class of html5.Widget
either under its
class-name, or an associated tag-name.
Examples:
from flare import html5
# register class Foo as <foo>-Tag
@html5.tag
class Foo(html5.Div):
pass
# register class Bar as <baz>-Tag
@html5.tag("baz")
class Bar(html5.Div):
pass