From 645bbaa5a9b6f0c50c5c7fae14c39dce633c3b8f Mon Sep 17 00:00:00 2001 From: Juro Oravec Date: Wed, 29 Nov 2023 03:09:56 +0100 Subject: [PATCH] feat: document frontmatter (#58) --- docs/docs/using-components.md | 201 ++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) diff --git a/docs/docs/using-components.md b/docs/docs/using-components.md index 2b99793..7249c61 100644 --- a/docs/docs/using-components.md +++ b/docs/docs/using-components.md @@ -103,3 +103,204 @@ Similar to a [`fragment`](/docs/template-tags-filters#fragment) tag, a component Assigning component output to a variable cannot be done inside a `with` block. ::: + +## Define types and defaults with front matter + +As [shown in Component context](#component-context) you can define +default values by using the `var` tag: + +```twig title="button.html" +{% var class=class|default:"btn btn-primary" %} + +``` + +If the defaults are more complex structures, using the `default` filter may not be enough. In such case you would need to define a custom tag or a filter to generate the defaults. + +Another limit of using the `var` tag is that it doesn't check for types. + +In these cases, you can add a front matter with custom Python code to define the types and defaults of the component's props. + +Front matter is a block of code at the beginning of the file, which starts and ends with lines with 3 dashes: + +```twig +--- +props.types = { + 'required_string': str, + 'optional_number': Optional[int], + 'default_number': int, +} +props.defaults = { + 'default_number': 10, +} + +props['new_number'] = props['default_number'] * 2 +--- + +The context contains: + +Required string: {{ required_string }} +Optional number: {{ optional_number }} +Default number: {{ default_number }} +New number: {{ new_number }} +``` + +The code in the front matter is evaluated as a regular Python code. + +It exposes the `props` variable (instance of `slippers.props.Props`), +which has 3 special features: + +### 1. `props.types` - If defined, component props are checked against types in `props.types` and raise `PropError` on mismatch. + +Using the previous example, if we set `required_string` to `int`, +or it was `None`, this would raise `PropError`. + +Since the type for `optional_number` is `Optional`, we CAN set `optional_number` to `None`, but setting it to other type like `str` would raise `PropError`. + +Although `default_number` is NOT `Optional`, we provided a default value, so we won't raise an error if we don't provide the value (or provide `None`). + +However, if the default value of `default_number` was NOT `int` this would raise `PropError`. + +For convenience with defining the types, the front matter is already prepopulated with the `typing` module, effectively the same as calling: + +```py +from typing import * +``` + +### 2. `props.defaults` - If defined, variables will fall back to these defaults if `None`. + +So the button example from [Component context](#component-context) can be rewritten as: + +```twig title="button.html" +--- +props.defaults = { + 'class': 'btn btn-primary' +} +--- + +``` + +And used as: + +```twig +{# Usage #} +{% button %} +{# Output #} + + +{# Usage #} +{% button class="btn btn-secondary" %} +{# Output #} + +``` + +### 3. Props mapping - Add or modify props with `props['prop_name']` + +To assign variables to the component's context, simply use: +```python +props['var_name'] = my_value +``` + +See the [next section](#preprocess-and-extend-component-context-with-front-matter) +which delves deeper into this. + +## Preprocess and extend component context with front matter + +Sometimes, you may want to define some data internal to the component, +like a static set of items in a menu. + +Normally, you'd have to define the items one by one: + +```twig title="menu.html" + +``` + +Or pass them as props: + +```twig title="menu.html" + +``` + +However, if these variables are never meant to be changed, then providing them through the props +doesn't make sense, as they will always be the same. + +It would clutter the component's interface, and make a room for error, as you now have to make sure +to pass in all "static" variables every time you use the component: + +```twig +{# You have to provide `items` every single time #} +{% menu items=items %} +{% menu items=items class="my_class" %} +``` + +Instead, you can make use of the front matter again, this time to +preprocess, define, or even import variables: + +```twig title="menu.html" +--- +from my_app.constants.icons import icon_bars_3 + +props.types = { + 'title_level': Optional[int], +} +props.defaults = { + 'title_level': 3, +} + +props['items'] = [ + ('Link 1', '/path1'), + ('Link 2', '/path2'), + ('Link 3', '/path3') +] + +props['title_tag'] = f'h{props['title_level']}' +--- + +``` + +Which can be used as: +```twig +{# Usage #} +{% menu title_tag=5 %} + +{# Output #} + +``` + +To assign variables to the component's context, simply use: +```python +props['var_name'] = my_value +``` + +And since the code in the front matter is evaluated as a regular Python code, we can import other modules.