Widgity! Multi-element designs with CakePHP

April 27th, 2010 by Harisenbon Leave a reply »

Since I started programming with CakePHP almost two years ago, I’ve had a bunch of widgets on the right side of my page ( http://www.JapaneseTesting.com ).

Which pages each of these widgets appear on is guided by the controller, action, user role, or any combination thereof.

I used to deal with this in a Controller-Model combination, called with resource-heavy Request Actions. Then I found a better way. A Simple way. A way with Branching Elements.

Read on to see how to dynamically put customizable element blocks on any of your pages.

JapaneseTesting.com WidgetsThe Layout

On my site, each of the widgets is a separate element, and the way I used to deal with deciding which page to display them on was by running a RequestAction on my Widgets Controller, which in turn grabbed the widget data from the DB, checked which widgets needed to be displayed when, grabbed the elements, and then displayed each of them. Overall a very thorough way of managing and displaying the widgets, but not very nice on my load-time (especially when I started using DebugKit).

Eventually I started using a widgeting component of my own design that took an array of widget types, their caching statuses, various data, etc and threw it all into a controller variable which in turn threw it into a helper and then finally displayed the widgets.

This might have actually been slower than the previous DB calls, and let code to be placed in either controllers or views, which defeated the whole purpose of having a single consolidated place to insert the code.

But then I found a better way:

Enter the $this->name and $this->action variables!

Every view (and every element in that view) has the $this->name (the controller name) and the $this->action (the controller’s action name) available to them. That means that in any view, we know exactly what method has been called to get there (and thus, what variables, data, etc are available).

We’re going to use these variables, along with some Auth information in order to create widgets that obey a set of rules to display a bunch of widgets.

Our five widget elements

We’re going to have 5 elements that are going to be displayed to the user, depending on what page they’re looking at, and if they’re logged in or not.

  • who’s logged in Display  [ Shown to users who are logged in ]
  • latest Articles Display    [ Always Shown ]
  • latest Posts Display         [ Only available on Forum pages ]
  • Upgrade now Advert      [ Available on every page except the Forum pages ]
  • Handy Post editing          [ Shown only on the Forums/edit page ]

We’re going to store these in our elements folder with the following names:

  • /elements/widgets/whos_online.ctp
  • /elements/widgets/latest_articles.ctp
  • /elements/widgets/latest_posts.ctp
  • /elements/widgets/upgrade_now.ctp
  • /elements/widgets/post_edit.ctp

Controller Widgets

Then, we’re going to create a couple of “controller” widgets that allow us to break up the show/don’t show logic a little bit. These are each going to be named after the controller that will display them.

It’s up to you whether or not you want to use these, or just stick them in the main controller (see below) but I like the distribution of data, and it keeps me from having a huge main controller loaded on every page load.

Also, because I might have multiple widget bars with different contents, I’m going to put these in a different elements folder, named “sidebar_rt”

  • Controls all the Widgets that are Available only on the Forms pages [ /elements/sidebar_rt/forms.ctp ]

The Main Controller

Finally, we’re going to make a main controller element that will call all the widgets from a single location so we don’t have a lot of unnecessary code in our layout.

  • Main Widget Controller [ /elements/sidebar_rt/index.ctp ]

Putting it all together

So, now that we have the files created, let’s see what goes in them.

First, in your /layouts/default.ctp file, we put the main sidebar controller into the layout.

// /layouts/default.ctp
<div class="sidebar right">
     <?= $this->element('sidebar_rt/index'); ?>
</div>

Then, set the logic for the sidebar widgets:

// /elements/sidebar_rt/index.ctp

// Include the widget that will always be shown
echo $this->element('widgets/latest_articles');

// Include the widgets that are shown only if the user is logged in
if( !empty( $_user_info ) ){
   echo $this->element('widgets/whos_online');
}

// Include the widget that's shown everywhere except the forums
if( $this->name != 'forums' ){
   echo $this->element('widgets/upgrade_now');
}

// Finally, include the Controller widget, which is named after the controller name
echo $this->element('sidebar_rt/'.$this->name);

Now we’ve got three of our five widgets down.

Next, we just need to set up the controllers for the forums and users widgets

// /elements/sidebar_rt/forums.ctp

// These widgets will be displayed on every page in the forums controller:
echo $this->element('widgets/latest_posts');

// These widgets will only be shown for the corresponding action
switch($this->action){
   case 'edit':
      echo $this->element('widgets/post_edit');
      break;
}

And we’re done!

Using variables in Elements

Remember earlier when we said that we can use our view variables in Elements? Well, since we know when each widget will be used (controller/action), we know what variables will be available to them.

So, let’s look at the ‘post_edit’ widget up above. We want to have buttons to edit the current post, but we need to be able to access the current element’s id and other data in order to make a good edit box.

Luckily, we have all the data from the main view available in the widget as well!

// /elements/widgets/post_edit.ctp

<div class="actions">
   <a href="/posts/publish/<?=$this->data['Post']['id']?>">Publish this post</a>
   <a href="/posts/preview/<?=$this->data['Post']['id']?>">Preview this post</a>
   <a href="/posts/delete/<?=$this->data['Post']['id']?>">Delete this post</a>
</div>

<div class="tags">
   <? foreach($this->data['Tags'] as $tag ):?>
      <a href="/tags/delete/<?=$tag['id']?>"><?=$tag['name']?></a>
   <?endforeach;?>
   <a href="/tags/add/post_id:<?=$this->data['Post']['id']?>">Add a tag</a>
</div>

Conclusion: Really not that hard

Really, getting a good widget system working is fairly simple, and doesn’t require a lot of backend programming, but you need to have a solid idea of what you want to display in your sidebar, and how you want to organize the data.

Remember that your widget data is usually supplemental to your page’s message, and you shouldn’t waste precious processor cycles or memory with a high-overhead system when a simple switch block will do the trick.

Bonus Chatter: Doing it all in one stroke

So, let’s say you don’t like having all those file calls and separation of files: if you want, you can do it all in one “controller” element.

So, like before, we have the same element setup

  • /elements
    • /widgets
      • whos_online.ctp
      • latest_articles.ctp
      • latest_posts.ctp
      • upgrade_now.ctp
      • post_edit.ctp
    • sidebar_rt.ctp

And in the layout, we’re just going to call the simple one liner:

// /layouts/default.ctp
<div class="sidebar right">
     <?= $this->element('sidebar_rt'); ?>
</div>

And then in the sidebar_rt.ctp file, we include a long switch statement

// /elements/sidebar_rt.ctp

// Include the widget that will always be shown
echo $this->element('widgets/latest_articles');

// Include the widgets that are shown only if the user is logged in
if( !empty( $_user_info ) ){
   echo $this->element('widgets/whos_online');
}

// Include the widgets for each controller / action page
switch( $this-> name ){
   case 'users':
      echo $this->element('widgets/upgrade_now');
      break;
   case 'forums':
      echo $this->element('widgets/latest_posts');
      break;
   case 'posts':
      switch( $this->action ){
         case 'edit':
            echo $this->element('widgets/post_edit');
            break;
      }
      echo $this->element('widgets/upgrade_now');
      break;
   default':
      echo $this->element('widgets/upgrade_now');
      break;

}

It’s a little more unwieldy than the separated version, but if you only have a few controllers that have special cases it might be easier to have all your widget data in one place.

Have any comments? Know of a better way? I’d love to hear them!

Advertisement

1 comment

  1. rathika says:

    In your element/widgets folder you have created the whos_online.ctp file, i want to know,how to create a database access in that ctp file. advice me.

Leave a Reply