Qomolangma实现篇(九):Qomo中的模板系统

一、模板技术概要

模板技术由来以久。在C++中的模板(Template)是编译时技术,而在.NET CLR中用于取代该技术的范型(Generic)则是一种运行时技术。由于范型理论的出现,使范型编程成为一个新的语言课题。在.NET CLR中,范型编程就是一种用模板来实现代码重用的高效技术(类范型和方法范型)。

然而,模板技术的使用也带来了很多的问题。例如:

  • 模板系统的出现,是为了解决了类型问题呢,还是只是解决代码重用的问题?
  • 通过模板来生成可运行代码框架,是否有利于节约代码呢?——而这种代码的节约又增加了多少运行效率上的开销呢?
  • 通过模板来生成框架之后,又是否可进行类型约束呢?类型约束的意义又何在呢?

相关的问题还非常的多。因此在Qomo的设计中,曾经一度为此而迷惑。但最终,我还是在Qomo的内核一层加入了模板系统。

二、Qomo内核中的模板系统

Qomo之所以要将Templet.js放入RTL,是希望在RTL一级支持一种全局性的模板语言,用于组织框架中的重复性元素,包括脚本、样式和HTML等,甚至可以通过Templet来生成类。——如同C++中的模板。

javascript的确不需要为了类型问题而实现一种模板或者范型系统,因此Qomo的模板与C++的模板并不完全等义。这也是我使用Templet这个词,而不是Template的原因之一。这尽管是一个小小的差异,

但也希望引起你的注意。^.^

Templet支持一个用"%...%"格式描述的标签。如果写成"%%",则表明应该替换成单个的"%"。对于标签中的被替换内容,Templet默认认为它来自于模板实例的Attribute。例如:

function MyTemplet() {
  _set('TempletContext', '<font color="red">%Info%</font>');

  Attribute(this, 'Info', 'hello, world!');
}
TMyTemplet = Class(TTemplet, 'MyTemplet');

var ctx = new MyTemplet();
document.writeln(ctx);

这段代码的运行结果,是向Document输入这样一段HTML:

<font color="red">hello, world!</font>

Templet的构造器还可以接受一个参数,它可以是从Qomo的Object继承下来的任意对象。这时,Templet将使用该对象的,而不是模板实例的Attribute。例如与上面的例子等效的代码是:

function MyObject() {
  Attribute(this, 'Info', 'hello, world!');
}
TMyObject = Class(TObject, 'MyObject');

function MyTemplet() {
  _set('TempletContext', '<font color="red">%Info%</font>');
}
TMyTemplet = Class(TTemplet, 'MyTemplet');

var ctx = new MyTemplet(new MyObject());
document.writeln(ctx);

Qomo内核中的Templet,在默认设计上与HTML标签或者其它任何特定的内容都没有关系。因此你可以用它来生成HTML的模板或者CSS的模板,甚至用它来生成javascript的模板并用eval()来执行。

——我想已经有人在开始假设这种技术能被应用到的环境了。哈哈。^.^

三、扩展Templet系统

如果需要对构造器支持的这个参数的Attribute取值进行限制,例如屏蔽对一些特性(Attribute)的读取,那么你也可以通过公布自己的IAttrProvider接口来达到目的。

例如:

function MyTemplet() {
  Attribute(this, 'Info', 'hello, world!');
  Attribute(this, 'Info2', 'hello, world!');
  _set('TempletContext', '<font color="red">%Info%, %Info2%</font>');

  var cls = _cls();
  var Intfs = Aggregate(cls.Create, IAttrProvider);
  var intf = Intfs.GetInterface(IAttrProvider);
  var has = intf.hasAttribute;
  var hasOwn = intf.hasOwnAttribute;

  intf.hasAttribute = function(n, t) {
    return (n=='Info' ? false : has.apply(intf, arguments));
  }
  intf.hasOwnAttribute = function(n, t) {
    return (n=='Info' ? false : hasOwn.apply(intf, arguments));
  }
}
TMyTemplet = Class(TTemplet, 'MyTemplet');

var ctx = new MyTemplet();
document.writeln(ctx);

在这个例子中,我们看到%Info%没有被替换,而%Info2%被替换了。这表明IAttrProvider接口被有效的重写了。

四、Templet系统在Qomo组件框架中的使用

在Qomo的组件框架中,模板系统是一个很关键的核心系统。简单的说,Qomo希望通过模板系统来简化HTML代码的生成。同时模板系统也用于描述某个组件在DOM结构上的基本需求。

为此,Qomo在/Components/Controls/HtmlTemplet.js中实现了一套基础的HTML模板。此外,还使用重写对象构造器的技术,来实现了Qomo组件与HTML中具体的Element的绑定。这里涉及到非常多的技术组件,但主要包括在:

  • /Components/Controls/HtmlTemplet.js
  • /Components/Controls/HtmlController.js

这两个单元的实现中。——事实上,这两个单元是交叉作用的。

首先,在HtmlController.js中,我们使用AOP的技术,拦截了THtmlController的构造过程,并重写了IAttrProvider接口。这样做的目的,是为了实现对THtmlController及其所有的子类进行影响,使得他们的实例可以从Qomo对象的属性(property)与特性(attribute)中来取得值,可以从这个Qomo对象所关联到的HTML Element的性质(HTML Attribute)中得到值。当然,为了完整地实现这个过程,HtmlController.js中还重写了this.get()和this.set()方法。

现在,我们在Qomo中就可以这样写一段代码:

<div id='elem' class="myClass">hello, world!</div>

<script>
var ctrl = new HtmlController();
ctrl.assign(document.getElementById('elem'));

document.writeln(ctrl.get('className'));
</script>

这样,我们看到输出字符串“myClass”,但是,我们并没有为THtmlController这个类声明“className”这样一个特性(attribute),ctrl.get()事实上是从elem这个元素中取得了值。

由于THtmlController类具有这样的功能,因此当一个关联了HTML Element的THtmlController被作为一个TTemplet对象的输入之后,就可以顺理成章地将HTML Element的attributes用于模板系统中了。

然而,这样仍然只是单向的功能。——也就是说,我们是为TTemplet提供了替换标签时所用的对象。这并不表现我们已经构造了“Qomo的组件框架的模板库”。

这后面这个重任就是由HtmlTemplet.js来完成的了。在这个单元里,我们实现了:

  • THtmlTemplet
  • TInlineTemplet
  • TBlockTemplet
  • TTagTemplet
  • TComponentTemplet

由于RTL已经内置了模板系统,因此事实上上面这些类的实现都非常简单。例如:

function TagTemplet() {
  _set('TempletContext', '<%tagName% id="%id%" class="%className%"%Attributes% />');
}
TTagTemplet = Class(THtmlTemplet, 'TagTemplet');

最后,在TComponentTemplet中,我们将组件跟样式(CSS)关联了起来。这样一来,我们就构成了整个Qomo的组件系统的基础逻辑:

  • HTML Element与一个THTMLController组件关联,这是表达在浏览器中的实际对象;
  • 每一个Qomo的组件可以有一个TComponentTemplet来描述该组件使用的DOM结构;
  • 由于Qomo中,通过.js的代码来操作的具体对象,是一个由TComponentTemplet约束过的DOM Element,所以只要其结构不变,则样式、效果的变化将不会影响到实现组件的.js代码。
  • 通过TComponentTemplet模板,我们将CSS、HTML DOM和Qomo组件关联起来,但这种关联非常松散,能轻易地表达“界面与逻辑分离”的UI设计思想。

这种结构,我们可以用下面的代码表现出来(这段代码在Qomo中是可以运行的):

<div id='elem' class="myClass">hello, world!</div>

<script>
var ctrl = new HtmlController('elem');
var temp = new ComponentTemplet(ctrl);
alert(t);
</script>

这段代码最终将显示“elem”对象作为一个Qomo组件的基本结构。

五、其它

本节的内容,主要讲述模板系统,但也讲述了它对于Qomo的组件系统的价值。由于Qomo的组件框架基于“界面与逻辑”分离的理论来进行实践,因此我们后续的文章中,将会用更多的时间来讲述相关的知识,以及Qomo的实现方法。

Qomo的最新内核代码和组件库的基础代码,近期将在开发组内发布。

本文中没有讲述HtmlController()对象如何改变对象构造过程,也没有在代码一层,来讲述它如何与Templet、CSS系统关联。这些是在今后的内容中讨论的。