Prototip Helper

By Andy Leeb (andy)
This is a helper for people using prototip. Prototip is available here: http://www.nickstakenburg.com/projects/prototip/files/prototip1.1.0.zip
The provided code was written for the 1.1.x branch of CakePHP. I would imagine that it would easily be updated for 1.2.x
There are three public functions in this class:
1. $prototip->tooltip()
2. $prototip->addTooltip()
3. $prototip->renderTooltips()

A single private function parses argument arrays:
$this->_parseOptions()

There are three arguments to pass to the tooltip() function: el the element, content the content of the tooltip, and an optional options array.

tooltip() simply adds a single toolip for the element specified in the argument. It automagically detects if it should render a simple tooltip, 2 arguments provided, or a fancy tooltip, 3 arguments provided.

addTooltip() adds a tooltip to an internal array, but does not render it to screen until told to do so. This is helpful if you are generating tooltips for each row in a database in an element, but wish to render all tooltips in a single script tag.

Update 03/02/08 - modified to allow any DOM element as the first argument for both tooltip() and addTootip(). (See code below).

renderTooltips() renders all tooltips added to the internal array via addTooltip() in a single script tag.

When using this helper in ajax callbacks, it's best to use the addTooltip() + renderTootlip() combination in your calling code, and in the callback update the tooltip with tooltip().

Here is the code:
Download code
<?php
class PrototipHelper extends Helper {

    
/**
     * @link http://www.nickstakenburg.com/projects/prototip/ the js library this helper works with
     * @var Array a flat array of the allowed options
     */
    
var $allowed_options = array('className','closeButton','duration','delay','effect','fixed','hideOn','hook','offset','showOn','target','title','viewport');

    
/**
     * @var Array a flat array with the special case option names
     */
     
var $sc_options = array('hook','offset','hideOn');

    
/**
     * @var Array holds the basic string for creating a new Tooltip
     */
    
var $tooltip = array('base'=>"new Tip('%s',%s);"'fancy'=>"new Tip('%s',%s,%s);");

    
/**
     * @var Array cakephp helpers used by this helper
     */
    
var $helpers = array('Html''Javascript');

    
/**
     * @var Array holds tooltips to be rendered in a block
     */
    
var $tips = array();

    
/**
     * @public
     *
     * @param String    $el                the id of the element
     * @param String    $content        html to show in the tip
     * @param Array        $options        the tooltip options
     *
     * @return String a formatted tooltip instantiation
     */
    
function tooltip($el,$content,$options=array()) {
        if ( 
substr($content,0,1)!='$'$content "'$content'";
        if(
$options) {
            return 
$this->Javascript->codeBlock($this->output(sprintf($this->tooltip['fancy'],$el,$content,$this->_parseOptions($options))));
        } else {
            return 
$this->Javascript->codeBlock($this->output(sprintf($this->tooltip['base'],$el,$content)));
        }
    }

    
/**
     * @public
     *
     * @param String    $el                the id of the element
     * @param String    $content        html to show in the tip
     * @param Array        $options        the tooltip options
     *
     * Adds a formatted tooltip to the $tips array
     */
    
function addTooltip($el$content$options=array()) {
        if ( 
substr($content,0,1)!='$'$content "'$content'";
        if(
$options) {
            
$this->tips[] = sprintf($this->tooltip['fancy'],$el,$content,$this->_parseOptions($options));
        } else {
            
$this->tips[] = sprintf($this->tooltip['base'],$el,$content);
        }
    }

    
/**
     * @public
     *
     * Renders the array of tooltips in $tips as a Javascript code block
     */
    
function renderTooltips() {
        
$tips_string "<script type=\"text/javascript\">\n";
        foreach(
$this->tips as $tip) {
            
$tips_string .= $tip "\n";
        }
        
$tips_string .= '</script>';
        return 
$this->output($tips_string);
    }

    
/**
     * @private
     *
     * @param Array $options an array of the options available to prototip
     *
     * @return String a formatted string of options i.e. {'opt':'value'...}
     */
    
function _parseOptions($options=array()) {
        
$opts "{";
        
$arr_opts = array();
        foreach(
$options as $key => $value) {
            if(
in_array($key,$this->allowed_options)) {
                if(
in_array($key$this->sc_options)) { //special case for formatting options
                    
if(strpos($value'{') !== false) { // the option has a tuple...e.g. 'hook:{target:'topLeft',tip:'rightMiddle'}
                        
$sc explode(',' ,$value);
                        
$str_sc_opts "'$key':";
                        
$sc_arr_opts = array();
                        foreach(
$sc as $opt => $val) {
                            
$sc_arr_opts[] = "$val";
                        }
                        
$arr_opts[] = $str_sc_opts join(","$sc_arr_opts);
                    }
                } else {
                    
$arr_opts[] = "'$key':'$value'";
                }
            }
        }
        
$opts .= join(","$arr_opts);
        
$opts .="}";
        return 
$opts;
    }
}

?>


NB: when using the prototip options that require a hash, you may need some funky quotes...e.g.Download code 'hook'=>"{'key':'value','key':'value'}"
unless you have numeric values, e.g.
Download code 'offset'=>"{'x':5,'y':10}" .
Loosey goosey scripting languages indeed.

 

Comments 584

CakePHP Team Comments Author Comments
 

Comment

1 Hack for ajax div problem

I had a problem where the prototip was used in an ajax div and when the imagelink with the prototip was reloaded the div wouldn't leave. So what I've done is added a function that runs at the start of renderTooltips to hide all the currently open divs. I don't know if it's of any use to anyone but I'm throwing it here. It works in Cake 1.2 as well.

Changes to renderTooltips()


    function renderTooltips($hideCurrent=false) {
        $tips_string = '<script type="text/javascript">';
        if($hideCurrent)
        $tips_string .= $this->_hideTips();
        foreach($this->tips as $tip) {
            $tips_string .= $tip . "\n";
        }
        $tips_string .= '</script>';
        return $this->output($tips_string);
    }

Add the following code at the bottom of the helper.


    function _hideTips(){
        
        $str = "for (i = 0; i < Tips.tips.length; ++i) {";
        $str .= "Tips.tips.hide();";
        $str .= "}";
        return $str;

    }

And to force a hide on all tips on the renderTooltips change


<?php echo $prototip->renderTooltips(); ?>

To


<?php echo $prototip->renderTooltips(true); ?>

Hope I didn't miss anything...
Posted Feb 15, 2008 by Jeff
 

Comment

2 Suggested change

I like this helper, and am modifying it to better fit my needs for the newer version of Prototip and Cake 1.2.

I like the $allowed_options array as a sanity check, but I don't really like how static-feeling the 'Special Case' options array is.

As a result, rather than parsing certain options as special cases, I decided to accept all options as nested arrays. This code might need a bit of work, but here is a changed _parseOptions function with the recursive _parseValues function that *should* allow infinitely deep option arrays for Prototip regardless of the option.

The quotes also looked slightly confusing so I added a check if the value is an integer, and added the quotes separately only if it is not.

PHP Snippet:

<?php function _parseOptions($options = array()) {
    
$opts '{ ';
    
$arr_opts = array();
    foreach(
$options as $key => $value) {
        if (
in_array($key$this->allowed_options)) {
            
$value _parseValues($value);
            
$arr_opts[] = "$key: $value";
        }
    }
    
$opts .= join(","$arr_opts);
    
$opts .= ' }';
    return 
$opts;
}

function 
_parseValues($values null) {
    if (!
is_array($values)) {
        if (!
is_int($values)) $values "'$values'";
        return 
$values;
    }
    
$value '{ ';
    
$val_opts = array();
    foreach(
$values as $key=>$val) {
        if (
is_array($val)) {
            
$val $this->_parseValues($val);
        }
        if (!
is_int($val)) $val "'$val'";
        
$val_opts[] = "$key: $val";
    }
    
$value .= join(','$val_opts);
    
$value .= ' }';
    return 
$value;
}
?>
Posted Sep 1, 2008 by Ben McClure
 

Comment

3 Rewritten and expanded

I made some further changes to the helper and rewrote most of it to fit my needs again.

You can add other options to the options array and not worry about them getting included in the actual tooltip. I used this to combine the tooltip and addTooltip functions by parsing a 'render' option in the options array. Now if $options['render'] == true, the tooptip will be rendered. Else it will be stored in the tips array.

I didn't explain how to specify the options array last time. Here it is:
THIS:
option1: value1,
option2: { opt1: { op: va }, opt2: val2 }

BECOMES THIS:
array(
'option1' => 'value1',
'option2' => array(
'opt1' => array(
'op' => 'va'
),
'opt2' => 'val2'
)
)

Here is my rewritten function with stripped comments to save space. This needs further testing.

PHP Snippet:

<?php class PrototipHelper extends Helper {
    var 
$helpers = array('Html''Javascript');
    
    var 
$allowed_options = array('ajax''border''borderColor''closeButton''delay''hideAfter''hideOn''hideOthers''hook''images''offset''radius''showOn''stem''style''target''title''viewport''width');

    var 
$tips = array();

    function 
tooltip($el$content$options = array()) {
        if (
$options['render'] == true) {
            return 
$this->Javascript->codeBlock($this->output(_createTip($el$content$options);));
        } else {
            
$this->tips[] = _createTip($el$content$options);
        }
    }

    function 
renderTooltips() {
        
$tips_string '';
        foreach(
$this->tips as $tip) {
            
$tips_string .= $tip."\n";
        }
        return 
$this->JavaScript->codeBlock($tips_string);
    }

    function 
_createTip($el$content$options = array()) {
        
$valid_el = array('\'''$');
        if (!
in_array(substr($el01), $valid_el)) $el "'$el'";
        if (
substr($content01) != '\'' &&
            
substr($content04) != 'new '$content "'$content'";
        if (
$options) {
            return 
'new Tip('.$el.', '.$content.', '.$this->_parseOptions($options).');';
        } else {
            return 
'new Tip('.$el.', '.$content');';
        }
    }

    function 
_parseOptions($options = array()) {
        
$opts '{ ';
        
$arr_opts = array();
        foreach(
$options as $key => $value) {
            if (
in_array($key$this->allowed_options)) {
                
$value _parseValues($value);
                
$arr_opts[] = "$key: $value";
            }
        }
        
$opts .= join(","$arr_opts);
        
$opts .= ' }';
        return 
$opts;
    }
    
    function 
_parseValues($values null) {
        if (!
is_array($values)) {
            if (!
is_int($values)) $values "'$values'";
            return 
$values;
        }
        
$value '{ ';
        
$val_opts = array();
        foreach(
$values as $key=>$val) {
            if (
is_array($val)) {
                
$val $this->_parseValues($val);
            }
            if (!
is_int($val)) $val "'$val'";
            
$val_opts[] = "$key: $val";
        }
        
$value .= join(','$val_opts);
        
$value .= ' }';
        return 
$value;
    }
?>
Posted Sep 1, 2008 by Ben McClure
 

Comment

4 Sloppy code fix from my last comment

Sorry for triple-posting. This site will unfortunately not allow me to edit my comments. I had a few coding errors in my rewrite. Here is the fixed code which is working fine for me so far:

PHP Snippet:

<?php class PrototipHelper extends Helper {
    var 
$helpers = array('Html''Javascript');
    
    var 
$allowed_options = array('ajax''border''borderColor''closeButton''delay''hideAfter''hideOn''hideOthers''hook''images''offset''radius''showOn''stem''style''target''title''viewport''width');

    var 
$tips = array();

    function 
tooltip($el$content$options = array()) {
        if (
$options['render'] == true) {
            return 
$this->Javascript->codeBlock($this->output($this->_createTip($el$content$options)));
        } else {
            
$this->tips[] = _createTip($el$content$options);
        }
    }

    function 
renderTooltips() {
        
$tips_string '';
        foreach(
$this->tips as $tip) {
            
$tips_string .= $tip."\n";
        }
        return 
$this->JavaScript->codeBlock($tips_string);
    }

    function 
_createTip($el$content$options = array()) {
        
$valid_el = array('\'''$');
        if (!
in_array(substr($el01), $valid_el)) $el "'$el'";
        if (
substr($content01) != '\'' &&
            
substr($content04) != 'new '$content "'$content'";
        if (
$options) {
            return 
'new Tip('.$el.', '.$content.', '.$this->_parseOptions($options).');';
        } else {
            return 
'new Tip('.$el.', '.$content.');';
        }
    }

    function 
_parseOptions($options = array()) {
        
$opts '{ ';
        
$arr_opts = array();
        foreach(
$options as $key => $value) {
            if (
in_array($key$this->allowed_options)) {
                
$value $this->_parseValues($value);
                
$arr_opts[] = "$key: $value";
            }
        }
        
$opts .= join(","$arr_opts);
        
$opts .= ' }';
        return 
$opts;
    }
    
    function 
_parseValues($values null) {
        if (!
is_array($values)) {
            if (!
is_int($values)) $values "'$values'";
            return 
$values;
        }
        
$value '{ ';
        
$val_opts = array();
        foreach(
$values as $key=>$val) {
            if (
is_array($val)) {
                
$val $this->_parseValues($val);
            }
            if (!
is_int($val)) $val "'$val'";
            
$val_opts[] = "$key: $val";
        }
        
$value .= join(','$val_opts);
        
$value .= ' }';
        return 
$value;
    }
}
?>
Posted Sep 1, 2008 by Ben McClure
 

Comment

5 Another enhancement

I couldn't help myself. To make things even simpler, I created an autoRender function in the PrototipHelper.

Calling it like this would automatically create tooltips for all links with 'rel' elements:

PHP Snippet:

<?php echo $prototip->autoRender('a[rel]''rel');?>
And the code:

PHP Snippet:

<?php function autoRender($cssSelector$tipField) {
    return 
$this->Javascript->codeBlock(
        
$this->output(
            
'document.observe(\'dom:loaded\', function() {
                $$(\''
.$cssSelector.'\').each(function(element) {
                    new Tip(element, element.'
.$tipField.');
                });
            });'
        
)
    );
}
?>
Posted Sep 1, 2008 by Ben McClure