Usage

Install

Install with pip:

pip install dazzler

Getting started

Create the directory structure of the project, usually the main application file will be on the root project and the code/pages will be in a package.

Pages inside the to the config page_directory

Example project structure:

app.py
dazzler.toml
/pages
    __init__.py
    /requirements
        styles.css
    my_page.py
app.py
from dazzler import Dazzler

app = Dazzler(__name__)

# Main app code, add pages, setup configs, etc.
...

if __name__ == '__main__':
    # Start the development server.
    app.start()

Tip

Quickstart with a github template https://github.com/T4rk1n/dazzler-app-template

Pages

Dazzler apps are divided in pages, each pages have it’s own layout and bindings.

The first argument of the page is the name of the file (__name__), it is used to determine the location of the page requirements folder. By default, a directory named requirements will be looked for and it’s contents are served by the page.

The name will also be used for the url and title, so make your filename is adequate or override them in the page constructor.

my_package.pages.my_page.py
from dazzler.system import Page

page = Page(
    __name__,
    layout=core.Container('My page'),
    url='/my-page',
    title='My Page'
)

Then you can add the page to the dazzler application with:

application.py
...
from my_package.pages import my_page
...
app.add_page(my_page.page)

You can now start the application ($ python application.py) and navigate to http://localhost:8150/my-page

See also

Page

Requirements

Every CSS/JS files contained in a directory named requirements at the same level of the page file will be included on the page. Styles are loaded after components styles to ensure priority of user CSS.

Note

The directory can be changed via page parameter requirements_dir. The path is relative to the page file.

Components

Many components are available to build beautiful interactive pages. They are composed of aspects which are properties shared between the backend and the frontend.

Every component has an identity which allows to add a trigger when a component aspect is updated by the browser to perform actions and update other aspects by the server.

Components are divided in packages which holds all their requirements and metadata.

my_package.pages.page_two.py
from dazzler.system import Page
from dazzler.components import core

# Container is the main components to holds other data, it is a div with
# some aspects to style it.
layout = core.Container([
    # A viewport can be used to create a tabbed layout.
    core.ViewPort([
        {
            'Home': core.Container([
                core.Html('h2', 'Homepage'),
                core.Container('Welcome to my page.'),
            ]),
            'About': core.Container([
                core.Html('h2', 'About'),
                core.Container('lorem ipsum')
            ])
        },
        tabbed=True,
    ])
])

page = Page(__name__, layout)

See also

Bindings

To update components after the initial layout, you can use page bindings. Bound functions takes a context argument that is used to set aspects and access to the session and user systems.

  • bind() executes updates via websockets, it allows for two-ways communication and long running functions. The BindingContext is used to get other component aspects in real time from the frontend. It can also be used to access the WebStorage of the browser.

  • call() is a regular request update. CallContext can only set aspects, states can be used if other aspects are required.

Short Identifier

Components properties can be specified as a simple string to use as trigger or state for bind(), call() or tie().

Syntax: <aspect>@<identity> -> clicks@btn

Examples

Binding

Update a container on click of a button.

pages/bound_page.py
from dazzler.system import Page, Trigger
from dazzler.components import core

layout = core.Container([
    core.Input(placeholder='Enter name', identity='input'),
    core.Button('Click me', identity='btn'),
    core.Container(identity='output'),
])

page = Page(__name__, layout)

@page.bind('clicks@btn')
async def on_click(ctx):
    name = await ctx.get_aspect('input', 'value')
    await ctx.set_aspect('output', children=f'Hello {name}')
Call

Calls can only set aspects synchronously once the bound function has finished.

pages/calls.py
from dazzler.system import Page, CallContext
from dazzler.components.core import Box, Button, Form, Text, Input

page = Page(
    __name__,
    Box([
        Form(
            fields=[
                {
                    'label': 'First Name',
                    'name': 'first-name',
                    'component': Input(identity='firstname')
                },
                {
                    'label': 'Last Name',
                    'name': 'last_name',
                    'component': Input(identity='lastname')
                }
            ],
            include_submit=False,
            identity='form',
        ),
        Button('Submit', identity='submit'),
        Box(identity='output')
    ], column=True)
)


@page.call('clicks@submit', 'value@firstname', 'value@lastname')
async def on_submit_call(ctx: CallContext):
    ctx.set_aspect(
        'output',
        children=Text(
            f'Hello {ctx.states["firstname"]["value"]}'
            f' {ctx.states["lastname"]["value"]}',
            color='green'
        )
    )
Regex Identifier

Regex bindings can be used as trigger/states for identity and aspect. Any trigger matching will be executed.

import re

from dazzler.components import core
from dazzler.system import Page, Trigger, BindingContext, State

page = Page(
    __name__,
    core.Container([
        core.Button('btn1', identity='btn1'),
        core.Button('btn2', identity='btn2'),
        core.Container(identity='output1'),
        core.Container(identity='output2'),
        core.Input(identity='state1'),
        core.Store(data='store', identity='state2'),
        core.Container(identity='state-output')
    ])
)


@page.bind(
    Trigger(r'btn\d', 'clicks', regex=True),
    State(r'state\d', '(value|data)', regex=True)
)
async def on_any_click(ctx: BindingContext):
    await ctx.set_aspect(
        re.compile(r'output\d'),
        children=f'clicked from button {ctx.trigger.identity}'
    )
    output = []
    for identity, aspects in ctx.states.items():

        for aspect_name, aspect_value in aspects.items():
            output.append(
                core.Container(
                    f'{aspect_name}@{identity}: {aspect_value}',
                    identity=f'{aspect_name}-{identity}-output')
            )

Ties & Transforms

You can use tie() method of Page to link aspects together without the need to define a binding for UI updates. This update operates entirely on the frontend.

Transform’s are operations of a tie to apply on the trigger value before updating the target aspect. They can be chained to provide interactions with components aspects on the frontend. AspectValue starts a chain with the aspect value resolved. For value transforms, a Target can be used instead of a raw value to get that aspect value.

Examples

1 to 1 tie

Update a ViewPort active view from a Dropdown value.

from dazzler.system import Page
from dazzler.components import core

page = Page(
    __name__,
    core.Container([
        core.Dropdown(
            options=['one', 'two', 'three'],
            value='one',
            identity='dropdown'
        ),
        core.ViewPort(
            active='one',
            views={
                'one': core.Container('one'),
                'two': core.Container('two'),
                'three': core.Container('three')
            },
            identity='viewport'
        )
    ])
)

page.tie('value@dropdown', 'active@viewport')
Transform tied values

Use Transform’s to change the tied trigger value.

Switch between a light and dark theme:

from dazzler.system import Page, transforms as t
from dazzler.components import core

page = Page(
    __name__,
    core.Container([
        core.Container('Theme transform'),
        core.Container(identity='mode'),
        core.Box([
            core.Text(
                'Choose theme: ',
                font_weight='bold',
                align_self='center'
            ),
            core.Dropdown(
                options=['light', 'dark'],
                value='light',
                identity='theme-dropdown',
                style={'width': '300px'}
            ),
        ]),
    ], identity='layout', style={'height': '100vh'})
)

page.tie('value@theme-dropdown', 'style@layout').transform(
    t.If(
        t.Equals('light'),
        then=(
            t.AspectValue('style@layout').t(
                t.Merge({'background': 'white', 'color': 'black'}))
        ),
        otherwise=(
            t.AspectValue('style@layout').t(
                t.Merge({'background': 'black', 'color': 'white'}))
        )
    )
)
# Contrary to bindings, you can attach multiples ties to the same trigger.
page.tie('value@theme-dropdown', 'children@mode').transform(
    t.Format('Theme selected: ${value}')
)

See also

Integrated systems

Authentication and session systems are available to use with databases.

Backends

PostgreSQL

PostgreSQL is available for both session and authentication.

Install

pip install dazzler[postgresql]

Configure
[postgres]
# Also set by environ: POSTGRES_DSN
dsn = 'host=localhost port=5432 dbname=dbname user=user password=pw'

[session]
backend = 'PostgreSQL'

[authentication]
authenticator = 'dazzler.contrib.postgresql:PostgresAuthenticator'
Redis

Redis can be used as a Session backend.

Install

pip install dazzler[redis]

Configure

Set the REDIS_URL environ variable, default: redis://localhost:6379.

[session]
backend = 'Redis'

Session

The session system is used to associate data to users of the application when using bindings. It is enabled by default and interacted with the BindingContext` session attribute, automatically scoped to the current user.

Basic usage:

@page.bind('clicks@btn')
async def on_click(ctx: BindingContext):
    my_value = await ctx.session.get('my_value')

Warning

The session system should not be used with Electron applications.

Disable the session system:

[session]
enable = false

Authentication

This system provide pages and routes for authentication like login, logout, register and a user administration page. Pages can require_login before the user can access it and additional authorizations can be necessary with the authorizations keyword of the Page.

Enable with configuration:

[authentication]
enable = true

Warning

The authentication system is currently not suited for use with Electron applications.