技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> 其他 --> 使用YUI 3开发Web应用的诀窍

使用YUI 3开发Web应用的诀窍

浏览:2075次  出处信息
VALUEUI。我们简单的将配置属性中的值设置为input中的值,我还给函数加了第三个参数{source:UI}set函数可以接受第三个参数,这个参数是一个object对象,它的属性可以加入attribute change事件的事件对象(event facade)中,由此就可以区别textboxvalueChange事件是来自内部代码还是用户输入。在bindUI中,我们可能已经这样设置了事件监听:

this._eventHandles.push(this.after(‘valueChange’,this._afterValueChange));

这是一个监听value变化的事件监听,上一个例子也是对<input>value的变化作监听。两个事件名称都一样,实际上,它们都是对一个叫做value的值的变化进行监听,但实际却不一样。通常,对属性变化的监听会放在initializer里,而此例涉及改变UI元素,所以把它放在bindUI中,也提醒我们这个事件响应涉及textbox。事件响应函数如下:

_afterValueChange: function (ev) {
    if (ev.source === UI) {
        return;
    }
    this._inputEl.set(VALUE, ev.newVal);
},

首先我们检查事件对象的source属性。如果事件来自UI,我们直接忽略。在这里,属性名UI和它的值都是任意的,你可以根据自己喜好定义。我在设定value的属性值时定义了UI和它的值,所以在这里我就可以访问UI这一属性,你也可以用其它的键值对。实际上,widget也提供了一个相同功能的常数Y.Widget.UI_SRC,只是名字有点长,所以我宁愿自己定义。

一个小技巧:你可以使用_set代替set来改变只读属性的值。_set方法本来是作为受保护方法,只能在类及其子类中访问的,但是javascript中对象成员都是公有的,所以_set实际上是个公共方法,外部也能访问。即使这样,我们还是会给只读属性声明readOnly:true,并且在API文档里也将这一属性标为只读。

最后一个实例方法是syncUI。前两个方法renderUIbindUI会且仅会执行一次,但syncUI则至少被widget自身调用一次,你也可以在后面的程序中多次调用这个方法。syncUI的作用是根据对象的状态刷新其外观。对象的状态可能一直在变化, 界面也会跟着变化。不过,如何编写这个方法不能一概而论,要根据具体情况。对于简单的用户界面,syncUI可以在每次有变化发生时都重绘界面中的全部内容。而对于复杂的用户界面,重绘整个界面费时且会造成图像闪烁,所以你最好只重绘发生变化的部分,这样的话,你就需要将重绘不同部分的代码分别放在不同的函数中,syncUI会将每一部分只调用一次。还有,在先前的renderUI的例子中,我改变了textbox的值,而只有在syncUI执行之后,这一变化才能在屏幕上显示出来。

更常见的使用方法是给每个UI元素设置单独的重绘函数。这个函数会在初始化时被syncUI调用一次,之后会在配置属性的发生变化后,通过事件响应函数调用。例如

_valueUIRefresh: function (value) {
    this._inputEl.set(VALUE, value);
}

这一函数和其它相似功能的重绘函数会在syncUI中被调用:

syncUI: function () {
    this._valueUIRefresh(this.get(VALUE));
    // other such refreshers
},

after事件响应函数中的代码如下:

_afterValueChange: function (ev) {
    if (ev.source === UI) {
        return;
    }
    this._valueUIRefresh(ev.newVal);
},

与其他模块的通信

在实现一个模块之后,它会和其他模块进行交互。传统的方法是紧耦合(通过Nicholas Zakas的视频中我们可以了解什么是紧耦合什么是松耦合),也就是通过方法调用和属性赋值来将这些模块紧密联系在一起,在这儿就不赘述了。下面我们介绍另一种方法――自定义事件,Y.Base里面包含了你所需要的全部方法。

首先,在initializer中,你要发布这个自定义事件,让大家都知道它:

initializer: function (cfg) {
    this.publish(‘eventName’, { /*… options … */});
},

需要注意的是,事件名称最好是一个常见单词,因为在后面你会经常使用它,常见单词可以避免出现拼写错误。然后,假设你拥有一个对象,例如:

var myWidget = new Y.MyWidget({ /* .. attributes … */ });

此时,你可以为它绑定事件:

myWidget.after(‘eventName’, this._eventNameListener, this);

然而,这样做虽然不像直接的方法调用那样联系紧密,但是由于必须有一个myWidget的实例,所以其实质还是紧耦合。也就是说,在两个模块的通信中,一个模块必须知道另一个的细节,或者存在一个监视模块为它们建立连接。在这个过程中,有两个配置项是非常重要的,broadcast和emitFacade。

第一个,broadcast,可以让你在其他的模块中为这个事件设置监听器。broadcast默认值为0,此时只能用前面所示的那个方法。如果希望事件可以在任何地方被监听,你需要改变broadcast的值。如果只是在沙箱内,broadcast值为1,如果需要在各个沙箱间,则broadcast值为2。一个沙箱如下所示:

YUI().use( ‘module1′, …, ‘moduleN’, function (Y) {
    // this is your sandbox
});

在页面中可以有多个这样的沙箱:

YUI().use( ‘module1′, …, ‘moduleN’, function (Y) {
    // this is your sandbox
});
YUI().use( ‘moduleX-1′, …, ‘moduleX-N’, function (Z) {
    // this is another sandbox
});

如果你设置broadcast值为2,你就可以在沙箱2中监听在沙箱1中发布的事件,具体细节请看Event user guide。我们继续讨论简单沙箱的情况。

要在一个沙箱内监听另一个模块中发布的事件,必须知道的是这个模块的静态属性NAME的值和事件名称。回想下,Y.Base.create方法所带的第一个参数的值,就是NAME属性的值。因此,如果你创建了这样一个模块:

Y.MyWidget = Y.Base.create(
    ‘xxxx’,
    Y.Widget,
    // … and so on

然后在initializer发布了一个’help’事件:

initializer: function (config) {
    this.publish(‘help’, {
        broadcast: 1,
        emitFacade: true
    });
},

那么,要在其他模块的沙箱内监听这个事件,就可以这样做:

Y.after(‘xxxx:help’, function (ev) { … }, this);

在这里调用了Y.after,而不是myWidget.after,所以我不再需要一个实例才能触发这个事件。你也可以用同样的方法来监听DOM事件或者其他的自定义事件,比如’valueChange’等,不同的仅仅是引号前面的前缀。你也可以使用别的东西作为前缀,Y.base会接受这个值,但是通常情况下,Y.base提供的默认值已经足够了。你还需要设置emitFacade值,因为需要一个对象来触发事件,从而为ev.target提供门面值(facade value)。也许你会想,如果监听器所在的模块获得了注册事件模块的对象,那不是重新成为紧耦合了么。但事实并非如此,只要你在监听器模块中不保留这个对象,耦合就不复存在。此外,我们还有更好的办法。

在发布事件时,我们可以添加所有在事件对象中监听器所需要的信息,例如:

this.fire(‘help’, {helpTopic: ‘Event Broadcasting’});

fire()方法的两个参数分别为发布事件的名称(也就是Y.Base为它增加前缀的类的名称)和包含一些特性的对象(这些特性需要复制给事件对象)。这样监听器就不需要为了获取一些信息而遍历注册事件模块,从而达到了松耦合的目的。监听器通过“事件广播”知道有这样的一些模块,甚至可能有很多这样的模块会响应help事件,但并不需要关注是哪一个模块正在响应它。这种方法也简化了日后新模块的添加。

事件和默认行为

要改变一个类的行为,通常的办法是建立一个子类,然后重写它的方法。YUI也可以完成这些工作。你可以用Y.Base.create来创建一个基类Y.Widget,然后用Y.Base.create来创建一个新的类来作为基类的扩展,并给予其一些特殊的行为。例如,先创建基类:

Y.MySimpleWidget = Y.Base.create(
    ‘simpleWidget’,
    Y.Widget,
    [],
    {
        // instance members here, amongst them:
        renderUI: function () {
            this.get(CBX).append(Y.Node.create(‘ … whatever goes into the widget … ‘ ));
        }
    },
    {
        ATTRS: {
            // configuration attributes
        }
        // other static members
    }
);

然后创建子类:

Y.MyFancyWidget = Y.Base.create(
    ‘fancyWidget’,
    Y.MySimpleWidget,
    [],
    {
        renderUI: function () {
            Y.MyFancyWidget.superclass.renderUI.apply(this, arguments);
            this.get(CBX).append(Y.Node.create(‘ … add some bells and whistles … ‘ ));
    }
    // Presumably the fancy version does not need any further static members so I skip the last argument
);

我们可以看到,MyFancyWidget通过添加一些细节改进了MySimpleWidget。但是这在某些情况下会有些问题,所以你需要设计一个更灵活,更容易改变的基类。自定义事件会对你有所帮助。

假设有个排序类,拥有keydirection两个参数,声明如下:

sort: function (key, direction) {
     // sorting happens here
},

如果这个函数的行为在某些情况下需要更改,您可以这么做,在initializer方法中添加自定义事件:

initializer: function (config) {
    // amongst many other things:
    this.publish(SORT, {defaultFn: this._defSortFn});
},

若SORT是一个具有排序功能的实例,你可以这样声明它的sort函数:

sort: function(key, direction) {
    this.fire(SORT, {key:key, direction:direction});
},

这样子,排序函数就转换为一个具有相同参数的事件触发函数。这样只是提供了一个转换接口,你仍然需要通过原始的排序函数来设计一个类:

_defSortFn: function (ev) {
    var key = ev.key, direction = ev.direction;
    // same code as the original sort function
},

这个_defSortFn类的函数体与原始的方法一模一样,达到相同的排序目的。但是你可以从事件对象中知道keydirection参数,只要一段简单的代码段就可以设置一个监听器,并且改变排序方法。

myObjectThatSorts.on(‘sort’, function (ev) {
    var key = ev.key, direction = ev.direction;
    ev.preventDefault();
    // now do your own sort
});

通过preventDefault我让myObjectThatSorts不再执行_defSortFn中的排序方法,从而可以根据我需要的结果做任何事,无论原来的排序是什么样。我甚至不用关心它是否停止,只要监听after事件来翻转UI上用来显示排序方向的箭头。

我也可以改变事件对象。当事件触发时,我们得到的是根据事件对象复制的一个对象,它从before监听器开始传播,通过默认的函数,到达after监听器,然后被丢弃。你可以在过程中改变它的一些属性的值。当然,在默认方法执行后再做任何改变是没有影响的,在执行之前改变事件对象才能对方法有所影响。例如。

myObjectThatSorts.on(‘sort’, function (ev) {
    ev.direction = (ev.direction===’desc’?'asc’:'desc’);
});

这样将得到倒置的排序结果。

YUI_config

在页面中调用一个模块的最简单的方法是通过scirpt标签来引用脚本,或者是将它放在script标签的url指向的combo文件中(combo可以通过手动连接或者支持combo的服务器自动完成)。而将自定义模块集成到YUI Loader中是一个更好的办法,可以极大的改进性能。这种方法很重要的一点是在使用YUI.add()插入模块时引入依赖文件(通过requires的最后一个参数),这样在调用use()时就可以按照正确的顺序调用它们。

对于小的web应用,你可以在页面load时加载所有内容,但是对于大型应用,这样是很不合理的,因为会花费太长的时间。你可以使用很多次use()方法去请求各个模块所需要的文件,然而这种让Loader在模块加载时去查找本模块的依赖文件是非常耗时的,它可能需要建立很多个请求,直到获得它需要的文件为止。相反,你可以预先告诉Loader各个模块和它的依赖文件,这样,当遇到这个模块时,加载器就可以并行的对它们进行处理。

为此,你需要为YUI Lodaer添加模块说明和要求来建立模块依赖关系,最简单的方法就是建立一个包含这些信息的yui_config.js文件(可以改为其他名字),如下所示:

YUI_config = {
    filter:’raw’,
    //combine:false,
    gallery: ‘gallery-2011.02.18-23-10′,
    groups: {
        js: {
            base: ‘build/’,
            modules: {
                ‘myWidget’: {
                    path: ‘myWidget/myWidget.js’,
                    requires: ['widget', 'widget-parent', 'widget-child', 'widget-stdmod', 'transition'],
                    skinnable: true
                },
                // other modules here
            }
        }
        // other groups here
    }
};

将这段代码放在常规的<script>标签内,并放在第一个YUI().use()代码段之前。它用来配置(YUI)库加载前需要的一些全局属性。,就像以前你必须放在YUI().use()的第一个参数一样,现在YUI可以代替你做这些。你可以使用这儿所列的任何配置项。

filter:“min”:产品代码(去除注释后的最简版本);

debugbebug版本(带有一些log语句,包含console组件);

raw:非最简版本(不带log,含有注释);其中后两者通常只应用于开发环境中。

combine:这个配置项仅仅应用在combo后一些难解决的bug的查找时。

gallery:如果你使用了gallery模块,填上它的版本号。

group:这个属性用来描述你的模块。

首先是组的名称,这里叫‘js’,也可以是其他名称。 你可以为放置在相同位置的一系列文件创建一个这样的组,每个组的第一个参数用来声明文件的根路径(可以是相对路径或者绝对路径)。除base之外,还有一些组的基本属性,具体请参照这儿

最后是modules属性,需要在这列举这个组的所有模块。调用每个模块的关键词是模块的名称,也就是你在YUI().add()YUI().use()的第一个参数。path是模块相关文件的位置,可以是在base基础上的相对路径,如果文件放在其他地方,也可以用全路径。其他的属性可以在这儿找到,和放在YUI.add()结尾的一样。requires属性里面可以是YUI modules, gallery modules或者你自己创建的modules,无论是这个组的还是其他组的。此外,如果设置skinnable:true的话,皮肤会被自动加载,就像我在文章开头所讲的那样。

为了简化这些工作,我创建了一个Windows脚本文件,可以为我自动配置YUI_config。它会扫描并阅读每一个模块文件,并提取出每一个YUI.add中的参数。对于我来说,它很有用,但并不一定适合你。

结语

YUI3非常灵活,你可以通过很多方法建立你的模块。比如通过Y.Base来派生,其实我也不是经常这么做,只是偶尔会用到,但在复杂系统中,依然非常推荐使用Y.Base。

建议继续学习:

  1. JQuery,Extjs,YUI,Prototype,Dojo 等JS框架的区别和应用场景简述    (阅读:3067)
  2. CSS实现HTML元素透明的那些事    (阅读:2843)
  3. YUI 还是 jQuery?    (阅读:2744)
  4. 动态加载JavaScript的小实践    (阅读:2249)
  5. The Deferred Evaluation of YUI 3    (阅读:1999)
  6. 使用minify合并YUI请求    (阅读:1972)
  7. YUI3设计中的激进和妥协    (阅读:1651)
QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习
<< 前一篇:常用统计图说明
© 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

京ICP备15002552号-1