About
In this documentation we described the use of models in the app
component system.
Review
Recall that in the app
component system, a component is a typed function (in the sense of typed) such that:
it returns a jinja string, i.e, its codomain is a subtype of
Jinja
;it is decorated with the
@component
decorator.
A component may contains certain special variables, as:
the
__depends_on__
variable, which lists the components that a defining component depends on;the inner vars, which are variables of type
Inner
the content vars, which are variables of type
Content
(these will be discussed in Statics).
Models
By definition, there is no restriction on the domain of a component, except by the fact that it can contains certain special variables, as described above. This means that the “typical variables” may have any type.
However, a better way to organize the components is to work with models (in the sense of typed), grouping the “typical variables” in a more semantic entity.
So, for example, consider the following component:
1from typed import SomeType, OtherType
2from app import component, Jinja
3
4@component
5def my_comp(some_var: SomeType, other_var: OtherType, ...) -> Jinja:
6 return """jinja
7 ...
8"""
You could join some_var
and other_var
into a single entity (for full details on using models, see typed/models):
1from typed import SomeType, OtherType, ...
2from typed.models import model, Optional
3
4@model
5class MyComp:
6 some_var: Optional(SomeType, SomeType())
7 other_var: Optional(OtherType, OtherType())
8 ...
Then, one could rewrite my_comp
as follows:
1from app import component, Jinja
2from some.where import MyComp
3
4@component
5def my_comp(my_comp: MyComp, ...) -> Jinja:
6 return """jinja
7 ...
8"""
Remark 1. The recommendation is to create models with the decorator
@model
and all variables beingOptional
(in the sense of typed/models). This will allow more flexibility in the rendering of the component, as described in Rendering, without breaking type safety. However, you are free to create the models as you want, say using more rigid decorators, as@exact
,@ordered
and@rigid
, or avoidingOptional
arguments.
Accessing Values
To each internal variable of a model there corresponds a namesake attribute. This means that if SomeModel
is a model with a variable some_var
and if model: SomeModel
is a variable of type SomeModel
, you could access the values some_var
as model.some_var
.
In terms of the component my_comp
defined above, one could then access my_comp.some_var
and my_comp.other_var
in the body of the component function, i.e, as local variables:
1from app import component, Jinja
2from some.where import MyComp
3
4@component
5def my_comp(my_comp: MyComp, ...) -> Jinja:
6 some_value = my_comp.some_var
7 other_value = my_comp.other_var
8 ...
9 return """jinja
10 ...
11"""
In this case, since local variables of components are automatically added to context of the component, some_value
and other_value
could then be used inside the returning jinja string:
1from app import component, Jinja
2from some.where import MyComp
3
4@component
5def my_comp(my_comp: MyComp, ...) -> Jinja:
6 some_value = my_comp.some_var
7 other_value = my_comp.other_var
8 ...
9 return """jinja
10 ...
11 {{ some_value }}
12 ...
13 {{ other_value }}
14 ...
15"""
The remarkable point here is that, in the construction of the @component
decorator, if a variable is defined with type being a model, then all attributes are directly added in the context as well. Therefore, you could use my_comp.some_var
, etc, directly inside the jinja string:
1from app import component, Jinja
2from some.where import MyComp
3
4@component
5def my_comp(my_comp: MyComp, ...) -> Jinja:
6 ...
7 return """jinja
8 ...
9 {{ my_comp.some_var }}
10 ...
11 {{ my_comp.other_var }}
12 ...
13"""
Builtins Models
Inside the module app.models
you will encounter a plethora of ready to use models, one for each basic HTML tag.
model |
tag |
---|---|
Div |
<div> |
Text |
<p> |
Title |
<h1>, <h2>, … <h6> |
Link |
<a> |
Image |
<img> |
Figure |
<figure> |
Button |
<button> |
Script |
<script> |
Asset |
<link> |
… |
The attributes of the builtin models correspond to (mostly of) the HTML tag attributes of the underlying HTML tags. There is also a Globals
model, which contains the HTML global attributes, which are shared across all HTML tags.
The global attributes can be accessed in each builtin model from a globals
variable. The only exception are the global attributes id
and class
, which appears as independent variables inside each model, named according to the tag name:
model |
tag |
attributes |
---|---|---|
Div |
<div> |
div_id, div_class, … |
Text |
<p> |
text_id, text_class, … |
Title |
<h1>, <h2>, … <h6> |
title_id, title_class, … |
Link |
<a> |
link_id, link_class, … |
Image |
<img> |
img_id, img_class, … |
… |
Model Components
To each builtin model, say associated with a tag model-tag
, there corresponds a namesake builtin component, which has a namesake variable of the given model type, and eventually a single inner var, maybe depending on others builtin components. These are the so-called model components.
model |
component |
---|---|
Div |
div |
Text |
text |
Title |
title |
Link |
link |
Image |
img |
Figure |
figure |
Button |
button |
Script |
script |
Asset |
asset |
… |
So, the generic structure of a model component is as follows:
1from app import component
2from app.models import SomeModel
3
4@component
5def some_model(some_model: SomeModel=SomeModel(), inner: Inner=Inner(), __depends_on__=[...]) -> Tag('model-tag'):
6 ...
7 return """
8<model-tag>
9 ...
10 {{ inner }}
11 ...
12</model-tag>
13"""
Actually, the <model-tag>
in a model component is dynamically constructed with all attributes of the model SomeModel
that are not null in the instance some_model
. So, for example, if some_model.some_attr
is set to some_value
, then it will appear in the returning jinja string:
1@component
2def some_model(some_model: SomeModel=SomeModel(), inner: Inner=Inner(), __depends_on__=[...]) -> Tag('model-tag'):
3 ...
4 return """
5<model-tag some_attr="some_value">
6 ...
7 {{ inner }}
8 ...
9</model-tag>
10"""
Otherwise, i.e, if some_model.some_attr
is not set, then the field some_attr
is not included in the returning <model-tag>
, turning the code much more clean.
This happens because the jinja string of a model component is constructed with the help of
if_
helper functions fromapp.helper
.
Constructing
Recall that there are certain operations in the type COMPONENT
of all components:
join
: corresponding to+
;concat
: corresponding*
;eval
: corresponding/
.
Using the builtin model components and the operations above, you can build derived components very quickly. For instance, suppose you want to build a div
with a title followed by a paragraph. You can just take:
1from app.components import div, title, text
2
3my_comp = div * (title + text)
The obtained component have the variables div: Div
, title: Title
and text: Text
, whose attributes can then be fixed to produce concrete instances of my_comp
.