Components¶
Structure¶
Each component is a valid HTML code. By simple words it consist of nested blocks with text and media content.
Sections (optional):
Client-side Java Scripts.
Main component HTML markup.
Local and global styles.
Server-side Python code.
Sample code:
<p ref:greetz>Any HTML code here</p>
<button on:click="action">Push me!</button>
<style>
/* any local style here */
p { color: green; }
</style>
<python>
# server-side python code
from pantra.ctx import *
def action(node):
refs['greetz'].set_text('Very well!')
</python>
Each component is stored to a file with extension html. Components are semantically grouped by directories, but component names should be unique. First letter of component name should be in uppercase.
Common components are stored in components directory. Other components specific to application stored in application directory.
Macro codes¶
pantra introduces mustache-like templating, providing macro-coding within HTML content.
Expressions¶
Code insertions in attribute values (single brackets):
<tag attr=”{expression}”> - evaluate Python expression to make string value.
<tag attr=”some text {expression1} some text {expression2}”> - string template with many insertions
<tag attr=”js_function(`{expression}`)”> - alternative notation: `{…}` <-> {…}
<tag attr=”@var_name”> - get value from local context variable * <tag attr=@var_name> - alternative (no quotes)
<tag attr=”#text to be translated”> - value, marked for translations. * <tag attr=”#text to be {translated}”> - string template marked for translation
<tag attr=”::{precalculated}”> - double-colon marks expression evaluated once (doesn’t change on update).
Code insertions in text content {{ expression }} (double quotes). Any Python code is allowed here. Supported one-line expression returning string value, including any context variables:
<p>Good morning, Mr. {{user_name}}.</p>
Use modifiers in the beginning of text content.
Note
Some modifiers can be combined with each other (in both cases):
<div>!#Starting countdown {{counter}}<div>
For each code insertion several variable names are reserved:
ctx - reference to local context (instance of Context)
refs - dictionary of named references
session - active user session instance
this - reference to the current rendering node
Read scripting protocol for details.
Conditional render¶
It is possible to render content depending on expression:
{{#if expression}}
<div>This is good</div>
{{#elif alternative}}
<div>This is also good</div>
{{#else}}
<div>This might be good</div>
{{/if}}
Warning
Conditional macro can not be inserted inside other tags. It should separated solid tags or other macro-blocks only.
Render in loop¶
Repeat similar part of HTML code in a loop:
{{#for line in poem}}
<div>{{line}}</div>
{{#else}}
<div>No poem ready yet</div>
{{/for}}
Tip
Loop syntax doesn’t support tuple unpacking yet.
Loops support index function for better caching:
When loop block updates - it calculates index function first
Then it compare new indices with old ones
Then it create blocks with new indices only, removes blocks not matched in the list, and perform order changes. It makes sufficient boost for table-based operation, like filtering, sorting and windowing.
{{#for row in table # row.id}}
<tr>
<td>row.id</td>
<td>row.data</td>
</tr>
{{/for}}
There is special variable name reserved in the loop context: forloop object with several attributes:
index - number of iteration starting from one: 1, 2, 3, …
index0 - number of iteration starting from zero: 0, 1, 2, …
parent - reference to the parent forloop object
index - value of the calculated index
Reactive¶
Any content generated by pantra is static by default. But it possible to partially enable reactivity using symbol ! as a prefix before “mustache”.
<div>!{{dynamic_message}}</div> - reactive text content
<div attr=”!{dynamic_expression}”> - reactive attribute value
<tag attr=”!some text {dynamic_expression}”> - reactive template
!{{#if dynamic_condition:}} - reactive condition
!{{#for item in dynamic_list}} - reactive list (not recommended)
Set value to variable¶
To set new variable to expression use:
{{#set var_name = expression}}
...
{{/set}}
Close tag for set could be omitted.
Supports multiple assignments:
{{#set
var_name1 = expression1
var_name2 = expression2
}}
...
{{/set}}
When update action is fired all inner content is updated. To rebuild inner content (clear -> build), it is possible to specify special form {{#set:clear …}}.
Special form sets scoped variables {{#set:scope …}}.
{{#set:scope action=do_smth}}
<Control/>
{{/set:scope}}
<button on:click="scope:action">Click me</button>
Reactive tag¶
Additionally to reactive macros there is a way to bind an action to any variable changes:
<react to="var_names" action="func_name>
<!-- block to be updated -->
</react>
var_names - any variable names in local context, separated by comma
func_name (optional) - calling action (function defined with argument node of ReactNode type)
Nested block (optional) will be updated on action
<p ref:box>{{get_message()}}</p>
<react to="message" action="update_message"/>
<button on:click="change_message">Change message</button>
<python>
message: str = "Getting ready"
def get_message():
return message
def update_message(node):
refs['box'].update(True)
def change_message(node):
ctx['message'] = "Changes tracked"
</python>
<react to="message">
<p>{{get_message()}}</p>
</react>
<button on:click="change_message">Change message</button>
<python>
message: str = "Getting ready"
def get_message():
return message
def change_message(node):
ctx['message'] = "Changes tracked"
</python>
Template slots¶
Similar to Web Components pantra has “slots” inside components. Is simple words, slots allow to put content inside components.
<ol>
<slot>
<li>Empty list</li>
</slot>
<ol>
<li>{{caption}}</li>
<NumberedList>
<ListItem caption="Two eggs"/>
<ListItem caption="One sausage"/>
</NumberedList>
Each component can use one unnamed slot (default) and many named.
<div>
<slot/>
<slot name="media"/>
<slot name="description"/>
<slot name="sources"/>
<div>
If no content filled for a component, slot content is generated instead.
<NumberedList/>
rendered as:
<ol> <li>Empty list</li> <ol>
To put content inside named slot, special tag <section> is introduced:
<Product> <section> <p>Top words</p> </section> <section name="media"> <img src="123.jpg"> <section> <section name="description"> <p>Long words</p> </section> </Product>
It is possible to extend components keeping slots structure unmodified using <section> with attribute reuse.
<h1>Hot Hot Hot</h1>
<Product>
<section reuse/>
<section name="media" reuse/>
<section name="description" reuse/>
<section name="sources" reuse/>
</Product>
Non-HTML namespaces¶
Pantra supports non-HTML schemas, compatible with any modern browser. Such schemas includes SVG images, MathML and other special tags listed below.
To describe component as “namespace-specific”, it is required to set ns_type global variable to a specific schema number:
<svg set:width set:height set:viewBox>
<slot>
<g font-size="30">
<text x="20" y="50" color="black">SVG</text>
</g>
</slot>
</svg>
<python>
from pantra.ctx import *
from pantra.imports import NSType
ns_type = NSType.SVG # <-- here !!!
width: Property[int] = 1200
height: Property[int] = 900
viewBox: Property[str] = ""
</python>
SVG images¶
Schema: http://www.w3.org/2000/svg
Specification: https://www.w3.org/TR/SVG
Value: NSType.SVG
Sample:
<svg version="1.1" width="5cm" height="5cm">
<desc>Two groups, each of two rectangles</desc>
<g id="group1" fill="red">
<rect x="1cm" y="1cm" width="1cm" height="1cm"/>
<rect x="3cm" y="1cm" width="1cm" height="1cm"/>
</g>
<g id="group2" fill="blue">
<rect x="1cm" y="3cm" width="1cm" height="1cm"/>
<rect x="3cm" y="3cm" width="1cm" height="1cm"/>
</g>
<!-- Show outline of viewport using 'rect' element -->
<rect x=".01cm" y=".01cm" width="4.98cm" height="4.98cm"
fill="none" stroke="blue" stroke-width=".02cm"/>
</svg>
XML events¶
Schema: http://www.w3.org/2001/xml-events
Specification: https://www.w3.org/TR/xml-events/
Value: NSType.EVENTS
Sample:
<secret ref="/login/password">
<caption>Please enter your password</caption>
<info ev:event="help">
Mail help@example.com in case of problems
</info>
<info ev:event="hint">
A pet's name
</info>
<info ev:event="alert">
This field is required
</info>
</secret>
XLink¶
Schema: http://www.w3.org/1999/xlink
Specification: https://www.w3.org/TR/xlink/
Value: NSType.XLINK
Sample:
<person
xlink:href="students/patjones62.xml"
xlink:label="student62"
xlink:role="http://www.example.com/linkprops/student"
xlink:title="Pat Jones" />
MathML¶
Schema: http://www.w3.org/1998/Math/MathML
Specification: https://www.w3.org/TR/MathML/
Value: NSType.MATH
Sample:
<math display="block">
<mrow>
<mi>x</mi>
<mo>=</mo>
<mfrac>
<mrow>
<mrow>
<mo>-</mo>
<mi>b</mi>
</mrow>
<mo>±</mo>
<msqrt>
<mrow>
<msup>
<mi>b</mi>
<mn>2</mn>
</msup>
<mo>-</mo>
<mrow>
<mn>4</mn>
<mo>⁢</mo>
<mi>a</mi>
<mo>⁢</mo>
<mi>c</mi>
</mrow>
</mrow>
</msqrt>
</mrow>
<mrow>
<mn>2</mn>
<mo>⁢</mo>
<mi>a</mi>
</mrow>
</mfrac>
</mrow>
</math>
Preserve whitespace¶
pantra collapses whitespaces by default the same way as it happens in Web Browser.
As example:
<div>
This is word
not related
</div>
It happens stripped after render:
<div>This is word not related</div>
Note
As far as specific CSS property exists, it doesn’t affect rendering behavior. The components` content is preliminary rendered by design.
pantra tries to preserve whitespaces for text templates:
<div>Hello, dear {{user_name}}!</div>
<div>Hello, dear Jinja!</div>
It is also possible to mark content as non-collapsible with cell mark # at the end:
<div>
This is word
not related
#</div>
<div>
This is word
not related
</div>
Data nodes¶
Every node could be converted to “data node”, which means node is rendered as a data into memory. It helps to describe custom layout of a component, that rendered later.
node should be marked as data-node=”node-name”
and then referenced as ctx.data_nodes[‘node-name’] in a code
Example:
<group data-node="datas">
ABC
<group>DEF</group>
GHI
</group>
<div ref:output class="magic-list"/>
<python>
def render_group(parent, datas):
for item in datas:
if isinstance(item, HTMLElement) and item.name == "group":
node = parent.add("ul")
render_text(node, item.text)
render_group(node, item)
elif isinstance(item, TextNode):
render_text(parent, item.text)
def render_text(parent, text):
if text:
for sym in text:
parent.add("li", text=sym)
def on_render():
datas = ctx.data_nodes["datas"]
render_group(ctx.refs["output"], datas)
</python>
is rendered as:
<div class="magic-list">
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
<ul>
<li>D</li>
<li>E</li>
<li>F</li>
</ul>
<li>G</li>
<li>H</li>
<li>I</li>
</ul>
</div>