左图是常见的页面布局图,具有相当普遍的使用量与代表意义,本文使用 XSL 打造这种布局的模板框架。
左图的特点是 Header、Sidebar、Footer 三个区域是公共区域,一个普通网站大部分页面都可以抽象出这三个区域,不同的页面只在 Body 区域有差异。那么在制作页面时只要编写 Body 区域,其他位置只要调用(include)公共的 Header、Sidebar、Footer区域就算完成页面了。这是正向思维,因为程序里面经常这么干,功能模块化,按需加载。用这种思路做网页模板,前端人员会抓狂,因为这种框架明显忽视了前端HTML结构的完整性与合理性,CSS、Javascript变得混乱不堪。
用Xsl打造模块框架,使用的恰好是反向思维。设计框架模板的作用是使每个页面都继承自模板,子页面又能重写扩展公共区域。既节省时间重用模块,又能体现灵活性。反向思路的原理是:编写的每个页面是框架模板的一个区域,当前页面被主框架调用,而不是主动调用其他区域。按照左图所示,先编写框架,仅包含 Header、Sidebar、Footer三大区域,Body区域的位置留空,调用一个固定名称的 <xsl:template name="main">...</xsl:template>,元素的名称定为main是约定,以后每个子页面都必须包含此元素。
为了不致概念混乱,xsl文件我们称为网页模板,xsl文件中的 <xsl:template> 直接呼作 template元素,它类似于程序中方法、函数的概念,有名称,且接受参数。
首先打造根级的网页模板,文件名为:mainframe.xsl,主要代码如下:
1 <html> 2 <head> 3 <title>主框架</title> 4 </head> 5 <body> 6 <div class="box"> 7 <div class="header">Header</div> 8 <div class="body"> 9 <div class="main"> 10 <xsl:call-template name="main" /> 11 </div> 12 <div class="sidebar">Sidebar</div> 13 </div> 14 <div class="footer">Footer</div> 15 </div> 16 </body> 17 </html>
上例中仅main元素是变量,其他部分都是确定的静态值,进一步抽象,把静态的 Header、Sidebar、Footer 以及 title 元素(下面需要用到)保存到另一个网页模板文件中去:common.xsl:
1 <xsl:template name="header"> 2 Header 3 </xsl:template> 4 5 <xsl:template name="sidebar"> 6 Sidebar 7 </xsl:template> 8 9 <xsl:template name="footer"> 10 Footer 11 </xsl:template> 12 13 <xsl:template name="pagetitle"> 14 主框架的标题 15 </xsl:template>
16
17 <!--空白的 main -->
18 <xsl:tempate name="main"/>
mainframe.xsl 于是修改为:
1 <xsl:import href="common.xsl" /> 2 ...... 3 <xsl:template match="/"> 4 <html> 5 <head> 6 <title><xsl:call-template name="pagetitle" /></title> 7 </head> 8 <body> 9 <div class="box"> 10 <div class="header"> 11 <xsl:call-template name="header" /> 12 </div> 13 <div class="body"> 14 <div class="main"> 15 <xsl:call-template name="main" /> 16 </div> 17 <div class="sidebar"> 18 <xsl:call-template name="sidebar" /> 19 </div> 20 </div> 21 <div class="footer"> 22 <xsl:call-template name="footer" /> 23 </div> 24 </div> 25 </body> 26 </html> 27 </xsl:template>
mainframe.xsl 只承担了整体排版布局,定义全局CSS、Javascript,定义默认的Header、Sidebar、Footer,它与Body区域无关。 其中 Body 区域调用了一个名为 main 的template元素。在 common.xsl 文档中定义了一个内容空白的名为 main 的 template元素,但实际上该区域不可能为空,因此在以后要做的每个页面(产品页、新闻页、用户后台等等)中,都必须再次定义一个名为 main 的template元素,用作覆盖定义。比如新闻列表页的XSL主要代码如下(文件名为list.xsl):
1 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 2 <xsl:import href="mainframe.xsl"/> 3 4 <xsl:template name="main"> 5 此处是新闻列表页 6 </xsl:template> 7 8 </xsl:stylesheet>
上面有两处地方需要注意,第一是必须在 xsl 文件的开头载入主框架(主框架又载入 common.xsl),其次是此页面默认入口是名为 main 的 template,list.xsl文件中不能存在自动匹配的template元素(如 <xsl:tempate match="/">,因为该元素与存在于mainframe.xsl中)。list.xsl 中的 main 元素是为主框架准备的,制作新闻列表页,完全无需考虑Header、Sidebar、Footer等区域的数据与表现,如果使用C#转换新闻XML数据与list.xsl,得到的HTML却包含了完整的 Header、Body、Sidebar、Footer区域。这个过程,相当于比较粗糙地实现了模板的继承。
网页的标题(title元素)是在mainframe中定义的,但每个页面的标题可能都不同,这就需要在Xsl文件中重写名为pagetitle的template元素,用以覆盖common.xsl中的默认值。list.xsl 文件改动如下:
1 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 2 <xsl:import href="mainframe.xsl"/> 3 4 <xsl:template name="main"> 5 此处是新闻列表页 6 </xsl:template> 7 8 <xsl:template name="pagetitle"> 9 列表页的标题 xxxx 10 </xsl:template> 11 12 </xsl:stylesheet>
上面简单地实现了模板重载。
Xsl的一些坑:
1、必须使用 xsl:import 元素导进来的模板才可以使用同名template元素覆盖默认的定义,如果使用 xsl:include 引入,将产生错误:"不能使用相同导入优先权多次定义命名模板"。
2、使用前面文章介绍的方法,C#编译list.xsl时会把mainframe.xsl与common.xsl当做list.xsl文件中的一部分,如果list.xsl文件不动,仅修改mainframe.xsl与common.xsl两个文件,list.xsl依赖的缓存不会被回收(因为没有依赖前面的2个xsl文件)。
3、在Xsl文件中不能出现两个连续的减号,如 i--,会被认作语法错误。
4、Xml数据源请使用utf-8格式,Xsl文件也请使用Utf-8+BOM。编码不一致会产生莫名其妙的错误。
过去5年,我一直使用Xsl模板技术来武装公司的项目,有OA类、电商类等多种类型的项目,实际应用中感觉它还是能经受得起产品经理与客户的折腾,并且还能保持不错的性能。由于弃用aspx页面,因此有好些比较底层的工作需要手动完成,如页面缓存(服务端、浏览器端)、表单安全验证、控件封装等等,好处就是这么走一遭,团队成员整体提升了对HTML/XML的认识,对http协议也有更细致的了解。
本系列文章至此终。