一个YUI 3应用配方

2011年4月1日起,在2:52 am由萨蒂扬| 开发 | 18评论

锐3已设计围绕模块的应用。 我将不讨论一个模块是什么,因为它已经由Nicholas Zakas在他的演讲可扩展JavaScript的应用程序体系结构描述。 我就坚持到如何建立这些模块。 我会说什么,可以发现在上线文件,连同其他一些替代品,毕竟,这是很好的文档点:告诉你有关的所有可能的方式做事。 这就是为什么这是一个配方,只是当中许多人做的方式。 它还假定一个很小的应用程序,并不像许多层作为尼古拉斯的建议,因为这仅仅是一个文章,而不是一本书。

识别模块

第一步是找出我们需要的模块。 一个好的方法是先切到各个部分的设计应用程序的屏幕:标题栏,菜单栏,内容,侧板或任何其他有可能是有。 然后在库所提供的。 例如,3锐有没有菜单,但有节点的插件MenuNav ,这需要一个基本的菜单嵌套无序的名单<UL>元素结构,并把它们变成积极的菜单。 或者您可能希望检查锐画廊的基本组成部分。 无论如何,最终你会达到的地步,你有一个盒子中,你必须自己填写,布局,让我们的做到这一点。

我建议每个模块放置在其自己的文件和其自己的目录,由同一个名字。 因此, weather模块将weather/weather.js 这样做的原因是因为你的模块可能需要一些造型,一些CSS和图像文件,它很容易内置的装载机,如果你把它们在这种情况下,它可以很容易地找到他们,主样式表将在weather/assets/skins/sam/weather.css ,与其他资产,图像等,一起。 这是假设你不使用YUI的生成器已经这样做的事情,无论如何,但那是另一个故事。 文件夹名称的assetsskins有或多或少的不言自明,但sam是不是很明显。 它是skin装载机的财产为默认值,因为这是设计师后,山姆林德名为锐,附带的默认皮肤。 因为这表明,你把你的名字,对自己的皮肤和skin属性允许你告诉锐加载它们,但为了保持简单,就让我们一起去的默认。

模块文件模板

这是我使用更多的时候,文件的结构,在某一时刻,我将描述:

  / * jslint与开发:真实,UNDEF:真实,newcap:真正的,严格的:真实,maxerr:50 * / 
 / *全球锐* / 
 / ** 
  *模块名称模块创建等等等等 
  * @模块的模块名 
  * / 
 YUI.add(“模块名称”功能(Y){ 
     “使用严格”; 
     / /方便的常量和模块中使用的快捷键 
     VAR郎= Y.Lang的, 
         CBX公司='contentBox', 
        北方新宇='boundingBox的',
        名称='XXXX'; 

 
     / ** 
      *做的XXXX类.... 
      * @类XXXX 
      * @扩展控件 
      * @使用WidgetParent的 
      * @构造 
      {对象} * @ cfg的配置属性 
      * / 
     Y.Xxxx = Y.Base.create( 
        名称, 
         Y.Widget, 
         [Y.WidgetParent] 
         { 
             / /实例成员 
         }, 
         { 
             / /静态成员,特别是: 
             ATTRS:{ 
             } 
         } 
     ); 

 
 },'0 .99',{ 
    要求:['部件','小部件的父'], 
    换肤:真 
 }); 

前两行的意见是JSLint ,我真的建议的JavaScript验证工具的好处。 如果你到网络版本 ,有一个框来设置选项。 在该选项框的底部,你可以看到这些选项到文件本身的编码方式。 如果使用YUI的生成器,它会为你运行JSLint,并为您设置这些选项,但你仍然可以覆盖任何个人文件,如果你想。

doc注释为YUI API文档生成器。 这是头痛,如果包括那些API文档的初始模板,最终你会填补他们。 随着应用的增长,你是无法记住这一切,你需要他们。

现在来实际第一线代码, YUI.add()语句。 这是告诉YUI加载信息模块和其他一些作品的名称和内容。 模块名称通常是命名用字与字之间的所有小写字母和连字符。 这些都是你在API文档在左侧最顶端的索引的名称。 你可以看到没有严格遵循公约,一些模块名称没有任何连字符。 无论如何,这主要是给你,只要你使用它始终。

YUI.add()语句的第二个参数是一个函数接受一个参数,习惯地称为Y 该功能模块包含的身体和Y是沙盒了YUI的实例,它是在这里你可以找到YUI和其他所有的库模块要求的参考。 跳转到的代码框的底部,你可以看到其余.add()方法的参数,版本( '0.99′或还没有应用),此模块的配置,AA系列中的对象属性。 在这里,我告诉装载机,该模块需要widgetwidget-parent ,它有一个皮肤。 上市widget是多余的,因为widget-parent已经要求widget ,但不麻烦,自己的Loader不会两次加载一个模块,并在稍后一点,如果你把一个依赖,你不需要检查其他假设,你可能已经作出:国家所有,它让装载机交易。 你可以找到一个API文档中的所有选项的列表.addModule()方法的装载机。

在函数体中,第一件事就是“use strict”;声明。 这是您的代码以符合ECMAScript的标准,这在这一点上,使你在安全方面,以确保所有的平台,你很可能会在未来遇到的兼容性。 对于年龄较大的口译员,这个宣言是没有什么比一个不分配给任何被忽略的字符串。 “use”声明具有的功能范围,它是安全的放置在函数体内,它比在文件的顶部,所以它不仅影响你定义的模块。 如果你把它在文件的顶部,它也将适用于任何其他JavaScript文件加载之后,其中许多人可能不符合ES5。

随之而来的快捷键和常数,这是只有在使用常数,因为JavaScript有没有常数的概念。 我们称他们在所有的大写字母和下划线,为常数往往是在其他语言。 有两个很好的理由使用常量,特别是字符串常量。 第一是当你写几次相同的字符串,你可能打错其中之一,你只会注意到当一个错误弹出。 如果您使用的常数,JSLint会警告您,当您键入常数的名称是错误的,因为这将是不确定的。 第二个原因是,YUI压缩机可以做得更好,因为常数的名称可以被压缩,而字符串常量不能。 命名常量的好人选配置属性和事件的名称。

如,为Lang Y.Lang Lang捷径都还不错,因为它们允许你键入,解释评估(每个点意味着新的搜索到的对象的成员),他们可以由YUI压缩机压缩。

API文档类的意见后,我们得到实际的声明。 我们有我们的财产申报类Y ,从而Y.Xxxx 我的建议是使用Y.Base.create()来创建它,如下所示。 它只能创建派生类Base (和Widget这也将是一个子类的Base ),但将覆盖的模块,您将使用最需要做其他方式,所以它是不寻常的。 第一个参数是模块的名称, NAME属性描述为Base组件。 按照惯例,“ NAME “属性是骆驼的情况下版本的类的名称。 这名被用作前缀事件(前像结肠的一部分“io:success” )所产生的CSS类名, Widget类(即: “yui3-xxxx-content” ),并为默认实现toString()你会经常看到在调试器的痕迹。 在这里,我使用的常量的值的NAME ,定义类的NAME属性。

第二个参数是它扩展的类。 你会经常使用两种Y.Base ,实用模块,这将有没有用户界面, Y.Widget那些将有一个UI, Y.Plugin.Base插件或任何其他类,如任何来自Y.Base你可能已经使用Y.Base.create()创建的。

第三个参数持有将要使用的扩展。 扩展类的属性和方法,你要到类混合。 良好的扩展候选人是ArrayList Base或任何Widget-Xxxx为子模块Widget AttributeEventTargetPluginHost生效已混合的Base所以你总是可以指望在这三个有。 扩展非常强大,如果你在看源代码Overlay ,你可以看到有什么,但Widget和扩展。 几个扩展到一个组件可以混合,所以第三个参数是一个数组。

最后,我们得到的实际代码。 第四个和第五个参数是包含类的实例和静态成员对象文字。 实例成员的属性和方法到类prototype ,每个实例将得到一个副本,通常需要通过引用this 静态成员是那些将被所有实例共享。

配置属性

这些静态成员的最重要的是ATTRS财产。 这将列出的配置属性将有你的类。 例如,可以说,我们希望有一个配置属性value存放数值,并初步设定为0。 在第五个参数,我们将因此宣告:

  ATTRS:{
    值:{
        值:0
        验证:Lang.isNumber
     }
 } 

我们可以列出任何ATTRS属性在属性的数量和每个人都可以有几个选项,其中两个我这里显示配置。 你可以读选项其余addAttr()方法的Attribute 可以看出,我用我宣布开始在模块声明Lang快捷。 validator必须是一个函数,它的价值,以验证并返回一个布尔。 的所有Y.Lang.isXxxx方法做到底,这样他们就可以直接使用。 对于更详细的验证,二传手或干将,你需要定义功能。 我建议提供作为字符串的函数的名称, Attribute需要解决实际的函数的函数的名称。 例如,如果我是来定义,也就是说, validCodes可以采取一个有效的代码或一个有效的代码数组的属性,但应该总是返回一个数组,我会做:

  ATTRS:{
     validCodes:{
        二传手:“_setValidCodes”,
     }
 } 

我们需要沿其他实例成员申报_setValidCodes在第四个参数的方法Y.Base.create()

  _setValidCodes:函数(值){
    如果(!Lang.isArray(值)){
        值= [值];
     }
    返回值;
 } 

最好是申报的setter,getter和任何但最琐碎的验证作为单独的实例功能,让Attribute解析成实际的函数调用的函数名。

在一般情况下,使用的setter“正常化值,如上图所示,不产生二次影响。 所有配置属性将触发之前和之后能够防止改变属性变化事件与之前的事件。 使用这些变化事件产生任何辅助作用,而不是二传手。 后事件是最好的,因为到那时,你知道,没有阻止,因为其他代码可能已签署前(上)更改事件,并取消它的属性设置。

可以使您的属性或多或少严格取决于你如何定义你的验证和setter。 如果验证程序非常严格,你的属性将是非常严格的,只接受有效的值,在这种情况下,二传手可能是不必要的。 另一方面,你可能无法使用所有的验证,并依靠二传手完全按摩接收到的东西接受任何价值。 例如,你可以有这两个:

 验证:Y.Lang.isBoolean,/ /使接受严格的布尔属性
二传手:布尔,/ /属性接受任何价值,并有布尔变成一个 

塞特斯也可以作为验证。 制定者应该返回被分配给该属性,但它们也可以返回值Y.Attribute.INVALID_VALUE将离开的属性不变,如果拒绝了一个验证它。

我定义了一个配置属性时,我常常为它定义一个常量,在模块的顶部(沿CBXBBX的捷径我的地方),例如:

  var的值='值',
     VALID_CODES ='validCodes“; 

的机会,我将使用这些配置属性模块内的几次,这会来救我的一些麻烦。 但是,要小心,不要使用该常量在声明属性时,不这样做:

  ATTRS:{
     / / ***不要这样做*** / /
    值:{
        值:0
        验证:Lang.isNumber
     }
 } 

如果你这样做,你会得到属性VALUE而不是的value 这是一个JavaScript的问题,不是一个YUI。 此外,要小心,不要对任何超越的配置属性已经声明为基类或任何扩展。 Widget已经有一堆属性的声明(见表格 ),虽然你很难添加boundingBox属性自己,你visibledisabledheightwidth已定义可能很容易忘记。 如果你的使用目的符合什么Widget使用他们,一切都会好起来。 这就是说,你可以改变其中任何定义。 Y.Base.create()合并扩展基类的配置属性的定义,所以,如果你要改变,也就是说,默认值现有的属性,你可以这样做再次宣布在你的子类属性。

要小心,如果你的意思是初始化一个数组或对象的属性。 通过参考对象(数组是对象),如果你初始化一个对象的属性,他们可能都结束了指向同一个对象,这样当你改变它的一部分(从数组中删除一个项目或添加属性对象),你结束所有Base改变一次。实例,但是,有一些内部的逻辑,这将允许您安全地初始化对象和数组常量属性。 如果初始化的值是一个对象或数组文本,然后Base将克隆。 使用valueFn选项,或在其他对象初始化类初始化。

其他静态成员

有两个其他的静态成员,您可以定义在第五的说法Y.Base.create() 如果您正在创建一个插件,你绝对必须申报的NS的财产,如果你没有,插件将无法正常工作,静静地失败。 NS属性必须设置一个将作为属性的名称存储在宿主对象插件,牢记这一点,当你选择这个名字,所以你不要覆盖任何现有的属性的字符串。

如果您正在构建一个部件,你打算支持渐进增强,那么你会使用的HTML_PARSER静态属性。 这是设置为一个对象,其中包含配置属性后命名的属性设置,从分析现有的HTML,要么CSS3选择器或功能,会产生自己的价值观。 渐进增强 Widget的用户指南。

您可能还需要提供要使用你的类的开发人员使用的值。 在文件的顶部声明的常量是完全从模块本身以外的无形。 如果你想向公众提供的常数,这是做它的地方。 这样的例子是HEADERBODYFOOTER的,常量WidgetStdMod (使用它们,你必须使用完全合格的名称: Y.WidgetStdMod.BODY等)。

实例成员

第四个参数Y.Base.create()是将进入prototype创建的类的属性和方法。 通常情况下,我们申报购买物业第一和方法。 我没有任何理由,为了实际上是无关紧要的,既不是JavaScript的也不YUI的需要你这样做,但它可以更容易地找到源文件中的东西。 虽然飞在初始创建的实例的属性可以,我建议明确宣布他们和初始化。 每个属性之前,应先通过一个API文档注释。

属性通常是私人的,它的名字前缀一个下划线。 这是最好的,如果通过配置属性,而不是属性暴露该对象的公共接口。 属性是非常愚蠢的,可配置属性验证,类型转换(通过setter),并产生二次效应(通过更改事件),直到你找到了你想要的所有功能,它往往是不长。

与配置属性,不初始化属性对象或数组,他们最终都会指向同一个对象,你遇到了麻烦。 这是更好地设置属性是持有对象null null 。 另外,不要离开属性没有设置,如果你不知道自己的价值,将它们设置为null代替。 后来,在调试时,属性设置为undefined点错误,通常是一个错字。

基本实例方法

您可能已经注意到,我们没有宣布任何构造,为我们的子类。 Base模块的初始化,然后调用称为方法initializer ,如果它存在,相同的参数,它已经收到这样实例化时,所有目的而言,你可以考虑, initializer是你的构造。 从派生类Base通常需要一个参数被创建时,一个对象,其中包含配置属性。 Base (或Widget ,因为它是一类的Base )读取这个参数,并设置配置属性之前调用initializer Widget Widget ,如果有一个HTML_PARSER财产,它也已被处理和读标记的属性值将设置以及。

initializer方法有几个任务。 首先,它应该设置任何属性,需要初始化对象或数组。 然后,它会发布这个类会产生的所有事件。 EventTarget将允许你触发一个事件尚未公布首次使用默认设置的事件,但即使在这种情况下,我建议你无论如何宣布他们。 这是一个好地方,增加对这些事件的API文档意见,即使它看起来有点古怪,在一个​​函数声明的身体,但有没有更好的地方,这样做的。

将已收到的参数initializer处理,届时,但有时你需要一些额外的选项,用于初始化,你不在乎,保持他们的实际属性。 例如, Base接受的属性on afterbubbleTargetspluginsBase ),虽然它对于那些没有配置属性。 ,同样WidgetParent需要一个children在初始化属性,但有没有这个名字的配置属性。 initializer方法是一个处理它们。 因此,尽管你的类最终会实例上只有一个参数,这个单一的参数可以携带您可能需要的所有信息。

JavaScript有没有析构函数的概念。 Base补偿这允许你声明一个destructor方法,在那里你可以把代码释放你的对象可能采取的资源。 这只是一个部分解决方案,JavaScript解释器并不调用它时自动删除一个对象,所以你仍然负责销毁对象之前抛弃它,但至少你知道将有一个析构函数。

您的类的用户将不会直接调用initializerdestructorBase将打电话给他们需要的时候。 initializer的对象被实例化时,将被称为destructor将被调用时,您的类的用户调用其destroy方法。

,往往产生内存泄漏的事情之一是留下的事件监听器。 Widget尝试以分离的边界框元素中包含的用户界面元素的所有听众,但它可以不分离的任何其他。 Base可以不分离任何在所有的事件监听器。 这是我使用的代码,以帮助我。 沿其他私人,实例的属性,我宣布_eventHandles财产:

  _eventHandles:空, 

然后,在initializer方法,我将它设置一个数组:

 初始化函数(CFG){
     this._eventHandles = [];
     / / ......
 }, 

在相同的initializer (也bindUI如果它是Widget Widget ),我会再附上做听众:

  ,this._eventHandles.push(this.after(“someAttributeChange”,this._afterSomeAttributeChange,本)); 

然后,在destructor ,我有:

 析构函数(){
     y.each(this._eventHandles,功能(处理){
         handle.detach();
     });
 }, 

正是在这里,在initializer ,产生二次效应(你可能会有所不同的属性,应该挂钩的事件监听器bindUI如果这一次要作用,处理与UI)。 正如我刚才所说,属性setter函数应该只处理属性值正常化。 该设置应该超越存储的值产生任何影响,这些应该由事件侦听器处理。 在上述例子中,我已经设置方法_afterSomeAttributeChange听为任何变化someAttribute属性。 事件监听器将接收一个参数,我通常所说的ev事件门面,几个属性,其中之一, newVal包含的值被设置的对象。

Widget实例属性

两个重要的属性Widget用途是BOUNDING_TEMPLATECONTENT_TEMPLATE 最初都设置为“<div></div>”这产生了两个集装箱内的其他大部分部件使用的标准结构。 然而,这可能不适合所有部件,例如, Button Button部件可以更好地服务由<span>内锚(元素<a> )元素,而不是两个的嵌套<div>小号。 事实上,你可能不在乎有contentBox所有Widget不需要你。 这两个实例的属性,你可以设置你想要的任何标记。 例如,对于Button类Button ,我可能有:

  BOUNDING_TEMPLATE:“<A>”
 CONTENT_TEMPLATE:空, 

CONTENT_TEMPLATE设置为null null会告诉你不想Widget contentBox在所有Widget 在这种情况下contentBox配置属性将指向相同的元素,作为boundingBox配置属性。

你不应该把这些模板把整个部件的HTML,使这两个简单的HTML元素和创建任何通过代码的额外的标记renderUI (我们稍后将看到)。

Widget将添加id id属性和它使用的标准类你想要的任何标记,如yui3-xxxxyui3-xxxx-visibleyui3-xxxx-disabled ,其中xxxx是成小写NAME打开属性的值。

Widget实例方法

Widget拆分在几个步骤,其初始化。 除了initializer调用的对象被实例化时,和destructor ,被称为destroy ,这两种方法所处理BaseWidget增加, renderUI bindUIsyncUI Widget render renderUI syncUI建设阶段,将在序列时调用Widgetrender方法是调用。

renderUI方法需要照顾生产的部件的基本HTML。 同时boundingBoxcontentBox已经呈现在这一点上。 如果使用渐进增强, renderUI首先要检查是否已经存在的元素在页面上。 如果我们使用的HTML_PARSER财产,那么引用这些元素的配置属性将被设置,届时,如果没有,我们需要创建它们。

要做到这一点,最简单的方法(假设没有渐进增强)是使用Y.Node.create ,这样的:

  renderUI:函数(){
     CBX = this.get(CBX公司);
     cbx.append(Y.Node.create(Y.substitute(Y.Xxxx.TEMPLATE,CLASS_NAMES)));
 }, 

这是假设了很多东西,我将马上解释。 首先,我CBX不断宣布在第一个代码中所示,在这篇文章中。 然后,它假定Node被载入,哪些Widget使用,所以它是安全的,但它也假定的Y.substitute是存在的,它是可选的。 你必须添加'substitute'requires为您的模块列表。 然后,预计是在一个静态变量所谓的TEMPLATE这是由你来定义以及其他静态类成员(,权利ATTRS等)部件的模板。 最后,它假定有一个恒定的CLASS_NAMES宣布某处。

通常,我宣布CLASS_NAMES在我的模块定义,以及北方新宇和CBX公司(在这篇文章中看到的第一个代码框),像这样:

 北方新宇='boundingBox的“,
     CBX公司='contentBox',
     NAME ='按钮',
     / /其他常量和快捷....
    永进= Y.ClassNameManager.getClassName,
     getClassName =函数(){
         VAR ARGS = Y.Array(参数);
         args.unshift(姓名);
        返回YCM.apply(此参数)与toLowerCase();
     },
    标签='标签',
    按下='压',
    图标='图标',
     CLASS_NAMES = {
         getClassName(按下),按下:
        图标:getClassName(图标),
        标签:getClassName(LABEL),
         noLabel:getClassName('不',标签)
     }; 

届时ClassNameManager Widget CLASS_NAMES恒定对象ClassNameManager (其中还包括Widget )与所创建的属性。 在上述代码中,我首先创建快捷YCM以使以后访问更快,然后我创建的函数的的getClassName ,这是一个私有函数只能在模块定义访问。 函数的工作原理很像Widget widget的名称相同的方法,但我可以用它来 ​​进一步定义的静态值,它是一个静态函数。 这是我做什么以后,当我创造与生成的类名作为自己的财产为对象的CLASS_NAMES 这让我写一个TEMPLATE ,如字符串:

 模板:的“<label class="{label}"> <input/>” 

这是非常愚蠢至今。 我也想融入这个模板值从其他来源,特别是配置属性。 这是我是怎么做的:

  this.get(CBX公司)。追加(Y.Node.create(Y.substitute(模板,CLASS_NAMES,Y.bind(功能(关键,建议中,arg){
    回报(===关键“_”this.get(阿根廷):建议);
 })))); 

我添加了第三个参数到Y.substitute ,功能。 通常情况下,占位符Y.substitute括在大括号之间的字符,但是,如果有一个空间,它会分成两个占位符,部分空间是关键,第二个可选参数。 这都得心应手,当第三个参数是一个功能,比如这里。 函数将接收三个参数,第一个是关键,二是更换对象中发现的价值,这里CLASS_NAMES ,如果有的话,第三个是可选参数。 因此,在上述声明中,我可以使用这样的模板:

 模板:'<label class="{label} for="{_ id}"/> <input id="{_ id}" value="{_ value}" />“ 

Y.substitute会找到{label}它在CLASS_NAMES搜索。 它会找到它,并得到的'yui3-button-label' 然后,它会调用带参数的替换功能'label''yui3-button-label'undefined key是不等于'_'将在第二个参数,原始的类的名称返回值。 当它到达{_ id}有没有财产的价值,称为_CLASS_NAMES ,所以它会调用带参数的替换功能'_' undefined'id' 与平等的key '_'该函数将去获取的价值'id'属性。 它将再次做同样的{_ value}占位符。

隐藏在顶部的所有声明的常量从任何代码以外的模块,但你可能想使他们中的一些可见,如CLASS_NAMES 做静态成员,一节中,最后一个参数Y.Base.create ,你可以有:

  CLASS_NAMES:CLASS_NAMES 

然后将所有的类名对象为可见Y.MyWidget.CLASS_NAMES

我建议你​​做尽可能多的格式,你可以用HTML字符串,这将使部件的内容。 JavaScript中的字符串操作是速度远远超过访问DOM,所以,你打电话之前Y.Node.create该字符串,速度越快你会得到它。

bindUI任何部件被称为是下一个实例方法。 这是你附加的事件侦听器所创建的任何元素renderUI ,例如,在值中的任何变化监听器<input>TEMPLATE上面。 TextBox和配置属性的值应该始终保持同步。 可以通过代码或用户打字输入框的value属性可以改变。 如果从外部代码,文本框应该被刷新,如果从TextBox,它不应该,否则你可能会进入一个无限循环:在TextBox的变化设定value属性,然后设置value在TextBox然后变化和套value属性等。 让我们看看如何处理这种情况。 我们设置的合成上输入框valueChange事件侦听器。 要做到这一点,我们需要添加的的event-valuechange模块requires这个模块列表。

  ,this._eventHandles.push(this._inputEl.after(“valueChange”,this._afterInputChange,本)); 

我们假设对象有一个参考_inputEl中保存的文本框。 听者:

  _afterInputChange:功能(EV){
     this.set(值,ev.target.get(值){来源:界面});
 }, 

在这里,我们假设我们有常量的VALUEUI宣布为'value''ui'分别。 我们只需设置属性value输入框读出的值。 然而,我们增加了第三个参数的设置方法: {source:UI} set方法,可以采取第三个参数,一个对象,其属性将混合的属性更改事件的事件门面。 就是这样,我们可以告诉正在从TextBox或从外部代码设置值之间的差异。 bindUI我们将不得不设置这个监听器:

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

这是在变化的监听对象的value属性,其他的价值变化是<input>盒,他们被称为相同的,毕竟,他们都听取被称为有价值的东西的变化,但不是一回事。 通常情况下,在initializer设置属性变化的听众,但是,因为这会影响UI元素,我们把它bindUI让我们知道文本框将在那里。 听众将有:

  _afterValueChange:功能(EV){
     (ev.source ===用户界面){
        返回;
     }
     this._inputEl.set(值,ev.newVal)的;
 }, 

我们做的第一件事就是检查source事件。 如果从UI然后我们忽略它。 无论是属性的名称, source和其价值, UI是任意的,这些都是我用时,设定value属性,所以这些都是我在监听检查的,但任何名称/值会做一样好。 其实, Widget ,提供一个恒定的Y.Widget.UI_SRC ,但它是一种长期的,所以我可能会使用一个快捷方式,反正。

另一珍闻:您可以设置使用声明为只读属性_set ,而不是set _set方法是为了得到保护,将在内部使用,但我们知道,JavaScript的安全一无所知,所以_set是开放给任何,但至少,我们试图通过宣布属性readOnly:true记录等在API文档。

最后声明syncUI 虽然前两个, renderUIbindUI会被称为一次且仅一次, syncUI将被称为控件本身至少一次,你可能把它几次后。 其目的是为了刷新UI对象,以反映当前状态。 由于国家可能会改变,用户界面​​可能需要随着时间的推移被刷新。 不过,我不能提供一个简单配方处理。 对于一个简单的用户界面, syncUI可能刷新在屏幕上的一切,被称为每次有什么变化。 对于更复杂的用户界面令人耳目一新整个UI可能需要一些时间,造成闪烁,所以你可能想只刷新你需要的点点滴滴。 如果是这样,你将有不同的方法来刷新这些部件, syncUI将调用每个只有一次。 此外,正如我在例如显示renderUI ,我设置文本框的值,但应在完成syncUI

在一般情况下,你将有一个功能,可以单独设置每个UI元素。 该功能将被称为从syncUI一次,初始化的时候,和任意数量后的属性变化事件监听器的时候。 例如,我们可以有:

  _valueUIRefresh:功能(值){
     this._inputEl.set(价值,价值);
 } 

以及其他类似的setter方法可以从syncUI称为:

  syncUI:函数(){
     this._valueUIRefresh(this.get(值));
     / /其他等提神 
 }, 

及后的侦听器:

  _afterValueChange:功能(EV){
     (ev.source ===用户界面){
        返回;
     }
     this._valueUIRefresh(ev.newVal);
 }, 

与他人沟通

一旦您有您的模块完成的逻辑,你想让它与网页上的其他模块交互。 如果你看过尼古拉斯Zakas视频,你已经知道什么是紧密和松散的耦合。 调用方法和属性从一个模块,这些模块紧密结合,这是传统方式的另一种手段,所以我不会谈论它,因为你知道如何做到这一点。 其他的办法做到这一点,是触发自定义事件。 Base已经包含了所有你需要做的。

首先,在initializer ,您publish的自定义事件,你希望大家了解。

 初始化函数(CFG){
     this.publish(“eventName',{/ * ...选项... * /});
 }, 

通常情况下,事件的名称来自一个常数,因为你会使用相同的名称,每次你开除,你不想错别字出现。

一般来说,当你有一个对象的引用,如:

  MyWidget的新Y.MyWidget({/ * ...属性... * /}); 

您可以收听到其事件做:

  myWidget.after(“eventName”,this._eventNameListener,本); 

然而,做到这一点,你需要有一个myWidget参考,这是不一样紧紧加上直接调用其方法,但它仍然是相当紧张:至少有一个模块知道其他,或许,了解上司模块和设置在它们之间的联系。 两个选项是非常重要的模块之间的通信,在broadcastemitFacade

第一, broadcast ,可让您设定事件,在其他模块的听众。 broadcast在0左,默认情况下,你必须做如上所示。 如果你想将聆听到其他地方的事件,你将要broadcast设置为1,这样的事件在同一沙箱,有时2 broadcast ,这样他们就可以去对面的沙箱。 在此背景下,一个沙盘是什么,你当你调用:

  YUI的()。使用(“模块1”,...,moduleN“功能(Y){
     / /这是你的沙箱
 }); 

您可以在您的网页中有几个这样的沙箱:

  YUI的()。使用(“模块1”,...,moduleN“功能(Y){
     / /这是你的沙箱
 });
 YUI的()。使用(“MODULEX-1','MODULEX-N”功能(z){
     / /这是另一个沙箱
 }); 

如果设置为2的broadcast ,然后在第二的沙箱中的对象可以收听到一个事件时,在第一发射。 你可以看到事件的用户指南中的细节。 让我们坚持简单的沙箱的情况下。

要收听在同一沙箱中,你需要知道的价值从另一个模块发射事件NAME该模块的静态属性和事件的名称。 请记住, Y.Base.create需要,作为其第一个参数,值,它将使用其NAME属性,因此,如果您创建了在这样一个模块:

  Y.MyWidget = Y.Base.create(
     “XXXX”
     Y.Widget,
     / / ...等 

然后,在初始化您发布的'help'这样的事件:

 初始化函数(CONFIG){
     this.publish('帮助',{
        广播:1,
         emitFacade:真
     });
 }, 

在同一沙箱中的事件,在任何其他模块来听,你做的事:

  y.after(“XXXX:帮助”功能(EV){...},这); 

在这里,我呼吁Y.after ,不myWidget.after ,我并不需要有一个模块触发事件的参考。 这是相同的方法用于听DOM事件或其他如合成事件'valueChange'唯一的区别在于前缀,冒号前的部分。 Base已经前缀NAME属性值的所有事件,以便照顾你没有发布时他们采取这种照顾。 你可以这样做,你甚至可以用别的东西作为前缀,如果有一个这样的前缀是, Base将尊重它,但通常你只是想,该Base提供的默认。

你还需要设置emitFacade因为你会希望有一个触发事件,该事件的门面中提供的实例参考ev.target 但是别急,如果监听器模块发射模块得到一个参考,不,他们再次变得紧密结合? 不大,只要你不保留在聆听模块,参考,耦合将出现波​​动。 尽管如此,我们可以做的更好。

当触发事件时,我们可以添加听者需要的门面,这样,所有的信息:

  this.fire('帮助',{helpTopic:“广播事件”}); 

fire法被解雇的事件(名称, Base将进一步与前缀类的NAME )和一个对象,其中包含任意数量的属性将被合并到事件门面。 听者并不需要查询的任何信息的发射模块,所有可能需要的是有。 这是宽松的,因为它得到。 监听器只知道一些模块,并有可能会有很多这样的模块,是为帮助“事件广播”的要求,这是真的,它需要知道。 它甚至不关心哪个模块要求它。 可以添加新的模块后,还将为他们工作的帮助系统。

事件和默认行为

通常的解决办法,以改变类的行为是子类,所以你可以覆盖其职能之一,不管它是什么,而不是你想要做的。 你仍然可以做到这一点。 你可以使用Y.Base.create定义模块的基础上,说Y.Widget然后再次使用Y.Base.create使用新的模块为基础,以改变一个特定的行为。 例如,我可能有:

  Y.MySimpleWidget = Y.Base.create(
     “simpleWidget”
     Y.Widget,
     []
     {
         / /实例成员,在他们之中:
         renderUI:函数(){
             。this.get(CBX公司)追加(Y.Node.create(“......无论到部件......”));
         }
     },
     {
         ATTRS:{
             / /配置属性
         }
         / /其他静态成员
     }
 ); 

然后:

  Y.MyFancyWidget = Y.Base.create(
     “fancyWidget”
     Y.MySimpleWidget,
     []
     {
         renderUI:函数(){
             Y.MyFancyWidget.superclass.renderUI.apply(这一点,参数);
             this.get(CBX公司)追加(Y.Node.create(“......添加一些花里胡哨的......”));
     }
     / /大概看中的版本并不需要任何进一步的静态成员,所以我跳过最后一个参数
 ); 

MyFancyWidget提高了MySimpleWidget加入一些花俏。 这可能是太多的麻烦,在某些情况下,你可能会规划为一个基类更灵活,更容易改变。 自定义事件,可以帮助。

试想一下,你有一个类,它有一个sort功能。 排序函数接受一个keydirection参数是这样声明的:

 排序功能(键,方向){
      / /排序在这里发生
 }, 

如果你知道这个函数的行为,在某些情况下可能会改变,你可能做到以下几点。 initializer方法,你可以有:

 初始化函数(CONFIG){
     / /其中包括许多其他的事情:
     this.publish(排序,{defaultFn:this._defSortFn});
 }, 

如果SORT是恒定的含'sort' 然后,你申报的sort这样的功能:

 排序功能(键,方向){
     this.fire(排序,{键:键,方向:方向});
 }, 

简单的sort功能转换成含有相同的参数燃煤事件标准的函数调用。 虽然这是为了提供替代品,你还想要某种排序的类,你通过默认的排序功能:

  _defSortFn:功能(EV){
     VAR键= ev.key,方向= ev.direction;
     / /原来的排序功能相同的代码
 }, 

类排序前,身体_defSortFn可能只是从原来的,一旦您已阅读,相同的keydirection事件门面的参数,但任何其他代码,可以设置一个监听相同的排序事件和改变,例如:

  myObjectThatSorts.on(“排序”功能(EV){
     VAR键= ev.key,方向= ev.direction;
     ev.preventDefault();
     / /现在做自己的排序
 }); 

调用preventDefault我告诉myObjectThatSorts不打电话_defSortFn 我有条件的可以做这个决定的基础上无论我想,我是否可以离开原来的排序提前去或无条件地停止它,因为我在这里做。 我可能甚至不关心永远阻止它,我可能会听到after事件,只需翻转UI中的信号的方式排序了一个箭头的地方。

我也可以改变事件的门面。 门面被开枪事件时,它通过各种传播(上)的听众,默认功能,然后,直到最后被丢弃后听众建立的事件只有一个副本。 在任何时候,你可以改变其属性值。 当然,这并不重要你可能会做后默认的函数被调用的任何变化,但在做任何改变之前(上)听众将达到预设的功能,例如:

  myObjectThatSorts.on(“排序”功能(EV){
     ev.direction =(ev.direction ==='DESC'ASC'?'DESC');
 }); 

这排序倒挂。

YUI_config

最简单的方法得到您的网页上您的模块,包括在自己的它的<script>标记或组合URL指向一个脚本标记(通过在服务器上创建一个文件,该文件是一个文件的手动连接或组合服务服务器支持)。 纳入装载机的自定义模块,是一种更先进的选择,虽然它可能会提高性能。 在这种情况下,重要的一点是确保YUI.add()requires: [...]中的最后一个参数,所以use()将适用于以正确的顺序模块和它的依赖。

对于小型应用程序,你可能会拥有一切从一开始就正如上文所述,加载。 然而,对于较大的应用程序,你可能不希望一切从一开始就加载,因为它可以采取太长。 您可以拨打use()不止一次地要求额外的功能需要。 然而,装载机找到对每个模块的依赖关系时,它加载每个耗时,因为它可能需要几个连续的请求,直到它终于得到它需要的一切。 相反,你可以预先警告装载机模块和它们的依赖,因此,在时机成熟时,它知道如何对付他们,可并行加载。

这样做,你需要添加的模块的描述和要求,YUI加载使用的表来获取模块。 最简单的方法是,建立yui_config.js文件(或任何你想称呼它),它包含了所有这些定义。 该文件将看起来像这样:

  YUI_config = {
    过滤器:“原始”,
     / /组合:虚假,
    画廊:画廊2011.02.18-23-10',
    组:{
         JS:{
            基地:建立/
            模块:{
                 “MyWidget的”:{
                    路径:“MyWidget的/ myWidget.js,
                    要求:'部件','小部件的父','部件的孩子','小部件stdmod','过渡'],
                    换肤:真
                 },
                 / /这里的其他模块
             }
         }
         / /这里的其他群体
     }
 }; 

你包括在常规文件<script>在你的HTML文件的标记之前发出第一的YUI().use()语句。 他们取代那些选项,否则将作为YUI().use()的第一个参数YUI(YUI_config).use() YUI().use()如果你没有YUI(YUI_config).use() ,但锐为你做的。 你可以使用任何在这里列出的选项。

filter选项,可以设置为'min'的生产代码(默认的,所以你通常会注释掉), 'debug'完全扩大与日志报表(这可能会压倒你的控制台)和'raw'完全没有日志扩大报表,最后两个只用于发展。 同样, combine选项,仅用于当你有非常艰难的错误,你要找出到底是怎么回事,在这些巨大的连击丢失。 然后你把你的gallery选项,如果您使用任何画廊模块,冻结您的画廊模块的版本,你知道它的工作原理。

groups选项是你开始描述自己的模块。 第一个名字,在这种情况下, js ,可以是任何东西,无论你想打电话给你的文件组。 每个家庭档案,在一个共同的位置,你可以创建这样一个组。 在每个组的第一个声明是base组相对的主页或绝对路径的文件的位置。 也就是说,基本上,为分组文件的标准,然而,有多个选项, 在这里上市。

最后,在你的modules部分开始列出你的模块。 每个条目的关键是YUI.add YUI().use()模块的名称,您已经使用文件中的第一个参数YUI.add相同,你会在使用的模块列表,当你发出YUI().use()的名称相同YUI().use()在您的应用程序调用。 那么你指定的模块文件的位置,相对于以前的basefullpath如果位于其他地方,其余的声明,并列出YUI.add 这里 ,在最后的选项。 requires名单可以列出锐模块,画廊模块或你自己的模块,无论是在同一组或从您的配置文件中的其他群体。 皮肤会被自动加载设置skinnable:true如果你找到他们,正如我在本文开头的建议。

为了简化自己的事情,我创建了一个 Windows YUI_config 脚本文件 ,建立了我的YUI_config选项。 它基本上是扫描模块文件的文件夹,并读取每个提取的信息,从每个YUI.add调用定义一个假提取我的论点YUI.add功 ​​能。 这使得大量的非常简单化的假设,但它为我工作,因为它是,您可以使用您自己的风险。

结论

YUI3非常灵活,你可以建立自己的模块,在许多方面。 这是不超过一个办法做到这一点,我永远不会做这种方式,有时,不经常,我不需要Base提供如此Y.Base.create什么都没有用,但这个工作大部分的时间。

共享和扩展: 书签del.icio.us Digg它! | reddit!

18评论

  1. 非常感谢这篇文章的很多。 这是很难找到约在官方文档的外观信息。 现在我更清楚。

    PLV - 4月1日起,2011年

  2. 真棒的文章!

    谢谢你,萨蒂扬!

    约翰- 4月1日起,2011年

  3. 精彩文章!

    由丹尼尔·斯托克曼- 4月1日起,2011年

  4. “这是一个JavaScript的问题,而不是一个YUI时。”

    这不是问题,但非常正常的行为。 :)

    约翰- 4月3日,2011年

  5. 谢谢你的文章! 清除了,我有很多疑虑。

    我有一个问题:)我虽然不太了解syncUI方法的使用。 你曾表示其使用“,其目的是为了刷新UI,以反映对象的当前状态”。 难道你不这样做bindUI()方法刷新UI部件的属性,当您注册afterValueChange事件?

    Vignesh - 4月5日,2011年

  6. 我刚刚开始学习YUI3,我认为这篇文章将帮助我深深地理解。

    百合- 4月5日,2011年

  7. [...]支持移动,“简单”的主题,一个新的厨房水槽状部件浏览器,更多的是为YUI 3应用配方 - Satyen YUI团队德赛有关如何组织YUI应用程序的详细进入WebGL的[...]

    pingback的JSMag由 JavaScript 杂志博客»博客存档»新闻综述:IOS视口修复,移动样板,CommunityJS,Ender.js - 2011年4月8日,

  8. 我已经把它进入中国。
    http://ued.taobao.com/blog/2011/04/14/a-recipe-for-a-yui-3-application/~~V

    评论jayli - 4月14日,2011年

  9. @ Vignesh:
    听众的关于syncUI方法,添加在bindUI不被解雇,在同步期间,因为已经设置的对象的状态的时候,你绑定听众,所以没有国家的变化。

    我会用伪代码,以帮助解释:
    OBJ =新Y.Widget({attr1:55});
    / / attr1状态现在是55
    obj.render()
    因此,在渲染过程中,因为没有什么变化,没有变化事件被激发,因此,为什么你可能需要调用的syncUI方法。

    理想的情况下,你应该有一个由变更后的事件,只是被称为_uiSetAttr1方法,可以称为同步过程中,只需处理更新UI的响应,ATTR改变。

    也有属性,有助于使这种胶水布线更容易。 关闭我的头顶,我不记得,如果他们仍然私人,但如果他们(我认为这是无论BIND_UI或BIND_UI_ATTRS用于同步)所有您需要做的是一套方法称为_uiSetAttrname并列出对象中,ATTRS,它会自动为你做什么,所有接线。

    我希望帮助:)

    Nate 卡瓦诺 - 4月16日,2011年

  10. [...]配方为YUI YUI3合金的UI构建可重复使用的小工具中的应用[...]

    pingback的建设Chrome的应用程序使用YUI - (1)“三部曲 - 2011年4月17日,

  11. 内特

    你是指财产_UI_ATTRS这是一个具有两个属性, BINDSYNC对象。 由于其最初的底线信号和文件指出,他们是私人的,除非你真的知道你在做什么,这是更好地远离你的手。 (我的意思是在通用术语'你')

    这些每个人都有一个属性的名字都已经初始化部件,所以任何变化,需要认真进行,不破坏它们的初始值数组。

    对于那些在上市_UI_ATTRS.BIND阵列,将“变更后的”监听连接之前我们bindUI方法将调用像一个名为的方法_uiSetXxxx ,其中xxxx是属性的名称。

    同样,在一个名为配置属性_UI_ATTRS.SYNC会有此之前,我们自己的权利相同_uiSetXxxx称为syncUI方法。 这些每个_uiSetXxxx方法,将收到的属性值,无论是初始值或更改后的值。

    Indeed, this is a clever mechanism and it would be great if it was made public, or an alternative public equivalent were to be provided and thus supported in the long term. A formal statement by the YUI team regarding the long-term support for this mechanism would be welcome.

    Nevertheless, I should have mentioned it or suggested a similar custom approach. So, since the Pandora's box is open, let me explain it.

    Say you have an attribute “myAttr” which has an effect on the UI. You need to provide a method called _uiSetMyAttr (note the M in myAttr turned uppercase) which receives the new value to set and a second argument which might either be undefined (not there) or set to Widget.UI_SRC and affects the UI when the second argument is not set. You would define it in the instance member section like this:

    _uiSetMyAttr: function (value, src) {
    if (src === Y.Widget.UI_SRC) { return; }
    // set the UI element
    }

    To get the UI set initially, right before your own syncUI is called, push the name of the attribute into _UI_ATTRS.SYNC , usually in the initializer , like this:

    this._UI_ATTRS.SYNC.push("myAttr");

    To have it further called after any change in the attribute, push it also into _UI_ATTRS.BIND , like this:

    this._UI_ATTRS.BIND.push("myAttr");

    感谢您的小费。

    Comment by Satyam — April 17, 2011 #

  12. Here's the enhancement request:

    http://yuilibrary.com/projects/yui3/ticket/2529439

    Comment by Satyen Desai — April 18, 2011 #

  13. Heya Satyam, thanks for the detailed writeup. We actually support this as a public attribute in our Alloy Component gallery module since it's so useful, but we allow it to be defined on the class level and handle automatically copying of the arrays so that if you pass in a custom set it is merged with a copy of the defaults instead of having to create a new array and concat it (or doing it in the initializer).

    The only downside I've found to the API (and it's a small one at that) are the times when I want other data from the event passed in (maybe the second or third arg would be good to have the event object being passed in).

    Btw, great article, thanks for consolidating this all here :)

    Comment by Nate Cavanaugh — April 21, 2011 #

  14. [...] Recipe for a YUI 3 Application 原文地址:http://www.yuiblog.com/blog/2011/04/01/a-recipe-for-a-yui-3-application/ 译文:使用YUI [...]

    Pingback by [译]使用YUI 3开发Web应用的诀窍 « Uedmagazine — April 22, 2011 #

  15. A correction to my previous comment :

    The extra attributes to monitor should be added to the _UI_ATTRS arrays via method concat() not push():

    this._UI_ATTRS.SYNC.concat("myAttr");
    this._UI_ATTRS.BIND.concat("myAttr");

    Using push() will add the new attribute to an array which is shared by Widget and all its subclasses while concat() will create a new copy for this particular subclass

    BTW: concat() allows several values to be concatenated at once:

    this._UI_ATTRS.BIND.concat("myAttr1","myAttr2","myAttr3");

    Comment by Satyam — May 14, 2011 #

  16. Sorry I missed once again in my correction . The way to add an attribute to be handled by Widget is this:

    this._UI_ATTRS = {
    BIND: this._UI_ATTRS.BIND.concat("myAttr"),
    SYNC: this._UI_ATTRS.SYNC.concat("myAttr")
    };

    Method concat() does not modify the array it applies to so the code in the previous comment does nothing. Sorry about the confusion.

    Comment by Satyam — May 14, 2011 #

  17. Hi Satyam. 伟大的文章! I'm in doubt with something and hope you could clarify this issue for me. You wrote: “Base cannot detach any event listeners at all”. But Base's destroy() method calls detachAll(), inherited from EventTarget, which according to the documentation, it should remove all listeners. 我错过什么?

    Comment by alejandroci — May 25, 2011 #

  18. alejandroci ,
    You are right, the wording of that phrase is not right, it should have been 'Base cannot remove all events'. Indeed, it does remove some via detachAll() , which it inherits from EventTarget which comes along Attribute. Event Target is capable of removing all events created by itself via this.on and this.after .
    It cannot detach those subscribed to via Y.on , Y.after on events broadcast by other objects (subscribed via the " publisher : event " notation) or via a reference to some other object ( myPublisher.on() ).
    Widget can only detach events picked by delegation to its bounding box.
    So, YUI tries to keep it safe as much as possible with what it knows but it can't do it all, not with any reasonable amount of code. If you forget, YUI will cope as best as it can, but it is better if you do it yourself.

    Comment by Satyam — May 26, 2011 #

很抱歉,评论已被封闭,在这个时候。

主办雅虎

©2006-2012雅虎公司所有权利保留。 隐私政策 - 服务条款

支持WordPress的关于雅虎 虚拟主机