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
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.
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:
...
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
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.
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
components
packages
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. TheBindingContext
is used toget
other component aspects in real time from the frontend. It can also be used to access theWebStorage
of the browser.call()
is a regular request update.CallContext
can onlyset
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.
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.
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
dazzler.system.transforms
- API reference.Transforms - Example of most transforms usage.
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')
See also
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
See also
Warning
The authentication system is currently not suited for use with Electron applications.