Perforce Chronicle 2012.2/486814
API Documentation

P4Cms_View_Helper_Dojo_Container Class Reference

Derivative of dojo container helper that provides control over which dojo components are rendered. More...

List of all members.

Public Member Functions

 __toString ()
 Extend toString to perform auto build if enabled.
 _buildModule ($module, array &$built=array())
 Recursive function to build a module and all of its dependencies.
 clearModulePaths ()
 Clear all module paths.
 clearModules ()
 Clear all registered modules.
 clearOnLoad ()
 Clear all onLoad functions.
 clearZendLoad ()
 Clear all 'zend' onLoad functions.
 getAssetHandler ()
 Get the asset handler used to store the aggregated dojo file.
 getBestLocaleFile ($module, $bundle)
 Finds the closest matching package for the client's locale.
 getBestPreloadLocaleFile ($package)
 Finds the best preloadable locale file for the client's locale.
 getDocumentRoot ()
 Get the file-system path to the document root.
 getLocale ($hyphenate=false)
 Get the client's (browser) locale - normalized to lower case because that is how dojo likes it.
 setAssetHandler (P4Cms_AssetHandlerInterface $handler=null)
 Set the asset handler used to store the aggregated dojo file.
 setAutoBuild ($build)
 Enable or disable automatic dojo builds.
 setDocumentRoot ($path)
 Set the file-system path to the document root.
 setRender ($elements)
 Set which dojo elements you want to be rendered.

Protected Member Functions

 _buildDojo ()
 Generate a single dojo javascript file containing:
 _canGzipCompress ()
 Check if this PHP can generate gzip compressed data.
 _clientAcceptsGzip ()
 Check if the client can accept gzip encoded content.
 _makeConditional ($js, $module)
 Wrap the given js in a hasResource check unless it already has one.
 _moduleToFilename ($module)
 Attempt to determine the local filename for a given dojo module.
 _renderDjConfig ()
 Don't render config if disabled and ensure the baseUrl gets set if we have a dojo build (as indicated by buildUri)
 _renderDojoScriptTag ()
 Don't render dojo script tag if disabled or if we have a dojo build (as indicated by buildUri)
 _renderExtras ()
 Include dojo build in extras.
 _renderLayers ()
 Don't render layers if disabled.
 _renderStylesheets ()
 Don't render stylesheets if disabled.

Protected Attributes

 $_assetHandler = null
 $_build = false
 $_buildUri = null
 $_built = false
 $_documentRoot = null
 $_locale = null
 $_render

Detailed Description

Derivative of dojo container helper that provides control over which dojo components are rendered.

Copyright:
2011-2012 Perforce Software. All rights reserved
License:
Please see LICENSE.txt in top-level folder of this distribution.
Version:
2012.2/486814

Member Function Documentation

P4Cms_View_Helper_Dojo_Container::__toString ( )

Extend toString to perform auto build if enabled.

    {
        if ($this->_build && !$this->_built) {
            $this->_buildDojo();
        }

        return parent::__toString();
    }
P4Cms_View_Helper_Dojo_Container::_buildDojo ( ) [protected]

Generate a single dojo javascript file containing:

  • the dojo base
  • module path registration
  • all of the the required modules (recursively building dependencies)

Re-builds whenever a top-level module changes. Does not detect changes in indirectly required modules.

    {
        // bail out if asset handler is unset.
        if (!$this->getAssetHandler()) {
            P4Cms_Log::log(
                "Failed to build dojo. Asset handler is unset.",
                P4Cms_Log::ERR
            );
            return;
        }

        // bail out if document root is unset.
        if (!$this->getDocumentRoot()) {
            P4Cms_Log::log(
                "Failed to build dojo. Document root is unset.",
                P4Cms_Log::ERR
            );
            return;
        }
        
        // collect module file info.
        $latest  = 0;
        $files   = array();
        $ignored = array();
        foreach ($this->getModules() as $module) {
            $file = $this->_moduleToFilename($module);
            if (!file_exists($file)) {
                $ignored[] = $module;
                continue;
            }

            $time    = filemtime($file);
            $files[] = $file;
            $latest  = $time > $latest ? $time : $latest;
        }

        // if we have no files, nothing to build.
        if (empty($files)) {
            return;
        }

        // determine if compression should be enabled
        $compressed = $this->_canGzipCompress() && $this->_clientAcceptsGzip();

        // generate build filename.
        // combine the list of required modules with the client's locale and
        // the latest mod time so any changes will produce a different build.
        $buildFile = 'dojo-' . md5(implode(',', $files) . $this->getLocale() . $latest) 
                   . ($compressed ? '.jsgz' : '.js');

        // if build file doesn't exist, (re)build.
        if (!$this->getAssetHandler()->exists($buildFile)) {
            $built = array();

            // start with dojo base
            $base = $this->_buildModule("dojo", $built);

            // follow-up dojo base immediately with module path registration.
            // must come before optional module build because a module could
            // dojo.require a registered module that we are unable to build.
            foreach ($this->getModulePaths() as $module => $path) {
                $base .= 'dojo.registerModulePath("' 
                       .  $this->view->escape($module) .  '", "'
                       .  $this->view->escape($path) . '");';
            }

            // now build each of the optional modules that have been required.
            $modules = "";
            foreach ($this->getModules() as $module) {
                $modules .= $this->_buildModule($module, $built);
            }

            // make module build conditional so it only runs once.
            $modules = $this->_makeConditional($modules, basename($buildFile));

            // combine the dojo base build with the optional modules build.
            $build = $base . $modules;

            // also compress if possible.
            if ($compressed) {
                $build = gzencode($build, 9);
            }

            // write out build file, on failure; skip aggregation.
            if (!$this->getAssetHandler()->put($buildFile, $build)) {
                return;
            }
        }

        // only keep required modules that we couldn't build.
        $this->_modules = $ignored;

        // set the build src link.
        $request         = Zend_Controller_Front::getInstance()->getRequest();
        $this->_buildUri = $this->getAssetHandler()->uri($buildFile);

        $this->_built = true;
    }
P4Cms_View_Helper_Dojo_Container::_buildModule ( module,
array &$  built = array() 
)

Recursive function to build a module and all of its dependencies.

Works by expanding dojo.require statements into the contents of the named module.

Note this method is public so that we can call it from an anonymous function. Consider it protected.

Parameters:
string$modulethe name of the module to build.
array&$builtoptional - by reference - list of modules already built.
Returns:
string the resulting js.
    {
        // early exit if file does not exist.
        $file = $this->_moduleToFilename($module);
        if (!is_file($file)) {
            return false;
        }

        // prevent infinite recursion.
        if (!array_key_exists($module, $built)) {
            $built[$module] = true;
        } else {
            return;
        }

        // read out file
        $build = file_get_contents($file);

        // make an alias to 'this' for the benefit of the anonymous functions
        // php 5.3 does not permit the use of 'this' inside closures
        $self = $this;
        
        // locate and replace dojo.requireLocalization() statements
        // with the appropriate translation package data
        $build = preg_replace_callback(
            '/d(?:ojo)?.requireLocalization\(([^)]+)\)\s*;?/i',
            function($match) use (&$built, $self)
            {
                // extract arguments from require localization call using
                // the str_getcsv function because it knows how to parse 
                // comma-delimited quoted strings.
                $args = str_getcsv($match[1]);
                
                // ensure requireLocalization was called with both module 
                // and bundle args or we won't know what to do with it.
                if (!isset($args[0], $args[1])) {
                    return $match[0];
                }
                
                $module  = $args[0];
                $bundle  = $args[1];
                $package = $module . '.nls.' . $bundle;
        
                // only build localization packages once.
                if (!array_key_exists($package, $built)) {
                    $built[$package] = true;
                } else {
                    return $match[0];
                }

                // find the best available localization package for the 
                // client's locale - nothing to do if we can't find one.
                $file = $self->getBestLocaleFile($module, $bundle);
                if (!$file) {
                    return $match[0];
                }

                // add the localization package to the dojo build.
                // this amounts to creating an 'nls' object named for the 
                // package with three elements: one for the exact locale, 
                // one for the language and one for 'ROOT' - this nls object
                // and each of its elements are then dojo.require()'d to 
                // register them as 'loadedModules' in dojo - note, we embed 
                // this all as an anonymous function that calls itself to 
                // provide local scope for the data variable.
                $locale = $self->getLocale();
                $lang   = reset(explode('_', $locale));
                $data   = file_get_contents($file);
                $js     = '(function(){'
                        . 'var data = ' . $data . ';'
                        . 'dojo.getObject("' . $package . '", true);'
                        . $package . ' = {'
                        . $locale . ': data,'
                        . $lang . ': data,'
                        . 'ROOT: data};'
                        . 'dojo.provide("' . $package . '");'
                        . 'dojo.provide("' . $package . '.' . $locale . '");'
                        . 'dojo.provide("' . $package . '.' . $lang . '");'
                        . 'dojo.provide("' . $package . '.ROOT");'
                        . '})();';
                        
                return $js . $match[0];
            },
            $build
        );

        // some dojo components (datagrid, I'm looking at you!) use the 
        // protected '_preloadLocalizations' method to get i18n packages.
        // we want to resolve these as well to save the http request.
        $build = preg_replace_callback(
            '/dojo.i18n._preloadLocalizations\([\'"]?([^\'")]+)[\'"]?[^)]*\)\s*;?/i',
            function($match) use (&$built, $self)
            {
                // only preload a given localization package once.
                $package = $match[1];
                if (!array_key_exists($package, $built)) {
                    $built[$package] = true;
                } else {
                    return $match[0];
                }

                // find the best available preload localization package for
                // the client's locale - nothing to do if we can't find one.
                $file = $self->getBestPreloadLocaleFile($package);
                if (!$file) {
                    return $match[0];
                }
                
                // add the preload localization file to the build
                // nothing special required, just insert the contents.
                return file_get_contents($file) . $match[0];
            },
            $build
        );
        
        // replace dojo.require() statements with
        // the dependencies they name where possible.
        // note: also recognizes and expands d.require().
        $build = preg_replace_callback(
            '/d(?:ojo)?.require\([\'"]?([^\'")]+)[\'"]?\)\s*;?/i',
            function($match) use (&$built, $self)
            {
                $build = $self->_buildModule($match[1], $built);
                if ($build !== false) {
                    return $build;
                }

                // could not satisfy dependency - keep require.
                return $match[0];
            },
            $build
        );
            
        // wrap module build in resource check.
        $build = $this->_makeConditional($build, $module);

        return $build;
    }
P4Cms_View_Helper_Dojo_Container::_canGzipCompress ( ) [protected]

Check if this PHP can generate gzip compressed data.

Returns:
bool true if this PHP has gzip support.
    {
        return function_exists('gzencode');
    }
P4Cms_View_Helper_Dojo_Container::_clientAcceptsGzip ( ) [protected]

Check if the client can accept gzip encoded content.

Returns:
bool true if the client supports gzip; false otherwise.
    {
        $front   = Zend_Controller_Front::getInstance();
        $request = $front->getRequest();
        $accepts = $request->getHeader('Accept-Encoding');

        return strpos($accepts, 'gzip') !== false;
    }
P4Cms_View_Helper_Dojo_Container::_makeConditional ( js,
module 
) [protected]

Wrap the given js in a hasResource check unless it already has one.

Parameters:
string$jsthe js to wrap
string$modulethe originating module.
Returns:
string the conditional js.
    {
        // special handling for the dojo base.
        // make base conditional on dojo being undefined.
        if ($module == 'dojo') {
            return "\nif (typeof dojo === 'undefined') {" . $js . "\n}";
        }
        
        // if already conditional, do nothing (already compiled)
        $pattern = '/dojo._hasResource\[[\'"]' . $module . '[\'"]\]/';
        if (preg_match($pattern, $js)) {
            return $js;
        }

        // wrap in has resource conditional and return.
        return "\nif(!dojo._hasResource['$module']){"
             . "dojo._hasResource['$module']=true;"
             . $js
             . "\n}";
    }
P4Cms_View_Helper_Dojo_Container::_moduleToFilename ( module) [protected]

Attempt to determine the local filename for a given dojo module.

Parameters:
string$modulethe name of the dojo module to get the filename for.
Returns:
string the likely filename of the module.
    {
        $paths = $this->getModulePaths();

        // special handling for 'dojo' base.
        if ($module === 'dojo') {
            return $this->getDocumentRoot() . $this->getLocalPath();
        }
        
        // add dojo paths.
        $basePath       = dirname(dirname($this->getLocalPath()));
        $paths['dojo']  = $basePath . '/dojo';
        $paths['dijit'] = $basePath . '/dijit';
        $paths['dojox'] = $basePath . '/dojox';
        
        // find path to module.
        $path  = null;
        $parts = explode(".", $module);
        $extra = array();
        while ($parts && !$path) {
            $key = implode(".", $parts);
            if (isset($paths[$key])) {
                $path = $paths[$key];
            } else {
                array_unshift($extra, array_pop($parts));
            }
        }

        if (!$path) {
            return null;
        }

        $file = $this->getDocumentRoot() . $path
              . (count($extra) ? "/" . implode("/", $extra) : "")
              . ".js";
        return $file;
    }
P4Cms_View_Helper_Dojo_Container::_renderDjConfig ( ) [protected]

Don't render config if disabled and ensure the baseUrl gets set if we have a dojo build (as indicated by buildUri)

Returns:
string rendered dojo config.
    {
        if (!$this->_render['config']) {
            return;
        }
        
        // we need to explicitly set the baseUrl for builds because dojo 
        // won't be able to infer it correctly from the build location.
        if ($this->_buildUri) {
            $this->setDjConfigOption('baseUrl', dirname($this->getLocalPath()) . '/');
        }

        return parent::_renderDjConfig();
    }
P4Cms_View_Helper_Dojo_Container::_renderDojoScriptTag ( ) [protected]

Don't render dojo script tag if disabled or if we have a dojo build (as indicated by buildUri)

Returns:
string rendered dojo script tag.
    {
        if ($this->_buildUri || !$this->_render['scriptTag']) {
            return;
        }

        return parent::_renderDojoScriptTag();
    }
P4Cms_View_Helper_Dojo_Container::_renderExtras ( ) [protected]

Include dojo build in extras.

Don't render extras if disabled.

Returns:
string rendered dojo extras.
    {
        if (!$this->_render['extras']) {
            return;
        }

        // if no build, just return parent.
        if (!$this->_buildUri) {
            return parent::_renderExtras();
        }

        // output script tag for build file - then clear it so
        // it won't be output twice if the view helper runs again.
        $html = '<script type="text/javascript" src="'
              .  $this->_buildUri . '"></script>';
        $this->_buildUri = null;

        // clear module paths so they aren't rendered twice.
        // (they are included in the dojo build file)
        $this->clearModulePaths();
        
        // add on parent's extras
        $html .= parent::_renderExtras();

        return $html;
    }
P4Cms_View_Helper_Dojo_Container::_renderLayers ( ) [protected]

Don't render layers if disabled.

Returns:
string rendered dojo layers.
    {
        if (!$this->_render['layers']) {
            return;
        }

        return parent::_renderLayers();
    }
P4Cms_View_Helper_Dojo_Container::_renderStylesheets ( ) [protected]

Don't render stylesheets if disabled.

Returns:
string rendered dojo stylesheets.
    {
        if (!$this->_render['stylesheets']) {
            return;
        }

        return parent::_renderStylesheets();
    }
P4Cms_View_Helper_Dojo_Container::clearModulePaths ( )

Clear all module paths.

Returns:
P4Cms_View_Helper_Dojo_Container
    {
        $this->_modulePaths = array();
        return $this;
    }
P4Cms_View_Helper_Dojo_Container::clearModules ( )

Clear all registered modules.

Returns:
P4Cms_View_Helper_Dojo_Container
    {
        $this->_modules = array();
        return $this;
    }
P4Cms_View_Helper_Dojo_Container::clearOnLoad ( )

Clear all onLoad functions.

Returns:
P4Cms_View_Helper_Dojo_Container
    {
        $this->_onLoadActions = array();
        return $this;
    }
P4Cms_View_Helper_Dojo_Container::clearZendLoad ( )

Clear all 'zend' onLoad functions.

Returns:
P4Cms_View_Helper_Dojo_Container
    {
        $this->_zendLoadActions = array();
        return $this;
    }
P4Cms_View_Helper_Dojo_Container::getAssetHandler ( )

Get the asset handler used to store the aggregated dojo file.

Returns:
P4Cms_AssetHandlerInterface|null the handler to use or null
    {
        return $this->_assetHandler;
    }
P4Cms_View_Helper_Dojo_Container::getBestLocaleFile ( module,
bundle 
)

Finds the closest matching package for the client's locale.

Searches for the exact locale, then language, then 'root'. For example, if the locale is 'en-us', looks for:

<module>/nls/en-us/<bundle>.js <module>/nls/en/<bundle>.js <module>/nls/<bundle>.js

Parameters:
string$modulethe name of the dojo module to get a localization package for (e.g. 'dijit')
string$bundlethe name of the locale bundle to get (e.g. 'common')
Returns:
string the filename of the best matching localizaton package
    {
        $locale   = $this->getLocale(true);
        $path     = substr($this->_moduleToFilename($module), 0, -3) . '/nls/';
        $attempts = array($locale . '/', reset(explode('-', $locale)) . '/', '');
        
        foreach ($attempts as $attempt) {
            $file = $path . $attempt . $bundle . '.js';
            if (is_readable($file)) {
                return $file;
            }
        }
        
        return false;
    }
P4Cms_View_Helper_Dojo_Container::getBestPreloadLocaleFile ( package)

Finds the best preloadable locale file for the client's locale.

Searches for the exact locale, then language, then 'root'. For example, if the locale is 'en-us', looks for:

<module>_en-us.js <module>_en.js <module>_ROOT.js

Parameters:
string$packagethe name of the preloadable locale package (e.g. 'dojox.grid.nls.DataGrid')
Returns:
string the filename of the best matching localizaton package
    {
        $locale   = $this->getLocale(true);
        $path     = substr($this->_moduleToFilename($package), 0, -3);
        $attempts = array($locale, reset(explode('-', $locale)), 'ROOT');
        
        foreach ($attempts as $attempt) {
            $file = $path . "_" . $attempt . '.js';
            if (is_readable($file)) {
                return $file;
            }
        }
        
        return false;
    }
P4Cms_View_Helper_Dojo_Container::getDocumentRoot ( )

Get the file-system path to the document root.

Returns:
string the location of the public folder.
    {
        return $this->_documentRoot;
    }
P4Cms_View_Helper_Dojo_Container::getLocale ( hyphenate = false)

Get the client's (browser) locale - normalized to lower case because that is how dojo likes it.

Parameters:
bool$hyphenateoptional - use hyphen instead of underscore to separate the language from the territory (defaults to false).
Returns:
string the client's locale string.
    {
        if (!$this->_locale) {
            $this->_locale = strtolower(new Zend_Locale);
        }
        
        return $hyphenate 
            ? str_replace('_', '-', $this->_locale) 
            : $this->_locale;
    }
P4Cms_View_Helper_Dojo_Container::setAssetHandler ( P4Cms_AssetHandlerInterface handler = null)

Set the asset handler used to store the aggregated dojo file.

Parameters:
P4Cms_AssetHandlerInterface | null$handlerThe handler to use or null
Returns:
P4Cms_View_Helper_Dojo_Container provides fluent interface.
    {
        $this->_assetHandler = $handler;

        return $this;
    }
P4Cms_View_Helper_Dojo_Container::setAutoBuild ( build)

Enable or disable automatic dojo builds.

Parameters:
bool$buildset to true to enable, false to disable.
Returns:
P4Cms_View_Helper_Dojo_Container provides fluent interface.
    {
        $this->_build = (bool) $build;
        return $this;
    }
P4Cms_View_Helper_Dojo_Container::setDocumentRoot ( path)

Set the file-system path to the document root.

Parameters:
string$paththe location of the public folder.
Returns:
P4Cms_View_Helper_Dojo_Container provides fluent interface.
    {
        $this->_documentRoot = rtrim($path, '/\\');
        return $this;
    }
P4Cms_View_Helper_Dojo_Container::setRender ( elements)

Set which dojo elements you want to be rendered.

Parameters:
array$elementslist of dojo elements to render (e.g. array('extras', 'layers')). valid elements are:

  • config
  • scriptTag
  • extras
  • layers
  • stylesheets
Returns:
P4Cms_View_Helper_Dojo_Container provides fluent interface.
    {
        foreach ($this->_render as $element => $render) {
            if (in_array($element, $elements)) {
                $this->_render[$element] = true;
            } else {
                $this->_render[$element] = false;
            }
        }

        return $this;
    }

Member Data Documentation

P4Cms_View_Helper_Dojo_Container::$_assetHandler = null [protected]
P4Cms_View_Helper_Dojo_Container::$_build = false [protected]
P4Cms_View_Helper_Dojo_Container::$_buildUri = null [protected]
P4Cms_View_Helper_Dojo_Container::$_built = false [protected]
P4Cms_View_Helper_Dojo_Container::$_documentRoot = null [protected]
P4Cms_View_Helper_Dojo_Container::$_locale = null [protected]
P4Cms_View_Helper_Dojo_Container::$_render [protected]
Initial value:
 array(
        'config'        => true,
        'scriptTag'     => true,
        'extras'        => true,
        'layers'        => true,
        'stylesheets'   => true
    )

The documentation for this class was generated from the following file: