Changeset 7975


Ignore:
Timestamp:
Dec 2, 2010, 8:46:30 PM (13 years ago)
Author:
rvelices
Message:

new template features: combine_script, footer_script and get_combined_scripts
migrated public templates only; need more code doc

Location:
trunk
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/include/template.class.php

    r6363 r7975  
    4444  var $html_head_elements = array();
    4545
     46  var $scriptLoader;
     47  var $html_footer_raw_script = array();
     48
    4649  function Template($root = ".", $theme= "", $path = "template")
    4750  {
    4851    global $conf, $lang_info;
    4952
     53    $this->scriptLoader = new ScriptLoader;
    5054    $this->smarty = new Smarty;
    5155    $this->smarty->debugging = $conf['debug_template'];
     
    8387    $this->smarty->register_modifier( 'get_extent', array(&$this, 'get_extent') );
    8488    $this->smarty->register_block('html_head', array(&$this, 'block_html_head') );
     89    $this->smarty->register_function('combine_script', array(&$this, 'func_combine_script') );
     90    $this->smarty->register_function('get_combined_scripts', array(&$this, 'func_get_combined_scripts') );
     91    $this->smarty->register_block('footer_script', array(&$this, 'block_footer_script') );
    8592    $this->smarty->register_function('known_script', array(&$this, 'func_known_script') );
    8693    $this->smarty->register_prefilter( array('Template', 'prefilter_white_space') );
     
    377384  function flush()
    378385  {
     386    if (!$this->scriptLoader->did_head())
     387    {
     388      $search = "\n</head>";
     389      $pos = strpos( $this->output, $search );
     390      if ($pos !== false)
     391      {
     392          $scripts = $this->scriptLoader->get_head_scripts();
     393          $content = array();
     394          foreach ($scripts as $id => $script)
     395          {
     396              $content[]=
     397                  '<script type="text/javascript" src="'
     398                  . Template::make_script_src($script)
     399                  .'"></script>';
     400          }
     401
     402          $this->output = substr_replace( $this->output, "\n".implode( "\n", $content ), $pos, 0 );
     403      } //else maybe error or warning ?
     404    }
     405   
    379406    if ( count($this->html_head_elements) )
    380407    {
     
    473500    }
    474501  }
     502 
     503  function func_combine_script($params, &$smarty)
     504  {
     505    if (!isset($params['id']))
     506    {
     507      $smarty->trigger_error("combine_script: missing 'id' parameter", E_USER_ERROR);
     508    }
     509    $load = 0;
     510    if (isset($params['load']))
     511    {
     512      switch ($params['load'])
     513      {
     514        case 'header': break;
     515        case 'footer': $load=1; break;
     516        case 'async': $load=2; break;
     517        default: $smarty->trigger_error("combine_script: invalid 'load' parameter", E_USER_ERROR);
     518      }
     519    }
     520    $this->scriptLoader->add( $params['id'], $load,
     521      empty($params['require']) ? array() : explode( ',', $params['require'] ),
     522      @$params['path'],
     523      isset($params['version']) ? $params['version'] : 0 );
     524  }
     525
     526
     527  function func_get_combined_scripts($params, &$smarty)
     528  {
     529    if (!isset($params['load']))
     530    {
     531      $smarty->trigger_error("get_combined_scripts: missing 'load' parameter", E_USER_ERROR);
     532    }
     533    $load = $params['load']=='header' ? 0 : 1;
     534    $content = array();
     535   
     536    if ($load==0)
     537    {
     538      if ($this->scriptLoader->did_head())
     539        fatal_error('get_combined_scripts several times header');
     540       
     541      $scripts = $this->scriptLoader->get_head_scripts();
     542      foreach ($scripts as $id => $script)
     543      {
     544          $content[]=
     545              '<script type="text/javascript" src="'
     546              . Template::make_script_src($script)
     547              .'"></script>';
     548      }
     549    }
     550    else
     551    {
     552      if (!$this->scriptLoader->did_head())
     553        fatal_error('get_combined_scripts didn\'t call header');
     554      $scripts = $this->scriptLoader->get_footer_scripts();
     555      foreach ($scripts[0] as $id => $script)
     556      {
     557        $content[]=
     558          '<script type="text/javascript" src="'
     559          . Template::make_script_src($script)
     560          .'"></script>';
     561      }
     562      if (count($this->html_footer_raw_script))
     563      {
     564        $content[]= '<script type="text/javascript">';
     565        $content = array_merge($content, $this->html_footer_raw_script);
     566        $content[]= '</script>';
     567      }
     568
     569      if (count($scripts[1]))
     570      {
     571        $content[]= '<script type="text/javascript">';
     572        $content[]= '(function() {
     573  var after = document.getElementsByTagName(\'script\')[document.getElementsByTagName(\'script\').length-1];
     574  var s;';
     575        foreach ($scripts[1] as $id => $script)
     576        {
     577          $content[]=
     578            's=document.createElement(\'script\'); s.type = \'text/javascript\'; s.async = true; s.src = \''
     579            . Template::make_script_src($script)
     580            .'\';';
     581          $content[]= 'after = after.parentNode.insertBefore(s, after);';
     582        }
     583        $content[]= '})();';
     584        $content[]= '</script>';
     585      }
     586    }
     587    return implode("\n", $content);
     588  }
     589
     590
     591  private static function make_script_src( $script )
     592  {
     593    $ret = '';
     594    if ( url_is_remote($script->path) )
     595      $ret = $script->path;
     596    else
     597    {
     598      $ret = get_root_url().$script->path;
     599      if ($script->version!==false)
     600      {
     601        $ret.= '?v'. ($script->version ? $script->version : PHPWG_VERSION);
     602      }
     603    }
     604    return $ret;
     605  }
     606
     607  function block_footer_script($params, $content, &$smarty, &$repeat)
     608  {
     609    $content = trim($content);
     610    if ( !empty($content) )
     611    { // second call
     612      $this->html_footer_raw_script[] = $content;
     613    }
     614  }
    475615
    476616 /**
     
    645785}
    646786
     787
     788final class Script
     789{
     790  public $load_mode;
     791  public $precedents = array();
     792  public $path;
     793  public $version;
     794  public $extra = array();
     795
     796  function Script($load_mode, $precedents, $path, $version)
     797  {
     798    $this->load_mode = $load_mode;
     799    $this->precedents = $precedents;
     800    $this->path = $path;
     801    $this->version = $version;
     802  }
     803
     804  function set_path($path)
     805  {
     806    if (!empty($path))
     807      $this->path = $path;
     808  }
     809}
     810
     811
     812/** Manage a list of required scripts for a page, by optimizing their loading location (head, bottom, async)
     813and later on by combining them in a unique file respecting at the same time dependencies.*/
     814class ScriptLoader
     815{
     816  private $registered_scripts;
     817  private $did_head;
     818  private static $known_paths = array(
     819      'core.scripts' => 'themes/default/js/scripts.js',
     820      'jquery' => 'themes/default/js/jquery.min.js',
     821      'jquery.ui' => 'themes/default/js/ui/packed/ui.core.packed.js'
     822    );
     823
     824  function __construct()
     825  {
     826    $this->clear();
     827  }
     828
     829  function clear()
     830  {
     831    $this->registered_scripts = array();
     832    $this->did_head = false;
     833  }
     834
     835  function add($id, $load_mode, $require, $path, $version=0)
     836  {
     837    if ($this->did_head && $load_mode==0 )
     838    {
     839      trigger_error("Attempt to add a new script $id but the head has been written", E_USER_WARNING);
     840    }
     841    if (! isset( $this->registered_scripts[$id] ) )
     842    {
     843      $script = new Script($load_mode, $require, $path, $version);
     844      self::fill_well_known($id, $script);
     845      $this->registered_scripts[$id] = $script;
     846    }
     847    else
     848    {
     849      $script = & $this->registered_scripts[$id];
     850      if (count($require))
     851      {
     852        $script->precedents = array_unique( array_merge($script->precedents, $require) );
     853      }
     854      $script->set_path($path);
     855      if ($version && version_compare($script->version, $version)<0 )
     856        $script->version = $version;
     857      if ($load_mode < $script->load_mode)
     858        $script->load_mode = $load_mode;
     859    }
     860  }
     861
     862  function did_head()
     863  {
     864    return $this->did_head;
     865  }
     866
     867  private static function fill_well_known($id, $script)
     868  {
     869    if ( empty($script->path) && isset(self::$known_paths[$id]))
     870    {
     871      $script->path = self::$known_paths[$id];
     872    }
     873    if ( strncmp($id, 'jquery.', 7)==0 )
     874    {
     875      if ( !in_array('jquery', $script->precedents ) )
     876        $script->precedents[] = 'jquery';
     877      if ( strncmp($id, 'jquery.ui.', 10)==0 && !in_array('jquery.ui', $script->precedents ) )
     878        $script->precedents[] = 'jquery.ui';
     879    }
     880  }
     881
     882  function get_head_scripts()
     883  {
     884    do
     885    {
     886      $changed = false;
     887      foreach( $this->registered_scripts as $id => $script)
     888      {
     889        $load = $script->load_mode;
     890        if ($load==0)
     891          continue;
     892        if ($load==2)
     893          $load=1; // we are async -> a predecessor cannot be async because the script execution order is not guaranteed
     894        foreach( $script->precedents as $precedent)
     895        {
     896          if ( !isset($this->registered_scripts[$precedent] ) )
     897          {
     898            trigger_error("Script $id requires undefined script $precedent", E_USER_WARNING);
     899            continue;
     900          }
     901          if ( $this->registered_scripts[$precedent]->load_mode > $load )
     902          {
     903            $this->registered_scripts[$precedent]->load_mode = $load;
     904            $changed = true;
     905          }
     906        }
     907      }
     908    }
     909    while ($changed);
     910
     911    foreach( array_keys($this->registered_scripts) as $id )
     912    {
     913      $this->compute_script_topological_order($id);
     914    }
     915
     916    uasort($this->registered_scripts, array('ScriptLoader', 'cmp_by_mode_and_order'));
     917
     918    $result = array();
     919    foreach( $this->registered_scripts as $id => $script)
     920    {
     921      if ($script->load_mode > 0)
     922        break;
     923      if ( !empty($script->path) )
     924        $result[$id] = $script;
     925      else
     926        trigger_error("Script $id has an undefined path", E_USER_WARNING);
     927    }
     928    $this->did_head = true;
     929    return $result;
     930  }
     931
     932  function get_footer_scripts()
     933  {
     934    if (!$this->did_head)
     935    {
     936      trigger_error("Attempt to write footer scripts without header scripts", E_USER_WARNING);
     937    }
     938    $result = array( array(), array() );
     939    foreach( $this->registered_scripts as $id => $script)
     940    {
     941      if ($script->load_mode > 0)
     942      {
     943        if ( !empty( $script->path ) )
     944        {
     945          $result[$script->load_mode-1][$id] = $script;
     946        }
     947        else
     948          trigger_error("Script $id has an undefined path", E_USER_WARNING);
     949      }
     950    }
     951    return $result;
     952  }
     953
     954  private function compute_script_topological_order($script_id)
     955  {
     956    if (!isset($this->registered_scripts[$script_id]))
     957    {
     958      trigger_error("Undefined script $script_id is required by someone", E_USER_WARNING);
     959      return 0;
     960    }
     961    $script = & $this->registered_scripts[$script_id];
     962    if (isset($script->extra['order']))
     963      return $script->extra['order'];
     964    if (count($script->precedents) == 0)
     965      return ($script->extra['order'] = 0);
     966    $max = 0;
     967    foreach( $script->precedents as $precedent)
     968      $max = max($max, $this->compute_script_topological_order($precedent) );
     969    $max++;
     970    return ($script->extra['order'] = $max);
     971  }
     972
     973  private static function cmp_by_mode_and_order($s1, $s2)
     974  {
     975    $ret = $s1->load_mode - $s2->load_mode;
     976    if (!$ret)
     977      $ret = $s1->extra['order'] - $s2->extra['order'];
     978    return $ret;
     979  }
     980}
     981
    647982?>
  • trunk/themes/default/template/footer.tpl

    r5244 r7975  
    1919  {/if}
    2020
     21{get_combined_scripts load='footer'}
    2122
    2223{if isset($footer_elements)}
  • trunk/themes/default/template/header.tpl

    r7474 r7975  
    4141
    4242{if not empty($page_refresh)    }<meta http-equiv="refresh" content="{$page_refresh.TIME};url={$page_refresh.U_REFRESH}">{/if}
    43 
     43{*
    4444<script type="text/javascript" src="{$ROOT_URL}themes/default/js/scripts.js"></script>
     45*}
    4546<!--[if lt IE 7]>
    4647<script type="text/javascript" src="{$ROOT_URL}themes/default/js/pngfix.js"></script>
    4748<![endif]-->
     49
     50{get_combined_scripts load='header'}
    4851
    4952{if not empty($head_elements)}
  • trunk/themes/default/template/include/datepicker.inc.tpl

    r7798 r7975  
    11
    2 {known_script id="jquery" src=$ROOT_URL|@cat:"themes/default/js/jquery.packed.js"}
    3 {known_script id="jquery.ui" src=$ROOT_URL|@cat:"themes/default/js/ui/packed/ui.core.packed.js"}
    4 {known_script id="jquery.ui.datepicker" src=$ROOT_URL|@cat:"themes/default/js/ui/packed/ui.datepicker.packed.js"}
    5 {known_script id="datepicker.js" src=$ROOT_URL|@cat:"themes/default/js/datepicker.js"}
     2{combine_script id='jquery' load='footer' path='themes/default/js/jquery.packed.js'}
     3{combine_script id='jquery.ui' load='footer' require='jquery' path='themes/default/js/ui/packed/ui.core.packed.js'}
     4{combine_script id='jquery.ui.datepicker' load='footer' require='jquery.ui' path='themes/default/js/ui/packed/ui.datepicker.packed.js'}
     5{combine_script id='datepicker.js' load='footer' require='jquery.ui.datepicker' path='themes/default/js/datepicker.js'}
    66
    77{assign var="datepicker_language" value="themes/default/js/ui/i18n/ui.datepicker-"|@cat:$lang_info.code|@cat:".js"}
    88
    99{if "PHPWG_ROOT_PATH"|@constant|@cat:$datepicker_language|@file_exists}
    10 {known_script id="jquery.ui.datepicker-$lang_info.code" src=$ROOT_URL|@cat:$datepicker_language}
     10{combine_script id="jquery.ui.datepicker-$lang_info.code" path=$datepicker_language}
    1111{/if}
    1212
     
    1515{/html_head}
    1616
    17 <script type="text/javascript">
     17{footer_script}
    1818function pwg_initialization_datepicker(day, month, year, linked_date, checked_on_change, min_linked_date, max_linked_date)
    1919{ldelim}
     
    2222    day, month, year, linked_date, checked_on_change, min_linked_date, max_linked_date);
    2323}
    24 </script>
     24{/footer_script}
  • trunk/themes/default/template/include/resize.inc.tpl

    r5123 r7975  
    1 {known_script id="jquery" src=$ROOT_URL|@cat:"themes/default/js/jquery.packed.js"}
    2 {known_script id="jquery.ui" src=$ROOT_URL|@cat:"themes/default/js/ui/packed/ui.core.packed.js"}
    3 {known_script id="jquery.ui.resizable" src=$ROOT_URL|@cat:"themes/default/js/ui/packed/ui.resizable.packed.js"}
     1{combine_script id='jquery' load='footer' path='themes/default/js/jquery.packed.js'}
     2{combine_script id='jquery.ui' load='footer' require='jquery' path='themes/default/js/ui/packed/ui.core.packed.js'}
     3{combine_script id='jquery.ui.resizable' load='footer' require='jquery.ui' path='themes/default/js/ui/packed/ui.resizable.packed.js'}
    44
    55{* Resize possible *}
    6 {literal}
    7 <script type="text/javascript">
     6{footer_script}{literal}
    87  jQuery().ready(function(){
    98    // Resize possible for list
     
    1918    });
    2019  });
    21 </script>
    22 {/literal}
     20{/literal}{/footer_script}
     21
  • trunk/themes/default/template/index.tpl

    r6951 r7975  
    2828
    2929      {if isset($U_SEARCH_RULES) }
     30                        {combine_script id='core.scripts' load='async' path='themes/default/js/scripts.js'}
    3031      <li><a href="{$U_SEARCH_RULES}" onclick="popuphelp(this.href); return false;" title="{'Search rules'|@translate}" rel="nofollow"><img src="{$ROOT_URL}{$themeconf.icon_dir}/search_rules.png" class="button" alt="(?)"></a></li>
    3132      {/if}
  • trunk/themes/default/template/picture.tpl

    r7957 r7975  
    6060    {/if}
    6161    {if isset($U_CADDIE) }{*caddie management BEGIN*}
     62{combine_script id='core.scripts' load='async' path='themes/default/js/scripts.js'}
    6263<script type="text/javascript">
    6364{literal}function addToCadie(aElement, rootUrl, id)
     
    209210                        {/if}
    210211                        {/foreach}
     212                        {combine_script id='core.scripts' load='async' path='themes/default/js/scripts.js'}
     213                        {combine_script id='rating' load='async' require='core.scripts' path='themes/default/js/rating.js'}
    211214                        <script type="text/javascript">
    212215                                var _pwgRatingAutoQueue = _pwgRatingAutoQueue || [];
     
    214217                                        updateRateText: "{'Update your rating'|@translate|@escape:'javascript'}", updateRateElement: document.getElementById("updateRate"),
    215218                                        ratingSummaryText: "{'%.2f (rated %d times)'|@translate|@escape:'javascript'}", ratingSummaryElement: document.getElementById("ratingSummary") {rdelim} );
    216                                 (function () {ldelim}
     219                                /*(function () {ldelim}
    217220                                var s = document.createElement('script'); s.type = 'text/javascript'; s.async = true; s.src = '{$ROOT_URL}themes/default/js/rating.js';
    218221                                var s0 = document.getElementsByTagName('script')[0]; s0.parentNode.insertBefore(s, s0);
    219                                 })();
     222                                })();*/
    220223                        </script>
    221224                        </div>
     
    229232                <td class="label">{'Who can see this photo?'|@translate}</td>
    230233                <td class="value">
     234{combine_script id='core.scripts' load='async' path='themes/default/js/scripts.js'}
    231235<script type="text/javascript">
    232236{literal}function setPrivacyLevel(selectElement, rootUrl, id, level)
  • trunk/themes/default/template/search.tpl

    r6951 r7975  
    11
    22{* Example of resizeable *}
    3 {*
     3
    44{include file='include/resize.inc.tpl'}
    5 *}
     5
    66
    77{* Example of datepicker *}
    8 {*
     8
    99{include file='include/datepicker.inc.tpl'}
    1010
    11 {literal}
    12 <script type="text/javascript">
     11{footer_script}{literal}
    1312  pwg_initialization_datepicker("#start_day", "#start_month", "#start_year", "#start_linked_date", null, null, "#end_linked_date");
    1413  pwg_initialization_datepicker("#end_day", "#end_month", "#end_year", "#end_linked_date", null, "#start_linked_date", null);
    1514 jQuery().ready(function(){ $(".date_today").hide(); });
    16 </script>
    17 {/literal}
    18 *}
     15{/literal}{/footer_script}
     16
    1917
    2018<div id="content" class="content">
     
    2220  <div class="titrePage">
    2321    <ul class="categoryActions">
     22                        {combine_script id='core.scripts' load='async' path='themes/default/js/scripts.js'}
    2423      <li><a href="{$U_HELP}" onclick="popuphelp(this.href); return false;" title="{'Help'|@translate}" rel="nofollow"><img src="{$ROOT_URL}{$themeconf.icon_dir}/help.png" class="button" alt="(?)"></a></li>
    2524      <li><a href="{$U_HOME}" title="{'Home'|@translate}" rel="Home"><img src="{$ROOT_URL}{$themeconf.icon_dir}/home.png" class="button" alt="{'Home'|@translate}"></a></li>
Note: See TracChangeset for help on using the changeset viewer.