Model::loop() - v1.0

Submitted by jplozano - 10 years ago

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();