Recently in Dynamic Content Category

Document Modeling with Bricolage

Previous Perl.com articles have reviewed where Bricolage fits into the universe of content management systems and worked through Bricolage installation and Bricolage configuration. Now it's time to go through the steps required to model the structure of an existing web page in Bricolage. Part of the motivation for the redesign of the Bricolage website last summer was to create good examples of document types and templates for use in Bricolage itself. You can take advantage of that work by analyzing a page on the current Bricolage site to determine how to break it down into its basic elements.

First, here's a brief introduction to document types in Bricolage.

The Elements of Bricolage Stories

Bricolage features two types of documents: stories and media. Stories contain text content and metadata; media are just like stories, but can have a single media file associated with them (an image file, movie, sound file, PDF, etc.). Whether a document is a story document or a media document, elements define its structure.

Elements are the basic building blocks of all documents in Bricolage. There are two types of elements: container elements and field elements. Container elements can contain any number of fields and other container elements. Fields, on the other hand, contain text content. Bricolage presents fields as standard HTML fields such as text, textarea, pulldown, radio, etc.--even a date and time widget. In a document model, one container element is the top element and it, along with all of the fields and subelements it contains, constitutes the structural model for the content of documents based on it.

The important point here is that Bricolage encourages a highly structured model for your documents; documents based on the models are thus structurally consistent. It also makes it easy to write incredibly flexible templates to output content in a variety of formats. (The next article in this series will cover Bricolage templating.) Document models can also be deeply hierarchical, to whatever extent is necessary to accurately model the structure of the documents being managed. Be careful, though, because if the model has too many levels of hierarchy, it will be more difficult for users to conceptualize when editing documents, as well as more work to drill down into deeply nested elements in the Bricolage user interface.

Document Analysis

Bricolage.cc Article Screenshot
Figure 1. A screenshot of the article being analyzed

Document analysis is the process of analyzing the layout of a document and breaking it down into its basic elements. Examining a page on a website, you must determine how all of the basic parts fit together and in what hierarchy, so that you can model the element structure necessary to accurately represent the document in Bricolage. That is, determine the container elements and fields that would be necessary to accurately recreate the document in Bricolage.

For the sake of this article, consider the structure of a typical page on the Bricolage website, because it makes a nice representative sample of the elements articles on the site will likely need (see Figure 1).

Identifying Content

The first thing to do is to determine what part of that page constitutes content and what does not. The term "content" here distinguishes those parts of the page that are important to the document itself, as opposed to the site overall or to a section of the site. For example, the banner at the top of the page appears on every page on the site; it is not specific to this document, nor is it significant to the document's contents. Likewise, the footer section is global to the site and contributes nothing to the document. The Recent News list in the right-hand column also has nothing to do with the contents of the article, it being a simple list of the five most recent articles published on the entire site.

These other components are includes, because they're included on many pages--or even on every page of the site. It also doesn't hurt that web servers generally pull them into the layout via a server-side include technology (such as mod_include, HTML::Mason, PHP, or JSP). Because they're not significant to the content of the document, you can ignore them for the rest of this analysis. Figure 2 depicts everything that remains.

Article Content Only
Figure 2. The important part of the document for the purposes of analysis: the actual content

Defining the Top Element

Having isolated the content of the page, you can start breaking its content down into its component parts. First, give the document type a name; this name will also be the name of the top-level element. Because this is an article in the Bricolage website, this is simple: it's Article.

With that out of the way, it's time to pick out the field subelements of the document element. As fields are meaningful blocks of text, this is generally simple to do: they're headlines, paragraphs, subheads, and the like. Figure 2 indeed shows meaningful blocks of text:

Headline
This is the title of the article. In this case, it's "David Wheeler Interviewed on Online Tonight."
Dateline
This is the date for the article, here "2002.12.18."
Paragraph
These blocks of text make up the bulk of the content of the article. The first paragraph starts with "Bricolage maintainer and lead developer David Wheeler appeared on the Online Tonight with David Lawrence radio show."
Header
Section headers break up the content into sections, such as "How it All Started" and "Bricolage vs. Blogging Tools."

Identifying Subelements

Related Image
Figure 3. The components making up a "Related Image" element

Pretty simple, right? Well the interesting part comes when you identify the container subelements of the layout. The way to do so is to look for areas where content has been logically grouped together as a unit; for instance, to combine an image and its caption. Such is exactly the case with the picture of David Lawrence. Call this container element a "Related Image," because it creates a link to an image document that's related to the current article content. Figure 3 illustrates the breakdown of the newly identified element: It has a link to a related media document and it has an "Alt Text" field and a "Caption" field.

Related Audio
Figure 4. The pieces of the "Related Audio" element include a tooltip (not shown)

Another clearly grouped collection of content is the box entitled "David Wheeler Takes to the Airwaves." This one has a link to an audio document and a description of its contents. It also has a speaker icon, but this isn't really content; it's more a hint to the viewer as to what she's linking to. That is, it's not content, but presentation. It adds no semantic meaning to the article. To parallel the "Related Image" subelement, call this one "Related Audio." Figure 4 highlights its subelements.

Pull Quote
Figure 5. The "Pull Quote" element has a Paragraph field and an attribution field

Finally, there's one last subelement, the "Pull Quote." It simply groups together a quotation paragraph and an attribution. See Figure 5.

A Note on Layout

Ideally, your analysis is now complete, as you've successfully identified all of the elements of our Article document type using this layout. There is one more thing to consider, though: the placement of the Related Audio, Related Image, and Pull Quote subelements.

The ideal in content management is to separate content from presentation. You've successfully done so here: you've identified the parts of an article (paragraphs, pull quotes, headers, etc.) without specifying anything about the layout (colors, type faces, spacing, placement, etc.). In looking at this example more closely, note that the related audio box is on the right, the related image is on the left, and the pull quote is on the right again. The question is, when a template formats this document for output, how will it determine what goes on the left and what goes on the right?

You might guess that related audio always goes on the right, related images always on the left, and pull quotes always on the right--but if an article has no related audio or pull quotes and five different related images, the layout would end up looking lopsided. Your editors (those who have the least bit of design sensibility) won't like that.

Another way to handle it is to come up with an algorithm to place these suckers. You might decide, for example, that between the three boxed subelements (Related Audio, Related Image, and Pull Quote), placement might rotate back and forth between the right and the left, or perhaps one on the right, two on the left, two on the right, etc. Of course, the problem with this approach is that, depending on how algorithmic you need it to be, the algorithm might be tricky to write. Plus, your editors still might not like it, and ask you, "Why can't I just tell it to put it on the left?"

Why not? The separation of content from presentation is the ideal, yes, but it's not always practical. Plus, sometimes, you can give people the sort of control they need without unduly sacrificing the separation. In this case, solve the problem simply by adding an extra field to each of the subelements: "Position," with two possible values, "right" and "left." Now, when an editor adds a pull quote to a document, she can specify the placement in such a way that the template can easily place it. As for the separation of content from presentation: this information is more metadata about the element than content, and you can ignore it when you need to create purely semantic representations (such as XML).

The upshot is that during a document analysis, you must pay careful attention to the semantically meaningful parts of a document and model the appropriate elements, and then weigh the importance of providing additional, non-semantic presentation metadata fields to give editors the control they need to get their jobs done. Go for the middle road and keep everyone happy.

Table 1
Table 1. The completed document model. In Bricolage, all subelements are optional, meaning that users can add any number of Related Image, Related Audio, and Pull Quote elements to a document.

The Document Model

Having performed your document analysis (and of course, in practice, you should examine several different pages on a site to ensure that you don't miss any elements that are present in some articles but not others), you can now synthesize the breakdown of the elements into a comprehensive model. In practice, that's pretty simple. See Table 1 for the complete representation. Based on the analysis, you've decided on the types of the fields (text, textarea, pulldown, etc.), whether they're a required part of the element they're in, and whether they're repeatable. For example, the headline of the article is required, but not repeatable, because all articles must have a headline but not more than one headline. Paragraphs, on the other hand, are not required, but there can be more than one. You can also say that paragraphs are required and repeatable, in which case at least one would need to be in the element. When doing your own document analysis, take the path that makes the most sense to you.

Container elements, on the other hand, currently have no such occurrence specification. You can add any number of any of the defined subelements to a document, including none. We may add occurrence specification for container elements in a future version of Bricolage.

This example is pretty simple. However, the Article document type for the Bricolage website is quite a bit more complex. See the complete model of the Article document type. In fact, the Bricolage site contains a graphical representation of all of the Bricolage website document models. (I used the special Bricolage introspection template to generate the tables displaying the models.)

Element Administration

Navigate to the Element Manager
Figure 6. Navigate to the Element Type Manager and the Element Manager to administer elements

The next step is to model, in Bricolage, the elements you've discovered. There are two administrative tools to use to create the elements: The Element Type Manager and the Element Manager. To access these managers, use the Bricolage side menus (see Figure 6) to navigate to ADMIN -> PUBLISHING -> and select the appropriate manager. You will see a search interface to search for and edit element or element-type objects. Clicking the Edit link for an element type, for example, brings up the Element Type Profile, where you can edit the attributes of the element type.

Element Types

Each element in Bricolage has an associated element type that defines a few attributes that its associated element share. There are three different kinds of element types: story element types, which define the structure of story documents; media element types, which define the structure of media documents; and subelement element types, which define the structure of container subelements. See Figure 7 for a snippet of the element type interface. The attributes you can set on element types are:

Stories Element Type
Figure 7. A segment of an element type profile called "Story"

Page
Indicates whether an element is a page. Sometimes documents need to have multiple pages through which readers can navigate. You may recognize multi-page documents from Perl.com, where you navigate from page to page via Next Page and Previous Page links.
Fixed
A fixed document is typically one that does not have the date as a part of its URI. Output channels defined in Bricolage specify two URI formats for the documents published to them. One of those formats is the "fixed URI format." If a story based on an element has the "Fixed" checkbox checked in its element type, the story uses the "fixed URI format." Otherwise, it'll use the non-fixed URI format. (The next article will cover output channels in detail.)
Related Story
Elements associated with an element type with the Related Story checkbox checked can have an associated story document. This is useful for creating lists of stories related to the current story, for example.
Related Media
The Related Media checkbox is just like the Related Story checkbox, except that it allows the creation of a relationship to a media document rather than to a story document. The Related Image and Related Audio elements depend on this checkbox being checked.
Media Type
This applies only to media element types--that is, elements that define the structure of media documents. This attribute is a pulldown list, featuring the options Image, Video, Audio, or Other. Of these options, only Image has any effect; image elements automatically have fields associated with them for height, width, color depth, and a few others. Bricolage automatically populates these fields when you upload an image file.

Given how element types work, the Article document type requires the following element types:

Audio
There are some media documents to manage. The first is Audio, required to manage the audio recordings associated with the Related Audio subelement. The Audio element type is a media element type with its media type set to Audio, surprisingly enough.
Image
The Image element type sets attributes for image document types. It is just like the Audio element type, except that it has its media type set to Image instead of Audio. Go figure.
Story
This defines the shared attributes of story elements, of which the Article element is one. It does not use a fixed URL (so that the article's cover date shows up in the URI), and has neither a related story nor related media. The Page attribute is irrelevant to this element type.
Subelement
This simple element type defines generic subelements. There are no special attributes of these subelements, so don't check any. The Pull Quote subelement is an example: it's not a page, and it doesn't have an associated media document or story document.
Related Media
This subelement element type defines elements that have media files associated with them. It has its Related Media checkbox checked, and will be used to define Related Audio and Related Image elements.

Creating the Related Image element
Figure 8. Creating the Related Image element

Elements

Now it's time for the building blocks. You defined several elements in your analysis of the Bricolage.cc page. Now you need to model them in Bricolage, basing each on an element type. When administering elements, you typically first create those farthest down the hierarchy, because you'll need to have them available to associate when you create those farther up. Start with the Pull Quote, Related Image, and Related Audio elements, and then create the Article story type element.

First create the Related Image element (Figure 8). The Key Name field uniquely identifies the element throughout Bricolage, while the burner determines the kind of templates that will be used to format Related Image elements. Be sure to select the Related Media element type! This is useful for many things, including giving templates the ability to easily get a handle on an element, as well as providing the file name for templates based on this element. It's generally a good idea to make the key name the same as the element name, but lowercased and with spaces replaced with underscores (uppercase letters and spaces are not allowed in key names).

The Burner pulldown list allows selection of the burner that will be used to format the element. As mentioned in the Bricolage introduction article, Bricolage currently supports three templating systems for formatting content: Mason, Template Toolkit, and HTML::Template (but with PHP support arriving in the forthcoming Bricolage 1.10, due out in September; stay tuned!). The Bricolage objects that push stories through the templates in a particular output channel are burners, ostensibly because they "burn" files to disk (but rumor has it that the name really came about when the original developer of the Mason burner wanted to write a burn_one() method). I've selected Mason for this example because all of the Bricolage site templates use Mason.

Element custom field definition
Figure 9. The fields subelements defined for the Related Image element

Click the Next button to create the Related Image element. Now you need to add any necessary subelements and fields. Look back at the model in Table 1: the Related Image element has no subelements, but it does have a few fields. Figure 9 pulls out the relevant part of the element profile. (Click an Edit link to edit an individual field element definition, or use the Add New Field section to add new fields to the element.) Note that you've already added the necessary fields. Should you decide that they should display in a different order, simply change the values of the numbered fields (changing one will cause the others to renumber automatically). You can also edit or delete each one to change its values.

The Add New Field section allows you to define new fields for the element, selecting from several available types (text, textarea, pulldown, date, etc.) and filling in the appropriate fields. Each option features different fields to fill in, but the important ones are:

Key Name
This field uniquely identifies the field among all the fields in the container element. While no field in a single element can have the same key name, a field in one element can have the same key name as a field in another element. Like container element key names, they must be lowercase and have no spaces. Again, I tend to use a key name that is the same as the name except for those two rules.
Label
This is the name of the field. A field can have the same name as another field, but that would be silly, now wouldn't it?
Default Value
The default value for a field when adding a new one to an element. For example, a URL field might have a default value of "http://" as a sort of reminder to editors as to what belongs in the field. It might also make sense to use the most common value for the default (such as with the Position field, where the default is "left").
Size
The size of the field in the display. Text fields use this value to set the width of the field (in characters), while Select fields use it to set the number of values to be displayed in a scrollable select field. The textarea field has Rows and Columns fields, instead, to indicate the size at which to display the field in the interface.
Maximum Size
The maximum size of the allowed value. This option limits the length of a text or textarea value to a certain number of letters.
Required
Indicates whether, when adding a new element, the field will be included in the element or the editor needs to add by the editor. The "required" moniker is unfortunate in this case, because it does not mean that the field is required to have a value, only that it's required to be present in the user interface.
Repeatable
Indicates whether or not a field is repeatable. For example, a paragraph field would be repeatable, because we want an editor to be able to add more than one paragraph to a story.
Position
Indicates the order in which the field appears in the user interface. This value affects only required fields, and causes all required fields to be displayed in the order indicated. This does not prevent an editor from reordering fields, however.
Options
This field allows you to specify a range of values for a field. It applies only to Radio Button, Pulldown, and Select fields. Each value goes on a single line, with the option first and then an optional label, separated by a comma.
Allow Multiple
This checkbox is specific to the Select field. When checked, users can select more than one value.

You may edit each of these fields, with the exception of the key name, by clicking its Edit link.

Following the model (Table 1), creating the necessary fields for the Related Audio element becomes simple. The Title, Link Text, and Tooltip fields are simple non-repeating text fields; the Description is a non-repeating textarea field; and the Position field is a non-repeating pulldown field. It's fine to accept the default values for the other fields (size, columns, rows, max size, etc.). For the Position field, however, add two values to the Options field:

left,Left
right,Right

Also set the default value. I've opted for "left" (and, indeed, that's what shows up as the default value for the field in Figure 9), but you could also set it to "right."

Creating the Related Audio and Pull Quote elements is much the same. The only difference is in the fields included and in the selection of an element type (Related Audio is a Related Media type, like Related Image, but Pull Quote is a Subelement).

Now, the Article element--the element that determines the structure of all articles, and therefore defines the Article document type--is a bit different. Defining its fields is of course the same, but you actually have modeled more fields than you need. You see, all documents in Bricolage have a Title field and a Cover Date. You can use these in place of custom Headline and Dateline fields; therefore, you don't have to define them. What's left, Paragraph and Header, define in the same way as fields in all elements.

Subelements of Article
Figure 10. The subelements of the Article element. Click the Add Subelement button to search for other elements and add them to Article

Defining subelements is almost the same, although the other elements had no subelements of their own. Figure 10 shows the Existing Subelements section of the element profile. In this example, the Related Audio and Related Image subelements have already been associated with the Article element, but you still need to add the Pull Quote element. Click the Add Subelement button to bring up a page very similar to the element manager: there's a search box, and you can view the results of your search. Unlike the element manager, however, this interface displays only subelements (that is, those elements that are associated with subelement element types, not story or media element types). Type in and search on the string "pull", and then when you get the search results, select the Add to Element checkbox, and click the Add Elements button. Figure 11 illustrates this process.

Adding the Pull Quote subelement
Figure 11. Use the search field to find the element you want, check the "Add to Element" checkbox, and click the Add Elements button.

Now the Article document type is complete! Well, almost. I've left out that there are other fields to fill in for story type and media type elements: sites and output channels. Because Bricolage is designed to manage multiple sites, each story or media type element must be associated with one or more sites. Each site associated with the element can create documents based on the element. By default, Bricolage has only one Site, Default, so you can just leave it associated with the Article element (if your installation has only one site, then that one site will be associated with all new story and media type elements).

As for output channels, each site has a primary output channel associated with the element. You can add extra output channels as needed. Bricolage has a single output channel by default, called Web, and you can leave this as the selected value. Again, the next article will cover output channels in more depth.

Now the Article document type is complete, and you're ready to start creating documents based on it.

Categories

There is one final topic to discuss before you can create a story document based on the Article document type: categories. Categories are hierarchically organized content locations. Think of them as similar to a file system of directories (or folders, for you Mac fans out there). Indeed, categories in Bricolage are used in the URIs of story and media documents, so the translation is close (modulo date parts and slugs). They are also used to categorize content, so it makes sense to create category hierarchies that make sense for your site.

Creating a new category
Figure 12. Create new categories starting with top-level categories added as subcategories to the root category, "/"

For example, if your site were a newspaper featuring movie and book reviews, you might want to create a category named Reviews as a subcategory of the root category ("/"), and then subcategories of the Reviews category named Books and Movies. The full URIs for these categories would be /reviews, /reviews/books, and /reviews/movies, respectively.

To create categories for your site, navigate to the Categories manager under ADMIN -> PUBLISHING and click "Add a New Category" for each category you wish to add. The root category already exists for each site, so start by creating your top-level categories and make your way down. See Figure 12 for an example creating a top-level Reviews category.

Navigating to the Story workflow
Figure 13. Navigate to the Story workflow and click New Story to create and edit a new story

Document Editing

Having created the categories you need, you can create a story document based on the Article story type. Open the Story workflow in the side navigation and select New Story (Figure 13) to bring up the New Story screen shown in Figure 14. Enter a title and slug. A slug is a very short hint (one or two words, separated by an underscore or dash) as to the contents of the story. Generally, the slug should complement the category selection rather than repeat it. For example, if you were creating a review of The Princess Bride in your /reviews/books category, you might make the slug "princess_bride." Select the Article story type, and cover date and click the Create button. This action creates a new story in the Story workflow and puts it on that workflow's "start desk," in this case the Edit desk.

Creating a new story
Figure 14. Create a new story based on the Article story type. Be sure to enter a meaningful slug and cover date, and put the article in the relevant category

Editing story content
Figure 15. Add fields and elements to the story in the Content section of the story profile

The new story will then open in the story profile. The first section of the profile, Properties, mostly covers the same data points as the New Story screen. It's the second section, Content, that is most interesting (Figure 15). The bottom of this section has an Add Element button and a select list. From this list, you can select the fields or container subelements you'd like to add to the story. After adding a field, you can edit it, as with the Teaser and Long Teaser fields in Figure 15.

Notice that the names of the elements available in the Add Element select list is identical to the names of the container elements and fields associated with your modeled Article story type element. Because these are the only content containers that editors can add to a story, the structural integrity of the story strictly adheres to the model you created. The advantage is that the structure of the stories to be published to the site will be consistent across stories. This approach enables the maintenance of document standards and styles across a site, which can be very important in a large organization with thousands of pages of content to maintain. It also allows for simple yet extremely flexible templating, so that you can output documents in a variety of formats (XHTML, RSS, PDF, etc.) while maintaining identity and design standards.

Why are container elements and fields all listed together in one select list? Because as an editor works on a story, she doesn't need to think in great detail about what to add. It's unnatural for a writer to have to stop and think, "Do I want to add a container element or a field?" as she decides from which list to select. She only has to think, "I need to add a paragraph" or "I think a pull quote would work well here." The difference is important to us a document modelers and, later, as template developers, but for editors, the distinction is irrelevant.

Editing the Related Image subelement
Figure 16. Edit the fields of the Related Image subelement. Click the Edit button in the Related Media section to search for an image document to relate to this element

If you elect to add a subelement, say by selecting the Related Image option, you will then see the profile for that element. Figure 16 illustrates the Related Image element profile. Note how the required fields for this element (Title, Alt Text, Position, and Caption) are in the profile. Furthermore, because this is a related media element, there's an extra section called, curiously enough, Related Media. By clicking the Edit button, you can search your existing media documents to create a relationship, much as you searched for subelements to add to an element in the element manager. In Bricolage 1.10.0, you can also upload a media file directly in this interface, implicitly creating a media document in the same category and with the same cover date as the current story.

Click the Save button to save the element. Back in the main story profile, the new Related Image subelement will appear in the Content Section. You can re-order elements and fields by selecting new values from the Position select lists adjacent to each element. To edit a field, simply do so directly. To edit the Related Media subelement again, click its Edit button and you'll return to its profile.

Before finishing with this story, there's one more editing interface that's important to know. Adding individual fields to a story so as to keep their contents in independent records is all well and good, but the first time you tell an editor that she has to add each paragraph one at a time, duck. Just as an editor doesn't want to think about whether to add a container element or field to a story, she also doesn't want to have to go to a select list, click a button, and wait for a browser reload every time she wants to add a single paragraph.

Super Bulk Edit
Figure 17. Super Bulk Edit allows editors to use POD-like syntax to edit all of the fields in an element in a single textarea box

For this reason, Bricolage features two other interfaces for editing. The first, Bulk Edit, allows editors to edit all instances of a repeatable field in a single textarea. This is convenient if the majority of the content of a story is paragraphs, for example. The editor can simply write all the paragraphs she wants in a textarea box, separating each with a blank line. To use it, simply select the type of repeatable field to edit and click the Bulk Edit button to the right of the Add Element button and list.

Most documents are not simple lists of paragraphs though (if they were, you wouldn't need a tool as sophisticated as Bricolage). Instead, they're a mix of different blocks of content; the so-called Super Bulk Edit feature will likely prove to be more useful. To use it, select Super Bulk Edit from the Bulk Edit select list and then click the Bulk Edit button. Doing so will bring up the screen depicted in Figure 17. Here, you can edit all of the fields and subelements of the current element in a single textarea field. Each individual field or subelement has POD-like tags that start with an equal sign (=) and end with the key name of the element or field. The tags for subelements each exist on a line by themselves, while field tags identify the field to which the contents below them belong. You can even select a default element (say, Paragraph) from the Default Element select list, and then any blocks of text that have no POD tag will default to that element. This approach makes it easy for writers to edit all the different parts of an element without having to select, reload, and wait for every one. They can also create and reorder subelements, although they cannot change the contents of subelements themselves.

Click Save to return to the story profile, and the Save button there to save the story and return to My Workspace. Now you have created a story based on the article model. Go ahead, create as many more as you like.

Up Next: Templating

The next article will show how to write your first templates in Bricolage to format stories for output, so that you can actually see how your documents will look when they're published. Stay tuned!

Building Navigation Menus


Navigation menus are a group of links given at one side of the page that allows users to navigate to different places of a website. Navigation menus allow site visitors to explore other pages of the site and to find what they want more easily. For example, Paul Graham's home page contains a simple navigation menu made out of images. It doesn't change as the site visitor move to different pages of the site. The KDE desktop environment home page contains a more sophisticated menu. Click on the link to the screenshots to see a submenu for links to screenshots from multiple versions of KDE. Other menu items have similar expansions.

Common Patterns in Navigation Menus and Site Flow

There are several patterns in maintaining navigation menus and general site flow.

A Tree of Items

Usually, the items in the navigation menus are a tree structure (as is the case for the KDE site) or a flat list. Sometimes, branches of the tree can expand or collapse depending on the current page. This prevents having to display the entire tree at once.

Next/Previous/Up Links to Traverse a Site

Many sites provide links to traverse the pages of the site in order: a Next link to go to the next page, a Previous link to go to the previous page, an Up link to go to the section containing the current page, a Contents link to go to the main page, and so on.

HTML can represent these links by using <head> tag and <link rel="next" href="" />. directives. Mozilla, Firefox, and Opera all support these tags. They can be also visible in the HTML as normal links, as is the case with GNU Documentation.

Site Maps and Breadcrumb Trails

Other navigation aids provided by sites include a site map (like the one on Eric S. Raymond's home page) and a breadcrumb trail. A breadcrumb trail is a path of the components of the navigation menu that leads to the current page. The documentation for Module::Build::Cookbook on search.cpan.org provides an example ("Ken Williams > Module-Build > Module::Build::Cookbook," in this case).

Hidden Pages and Skipped Pages

Hidden pages are part of the site flow (the next/previous scheme) but don't appear in the navigation menu. Skipped pages are the opposite: they appear in the navigation menu, but are not part of the site flow.

Introducing HTML::Widgets::NavMenu

How do you create menus? HTML::Widgets::NavMenu is a CPAN module for maintaining navigation menus and site flow in general. It supports all of the above-mentioned patterns and some others, has a comprehensive test suite, and is under active maintenance. I have successfully used this module to maintain the site flow logic for such sites as my personal home page and the Perl Beginners' Site. Other people use it for their own sites.

This makes it easy to generate and maintain such navigation menus in Perl. It is generic enough so that it can generate static HTML or dynamic HTML on the fly for use within server-side scripts (CGI, mod_perl, etc.).

To install it, use a CPAN front end by issuing a command such as perl -MCPANPLUS -e "install HTML::Widgets::NavMenu" or perl -MCPAN -e "install HTML::Widgets::NavMenu".

A Simple Example

Here's a simple example: a navigation tree that contains a home page and two other pages.

You can see the complete code for this example:

#!/usr/bin/perl

use strict;
use warnings;

use HTML::Widgets::NavMenu;
use File::Path;

This is the standard way to begin a Perl script. It imports the module and the File::Path module, both of which it uses later.

my $css_style = <<"EOF";
a:hover { background-color : palegreen; }
.body {
.
.
.
EOF

This code defines a CSS stylesheet to make things nicer visually.

my $nav_menu_tree =
{
    'host'  => "default",
    'text'  => "Top 1",
    'title' => "T1 Title",
    'subs'  =>
    [
        {
            'text' => "Home",
            'url'  => "",
        },
        {
            'text'  => "About Me",
            'title' => "About Myself",
            'url'   => "me/",
        },
        {
            'text'  => "Links",
            'title' => "Hyperlinks to other Pages",
            'url'   => "links/",
        },
    ],
};

Now this is important. This is the tree that describes the navigation menu. It is a standard nested Perl 5 data structure, with well-specified keys. These keys are:

  • host: A specification of the host on which the sub-tree starting from that node resides. HTML::Widgets::NavMenu menus can span several hosts on several domains. In this case, the menu uses just one host, so default here is fine.
  • text: What to place inside of the <a>...</a> tag (or alternatively, the <b> tag, if it's the current page).
  • title: Text to place as a title attribute to a hyperlink (usually displayed as a tooltip). It can display more detailed information, helping to keep the link text itself short.
  • url: The path within the host where this item resides. Note that all URLs are relative to the top of the host, not the URL of their supernode. If the supernode has a path of software/ and you wish the subnode to have a path of software/gimp/, specify url => 'software/gimp/'.
  • subs: An array reference that contains the node's sub-items. Normally, this will render them in a submenu.

One final note: HTML::Widgets::NavMenu does not render the top item. The rendering starts from its sub-items.

my %hosts =
(
    'hosts' =>
    {
        'default' =>
        {
            'base_url' => ("http://web-cpan.berlios.de/modules/" .
                "HTML-Widgets-NavMenu/article/examples/simple/dest/"),
        },
    },
);

This is the hosts map, which holds the hosts for the site. Here there is only one host, called default.

my @pages =
(
    {
        'path'    => "",
        'title'   => "John Doe's Homepage",
        'content' => <<'EOF',
<p>
Hi! This is the homepage of John Doe. I hope you enjoy your stay here.
</p>
EOF
    },
    .
    .
    .
);

The purpose of this array is to enumerate the pages, giving each one the <title> tag, the <h1> title, and the content that it contains. It's not part of HTML::Widgets::NavMenu, but rather something that this script uses to render meaningful pages.

foreach my $page (@pages)
{
    my $path     = $page->{'path'};
    my $title    = $page->{'title'};
    my $content  = $page->{'content'};
    my $nav_menu =

        HTML::Widgets::NavMenu->new(
            path_info     => "/$path",
            current_host  => "default",
            hosts         => \%hosts,
            tree_contents => $nav_menu_tree,
        );

    my $nav_menu_results = $nav_menu->render();
    my $nav_menu_text    = join("\n", @{$nav_menu_results->{'html'}});
    
    my $file_path = $path;
    if (($file_path =~ m{/$}) || ($file_path eq ""))
    {
        $file_path .= "index.html";
    }
    my $full_path = "dest/$file_path";
    $full_path =~ m{^(.*)/[^/]+$};

	# mkpath() throws an exception if it isn't successful, which will cause
	# this program to terminate.  This is what we want.
    mkpath($1, 0, 0755);
    open my $out, ">", $full_path or
        die "Could not open \"$full_path\" for writing: $!\n";
    
    print {$out} <<"EOF";
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html
     PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>$title</title>
<style type="text/css">
$css_style
</style>
</head>
<body>
<div class="navbar">
$nav_menu_text
</div>
<div class="body">
<h1>$title</h1>
$content
</div>
</body>
</html>
EOF

    close($out);
}

This loop iterates over all the pages and renders each one in turn. If the directory up to the file does not exist, the program creates it by using the mkpath() function. The most important lines are:

    my $nav_menu =
        HTML::Widgets::NavMenu->new(
            path_info     => "/$path",
            current_host  => "default",
            hosts         => \%hosts,
            tree_contents => $nav_menu_tree,
        );

    my $nav_menu_results = $nav_menu->render();
    my $nav_menu_text    = join("\n", @{$nav_menu_results->{'html'}});

This code initializes a new navigation menu, giving it four named parameters. path_info is the path within the host. Note that, as opposed to the paths in the navigation menu, it starts with a slash. This is to allow some CGI-related redirections. current_host is the current host (again, it's default). Finally, hosts and tree_contents point to hosts and the tree of contents, respectively.

The object render() method returns the results in a hash reference, with the navigation menu results as an array of tags pointed by the html key. The code finally joins and returns them.

The program produces this result, with three entries, placed in a <ul>. When a user visits a page, the corresponding menu entry displays in bold and has its link removed.

A More Complex Example

Now consider a more complex example. This time, the tree is considerably larger and contains nested items. There are now subs of other pages.

The final site has a menu. When accessing a page (for example, the "About Myself" page) its expands so visitors can see its sub-items.

Adding More Navigation Aids

The next step is to add a breadcrumb trail, navigation links, and a site map to the site. You can inspect the new code to see if you understand it and view the final site.

The breadcrumb trail appears right at the top of the site. Below it is a toolbar with navigation links like "next," "previous," and "up." Finally, there's a site map. Here are the salient points of the code's modifications:

  1. The code loads the Template Toolkit to render the page, then fills in the variables of the template to define the template itself and to process it into the output file.
  2. The CSS stylesheet has several new styles, to make the modified page look nicer.
  3. A template portion to transform a breadcrumb-trail object as returned by HTML::Widgets::NavMenu into HTML. It should be easy to understand.
  4. The bottom of the navigation menu tree now has an entry with a link to the site map page.
  5. The site map is now part of the @pages array. It initializes an HTML::Widgets::NavMenu with the appropriate URL, and then uses its gen_site_map() function.
  6. There is new code used to generate the navigation links. These links are a hash reference with the keys being the relevance of the link and the value being an object supplying information about the link (such as direct_url() or title()). There are two loops that renders each link into both the HTML <head> tag <link> elements, or the toolbar to present on the page.
  7. The text of the breadcrumb trail is a join of their HTML representations.
  8. The generated HTML template includes the new page elements.

Fine-Grained Site Flow

The final example modifies the site to have a more sophisticated site flow. Looking at the changes shows several more additions. Their implications are:

  1. Both English resumés have a 'skip' => 1, pair. This caused these pages to appear in the navigation menu, but not to be part of the traversal flow. Clicking "next" at that page will skip them both. Pressing "prev" at the page that follows them leads to the page that precedes them.
  2. The Humour section has its 'show_always' attribute set, causing it to expand on all pages of the site.
  3. 'expand' is a regular expression for the Software section. As a result, accessing a page not specified in the navigation menu but that matches that regular expression causes the Software section to expand there.
  4. The software tools page entry has the attribute 'hide' => 1. This removes it from the navigation menu but allows it to appear in the site flow. Clicking on "next" on the preceding page will reach it.

A CGI Script

Until now, the examples have demonstrated generating a set of static HTML pages. The code can also run dynamically on a server. One approach is to use the ubiquitous CGI.pm, which comes bundled with Perl.

Converting to the CGI script required few changes. Inside of the page loop, the code checks if the page matches the CGI path info (the path appended after the CGI script name). If so, the code calls the render_page() function.

render_page() is similar to the rest of the loop except that it prints the output to STDOUT after the CGI header. Finally, after the loop ends, the code checks that it has found a page. If not, it displays an error page.

Note that the way this script looks for a suitable page is suboptimal. A better-engineered script might keep the page paths in a persistent hash or other data structure from which to look up the path info.

Conclusion

This article demonstrated how to use HTML::Widgets::NavMenu to maintain navigation menus and organize site flow. Reading its documentation may reveal other useful features. Now you no longer have an excuse for lacking the niceties demonstrated here on your site. Happy hacking!

Acknowledgments

Thanks to Diego Iastrubni, Aankehn, and chromatic (my editor) for giving some useful commentary on early drafts of this document.

Catalyst

Web frameworks are an area of significant interest at the moment. Now that we've all learned the basics of web programming, we're ready to get the common stuff out of the way to concentrate on the task at hand; no one wants to spend time rewriting the same bits of glue to handle parameter processing, request dispatching, and the like.

A model currently favored for web applications is MVC, or Model-View-Controller. This design pattern, originally from Smalltalk, supports the separation of the three main areas of an application--handling application flow (Controller), processing information (Model), and outputting results (View)--so that it is possible to change or replace any one without affecting the others.

Catalyst is a new MVC framework for Perl. It is currently under rapid development, but the core API is now stable, and a growing number of projects use it. Catalyst borrows from other frameworks, such as Ruby on Rails and Apache Struts, but its main goal is to be a flexible, powerful, and fast framework for developing any type of web project in Perl. This article, the first of a series of two, introduces Catalyst and shows a simple application; a later article will demonstrate how to write a more complex project.

Inspirations

Catalyst grew out of Maypole, an MVC framework developed by Simon Cozens (and discussed last year on Perl.com; see "Rapid Web Application Development with Maypole," for example). Maypole works well for typical CRUD (Create, Retrieve, Update, Delete) databases on the Web. It includes a variety of useful methods and prewritten templates and template macros that make it very easy to set up a powerful web database. However, it focuses so strongly on CRUD that it is less flexible for other tasks. One of the goals of Catalyst is to provide a framework well suited for any web-related project.

Ruby on Rails was another inspiration; this popular system has done much to promote interest in the Ruby programming language. Features we borrowed from RoR are the use of helper scripts to generate application components and the ability to have multiple controllers in a single application. Both RoR and Struts allow the use of forwarding within applications, which also proved useful for Catalyst.

Features

Speed

We planned Catalyst as an enterprise-level framework, able to handle a significant load. It makes heavy use of caching. Catalyst applications register their actions in the dispatcher at compile time, making it possible to process runtime requests quickly, without needing elaborate checks. Regex dispatches are all precompiled. Catalyst builds only the structures it needs, so there are no delays to generate (for example) unused database relations.

Simplicity

Components

Catalyst has many prebuilt components and plugins for common modules and tasks. For example, there are View classes available for Template Toolkit, HTML::Template, Mason, Petal, and PSP. Plugins are available for dozens of applications and functions, including Data::FormValidator, authentication based on LDAP or Class::DBI, several caching modules, HTML::FillInForm, and XML-RPC.

Catalyst supports component auto-discovery; if you put a component in the correct place, Catalyst will find and load it automagically. Just place a Catalog controller in /AppName/Controller/Catalog.pm (or, in practice, in the shortened /AppName/C/Catalog.pm); there's no need to use each item. You can also declare plugins in the application class with short names, so that:

use Catalyst qw/Email Prototype Textile/;

will load Catalyst::Plugin::Email, Catalyst::Plugin::Prototype, and Catalyst::Plugin::Textile in one shot.

Development

Catalyst comes with a built-in lightweight HTTP server for development purposes. This runs on any platform; you can quickly restart it to reload any changes. This server functions similarly to production-level servers, so you can use it throughout the testing process--or longer; it's a great choice if you want to deliver a self-contained desktop application. Scalability is simple, though: when you want to move on, it is trivial to switch the engine to use plain CGI, mod_perl1, mod_perl2, FastCGI, or even the Zeus web server.

Debugging (Figure 1) and logging (Figure 2) support is also built-in. With debugging enabled, Catalyst sends very detailed reports to the error log, including summaries of the loaded components, fine-grained timing of each action and request, argument listings for requests, and more. Logging works by using the the Catalyst::Log class; you can log any action for debugging or information purposes by adding lines like:

$c->log->info("We made it past the for loop");
$c->log->debug( $sql_query );

Log screenshot
Figure 1. Logging

Crashes will display a flashy debug screen showing details of relevant data structures, software and OS versions, and the line numbers of errors.

Debug screenshot
Figure 2. Debugging

Helper scripts, generated with Template Toolkit, are available for the main application and most components. These allow you to quickly generate starter code (including basic unit tests) for the application framework. With a single line, you can create a Model class based on Class::DBI that pulls in the appropriate Catalyst base model class, sets up the pattern for the CDBI configuration hash, and generates a perldoc skeleton.

Flexibility
Catalyst allows you to use multiple models, views, and controllers--not just as an option when setting up an application, but as a totally flexible part of an application's flow. You can mix and match different elements within the same application or even within the same method. Want to use Class::DBI for your database storage and LDAP for authentication? You can have two models. Want to use Template Toolkit for web display and PDF::Template for print output? No problem. Catalyst uses a simple building-block approach to its add-ins: if you want to use a component, you say so, and if you don't say so, Catalyst won't use it. With so many components and plugins available, based on CPAN modules, it's easy to use what you want, but you don't have to use something you don't need. Catalyst features advanced URL-to-action dispatching. There are multiple ways to map a URL to an action (that is, a Catalyst method), depending on your requirements. First, there is literal dispatching, which will match a specific path:
package MyApp::C::Quux;

# matches only http://localhost:3000/foo/bar/yada
sub baz : Path('foo/bar/yada') { }

A top-level, or global, dispatch matches the method name directly at the application base:

package MyApp::C::Foo;

# matches only http://localhost:3000/bar
sub bar : Global { }

A local, or namespace-prefixed, dispatch acts only in the namespace derived from the name of your Controller class:

package MyApp::C::Catalog::Product;

# matches http://localhost:3000/catalog/product/buy
sub buy : Local { }

package MyApp::C::Catalog::Order;

# matches http://localhost:3000/catalog/order/review
sub review : Local { }

The most flexible is a regex dispatch, which acts on a URL that matches the pattern in the key. If you use capturing parentheses, the matched values are available in the $c->request->snippets array.

package MyApp::C::Catalog;

# will match http://localhost:3000/item23/order189
sub bar : Regex('^item(\d+)/order(\d+)$') { 
   my ( $self, $c ) = @_;
   my $item_number  = $c->request->snippets->[0];
   my $order_number = $c->request->snippets->[1];
   # ...    
}

The regex will act globally; if you want it to act only on a namespace, use the name of the namespace in the body of the regex:

sub foo : Regex('^catalog/item(\d+)$') { # ...

Finally, you can have private methods, which are never available through URLs. You can only reach them from within the application, with a namespace-prefixed path:

package MyApp::C::Foo;
# matches nothing, and is only available via $c->forward('/foo/bar').
sub bar : Private { }

A single Context object ($context, or more usually as its alias $c) is available throughout the application, and is the primary way of interacting with other elements. Through this object, you can access the request object ($c->request->params will return or set parameters, $c->request->cookies will return or set cookies), share data among components, and control the flow of your application. A response object contains response-specific information ($c->response->status(404)) and the Catalyst::Log class is made directly available, as shown above. The stash is a universal hash for sharing data among application components:

$c->stash->{error_message} = "You must select an entry";

# then, in a TT template:
[% IF error_message %]
   <h3>[% error_message %]</h3>
[% END %]

Stash values go directly into the templates, but the entire context object is also available:

<h1>[% c.config.name %]</h1>

To show a Mason example, if you want to use Catalyst::View::Mason:

% foreach my $k (keys $c->req->params) {
  param: <% $k %>: value: <% $c->req->params->{$k} %>
% }

Sample Application: MiniMojo, an Ajax-Based Wiki in 30 Lines of Written Code

Now that you have a sense of what Catalyst is, it's time to look at what it can do. The example application is MiniMojo, a wiki based on Ajax, which is a JavaScript framework that uses the XMLHttpRequest object to create highly dynamic web pages without needing to send full pages back and forth between the server and client.

Remember that from the Catalyst perspective, Ajax is just a case of sending more text to the browser, except that this text is in the form of client-side JavaScript that talks to the server, rather than a boilerplate copyright notice or a navigation sidebar. It makes no difference to Catalyst.

Installation

Catalyst has a relatively large number of requirements; most, however, are easy to install, along with their dependencies, from CPAN. The following list should take care of everything you need for this project:

Generate the Application Skeleton

Run this command:

$ catalyst.pl MiniMojo
$ cd MiniMojo

You've just created the skeleton for your entire application, complete with a helper script keyed to MiniMojo to generate individual classes, basic test scripts, and more.

Run the built-in server:

$ script/minimojo_server.pl

MiniMojo is already running, though it isn't doing much just yet. (You should have received a web page consisting solely of the text "Congratulations, MiniMojo is on Catalyst!") Press Ctrl-C to stop the server.

Add Basic Methods to Your Application Class

Add a private end action to your application class, lib/MiniMojo.pm, by editing the new file:

sub end : Private {
    my ( $self, $c ) = @_;
    $c->forward('MiniMojo::V::TT') unless $c->res->output;
}

Catalyst automatically calls the end action at the end of a request cycle. It's one of four built-in Private actions. It's a typical pattern in Catalyst to use end to forward the application to the View component for rendering, though if necessary you could do it yourself (for example, if you want to use different Views in the same application--perhaps one to generate web pages with Template Toolkit and another to generate PDFs with PDF::Template).

Replace the existing, helper-generated default action in the same class with:

sub default : Private {
    my ( $self, $c ) = @_;
    $c->forward('/page/show');
}

In case the client has specified no other appropriate action, this will forward on to the page controller's show method. As Private actions, nothing can call these from outside the application. Any method from within the application can call them. The default action is another built-in Private action, along with begin, auto, and end. Again, Catalyst calls them automatically at relevant points in the request cycle.

Set Up the Model (SQLite Database) and Use the Helper to Create Model Classes

Next, create a file, minimojo.sql, that contains the SQL for setting up your page table in SQLite.

-- minimojo.sql
CREATE TABLE page (
    id INTEGER PRIMARY KEY,
    title TEXT,
    body TEXT
);

Create a database from it, using the sqlite command-line program:

$ sqlite minimojo.db < minimojo.sql

Depending on your setup, it might be necessary to call this as sqlite3.

Use the helper to create model classes and basic unit tests (Figure 3 shows the results):

$ script/minimojo_create.pl model CDBI CDBI dbi:SQLite:/path/to/minimojo.db

Model-creation screenshot
Figure 3. Creating the model

The minimojo_create.pl script is a helper that uses Template Toolkit to automate the creation of particular modules. The previous command creates a model (in contrast to a controller or a view) called CDBI.pm, using the CDBI helper, setting the connection string to dbi:SQLite:/path/to/minimojo.db, the database you just created. (Use the appropriate path for your system.) The helper will write the models into lib/MiniMojo/M/. There are various options for the helper scripts; the only requirement is the type and the name. (You can create your own modules from scratch, without using the helper.)

Set Up the View (Template::Toolkit) and Use the Helper to Create View Classes

Use the helper to create a view class:

$ script/minimojo_create.pl view TT TT

View classes go into lib/MiniMojo/V/.

Set Up a Controller Class Using the Helper

Create a controller class called Page with the helper:

$ script/minimojo_create.pl controller Page

Controller classes live in lib/MiniMojo/C/.

Add a show action to lib/MiniMojo/C/Page.pm:

sub show : Regex('^(\w+)\.html$') {
    my ( $self, $c ) = @_;
    $c->stash->{template} = 'view.tt';
    # $c->forward('page');
}

The Regex dispatch matches a page in foo.html, where foo is any sequence of word characters. This sequence is available in the $context->request->snippets array, where the page action uses it to display an existing page or to create a new one. The rest of this action sets the appropriate template and sends the application to the page action. (Leave the forward command commented out until you have written the page action.)

Restart the server with $ script/minimojo_server.pl and point a web browser to http://localhost:3000/show/ to see the debug screen (you don't yet have the template that show is trying to send people to).

Create root/view.tt:

<html>
    <head><title>MiniMojo</title></head>
    <body>
        <h1>MiniMojo is set up!</h1>
    </body>
</html>

Test again by killing the server with Ctrl-C and restarting it, and go to http://localhost:3000/show/. You should see the page you just defined.

Add the Display and Edit Code

Modify the application class lib/MiniMojo.pm to include the Prototype and Textile plugins:

use Catalyst qw/-Debug Prototype Textile/;

Note that you can use the plugins by specifying their base names; Catalyst figures out what you mean without making you use Catalyst::Plugin::Prototype.

Modify the page controller, lib/MiniMojo/C/Page.pm, to add page-view and editing code:

sub page : Private {
    my ( $self, $c, $title ) = @_;
    $title ||= $c->req->snippets->[0] || 'Frontpage';
    my $query = { title => $title };
    $c->stash->{page} = MiniMojo::M::CDBI::Page->find_or_create($query);
}

The private page method sets a title--whether passed in to it, taken from the snippets array (that matches the regex in show), or defaulting to "Frontpage." The $query variable holds a hashref used for Class::DBI's find_or_create method, seeding the stash for the page variable with the result of this CDBI query. At the end of the method, control flow returns to the calling method.

Now uncomment the $c->forward('page'); line in the show action.

sub edit : Local {
    my ( $self, $c, $title ) = @_;
    $c->forward('page');
    $c->stash->{page}->body( $c->req->params->{body} )
      if $c->req->params->{body};
    my $body = $c->stash->{page}->body || 'Just type something...';
    my $html = $c->textile->process($body);

    my $base = $c->req->base;
    $html    =~ s{(?<![\?\\\/\[])(\b[A-Z][a-z]+[A-Z]\w*)}
                 {<a href="$base$1.html">$1</a>}g;

    $c->res->output($html);
}

The edit method first forwards the action off to page, so that the stash's page object contains the result of the CDBI query. If there is a value for body, it will use this; otherwise "Just type something..." is the default. The code then processes the body with Textile, which converts plain text to HTML, and then runs the body through a regex to convert camel-case text into links, with the URL base taken from the Catalyst request object. Finally, it outputs the HTML.

Set Up the Wiki with Ajax

Modify root/view.tt to include Ajax code:

<html>
     <head><title>MiniMojo</title></head>
     [% c.prototype.define_javascript_functions %]
     [% url = base _ 'page/edit/' _ page.title %]
     <body Onload="new Ajax.Updater( 'view',  '[% url %]' )">
         <h1>[% page.title %]</h1>
         <div id="view"></div>
         <textarea id="editor" rows="24" cols="80">[% page.body %]</textarea>
         [% c.prototype.observe_field( 'editor', {
             url => url,
             with => "'body='+value",
             update => 'view' }
         ) %]
     </body>
</html>

The line:

[% c.prototype.define_javascript_functions %]

includes the whole prototype.js library in a script block. Note that the prototype plugin is available in the context object.

The section

[% url = base _ 'page/edit/' _ page.title %] 
<body Onload="new Ajax.Updater( 'view',  '[% url %]' )">
<h1>[% page.title %]</h1>
<div id="view"></div>

constructs the Ajax URL and updates the view div when loading the page.

Finally:

<textarea id="editor" rows="24" cols="80">[% page.body %]</textarea>
    [% c.prototype.observe_field( 'editor', {
        url => url,
        with => "'body='+value",
        update => 'view' }
    ) %]

periodically checks the textarea for changes and makes an Ajax request on demand.

That's it! Now you can re-run the server and your wiki is up and running (Figure 4). To use the wiki, simply start typing in the textarea. As you type, the wiki will regularly echo your entry above, passing it through the formatter. When you type something in camel case, it will automatically create a link you can click to go to the new page.

screenshot of the running wiki
Figure 4. The running wiki

Enjoy your new Catalyst-powered Ajax wiki!

Resources

For more information, see the Catalyst documentation, in particular the Catalyst::Manual::Intro module, which gives a thorough introduction to the framework. There are two Catalyst mailing lists, a general list and a developer list. The best place to discuss Catalyst, though, is the #catalyst IRC channel at irc.perl.org. The Catalyst home page is currently just a collection of a few links, but we will extend it in the near future.

Thanks to Catalyst lead developer Sebastian Riedel for help with this article and, of course, for Catalyst itself.

Visit the home of the Perl programming language: Perl.org

Sponsored by

Powered by Movable Type 5.02