解析顺序
布局xml文件一般位于app/design/{area}/{package}/{theme}/layout/目录下。Layout文件一般包含block、reference、action三种标签。
对于Magento系统,首先会将系统中相关的layout合并,合并一般是包括app\design\frontend\base \default\layout目录下的xml文件,以及指定的模板下面的对应的layout文件。最终普通网页合并之后的从default下 output=”toHtml”进行最终的输出,那么解析的block或其他元素也是从该节点进行解析。
1.顶层的block一般位于page.xml中,如下所示。Output表示通过toHtml进行输出。默认使用3columns.phtml三列布局。Type对应Mage_Page_Block_Html类。
<block type="page/html" name="root" output="toHtml" template="page/3columns.phtml">
2.在顶层的block中,一般包含以下几个关键部分,分别是Html头部、网页头部、内容左部中部右部、网页底部这么几个基本结构布局。
<block type="page/html_head" name="head" as="head">
<block type="page/html_header" name="header" as="header">
<block type="page/html_breadcrumbs" name="breadcrumbs" as="breadcrumbs"/>
<block type="core/text_list" name="left" as="left" translate="label">
<block type="core/text_list" name="content" as="content" translate="label">
<block type="core/text_list" name="right" as="right" translate="label">
<block type="page/html_footer" name="footer" as="footer" template="page/html/footer.phtml">
3.每个模块一般情况下都会有对应的模块xml文件,如目录布局文件为catalog.xml文件,支付为checkout.xml。不过对于magento系统来说,最终还是合并读取的。
4.如果是目录模块被调用,在catalog.xml中,首先会将default节点中所有元素进行加载和解析,然后根据对应产品模块具体页面加载 对应的节点,如分类文件默认采用catalog_category_default节点下的元素更新,如果分类设置为Is Anchor,则采用catalog_category_layered节点下的元素更新。产品默认采用catalog_product_view节点下 的元素更新。
现在我们详细的分析一下page中的各个布局结构
1.Html头部:Html头部主要包括增加默认的js和css相关文件。
<block type="page/html_head" name="head" as="head">
<action method="addJs"><script>prototype/prototype.js</script></action>
<action method="addCss"><stylesheet>css/styles.css</stylesheet></action>
</block>
2.页面头部:主要包括一些头部链接、语言切换等
<block type="page/html_header" name="header" as="header">
<block type="page/template_links" name="top.links" as="topLinks"/>
<block type="page/switch" name="store_language" as="store_language" template="page/switch/languages.phtml"/>
<block type="core/text_list" name="top.menu" as="topMenu" translate="label">
<label>Navigation Bar</label>
</block>
<block type="page/html_wrapper" name="top.container" as="topContainer" translate="label">
<label>Page Header</label>
<action method="setElementClass"><value>top-container</value></action>
</block>
</block>
3.左右部侧栏,一般有product_compare_sidebar、catalog.leftnav、catalog.product.related等侧边栏,具体需要看对应页面的所对应的侧边栏。
4.content内容一般在具体页面中进行指定,不同模块的内容肯定是不同的,在page.xml文件中只是定义了一个as。
5.footer包括了切换store、常见链接等。
解析内容
布局xml文件一般位于app/design/{area}/{package}/{theme}/layout/目录下。Layout文件一般包含block、reference、action三种标签。
1.Block标签指明对应的Html数据块,在指定是一个Block后,系统首先会根据该Block的配置生成一个html数据块,然后再继续解析它所包含的其他内容。
<block type="checkout/cart_sidebar" name="cart_sidebar" template="checkout/cart/sidebar.phtml" before="-">
<action method="addItemRender"><type>configurable</type><block>checkout/cart_item_renderer_configurable</block><template>checkout/cart/sidebar/default.phtml</template></action>
<block type="core/text_list" name="cart_sidebar.extra_actions" as="extra_actions" translate="label" module="checkout">
<label>Shopping Cart Sidebar Extra Actions</label>
</block>
</block>
如在解析type=”checkout/cart_sidebar” block的时候,首先在config.xml找到checkout的block所对应的半类名,得到Mage_Checkout_Block,再和 cart_sidebar组成全称类名为Mage_Checkout_Block_Cart_Sidebar,该Block类所对应的模板文件为 checkout/cart/sidebar.phtml,before=”-”表明在同级别上,它是排在最前面的。Name需要唯一,作为解析和引用使 用。
然后在解析子Block时候,如type=”core/text_list”的Block,会告诉他的父节点cart_sidebar该子节点信息,这 样,在父节点所对应的模板文件中,才能使用getChildHtml(“cart_sidebar.extra_actions”)函数调用子节点的 html信息。
如果有as节点,表示该节点可以在其他地方被引用,也就是说可以在其他地方再次解析,比如as=’left’,则可以在其他地方用reference中进行添加相关block操作。
2.Reference标签指明其他Block Name在该区域的一个引用,Reference所对应的Block一般都有as属性。一般也只有一个name值,表示这一Block会使用该 Reference填充内容,将该Reference下的子Block作为对应Block的子Block进行解析。
3.Action表明指定的Block执行一些特别的动作,比如添加js,css,给常量赋值,调用Block中对应的函数等。
<block type="core/template" name="right.permanent.callout" template="callouts/right_col.phtml">
<action method="setImgSrc"><src>images/media/col_right_callout.jpg</src></action>
<action method="setImgAlt" translate="alt" module="catalog"><alt>Keep your eyes open for our special Back to School items and save A LOT!</alt></action>
</block>
在Block当中调用Mage_Core_Block_Template类解析callouts/right_col.phtml。在该block 下的action中,没有指定节点,表明该action作用于上级Block即right.permanent.callout。在方法中使用 setImgSrc函数,那么对应的,可以在模板中使用getImgSrc获取到action中所包含的值。
在setImgAlt中,也可以使用getImgAlt获取值,不过其中使用translate属性和module属性,那么会调用Catalog中的Helper,对alt中的内容进行翻译成对应的本地化文字。
附:
* Create layout blocks from configuration
*
* @param Mage_Core_Layout_Element|null $parent
*/
public function generateBlocks($parent=null)//Mage_Core_Model_Layout {
if (emptyempty($parent)) {
$parent = $this->getNode();
}
foreach ($parent as $node) {
$attributes = $node->attributes();
if ((bool)$attributes->ignore) {
continue;
}
switch ($node->getName()) {
case 'block':
$this->_generateBlock($node, $parent);
$this->generateBlocks($node);
break;
case 'reference':
$this->generateBlocks($node);
break;
case 'action':
$this->_generateAction($node, $parent);
break;
}
}
}
protected function _generateBlock($node, $parent) {
if (!emptyempty($node['class'])) {
$className = (string)$node['class'];
} else {
$className = Mage::getConfig()->getBlockClassName((string)$node['type']);
}
$blockName = (string)$node['name'];
$_profilerKey = 'BLOCK: '.$blockName;
Varien_Profiler::start($_profilerKey);
$block = $this->addBlock($className, $blockName);
if (!$block) {
return $this;
}
if (!emptyempty($node['parent'])) {
$parentBlock = $this->getBlock((string)$node['parent']);
} else {
$parentName = $parent->getBlockName();
if (!emptyempty($parentName)) {
$parentBlock = $this->getBlock($parentName);
}
}
if (!emptyempty($parentBlock)) {
$alias = isset($node['as']) ? (string)$node['as'] : '';
if (isset($node['before'])) {
$sibling = (string)$node['before'];
if ('-'===$sibling) {
$sibling = '';
}
$parentBlock->insert($block, $sibling, false, $alias);
} elseif (isset($node['after'])) {
$sibling = (string)$node['after'];
if ('-'===$sibling) {
$sibling = '';
}
$parentBlock->insert($block, $sibling, true, $alias);
} else {
$parentBlock->append($block, $alias);
}
}
if (!emptyempty($node['template'])) {
$block->setTemplate((string)$node['template']);
}
if (!emptyempty($node['output'])) {
$method = (string)$node['output'];
$this->addOutputBlock($blockName, $method);
}
Varien_Profiler::stop($_profilerKey);
return $this;
}
protected function _generateAction($node, $parent) {
if (isset($node['ifconfig']) && ($configPath = (string)$node['ifconfig'])) {
if (!Mage::getStoreConfigFlag($configPath)) {
return $this;
}
}
$method = (string)$node['method'];
if (!emptyempty($node['block'])) {
$parentName = (string)$node['block'];
} else {
$parentName = $parent->getBlockName();
}
$_profilerKey = 'BLOCK ACTION: '.$parentName.' -> '.$method;
Varien_Profiler::start($_profilerKey);
if (!emptyempty($parentName)) {
$block = $this->getBlock($parentName);
}
if (!emptyempty($block)) {
$args = (array)$node->children();
unset($args['@attributes']);
foreach ($args as $key => $arg) {
if (($arg instanceof Mage_Core_Model_Layout_Element)) {
if (isset($arg['helper'])) {
$helperName = explode('/', (string)$arg['helper']);
$helperMethod = array_pop($helperName);
$helperName = implode('/', $helperName);
$arg = $arg->asArray();
unset($arg['@']);
$args[$key] = call_user_func_array(array(Mage::helper($helperName), $helperMethod), $arg);
} else {
/** * if there is no helper we hope that this is assoc array */
$arr = array();
foreach($arg as $subkey => $value) {
$arr[(string)$subkey] = $value->asArray();
}
if (!emptyempty($arr)) {
$args[$key] = $arr;
}
}
}
}
if (isset($node['json'])) {
$json = explode(' ', (string)$node['json']);
foreach ($json as $arg) {
$args[$arg] = Mage::helper('core')->jsonDecode($args[$arg]);
}
}
$this->_translateLayoutNode($node, $args);
call_user_func_array(array($block, $method), $args);
}
Varien_Profiler::stop($_profilerKey);
return $this;
}
在addJs、addCss的代码一般在page/html_head类型的block当中,Magento首先会将所有文件存储在$_data当中,最终通过getCssJsHtml函数解析成对应的html代码输出。
Head中支持add类型的方法有addCss、addJs、addCssIe、addJsIe、addLinkRel五种。
<reference name="head">
<action method="addCss"><stylesheet>css/local.css</stylesheet></action>
<action method="addJs"><script>scriptaculous/controls.js</script></action>
<action method="addItem"><type>js</type><name>lib/ds-sleight.js</name><params/><if>lt IE 7</if></action>
</reference>
通用的函数是addItem,需要指定type和name,如果有条件判断的话就放在if标签当中
如果需要删除某个js或者css,可以使用removeItem方法
jslib/ds-sleight.js
其他的action函数需要看其type所对应的类中所支持的函数。
/** * Get HEAD HTML with CSS/JS/RSS definitions * (actually it also renders other elements, TODO: fix it up or rename this method) * * @return string */
public function getCssJsHtml()//Mage_Page_Block_Html_Head extends Mage_Core_Block_Template {
// separate items by types
$lines = array();
foreach ($this->_data['items'] as $item) {
if (!is_null($item['cond']) && !$this->getData($item['cond']) || !isset($item['name'])) {
continue;
}
$if = !emptyempty($item['if']) ? $item['if'] : '';
$params = !emptyempty($item['params']) ? $item['params'] : '';
switch ($item['type']) {
case 'js': // js/*.js
case 'skin_js': // skin/*/*.js
case 'js_css': // js/*.css
case 'skin_css': // skin/*/*.css
$lines[$if][$item['type']][$params][$item['name']] = $item['name'];
break;
default:
$this->_separateOtherHtmlHeadElements($lines, $if, $item['type'], $params, $item['name'], $item);
break;
}
}
// prepare HTML
$shouldMergeJs = Mage::getStoreConfigFlag('dev/js/merge_files');
$shouldMergeCss = Mage::getStoreConfigFlag('dev/css/merge_css_files');
$html = '';
foreach ($lines as $if => $items) {
if (emptyempty($items)) {
continue;
}
if (!emptyempty($if)) {
$html .= '<!--[if '.$if.']>'."\n";
}
// static and skin css
$html .= $this->_prepareStaticAndSkinElements('<link rel="stylesheet" type="text/css" href="%s"%s />' . "\n",
emptyempty($items['js_css']) ? array() : $items['js_css'],
emptyempty($items['skin_css']) ? array() : $items['skin_css'],
$shouldMergeCss ? array(Mage::getDesign(), 'getMergedCssUrl') : null
);
// static and skin javascripts
$html .= $this->_prepareStaticAndSkinElements('<script type="text/javascript" src="%s"%s></script>' . "\n",
emptyempty($items['js']) ? array() : $items['js'],
emptyempty($items['skin_js']) ? array() : $items['skin_js'],
$shouldMergeJs ? array(Mage::getDesign(), 'getMergedJsUrl') : null
);
// other stuff
if (!emptyempty($items['other'])) {
$html .= $this->_prepareOtherHtmlHeadElements($items['other']) . "\n";
}
if (!emptyempty($if)) {
$html .= '<![endif]-->'."\n";
}
}
return $html;
}
布局原理解析
Magento中的布局(Layout)包含一小部分的标记集合,作为详细说明关于程序如何建立一个页面,如何建立它的行为和每个构建的区块。最佳 的布局途径是在每个角度正确的划分和使用。为了让您能够做到这一点,下面是一些行为特性的布局XML标记。
句柄(Handle)
Handle (图1)是一个标识符,决定应用程序要如何通过嵌套的更新处理它。
如果句柄的名称是< default>,然后应用程序知道在加载网店的几乎所有页面之前应该加载此特定页面布局的嵌套更新(我们说’几乎 所有的’,因为一些特殊的页面像产品图片弹出窗口就没有加载布局中的< default>句柄)。
如果Magento找到< default>以外的句柄,它将按照指定的句柄中的页面嵌套更新对页面进行处理。例 如,< catalog_product_view>包含Product View页面的布局更新,而< catalog_product_compare_index>包含Compare Product 页面的更新布局。句柄作为设计人员设置在网店中的标识符,他不需要广泛的理解 Magento编程,也不应该需要修改。
< block>
Magento通过< block>标记决定页面中的每个区块的行为和视觉表现。在Magento中我们已经提到了两种类型的区块-结 构区块(structural blocks)和内 容区块(content blocks)。区分这两种区块最好的方式是通过分配给它的标记属性来区分。结构区块通常包含属性’as’,通过这个属 性值程序可以与指定的区域(由getChildHtml 方法指定)中的模板联系。你会发现在默认布局许多地方出现这个’as’属性,因为默认布局的一个性质就是是建立一个实际的布局,在各个不同的页面 中的具体布局上就可以开始增加。例如,在默认布局中,有像‘left’、‘right’、‘content’和‘footer’这些结构区块。并不是说这 些区块不能存在于正常的布局更新中,但我们为什么不首先在默认布局中建立这些结构区块,然后在后面每个具体的页面基础上添加内容呢?让我们进一步挖 掘< block>的现有属性。
type – 这是模块类的标识符,它定义了区块的功能。此属性不应该被修改。
name – 这是名称,其他的区块可以通过此名称引用此区块(看图3)。
before (and) after – 这两种方法决定内容区块在结构区块中的位置。before="-" 和 after="-"这样的命令标志此区块的位置是一个结构区块的最上方或最下方。
template - 这个属性指定的值决定了此区块的功能是使用哪个模板。例如,如果这个属性值指定了'catalog/category/view.phtml ', 程序就会载入‘app/design/frontend/template/catalog/category/view.phtml ’ 模板文件。要了解布局是如何通过模板执行的,阅读分 步指南建设一个主题。
action – <action> 是用来控制前台的功能的,如加载或不加载一个JavaScript。一套完整的action方式将很快推出,但此时的最佳的学习途径是了解现有的布局更新 上面的不同Action方法。
as – 此属性指 定模板文件中会调用那个区块。当您在模板中看到getChildHtml(' block_name ')的PHP方法,可以肯定它指的是引用属性'as'的值为' block_name '的区块。 (例如:在骨架模板中的方法<?=$this->getChildHtml('header')?>是调用<block as=“header”>)
< reference>
< reference>是用来引用另一个区块。要引用灵位一个区块,在内部的更新将应用于与其关联的< block>(见图 3)。
要使用引用,可以通过区块中的‘name’属性值引用。此属性的指向标签中’name’属性。所以,如果你使用< reference name=”right”>,响应的区块名称将是< block name=”right”>。