Nodes drive the actual rendering of your Form. Internally a Form keeps track of a list of Node‘s and then passes them off to the Renderer when a render of the Form is requested. Lets look at a simple example Form as shown in the introduction:
from yota import Form
from yota.nodes import *
class PersonalForm(Form):
first = EntryNode()
last = EntryNode()
address = EntryNode()
submit = SubmitNode(title="Submit")
All of the attributes defined in the above class are Node instances. Internally there is some trickery that preservers the order of these attributes, but this is not important to understand for using them. Just realize that unlike a regular object in Python, the order of these attributes effects the output of your Form.
Note
Some attribute names are reserved and trying to overwrite them with Node attributes will break things. Ensure that the names you select for your Node attributes do not collide with parameters to Form or keyword attributes that you pass to your Form.
The canonical Node is just a reference to some kind of rendering template (by default, Jinaj2 templates) and some associated metadata that will control how the template is rendered.
Let’s examine one of the builtin Nodes availible in Yota, and some of the things we can do with it. Let us look at the nodes.EntryNode. It has the following template:
{% extends base %}
{% block control %}
<input data-piecewise="{{ piecewise_trigger }}"
type="text"
id="{{ id }}"
value="{{ data }}"
name="{{ name }}"
placeholder="{{ placeholder }}">
{% endblock %}
Above we see what looks vaugly like HTML. If you’re not familiar with Jinja it would be a good idea to give their documentation a cursory glance before proceeding much further. However, the jist is that the sections enclosed in double curly-braces {{ }} will be replaced with variables, while the {% %} enclosed areas represent some sort of control structure. The meat of the above template is the input field. You can see that most of its attributes are replaced by variables.
Now take a look at the extends portion on the first line of our template. This is actually importing another template which is used as the base for many different builtin Nodes in Yota. We can see that template here:
<div class="control-group">
{% block error %}
{% if errors %}
<div class="alert alert-error">
{{ errors[0]['message'] }}
</div>
{% endif %}
{% endblock %}
{% if label %}
{% block label %}
<label class="control-label" for="{{ name }}">{{ title }}</label>
{% endblock %}
{% endif %}
<div class="controls">
{% block control %}
{% endblock %}
</div>
</div>
This template is just the default horizontal form layout for Bootstrap. Up top you can see a section reserved for displaying errors and in the middle a section to display a label. At the bottom is where the other template gets injected through the magic of blocks. Again, refer to the Jinja2 documentation for more information on this.
The actual Node definition is basically nothing:
class EntryNode(BaseNode):
template = 'entry'
Notice that the template is just entry, not entry.html. This is because the renderer auto-appends the suffix so Nodes can be used across different templating engines.
To understand more of what’s going on under the covers, here’s some explaination about how the variables used in the above templates are generated.
Some of the values, such as id and name will get automatically generated by Node.set_identifiers(), and will be based off of what you name the attribute in you class definition.
The data attribute is automatically populated when validation is run. This is performed by Node.resolve_data() and talked about in the Custom Node section below.
This attriubte is a list of errors generated by validator callables. More about this in validation.
The remained of variables in the above template are just plain old attributes with defaults. Keep in mind that attriubtes/arguments in Yota do not behave quite like they do normally in Python. Learn more about this in Attribute and Argument behaviour in the Form page.
The majority of Node attributes may be overridden either through initialization of the function, like so:
my_node = EntryNode(name="Something else", template="custom_entry")
Or by setting it as a class attribute in your Node definition like so:
class EntryNode(BaseNode):
template = 'entry'
_ignores = ['template']
However, keep in mind that attributes that are auto-generated, such as name, id, and title should not be set as class attributes since they will get overriden when they are generated. By default, the following attributes are reserved:
Most Node definitions are quite simple, with the majority simply changing the template being used. More complex Node semantics are availible by overriding some of their built in methods, such as Node.resolve_data() or Node.set_identifiers(). These are all described in the API documentation, but some examples will be given here of how you might wish to use these methods.
The default Node implementation assumes that your Node only contains one input, and as such its data output is assumed to be tied directly to this single input. The Node.set_identifiers() method defines a defualt implementation for naming your input field that looks something like this:
try:
self.data = data[self.name]
except KeyError:
self.data = self._null_val
You can see above that the Node’s name is used to pick out the data that is associated with this Node. But say your Node includes multiple input fields, perhaps you have a date picker. A simple template may look like this:
Month: <input type="text" name="{ name }_month" placeholder="Month" /><br />
Day: <input type="text" name="{ name }_day" placeholder="Day" /><br />
Year: <input type="text" name="{ name }_year" placeholder="Year" /><br />
Now of course the Node.resolve_data() will fail to find anything associated with “name” since it doesn’t exist, and instead an implementation may look something like this.
def resolve_data(self, data):
try:
day = data[self.name + '_day']
month = data[self.name + '_month']
year = data[self.name + '_year']
except KeyError:
self.data = self._null_val
# set data to a tuple of values for validation
self.data = (year, month, day)
Aside from our crappy looking form, and some lack of bounds checking everything is good. Now say we wanted to make this form work with AJAX, and we wanted to make the border of each of the form elements red when there was an error. Well this is a problem, because our JavaScript doesn’t implicity know how to find the elements. You could modify your render_error method to manually catch this case, but this wouldn’t be a very resiliant option. Instead, we can make our default functions aware of these extra elements. This is done through the json_identifiers method.
Node.json_identifiers() is executed by validation methods when sending erros back to the client side via JSON. It is used to give the client side inoformation about where the error data should be placed in the DOM. Essentially your render_error and render_success methods are passed an ‘ids’ object, and this is a direct serialization of the return from this function. The default render_error and render_success methods expect the following keys:
When the Node is added to a Form the set_identifiers method is called to setup some unique names to be used in the template and possibly AJAX. Perhaps you’d like a different semantic for automatically titling your date pickers? Overriding this function may also be wanted if you’re writing a Node with multiple form elements in it. This all depends on your preference.
def set_identifiers(self, parent_name):
super(MySuperSpecialNode, self).set_identifiers(parent_name)
if not hasattr(self, 'title'):
self.title = self._attr_name.capitalize() + " Very Special"
This base Node supplies the name of the base rendering template that is used for standard form elements. This base template provides error divs and the horizontal form layout for Bootstrap by default through the horiz.html base template.
A base to inherit from for Nodes that aren’t designed to generate output, such as the SubmitNode or the LeaderNode. It must override resolve_data, otherwise the data will be set to Node._null_val.
is a list of tuples providing the key and value for the dropdown list items.
Note
The first item of the tuple must be a string in order to match returned data properly and re-select the same list item when a validation error occurs.
Attr items: | Must be a list of tuples where the first element is the value of the second is the label. |
---|
Node for providing a group of radio buttons. Requires buttons attribute.
Attr buttons: | Must be a list of tuples where the first element is the value of the second is the label. |
---|
Node for providing a group of checkboxes. Requires boxes attribute. Instead of defining an ID value explicitly the Node.set_identifiers defines a prefix value to be prefixed to all id elements for checkboxes in the group. The output data is a list containing the names of the checkboxes that were checked.
Attr boxes: | Must be a list of tuples where the first element is the name, the second is the label. |
---|
A node with a basic textarea template with defaults provided.
Attr rows: | The number of rows to make the textarea |
---|---|
Attr columns: | The number of columns to make the textarea |
Nodes are holders of context for rendering and displaying validating for a portion of your Form. This default base Node is designed to provide a template along with specific context information to a templating engine such as Jinja2. For validation a Node acts as an information source or an error sink. Essentially Nodes can be used to source data for use in a Check, and they can then be delivered some sort of validation error via a the internal errors attribute.
Note
By default all keyword attributes passed to a Node’s init function are passed onto the rendering context. To override this, use the Node._ignores attribute.
Parameters: |
|
---|
The default Node init method accepts any keyword arguments and adds them to the Node’s rendering context. In addition any class attributes may be added to custom Nodes and these attributes will be copied at instantiation time and passed into the rendering context.
Allows tracking the order of Node creation
This method serves mostly as a wrapper alowing for different error ordering semantics, or possibly error post-processing. Errors from validation methods should be added in this way allowing them to be caught. More information about what gets passed in in the Validators and Checks section.
Builds our rendering context for the Node at render time. By default all attributes of the Node are added to the global namespace and the global rendering context is passed in under the variable ‘g’. This function is designed to be overridden for customization. :param g_context: The global rendering context passed in from the rendering method.
Parameters: | g_context – This is the global context passed in from the parent Form object. By default it’s included under the ‘g’ key, similar to Flask’s globals. |
---|
As the title suggests this needs to return an iterable of names. These should be names corresponding to form elements that the Node will generate. This list is uesed by piecewise validation to determine if a Node has been visisted based on a list of names that have been visited, bridging Nodes to elements.
Allows passing arbitrary identification information to your JSON error rendering callback. For instance, a common use case is the display an error message in a pre-defined div with a specific id. Well you may perhaps pass in an ‘error_div_id’ attribute to the JSON callback to use when trying to render this error. The default for Yota builtin nodes is to pass ‘error_id’ indicating the id of the error container in addition to a list containing all input elements in the Node’s ids.
This method links data from form submission back to Nodes. HTML form data is represented by a dictionary that is keyed by the ‘name’ attribute of the form element. Since most Nodes only render a single form element, and the default set_identifiers generates a single ‘name’ attribute for the Node then this function attempts to find data by linking the two together. However, if you were to change that semantic this would need to change. Look at the CheckGroupNode for a reference impplementation of this behaviour, or the Docs under “Custom Nodes”. This method should operate by setting its own data attribute, as this is how Validators conventionally look for data.
Parameters: | data – The dictionary of data that is passed to your validation method call. |
---|
This function gets called by the parent Form when it is initialized or inserted. It is designed to set various unique identifiers. By default it generates an id for the Node that is {parent_name}_{_attr_id}, a title for the Node that is the _attr_name capitalized, and a name for the element that is just the _attr_name. All of these attributes are then passed onto the rendering context of the Node by default. By default all of these attributes will yield to attributes passed into the __init__ method.
Parameters: | parent_name (string) – The name of the parent form. Useful in ensuring unique identifiers on your element names. |
---|