=========== Nodes =========== .. py:currentmodule:: yota Nodes drive the actual rendering of your :class:`Form`. Internally a :class:`Form` keeps track of a list of :class:`Node`'s and then passes them off to the :ref:`Renderer ` when a render of the :class:`Form` is requested. Lets look at a simple example Form as shown in the introduction: .. code-block:: python 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 :class:`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 :class:`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. A Simple Node ============================== 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 :class:`nodes.EntryNode`. It has the following template: .. code-block:: html {% extends base %} {% block control %} {% 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: .. code-block:: html
{% block error %} {% if errors %}
{{ errors[0]['message'] }}
{% endif %} {% endblock %} {% if label %} {% block label %} {% endblock %} {% endif %}
{% block control %} {% endblock %}
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: .. code-block:: python 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. Identifiers ******************************* Some of the values, such as id and name will get automatically generated by :meth:`Node.set_identifiers`, and will be based off of what you name the attribute in you class definition. Data ******************************* The data attribute is automatically populated when validation is run. This is performed by :meth:`Node.resolve_data` and talked about in the Custom Node section below. Errors ******************************* This attriubte is a list of errors generated by validator callables. More about this in validation. Other **************************** 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: .. code-block:: python my_node = EntryNode(name="Something else", template="custom_entry") Or by setting it as a class attribute in your Node definition like so: .. code-block:: python 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: + name + id + title + errors + data + _attr_name + _ignores + _requires + _create_counter Custom Nodes =============================== 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 :meth:`Node.resolve_data` or :meth:`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. Changing data resolution **************************** 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 :meth:`Node.set_identifiers` method defines a defualt implementation for naming your input field that looks something like this: .. code-block:: python 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: .. code-block:: html Month:
Day:
Year:
Now of course the :meth:`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. .. code-block:: python 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. Modifying AJAX rendering **************************** :meth:`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: * 'error_id': This should be an id value of a DOM element that you would like to place your error 'message' in. This is not actually used by default, but is implemented by all builtin Nodes. It corresponds to the DOM element that renders regular errors. * 'elements': This supplies a list of all ids of form elements in the Node. Error tooltips point to the first element. set_identifiers **************************** 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. .. code-block:: python 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" .. py:currentmodule:: yota Builtin Nodes ===================== .. autoclass:: yota.nodes.BaseNode .. autoclass:: yota.nodes.NonDataNode .. autoclass:: yota.nodes.ListNode .. autoclass:: yota.nodes.RadioNode .. autoclass:: yota.nodes.CheckGroupNode .. autoclass:: yota.nodes.ButtonNode .. autoclass:: yota.nodes.EntryNode .. autoclass:: yota.nodes.PasswordNode .. autoclass:: yota.nodes.FileNode .. autoclass:: yota.nodes.TextareaNode .. autoclass:: yota.nodes.SubmitNode .. autoclass:: yota.nodes.LeaderNode Node API =========== .. autoclass:: Node :members: :undoc-members: :private-members: