Create Asset/Package class to reduce code duplication

This commit is contained in:
Dmitriy Simushev 2014-10-14 09:36:55 +00:00
parent f8d4d074d5
commit 71d2b234c6
6 changed files with 246 additions and 120 deletions

View File

@ -43,13 +43,22 @@ class AssetManager implements AssetManagerInterface
*
* @var array
*/
protected $jsAssets = array();
protected $jsAssets = null;
/**
* List of attached CSS assets.
*
* @var array
*/
protected $cssAssets = array();
protected $cssAssets = null;
/**
* Class constructor.
*/
public function __construct()
{
$this->jsAssets = new Package();
$this->cssAssets = new Package();
}
/**
* Sets a request which will be used as a context.
@ -66,8 +75,8 @@ class AssetManager implements AssetManagerInterface
// The request has been changed thus all attaches assets are outdated
// now. Clear them all.
$this->jsAssets = array();
$this->cssAssets = array();
$this->jsAssets = new Package();
$this->cssAssets = new Package();
}
/**
@ -95,11 +104,7 @@ class AssetManager implements AssetManagerInterface
*/
public function attachJs($content, $type = AssetManagerInterface::RELATIVE_URL, $weight = 0)
{
$this->jsAssets[] = array(
'content' => $content,
'type' => $type,
'weight' => $weight,
);
$this->jsAssets->addAsset($content, $type, $weight);
}
/**
@ -107,12 +112,16 @@ class AssetManager implements AssetManagerInterface
*/
public function getJsAssets()
{
return $this->sort(
array_merge(
$this->jsAssets,
$this->triggerJsEvent()
)
);
// If plugins assets are stored in $this->jsAssets several calls to the
// method will duplicate The temporary package is used to avoid such
// behaviour.
$combined_assets = clone $this->jsAssets;
$combined_assets->merge($this->triggerJsEvent());
$assets = $combined_assets->getAssets();
unset($combined_assets);
return $assets;
}
/**
@ -120,11 +129,7 @@ class AssetManager implements AssetManagerInterface
*/
public function attachCss($content, $type = AssetManagerInterface::RELATIVE_URL, $weight = 0)
{
$this->cssAssets[] = array(
'content' => $content,
'type' => $type,
'weight' => $weight,
);
$this->cssAssets->addAsset($content, $type, $weight);
}
/**
@ -132,12 +137,16 @@ class AssetManager implements AssetManagerInterface
*/
public function getCssAssets()
{
return $this->sort(
array_merge(
$this->cssAssets,
$this->triggerCssEvent()
)
);
// If plugins assets are stored in $this->cssAssets several calls to the
// method will duplicate The temporary package is used to avoid such
// behaviour.
$combined_assets = clone $this->cssAssets;
$combined_assets->merge($this->triggerCssEvent());
$assets = $combined_assets->getAssets();
unset($combined_assets);
return $assets;
}
/**
@ -179,7 +188,7 @@ class AssetManager implements AssetManagerInterface
* are plugins options. Modify this array to add or change plugins
* options.
*
* @return array Assets list.
* @return Package Assets list.
*/
protected function triggerJsEvent()
{
@ -198,13 +207,13 @@ class AssetManager implements AssetManagerInterface
'plugins' => array(),
);
EventDispatcher::getInstance()->triggerEvent('pageAddJSPluginOptions', $event);
$assets[] = array(
'content' => sprintf(
$assets->addAsset(
sprintf(
'var Mibew = Mibew || {}; Mibew.PluginOptions = %s;',
json_encode($event['plugins'])
),
'type' => AssetManagerInterface::INLINE,
'weight' => 0,
AssetManagerInterface::INLINE,
0
);
return $assets;
@ -224,7 +233,7 @@ class AssetManager implements AssetManagerInterface
* of their meaning. Modify this array to add or remove additional CSS
* files.
*
* @return array Assets list.
* @return Package Assets list.
*/
protected function triggerCssEvent()
{
@ -244,24 +253,29 @@ class AssetManager implements AssetManagerInterface
* string or an asset array. If a string is used it is treated as an
* absolute URL of the asset. If an array is used it is treated as a
* normal asset array and must have "content" and "type" items.
* @return array A list of normalized assets.
* @return Package A list of normalized assets.
* @throws \InvalidArgumentException If the passed in assets list is not
* valid.
*/
protected function normalizeAssets($assets)
{
$normalized_assets = array();
$normalized_assets = new Package();
foreach ($assets as $asset) {
if (is_string($asset)) {
$normalized_assets[] = array(
'content' => $asset,
'type' => AssetManagerInterface::RELATIVE_URL,
'weight' => 0,
$normalized_assets->addAsset(
$asset,
AssetManagerInterface::RELATIVE_URL,
0
);
} elseif (is_array($asset) && !empty($asset['type']) && !empty($asset['content'])) {
// Weight is optional so we have to make sure it is in place.
$normalized_assets[] = $asset + array('weight' => 0);
$asset += array('weight' => 0);
$normalized_assets->addAsset(
$asset['content'],
$asset['type'],
$asset['weight']
);
} else {
throw new \InvalidArgumentException('Invalid asset item');
}
@ -269,46 +283,4 @@ class AssetManager implements AssetManagerInterface
return $normalized_assets;
}
/**
* Sort assets according to their weights.
*
* If weights of two assets are equal the order from the original array will
* be kept.
*
* @param array $assets The original List of assets.
* @return array Sorted list of assets.
*/
protected function sort($assets)
{
// We should keep order for assets with equal weight. Thus we must
// combine original order and weight property before real sort.
$tmp = array();
$offset = 0;
foreach ($assets as $asset) {
$key = $asset['weight'] . '|' . $offset;
$tmp[$key] = $asset;
$offset++;
}
uksort($tmp, function ($a, $b) {
list($a_weight, $a_offset) = explode('|', $a, 2);
list($b_weight, $b_offset) = explode('|', $b, 2);
if ($a_weight != $b_weight) {
return ($a_weight < $b_weight) ? -1 : 1;
}
// Weights are equal. Check the offset to determine which asset was
// attached first.
if ($a_offset != $b_offset) {
return ($a_offset < $b_offset) ? -1 : 1;
}
return 0;
});
// Artificial sorting keys should be removed from the resulting array.
return array_values($tmp);
}
}

View File

@ -29,15 +29,15 @@ interface AssetManagerInterface
/**
* Indicates that content of an asset is an absolute URL.
*/
const ABSOLUTE_URL = 'absolute';
const ABSOLUTE_URL = Package::ABSOLUTE_URL;
/**
* Indicates that content of an asset is a relative URL.
*/
const RELATIVE_URL = 'relative';
const RELATIVE_URL = Package::RELATIVE_URL;
/**
* Indicates that content of an asset is raw CSS or JS.
*/
const INLINE = 'inline';
const INLINE = Package::INLINE;
/**
* Gets an instance of Assets URL Generator.

View File

@ -0,0 +1,161 @@
<?php
/*
* This file is a part of Mibew Messenger.
*
* Copyright 2005-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Mibew\Asset;
/**
* Represents a manageable set of assets.
*/
class Package
{
/**
* Indicates that assets sould not be sorted.
*/
const SORT_NONE = 'none';
/**
* Indicates that assets should be sorted according their weights.
*/
const SORT_WEIGHT = 'weight';
/**
* Indicates that content of an asset is an absolute URL.
*/
const ABSOLUTE_URL = 'absolute';
/**
* Indicates that content of an asset is a relative URL.
*/
const RELATIVE_URL = 'relative';
/**
* Indicates that content of an asset is raw file content.
*/
const INLINE = 'inline';
/**
* @var array Assets list
*/
protected $assets = array();
/**
* Attaches an asset to the package.
*
* @param type $content Content of the asset. It can be a kind of URL of
* plain content depends on the second argument of the method.
* @param mixed $type Determines asset type. It can be one of
* {@link Package::ABSOLUTE_URL}, {@link Package::RELATIVE_URL}
* or {@link Package::INLINE} constants.
* @param int $weight Weight of the assets. Assets with lower weight will be
* "float" to the begging of the resulting assets array.
*/
public function addAsset($content, $type, $weight)
{
$asset = array(
'content' => $content,
'type' => $type,
'weight' => $weight,
);
if ($type == self::INLINE) {
$this->assets[] = $asset;
} else {
// Relative and absolute URLs should not be added twice.
$this->assets[$content] = $asset;
}
}
/**
* Retrieves all attached assets.
*
* @param mixed $sort Indicates if assets should be sorted. Can be equal to
* either {@link Package::SORT_NONE} or {@link Package::SORT_WEIGHT}
* constant.
* @return array List of attached assets. Each item is an array with
* the following keys:
* - content: string, can be either a kind of URL or raw CSS content.
* - type: mixed, determines asset type. It can be one of
* AssetManagerInterface::ABSOLUTE_URL,
* AssetManagerInterface::RELATIVE_URL or AssetManagerInterface::INLINE
* constants.
* - weight: int, weight of the asset which was set via
* AssetManagerInterface::attachCss method.
*/
public function getAssets($sort = self::SORT_WEIGHT)
{
if ($sort == self::SORT_NONE) {
// Internal artificial keys should be removed
return array_values($this->assets);
}
return $this->sort($this->assets);
}
/**
* Merges the package with another one.
*
* @param Package $package A package that should be merged in the current
* one.
*/
public function merge(Package $package)
{
foreach ($package->getAssets(self::SORT_NONE) as $asset) {
$this->addAsset($asset['content'], $asset['type'], $asset['weight']);
}
}
/**
* Sort assets according to their weights.
*
* If weights of two assets are equal the order from the original array will
* be kept.
*
* @param array $assets The original List of assets.
* @return array Sorted list of assets.
*/
protected function sort($assets)
{
// We should keep order for assets with equal weight. Thus we must
// combine original order and weight property before real sort.
$tmp = array();
$offset = 0;
foreach ($assets as $asset) {
$key = $asset['weight'] . '|' . $offset;
$tmp[$key] = $asset;
$offset++;
}
uksort($tmp, function ($a, $b) {
list($a_weight, $a_offset) = explode('|', $a, 2);
list($b_weight, $b_offset) = explode('|', $b, 2);
if ($a_weight != $b_weight) {
return ($a_weight < $b_weight) ? -1 : 1;
}
// Weights are equal. Check the offset to determine which asset was
// attached first.
if ($a_offset != $b_offset) {
return ($a_offset < $b_offset) ? -1 : 1;
}
return 0;
});
// Artificial sorting keys should be removed from the resulting array.
return array_values($tmp);
}
}

View File

@ -226,6 +226,35 @@ abstract class AbstractController implements
*/
public function render($template, array $parameters = array())
{
// Attach all needed JavaScript files. This is done here to decouple
// templates and JavaScript applications.
$assets = array(
// External libs
'js/libs/jquery.min.js',
'js/libs/json2.js',
'js/libs/underscore-min.js',
'js/libs/backbone-min.js',
'js/libs/backbone.marionette.min.js',
'js/libs/handlebars.min.js',
// Client side templates
$this->getStyle()->getFilesPath() . '/templates_compiled/client_side/templates.js',
// Default client side application files
'js/compiled/mibewapi.js',
'js/compiled/default_app.js',
);
foreach ($assets as $asset) {
$this->getAssetManager()->attachJs($asset, AssetManagerInterface::RELATIVE_URL, -1000);
}
// Localization file is added as absolute URL because URL Generator
// already prepended base URL to its result.
$this->getAssetManager()->attachJs(
$this->generateUrl('js_translation', array('locale' => get_current_locale())),
AssetManagerInterface::ABSOLUTE_URL,
-1000
);
return $this->getStyle()->render($template, $parameters);
}

View File

@ -9,24 +9,6 @@
<!-- Extra CSS files -->
{{cssAssets}}
<!-- External libs -->
<script type="text/javascript" src="{{asset "js/libs/jquery.min.js"}}"></script>
<script type="text/javascript" src="{{asset "js/libs/json2.js"}}"></script>
<script type="text/javascript" src="{{asset "js/libs/underscore-min.js"}}"></script>
<script type="text/javascript" src="{{asset "js/libs/backbone-min.js"}}"></script>
<script type="text/javascript" src="{{asset "js/libs/backbone.marionette.min.js"}}"></script>
<script type="text/javascript" src="{{asset "js/libs/handlebars.min.js"}}"></script>
<!-- Javascript templates -->
<script type="text/javascript" src="{{asset "@CurrentStyle/templates_compiled/client_side/templates.js"}}"></script>
<!-- Default application files -->
<script type="text/javascript" src="{{asset "js/compiled/mibewapi.js"}}"></script>
<script type="text/javascript" src="{{asset "js/compiled/default_app.js"}}"></script>
<!-- Localized string -->
<script type="text/javascript" src="{{route "js_translation" locale=currentLocale}}"></script>
<!-- Extra JavaScript files -->
{{jsAssets}}

View File

@ -14,24 +14,6 @@
<!-- Extra CSS files -->
{{cssAssets}}
<!-- External libs -->
<script type="text/javascript" src="{{asset "js/libs/jquery.min.js"}}"></script>
<script type="text/javascript" src="{{asset "js/libs/json2.js"}}"></script>
<script type="text/javascript" src="{{asset "js/libs/underscore-min.js"}}"></script>
<script type="text/javascript" src="{{asset "js/libs/backbone-min.js"}}"></script>
<script type="text/javascript" src="{{asset "js/libs/backbone.marionette.min.js"}}"></script>
<script type="text/javascript" src="{{asset "js/libs/handlebars.min.js"}}"></script>
<!-- Client side templates -->
<script type="text/javascript" src="{{asset "@CurrentStyle/templates_compiled/client_side/templates.js"}}"></script>
<!-- Default application files -->
<script type="text/javascript" src="{{asset "js/compiled/mibewapi.js"}}"></script>
<script type="text/javascript" src="{{asset "js/compiled/default_app.js"}}"></script>
<!-- Localized string -->
<script type="text/javascript" src="{{route "js_translation" locale=currentLocale}}"></script>
<!-- Extra JavaScript files -->
{{jsAssets}}