Personal tools
You are here: Home Documentation Tutorials Using YUI on Grok Adding a YUI menu

Adding a YUI menu

We'll first define a menu using megrok.navigation, and then override the default template to make it YUI compatible. Then call some YUI js code to make it work.
In this tutorial you'll be learning how to use the Yahoo! User Interface Library, or YUI in short in your Grok project. YUI is a JavaScript and CSS library providing all sorts of handy components that you can use to make you site work an look better without too much effort, and in a cross-browser compatible way. In the process, you'll also be learning some basic uses of megrok.layout, megrok.navigation and JSON.
Page 2 of 6.

Defining the Menu

For this part of the tutorial, you should have some basic understanding of what megrok.navigation does. Check the megrok.navigation page on PyPI.

The menus created by megrok.navigation are viewletmanagers, and the items are viewlets, so if you define a menu, you are in fact defining a viewletmanager. Create a new source file 'menu.py', and add this code

import grok
from megrok import navigation

class MainMenu(navigation.Menu):
        grok.name('main-menu')

Edit 'layout.pt', and replace

<div id="header" role="navigation">Navigation will go here!</div>

with

<div id="header" role="navigation" tal:content="structure provider:main-menu"></div>

Edit 'app.py' by adding some imports and changing the Index view definition to

from megrok import navigation
from menu import MainMenu

class Index(layout.Page):
    navigation.sitemenuitem(MainMenu, 'Home', order=-1)

We created a menu, called it 'main-menu', told the layout template to render it at the top of the page, and added the main index view as the home link, setting its order to -1 to have it rendered first (the default order is 0).

Restart your server and take a look.

site_p2a.jpg

Not exactly state-of-the-art, is it? The default templates of megrok.navigation just render an unordered list (<ul>), and there is no CSS or javascript in place to do anything fancy. A YUI menu requires a bit more than just an <ul>, so we need to tell megrok.navigation to use a different template.

Overriding the templates

megrok.template makes this an easy job. Go back to 'menu.py' and change the menu definition, and add 2 page templates

from megrok import pagetemplate
from hurry import yui

class MainMenu(navigation.Menu):
    grok.name('main-menu')

    def update(self):
        super(MainMenu, self).update()
        yui.menu.need()

    id = 'main-menu'
    cssClass='yuimenubar yuimenubarnav'
    cssItemClass='yuimenubaritem'
    cssItemLabelClass='yuimenubaritemlabel'

class MenuTemplate(pagetemplate.PageTemplate):
    grok.template('menu')
    pagetemplate.view(navigation.interfaces.IMenu)

class ItemTemplate(pagetemplate.PageTemplate):
    grok.template('item')
    pagetemplate.view(navigation.interfaces.IMenuItem)

The menu definition overrides the update method to tell hurry.yui that it need() s its menu resource library. Don't forget to call the super classes update when you invoke need() here, as a menu is a viewletmanager, and as such is required to call its viewlets' update method. Create a directory 'menu_templates', and add a file 'menu.pt' with this content:

<div tal:attributes="id viewletmanager/id|default;class viewletmanager/cssClass"
        tal:define="viewlets viewletmanager/viewlets"
        tal:condition="viewlets">
        <div class="bd">
                <ul class="first-of-type">
                        <tal:repeat tal:repeat='viewlet viewlets'
                            tal:replace='structure viewlet/render'/>
                </ul>
        </div>
</div>

Also, add a file 'item.pt' with this content:

<li  tal:attributes="class viewletmanager/cssItemClass"
        tal:define="submenu viewlet/subMenu | nothing">
  <a href="#"
     tal:attributes="href viewlet/link; class viewletmanager/cssItemLabelClass">
     <img tal:condition="viewlet/icon | nothing" tal:attributes="src viewlet/icon;
                                                                     title viewlet/title"/>
     <span tal:content="viewlet/title">Title</span></a>
  <tal:block condition="submenu"
             tal:replace="structure provider:${submenu}">sub menu items</tal:block>
</li>

All we did now was making sure the menu is rendered in a YUI compatible manner, using DIV and UL elements, and setting the right CSS classes. When overriding the megrok.navigation templates, you should bear two things in mind:

  • don't forget to render the viewlets (the structure viewlet/render TALES expression).
  • if you have submenus, render them in the item after the title (using a structure provider:$viewlet/subMenu statement or equivalent)

Letting YUI loose

So far, so good, but the menu still looks the same. Let's get YUI to do it's magic with it! We need to add a piece of javascript to the page. It's common practice to do that at the bottom of the page. We could just add it to the layout, but there is a more elegant way: add a viewletmanager to the bottom of the layout, and add scripts as viewlets, so the layout doesn't have to know what is needed.

Add this line to 'layout.pt' just above the </body> tag:

<tal:block tal:replace="structure provider:scripts"/>

Edit 'layout.py', and add this:

class Scripts(grok.ViewletManager):
    grok.context(Interface)

Now, in 'menu.py' add this:

from layout import Scripts
from zope.interface import Interface

class MenuScript(grok.Viewlet):
    grok.viewletmanager(Scripts)
    grok.context(Interface)
    grok.template('script')

And add a file 'script.pt' in the 'menu_templates' dir containing

<script type="text/javascript">
    YAHOO.util.Event.onContentReady("main-menu", function () {
        var oMenuBar = new YAHOO.widget.MenuBar("main-menu", {
                                                    autosubmenudisplay: true,
                                                    hidedelay: 750,
                                                    lazyload: true });
        oMenuBar.render();
    });
</script>

Now there is still one important part missing: the default YUI skin called 'sam'. First of all, you need to activate it by adding an attribute 'class' with the value 'yui-skin-sam' to the <body> tag in 'layout.pt':

<body class="yui-skin-sam">

But the skin CSS also needs to be loaded. A quick way is to load the entire sam skin by adding a yui.sam.need() call in the layout's update. But this loads the styles for all YUI components. That makes up for quite a hefty file, whereas we're only interested in the menu part. YUI also provides separate skin files for each component. We'll use such a separate file, but again, we're going to do it using a viewletmanager.

Add this to the 'layout.pt's <head> element just between the <title> and <link> elements:

<tal:block tal:replace="structure provider:stylesheets"/>

And add this class to 'layout.py':

class StyleSheets(grok.ViewletManager):
    grok.context(Interface)

Now edit 'menu.py' to add

from layout import StyleSheets

class MenuStylesheet(grok.Viewlet):
    grok.viewletmanager(StyleSheets)
    grok.context(Interface)
    template = grok.PageTemplate('<link rel="stylesheet" type="text/css" '
                   'tal:attributes="href context/++resource++yui/menu/assets/skins/sam/menu.css" />')

We took a shortcut here in order not to have to create a separate .pt file for 1 line of code.

Now restart the server and take a look.

site_p2b.jpg

Looks a lot better, doesn't it?

Some more fun!

Let's have some more fun. Let's pretend we are creating (yet another) blogging app. Add a new menu in 'menu.py':

class BlogMenu(navigation.ContentMenu):
    grok.name('blog-menu')

    def update(self):
        super(BlogMenu, self).update()
        yui.menu.need()

    def getContent(self):
        return grok.getSite().values()
    def getTitle(self, entry):
        return entry.title

    id = 'blog-menu'
    cssClass='yuimenu yuimenunav'
    cssItemClass='yuimenuitem'
    cssItemLabelClass='yuimenuitemlabel'

and add this directive to the MainMenu class definition

navigation.submenu('blog-menu', 'Blogs')

Here we created a 'ContentMenu': a menu linking to site content, which is a specialization of a normal menu. A standard menu links to other views of the current context (navigation.menuitem) or of the site (navigation.sitemenuitem). A ContentMenu links to views (typically the 'index' view) of arbitrary (but locatable) objects you choose in the getContent() method. We then add the Blog menu as a submenu (navigation.submenu) to the Main menu so that it will appear as a typical desktop application style drop-down menu.

Create 'blog.py' containing

import grok
from zope.interface import Interface
from zope import schema
from megrok import layout, navigation
from urllib import quote_plus
from menu import BlogMenu
from cgi import escape

class IBlogEntry(Interface):
    title = schema.TextLine(title=u'title')
    text = schema.Text(title=u'Body')

class BlogEntry(grok.Model):
    grok.implements(IBlogEntry)

    @property
    def htmltext(self):
        return escape(self.text).replace('\n', '<br/>');

class BlogIndex(layout.Page):
    grok.name('index')

class Add(layout.AddForm):
    navigation.sitemenuitem(BlogMenu, order=-1)
    grok.title('Add a Blog Entry')
    grok.context(grok.Application)
    form_fields = grok.Fields(IBlogEntry)

    @grok.action('Add entry')
    def Add(self, **data):
        entry = BlogEntry()
        self.applyData(entry, **data)
        grok.getSite()[quote_plus(entry.title)] = entry
        self.redirect(self.url(entry))

Create a directory called 'blog_templates and add 'blogindex.pt', containing

<h1 tal:content="context/title"></h1>
<p tal:content="structure context/htmltext"></p>

Restart the server and have a look. Add a few blog entries to check it out.

site_p2c.jpg

Well done, you implemented a YUI menu in Grok!

Of course, you can skin the menu all you like, but that's beyond the scope of this tutorial, just check the YUI docs on how to do that.