jump to navigation

Custom HTML Tags (WidgetHelper) June 20, 2006

Posted by rossoft in CakePHP.
trackback

Do you want create custom helpers accessible in your views like:

Example of HighlightWidget:
<h:span class="high" search="/I'm \w+/">
          Hi, I'm John. What's your name? I'm Marcus.
</h:span>

And it will output the html:
Hi, <span class="high">I'm john</span>. What's your name?

Example of RepeaterWidget:
<?php $array=array('Frodo'=>'Elijah Wood','Aragorn'=>'Viggo Mortensen')
      $repeaterWidget->bind('cast',$array))?>
  <repeater:repeater array='cast'>
      <b><repeater:key/>:</b> <repeater:value/>
      <br/>
  </repeater>

 
Will output the html:
     <b>Frodo:</b>: Elijah Wood
     <br/>
     <b>Aragorn:</b>: Viggo Mortensen
     <br/>

The usage is really easy. The configuration is the same as a normal helper,
only include in your controller $helpers=array('xxx',…,'RepeaterWidget','HighlightWidget');
And the definition of new Widgets is easy too, see the Highlight & Repeater code:
HighlightWidgetHelper:

<?php
/**
 * HighlightWidget Helper
 *
 * Highlights some expression found in a html text.
 *
 * Example:
 * In style declaration:
 *         .high { color: yellow; }
 * In view:
 * <h:span class="high" search="/I'm \w+/">
 *         Hi, I'm john. What's your name?
 * </h:h>
 *
 * Will output the html:
 * Hi, <span class="high">I'm john</span>. What's your name?
 *
 * @author RosSoft
 * @version 0.1
 * @license MIT
 *
 */
require_once(dirname(__FILE__) . DS . 'widget_helper.php');
class HighlightWidgetHelper extends WidgetHelper
{
    //main tags name => array subtags
    var $tag=array('h:div','h:span');
    
    function tag_h_div($attr,$inner_html)
    {
        return $this->replace($attr,$inner_html,'div');
    }        
    
    function tag_h_span($attr,$inner_html)
    {
        return $this->replace($attr,$inner_html,'span');
    }
    
    function replace($attr,$inner_html,$type)
    {
        $class=@$attr['class'];
        $expression=@$attr['search'];
        return preg_replace($expression,"<$type class=\"$class\">" . '\' . "</$type>",$inner_html);        
    }
    
}
?>

RepeaterWidgetHelper:

<?php
/**
 * RepeaterWidget Helper
 *
 * Repeats the html within the tag <repeater:repeater>HTML</repeater:repeater>
 * that contains the subtags <repeater:key /> and <repeater:value />
 * It replaces the subtags with the key & value from an associative array.
 * You must bind the widget to the array through
 * Example:
 * <?php
 * $array=array('Frodo'=>'Elijah Wood','Aragorn'=>'Viggo Mortensen')
 * $repeaterWidget->bind('cast',$array))?> *
 * <repeater:repeater array='cast'>
 *     <b><repeater:key/>:</b> <repeater:value/>
 *     <br/>
 * </repeater>
 *
 * Will output the html:
 *     <b>Frodo:</b>: Elijah Wood
 *     <br/>
 *     <b>Aragorn:</b>: Viggo Mortensen
 *     <br/>
 *
 * @author RosSoft
 * @version 0.1
 * @license MIT
 *
 * @link http://www.phpit.net/article/create-html-widgets-php/4/
 */
require_once(dirname(__FILE__) . DS . 'widget_helper.php');
class RepeaterWidgetHelper extends WidgetHelper
{
    //main tags name => array subtags
    var $tag=array(
                'repeater:repeater'=>array('repeater:key','repeater:value')
            );
    
    function tag_repeater_repeater($attr,$inner_html)
    {
        $array=$this->_get_bound($attr['array']);        

        // loop through bound array
        $new_html = '';
        foreach ($array as $k=> $v)
        {
            $new_html .= $this->_process_subtags(array('key'=>$k,'value'=>$v));
        }
        return $new_html;
    }
    
    function subtag_repeater_key($attr,$inner_html,$params)
    {
        return $params['key'];
    }
    
    function subtag_repeater_value($attr,$inner_html,$params)
    {
        return $params['value'];
    }
}
?>

The widgets are helpers, then you can use others helpers with $helpers variable, also
you can call a function of them with $highlightWidget->somefunction() like before.

The core class is WidgetHelper. That class must be extended in your widgets (you must do
a include_once for this).

Put in app/views/helpers/widget_helper.php (it ends in _helper) :

<?php
/**
 * Widget Helper
 *
 * Parses custom html tags like
 * <mytag:mytag1 attr1="value1" attr2="value2">some html</mytag:mytag1>
 * (This class must be extended)
 *
 * @author RosSoft
 * @version 0.1
 * @license MIT
 *
 * The filename is widget_helper.php because you must
 * overload it, not use directly.
 *
 */
 
class WidgetHelper extends Helper
{
    var $_library; //static array of widgets instances
    
    //main tag names => array subtags
    var $tag=array();
    var $_data=array();
    
    var $helpers=array('Util');
    
    function __construct()
    {
        static $library=array();  //for php4 compat
           $this->_library=& $library;           
           
           $this->_library[]=& $this; //add current instance
        
        if (!in_array('Util',$this->helpers))
        {
            array_push($this->helpers,'Util');
        }
    }
    
    /**
     * afterRender Callback
     */
    function afterRender()
    {
        static $once=true;
        if (! $once) return;
        $once=false;
        
        $html=@ob_get_clean();
        ob_start();
        $matches=array();
        
        preg_match_all("/(<(\w+:\w+)([^>]*)>(.*?)<\/\\2>)|(<(\w+:\w+)([^>]*)\/>())/is", $html, $matches,PREG_SET_ORDER);
        foreach ($matches as $match)
        {
            if (isset($match[6]))
            {
                $tagname=$match[6];
                $attributes=$match[7];
                $inner_html=$match[8];
            }
            else
            {
                $tagname=$match[2];
                $attributes=$match[3];
                $inner_html=$match[4];                
            }
            $outer_html=$match[0];
            foreach ($this->_library as $widget)
            {
                foreach ($widget->tag as $tag=>$subtags)
                {
                    if (is_numeric($tag))
                    {
                        $tag=$subtags;
                        $subtags=array();
                    }
                    if ($tag==$tagname)
                    {
                        $html=$widget->_process_tag($html, $tag, $outer_html,$inner_html,$attributes,$subtags);
                    }
                }
            }
        }
        echo $html;
    }

    /**
     * Bind data to the widget
     *
     * @param string $name Name of the data
     * @param mixed $data The data
     */    
    function bind($name, $data)
    {
        $this->_data[$name] = $data;
    }

    /**
     * Process one tag
     * @param string $content The complete document
     * @param string $tag_html One tag
     */
    function _process_tag($html, $tag_id,$outer_html,$inner_html,$attributes,$subtags)
    {
        // Parse tag
        $attributes= $this->_parse_attributes($attributes);        

        $this->_tag_id=$tag_id;
        $this->_tag_inner_html=$inner_html;
        $this->_tag_subtags=$subtags;

        $method_name='tag_' . str_replace(':','_',$tag_id);
        $out=$this->$method_name($attributes,$inner_html);
        if ($out!==false)
        {
            return str_replace($outer_html,$out, $html);
        }
        else
        {
            //show the error through html entities
            return str_replace($outer_html,h($outer_html), $html);
        }
    }
    
    function _get_bound($name)
    {
        return $this->_data[$name];
    }
    
    function _get_field_value($fieldName)
    {
        return $this->Util->retrieve_value($fieldName);    
    }
    
    function _get_view_value($var_name)
    {
        return $this->view->_viewVars[$var_name];
    }

    function _process_subtags($params=array(),$html=null)
    {
        if ($html===null)
        {
            $html=$this->_tag_inner_html;
        }
        foreach ($this->_tag_subtags as $subtag)
        {
            preg_match_all("/(\<$subtag([^>]*)\>(.*?)\<\/$subtag\>)|(\<$subtag([^>]*)\/\>())/is", $html, $matches,PREG_SET_ORDER);
            for ($i=0; $i < count($matches); $i++)            
            {
                $outer_html= $matches[$i][0];
                if (isset($matches[$i][5]))
                {
                    $attributes=$matches[$i][5];
                }
                else
                {
                    $attributes=$matches[$i][2];
                }
                $attributes=$this->_parse_attributes($attributes);                                
                
                $inner_html= $matches[$i][3];
                $method_name='subtag_' . str_replace(':','_',$subtag);
                $out=$this->$method_name($attributes,$inner_html,$params);
                if ($out!==false)
                {                                
                    $html= str_replace($outer_html,  $out, $html);
                }
                else
                {
                    //show the error through html entities
                    $html= str_replace($outer_html,  h($out), $html);
                }                
            }
                                    
        }
        return $html;
    }
    
    function _parse_attributes($html)
    {
        $matches=array();
        $attributes=array();
        preg_match_all('/(\w+)\s*=\s*"([^"]*)"/is', $html, $matches,PREG_SET_ORDER);
        foreach ($matches as $attr)
        {
            $attributes[low($attr[1])]=$attr[2];
        }
        return $attributes;
    }          
                      
}
?>

Comments»

1. olivvv - June 20, 2006

Looks like this could help all the peoples that are making wizards, survey tool and other form generators, isn’t it ?

2. rossoft - June 20, 2006

yeah, I’m starting the FormWidget

3. mrjazz - June 23, 2006

I think use attribute bind in repeater tag repeater:

:

And rewrite tag_repeater_repeater:

function tag_repeater_repeater($attr,$inner_html)
{
if($attr[‘bind’] != ”) {
$array=$this->view->_viewVars[$attr[‘bind’]];
} else {
$array=$this->_get_bound($attr[‘array’]);
}

// loop through bound array
$new_html = ”;
foreach ($array as $k=> $v)
{
$new_html .= $this->_process_subtags(array(‘key’=>$k,’value’=>$v));
}
return $new_html;
}

4. rossoft - June 23, 2006

yes, that’s good idea, thanks. But remember that RepeaterWidget & HighlightWidget are stupid examples, the useful thing is the HelperWidget

5. Jon Baer - June 26, 2006

Is there an easy way for the widget to simple render an element w/ its attributes as parameters?

6. rossoft - June 26, 2006

yes: a helper with 2 ways: using or

( the function tag_element_sample has the name of the element to render )

require_once(dirname(__FILE__) . DS . ‘widget_helper.php’);
class ElementsWidgetHelper extends WidgetHelper
{
//main tags name => array subtags
var $tag=array(‘element:element’,’element:sample’);

//example:
function tag_element_element($attr,$inner_html)
{
$name=$attr[‘name’];
unset($attr[‘name’]);
return $this->view->renderElement($name,$attr);
}

//example:
function tag_element_sample($attr,$inner_html)
{
$name=’my_element’;
return $this->view->renderElement($name,$attr);
}
}

7. rossoft - June 26, 2006

example: <element:element name=”my_element” ar1=”hello world” />

and
<element:sample var1=”hello world” />

8. Jon Baer - June 27, 2006

Now *that* is what I call slick … thank you!

(That should be included as a snippet on cakeforge by itself, the ElementWidget)

9. rossoft - June 27, 2006
10. Anonymous - July 1, 2006
11. Lyubomir Petrov - July 18, 2006

You have an bug in the example in hightlightwidget:
* In view:
*
* Hi, I’m john. What’s your name?
* ()
This safes some times for sleepy devs like me 😉

12. Lyubomir Petrov - July 18, 2006

Oups, sorrye posted HTML tags up.
You have an bug in the example in hightlightwidget:
* In view:
* [h:span class=”high” search=”/I’m \w+/”]
* Hi, I’m john. What’s your name?
* [/h:h] (this must be: [/h:span])
This safes some times for sleepy devs like me

13. sam.benskin.co.uk » Archives » Can a slice of CakePHP save me? - August 1, 2006

[…] What does this mean for the end user, i hear you cry? Well, as Miguel Ros demonstrates in his fantastic example, Custom HTML Tags, it seems to be very possible to write your very own markup, and have CakePHP transform it into valid HTML.  This would allow someone to create a library of custom tags, that could be changed into HTML that has specific styles and layouts already associated with it.  For example, a custom div with four containing divs could be transformed into a Tab Pane layout, with two divs being tabs, and two being the content in those tabs, with the main div controling the size and postion and so on. […]

14. Christian - August 5, 2007

In one of the last lines of the HighlightWidgetHelper, there is a syntax error. Replacement with the following code solves the issue:

return preg_replace($expression,”I’m \\1″,$inner_html);

15. new - November 4, 2007

how to output in php html tags like that

Test

16. jubobeven - August 3, 2008

I agreed with you

17. ooopinionsss - December 3, 2008

How you think when the economic crisis will end? I wish to make statistics of independent opinions!


Leave a comment