Elemental: Conditional Content with CakePHP
I finally got around to implementing conditional content here on Snook.ca. I've wanted to do this since I moved to the new design just over a month and a half ago. The left sidebar has a number of elements like the projects I've worked, quick links, etc. As some people have noticed, if the article was particularly short, the comments would often overlap the rather lengthy content. I didn't want that.
With a little bit of late night development and some quick help from Larry (aka PhpNut, the lead developer of CakePHP), I've developed what I have dubbed "Elemental".
Elemental
Elemental consists of 2 parts: some new controller methods added to app_controller.php
, and a new helper called Elemental.
In app_controller.php add:
function enableComponent($component, $params=null)
{
$name = Inflector::camelize($component);
$found = loadComponent($name);
if($found)
{
$cn = $name . 'Component';
$comp = new $cn($params);
$comp->startup($this);
$this->viewVars['elements'][$component] = true;
}
}
function enableElement($element)
{
$this->viewVars['elements'][$element] = true;
}
function disableElement($element)
{
$this->viewVars['elements'][$element] = false;
}
The methods enableElement
and disableElement will turn on or off whether a particular element should be displayed in the view. The method enableComponent
takes the extra step and conditionally loads a component and enables the related element that goes along with that.
To use the element in your view just do:
echo $elemental->load('projects');
In this particular example, it calls the projects.ctp
element. This should work exactly like $this->renderElement
but will only load if the element has been enabled using enableElement
or enableComponent
..
Here's the Elemental helper:
<?php
class ElementalHelper extends Helper {
function load($element, $params=array())
{
$view =& ClassRegistry::getObject('view');
if(isset($view->viewVars['elements']) &&
isset($view->viewVars['elements'][$element]) &&
$view->viewVars['elements'][$element])
{
return $view->renderElement($element, $params);
}
}
}
?>
Using it on Snook.ca
What I've done on Snook.ca then, is to break up the sidebar into a few different elements. All elements are enabled by default via the app_controller. Then, in my posts controller, I check the length of the body element and disable elements as the article gets shorter. Here are some examples:
- Long article shows all elements
- Medium-sized article shows just some of the elements
- Short article shows no elements
I can now keep the sidebars light and avoid overlap or throw in some bonus material if the article is longer.
Conversation
Hey thanks for sharing this. However, I still am little confused wrt renderElement vs your enableElement. renderElement can also do choicing out of content based on our conditions...do you mean to say, it will still load those elements but then just not display them and your enableElement will not even load them? Like how is it diff from -
This is so cool - I can think of combining it with some other stuff out there and we have got truely dynamic or contextual sidebars
Thanks for the efforts
@Mandy: Sure, renderElement vs enableElement may seem trivial but I dislike having to put conditionals around everything in my view. Being able to easily turn it on or off via the controller is nice, leaving my views really clean. Think about using this for displaying admin menus or what have you. In addition, using enableComponent is the sweetest part as components get loaded only when needed and allows you to link a component with an element and have both "turned on" via one command. That's why I like it. :)
Looks good Snook. I agree with you, I would rather have the logic for this inside of my controllers versus the view. I despise using conditional statements in my views (regardless of the framework).
I have found myself using custom helpers for some things. For things like alternating and such - I create a custom method in my helpers to display thing accordingly (some of this might be included in an element). This is usually for things that are nested in a loop and cannot be achieved in the controller (without running through result sets and assigning new arrays).
I like how you have it check the length of the content and only include as necessary - keeps your frontend layout nice and clean as well! Nice work...
This is really neat, Jonathan. I have an ongoing project where I've managed to do without using conditionals in my views by setting variables to empty strings and still echoing them in my views. No doubts you've shared a neater approach and I'm going to revise my code :-)
Thanks!
Hi Jonathan,
I like the freedom/cleanliness of what you propose here, I'm curious as to what your components look like, and what convention, if any, you follow for the data that you want to pass from your components to your elements. There is the risk for element and main content vars to overlap/conflict if there is no active separation.
I wrote about something similar in the past, in which I always rendered a side_bar element which would loop on the defined/enabled dynamic elements - didn't seem perfect at the time since the side_bar element contained a bit too much logic. Making use of a helper seems a good step forwards.... I think I feel a refactoring coming on :)
Cheers,
AD
@Andy: currently, I don't have any convention. I suppose, like this Elemental script, I should contain it within a single namespace of sorts. Now that I have this script, I'm more likely to use it because of the way it easily ties components and elements together. Otherwise, conflict is always possible and hopefully your naming convention is clear and unique enough not to be a problem.
I can see using this in a CMS, allowing the user to define what elements show in a sidebar (similar to the sidebar widgets in wordpress 2.2). It would be a pretty simple modification to make. Thanks again for the inspiration!
Great piece of information. Thanks for putting this out there for the cake community.
Interesting approach. I like it :)
What do think about assigning the params for renderElement from within the enable method? Saving you the $this->set() ..
f.e. $this->enableElement('projects', $projectData);
in the helper.. $view->viewVars['elements'][$element] = array( with the data ) = $params
@Kjell Bublitz: That is a good idea. I like it! I'm also thinking of looking into turning the controller methods into a component of its own so that it's more "drag and drop" for any project. If I get to that, I'll see about submitting it to the Bakery.
I have implemented something similar. I've added a table to the database to maintain sidebar blocks. Each record contains, controller, action, helper, helper_method, sort_order. In app_controller.php, in beforeRender, I query the database to get a list of blocks based on $this->params. Derived controllers can override this behavior by setting a flag (renderBlocks). Helper::helper_method gets called from app/views/layout/mysite.thtml
Found this copy of your article elsewhere, with no attribution, thought you might want to know. Thanks for the original one though... (spaces etc inserted into link on purpose)
http://www dot designfloat dot com / Programming / Elemental_Conditional_Content_with_CakePHP/