javascript.php

Go to the documentation of this file.
00001 <?php
00002 /* SVN FILE: $Id: javascript_8php-source.html 580 2008-07-01 14:45:49Z gwoo $ */
00003 
00004 /**
00005  * Javascript Helper class file.
00006  *
00007  * PHP versions 4 and 5
00008  *
00009  * CakePHP(tm) :  Rapid Development Framework <http://www.cakephp.org/>
00010  * Copyright 2005-2008, Cake Software Foundation, Inc.
00011  *                              1785 E. Sahara Avenue, Suite 490-204
00012  *                              Las Vegas, Nevada 89104
00013  *
00014  * Licensed under The MIT License
00015  * Redistributions of files must retain the above copyright notice.
00016  *
00017  * @filesource
00018  * @copyright       Copyright 2005-2008, Cake Software Foundation, Inc.
00019  * @link                http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
00020  * @package         cake
00021  * @subpackage      cake.cake.libs.view.helpers
00022  * @since           CakePHP(tm) v 0.10.0.1076
00023  * @version         $Revision: 580 $
00024  * @modifiedby      $LastChangedBy: gwoo $
00025  * @lastmodified    $Date: 2008-07-01 09:45:49 -0500 (Tue, 01 Jul 2008) $
00026  * @license         http://www.opensource.org/licenses/mit-license.php The MIT License
00027  */
00028 
00029 /**
00030  * Javascript Helper class for easy use of JavaScript.
00031  *
00032  * JavascriptHelper encloses all methods needed while working with JavaScript.
00033  *
00034  * @package     cake
00035  * @subpackage  cake.cake.libs.view.helpers
00036  */
00037 
00038 class JavascriptHelper extends AppHelper {
00039 /**
00040  * Determines whether native JSON extension is used for encoding.  Set by object constructor.
00041  *
00042  * @var boolean
00043  * @access public
00044  */
00045     var $useNative = false;
00046 /**
00047  * If true, automatically writes events to the end of a script or to an external JavaScript file
00048  * at the end of page execution
00049  *
00050  * @var boolean
00051  * @access public
00052  */
00053     var $enabled = true;
00054 /**
00055  * Indicates whether <script /> blocks should be written 'safely,' i.e. wrapped in CDATA blocks
00056  *
00057  * @var boolean
00058  * @access public
00059  */
00060     var $safe = false;
00061 /**
00062  * HTML tags used by this helper.
00063  *
00064  * @var array
00065  * @access public
00066  */
00067     var $tags = array(
00068         'javascriptblock' => '<script type="text/javascript">%s</script>',
00069         'javascriptstart' => '<script type="text/javascript">',
00070         'javascriptlink' => '<script type="text/javascript" src="%s"></script>',
00071         'javascriptend' => '</script>'
00072     );
00073 /**
00074  * Holds options passed to codeBlock(), saved for when block is dumped to output
00075  *
00076  * @var array
00077  * @access protected
00078  * @see JavascriptHelper::codeBlock()
00079  */
00080     var $_blockOptions = array();
00081 /**
00082  * Caches events written by event() for output at the end of page execution
00083  *
00084  * @var array
00085  * @access protected
00086  * @see JavascriptHelper::event()
00087  */
00088     var $_cachedEvents = array();
00089 /**
00090  * Indicates whether generated events should be cached for later output (can be written at the end of the page,
00091  * in the <head />, or to an external file).
00092  *
00093  * @var boolean
00094  * @access protected
00095  * @see JavascriptHelper::event()
00096  * @see JavascriptHelper::writeEvents()
00097  */
00098     var $_cacheEvents = false;
00099 /**
00100  * Indicates whether cached events should be written to an external file
00101  *
00102  * @var boolean
00103  * @access protected
00104  * @see JavascriptHelper::event()
00105  * @see JavascriptHelper::writeEvents()
00106  */
00107     var $_cacheToFile = false;
00108 /**
00109  * Indicates whether *all* generated JavaScript should be cached for later output
00110  *
00111  * @var boolean
00112  * @access protected
00113  * @see JavascriptHelper::codeBlock()
00114  * @see JavascriptHelper::blockEnd()
00115  */
00116     var $_cacheAll = false;
00117 /**
00118  * Contains event rules attached with CSS selectors.  Used with the event:Selectors JavaScript library.
00119  *
00120  * @var array
00121  * @access protected
00122  * @see JavascriptHelper::event()
00123  * @link http://alternateidea.com/event-selectors/
00124  */
00125     var $_rules = array();
00126 /**
00127  * @var string
00128  * @access private
00129  */
00130     var $__scriptBuffer = null;
00131 /**
00132  * Constructor. Checks for presence of native PHP JSON extension to use for object encoding
00133  *
00134  * @access public
00135  */
00136     function __construct($options = array()) {
00137         if (!empty($options)) {
00138             foreach ($options as $key => $val) {
00139                 if (is_numeric($key)) {
00140                     $key = $val;
00141                     $val = true;
00142                 }
00143                 switch ($key) {
00144                     case 'cache':
00145 
00146                     break;
00147                     case 'safe':
00148                         $this->safe = $val;
00149                     break;
00150                 }
00151             }
00152         }
00153         $this->useNative = function_exists('json_encode');
00154         return parent::__construct($options);
00155     }
00156 /**
00157  * Returns a JavaScript script tag.
00158  *
00159  * @param string $script The JavaScript to be wrapped in SCRIPT tags.
00160  * @param array $options Set of options: allowCache, safe
00161  * @param boolean $safe DEPRECATED. Use $options['safe'] instead
00162  * @return string The full SCRIPT element, with the JavaScript inside it.
00163  */
00164     function codeBlock($script = null, $options = array(), $safe = true) {
00165         if (!empty($options) && !is_array($options)) {
00166             $options = array('allowCache' => $options);
00167         } else if (empty($options)) {
00168             $options = array();
00169         }
00170         $defaultOptions = array('allowCache' => true, 'safe' => true, 'inline' => true);
00171         $options = array_merge($defaultOptions, compact('safe'), $options);
00172 
00173         if ($this->_cacheEvents && $this->_cacheAll && $options['allowCache'] && $script !== null) {
00174             $this->_cachedEvents[] = $script;
00175         } else {
00176             $block = ($script !== null);
00177             $safe = ($options['safe'] || $this->safe);
00178             if ($safe && !($this->_cacheAll && $options['allowCache'])) {
00179                 $script  = "\n" . '//<![CDATA[' . "\n" . $script;
00180                 if ($block) {
00181                     $script .= "\n" . '//]]>' . "\n";
00182                 }
00183             }
00184 
00185             if ($script === null) {
00186                 $this->__scriptBuffer = @ob_get_contents();
00187                 $this->_blockOptions = $options;
00188                 $this->inBlock = true;
00189                 @ob_end_clean();
00190                 ob_start();
00191                 return null;
00192             } else if (!$block) {
00193                 $this->_blockOptions = $options;
00194             }
00195 
00196             if ($options['inline']) {
00197                 if ($block) {
00198                     return sprintf($this->tags['javascriptblock'], $script);
00199                 } else {
00200                     return sprintf($this->tags['javascriptstart']).ife($safe, "\n" . '//<![CDATA[' . "\n", '');
00201                 }
00202             } elseif ($block) {
00203                 $view =& ClassRegistry::getObject('view');
00204                 $view->addScript(sprintf($this->tags['javascriptblock'], $script));
00205             }
00206         }
00207     }
00208 /**
00209  * Ends a block of cached JavaScript code
00210  *
00211  * @return mixed
00212  */
00213     function blockEnd() {
00214         $script = @ob_get_contents();
00215         @ob_end_clean();
00216         ob_start();
00217         echo $this->__scriptBuffer;
00218         $this->__scriptBuffer = null;
00219         $options = $this->_blockOptions;
00220         $safe = ($options['safe'] || $this->safe);
00221         $this->_blockOptions = array();
00222         $this->inBlock = false;
00223 
00224         if (isset($options['inline']) && !$options['inline']) {
00225             $view =& ClassRegistry::getObject('view');
00226             $view->addScript(sprintf($this->tags['javascriptblock'], $script));
00227         }
00228 
00229         if (!empty($script) && $this->_cacheAll && $options['allowCache']) {
00230             $this->_cachedEvents[] = $script;
00231             return null;
00232         }
00233         return ife($safe, "\n" . '//]]>' . "\n", '').$this->tags['javascriptend'];
00234     }
00235 /**
00236  * Returns a JavaScript include tag (SCRIPT element).  If the filename is prefixed with "/",
00237  * the path will be relative to the base path of your application.  Otherwise, the path will
00238  * be relative to your JavaScript path, usually webroot/js.
00239  *
00240  * @param  mixed  $url String URL to JavaScript file, or an array of URLs.
00241  * @param  boolean $inline If true, the <script /> tag will be printed inline,
00242  *                         otherwise it will be printed in the <head />, using $scripts_for_layout
00243  * @see JS_URL
00244  * @return string
00245  */
00246     function link($url, $inline = true) {
00247         if (is_array($url)) {
00248             $out = '';
00249             foreach ($url as $i) {
00250                 $out .= "\n\t" . $this->link($i, $inline);
00251             }
00252             if ($inline)  {
00253                 return $out . "\n";
00254             }
00255             return;
00256         }
00257 
00258         if (strpos($url, '://') === false) {
00259             if ($url{0} !== '/') {
00260                 $url = JS_URL . $url;
00261             }
00262             if (strpos($url, '?') === false) {
00263                 if (strpos($url, '.js') === false) {
00264                     $url .= '.js';
00265                 }
00266                 if ((Configure::read('Asset.timestamp') === true && Configure::read() > 0) || Configure::read('Asset.timestamp') === 'force') {
00267                     $url .= '?' . @filemtime(WWW_ROOT . str_replace('/', DS, $url));
00268                 }
00269             }
00270             $url = $this->webroot($url);
00271 
00272             if (Configure::read('Asset.filter.js')) {
00273                 $url = str_replace(JS_URL, 'cjs/', $url);
00274             }
00275         }
00276         $out = $this->output(sprintf($this->tags['javascriptlink'], $url));
00277 
00278         if ($inline) {
00279             return $out;
00280         } else {
00281             $view =& ClassRegistry::getObject('view');
00282             $view->addScript($out);
00283         }
00284     }
00285 /**
00286  * Escape carriage returns and single and double quotes for JavaScript segments.
00287  *
00288  * @param string $script string that might have javascript elements
00289  * @return string escaped string
00290  */
00291     function escapeScript($script) {
00292         $script = str_replace(array("\r\n", "\n", "\r"), '\n', $script);
00293         $script = str_replace(array('"', "'"), array('\"', "\\'"), $script);
00294         return $script;
00295     }
00296 /**
00297  * Escape a string to be JavaScript friendly.
00298  *
00299  * List of escaped ellements:
00300  *  + "\r\n" => '\n'
00301  *  + "\r" => '\n'
00302  *  + "\n" => '\n'
00303  *  + '"' => '\"'
00304  *  + "'" => "\\'"
00305  *
00306  * @param  string $script String that needs to get escaped.
00307  * @return string Escaped string.
00308  */
00309     function escapeString($string) {
00310         $escape = array("\r\n" => '\n', "\r" => '\n', "\n" => '\n', '"' => '\"', "'" => "\\'");
00311         return str_replace(array_keys($escape), array_values($escape), $string);
00312     }
00313 /**
00314  * Attach an event to an element. Used with the Prototype library.
00315  *
00316  * @param string $object Object to be observed
00317  * @param string $event event to observe
00318  * @param string $observer function to call
00319  * @param array $options Set options: useCapture, allowCache, safe
00320  * @return boolean true on success
00321  */
00322     function event($object, $event, $observer = null, $options = array()) {
00323         if (!empty($options) && !is_array($options)) {
00324             $options = array('useCapture' => $options);
00325         } else if (empty($options)) {
00326             $options = array();
00327         }
00328 
00329         $defaultOptions = array('useCapture' => false);
00330         $options = array_merge($defaultOptions, $options);
00331 
00332         if ($options['useCapture'] == true) {
00333             $options['useCapture'] = 'true';
00334         } else {
00335             $options['useCapture'] = 'false';
00336         }
00337 
00338         if (strpos($object, 'window') !== false || strpos($object, 'document') !== false || strpos($object, '$(') !== false || strpos($object, '"') !== false || strpos($object, '\'') !== false) {
00339             $b = "Event.observe({$object}, '{$event}', function(event) { {$observer} }, {$options['useCapture']});";
00340         } elseif ($object[0] == '\'') {
00341             $b = "Event.observe(" . substr($object, 1) . ", '{$event}', function(event) { {$observer} }, {$options['useCapture']});";
00342         } else {
00343             $chars = array('#', ' ', ', ', '.', ':');
00344             $found = false;
00345             foreach ($chars as $char) {
00346                 if (strpos($object, $char) !== false) {
00347                     $found = true;
00348                     break;
00349                 }
00350             }
00351             if ($found) {
00352                 $this->_rules[$object] = $event;
00353             } else {
00354                 $b = "Event.observe(\$('{$object}'), '{$event}', function(event) { {$observer} }, {$options['useCapture']});";
00355             }
00356         }
00357 
00358         if (isset($b) && !empty($b)) {
00359             if ($this->_cacheEvents === true) {
00360                 $this->_cachedEvents[] = $b;
00361                 return;
00362             } else {
00363                 return $this->codeBlock($b, array_diff_key($options, $defaultOptions));
00364             }
00365         }
00366     }
00367 /**
00368  * Cache JavaScript events created with event()
00369  *
00370  * @param boolean $file If true, code will be written to a file
00371  * @param boolean $all If true, all code written with JavascriptHelper will be sent to a file
00372  * @return null
00373  */
00374     function cacheEvents($file = false, $all = false) {
00375         $this->_cacheEvents = true;
00376         $this->_cacheToFile = $file;
00377         $this->_cacheAll = $all;
00378     }
00379 /**
00380  * Gets (and clears) the current JavaScript event cache
00381  *
00382  * @param boolean $clear
00383  * @return string
00384  */
00385     function getCache($clear = true) {
00386         $out = '';
00387         $rules = array();
00388 
00389         if (!empty($this->_rules)) {
00390             foreach ($this->_rules as $sel => $event) {
00391                 $rules[] = "\t'{$sel}': function(element, event) {\n\t\t{$event}\n\t}";
00392             }
00393         }
00394         $data = implode("\n", $this->_cachedEvents);
00395 
00396         if (!empty($rules)) {
00397             $data .= "\nvar Rules = {\n" . implode(",\n\n", $rules) . "\n}";
00398             $data .= "\nEventSelectors.start(Rules);\n";
00399         }
00400         if ($clear) {
00401             $this->_rules = array();
00402             $this->_cacheEvents = false;
00403             $this->_cachedEvents = array();
00404         }
00405         return $data;
00406     }
00407 /**
00408  * Write cached JavaScript events
00409  *
00410  * @param boolean $inline If true, returns JavaScript event code.  Otherwise it is added to the
00411  *                        output of $scripts_for_layout in the layout.
00412  * @param array $options Set options for codeBlock
00413  * @return string
00414  */
00415     function writeEvents($inline = true, $options = array()) {
00416         $out = '';
00417         $rules = array();
00418 
00419         if ($this->_cacheEvents) {
00420             $data = $this->getCache();
00421 
00422             if (!empty($data)) {
00423                 if ($this->_cacheToFile) {
00424                     $filename = md5($data);
00425                     if (!file_exists(JS . $filename . '.js')) {
00426                         cache(str_replace(WWW_ROOT, '', JS) . $filename . '.js', $data, '+999 days', 'public');
00427                     }
00428                     $out = $this->link($filename);
00429                 } else {
00430                     $out = $this->codeBlock("\n" . $data . "\n", $options);
00431                 }
00432                 if ($inline) {
00433                     return $out;
00434                 } else {
00435                     $view =& ClassRegistry::getObject('view');
00436                     $view->addScript($out);
00437                 }
00438             }
00439         }
00440     }
00441 /**
00442  * Includes the Prototype Javascript library (and anything else) inside a single script tag.
00443  *
00444  * Note: The recommended approach is to copy the contents of
00445  * javascripts into your application's
00446  * public/javascripts/ directory, and use @see javascriptIncludeTag() to
00447  * create remote script links.
00448  *
00449  * @param string $script Script file to include
00450  * @param array $options Set options for codeBlock
00451  * @return string script with all javascript in/javascripts folder
00452  */
00453     function includeScript($script = "", $options = array()) {
00454         if ($script == "") {
00455             $files = scandir(JS);
00456             $javascript = '';
00457 
00458             foreach ($files as $file) {
00459                 if (substr($file, -3) == '.js') {
00460                     $javascript .= file_get_contents(JS . "{$file}") . "\n\n";
00461                 }
00462             }
00463         } else {
00464             $javascript = file_get_contents(JS . "$script.js") . "\n\n";
00465         }
00466         return $this->codeBlock("\n\n" . $javascript, $options);
00467     }
00468 /**
00469  * Generates a JavaScript object in JavaScript Object Notation (JSON)
00470  * from an array
00471  *
00472  * @param array $data Data to be converted
00473  * @param array $options Set of options: block, prefix, postfix, stringKeys, quoteKeys, q
00474  * @param string $prefix DEPRECATED, use $options['prefix'] instead. Prepends the string to the returned data
00475  * @param string $postfix DEPRECATED, use $options['postfix'] instead. Appends the string to the returned data
00476  * @param array $stringKeys DEPRECATED, use $options['stringKeys'] instead. A list of array keys to be treated as a string
00477  * @param boolean $quoteKeys DEPRECATED, use $options['quoteKeys'] instead. If false, treats $stringKey as a list of keys *not* to be quoted
00478  * @param string $q DEPRECATED, use $options['q'] instead. The type of quote to use
00479  * @return string A JSON code block
00480  */
00481     function object($data = array(), $options = array(), $prefix = null, $postfix = null, $stringKeys = null, $quoteKeys = null, $q = null) {
00482         if (!empty($options) && !is_array($options)) {
00483             $options = array('block' => $options);
00484         } else if (empty($options)) {
00485             $options = array();
00486         }
00487 
00488         $defaultOptions = array('block' => false, 'prefix' => '', 'postfix' => '', 'stringKeys' => array(), 'quoteKeys' => true, 'q' => '"');
00489         $options = array_merge($defaultOptions, $options, array_filter(compact(array_keys($defaultOptions))));
00490 
00491         if (is_object($data)) {
00492             $data = get_object_vars($data);
00493         }
00494 
00495         $out = $keys = array();
00496         $numeric = true;
00497 
00498         if ($this->useNative) {
00499             $rt = json_encode($data);
00500         } else {
00501             if (is_array($data)) {
00502                 $keys = array_keys($data);
00503             }
00504 
00505             if (!empty($keys)) {
00506                 $numeric = (array_values($keys) === array_keys(array_values($keys)));
00507             }
00508 
00509             foreach ($data as $key => $val) {
00510                 if (is_array($val) || is_object($val)) {
00511                     $val = $this->object($val, am($options, array('block' => false)));
00512                 } else {
00513                     $val = $this->value($val, (!count($options['stringKeys']) || ($options['quoteKeys'] && in_array($key, $options['stringKeys'], true)) || (!$options['quoteKeys'] && !in_array($key, $options['stringKeys'], true))));
00514                 }
00515                 if (!$numeric) {
00516                     $val = $options['q'] . $this->value($key, false) . $options['q'] . ':' . $val;
00517                 }
00518                 $out[] = $val;
00519             }
00520 
00521             if (!$numeric) {
00522                 $rt = '{' . join(',', $out) . '}';
00523             } else {
00524                 $rt = '[' . join(',', $out) . ']';
00525             }
00526         }
00527         $rt = $options['prefix'] . $rt . $options['postfix'];
00528 
00529         if ($options['block']) {
00530             $rt = $this->codeBlock($rt, array_diff_key($options, $defaultOptions));
00531         }
00532 
00533         return $rt;
00534     }
00535 /**
00536  * Converts a PHP-native variable of any type to a JSON-equivalent representation
00537  *
00538  * @param mixed $val A PHP variable to be converted to JSON
00539  * @param boolean $quoteStrings If false, leaves string values unquoted
00540  * @return string a JavaScript-safe/JSON representation of $val
00541  */
00542     function value($val, $quoteStrings = true) {
00543         switch (true) {
00544             case (is_array($val) || is_object($val)):
00545                 $val = $this->object($val);
00546             break;
00547             case ($val === null):
00548                 $val = 'null';
00549             break;
00550             case (is_bool($val)):
00551                 $val = ife($val, 'true', 'false');
00552             break;
00553             case (is_int($val)):
00554                 $val = $val;
00555             break;
00556             case (is_float($val)):
00557                 $val = sprintf("%.11f", $val);
00558             break;
00559             default:
00560                 $val = $this->escapeString($val);
00561                 if ($quoteStrings) {
00562                     $val = '"' . $val . '"';
00563                 }
00564             break;
00565         }
00566         return $val;
00567     }
00568 /**
00569  * AfterRender callback.  Writes any cached events to the view, or to a temp file.
00570  *
00571  * @return null
00572  */
00573     function afterRender() {
00574         if (!$this->enabled) {
00575             return;
00576         }
00577         echo $this->writeEvents(true);
00578     }
00579 }
00580 
00581 ?>