Advanced topics =============== Renderers --------- Elements from the :mod:`flask_nav.elements` module do not have any methods for conversion to HTML code; this functionality is placed in :class:`.Renderer` classes. These implement the visitor_ pattern and allow specifying a multitude of ways of converting your navigational structure into HTML. Whenever :meth:`flask_nav.elements.NavigationItem.render` is called, it just looks up the desired renderer and calls the ``visit`` method of the renderer on itself. The result is returned as a markupsafe string. Implementing custom renderers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ As an example, we are going to create a new renderer that uses nothing but ``
`` tags [1]_. Flask- Nav uses the dominate_ library to create HTML output which we also use here: .. code-block:: python from dominate import tags from flask_nav.renderers import Renderer class JustDivRenderer(Renderer): def visit_Navbar(self, node): sub = [] for item in node.items: sub.append(self.visit(item)) return tags.div('Navigation:', *sub) def visit_View(self, node): return tags.div('{} ({})'.format(node.title, node.get_url())) def visit_Subgroup(self, node): # almost the same as visit_Navbar, but written a bit more concise return tags.div(node.title, *[self.visit(item) for item in node.items]) Now that we have our renderer, we need to register it on the app. This can be done inside your ``create_app`` function (or elsewhere), by calling register .. code-block:: python from flask_nav import register_renderer def create_app(): # [...] register_renderer(app, 'just_div', JustDivRenderer) return app Now we can use it inside the template: .. code-block:: jinja {{nav.top.render(renderer='just_div')}} If you are defining your custom renderers close to where the extension instance lives, instead of ``register_renderer``, the :meth:`~.Nav.renderer`-decorator can be used: .. code-block:: python @nav.renderer() class JustDivRenderer(Renderer): pass # ... # upon registration, 'just_div_renderer' will be a registered Elements -------- Any navigational structure is composed out of items. Thanks to the visitor pattern, these can be of any class, but it is worthwhile to make all descend from :class:`.NavigationItem`. Typically, :class:`.Navbar` is the top level object of a navigational bar, but that is not a requirement. Furthermore, if the renderer supports it, any part of a navigational structure can be rendered on its own, be it a lone link or full submenu. Custom elements ~~~~~~~~~~~~~~~ Sometimes you may need to implement your own Element classes. This is easily done by subclassing either :class:`.NavigationItem` or a more concrete class (``get_auth_user`` is a placeholder here for any way your favorite authentication framework returns the current user): .. code-block:: python class UserGreeting(Text): def __init__(self): pass @property def text(self): return 'Hello, {}'.format('bob') Note that when subclassing :class:`.NavigationItem`, renderers will most likely not have a default rendering method. By subclassing :class:`.Text` in the example, existing methods on renderers for the text class can be used, as visitors will go up the full inheritance chain when a visitor for the current class cannot be found. Dynamic construction -------------------- In the `Custom elements` section, a bit of dynamic behavior is already seen: The greeting changes depending on who's logged in. This does not alter the structure of the bar though, there is always a ``UserGreeting`` object inside the structure. To create dynamic instance of navbars, simply pass a :func:`callable` object like a function to :meth:`.register_element`: .. code-block:: python def top_nav(): return Navbar(...) nav.register_element('top_nav', top_nav) This is a common pattern, for this reason the :meth:`.navigation`-decorator is available: .. code-block:: python @nav.navigation def top_nav(): # ... The ``top_nav()`` function will be called every time a navbar must be rendered. At this point, a user should have already logged, making it possible for example to present him with menu items only available to registered users. This mechanism can also be used to lazily instantiate navbars, if they are expensive to setup but rarely used. It is also possible to preinstantiate non-dynamic parts and just compose these with dynamic instances. .. _visitor: https://en.wikipedia.org/wiki/Visitor_pattern .. _dominate: https://github.com/Knio/dominate/ .. [1] Which is probably not a good idea, but a valid example.