Mercurial > hg > Members > shoshi > webvirt
diff cake/libs/view/helpers/javascript.php @ 0:261e66bd5a0c
hg init
author | Shoshi TAMAKI <shoshi@cr.ie.u-ryukyu.ac.jp> |
---|---|
date | Sun, 24 Jul 2011 21:08:31 +0900 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cake/libs/view/helpers/javascript.php Sun Jul 24 21:08:31 2011 +0900 @@ -0,0 +1,721 @@ +<?php +/** + * Javascript Helper class file. + * + * PHP versions 4 and 5 + * + * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://cakephp.org CakePHP(tm) Project + * @package cake + * @subpackage cake.cake.libs.view.helpers + * @since CakePHP(tm) v 0.10.0.1076 + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ + +/** + * Javascript Helper class for easy use of JavaScript. + * + * JavascriptHelper encloses all methods needed while working with JavaScript. + * + * @package cake + * @subpackage cake.cake.libs.view.helpers + * @link http://book.cakephp.org/view/1450/Javascript + */ +class JavascriptHelper extends AppHelper { + +/** + * Determines whether native JSON extension is used for encoding. Set by object constructor. + * + * @var boolean + * @access public + */ + var $useNative = false; + +/** + * If true, automatically writes events to the end of a script or to an external JavaScript file + * at the end of page execution + * + * @var boolean + * @access public + */ + var $enabled = true; + +/** + * Indicates whether <script /> blocks should be written 'safely,' i.e. wrapped in CDATA blocks + * + * @var boolean + * @access public + */ + var $safe = false; + +/** + * HTML tags used by this helper. + * + * @var array + * @access public + */ + var $tags = array( + 'javascriptstart' => '<script type="text/javascript">', + 'javascriptend' => '</script>', + 'javascriptblock' => '<script type="text/javascript">%s</script>', + 'javascriptlink' => '<script type="text/javascript" src="%s"></script>' + ); + +/** + * Holds options passed to codeBlock(), saved for when block is dumped to output + * + * @var array + * @access protected + * @see JavascriptHelper::codeBlock() + */ + var $_blockOptions = array(); + +/** + * Caches events written by event() for output at the end of page execution + * + * @var array + * @access protected + * @see JavascriptHelper::event() + */ + var $_cachedEvents = array(); + +/** + * Indicates whether generated events should be cached for later output (can be written at the + * end of the page, in the <head />, or to an external file). + * + * @var boolean + * @access protected + * @see JavascriptHelper::event() + * @see JavascriptHelper::writeEvents() + */ + var $_cacheEvents = false; + +/** + * Indicates whether cached events should be written to an external file + * + * @var boolean + * @access protected + * @see JavascriptHelper::event() + * @see JavascriptHelper::writeEvents() + */ + var $_cacheToFile = false; + +/** + * Indicates whether *all* generated JavaScript should be cached for later output + * + * @var boolean + * @access protected + * @see JavascriptHelper::codeBlock() + * @see JavascriptHelper::blockEnd() + */ + var $_cacheAll = false; + +/** + * Contains event rules attached with CSS selectors. Used with the event:Selectors JavaScript + * library. + * + * @var array + * @access protected + * @see JavascriptHelper::event() + * @link http://alternateidea.com/event-selectors/ + */ + var $_rules = array(); + +/** + * @var string + * @access private + */ + var $__scriptBuffer = null; + +/** + * Constructor. Checks for presence of native PHP JSON extension to use for object encoding + * + * @access public + */ + function __construct($options = array()) { + if (!empty($options)) { + foreach ($options as $key => $val) { + if (is_numeric($key)) { + $key = $val; + $val = true; + } + switch ($key) { + case 'cache': + + break; + case 'safe': + $this->safe = $val; + break; + } + } + } + $this->useNative = function_exists('json_encode'); + return parent::__construct($options); + } + +/** + * Returns a JavaScript script tag. + * + * Options: + * + * - allowCache: boolean, designates whether this block is cacheable using the + * current cache settings. + * - safe: boolean, whether this block should be wrapped in CDATA tags. Defaults + * to helper's object configuration. + * - inline: whether the block should be printed inline, or written + * to cached for later output (i.e. $scripts_for_layout). + * + * @param string $script The JavaScript to be wrapped in SCRIPT tags. + * @param array $options Set of options: + * @return string The full SCRIPT element, with the JavaScript inside it, or null, + * if 'inline' is set to false. + */ + function codeBlock($script = null, $options = array()) { + if (!empty($options) && !is_array($options)) { + $options = array('allowCache' => $options); + } elseif (empty($options)) { + $options = array(); + } + $defaultOptions = array('allowCache' => true, 'safe' => true, 'inline' => true); + $options = array_merge($defaultOptions, $options); + + if (empty($script)) { + $this->__scriptBuffer = @ob_get_contents(); + $this->_blockOptions = $options; + $this->inBlock = true; + @ob_end_clean(); + ob_start(); + return null; + } + if ($this->_cacheEvents && $this->_cacheAll && $options['allowCache']) { + $this->_cachedEvents[] = $script; + return null; + } + if ($options['safe'] || $this->safe) { + $script = "\n" . '//<![CDATA[' . "\n" . $script . "\n" . '//]]>' . "\n"; + } + if ($options['inline']) { + return sprintf($this->tags['javascriptblock'], $script); + } else { + $view =& ClassRegistry::getObject('view'); + $view->addScript(sprintf($this->tags['javascriptblock'], $script)); + } + } + +/** + * Ends a block of cached JavaScript code + * + * @return mixed + */ + function blockEnd() { + if (!isset($this->inBlock) || !$this->inBlock) { + return; + } + $script = @ob_get_contents(); + @ob_end_clean(); + ob_start(); + echo $this->__scriptBuffer; + $this->__scriptBuffer = null; + $options = $this->_blockOptions; + $this->_blockOptions = array(); + $this->inBlock = false; + + if (empty($script)) { + return null; + } + + return $this->codeBlock($script, $options); + } + +/** + * Returns a JavaScript include tag (SCRIPT element). If the filename is prefixed with "/", + * the path will be relative to the base path of your application. Otherwise, the path will + * be relative to your JavaScript path, usually webroot/js. + * + * @param mixed $url String URL to JavaScript file, or an array of URLs. + * @param boolean $inline If true, the <script /> tag will be printed inline, + * otherwise it will be printed in the <head />, using $scripts_for_layout + * @see JS_URL + * @return string + */ + function link($url, $inline = true) { + if (is_array($url)) { + $out = ''; + foreach ($url as $i) { + $out .= "\n\t" . $this->link($i, $inline); + } + if ($inline) { + return $out . "\n"; + } + return; + } + + if (strpos($url, '://') === false) { + if ($url[0] !== '/') { + $url = JS_URL . $url; + } + if (strpos($url, '?') === false) { + if (substr($url, -3) !== '.js') { + $url .= '.js'; + } + } + $url = $this->assetTimestamp($this->webroot($url)); + + if (Configure::read('Asset.filter.js')) { + $pos = strpos($url, JS_URL); + if ($pos !== false) { + $url = substr($url, 0, $pos) . 'cjs/' . substr($url, $pos + strlen(JS_URL)); + } + } + } + $out = sprintf($this->tags['javascriptlink'], $url); + + if ($inline) { + return $out; + } else { + $view =& ClassRegistry::getObject('view'); + $view->addScript($out); + } + } + +/** + * Escape carriage returns and single and double quotes for JavaScript segments. + * + * @param string $script string that might have javascript elements + * @return string escaped string + */ + function escapeScript($script) { + $script = str_replace(array("\r\n", "\n", "\r"), '\n', $script); + $script = str_replace(array('"', "'"), array('\"', "\\'"), $script); + return $script; + } + +/** + * Escape a string to be JavaScript friendly. + * + * List of escaped ellements: + * + "\r\n" => '\n' + * + "\r" => '\n' + * + "\n" => '\n' + * + '"' => '\"' + * + "'" => "\\'" + * + * @param string $script String that needs to get escaped. + * @return string Escaped string. + */ + function escapeString($string) { + App::import('Core', 'Multibyte'); + $escape = array("\r\n" => "\n", "\r" => "\n"); + $string = str_replace(array_keys($escape), array_values($escape), $string); + return $this->_utf8ToHex($string); + } + +/** + * Encode a string into JSON. Converts and escapes necessary characters. + * + * @return void + */ + function _utf8ToHex($string) { + $length = strlen($string); + $return = ''; + for ($i = 0; $i < $length; ++$i) { + $ord = ord($string{$i}); + switch (true) { + case $ord == 0x08: + $return .= '\b'; + break; + case $ord == 0x09: + $return .= '\t'; + break; + case $ord == 0x0A: + $return .= '\n'; + break; + case $ord == 0x0C: + $return .= '\f'; + break; + case $ord == 0x0D: + $return .= '\r'; + break; + case $ord == 0x22: + case $ord == 0x2F: + case $ord == 0x5C: + case $ord == 0x27: + $return .= '\\' . $string{$i}; + break; + case (($ord >= 0x20) && ($ord <= 0x7F)): + $return .= $string{$i}; + break; + case (($ord & 0xE0) == 0xC0): + if ($i + 1 >= $length) { + $i += 1; + $return .= '?'; + break; + } + $charbits = $string{$i} . $string{$i + 1}; + $char = Multibyte::utf8($charbits); + $return .= sprintf('\u%04s', dechex($char[0])); + $i += 1; + break; + case (($ord & 0xF0) == 0xE0): + if ($i + 2 >= $length) { + $i += 2; + $return .= '?'; + break; + } + $charbits = $string{$i} . $string{$i + 1} . $string{$i + 2}; + $char = Multibyte::utf8($charbits); + $return .= sprintf('\u%04s', dechex($char[0])); + $i += 2; + break; + case (($ord & 0xF8) == 0xF0): + if ($i + 3 >= $length) { + $i += 3; + $return .= '?'; + break; + } + $charbits = $string{$i} . $string{$i + 1} . $string{$i + 2} . $string{$i + 3}; + $char = Multibyte::utf8($charbits); + $return .= sprintf('\u%04s', dechex($char[0])); + $i += 3; + break; + case (($ord & 0xFC) == 0xF8): + if ($i + 4 >= $length) { + $i += 4; + $return .= '?'; + break; + } + $charbits = $string{$i} . $string{$i + 1} . $string{$i + 2} . $string{$i + 3} . $string{$i + 4}; + $char = Multibyte::utf8($charbits); + $return .= sprintf('\u%04s', dechex($char[0])); + $i += 4; + break; + case (($ord & 0xFE) == 0xFC): + if ($i + 5 >= $length) { + $i += 5; + $return .= '?'; + break; + } + $charbits = $string{$i} . $string{$i + 1} . $string{$i + 2} . $string{$i + 3} . $string{$i + 4} . $string{$i + 5}; + $char = Multibyte::utf8($charbits); + $return .= sprintf('\u%04s', dechex($char[0])); + $i += 5; + break; + } + } + return $return; + } + +/** + * Attach an event to an element. Used with the Prototype library. + * + * @param string $object Object to be observed + * @param string $event event to observe + * @param string $observer function to call + * @param array $options Set options: useCapture, allowCache, safe + * @return boolean true on success + */ + function event($object, $event, $observer = null, $options = array()) { + if (!empty($options) && !is_array($options)) { + $options = array('useCapture' => $options); + } else if (empty($options)) { + $options = array(); + } + + $defaultOptions = array('useCapture' => false); + $options = array_merge($defaultOptions, $options); + + if ($options['useCapture'] == true) { + $options['useCapture'] = 'true'; + } else { + $options['useCapture'] = 'false'; + } + $isObject = ( + strpos($object, 'window') !== false || strpos($object, 'document') !== false || + strpos($object, '$(') !== false || strpos($object, '"') !== false || + strpos($object, '\'') !== false + ); + + if ($isObject) { + $b = "Event.observe({$object}, '{$event}', function(event) { {$observer} }, "; + $b .= "{$options['useCapture']});"; + } elseif ($object[0] == '\'') { + $b = "Event.observe(" . substr($object, 1) . ", '{$event}', function(event) { "; + $b .= "{$observer} }, {$options['useCapture']});"; + } else { + $chars = array('#', ' ', ', ', '.', ':'); + $found = false; + foreach ($chars as $char) { + if (strpos($object, $char) !== false) { + $found = true; + break; + } + } + if ($found) { + $this->_rules[$object] = $event; + } else { + $b = "Event.observe(\$('{$object}'), '{$event}', function(event) { "; + $b .= "{$observer} }, {$options['useCapture']});"; + } + } + + if (isset($b) && !empty($b)) { + if ($this->_cacheEvents === true) { + $this->_cachedEvents[] = $b; + return; + } else { + return $this->codeBlock($b, array_diff_key($options, $defaultOptions)); + } + } + } + +/** + * Cache JavaScript events created with event() + * + * @param boolean $file If true, code will be written to a file + * @param boolean $all If true, all code written with JavascriptHelper will be sent to a file + * @return null + */ + function cacheEvents($file = false, $all = false) { + $this->_cacheEvents = true; + $this->_cacheToFile = $file; + $this->_cacheAll = $all; + } + +/** + * Gets (and clears) the current JavaScript event cache + * + * @param boolean $clear + * @return string + */ + function getCache($clear = true) { + $out = ''; + $rules = array(); + + if (!empty($this->_rules)) { + foreach ($this->_rules as $sel => $event) { + $rules[] = "\t'{$sel}': function(element, event) {\n\t\t{$event}\n\t}"; + } + } + $data = implode("\n", $this->_cachedEvents); + + if (!empty($rules)) { + $data .= "\nvar Rules = {\n" . implode(",\n\n", $rules) . "\n}"; + $data .= "\nEventSelectors.start(Rules);\n"; + } + if ($clear) { + $this->_rules = array(); + $this->_cacheEvents = false; + $this->_cachedEvents = array(); + } + return $data; + } + +/** + * Write cached JavaScript events + * + * @param boolean $inline If true, returns JavaScript event code. Otherwise it is added to the + * output of $scripts_for_layout in the layout. + * @param array $options Set options for codeBlock + * @return string + */ + function writeEvents($inline = true, $options = array()) { + $out = ''; + $rules = array(); + + if (!$this->_cacheEvents) { + return; + } + $data = $this->getCache(); + + if (empty($data)) { + return; + } + + if ($this->_cacheToFile) { + $filename = md5($data); + if (!file_exists(JS . $filename . '.js')) { + cache(str_replace(WWW_ROOT, '', JS) . $filename . '.js', $data, '+999 days', 'public'); + } + $out = $this->link($filename); + } else { + $out = $this->codeBlock("\n" . $data . "\n", $options); + } + + if ($inline) { + return $out; + } else { + $view =& ClassRegistry::getObject('view'); + $view->addScript($out); + } + } + +/** + * Includes the Prototype Javascript library (and anything else) inside a single script tag. + * + * Note: The recommended approach is to copy the contents of + * javascripts into your application's + * public/javascripts/ directory, and use @see javascriptIncludeTag() to + * create remote script links. + * + * @param string $script Script file to include + * @param array $options Set options for codeBlock + * @return string script with all javascript in/javascripts folder + */ + function includeScript($script = "", $options = array()) { + if ($script == "") { + $files = scandir(JS); + $javascript = ''; + + foreach ($files as $file) { + if (substr($file, -3) == '.js') { + $javascript .= file_get_contents(JS . "{$file}") . "\n\n"; + } + } + } else { + $javascript = file_get_contents(JS . "$script.js") . "\n\n"; + } + return $this->codeBlock("\n\n" . $javascript, $options); + } + +/** + * Generates a JavaScript object in JavaScript Object Notation (JSON) + * from an array + * + * ### Options + * + * - block - Wraps the return value in a script tag if true. Default is false + * - prefix - Prepends the string to the returned data. Default is '' + * - postfix - Appends the string to the returned data. Default is '' + * - stringKeys - A list of array keys to be treated as a string. + * - quoteKeys - If false treats $stringKeys as a list of keys **not** to be quoted. Default is true. + * - q - The type of quote to use. Default is '"'. This option only affects the keys, not the values. + * + * @param array $data Data to be converted + * @param array $options Set of options: block, prefix, postfix, stringKeys, quoteKeys, q + * @return string A JSON code block + */ + function object($data = array(), $options = array()) { + if (!empty($options) && !is_array($options)) { + $options = array('block' => $options); + } else if (empty($options)) { + $options = array(); + } + + $defaultOptions = array( + 'block' => false, 'prefix' => '', 'postfix' => '', + 'stringKeys' => array(), 'quoteKeys' => true, 'q' => '"' + ); + $options = array_merge($defaultOptions, $options, array_filter(compact(array_keys($defaultOptions)))); + + if (is_object($data)) { + $data = get_object_vars($data); + } + + $out = $keys = array(); + $numeric = true; + + if ($this->useNative) { + $rt = json_encode($data); + } else { + if (is_null($data)) { + return 'null'; + } + if (is_bool($data)) { + return $data ? 'true' : 'false'; + } + + if (is_array($data)) { + $keys = array_keys($data); + } + + if (!empty($keys)) { + $numeric = (array_values($keys) === array_keys(array_values($keys))); + } + + foreach ($data as $key => $val) { + if (is_array($val) || is_object($val)) { + $val = $this->object( + $val, + array_merge($options, array('block' => false, 'prefix' => '', 'postfix' => '')) + ); + } else { + $quoteStrings = ( + !count($options['stringKeys']) || + ($options['quoteKeys'] && in_array($key, $options['stringKeys'], true)) || + (!$options['quoteKeys'] && !in_array($key, $options['stringKeys'], true)) + ); + $val = $this->value($val, $quoteStrings); + } + if (!$numeric) { + $val = $options['q'] . $this->value($key, false) . $options['q'] . ':' . $val; + } + $out[] = $val; + } + + if (!$numeric) { + $rt = '{' . implode(',', $out) . '}'; + } else { + $rt = '[' . implode(',', $out) . ']'; + } + } + $rt = $options['prefix'] . $rt . $options['postfix']; + + if ($options['block']) { + $rt = $this->codeBlock($rt, array_diff_key($options, $defaultOptions)); + } + + return $rt; + } + +/** + * Converts a PHP-native variable of any type to a JSON-equivalent representation + * + * @param mixed $val A PHP variable to be converted to JSON + * @param boolean $quoteStrings If false, leaves string values unquoted + * @return string a JavaScript-safe/JSON representation of $val + */ + function value($val, $quoteStrings = true) { + switch (true) { + case (is_array($val) || is_object($val)): + $val = $this->object($val); + break; + case ($val === null): + $val = 'null'; + break; + case (is_bool($val)): + $val = !empty($val) ? 'true' : 'false'; + break; + case (is_int($val)): + $val = $val; + break; + case (is_float($val)): + $val = sprintf("%.11f", $val); + break; + default: + $val = $this->escapeString($val); + if ($quoteStrings) { + $val = '"' . $val . '"'; + } + break; + } + return $val; + } + +/** + * AfterRender callback. Writes any cached events to the view, or to a temp file. + * + * @return null + */ + function afterRender() { + if (!$this->enabled) { + return; + } + echo $this->writeEvents(true); + } +}