Advanced topics

Renderers

Elements from the flask_nav.elements module do not have any methods for conversion to HTML code; this functionality is placed in Renderer classes. These implement the visitor pattern and allow specifying a multitude of ways of converting your navigational structure into HTML.

Whenever 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 <div> tags [1]. Flask- Nav uses the dominate library to create HTML output which we also use here:

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

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:

{{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 renderer()-decorator can be used:

@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 NavigationItem.

Typically, 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 NavigationItem or a more concrete class (get_auth_user is a placeholder here for any way your favorite authentication framework returns the current user):

class UserGreeting(Text):
    def __init__(self):
        pass

    @property
    def text(self):
        return 'Hello, {}'.format('bob')

Note that when subclassing NavigationItem, renderers will most likely not have a default rendering method. By subclassing 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 callable() object like a function to register_element():

def top_nav():
    return Navbar(...)

nav.register_element('top_nav', top_nav)

This is a common pattern, for this reason the navigation()-decorator is available:

@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.

[1]Which is probably not a good idea, but a valid example.