3.10. Creating Another Module

Click on the home page link, and you'll notice it is taking us to the PHOCOA examples page. We definitely don't want this as the home page of our blog. Instead, we'll create a new home page to show the 10 latest blog posts.

To accomplish this, we're going to create one module that will handle displaying blog posts to the public. Then, we'll create a home page implementation that leverages our public-facing blog page to create a nice home portal.

Create a New Module

First, let's create a new module for our public-facing blog access. CD to the modules directory and use the phocoa utility to do this:

$ phocoa createModule         
phing -f /Users/alanpinstein/dev/sandbox/phocoa/phocoa/phing/build.xml -Dusing.phocoa.make=true -Dphocoa.pwd=/Users/alanpinstein/dev/sandbox/blog/blog/modules -Dphocoa.dir=/Users/alanpinstein/dev/sandbox/phocoa/phocoa -Dphocoa.project.name=blog -Dphocoa.project.dir=/Users/alanpinstein/dev/sandbox/blog/blog createModule
Buildfile: /Users/alanpinstein/dev/sandbox/phocoa/phocoa/phing/build.xml

phocoa > prepareGeneral:
     [echo] PHOCOA framework base dir at: /Users/alanpinstein/dev/sandbox/phocoa/phocoa

phocoa > prepareProject:
     [echo] 1
      [php] Evaluating PHP expression: $_ENV['_']
     [echo] PHOCOA project dir at: /Users/alanpinstein/dev/sandbox/blog/blog
[realpathexpandhome] Resolved /Users/alanpinstein/dev/sandbox/blog/blog/.. to /Users/alanpinstein/dev/sandbox/blog
     [echo] PHOCOA project container dir at: /Users/alanpinstein/dev/sandbox/blog
 [property] Loading /Users/alanpinstein/dev/sandbox/blog/blog/conf/build.properties
 [property] Unable to find property file: /Users/alanpinstein/dev/sandbox/blog/blog/conf/build.properties... skipped

phocoa > createModule:
Module name: blogview
Default page [blank for none]: list
     [exec] Executing command: /opt/local/bin/php /Users/alanpinstein/dev/sandbox/phocoa/phocoa/framework/createModule.php blogview list 2>&1
     [exec] Writing blogview/blogview.php
     [exec] Done building module blogview!
     [exec] Writing list.tpl
     [exec] Writing list.yaml
     [exec] Done!

BUILD FINISHED

Total time: 37.1642 seconds

This new module/page is accessible at http://servername/blogview. Notice how PHOCOA automatically redirects the user to http://servername/blogview/list since we've set up our list view as the default page.

Setup our Shared Instances and Load Data

Now we need to add code to this module to display our list. First off, we need to create a shared instance to hold our array of blog posts, by editing shared.yaml:

Blog             : 
  class     : WFArrayController
  properties:
    automaticallyPreparesContent: false
    class                       : Blog
    classIdentifiers            : blogId
postDateFormatter:
  class     : WFUNIXDateFormatter
  properties: 
    formatString: M j \a\t H:m

Next, we need to have our list page delegate load the 10 most recent blog posts into our Blog array controller. Add the following code to blogview.php:

class module_blogview_list
{
    function parametersDidLoad($page, $params)
    {
        // load the 10 most recent blog posts in desc order.
        $c = new Criteria;
        $c->addDescendingOrderByColumn(BlogPeer::POST_DTS);
        $c->setLimit(10);
        $page->sharedOutlet('Blog')->setContent( BlogPeer::doSelect($c) );
    }
}

Set up UI Widgets

Now we need to add our page UI objects to display the blog posts. For each post, we'll show the title (hyperlinked to the full post) and the post date.

postDts: 
  children  : 
    postDtsPrototype: 
      bindings  :
        value:
          controllerKey: '#current#'
          instanceID   : Blog
          modelKeyPath : postDts
      class     : WFLabel
      properties: 
        formatter: '#module#postDateFormatter'
  class     : WFDynamic
  properties: 
    arrayController: '#module#Blog'
title  : 
  children  : 
    titlePrototype: 
      bindings: 
        label: 
          controllerKey: '#current#'
          instanceID   : Blog
          modelKeyPath : title
        value:  
          controllerKey: '#current#'
          instanceID   : Blog
          modelKeyPath : blogId
          options      : 
            ValuePattern: /blogview/read/%1%
      class   : WFLink
  class     : WFDynamic
  properties: 
    arrayController: '#module#Blog'

There's a lot going on in this setup! Let's break it down.

PHOCOA has a special mechanism for automatically creating UI widgets for each iteration in a loop. A special widget called WFDynamic is used to handle the creation of widgets dynamically, one for each item in the associated array controller. A WFDynamic should have one child, named <id of WFDynamic>Prototype, which is used as a prototype for each widget created. This prototype can have formatters, bindings, etc on it, and PHOCOA will create each widget and attach data bindings as appropriate based on your prototype.

Notice the binding on the titlePrototype; it has a ValuePattern option. This is a mechanism similar to printf to make it easy to contstruct strings dynamically. The value property of a UI widget supports multi-value bindings, which allows you to string together one or more distinct values along with a format string into a single value to be used by the widget. The value property is mapped to %1%, value2 to %2%, etc. This makes it very easy to create URL's on the fly within the bindings mechanism.

Code Page HTML

Now, let's create the view code needed to display our list. Edit list.tpl:

<table>
{section name=posts loop=$__module->valueForKeyPath('Blog.arrangedObjectCount')}
    <tr> 
        <td>{WFView id="title}</td>
        <td>{WFView id="postDts"}</td>
    </tr>
{sectionelse}
    <tr><td>No blog posts.</td></tr>
{/section}
</table>

This simple bit of smarty creates our entire blog post list. Notice the $__module variable used to generate the loop count for section. PHOCOA automatically assigns a few variables to the template for you. See the WFPage API docs for a complete list.

Also notice that we're using Key-Value Coding to access the loop count from our array controller.

Reload the page, and you'll now see the working version of the last 10 blog posts. If you click on one of our links, you'll notice you get a 404 error since we haven't yet created the page to read the blog post. Let's do that now.

$ phocoa createPage
phing -f /Users/alanpinstein/dev/sandbox/phocoa/phocoa/phing/build.xml -Dusing.phocoa.make=true -Dphocoa.pwd=/Users/alanpinstein/dev/sandbox/blog/blog/modules/blogview -Dphocoa.dir=/Users/alanpinstein/dev/sandbox/phocoa/phocoa -Dphocoa.project.name=blog -Dphocoa.project.dir=/Users/alanpinstein/dev/sandbox/blog/blog createPage
Buildfile: /Users/alanpinstein/dev/sandbox/phocoa/phocoa/phing/build.xml

phocoa > prepareGeneral:
     [echo] PHOCOA framework base dir at: /Users/alanpinstein/dev/sandbox/phocoa/phocoa

phocoa > prepareProject:
     [echo] 1
      [php] Evaluating PHP expression: $_ENV['_']
     [echo] PHOCOA project dir at: /Users/alanpinstein/dev/sandbox/blog/blog
[realpathexpandhome] Resolved /Users/alanpinstein/dev/sandbox/blog/blog/.. to /Users/alanpinstein/dev/sandbox/blog
     [echo] PHOCOA project container dir at: /Users/alanpinstein/dev/sandbox/blog
 [property] Loading /Users/alanpinstein/dev/sandbox/blog/blog/conf/build.properties
 [property] Unable to find property file: /Users/alanpinstein/dev/sandbox/blog/blog/conf/build.properties... skipped

phocoa > createPage:
Page Name: read
     [exec] Executing command: /opt/local/bin/php /Users/alanpinstein/dev/sandbox/phocoa/phocoa/framework/createPage.php read 2>&1
     [exec] Writing read.tpl
     [exec] Writing read.yaml
     [exec] Done!

BUILD FINISHED

Total time: 2.9682 seconds

Create a Page to Display a Post

Our shared instances are already set up from the list page. So all we need to do is load the correct data in the page delegate for the read page.

For kicks, we'll implement this template in good old-fashioned smarty, just to show that you can seamlessly fall back on lower-level coding techniques if needed.

class module_blogview_read
{
    function parameterList()
    {
        return array('blogId');
    }
    function parametersDidLoad($page, $params)
    {
        // load the 10 most recent blog posts in desc order.
        $post = BlogPeer::retrieveByPK($params['blogId']);
        if (!$post) throw( new WFRequestController_NotFoundException("That post is not available.") );
        $page->assign('blog', $post);
    }
}

Now we need to implement the template.

<h2>{$blog->getTitle()}</h2>
<p>{$blog->getPostDts()|date_format}</p>
<div>{$blog->getPost()}</div>

Our public-facing blog posts pages are now done. Let's move on to the home page!