About

In this documentation we described the use of models in the app component system.

  1. About

  2. Review

  3. Models

  4. Accessing Values

  5. Builtins Models

  6. Model Components

  7. Constructing

  8. Other Docs

Review

Recall that in the app component system, a component is a typed function (in the sense of typed) such that:

  1. it returns a jinja string, i.e, its codomain is a subtype of Jinja;

  2. it is decorated with the @component decorator.

A component may contains certain special variables, as:

  1. the __depends_on__ variable, which lists the components that a defining component depends on;

  2. the inner vars, which are variables of type Inner

  3. 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 being Optional (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 avoiding Optional 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>

table 1

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, …

table 2

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

table 3: model components

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 from app.helper.

Constructing

Recall that there are certain operations in the type COMPONENT of all components:

  1. join: corresponding to +;

  2. concat: corresponding *;

  3. 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.

Other Docs

  1. components

  2. rendering

  3. statics

  4. compatibility

  5. list of entities