Using Magnolia for Mobile Content
When we started work on version 2.0 of the Texas State Mobile app for the iPhone, the biggest new feature that the Marketing department wanted to see was a “What’s New” screen. Their idea was that they could use it to accomodate all of the folks who wanted a presence in the mobile app, but didn’t really have enough content to justify adding a permanent module to the app. Here’s what it looks like:
What you can’t see in the photo is that each of the three sections scrolls left to right when the user swipes on that area, bringing more buttons into view. Additionally, each button can link to a web page or to content within the app itself. Because of these specialized behaviors, we couldn’t implement this screen as a web page. But, of course, the Marketing department was used to being able to update content quickly and easily, and wasn’t interested in having to publish a new version of the mobile app each time the content on this screen needed updating. (Nor was my team interested in managing that many trips through Apple’s approval process!)
After giving it some thought, we hit upon the right solution: let the content be managed in our Magnolia CMS, and have the app ask Magnolia for the needed data when it starts up. We got the flexibility we were used to with our web content, but the additional functionality that the native Objective C code provided.
The way we did this was first to build a simple template in Magnolia for managing the content. For each button, we needed a standard-resolution image, a high-resolution image (for retina displays), and a URL. (The URL could either be “http:” or “txstatemobile:”, a scheme we invented for linking to content within the app itself.) We created a paragraph and dialog definition accordingly:
And a paragraph template so that Magnolia would know how to render one of these buttons when called upon to do so:
<cms:out nodeDataName="image" var="imageurl" /> <img src="${pageContext.request.contextPath}${imageurl}" /> <p><cms:out nodeDataName="link" /></p> <br style="clear: both" />
(Note: all of the JSP code could be done more easily with Freemarker, but we weren’t up to speed on using it yet.)
In order to allow editors to manage the content, we needed to create collections to hold the content — one for each row on the app screen. Here’s a sample from our template’s JSP that includes the top and middle rows:
<div class="section"> <h1>Large Buttons</h1> <cms:contentNodeIterator contentNodeCollectionName="largeButtons"> <div> <cms:adminOnly> <cms:editBar /> </cms:adminOnly> <cms:includeTemplate /> </div> </cms:contentNodeIterator> <cms:adminOnly> <cms:newBar contentNodeCollectionName="largeButtons" paragraph="mobile-large-button" newLabel="Add New Large Button"/> </cms:adminOnly> </div> <div class="section"> <h1>Small Buttons (Middle Row)</h1> <cms:contentNodeIterator contentNodeCollectionName="smallButtonsMiddle"> <div> <cms:adminOnly> <cms:editBar /> </cms:adminOnly> <cms:includeTemplate /> </div> </cms:contentNodeIterator> <cms:adminOnly> <cms:newBar contentNodeCollectionName="smallButtonsMiddle" paragraph="mobile-small-button" newLabel="Add New Small Button"/> </cms:adminOnly> </div>
This gives the editor a nice, friendly interface for managing the content:
Now, the tricky bit is that we don’t want to present this content as XHTML to the iPhone app. We generally use JSON as our data interchange format of choice, so we created another template file, this one with the iPhone in mind as the consumer. Here’s the bit in the JSON template that corresponds to the above section in the human-friendly template:
"largeButtons":[ <cms:contentNodeIterator contentNodeCollectionName="largeButtons" varStatus="status"> <cms:out nodeDataName="image" var="imageurl" /> <cms:out nodeDataName="image-hirez" var="imagehirezurl" /> <cms:out nodeDataName="link" var="link" /> { "image": "${ pageContext.request.scheme }://${serverUrl}${pageContext.request.contextPath}${imageurl}", "image-hirez": "${ pageContext.request.scheme }://${serverUrl}${pageContext.request.contextPath}${imagehirezurl}", "link": "${fn:replace( link, ' ', '%20' )}" }<c:if test="${not status.last}">,</c:if> </cms:contentNodeIterator> ], "smallButtonsMiddle":[ <cms:contentNodeIterator contentNodeCollectionName="smallButtonsMiddle" varStatus="status"> <cms:out nodeDataName="image" var="imageurl" /> <cms:out nodeDataName="image-hirez" var="imagehirezurl" /> <cms:out nodeDataName="link" var="link" /> { "image": "${ pageContext.request.scheme }://${serverUrl}${pageContext.request.contextPath}${imageurl}", "image-hirez": "${ pageContext.request.scheme }://${serverUrl}${pageContext.request.contextPath}${imagehirezurl}", "link": "${fn:replace( link, ' ', '%20' )}" }<c:if test="${not status.last}">,</c:if> </cms:contentNodeIterator> ]
But how to get JSON when we want it and HTML when we want it? Simple! Sub-Templates. Here’s the template definition:
By defining the sub-template this way, we can ask for “whats-new.html” for the human-friendly view, and “whats-new.json” for the rendering the iPhone wants.
Now that we’ve got this data published, it’s a simple matter for the iPhone app to fetch the JSON, to download the images from the URLs Magnolia provides, and to apply them to the UIButtons we create in the iPhone app. Best of all, since we’re making the data available using internet standards like JSON and HTTP, we can take advantage of it elsewhere too. The University’s upcoming Android app uses exactly the same data for its What’s New screen as well, allowing the Marketing team to reach out to even more mobile users with no extra effort on their part.