Hierarchy
Ext.BaseExt.layout.ContextRequires
Files
管理一次布局中的全局环境.
本类负责执行以下工作:
当布局执行到"读阶段"或"写阶段"时, 必须始终明确当前处于哪个阶段. 多数 Layout中的函数在读阶段被调用, 如 claculate, completeLayout 和 finalizeLayout. 但也有例外, 如beginLayout, beginLayoutCycle 和 finishedLayout 则在写阶段被调用. 当 finishedLayout 在写阶段被调用时, 只能表示它将作为一次布局执行完成之后, 作全部捕捉并进行后续处理.
在读阶段, 对DOM的读操作应该使用相应的ContextItem来进行, 它会 通过提供缓存来避免重复的读取. 在读阶段绝不应该发生写操作! 需要写入的值应该被存入相应的 ContextItem 等待稍后被写入.
写阶段的规则与读阶段相似. 唯一的区别就是 ContextItem 的一些方法如 getStyle 仍然会对之前没有读取过的DOM属性值进行读操作. 这些细节对ContextItem的外部是不可见的, 所以在写阶段仍应该避免对ContextItem进行读操作.
计算布局间的相互依赖需要一次确定次数的循环. 在一次循环中, 某些布局会出去处理其他布局的计算结果. 计算流程为: 将组件树中的所有布局(组件和容器的)排成队列. 初始队列的顺序是自底向上的, 且组件布局 优先, 然后是容器布局(如果存在).
在初始阶段, 会调用所有布局的 beginLayout 方法来清除 来自DOM的干扰值. 换句话说, 这就是一个"写阶段", 此时应该被严格避免对DOM的读操作.
接下来布局进入迭代阶段, 或称之为"多重周期"阶段. 每个周期将会调用layoutQueue中所有布局的 calculate方法. 这些调用属于"写阶段", 此时须完全避免对DOM 的写操作.
规则 1: 重视读/写周期. 总是使用getProp 或 getDomProp 方法来获取计算值; 只使用 getStyle 方法来读取style; 使用 setProp 方法来设入DOM属性值. 当然不可避免的 有些读取操作仍然会直接来自DOM, 但如果 ContextItem 中 有函数做了相应的工作, 就应该用它来做底层地等效替换.
calculate的基本逻辑为: 使用 getProp 或 getDomProp 获取值, 计算结果并通过 setProp 发布. 需要注意的是 getProp 会在获取未知值时返回'undefined'. 但是调用该方法 这个行为会似的布局依赖并追踪这个值(在某种程度上). 换句话说, 布局的调用将会通过属性来请求"触发".
规则 2: 除非属性值是必须的, 否则尽量避免调用getProp. 多余的调用会降低效率因为布局将会依赖于从未真正使用过的值. 这条规则也同样适用于 getDomProp 以及判断函数 hasProp 和 hasDomProp.
因为 getProp 可能返回'undefined', 这通常会导致后续的数学 计算得到NaN. 仅仅是单独传递NaN或最终结果为NaN, 这通常都没有问题. 'undefined'和NaN都会被 Ext.layout.ContextItem.setProp 忽略, 因此多数时候没有必要去注意这种情况. 只有在它导致布局 不被执行或发布一个错误的计算值(非'undefined'或NaN)时, 才变得值得关注.
规则 3: 如果一个布局并没有计算出它需要计算的全部的值, 则calculate 在返回前必须将 done 设为 'false'. 该值初始化时为'true'因为得到 未完成状态比完成状态更容易一些(特别是上层和下层的类层次结构中).
规则 4: 一次布局绝不能返回一次未完成(错误)的结果. 否则将导致其他依赖布局使用错误的值进行计算, 从而 导致更多的错误值, 甚至一些布局在正确的结果尚未返回前就已经错误的标记自己的 done为完成状态. 这将破坏整个计算过程.
规则 5: 每一个值只应该由一个布局发布. 如果多个布局试图发布同一个值, 这将几乎无法避免的破坏规则 4. 为了帮助检测到这个问题, 布局诊断工具会对这种试图从不同的布局来得到一个值的行为设置陷阱.
复合布局的计算会由产生很多个结果. 这些结果值对于其他布局的计算十分重要, 需要尽可能早的通过 Ext.layout.Layout.calculate 来发布以避免不必要循环周期和性能消耗. 可是对于某些结果, 他们因为一些 方式被关联在了一起, 从而使得他们的发布将会是全有或全空的.(典型的遵循规则 4的行为).
规则 6: 一旦确认某些结果是正确的, 就应该立刻将他们发布, 不要等到全部的结果都计算完成才发布. 等待 全部计算都完成将会导致死锁. 这里的关键是在处理过程中遵循规则 4.
有些布局的计算依赖于某些关键值. 例如, HBox依赖width, 在width值未知时什么也做不了. 对于这种情况, 最好的办法 就是通过使用 block 或 domBlock (阻塞属性), 从而使布局等到这些值可用使才继续处理.
规则 7: 当值需要进一步处理才能得到时, 使用block 或 domBlock(阻塞属性). 这样能够尽可能减少浪费地重复计算.
规则 8: 阻塞只能在当前处理无法继续的情况下才能使用. 如果还尚有任何一个值可以被计算, 阻塞都可能导致死锁.
在之前的版本中, 布局由组件的代码直接调用, 有时在子组件如'afterLayout'函数中调用. 随着现在版本的灵活性, 为了解决布局时的复杂性和迭代问题, 这些工作应该由一个专门负责的布局(作为组件或容器)来完成.
规则 9: 用布局集合来解决布局问题, 而不是等待一个布局完成后再去引发执行其他的布局. 这对于现在的布局处理 非常重要, 因为现在处理的整个组件树, 而非孤立的某个布局.
一个最简单的布局执行的时序图大致如下:
Context Layout 1 Item 1 Layout 2 Item 2
| | | | |
---->X-------------->X | | |
run X---------------|-----------|---------->X |
X beginLayout | | | |
X | | | |
A X-------------->X | | |
X calculate X---------->X | |
X C X getProp | | |
B X X---------->X | |
X | setProp | | |
X | | | |
D X---------------|-----------|---------->X |
X calculate | | X---------->X
X | | | setProp |
E X | | | |
X---------------|-----------|---------->X |
X completeLayout| | F | |
X | | | |
G X | | | |
H X-------------->X | | |
X calculate X---------->X | |
X I X getProp | | |
X X---------->X | |
X | setProp | | |
J X-------------->X | | |
X completeLayout| | | |
X | | | |
K X-------------->X | | |
X---------------|-----------|---------->X |
X finalizeLayout| | | |
X | | | |
L X-------------->X | | |
X---------------|-----------|---------->X |
X finishedLayout| | | |
X | | | |
M X-------------->X | | |
X---------------|-----------|---------->X |
X notifyOwner | | | |
N | | | | |
- - - - -
说明:
A. 这是一次从函数run到函数runCycle的调用. 队列中的每个布局calculate函数的都将被调用.
B. 每当calculate函数调用后, 都会检查 done 标记看布局是否完成. 如果完成且此布局对象含有 completeLayout函数, 则布局加入队列等待此函数的调用. 若未完成, 布局会重新加入计算队列, 除非有阻塞属性(blocks)或触发属性(triggers)来控制他的队列位置.
C. 对Item属性调用getProp, 会将它作为触发属性进行监听 (以请求的属性名称作为键值). 对属性的修改会导致布局被重新加入计算队列. 调用 setProp 则会将值设入Item属性而不是直接设入DOM.
D. 以同样的循环, 调用其他布局(对每个布局重复步骤B和C.), 进入第一个周期.
E. 当一个周期结束, 如果有处理被执行(新的属性被写入上下文)且layoutQueue队列非空, 则进入下一个周期. 如果无处理被执行或队列中已没有待运行的布局, 则将所有缓存值写入DOM(一次刷新).
F. 刷新后, 所有done标记完成状态的布局, 调用其 completeLayout函数(如果有). 这会是他们再次被标记 为未完成(参见invalidate), 由于calculate也将被调用, 此时应认为处于"读阶段", 应该避免所有对DOM的写操作.
G. 刷新和调用任何进行中的completeLayout函数 通常会由调用getDomProp 使得触发属性产生新的布局计算, 以及由调用domBlock 访问阻塞属性从而解除阻塞中布局. 这些属性变量出现在布局需要得到DOM中的准确值却无法直接获取时. 如果此过程中未使得任何布局加入队列, 则认为布局失败. 否则, 我们继续下一循环周期.
H. 在循环周期的开始调用队列中所有布局的calculate函数. 重复步骤B到G.
I. 一旦布局完成了全部自己负责的计算, 就可以将自己的done置为完成状态. 此状态值等于刚进入calculate时的值, 且如果还有任何未完成的工作, 都应清除此状态值.
J. 现在所有的布局都已完成, 刷新所有的DOM值并调用completeLayout. 这将再次导致布局进入未完成状态, 然后我们也将再次回到另一个可能发生循环周期中.
K. 在所有的布局全部完成后, 调用所有布局的finalizeLayout函数. 由于completeLayout 的调用将导致布局再次变成未完成状态. 这比使用completeLayout 来说还是不太理想因为这将导致所有的 finalizeLayout 函数都被再次调用, 即使是在我们认为事情都已结束的情况下.
L. 在完成最后一次迭代之后, 所有布局的finishedLayout函数(如果有)将 被调用. 此调用针对每次run仅会发生一次, 且不会再导致进一步的布局.
M. 在finishedLayout 完成调用之后, 所有布局的notifyOwner函数(如果有)将 被调用. 此调用针对每次run仅会发生一次, 且不会再导致进一步的布局.
N. 一次最后的刷新以保证所有数据都被写入DOM.
很多布局问题需要多个布局间的协作. 某些情况下, 这可以被简单的理解为一个组件所属的容器的布局的计算结果提供 给组件的布局使用, 反之亦然. 一种稍远一些的协作出现在设置了最大伸展属性和盒布局中: 子组件的布局的计算结果 提供给父组件的布局, 再由父组件的布局来决定子组件的大小.
容器和其子组件直接各式各样的依赖关系, 由每个组件的尺寸模型(getSizeModel) 来描述.
为了保证协作的完成, 下面几对属性将被赋入组件的 ContextItem中:
扩展事件
Defaults to: []
待执行的布局队列.
待执行的布局队列.
本身
获取当前类的引用,此对象被实例化。不同于 statics,
this.self
是依赖范围,它意味着要使用动态继承。
参见 statics 详细对比
Ext.define('My.Cat', {
statics: {
speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
},
constructor: function() {
alert(this.self.speciesName); // 依赖 'this'
},
clone: function() {
return new this.self();
}
});
Ext.define('My.SnowLeopard', {
extend: 'My.Cat',
statics: {
speciesName: 'Snow Leopard' // My.SnowLeopard.speciesName = 'Snow Leopard'
}
});
var cat = new My.Cat(); // alerts 'Cat' 猫
var snowLeopard = new My.SnowLeopard(); // alerts 'Snow Leopard' 雪豹
var clone = snowLeopard.clone();
alert(Ext.getClassName(clone)); // alerts 'My.SnowLeopard'
成员
调用原来的方法,这是以前的override重写
Ext.define('My.Cat', {
constructor: function() {
alert("I'm a cat!");
}
});
My.Cat.override({
constructor: function() {
alert("I'm going to be a cat!");
this.callOverridden();
alert("Meeeeoooowwww");
}
});
var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
// alerts "I'm a cat!"
// alerts "Meeeeoooowwww"
This method has been deprecated since 4.1
版本 使用 callParent 代替.
参数的参数,数组或'参数'对象
来自当前方法,例如: this.callOverridden(arguments)
返回调用重写方法的结果。
所谓的"parent"方法是指当前的方法。 这是以前的方法派生或重写(参见 Ext.define)。
Ext.define('My.Base', {
constructor: function (x) {
this.x = x;
},
statics: {
method: function (x) {
return x;
}
}
});
Ext.define('My.Derived', {
extend: 'My.Base',
constructor: function () {
this.callParent([21]);
}
});
var obj = new My.Derived();
alert(obj.x); // alerts 21
这可以用来重写如下:
Ext.define('My.DerivedOverride', {
override: 'My.Derived',
constructor: function (x) {
this.callParent([x*2]); // 调用原来的My.Derived构造
}
});
var obj = new My.Derived();
alert(obj.x); // 现在提示 42
This also works with static methods.
Ext.define('My.Derived2', {
extend: 'My.Base',
statics: {
method: function (x) {
return this.callParent([x*2]); // 调用 My.Base.method
}
}
});
alert(My.Base.method(10); // alerts 10
alert(My.Derived2.method(10); // alerts 20
然后,它也可以重写静态方法。
Ext.define('My.Derived2Override', {
override: 'My.Derived2',
statics: {
method: function (x) {
return this.callParent([x*2]); // 调用 My.Derived2.method
}
}
});
alert(My.Derived2.method(10); // 现在提示 40
这个参数, 通过当前方法得到数组或者 arguments
对象,
例如: this.callParent(arguments)
返回调用父类的方法的结果。
通过遍历刷新队列(flushQueue)中的ContextItem, 来刷新所有未写入DOM的属性值.
这个类的初始化配置。典型例子:
Ext.define('My.awesome.Class', {
// 这是默认配置
config: {
name: 'Awesome',
isAwesome: true
},
constructor: function(config) {
this.initConfig(config);
}
});
var awesome = new My.awesome.Class({
name: 'Super Awesome'
});
alert(awesome.getName()); // 'Super Awesome' 超级棒
配置
mixins 混入原型 键-值对
清除一个或多个组件的布局(组件和容器)的属性值. 此函数可以在布局运行前被调用, 来识别需要进行布局的组件; 也可以在布局运行中用来重启某组件的布局.
一个Components或Component数组.
组件父容器的ContextItem.
'ture' 为所有属性值都需要清除, 否则只有那些由组件计算的属性值被清除.
将一个ContextItem加入Ext.layout.ContextItem.flushAnimations函数的执行队列
将一个ContextItem加入下次刷新DOM的执行队列, 此函数只应由Ext.layout.ContextItem调用.
将一个组件(及其子组件树)加入下一周期属性值清除的队列
执行属性值清除的组件或ContextItem.
一个对象,用于描述如何执行属性值清除 (详情参见Ext.layout.ContextItem.invalidate).
将一个布局加入下次计算循环周期的队列. 当布局为完成, 阻塞状态或已在队列中时不应调用此函数.此函数只能由 本类和Ext.layout.ContextItem调用.
加入队列的布局.
重置布局对象. 此函数在运行开始或运行中时由 invalidate 调用.
Set the size of a component, element or composite or an array of components or elements. 为一个组件, 元素, 组合结构或组件,元素的数组的尺寸大小赋值
赋值对象.
新的宽度值 (undefined 和 NaN将被忽略).
新的高度值 (undefined 和 NaN将被忽略).
获取从该对象被实例化的类的引用。 请注意不同于 self,
this.statics()
是独立的作用域,无论this
是否运行,总是返回其中的调用类。
Ext.define('My.Cat', {
statics: {
totalCreated: 0,
speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
},
constructor: function() {
var statics = this.statics();
alert(statics.speciesName); // 总是等于'Cat',无论'this'是什么,
// 相当于:My.Cat.speciesName
alert(this.self.speciesName); // 依赖 'this'
statics.totalCreated++;
},
clone: function() {
var cloned = new this.self; // 依赖 'this'
cloned.groupName = this.statics().speciesName; // 相当于: My.Cat.speciesName
return cloned;
}
});
Ext.define('My.SnowLeopard', {
extend: 'My.Cat',
statics: {
speciesName: 'Snow Leopard' // My.SnowLeopard.speciesName = 'Snow Leopard'
},
constructor: function() {
this.callParent();
}
});
var cat = new My.Cat(); // alerts 'Cat', 然后提示 'Cat'
var snowLeopard = new My.SnowLeopard(); // alerts 'Cat', 然后提示 'Snow Leopard'
var clone = snowLeopard.clone();
alert(Ext.getClassName(clone)); // alerts 'My.SnowLeopard'
alert(clone.groupName); // alerts 'Cat'
alert(My.Cat.totalCreated); // alerts 3
配置扩展
方法/属性添加到这个类的原型。
Ext.define('My.awesome.Cat', {
constructor: function() {
...
}
});
My.awesome.Cat.implement({
meow: function() {
alert('Meowww...');
}
});
var kitty = new My.awesome.Cat;
kitty.meow();
成员
添加/重写这个类的静态属性。
Ext.define('My.cool.Class', {
...
});
My.cool.Class.addStatics({
someProperty: 'someValue', // My.cool.Class.someProperty = 'someValue'
method1: function() { ... }, // My.cool.Class.method1 = function() { ... };
method2: function() { ... } // My.cool.Class.method2 = function() { ... };
});
成员
this
这个类的原型借用另一个类的成员
Ext.define('Bank', {
money: '$$$',
printMoney: function() {
alert('$$$$$$$');
}
});
Ext.define('Thief', {
...
});
Thief.borrow(Bank, ['money', 'printMoney']);
var steve = new Thief();
alert(steve.money); // alerts '$$$'
steve.printMoney(); // alerts '$$$$$$$'
this 借用成员
创建这个类的新实例。
Ext.define('My.cool.Class', {
...
});
My.cool.Class.create({
someConfig: true
});
所有参数传递至类的构造。
创建的实例。
创建现有的原型方法的别名。例如:
Ext.define('My.cool.Class', {
method1: function() { ... },
method2: function() { ... }
});
var test = new My.cool.Class();
My.cool.Class.createAlias({
method3: 'method1',
method4: 'method2'
});
test.method3(); // test.method1()
My.cool.Class.createAlias('method5', 'method3');
test.method5(); // test.method3() -> test.method1()
别名新方法的名称,或对象设置多个别名。 参见flexSetter
原来的方法名
以字符串格式,获取当前类的名称。
Ext.define('My.cool.Class', {
constructor: function() {
alert(this.self.getName()); // alerts 'My.cool.Class'
}
});
My.cool.Class.getName(); // 'My.cool.Class'
className 类名
重写这个类的成员。通过callParent重写的方法可以调用。
Ext.define('My.Cat', {
constructor: function() {
alert("I'm a cat!");
}
});
My.Cat.override({
constructor: function() {
alert("I'm going to be a cat!");
this.callParent(arguments);
alert("Meeeeoooowwww");
}
});
var kitty = new My.Cat(); // alerts "I'm going to be a cat!我要成为一只猫!"
// alerts "I'm a cat!我是一只猫!"
// alerts "Meeeeoooowwww"
在4.1版本, 直接利用这种方法已经过时了。 使用 Ext.define 代替:
Ext.define('My.CatOverride', {
override: 'My.Cat',
constructor: function() {
alert("I'm going to be a cat!");
this.callParent(arguments);
alert("Meeeeoooowwww");
}
});
以上完成了相同的结果,但可以由Ext.Loader重写, 其目标类和生成过程中,可以决定是否需要根据目标类所需的状态覆盖管理(My.Cat)。
This method has been deprecated since 4.1.0
使用 Ext.define 代替
添加到这个类的属性。 这应当被指定为一个对象包含一个或多个属性的文字。
this class 当前类