RendElements
Component ID
Component name
Component type
Maintenance status
Development status
Component security advisory coverage
Component created
Component changed
Component body
RendElements is a php class for working with Drupal Render Arrays. This is not a theme and it falls outside of the usual project types so this sandbox will be short lived and this project will be part of other themes I work on.
Read Young Hahn's post on the limitations of the theming layer. Especially the part on rules based theming. This isn't the complete solution but a small step in that direction enabled by render arrays.
A demonstration can be seen on Vimeo.
First steps:
Get a copy from the terminal:
cd /to/your/theme
git clone http://git.drupal.org/sandbox/dvessel/1122312.git RendElements
Include the classes from your template.php file:
include_once "RendElements/RendFilter.inc";
include_once "RendElements/RendElements.inc";
Basic usage:
@see render()
@see hide()
@see show()
// Instantiate the class:
$page = new RendElements($page);
// Render target:
$page->render('content');
// Hide all blocks within sidebar_first region:
$page->hide('sidebar_first > *');
// Show odd blocks within the same region:
$page->show('sidebar_first > *:odd');
// Renders non-hidden or even blocks:
$page->render('sidebar_first');
Other supporting methods:
// Check for non-empty elements:
$page->has('footer');
// Check for the existences of elements:
$page->exists('footer');
The ->has() and ->exists() methods differ in a way that is similar to the PHP functions !empty() vs. isset().
Selector support:
RendElements works through select strings or queries to target specific elements. It currently supports the following.
general selectors:
element_key
#id
(very limited since it is seldom part of the array #attributes).class
(must be part of render array #attributes)*
(universal, optional)
selection delimiters:
>
immediate descendant~
general sibling+
adjacent sibling
attribute selectors (applies to Drupal array properties):
@see http://www.w3.org/TR/css3-selectors/#attribute-selectors
@see http://api.drupal.org/api/drupal/developer--topics--forms_api_reference....
Note: The #hash prefix should not be included in the attribute selector.
[attribute]
(checks existence)[attribute=equals]
[attribute^=starts with]
[attribute*=contains]
[attribute$=ends with]
[attribute~=space separated]
[attribute[sub-property]]
[attribute[sub-property]=sub-value]
The last two selectors are custom. The first of the two checks for the existence of a sub-property, i.e., second level array or object properties while the second looks for a value in that sub-property. [attr[sub]]
equals ['#attr']['sub']
and [node[nid]]
equals ['#node']->nid
.
Boolean values can be checked with a lowercase 'true' or 'false', e.g., [printed=true]
.
For attribute values that are a part of a shallow array, each value from the array will be treated as space separated values. This allows it to work with all the operators.
For all the attribute selectors, the value can be quoted. Useful when whitespace needs to be exact. [attribute=" value "]
. The attribute and value can be spaced for readability but this would strip out any white spaces. [ attribute = value ]
.
pseudo selectors:
@see http://www.w3.org/TR/css3-selectors/#pseudo-classes
:root
:first-child
:last-child
:only-child
:nth-child(an+b|odd|even)
:nth-last-child(an+b|odd|even)
:odd
(custom shorthand for:nth-child(odd)
):even
(custom shorthand for:nth-child(even)
):empty
:not(arg)
(negation allows all the preceding simple selectors.)
Known issues:
How a selector is strung together has a limitation. Chaining together multiple types must be done in a specific order.
- universal
- key
- id
- class
- attribute
- pseudo
The above order is expected so key.class[attribute]
is okay while [attribute].class
is not even though it's perfectly valid in CSS.
It's also not possible to chain multiples of the same type. :nth-child(even):empty
will not produce any results.
[attr1]:not([attr2])
and :not(:empty)
will work since the negation pseudo selector has its own context to process another selector.
This could be an easy fix but my regex is still limited.
Notes on allowed behaviors and access:
The even though the page variable has been converted into an object from an array, it is still treated as an array in a lot of cases. RendElements is an extension of an ArrayObject. The only time the object will not work as an array is when accessing the element itself.
The following will work:
// accessing children through keys:
print render($page['content']);
// Adding to the array:
$page['new_section'] = $new_data;
// Looping:
foreach ($page as $key => $child) {
//...
}
This will cause an error:
// Passing the object itself into array functions.
array_pop($page);
// The ->root member to access the top level is okay.
array_pop($page->root);
// There are also built-in methods inherited from ArrayObject for common
// array manipulations. http://us.php.net/arrayobject
Notes on performance:
Although there are some optimizations, performance can suffer with generalized selectors. Most of the CPU time will be spent traversing the array for matches. Simple arrays are not a problem but it can add up on very complex render arrays. Use element keys and immediate descendent selectors to speed things up.
If you are sure you don't need to access children beyond a certain depth, use this method to limit it.
// Anything beyond 5 levels deep will be ignored:
$page->setMaxDepth(5);
// Remove max depth:
$page->setMaxDepth(FALSE);
Another way to speed up the process is to section off chunks of the array.
// Instantiate into chunks:
$header = new RendElements($page['header']);
$content = new RendElements($page['content']);
$sidebar_first = new RendElements($page['sidebar_first']);
$sidebar_second = new RendElements($page['sidebar_second']);
$footer = new RendElements($page['footer']);
// Narrowed scope for each query.
$content->render('node > *:odd > links);
This may be removed in the future but there's a timer you can read to monitor the time taken to execute a search in milliseconds. It is accumulative for all the searches performed so read it at the very bottom of your page.tpl.php file.
print timer_read('RendElements::executeSearch')
There is also a timer for the render call done through RendElements.
print timer_read('RendElements::render')