- 1. Introduction
- 2. InfoGlue basic concepts
- 3. Introducing the components
- 4. Velocity or JSP?
- 5. Preparation: creating some example documents
- 6. Basic components
- 7. Create the website structure
- 8. Navigation controls and iterators
- 9. Using the inheritance
- 10. Final considerations
1. Introduction
I was running a few web sites with PHP and I wanted really get rid of it, also because it gave me some security problems. So I really wanted to switch to a Java™-based CMS. One of the "problems" is that there's plenty of opensource products out there and if you're not expert of the topic it's not easy I did just a very quick competitive evaluation among one of the most mentioned products and at last I selected InfoGlue. I'm absolutely pleased with the results, even if the major pitfall of the product is the fact it lacks documentation (and at the moment I'm writing the wiki pages are not accessible).
In three days of work (relaxed work, since it's Xmas time) I was able to port all my websites (one is the site you're reading this tutorial from, the other is blueMarine's site) from PHP to InfoGlue (at the moment I'm writing I've still to migrate some discussion forums, which is a feature InfoGlue does not address deliberately). I did it by designing my own InfoGlue components from scratch and I thought to share my knowledge with others, in true opensource spirit.
Let me state that this is just unsupported stuff. It works (I'm successfully running three sites with it) but it still needs some clean up. Maybe I'll still work on it in the following weeks, maybe not. I'm available for comments and If I receive some feedback I could turn it in a very small official, supported project. If InfoGlue guys like it, I'd be willing to give my components to add to the InfoGlue distribution.
Please understand that this tutorial is just a first draft. I had not the time to check it carefully.
Actually I'm giving the component code listings embedded in this tutorial, so you could copy and paste them into your repository. But this is not enough, since some XML extra files that define component properties are required. InfoGlue has a "Export" feature that should pack a whole site into a single XML file that can be later used for importing in another InfoGlue instance. Indeed I intended to give you my components in this way. But... I can't make this working (the resulting file contains only a header). If somebody helps me to understand how to have it working I will be grateful, as well as the readers of this tutorial.
*** Update 5-Jan-2006: InfoGlue has released v2.1 and the developers kindly answered a bunch of doubts of mine. Some points of this tutorial that I was guessing about have been solved. I will publish a newer version of the tutorial as soon as possible.
2. InfoGlue basic concepts
InfoGlue website offers just some introductory manual and a small video tutorial. I suggest you to go through that material before going on; for guys in a hurry I'm trying to sum up which are the basic concepts of the product. I'm also supposing that the reader of this tutorial is confident with JSP concepts such as taglibs and EL language.
The most important points to understand in order to get started are the following ones:
- As any good CMS product, InfoGlue takes very seriously the issue of separating website contents from the underlying structure. So, the data model is composed of two distinct trees: a Content Tree and a Structure Tree. The former contains all your documents (HTML pages, images, templates, etc...) and you are free to model it as you want as the public website structure will be not related (at least not strictly related) to it. The latter contains Sitenodes, which are really the visible pages to the Internet.
- Of course there is a relationship between the Content Tree and the Structure Tree. Sitenodes in the Structure Tree points to Contents in the Content Tree to bind to contents.
- This relationship is in most cases one-to-one (each Sitenode is made of a single Content), but generally speaking it is many-to-many: a page can be composed of multiple Contents (e.g. a list of news or a blog) and a Content can be shared by more than one Sitenode. Indeed this last feature is very appreciated since it's the basis for an excellent material reuse.
- Both Contents and Sitenodes are composed of a Cover, which some basic commons properties, and the contained data.
- The Cover contains a Container name, a Publish date and an Expire date. The Container name is just used by the InfoGlue node navigators and has not any relationship with the page title that will be published.
- Contained data can be of different types and I'll talk about it later.
- InfoGlue is backed by a relational database (e.g. MySQL) which provides support for persistence and storage. A single InfoGlue installation can contain an unlimited number of Repositories (i.e. websites). For instance, my single installation contains three websites: the website of my company and those of two opensource projects I'm supporting. It is possible to have cross-references among repositories, that is a repository can refer to Contents contained in other repositories. Contents can be moved from a Repository to another. Other good news for material reuse.
- It is possible to freely move any kind of node among different Repositories.
- Any Repository is composed of two copies: a Working instance and a Live instance. When you make changes, you're working with the Working instance, and others won't see what you're doing. When a set of changes is ready to be thrown to the wolves, you just use a Publish tool to move them to the Live instance. Versioning is supported too.
- InfoGlue has excellent support for internationalisation since any Content or SiteNode can contain multiple versions each bound to a different language.
- Each document is bound to an URL composed of three parts: a siteNodeId, a contentId and a languageId (e.g. http://...../ViewPage.action?siteNodeId=167&languageId=1&contentId=-1). If a SiteNode is only bound to a single Content, the contentId is not present.
The two main content types in InfoGlue are:
- Article: an HTML-based document, composed of two parts, a Leadin text (sort of an introduction) and a FullText. Articles can be managed by means of a WYSISWYG editor which works pretty well.
- HTMLTemplate: a HTML-based document composed of a single part which is edited at a source level and is used to write a template with some dynamic scripting language.
3. Introducing the components
A Component in InfoGlue is a piece of HTML which can include other Components, attaching them in pre-defined places called slots. A slot is declared by inserting a special tag <ig:slot id="id"></ig:slot> (please note that you can't use the shortcut </ig:slot id="id">) and a Component visual editor assist you in adding and removing Components from the slots.
The components I designed are following a different idea than the one described in the available InfoGlue documentation and video tutorial. While InfoGlue examples are made of a "flat" page component, that is a basic pages with slots in which you add just a single level of sub-components, I went with a multi-level structure based on the well known "Composite" pattern. While this is a bit more complex to understand, I think that leads to a better component reuse.
I found that InfoGlue supports this approach, but with some pitfall that I was able to work around (I'll explain the details in the conclusion section of this tutorial). Beware that at this point, due to the lack of documents, I don't know if I was just lucky to have it working; maybe it's an approach that the InfoGlue developers are not interested in and it could not be supported in future releases of the product. Or maybe the pitfall I encountered is just a transient bug that will be fixed and in future everything will be smoother. I hope to get in touch with some InfoGlue developer to clarify these points.
In any case, with a little work you can reuse my components also with a flat structure.
This section contains a quick introduction of the components I designed. A more detailed analysis of each component is given in the next parts of this tutorial.
- Article. Shows an InfoGlue article in a full fashion (both the Leadin and the Fulltext).
- Body. Represents the body of a page, composed by the following slots: left, right, top, bottom, center.
- Breadcrumb. It's a navigation bar which shows the path of the current Sitenode.
- Content Iterator with Leadin. Iterates through the children of a Content showing Titles and Leadins; where a Fulltext is available, it provides a "Read more..." link.
- Dictionary Iterator. Iterates through the children of a Content showing them in alphabetical order and presenting them as the items of a dictionary.
- Footer. A component made of three rows, useful for headers with an (optional) horizontal menu above or below the header itself.
- Header. A component made of three rows, useful for headers with an (optional) horizontal menu above the footer itself.
- Horizontal Menu. A list of links shown as an horizontal menu.
- HTML Fragment. A component for including raw HTML fragments.
- Multiple Articles with Leadin. A components which shows Titles and Leadins of a custom-defined list of articles. Where an article has its own Fulltext, it provides a "Read more..." link.
- News iterator. A component which shows a list of news, sorted by descending PublishDate. Only Leadins are shown; a "Read more..." link is provided to access the Fulltext.
- Page. A component made of a header, a middle part and a footer.
- RSS Latest Item Leadin. A component which shows the most recent item taken from an external RSS feed. Only the Leadin is shown; a "Read more..." link is provided to navigate to the original document.
- Sidebar. A column composed of up to ten sub-components.
- Template Selector. A component used to select which template to use to format the current article.
- Vertical Menu. A list of links shown as a vertical menu.
- XHTML Container. The master container of everything; includes the heading XHTML declarations.
Please note that this structure can be improved and cleaned up. For instance, Vertical and Horizonal Menu could be implemented in the very same way, by using lists instead of tables and delegating the horizontal / vertical layout to the CSS style sheet. Header and Footer should be really renamed to something such as Three Rows and Two Rows (and maybe the latter could be dropped). The News iterator and the Content iterator could be merged and parametrized for what concerns the sort order, etc...
4. Velocity or JSP?
InfoGlue has been originally developed upon Velocity, an Apache scripting technology for creating dynamic web sites. Velocity is similar to JSP in the fact that it provides a scripting language that enables you to read an write data against a Java™ class, use control statemens such as 'if' or 'forEach', etc. Basically it has the same purpose of the JSP, but it's implemented in a different way.
Now, I understand this is much a matter of personal taste; anyway I must say I really don't like Velocity. While it has an advantage over JSP, that is it requires you to write less code, this is really not enough to persuade me to learn and use something that is not a widely adopted industrial standard, as JSP is (futhermore since when JSP introduced the EL expression language and the use of the dollar operator, the verbosity differences have been greatly reduced). Above all I do really prefer to code with an XML-based syntax, which JSP uses and which Velocity doesn't.
InfoGlue offers a Salomon's solution to this dilemma: version 2.0 let you to choose which technology to use. Actually you could also use both of them, JSP and Velocity, in the same page (I didn't investigate how InfoGlue does this, but I suppose it does a double processing on your code). I don't advice you to use both syntaxes in the same time since this leads to cumbersome code; in some cases I also noticed some problems.
In order to have EL language working with JSPs in InfoGlue you need to add the isELIgnored="false" attribute in the @page directive (see code listings below). This should be not required with a deployment descriptor version 2.4 and above (check http://java.sun.com/j2ee/1.4/docs/tutorial/doc/JSPIntro13.html), but InfoGlue actually uses a 2.3 descriptor (this is related to the way InfoGlue is built, it doesn't care if you're running it within the newest Tomcat).
Given the previous statement, I suppose you have understood that all of my components have been written use JSPs. If you prefer Velocity, it should not be hard to translate them.
Before looking at them in details, I have to point out some issues, mostly related to the fact that this is work in progress:
- My JSP components are more verbose than they should, and they also include scriptlets, that is chunks of Java™ code, which is something I don't like at all.
- The point is that InfoGlue offers a set of taglibs that could be used to remove all scriptlets from your JSPs, but I had some problems with some of them (basically I think there are bugs and in some cases maybe I didn't understand something).
- I will surely fix this in the following weeks if some InfoGlue developers fixes the problems or clarifies some obscure points.
- The Breadcrumb is still in Velocity code since I have still to figure out a good design with taglibs.
So, basically, if you'd like to draw some conclusions about the Velocity vs JSP comparison, I think it's unfair to do it now with the current development stage of my components.
5. Preparation: creating some example documents
The visual page layout editor in the Structure Tool of InfoGlue is pretty cool (apart from some quirks) and allows you to visually build your page structure item by item. For this reason, I suggest that you first create all the Contents needed for this tutorial and later you move to the Component editor (otherwise you would be forced to move back and forth from the Content Tool to the Structure Tool, which is a pain).
I suggest to create the following structure with the Content Tool:
www.myexample.com +-- Blog +-- Blog item 1 +-- Blog item 2 +-- Blog item 3 +-- Dictionary +-- Dictionary item 1 +-- Dictionary item 2 +-- Dictionary item 3 +-- News +-- News item 1 +-- News item 2 +-- News item 3 +-- Components +-- My Header +-- My Footer +-- Static content 1 +-- Static content 2 +-- Contact us +-- Products +-- Product 1 +-- Product 2 +-- Product 3 +-- Splash +-- Home
To be quick, I'd suggest that for any of these Contents you just insert a simple text such as "I'm the Blog item 1". Insert something both in the Leadin and in the FullText fields (for My Header and My Footer only in the FullText).
Now I'll explain all of my components, and at each step you will add the component into the template.
6. Basic components
In this section I'm going to illustrate the basic components I've developed. I will give some implementation details and the full listing, as well as some instruction to follow step by step in order to complete this tutorial. If you are not interested in the implementation details you can safely skip those parts.
6.1. XHTML Container
This component should be the master container of everything. It provides:
- a generic XHTML header;
- the link to the CSS style sheet;
- the title of the page, composed by a fixed, user-definable prefix and the title of the current SiteNode.
It contains a single slot, which should be filled with your contents. It defines the following ComponentProperties:
Property Name | Type | Entity | Multiplicity | Description |
CSS page | binding | SiteNode | single value | The CSS style sheet |
Article | binding | Content | single value | The main contents |
Template | binding | Content | single value | The template to format the main contents |
TitlePrefix | textfield | - | single value | The fixed prefix to use with the title |
HorizontalMenuPages | binding | SiteNode | collection | The list of links for a horizontal menu |
VerticalMenuPages | binding | SiteNode | collection | The collection of links for a vertical menu |
Multiple Articles | binding | Content | collection | The collection of articles for pages which display multiple articles |
Article and Multiple Articles should be really merged into a single property; furthermore a single, consistent naming scheme should be used (e.g. concerning spaces inside the names). These are minor improvements but I won't do them now since I fear that changing a name of a property will screw up my sites; furthermore now I've to stop working on it and write this tutorial!
Let me point out a major design flaw here: actually, only "CSS page" and "TitlePrefix" properties are used by XHTML Container. All the others are defined here but used by the other components. From an object oriented point of view, this is an unnecessary dependence: each component should define its own properties. But I've found some problems in doing this with InfoGlue, either because of an InfoGlue bug or because I've not understood something - more details later.
Now, let me point out some implementation details:
- A code block marked as "temp. fix" is used to make a taglib variable 'templateLogic' available to this and to other components. InfoGlue provides an implicit Velocity variable named 'templateLogic' that is used to access the basic entry point for data manipulation and retrieval. Using JSP there are specialized taglibs should be used instead of this variable, but as I anticipated I found some problems with them.
- ComponentProperties of type textfield can be accessed with the tag <igs:componentPropertyValue>. This tag reads a property and makes it available as a JSP variable.
- ComponentProperties of type Sitenode or Content can be accessed with the tag <igs:pageUrl> which returns the URL by which the pointed resource is made accessible.
<%-- (C) Copyright 2005, 2006 by Fabrizio Giudici - fabrizio.giudici@tidalwave.it --%> <%-- Released through the MIT License - http://www.opensource.org/licenses/mit-license.php --%> <%@ page language="java" isELIgnored="false" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="infoglue-content" prefix="igc" %> <%@ taglib uri="infoglue-structure" prefix="igs" %> <%-- Temp. fix used by other templates --%> <% request.setAttribute("templateLogic", request.getAttribute("org.infoglue.cms.deliver.templateLogic")); %> <%-- end fix --%> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <igs:componentPropertyValue id="titlePrefix" propertyName="TitlePrefix"/> <title>${titlePrefix}${templateLogic.pageTitle}</title> <meta http-equiv="Content-Type" content="text/html; charset=iso8859-1" /> <link href="<igs:pageUrl propertyName='CSSPage'/>" rel="stylesheet" type="text/css" /> </head> <!-- XHTML Container --> <body> <ig:slot id="Page"></ig:slot> </body> </html>
Tutorial instructions. Move to the Structure Tool and click on the "www.example.com" item. In the right pane, choose the "SiteNode" type as "ComponentPage". FORGET ABOUT the other two types, that are in InfoGlue for compability with older versions. Click "Save".
Now click "View Page Components" on the tool bar. A message "The page has no base component assigned yet. Click here to assign one" should appear. Click on the link.
Now you have to choose the Component to associate to this SiteNode. Select "XHTML Container".
Now a popup asks you to insert some values for the XHTML properties. Do nothing for now and close the popup.
A "Page Components" popup should appear that shows you your page structure in a tree view (if the popup does not appear, search for the "Component Palette" which is SURELY open. Maybe you have to use the scroll bars to find it. At the very right side, there is a "tree" icon, click on it).
As you can see, under "XHTML Container" there is a "Properties" branch (clicking on it the property popup appears again) and a Slots / Page branch. Openin the context menu (ctrl-click on Mac, right click on Windows/Linux) on Page a "Add component" option could be selected. But before proceeding we have first to introduce the next component.
PS There are other ways to add components. Under those popups, InfoGlue is showing you a preview of your page, which at this point is obviously empty. But you should see a "Click to add component". Click there (again: use the context menu, it's not obvious) and again the "Add component" option appears. Looks easier, but when later our page structure will be more complex, we will have a lot of "Click to add component" and it will be almost impossible to understand which slots of which component we are going to use! InfoGlue developers should really improve things here (it would be enough to show in each link the component and slot name!).
PS2 You could even drag and drop from the Component Palette onto "Click to add component", but again this faces with the above problem...
6.2. Page
This is a very simple component which defines three slots: a Header, a Middle and a Footer. It defines a single property named width that specifies the width of the page.
There is some formatting stuff, including two cells for drawing a shadow at both sides, that should be parametrized. Maybe this could be all moved to the CSS style sheet.
<%-- (C) Copyright 2005, 2006 by Fabrizio Giudici - fabrizio.giudici@tidalwave.it --%> <%-- Released through the MIT License - http://www.opensource.org/licenses/mit-license.php --%> <%@ page language="java" isELIgnored="false" %> <%@ taglib uri="infoglue-structure" prefix="igs" %> <!-- Page begins --> <table class="Page" cellpadding="0" cellspacing="0" border="0" align="center"> <tr> <td class="LeftShadow" width="18"> </td> <td> <igs:componentPropertyValue id="width" propertyName="width"/> <table width="${width}" class="Page" cellpadding="0" cellspacing="0" border="0"> <tr> <td><ig:slot id="Header"></ig:slot></td> </tr> <tr> <td valign="top"><ig:slot id="Middle"></ig:slot></td> </tr> <tr> <td><ig:slot id="Footer"></ig:slot></td> </tr> </table> </td> <td class="RightShadow" width="18"> </td> </tr> </table> <!-- Page ends -->
Tutorial instructions. Go back to the Structure Tool. Now get back to that "Add component" menu and activate it. The Component selector panel appears again. Select "Page". Again the Property popup appears. Enter 800 for width and then click "Save". Open again the "Page components" popup. You see that "Page" appears as a child of "XHTML Component". You now see the three slots of "Page": Header, Middle, Footer.
For each further steps you will have to open the "Page components" popup. I will not repeat this any longer, it will be implicitly assumed.
6.3. Three Rows
There is not really much to say about this component that just lays out three slots in a column.
<%-- (C) Copyright 2005, 2006 by Fabrizio Giudici - fabrizio.giudici@tidalwave.it --%> <%-- Released through the MIT License - http://www.opensource.org/licenses/mit-license.php --%> <!-- Three Rows begins --> <table width="100%" border="0" cellspacing="0" cellpadding="0"> <tr> <td><ig:slot id="Top"></ig:slot></td> </tr> <tr> <td><ig:slot id="Middle"></ig:slot></td> </tr> <tr> <td><ig:slot id="Bottom"></ig:slot></td> </tr> </table> <!-- Three Rows ends -->
Tutorial instructions. Go back to the Structure Tool. Select the "Header" slot of Page and add a Three Rows component in it. There are no properties to add. Select the "Footer" slot of Page and add another Three Rows component.
6.4. HTML Fragment
This component is used to include a raw HTML fragment. This can be used, for instance, to place a logo, a fixed text such as the copyright notice, etc. The text to include is specified as the "Article" property.
From the implementation point of view, this component retrieves the text to include thanks to the <igc:contentAttribute> tag (note that the FullText of the article is used). Notice the parse="true" which is needed to tell InfoGlue to run the Velocity and JSP upon the included fragment. This is really required since the InfoGlue HTML WYSIWYG editor codes embedded images and internal links as Velocity expressions that translate a SiteNode/Content id into a real URL.
<%-- (C) Copyright 2005, 2006 by Fabrizio Giudici - fabrizio.giudici@tidalwave.it --%> <%-- Released through the MIT License - http://www.opensource.org/licenses/mit-license.php --%> <%@ page language="java" isELIgnored="false" %> <%@ taglib uri="infoglue-content" prefix="igc" %> <!-- HTML Fragment begins --> <igc:contentAttribute id="leadIn" propertyName="Content" attributeName="Leadin" parse="true"/> <igc:contentAttribute id="text" propertyName="Content" attributeName="FullText" parse="true"/> <igc:contentAttribute id="template" propertyName="Content" attributeName="Template" parse="true"/> ${leadIn} ${text} ${template} <!-- HTML Fragment ends -->
Tutorial instructions. Go back to the Structure Tool. Select the "real header" slot of Header and add a HTML Fragment component. As the "Article" property select the "My Header" Content (click on the "Undefined" node and a selector panel pops up).
Now select the "footer contents" slot of Footer and add another HTML Fragment component. As "Article" select "my footer".
Now you should see that your page is starting to take form.
6.5. Body
There is not really much to say about this components that just layouts some slots.
<%-- (C) Copyright 2005, 2006 by Fabrizio Giudici - fabrizio.giudici@tidalwave.it --%> <%-- Released through the MIT License - http://www.opensource.org/licenses/mit-license.php --%> <!-- Body begins --> <table width="100%" border="0" cellspacing="0" cellpadding="0"> <tr> <td rowspan="3" valign="top" class="BodyLeft"><ig:slot id="Left"></ig:slot></td> <td valign="top" class="BodyTop"><ig:slot id="Top"></ig:slot></td> <td rowspan="3" valign="top" class="BodyRight"><ig:slot id="Right"></ig:slot></td> </tr> <tr> <td valign="top" class="BodyCenter"><ig:slot id="Center"></ig:slot></td> </tr> <tr> <td valign="top" class="BodyBottom"><ig:slot id="Bottom"></ig:slot></td> </tr> </table> <!-- Body ends -->
Tutorial instructions. Go back to the Structure Tool. Add a Body component into the "Middle" slot of Page.
6.6. Sidebar
This component just performs some layout, being a column with up to ten slots. You can define the width and the CSS class, so you can apply different styles to different instances
For what concerns the implementation, while including a fixed number of slots can appear ugly, this is the only way to go. It is not possible to rewrite this component by using a loop (something like <c:forEach ...><ig:slot id="..."></ig:slot></c:forEach>), since the InfoGlue component editor counts the number of <ig:slot> tags to figure out how many slots there are. In other words, the editor is not capable to understand <c:forEach> or similar stuff.
<%-- (C) Copyright 2005, 2006 by Fabrizio Giudici - fabrizio.giudici@tidalwave.it --%> <%-- Released through the MIT License - http://www.opensource.org/licenses/mit-license.php --%> <%@ page language="java" isELIgnored="false" %> <%@ taglib uri="infoglue-structure" prefix="igs" %> <!-- Sidebar begins --> <igs:componentPropertyValue id="class" propertyName="Class"/> <igs:componentPropertyValue id="width" propertyName="Width"/> <table width="${width}" class="${class}" cellpadding="0" cellspacing="0" border="0" height="100%"> <tr><td class="${class}Cell"><ig:slot id="content1"></ig:slot></td></tr> <tr><td class="${class}Separator"></td></tr> <tr><td class="${class}Cell"><ig:slot id="content2"></ig:slot></td></tr> <tr><td class="${class}Separator"></td></tr> <tr><td class="${class}Cell"><ig:slot id="content3"></ig:slot></td></tr> <tr><td class="${class}Separator"></td></tr> <tr><td class="${class}Cell"><ig:slot id="content4"></ig:slot></td></tr> <tr><td class="${class}Separator"></td></tr> <tr><td class="${class}Cell"><ig:slot id="content5"></ig:slot></td></tr> <tr><td class="${class}Separator"></td></tr> <tr><td class="${class}Cell"><ig:slot id="content6"></ig:slot></td></tr> <tr><td class="${class}Separator"></td></tr> <tr><td class="${class}Cell"><ig:slot id="content7"></ig:slot></td></tr> <tr><td class="${class}Separator"></td></tr> <tr><td class="${class}Cell"><ig:slot id="content8"></ig:slot></td></tr> <tr><td class="${class}Separator"></td></tr> <tr><td class="${class}Cell"><ig:slot id="content9"></ig:slot></td></tr> <tr><td class="${class}Separator"></td></tr> <tr><td class="${class}Cell"><ig:slot id="content10"></ig:slot></td></tr> </table> <!-- Sidebar ends -->
Tutorial instructions. Go back to the Structure Tool. Add a Sidebar component into the "left" slot of the Body component. Enter "150" as width and "LeftSidebar" as class. Add another Sidebar component into the "right" slot of the Body component. Enter "200" as width and "RightSidebar" as class.
Now add a HTML Fragment into the "content1" slot of the left sidebar and select "Static Content 1" as Article. Add another HTML Fragment into the "content1" slot of the right sidebar and select "Static Content 2" as Article. Again, your page is taking even more form.
6.7. Breadcrumb
This component shows the path of SiteNodes from home to the current shown page.
For what concerns the implementation, this is quite easily accomplished by using the expression \templateLogic.getParentSiteNode(\siteNodeId).siteNodeId which accesses the siteNodeId of the parent of the current node.
A special case is given when an explicit contentId is given. In this case, we just append \templateLogic.getContentAttribute(\templateLogic.contentId, "NavigationTitle").
#macro (printNode $siteNodeId) #set ($title = $templateLogic.componentLogic.getPageNavTitle($siteNodeId)) #set ($initial = $title.substring(0, 1)) #if ($templateLogic.getParentSiteNode($siteNodeId)) #printNode($templateLogic.getParentSiteNode($siteNodeId).siteNodeId) #if ($initial != "_") :: #end #end #set ($title = $templateLogic.componentLogic.getPageNavTitle($siteNodeId)) #set ($initial = $title.substring(0, 1)) #if ($initial != "_") <a href="$templateLogic.componentLogic.getPageUrl($siteNodeId)" class="Breadcrumb">$title</a> #end #end <!-- Breadcrumb begins --> <div align="right"> <span class="Breadcrumb"> #printNode($templateLogic.siteNodeId) #if ($templateLogic.contentId > 0) #set ($title = $templateLogic.getContentAttribute($templateLogic.contentId, "NavigationTitle")) :: <a href="" class="Breadcrumb">$title</a> #end </span> </div> <!-- Breadcrumb ends -->
Implementation details. Go back to the Structure Tool. Add a Breadcrumb into the "top" slot of the Body component.
6.8. Template Selector
This component allows to specify which formatting template should be used to render the contents of its slot.
Before talking about its implementation, think about it: do we really need it? Theoreticaly we don't, as we should able to change any component bound to a slot by just using the InfoGlue component editor. I will explain later why indeed we do need it.
The implementation is straightforward: the Template property, which is a Content, can be read by using the tag <igc:boundContents>. Since this always returns an array (to also be able to read properties with multiple values), we just dereference the first index ([0]) and then get the node id. At last we extract the template with templateLogic.getContentAttribute(templateId, "Template") (the latest "Template" comes from the fact that InfoGlue tags the source of an HTMLTemplate with this name).
If you look at the code, you'll find that there's a test for templateLogic.contentId > 0; in this case, a direct content is retrieved and formatted inline. This is something required with iterators, that I'll explain below.
This is indeed bad design: I should move this code to each iterator, also because indeed any iterator could decide to format things in a different way.
<%-- (C) Copyright 2005, 2006 by Fabrizio Giudici - fabrizio.giudici@tidalwave.it --%> <%-- Released through the MIT License - http://www.opensource.org/licenses/mit-license.php --%> <%@ page language="java" isELIgnored="false" %> <%@ page import="org.infoglue.deliver.controllers.kernel.impl.simple.*" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="infoglue-content" prefix="igc" %> <% BasicTemplateController templateLogic = (BasicTemplateController)request.getAttribute("templateLogic"); %> <igc:boundContents id="templateTemp" propertyName="Template"/> <c:set var="templateId" value="${templateTemp[0].id}"/> <!-- Template Selector begins - templateId = ${templateId} --> <c:choose> <c:when test="${(templateLogic.contentId != null) && (templateLogic.contentId > -1)}"> <%-- TODO: would be better to get the Article templateId and forward as in the 'otherwise' --%> <h2 class="ArticleHeadline"><%= templateLogic.getContentAttribute("Title") %></h2> <p class="ArticleLeadin"><%= templateLogic.getContentAttribute("Leadin") %></p> <p class="ArticleText"><%= templateLogic.getContentAttribute("FullText") %></p> </c:when> <c:otherwise> <% Integer templateId = (Integer)pageContext.getAttribute("templateId"); %> <%= templateLogic.getContentAttribute(templateId, "Template") %> </c:otherwise> </c:choose> <!-- Template Selector ends -->
Tutorial instructions. Go back to the Structure Tool. Add a Template Selector into the "center" slot of the Body component.
6.9. Article
This is probably the template you'll use most of time. It fully displays an article, both the Leadin and Fulltext, and should be used for rendering the standard contents of your website.
The implementation is straightforward, as templateLogic is used to extract the relevant parts of the article.
<%-- (C) Copyright 2005, 2006 by Fabrizio Giudici - fabrizio.giudici@tidalwave.it --%> <%-- Released through the MIT License - http://www.opensource.org/licenses/mit-license.php --%> <%@ page language="java" isELIgnored="false" %> <%@ page import="org.infoglue.deliver.controllers.kernel.impl.simple.*" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="infoglue-content" prefix="igc" %> <igc:boundContents id="contents" propertyName="Content"/> <c:set var="articleId" scope="request" value="${contents[0].id}"/> <% // Unfortunately there are not taglibs for templateLogic.getParsedContentAttribute(). Integer articleId = (Integer)pageContext.findAttribute("articleId"); BasicTemplateController templateLogic = (BasicTemplateController)request.getAttribute("org.infoglue.cms.deliver.templateLogic"); %> <!-- Article begins - articleId = ${articleId} --> <c:choose> <c:when test="${(templateLogic.contentId != null) && (templateLogic.contentId > -1)}"> <h2 class="ArticleHeadline"><%= templateLogic.getContentAttribute("Title") %></h2> <p class="ArticleLeadin"><%= templateLogic.getContentAttribute("Leadin") %></p> <p class="ArticleText"><%= templateLogic.getContentAttribute("FullText") %></p> </c:when> <c:otherwise> <h2 class="ArticleHeadline"><%= templateLogic.getParsedContentAttribute(articleId, "Title") %></h2> <p class="ArticleLeadin"><%= templateLogic.getParsedContentAttribute(articleId, "Leadin") %></p> <p class="ArticleText"><%= templateLogic.getParsedContentAttribute(articleId, "FullText") %></p> </c:otherwise> </c:choose> <!-- Article ends -->
Tutorial instructions. Go back to the Structure Tool. This time you shouldn't add anything: open the "Page components" popup and select the "Properties" branch of XHTML Container. Select the "Template" property and choose the "Article" component. Now select the "Article" property and choose the "Splash" content.
*** Indeed I should rename the "Article" property to something like "Contents" in order to make less confusion here.
Well, you've just (almost) finished your first page, which is the splash page, the one that will be shown when a user connects to your site. Click on "Preview Sitenode" on the icon bar, and a new browser window will appear with the preview of this page.
But indeed you also created the default page layout for your website. Adding other pages will be much more easier.
7. Create the website structure
Now let's create some structure of the website. In the Structure Tool select "www.example.com" and add some SiteNodes as in the following tree (each node should have a type of ComponentPage):
www.example.com +-- Home +-- Blog +-- Contact us +-- Dictionary +-- News +-- Products
Note that this is similar but not equal to the tree in the Content Tool. First of all, you needn't to create a separate SiteNode for each Content, but only for groups of Contents (e.g. you just need a single "News" SiteNode, indipendently by how many News Item you have).
Now select "Contact us" and "View components". Again InfoGlue is telling you "The page has no base component assigned yet. Click here to assign one". Click on here and select XHTML Container. But don't worry! You don't have to run through all the things that you did before, since your "Contact us" sitenode is inheriting the properties and the structure from its parent. You need only to set the "Article" property to the "Contact us" Content.
Repeat the same thing for the "Home" sitenode (again connecting it to the "Splash" content node).
*** In my opinion it's nice to have a "Home" sitenode which is different than the root node of the site. This allows you to have different splash screens and home page, and could be useful for instance to display some flash animation when users connect to your site (this would also require to change the page structure for the root node). If you don't need this, you can just ignore the "Home" sitenode.
8. Navigation controls and iterators
Navigation controls allow you to create menus to point at selected SiteNodes in your Repository; iterators make it possible to deal with multiple Contents from a single SiteNode.
8.1. Menu
This component implements a classic menu with a set of navigational links. Providing the appropriate class together with a CSS style sheet, Menu can be laid out both in an horizontal and a vertical fashion.
Menu gets the collection of bounded pages by using the tag <igs:boundPages> and then runs an iterator on them.
<%-- (C) Copyright 2005, 2006 by Fabrizio Giudici - fabrizio.giudici@tidalwave.it --%> <%-- Released through the MIT License - http://www.opensource.org/licenses/mit-license.php --%> <%@ page language="java" isELIgnored="false" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="infoglue-structure" prefix="igs" %> <!-- Menu begins --> <igs:componentPropertyValue id="class" propertyName="Class"/> <ul class="${class}"> <igs:boundPages id="nodes" propertyName="Menu"/> <%-- useInheritance="true" broken --%> <c:forEach items="${nodes}" var="node" > <li class="${class}"><a href="${node.url}" class="${class}">${node.navigationTitle}</a></li> </c:forEach> </ul> <!-- Menu ends -->
Tutorial instructions. Go back to the Structure Tool. Select the root node (www.example.com), since we want that the menus appear the same way for all the website. Add an Horizontal Menu in the "above the header" slot of the Header component and a Vertical Menu in a free slot in the left Sidebar.
To define the list of menu items, you have to open the Properties pane of the "XHTML Container". Select "HorizontalMenuPages". A selection pane appears that allows you to add some SiteNodes (and place them in the order you want). Add "Home" and "News" (remember to click on the "Save" button!). Repeat the process for the "VerticalMenuPages" property (this time add a different set of nodes, e.g. Home, Blog, News, Products, Dictionary).
Select the www.example.com node and click on "Preview sitenode". You will see that your menus have been created and they are working (if you click on any page other than "Home" and "Contact us" you will get an error, but just because we didn't complete the other SiteNodes).
8.2. Multiple Articles with Leadin
A simple variant of the Article component, it basically uses an iterator to run through all articles as we did in the Horizontal Menu.
From the implementation point of view, there are two basic differences:
- <igs:boundContents> is used in place of <igs:boundPages>, since now we are iterating the contents of the Multiple Articles property which is declared as Content (while HorizontalMenuPages was declared as SiteNode).
- For the same reason, we can't get the url by using article.url, since Contents are not SiteNodes and do not have a direct url to access them. So we have to build a three-id URL thanks to the templateLogic.getPageURL() method. As SiteNode id we are passing the id of the current node.
<%-- (C) Copyright 2005, 2006 by Fabrizio Giudici - fabrizio.giudici@tidalwave.it --%> <%-- Released through the MIT License - http://www.opensource.org/licenses/mit-license.php --%> <%@ page language="java" isELIgnored="false" %> <%@ page import="org.infoglue.deliver.controllers.kernel.impl.simple.*" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="infoglue-content" prefix="igc" %> <%@ taglib uri="infoglue-structure" prefix="igs" %> <% // Unfortunately there are not taglibs for templateLogic.getParsedContentAttribute(). BasicTemplateController templateLogic = (BasicTemplateController)request.getAttribute("org.infoglue.cms.deliver.templateLogic"); %> <!-- Multiple Articles begins --> <igc:boundContents id="articles" propertyName="Multiple Articles"/> <c:forEach var="article" items="${articles}"> <c:set var="articleId" value="${article.id}"/> <% Integer articleId = (Integer)pageContext.findAttribute("articleId"); %> <h2 class="ArticleHeadline"><%= templateLogic.getParsedContentAttribute(articleId, "Title") %></h2> <p class="ArticleLeadin"> <%= templateLogic.getParsedContentAttribute(articleId, "Leadin") %> <c:set var="fullText" value='<%= templateLogic.getParsedContentAttribute(articleId, "FullText") %>'/> <c:if test="${! empty fullText}"> <br/> <%-- igs:pageUrl siteNodeId="${templateLogic.siteNodeId}" languageId="-1" contentId="20"/ --%> <c:set var="url" value='<%= templateLogic.getPageUrl(templateLogic.getSiteNodeId(), templateLogic.getLanguageId(), articleId) %>'/> <a href="${url}">Read more...</a> </c:if> </p> </c:forEach> <!-- Multiple Articles ends -->
Tutorial instructions. Select the root node (www.example.com), which is the splash page. Open the Property pane and change "Template" from "Article" to "Multiple Articles with Leadin". Then define the "Multiple Articles" property and enter a set of pages of your choice (e.g. Splash, Product 1, New Item 1). Preview the site again; now you see that you just created a splash page with some leading contents, that can be freely selected by you.
8.3. Content Iterator with Leadin
This is very similar to Multiple Articles with Leadin, but the contents to iterate through are not explicitly defined as a property; instead, children of the current Article are used. It is useful when you have a lot of articles (e.g. a product list) that can be added incrementally and you don't want to create a new SiteNode for each one.
For what concerns the implementation, templateLogic.getChildContents() is used to retrieve the list of children from a node. You can specify if you want to go recursively and if the list must be sorted according to a certain property.
<%-- (C) Copyright 2005, 2006 by Fabrizio Giudici - fabrizio.giudici@tidalwave.it --%> <%-- Released through the MIT License - http://www.opensource.org/licenses/mit-license.php --%> <%@ page language="java" isELIgnored="false" %> <%@ page import="org.infoglue.deliver.controllers.kernel.impl.simple.*" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <% Integer articleId = (Integer)pageContext.findAttribute("articleId"); BasicTemplateController templateLogic = (BasicTemplateController)request.getAttribute("templateLogic"); request.setAttribute("pages", templateLogic.getChildContents(articleId, false, "Title", "asc")); %> <!-- Content Iterator with Leadin begins - articleId = ${articleId} --> <h2><%= templateLogic.getContentAttribute(articleId, "Title") %></h2> <c:forEach var='item' items='${pages}'> <c:set var="itemId" value ="${item.id}"/> <% Integer itemId = (Integer)pageContext.findAttribute("itemId"); %> <h3><%= templateLogic.getContentAttribute(itemId, "Title") %></h3> <p> <%= templateLogic.getParsedContentAttribute(itemId, "Leadin") %><br/> <a href="<%= templateLogic.getPageUrl(templateLogic.getSiteNodeId(), templateLogic.getLanguageId(), itemId) %>">Read more...</a> </p> </c:forEach> <!-- Content Iterator with Leadin ends -->
Tutorial instructions. Select the "Products" sitenode. InfoGlue will ask you to define the base component, and again add "XHTML Container". In the properties, click on "Template" and select "Multiple Articles with Leadin"; then click on "Article" and select "Products".
Go to the preview. You should see now that this page contains a list the leadin text from the Product 1, Product 2 and Product 3 pages. Clicking on "Read more..." will show you the full text about each product. Should you add new Contents under the "Products" folder, they will be automatically shown here.
8.4. News Iterator with Leadin
Very similar to Content Iterator with Leadin.
The only implementation differences are:
- contents are sorted according to the PublishDateTime property in descending order;
- a Max Items property allows to specify the maximum number of items to display (this is useful e.g. to show the three most recent news items in a sidebar)
- a "News folder" property can be specified if the news are to be retrieved from a different node than the current article. This is useful e.g. to show in a sidebar a set of fixed news which are not related to the main page contents.
<%-- (C) Copyright 2005, 2006 by Fabrizio Giudici - fabrizio.giudici@tidalwave.it --%> <%-- Released through the MIT License - http://www.opensource.org/licenses/mit-license.php --%> <%@ page language="java" isELIgnored="false" %> <%@ page import="org.infoglue.deliver.controllers.kernel.impl.simple.*" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jstl/fmt" prefix="fmt" %> <%@ taglib uri="infoglue-content" prefix="igc" %> <%@ taglib uri="infoglue-structure" prefix="igs" %> <igs:componentPropertyValue id="dateFormat" propertyName="Date format"/> <igs:componentPropertyValue id="maxItems" propertyName="Max Items"/> <igs:componentPropertyValue id="header" propertyName="Header"/> <!-- it would be nice if componentPropertyValue had a default attribute --> <c:if test='${(dateFormat == "Undefined") || (dateFormat == "")}'> <c:set var="dateFormat" value ="d-MMM-yyyy HH:mm"/> </c:if> <c:if test='${(maxItems == "Undefined") || (maxItems == "")}'> <c:set var="maxItems" value ="99"/> </c:if> <c:set var="class" value ="Sidebar"/> <%-- FIXME --%> <c:set var="title" value ="Latest news"/> <%-- FIXME --%> <!-- News Iterator with Leadin starts - dateFormat = ${dateFormat}, maxItems = ${maxItems}, class = ${class} --> <% BasicTemplateController templateLogic = (BasicTemplateController)request.getAttribute("templateLogic"); request.setAttribute("pages", templateLogic.getComponentLogic().getBoundFolderContents("News folder", false, "publishDateTime", "desc")); %> <%-- The selection does not always works. It depends on the order in which multiple components are declared. For instance, if the news iterator with the "News folder" not set is declared first, everything is fine. On the contrary, the parameter will be inherited by others that will ignore the articleId! Maybe we can try "useinheritance=false" in the componentpropertyvalue. --%> <c:if test="${empty pages}"> <% Integer articleId = (Integer)request.getAttribute("articleId"); request.setAttribute("pages", templateLogic.getChildContents(articleId, false, "publishDateTime", "desc")); %> <c:set var="class" value =""/> <%-- FIXME --%> <c:set var="title" value ="<%= templateLogic.getContentAttribute(articleId, "Title") %>"/> </c:if> <h2 class="${class}">${title}</h2> <c:set var="count" value="0"/> <c:forEach var='article' items='${pages}'> <c:if test="${count < maxItems}"> <c:set var="articleId" value ="${article.id}"/> <% Integer articleId = (Integer)pageContext.findAttribute("articleId"); %> <h3 class="${class}"><%= templateLogic.getContentAttribute(articleId, "Title") %></h3> <span class="PublishDate">${article.publishDateTime}</span> <br/> <p class="${class}"> <%= templateLogic.getParsedContentAttribute(articleId, "Leadin") %> - <a href="<%= templateLogic.getPageUrl(templateLogic.getSiteNodeId(), templateLogic.getLanguageId(), articleId) %>">Read more...</a> <%-- Does not work, does not accept expressions in attributes! igs:pageUrl siteNodeId="${templateLogic.siteNodeId}" languageId="${templateLogic.languageId}" contentId="${article.id}" --%> </p> </c:if> <c:set var="count" value="${count + 1}"/> </c:forEach> <!-- News Iterator with Leadin ends -->
Tutorial instructions. Select the "News" sitenode and open the Property pane. Click on "Template" and select "News Iterator with Leadin" (don't insert any property when InfoGlue asks for them); then click on "Article" and select the "News" folder. Go to preview and you will see that all the news items are shown, from the most recent to the oldest.
Repeat the same for the "Blog" sitenode: select "News Iterator with Leadin" as "Template" and the "Blog" folder as "Article". I think that you got the trick now.
Again, any content that you add with the Content Tool under Blog and News folders will appear in this pages.
You could even add a "recent news" box on the sidebar. Select a free slot in the right sidebar and add the "News Iterator with Leadin". This time you need to insert some properties: click on "News folder" and select the "News" folder. As "Class" enter Sidebar. If you don't do it, this component will search for items to display in the current article. Since the same component is used for the Blog, when the blog page is shown, on the sidebar news would be replaced... by the blog items.
** The Max Items property doesn't work well at the moment.
8.5. Dictionary Iterator
This differs from Content Iterator with Leadin only in the layout.
<%-- (C) Copyright 2005, 2006 by Fabrizio Giudici - fabrizio.giudici@tidalwave.it --%> <%-- Released through the MIT License - http://www.opensource.org/licenses/mit-license.php --%> <%@ page language="java" isELIgnored="false" %> <%@ page import="org.infoglue.deliver.controllers.kernel.impl.simple.*" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <% Integer articleId = (Integer)pageContext.findAttribute("articleId"); BasicTemplateController templateLogic = (BasicTemplateController)request.getAttribute("templateLogic"); request.setAttribute("pages", templateLogic.getChildContents(articleId, false, "Title", "asc")); %> <!-- Dictionary Iterator begins - articleId = ${articleId} --> <h2><%= templateLogic.getParsedContentAttribute(articleId, "Title") %></h2> <c:forEach var='item' items='${pages}'> <c:set var="itemId" value ="${item.id}"/> <% Integer ite