Our journey continues with adding the ability to support a fixed order for menu items which are derived from a container which looks, and quacks, like a dict. We also want to support the ability to change the maintained order of articles.
Since we know that the list of contained articles are not going to grow very large (there's a practical limit), it does not make a great deal of sense to expend much effort in permanently storing and maintaining lists of articles or article references. Instead, directly producing a new list and sorting the list every time we want to produce a sorted list of articles is hardly going to impact performance, while greatly reducing complexity and possibility for error.
The way we will remember the order of articles, is to store a persistent attribute called 'order' with each IArticle
.
As we have already seen, our sorter interface looks like this:
We can see that this specifies that the interface should define a method returning a list of sorted articles.
An adapter which returns an instance of IArticleSorter
when given an instance of an IArticle
, is defined as:
As you can see, this does nothing more than define a factory which spews out instances of the Sorter()
class. So what is the Sorter()
class, and what does it do? At the very least we know it must implement a method called sortedItems()
which according to the interface definition, returns a list of sorted articles:
Whoops! Information Overload!
What is that grok.implements(IArticleSorter, ILayout)
and grok.provides(IArticleSorter)
???
We are simply telling the ZCA that a Sorter model implements both IArticleSorter
and ILayout
interfaces. That is why the Sorter
model also defines a title
, since the site index view requires it (as per the ILayout
interface definition).
As the ZCA now has two interfaces defined for Sorter
, one cannot tell which one of the two the model provides. Hence the need for grok.provides(IArticleSorter)
.
Other than a few shenanigans in the renumber()
method, the rest of the Sorter
class implementation should be self-explanitory.
The renumber()
method is passed a sorted list of [references to] instances of IArticle
, as contained in their parent container. We traverse this list in order, and assign sequential numbers to the order
attribute. At the same time, we assign other attributes for prev
, next
, being the navTitle
values for the prior and next article in the list respectively. This will simplify implementing menu items for the prior or next items in the list while rendering a given article.
So this is why a simple call to sorter=IArticleSorter(context)
can return an instance of Sorter for the context, and subsequent calls to sorter.sortedItems()
will return the needed list of items- whilst as an added benefit ensuring consistency in things like prev/next links.
Ok, so what happens if we traverse the site and end up on an instance of a Sorter
?
Since Sorter
implements ILayout
, the default view, being index, will be rendered. This means that viewlets for the Masthead, Footer, Navigation and Content areas will be rendered (if they exist) using the Sorter
as a context.
To render the Sorter Content
, we define a viewlet to render the list of contained IArticle
instances:
For viewlets and views, the update()
function will be called prior to the render()
function or template. This means that one may perform any processing needed that the template might need later. In this case, we have an obscure looking ordermenu.need()
and an assignment to a class variable containing the sorted items.
The ordermenu
is defined in resource.py as:
In the Installation section we described how static resources are served through the fanstatic library. The definition of resources and their dependencies is generally found in the resource.py file, although there is nothing that enforces that module name. To include a resource - in this case ordermenu.js - in the <head />
tag of the site, simply include in the update()
function for the view or viewlet, a line such as ordermenu.need()
.
Our template for ReOrderViewlet
is found in reorderviewlet.pt:
After styling, the sorter renders as a list of items something like this:
For navigation buttons, we define the following two classes:
which adds two menu items to the sorter. The first navigates back to the parent article, while the other effectively navigates back to the same URL that got us to the sorter in the first place. What???
Take a closer look at the mclass = 'setItemOrder
...' line. The trick is there. A small javascript function in ordermenu.js traps and takes care of handling the button click, and performs the actual sorting function. The operation of the javascript function is described in the comments of ordermenu.js:
So, to be able to reorder a list of <li />
elements, we give them a class of 'movable'. When we click an 'li.movable', we change the state of the clicked item to 'moving'; the siblings above get an aboveme class, and those below get a belowme class. Clicking aboveme or belowme items completes the move operation. The rest is done with css.
The line $('<div />').load('setOrder', {'new_order':JSON.stringify(setOrder)}, function(response, status, xhr)...
is the important bit which actually updates the item order. The new order is simply an array of navTitle
values retrieved from the list items themselves, and already in the now required order. This code does a JSON call to the server, calling the setOrder(new_order)
function. At the server side, we implement the function within a grok.JSON
class:
As stated in the comments, one would not generally need to use JSON.stringify/simplejson.loads()
to deal with arguments. However, the fact that arrays are passed with incompatible names makes argument transparency not really doable in Python.