This is a simple macro in case you need to loop through a bunch of elements, without the need to use a foreach{}. There's support for Bootstrap class names: if your template's class attribute includes col-?-?, then this macro will add a "clear" tag to the end of each row, and also add a "row" class to the wrapper. To do (in a future release): pagination, multiple devices col- classes support.
// The magic:
HTML::macro('loop', function($query, $options = array())
{
// Default options
$options = array_merge(array(
'template' => null, // the template closure
'template_name' => null, // the template name
'wrapper_class' => '', // the wrapper class, in case 'wrapper' is not null
'wrapper' => 'ul', // wrapper tag; use null if you want no wrapper
'clear' => true, // in case out items float
'item_tag' => 'li', // the item tag
'item_data' => array(), // array data to be passed to the item method
'nothing' => 'No results.' // message if there's no results
), $options);
// If you are using Bootstrap, this pattern
// will detect if there is a "col-?" in our template's class attribute
// so we can add a "row" class to the item's container
$pattern = "/col\-[a-z]{2}\-(\d)/i";
$putRow = '';
// The loop :)
if($count = $query->count())
{
// The items collector
$items = array();
// Let's keep track of each item's index
// to pass it as argument to the template
$i = 0;
foreach($query as $row)
{
$i++;
// We can either have an item template passed as a Closure
// or has a method
$html = ($options['template'] ? $options['template']($row) : $row->item($options['template_name'], $options['item_tag'], $i, $options['item_data']));
// Collect the item
$items[] = $html;
if(preg_match($pattern, $html, $match)){
// If we get to the end of the row then add a clear tag
if($i == 12 / (int)$match[0] && $options['clear']){
$items[] = '<'.$options['item_tag'].' class="clear"></'.$options['item_tag'].'>';
$i = 0;
}
// Add a "row" class to the container in case the item
// has a Bootstrap column class name, like so "col-lg-6"
$putRow = 'row';
}
}
$itemsHtml = implode('', $items);
// Wrap them up
if($options['wrapper']){
$html = '<'.$options['wrapper'].' class="'.$options['wrapper_class'].' '.$putRow.'">'.$itemsHtml.'</'.$options['wrapper'].'>';
}else{
$html = $itemsHtml;
}
return $html;
}else
{
// Let's print a message in case there's nothing to show
return $options['nothing'];
}
return;
});
// In case 'template_name' => 'some_template_name',
// you should include an "item" method within your model, like so:
class News extends Eloquent
{
public function item($template = null, $tag = 'li', $i = 0, $data = array())
{
switch($template){
case 'edition':
return '
<'.$tag.'>
<h4>'.$this->title.'</h4>
<div class="actions pull-right">
'.(isset($data['show_delete'] && $data['show_delete'] == true) ? '<a href="#">Delete</a>' : '').'
</div>
</'.$tag.'>
';
break;
default:
return '
<'.$tag.'>
<p>'.$this->title.'</p>
<p>'.$this->created_at.'</p>
<p>'.$this->description.'</p>
</'.$tag.'>
';
break;
}
}
}
// Simple usage:
HTML::loop(News::all());
// More advanced usage:
HTML::loop(News::orderBy('created_at', 'asc')->get(), array(
'wrapper_class' => 'list-unstyled list-news',
'template_name' => 'edition',
'item_data' => array('show_delete' => true),
'item_tag' => 'div',
'nothing' => 'Nothing found. Sorry.'
));
// Using a closure as a template
HTML::loop(News::all(), array(
'template' => function($row){
return '<div>'.$row->title.'</div>';
}
));
// If you add a loop scope like so:
class News extends Eloquent
{
public function scopeLoop($query, $options)
{
return HTML::loop($query->get(), $options);
}
}
// ... you can then do:
News::loop();