.. % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
.. % Copyright 2001 by Object Craft P/L, Melbourne, Australia.
.. % LICENCE - see LICENCE file distributed with this software for details.
.. % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


.. _cust-ref:

**********************
Developing Custom Tags
**********************

In complex applications you may encounter presentation problems which the
standard collection of tags cannot easily solve.  Albatross allows you to
register additional tags, which are then available for use in your templates.
Custom tags are named with an ``alx-`` prefix to distinguish them from standard
``al-`` tags.

Custom tags should subclass either :class:`EmptyTag` or :class:`EnclosingTag`,
and have a ``name`` class attribute. The name should start with ``alx-`` and
contain only letters, numbers and the underscore character.

Custom tags that produce form inputs need to register the names of those inputs
with the :class:`NameRecorderMixin` via the :meth:`input_add` method. For more
information, see section :ref:`mixin-rec-name`, NameRecorderMixin in the Mixin
Class Reference.

The following is a simple calendar tag which formats a single month like the
unix :program:`cal(1)` program.

.. code-block:: python

   import time
   import calendar
   import albatross


   class Calendar(albatross.EmptyTag):

       name = 'alx-calendar'

       def to_html(self, ctx):
           year = self.get_attrib('year')
           if year is not None:
               year = ctx.eval_expr(year)
           month = self.get_attrib('month')
           if month is not None:
               month = ctx.eval_expr(month)
           if month is None or year is None:
               now = time.localtime(time.time())
               if year is None:
                   year = now[0]
               if month is None:
                   month = now[1]
           ctx.write_content('<table>\n')
           ctx.write_content('<tr align="center"><td colspan="7">%s %s</td></tr>\n' \
                             % (calendar.month_name[month], year))
           ctx.write_content('<tr>')
           for i in range(7):
               ctx.write_content('<td>%s</td>' \
                                 % calendar.day_abbr[(i + 6) % 7][:2])
           ctx.write_content('</tr>\n')
           calendar.setfirstweekday(6)
           for r in calendar.monthcalendar(year, month):
               ctx.write_content('<tr align="right">')
               for i in range(7):
                   if r[i]:
                       ctx.write_content('<td>%s</td>' % r[i])
                   else:
                       ctx.write_content('<td></td>')
               ctx.write_content('</tr>\n')
           ctx.write_content('</table>\n')

To use the tag in your application you must make the class available to the
execution context.  If you are using an Albatross application object you can do
this by passing the class to the :meth:`register_tagclasses` method of the
application object.

.. code-block:: python

   from albatross import SimpleApp

   app = SimpleApp('ext.py', '.', 'start')
   app.register_tagclasses(Calendar)

All Albatross application classes inherit from the :class:`ResourceMixin` in the
:mod:`albatross.context` module. Execution contexts which are used with
application objects inherit from the :class:`AppContext` class from the
:mod:`albatross.app` module which automatically retrieves all resources from the
parent application object.

If you are using the :class:`SimpleContext` class for your execution context
then you will need to call the :meth:`register_tagclasses` method of the
execution context immediately after construction.

The following is an example template file which uses the ``<alx-calendar>``
tag.

.. code-block:: albatross

   <html>
   <head><title>Calendar for <al-value expr="year"></title></head>
   <body>
   <h1>Calendar for <al-value expr="year"></h1>
   <table cellpadding="10">
   <al-for iter="r" expr="range(1,13)" cols="3" flow="across">
    <tr valign="top">
    <al-for iter="m" expr="r.value()">
     <td><alx-calendar month="m.value()" year="year"></td>
    </al-for>
    </tr>
   </al-for>
   </table>
   </body>
   </html>

A complete program which uses this extension tag and template file can be found
in the ``samples/extension`` directory.  Use the ``install.py`` script to
install the sample.

.. code-block:: sh

   cd samples/extension
   python install.py

The implementation of the standard tags also makes a good reference when writing
custom tags. All standard tags are defined in :mod:`albatross.tags`.


:mod:`albatross.template` --- Base classes for implementing tags
================================================================

.. module:: albatross.template


The module contains the following classes which are intended to be used in
implementing custom tags.


.. class:: Tag(ctx, filename, line_num, attribs)

   This is the base class upon which all tags are implemented.  You are unlikely to
   ever subclass this directly.  The :class:`EmptyTag` and :class:`EnclosingTag`
   classes inherit from this class.


.. class:: EmptyTag(ctx, filename, line_num, attribs)

   Use this class as a subclass for all tags which do not require a closing tag and
   therefore do not enclose content.  Examples of standard HTML tags which do not
   enclose content are ``<BR>`` and ``<HR>``.


.. class:: EnclosingTag(ctx, filename, line_num, attribs)

   Use this class as a subclass for all tags which enclose content. Examples of
   standard HTML tags which enclose content are ``<BODY>`` and ``<TABLE>``.


.. class:: Text(text)

   A simple wrapper around the string passed in the *text* constructor argument
   which passes that string to the :meth:`to_html` method when the object is
   converted to HTML.


.. class:: Content()

   A simple wrapper around a list which calls the :meth:`to_html` method of all
   list elements when the object is converted to HTML.


.. _cust-tag:

Tag Objects
-----------


.. method:: Tag.raise_error(msg)

   Raises a :exc:`TemplateError` exception using the string in the *msg* argument.


.. method:: Tag.has_attrib(name)

   Returns ``TRUE`` if the attribute specified in the *name* argument was defined
   for the tag.  All attribute names are converted to lower case by the template
   parser.


.. method:: Tag.assert_has_attrib(name)

   If the attribute specified in the *name* argument is not defined for the tag a
   :exc:`TemplateError` exception will be raised.


.. method:: Tag.assert_any_attrib(*names)

   If none of the attributes specified by the arguments are defined for the tag
   a :exc:`TemplateError` exception will be raised.

.. method:: Tag.get_attrib(name [, default ``= None``])

   Retrieves the value of the attribute specified in the *name* argument.


.. method:: Tag.set_attrib(name, value)

   Sets the value of the attribute named in the *name* argument to the value in the
   *value* argument.


.. method:: Tag.set_attrib_order(order)

   Defines the order that the tag attributes will be written during conversion to
   HTML.  The template parser captures the attribute sequence from the template
   file then calls this method.


.. method:: Tag.attrib_items()

   Returns a list of attribute name, value tuples which are defined for the tag.


.. method:: Tag.write_attribs_except(ctx [, ...])

   Sends all tag attributes to the :meth:`write_content` method of the execution
   context in the *ctx* argument.  Any attributes named in additional arguments
   will not be written.


.. _cust-emptytag:

EmptyTag Objects
----------------


.. method:: EmptyTag.has_content()

   Returns ``0`` to inform the template parser that the tag does not enclose
   content.


.. method:: EmptyTag.to_html(ctx)

   The template interpreter calls this method to convert the tag to HTML for the
   execution context in the *ctx* argument.  The default implementation does
   nothing.

   You must override this method in your tag class to perform all actions which are
   necessary to "execute" the tag.


.. _cust-enclosingtag:

EnclosingTag Objects
--------------------


.. attribute:: EnclosingTag.content

   An instance of the :class:`Content` class which is created during the
   constructor.


.. method:: EnclosingTag.has_content()

   Returns ``1`` to inform the template parser that the tag encloses content.


.. method:: EnclosingTag.append(item)

   Called by the template parser to append the content in the *item* argument to
   the tag.  The method implementation simply passes *item* to the :meth:`append`
   method of the :attr:`content` member.

   You should override this method if you need to maintain multiple content lists
   within your tag.


.. method:: EnclosingTag.to_html(ctx)

   The template interpreter calls this method to convert the tag to HTML for the
   execution context in the *ctx* argument.  The default implementation passes
   *ctx* to the the :meth:`to_html` method of the :attr:`content` member.

   You must override this method in your tag class to perform all actions which are
   necessary to "execute" the tag.


.. _cust-text:

Text Objects
------------


.. method:: Text.to_html(ctx)

   Sends the wrapped text to the :meth:`write_content` method of the execution
   context in the *ctx* argument.  You should not ever need to subclass these
   objects.


.. _cust-content:

Content Objects
---------------


.. method:: Content.append(item)

   Appends the value in the *item* argument to the internal Python list.


.. method:: Content.to_html(ctx)

   Sequentially invokes the :meth:`to_html` method of every item in the internal
   Python list passing the *ctx* argument.

