]>
Commit | Line | Data |
---|---|---|
6527f429 DM |
1 | /**\r |
2 | * This class manages state information for a component or element during a layout.\r | |
3 | * \r | |
4 | * # Blocks\r | |
5 | *\r | |
6 | * A "block" is a required value that is preventing further calculation. When a layout has\r | |
7 | * encountered a situation where it cannot possibly calculate results, it can associate\r | |
8 | * itself with the context item and missing property so that it will not be rescheduled\r | |
9 | * until that property is set.\r | |
10 | * \r | |
11 | * Blocks are a one-shot registration. Once the property changes, the block is removed.\r | |
12 | * \r | |
13 | * Be careful with blocks. If *any* further calculations can be made, a block is not the\r | |
14 | * right choice.\r | |
15 | * \r | |
16 | * # Triggers\r | |
17 | *\r | |
18 | * Whenever any call to {@link #getProp}, {@link #getDomProp}, {@link #hasProp} or\r | |
19 | * {@link #hasDomProp} is made, the current layout is automatically registered as being\r | |
20 | * dependent on that property in the appropriate state. Any changes to the property will\r | |
21 | * trigger the layout and it will be queued in the {@link Ext.layout.Context}.\r | |
22 | *\r | |
23 | * Triggers, once added, remain for the entire layout. Any changes to the property will\r | |
24 | * reschedule all unfinished layouts in their trigger set.\r | |
25 | *\r | |
26 | * @private\r | |
27 | */\r | |
28 | Ext.define('Ext.layout.ContextItem', {\r | |
29 | \r | |
30 | heightModel: null,\r | |
31 | widthModel: null,\r | |
32 | sizeModel: null,\r | |
33 | \r | |
34 | /**\r | |
35 | * There are several cases that allow us to skip (opt out) of laying out a component\r | |
36 | * and its children as long as its `lastBox` is not marked as `invalid`. If anything\r | |
37 | * happens to change things, the `lastBox` is marked as `invalid` by `updateLayout`\r | |
38 | * as it ascends the component hierarchy.\r | |
39 | * \r | |
40 | * @property {Boolean} optOut\r | |
41 | * @private\r | |
42 | * @readonly\r | |
43 | */\r | |
44 | optOut: false,\r | |
45 | \r | |
46 | ownerSizePolicy: null, // plaed here by Component.getSizeModel\r | |
47 | \r | |
48 | boxChildren: null,\r | |
49 | \r | |
50 | boxParent: null,\r | |
51 | \r | |
52 | children: [],\r | |
53 | \r | |
54 | dirty: null,\r | |
55 | \r | |
56 | // The number of dirty properties\r | |
57 | dirtyCount: 0,\r | |
58 | \r | |
59 | hasRawContent: true,\r | |
60 | \r | |
61 | isContextItem: true,\r | |
62 | \r | |
63 | isTopLevel: false,\r | |
64 | \r | |
65 | consumersContentHeight: 0,\r | |
66 | consumersContentWidth: 0,\r | |
67 | consumersContainerHeight: 0,\r | |
68 | consumersContainerWidth: 0,\r | |
69 | consumersHeight: 0,\r | |
70 | consumersWidth: 0,\r | |
71 | \r | |
72 | ownerCtContext: null,\r | |
73 | \r | |
74 | remainingChildDimensions: 0,\r | |
75 | \r | |
76 | // the current set of property values:\r | |
77 | props: null,\r | |
78 | \r | |
79 | /**\r | |
80 | * @property {Object} state\r | |
81 | * State variables that are cleared when invalidated. Only applies to component items.\r | |
82 | */\r | |
83 | state: null,\r | |
84 | \r | |
85 | /**\r | |
86 | * @property {Boolean} wrapsComponent\r | |
87 | * True if this item wraps a Component (rather than an Element).\r | |
88 | * @readonly\r | |
89 | */\r | |
90 | wrapsComponent: false,\r | |
91 | \r | |
92 | constructor: function (config) {\r | |
93 | var me = this,\r | |
94 | sizeModels = Ext.layout.SizeModel.sizeModels,\r | |
95 | configured = sizeModels.configured,\r | |
96 | shrinkWrap = sizeModels.shrinkWrap,\r | |
97 | el, lastBox, ownerCt, ownerCtContext, props, sizeModel, target,\r | |
98 | lastWidth, lastHeight, sameWidth, sameHeight, widthModel, heightModel, optOut;\r | |
99 | \r | |
100 | Ext.apply(me, config);\r | |
101 | \r | |
102 | target = me.target;\r | |
103 | el = me.el;\r | |
104 | me.id = target.id;\r | |
105 | \r | |
106 | // These hold collections of layouts that are either blocked or triggered by sets\r | |
107 | // to our properties (either ASAP or after flushing to the DOM). All of them have\r | |
108 | // the same structure:\r | |
109 | //\r | |
110 | // me.blocks = {\r | |
111 | // width: {\r | |
112 | // 'layout-1001': layout1001\r | |
113 | // }\r | |
114 | // }\r | |
115 | //\r | |
116 | // The property name is the primary key which yields an object keyed by layout id\r | |
117 | // with the layout instance as the value. This prevents duplicate entries for one\r | |
118 | // layout and gives O(1) access to the layout instance when we need to iterate and\r | |
119 | // process them.\r | |
120 | // \r | |
121 | // me.blocks = {};\r | |
122 | // me.domBlocks = {};\r | |
123 | // me.domTriggers = {};\r | |
124 | // me.triggers = {};\r | |
125 | \r | |
126 | me.flushedProps = {};\r | |
127 | me.props = props = {};\r | |
128 | \r | |
129 | // the set of cached styles for the element:\r | |
130 | me.styles = {};\r | |
131 | \r | |
132 | if (!target.isComponent) {\r | |
133 | lastBox = el.lastBox;\r | |
134 | } else {\r | |
135 | me.wrapsComponent = true;\r | |
136 | me.framing = target.frameSize || null;\r | |
137 | me.isComponentChild = target.ownerLayout && target.ownerLayout.isComponentLayout;\r | |
138 | \r | |
139 | lastBox = target.lastBox;\r | |
140 | \r | |
141 | // These items are created top-down, so the ContextItem of our ownerCt should\r | |
142 | // be available (if it is part of this layout run).\r | |
143 | ownerCt = target.ownerCt;\r | |
144 | if (ownerCt && (ownerCtContext = ownerCt.el && me.context.items[ownerCt.el.id])) {\r | |
145 | me.ownerCtContext = ownerCtContext;\r | |
146 | }\r | |
147 | \r | |
148 | // If our ownerCtContext is in the run, it will have a SizeModel that we use to\r | |
149 | // optimize the determination of our sizeModel. Also see recalculateSizeModel, similar\r | |
150 | // logic exists there.\r | |
151 | me.sizeModel = sizeModel = target.getSizeModel(ownerCtContext &&\r | |
152 | ownerCtContext.widthModel.pairsByHeightOrdinal[ownerCtContext.heightModel.ordinal]);\r | |
153 | \r | |
154 | // NOTE: The initial determination of sizeModel is valid (thankfully) and is\r | |
155 | // needed to cope with adding components to a layout run on-the-fly (e.g., in\r | |
156 | // the menu overflow handler of a box layout). Since this is the case, we do\r | |
157 | // not need to recompute the sizeModel in init unless it is a "full" init (as\r | |
158 | // our ownerCt's sizeModel could have changed in that case).\r | |
159 | \r | |
160 | me.widthModel = widthModel = sizeModel.width;\r | |
161 | me.heightModel = heightModel = sizeModel.height;\r | |
162 | \r | |
163 | // The lastBox is populated early but does not get an "invalid" property\r | |
164 | // until layout has occurred. The "false" value is placed in the lastBox\r | |
165 | // by Component.finishedLayout.\r | |
166 | if (lastBox && lastBox.invalid === false) {\r | |
167 | sameWidth = (target.width === (lastWidth = lastBox.width));\r | |
168 | sameHeight = (target.height === (lastHeight = lastBox.height));\r | |
169 | \r | |
170 | if (widthModel === shrinkWrap && heightModel === shrinkWrap) {\r | |
171 | optOut = true;\r | |
172 | } else if (widthModel === configured && sameWidth) {\r | |
173 | optOut = heightModel === shrinkWrap ||\r | |
174 | (heightModel === configured && sameHeight);\r | |
175 | }\r | |
176 | \r | |
177 | if (optOut) {\r | |
178 | // Flag this component and capture its last size...\r | |
179 | me.optOut = true;\r | |
180 | props.width = lastWidth;\r | |
181 | props.height = lastHeight;\r | |
182 | }\r | |
183 | }\r | |
184 | }\r | |
185 | \r | |
186 | me.lastBox = lastBox;\r | |
187 | },\r | |
188 | \r | |
189 | /**\r | |
190 | * Clears all properties on this object except (perhaps) those not calculated by this\r | |
191 | * component. This is more complex than it would seem because a layout can decide to\r | |
192 | * invalidate its results and run the component's layouts again, but since some of the\r | |
193 | * values may be calculated by the container, care must be taken to preserve those\r | |
194 | * values.\r | |
195 | *\r | |
196 | * @param {Boolean} full True if all properties are to be invalidated, false to keep\r | |
197 | * those calculated by the ownerCt.\r | |
198 | * @return {Mixed} A value to pass as the first argument to {@link #initContinue}.\r | |
199 | * @private\r | |
200 | */\r | |
201 | init: function (full, options) {\r | |
202 | var me = this,\r | |
203 | oldProps = me.props,\r | |
204 | oldDirty = me.dirty,\r | |
205 | ownerCtContext = me.ownerCtContext,\r | |
206 | ownerLayout = me.target.ownerLayout,\r | |
207 | firstTime = !me.state,\r | |
208 | ret = full || firstTime,\r | |
209 | children, i, n, ownerCt, sizeModel, target,\r | |
210 | oldHeightModel = me.heightModel,\r | |
211 | oldWidthModel = me.widthModel,\r | |
212 | newHeightModel, newWidthModel,\r | |
213 | remainingCount = 0;\r | |
214 | \r | |
215 | me.dirty = me.invalid = false;\r | |
216 | me.props = {};\r | |
217 | \r | |
218 | // Reset the number of child dimensions since the children will add their part:\r | |
219 | me.remainingChildDimensions = 0;\r | |
220 | \r | |
221 | if (me.boxChildren) {\r | |
222 | me.boxChildren.length = 0; // keep array (more GC friendly)\r | |
223 | }\r | |
224 | \r | |
225 | if (!firstTime) {\r | |
226 | me.clearAllBlocks('blocks');\r | |
227 | me.clearAllBlocks('domBlocks');\r | |
228 | }\r | |
229 | \r | |
230 | // For Element wrappers, we are done...\r | |
231 | if (!me.wrapsComponent) {\r | |
232 | return ret;\r | |
233 | }\r | |
234 | \r | |
235 | // From here on, we are only concerned with Component wrappers...\r | |
236 | target = me.target;\r | |
237 | me.state = {}; // only Component wrappers need a "state"\r | |
238 | \r | |
239 | if (firstTime) {\r | |
240 | // This must occur before we proceed since it can do many things (like add\r | |
241 | // child items perhaps):\r | |
242 | if (target.beforeLayout && target.beforeLayout !== Ext.emptyFn) {\r | |
243 | target.beforeLayout();\r | |
244 | }\r | |
245 | \r | |
246 | // Determine the ownerCtContext if we aren't given one. Normally the firstTime\r | |
247 | // we meet a component is before the context is run, but it is possible for\r | |
248 | // components to be added to a run that is already in progress. If so, we have\r | |
249 | // to lookup the ownerCtContext since the odds are very high that the new\r | |
250 | // component is a child of something already in the run. It is currently\r | |
251 | // unsupported to drag in the owner of a running component (needs testing).\r | |
252 | if (!ownerCtContext && (ownerCt = target.ownerCt)) {\r | |
253 | ownerCtContext = me.context.items[ownerCt.el.id];\r | |
254 | }\r | |
255 | \r | |
256 | if (ownerCtContext) {\r | |
257 | me.ownerCtContext = ownerCtContext;\r | |
258 | me.isBoxParent = ownerLayout && ownerLayout.isItemBoxParent(me);\r | |
259 | } else {\r | |
260 | me.isTopLevel = true; // this is used by initAnimation...\r | |
261 | }\r | |
262 | \r | |
263 | me.frameBodyContext = me.getEl('frameBody');\r | |
264 | } else {\r | |
265 | ownerCtContext = me.ownerCtContext;\r | |
266 | \r | |
267 | // In theory (though untested), this flag can change on-the-fly...\r | |
268 | me.isTopLevel = !ownerCtContext;\r | |
269 | \r | |
270 | // Init the children element items since they may have dirty state (no need to\r | |
271 | // do this the firstTime).\r | |
272 | children = me.children;\r | |
273 | for (i = 0, n = children.length; i < n; ++i) {\r | |
274 | children[i].init(true);\r | |
275 | }\r | |
276 | }\r | |
277 | \r | |
278 | // We need to know how we will determine content size: containers can look at the\r | |
279 | // results of their items but non-containers or item-less containers with just raw\r | |
280 | // markup need to be measured in the DOM:\r | |
281 | me.hasRawContent = !(target.isContainer && target.items.items.length > 0);\r | |
282 | \r | |
283 | if (full) {\r | |
284 | // We must null these out or getSizeModel will assume they are the correct,\r | |
285 | // dynamic size model and return them (the previous dynamic sizeModel).\r | |
286 | me.widthModel = me.heightModel = null;\r | |
287 | sizeModel = target.getSizeModel(ownerCtContext && \r | |
288 | ownerCtContext.widthModel.pairsByHeightOrdinal[ownerCtContext.heightModel.ordinal]);\r | |
289 | \r | |
290 | if (firstTime) {\r | |
291 | me.sizeModel = sizeModel;\r | |
292 | }\r | |
293 | \r | |
294 | me.widthModel = sizeModel.width;\r | |
295 | me.heightModel = sizeModel.height;\r | |
296 | \r | |
297 | // if we are a container child (e.g., not a docked item), and this is a full\r | |
298 | // init, that means our parent was invalidated, and therefore we must initialize\r | |
299 | // our remainingChildDimensions to ensure that containerChildrenSizeDone\r | |
300 | // gets set properly once all dimensions have had their sizes determined.\r | |
301 | // There are 3 possible scenarios here:\r | |
302 | //\r | |
303 | // 1. Layouts that both read and set sizes of their items (e.g. box). These\r | |
304 | // layouts must always add both dimensions to remainingChildDimensions.\r | |
305 | //\r | |
306 | // 2. Layouts that neither read nor set the size of their items (e.g.\r | |
307 | // autocontainer, form). These layouts will not create context items for their\r | |
308 | // children, and so we will never end up here.\r | |
309 | //\r | |
310 | // 3. Layouts that may set the size of their items, but will never read them\r | |
311 | // because they measure an outer containing element in the shrink-wrapping\r | |
312 | // dimension(s) (e.g. anchor, column). There are 2 possible outcomes:\r | |
313 | // a. The child item uses liquid CSS layout. In this case, the only dimensions\r | |
314 | // that affect containerChildrenSizeDone are the dimensions that the owner\r | |
315 | // layout is responsible for calculating, and so these are the dimensions\r | |
316 | // that are added to remainingChildDimensions. Non-calculated dimensions will\r | |
317 | // never be published because the child's component layout does not run.\r | |
318 | //\r | |
319 | // b. The child item does not use liquid CSS layout. In this case, the\r | |
320 | // component layout will run like normal, and any non-calculated dimensions\r | |
321 | // will be published, therefore, we need to add both dimensions to\r | |
322 | // remainingChildDimensions\r | |
323 | if (ownerCtContext && !me.isComponentChild) {\r | |
324 | if (ownerLayout.needsItemSize || !target.liquidLayout) {\r | |
325 | ownerCtContext.remainingChildDimensions += 2;\r | |
326 | } else {\r | |
327 | if (me.widthModel.calculated) {\r | |
328 | ++ownerCtContext.remainingChildDimensions;\r | |
329 | }\r | |
330 | if (me.heightModel.calculated) {\r | |
331 | ++ownerCtContext.remainingChildDimensions;\r | |
332 | }\r | |
333 | }\r | |
334 | }\r | |
335 | } else if (oldProps) {\r | |
336 | // these are almost always calculated by the ownerCt (we might need to track\r | |
337 | // this at some point more carefully):\r | |
338 | me.recoverProp('x', oldProps, oldDirty);\r | |
339 | me.recoverProp('y', oldProps, oldDirty);\r | |
340 | \r | |
341 | // if these are calculated by the ownerCt, don't trash them:\r | |
342 | if (me.widthModel.calculated) {\r | |
343 | me.recoverProp('width', oldProps, oldDirty);\r | |
344 | } else if ('width' in oldProps) {\r | |
345 | ++remainingCount;\r | |
346 | }\r | |
347 | if (me.heightModel.calculated) {\r | |
348 | me.recoverProp('height', oldProps, oldDirty);\r | |
349 | } else if ('height' in oldProps) {\r | |
350 | ++remainingCount;\r | |
351 | }\r | |
352 | \r | |
353 | // if we are a container child and this is not a full init, that means our\r | |
354 | // parent was not invalidated and therefore only the dimensions that were\r | |
355 | // set last time and removed from remainingChildDimensions last time, need to\r | |
356 | // be added back to remainingChildDimensions. This only needs to happen for\r | |
357 | // properties that we don't recover above (model=calculated)\r | |
358 | if (ownerCtContext && !me.isComponentChild) {\r | |
359 | ownerCtContext.remainingChildDimensions += remainingCount;\r | |
360 | }\r | |
361 | }\r | |
362 | \r | |
363 | if (oldProps && ownerLayout && ownerLayout.manageMargins) {\r | |
364 | me.recoverProp('margin-top', oldProps, oldDirty);\r | |
365 | me.recoverProp('margin-right', oldProps, oldDirty);\r | |
366 | me.recoverProp('margin-bottom', oldProps, oldDirty);\r | |
367 | me.recoverProp('margin-left', oldProps, oldDirty);\r | |
368 | }\r | |
369 | \r | |
370 | // Process any invalidate options present. These can only come from explicit calls\r | |
371 | // to the invalidate() method.\r | |
372 | if (options) {\r | |
373 | // Consider a container box with wrapping text. If the box is made wider, the\r | |
374 | // text will take up less height (until there is no more wrapping). Conversely,\r | |
375 | // if the box is made narrower, the height starts to increase due to wrapping.\r | |
376 | //\r | |
377 | // Imposing a minWidth constraint would increase the width. This may decrease\r | |
378 | // the height. If the box is shrinkWrap, however, the width will already be\r | |
379 | // such that there is no wrapping, so the height will not further decrease.\r | |
380 | // Since the height will also not increase if we widen the box, there is no\r | |
381 | // problem simultaneously imposing a minHeight or maxHeight constraint.\r | |
382 | //\r | |
383 | // When we impose as maxWidth constraint, however, we are shrinking the box\r | |
384 | // which may increase the height. If we are imposing a maxHeight constraint,\r | |
385 | // that is fine because a further increased height will still need to be\r | |
386 | // constrained. But if we are imposing a minHeight constraint, we cannot know\r | |
387 | // whether the increase in height due to wrapping will be greater than the\r | |
388 | // minHeight. If we impose a minHeight constraint at the same time, then, we\r | |
389 | // could easily be locking in the wrong height.\r | |
390 | //\r | |
391 | // It is important to note that this logic applies to simultaneously *adding*\r | |
392 | // both a maxWidth and a minHeight constraint. It is perfectly fine to have\r | |
393 | // a state with both constraints, but we cannot add them both at once.\r | |
394 | newHeightModel = options.heightModel;\r | |
395 | newWidthModel = options.widthModel;\r | |
396 | if (newWidthModel && newHeightModel && oldWidthModel && oldHeightModel) {\r | |
397 | if (oldWidthModel.shrinkWrap && oldHeightModel.shrinkWrap) {\r | |
398 | if (newWidthModel.constrainedMax && newHeightModel.constrainedMin) {\r | |
399 | newHeightModel = null;\r | |
400 | }\r | |
401 | }\r | |
402 | }\r | |
403 | \r | |
404 | // Apply size model updates (if any) and state updates (if any).\r | |
405 | if (newWidthModel) {\r | |
406 | me.widthModel = newWidthModel;\r | |
407 | }\r | |
408 | if (newHeightModel) {\r | |
409 | me.heightModel = newHeightModel;\r | |
410 | }\r | |
411 | \r | |
412 | if (options.state) {\r | |
413 | Ext.apply(me.state, options.state);\r | |
414 | }\r | |
415 | }\r | |
416 | \r | |
417 | return ret;\r | |
418 | },\r | |
419 | \r | |
420 | /**\r | |
421 | * @private\r | |
422 | */\r | |
423 | initContinue: function (full) {\r | |
424 | var me = this,\r | |
425 | ownerCtContext = me.ownerCtContext,\r | |
426 | comp = me.target,\r | |
427 | widthModel = me.widthModel,\r | |
428 | inheritedState = comp.getInherited(),\r | |
429 | boxParent;\r | |
430 | \r | |
431 | if (widthModel.fixed) { // calculated or configured\r | |
432 | inheritedState.inShrinkWrapTable = false;\r | |
433 | } else {\r | |
434 | delete inheritedState.inShrinkWrapTable;\r | |
435 | }\r | |
436 | \r | |
437 | if (full) {\r | |
438 | if (ownerCtContext && widthModel.shrinkWrap) {\r | |
439 | boxParent = ownerCtContext.isBoxParent ? ownerCtContext : ownerCtContext.boxParent;\r | |
440 | if (boxParent) {\r | |
441 | boxParent.addBoxChild(me);\r | |
442 | }\r | |
443 | } else if (widthModel.natural) {\r | |
444 | me.boxParent = ownerCtContext;\r | |
445 | }\r | |
446 | }\r | |
447 | \r | |
448 | return full;\r | |
449 | },\r | |
450 | \r | |
451 | /**\r | |
452 | * @private\r | |
453 | */\r | |
454 | initDone: function(containerLayoutDone) {\r | |
455 | var me = this,\r | |
456 | props = me.props,\r | |
457 | state = me.state;\r | |
458 | \r | |
459 | // These properties are only set when they are true:\r | |
460 | if (me.remainingChildDimensions === 0) {\r | |
461 | props.containerChildrenSizeDone = true;\r | |
462 | }\r | |
463 | if (containerLayoutDone) {\r | |
464 | props.containerLayoutDone = true;\r | |
465 | }\r | |
466 | \r | |
467 | if (me.boxChildren && me.boxChildren.length && me.widthModel.shrinkWrap) {\r | |
468 | // set a very large width to allow the children to measure their natural\r | |
469 | // widths (this is cleared once all children have been measured):\r | |
470 | me.el.setWidth(10000);\r | |
471 | \r | |
472 | // don't run layouts for this component until we clear this width...\r | |
473 | state.blocks = (state.blocks || 0) + 1;\r | |
474 | }\r | |
475 | },\r | |
476 | \r | |
477 | /**\r | |
478 | * @private\r | |
479 | */\r | |
480 | initAnimation: function() {\r | |
481 | var me = this,\r | |
482 | target = me.target,\r | |
483 | ownerCtContext = me.ownerCtContext;\r | |
484 | \r | |
485 | if (ownerCtContext && ownerCtContext.isTopLevel) {\r | |
486 | // See which properties we are supposed to animate to their new state.\r | |
487 | // If there are any, queue ourself to be animated by the owning Context\r | |
488 | me.animatePolicy = target.ownerLayout.getAnimatePolicy(me);\r | |
489 | } else if (!ownerCtContext && target.isCollapsingOrExpanding && target.animCollapse) {\r | |
490 | // Collapsing/expnding a top level Panel with animation. We need to fabricate\r | |
491 | // an animatePolicy depending on which dimension the collapse is using,\r | |
492 | // isCollapsingOrExpanding is set during the collapse/expand process.\r | |
493 | me.animatePolicy = target.componentLayout.getAnimatePolicy(me);\r | |
494 | }\r | |
495 | \r | |
496 | if (me.animatePolicy) {\r | |
497 | me.context.queueAnimation(me);\r | |
498 | }\r | |
499 | },\r | |
500 | \r | |
501 | /**\r | |
502 | * Adds a block.\r | |
503 | * \r | |
504 | * @param {String} name The name of the block list ('blocks' or 'domBlocks').\r | |
505 | * @param {Ext.layout.Layout} layout The layout that is blocked.\r | |
506 | * @param {String} propName The property name that blocked the layout (e.g., 'width').\r | |
507 | * @private\r | |
508 | */\r | |
509 | addBlock: function (name, layout, propName) {\r | |
510 | var me = this,\r | |
511 | collection = me[name] || (me[name] = {}),\r | |
512 | blockedLayouts = collection[propName] || (collection[propName] = {});\r | |
513 | \r | |
514 | if (!blockedLayouts[layout.id]) {\r | |
515 | blockedLayouts[layout.id] = layout;\r | |
516 | ++layout.blockCount;\r | |
517 | ++me.context.blockCount;\r | |
518 | }\r | |
519 | },\r | |
520 | \r | |
521 | addBoxChild: function (boxChildItem) {\r | |
522 | var me = this,\r | |
523 | children,\r | |
524 | widthModel = boxChildItem.widthModel;\r | |
525 | \r | |
526 | boxChildItem.boxParent = this;\r | |
527 | \r | |
528 | // Children that are widthModel.auto (regardless of heightModel) that measure the\r | |
529 | // DOM (by virtue of hasRawContent), need to wait for their "box parent" to be sized.\r | |
530 | // If they measure too early, they will be wrong results. In the widthModel.shrinkWrap\r | |
531 | // case, the boxParent "crushes" the child. In the case of widthModel.natural, the\r | |
532 | // boxParent's width is likely a key part of the child's width (e.g., "50%" or just\r | |
533 | // normal block-level behavior of 100% width)\r | |
534 | boxChildItem.measuresBox = widthModel.shrinkWrap ? boxChildItem.hasRawContent : widthModel.natural;\r | |
535 | \r | |
536 | if (boxChildItem.measuresBox) {\r | |
537 | children = me.boxChildren;\r | |
538 | \r | |
539 | if (children) {\r | |
540 | children.push(boxChildItem);\r | |
541 | } else {\r | |
542 | me.boxChildren = [ boxChildItem ];\r | |
543 | }\r | |
544 | }\r | |
545 | },\r | |
546 | \r | |
547 | /**\r | |
548 | * Adds x and y values from a props object to a styles object as "left" and "top" values.\r | |
549 | * Overridden to add the x property as "right" in rtl mode.\r | |
550 | * @property {Object} styles A styles object for an Element\r | |
551 | * @property {Object} props A ContextItem props object\r | |
552 | * @return {Number} count The number of styles that were set.\r | |
553 | * @private\r | |
554 | */\r | |
555 | addPositionStyles: function(styles, props) {\r | |
556 | var x = props.x,\r | |
557 | y = props.y,\r | |
558 | count = 0;\r | |
559 | \r | |
560 | if (x !== undefined) {\r | |
561 | styles.left = x + 'px';\r | |
562 | ++count;\r | |
563 | }\r | |
564 | if (y !== undefined) {\r | |
565 | styles.top = y + 'px';\r | |
566 | ++count;\r | |
567 | }\r | |
568 | return count;\r | |
569 | },\r | |
570 | \r | |
571 | /**\r | |
572 | * Adds a trigger.\r | |
573 | * \r | |
574 | * @param {String} propName The property name that triggers the layout (e.g., 'width').\r | |
575 | * @param {Boolean} inDom True if the trigger list is `domTriggers`, false if `triggers`.\r | |
576 | * @private\r | |
577 | */\r | |
578 | addTrigger: function (propName, inDom) {\r | |
579 | var me = this,\r | |
580 | name = inDom ? 'domTriggers' : 'triggers',\r | |
581 | collection = me[name] || (me[name] = {}),\r | |
582 | context = me.context,\r | |
583 | layout = context.currentLayout,\r | |
584 | triggers = collection[propName] || (collection[propName] = {});\r | |
585 | \r | |
586 | if (!triggers[layout.id]) {\r | |
587 | triggers[layout.id] = layout;\r | |
588 | ++layout.triggerCount;\r | |
589 | \r | |
590 | triggers = context.triggers[inDom ? 'dom' : 'data'];\r | |
591 | (triggers[layout.id] || (triggers[layout.id] = [])).push({\r | |
592 | item: this,\r | |
593 | prop: propName\r | |
594 | });\r | |
595 | \r | |
596 | if (me.props[propName] !== undefined) {\r | |
597 | if (!inDom || !(me.dirty && (propName in me.dirty))) {\r | |
598 | ++layout.firedTriggers;\r | |
599 | }\r | |
600 | }\r | |
601 | }\r | |
602 | },\r | |
603 | \r | |
604 | boxChildMeasured: function () {\r | |
605 | var me = this,\r | |
606 | state = me.state,\r | |
607 | count = (state.boxesMeasured = (state.boxesMeasured || 0) + 1);\r | |
608 | \r | |
609 | if (count === me.boxChildren.length) {\r | |
610 | // all of our children have measured themselves, so we can clear the width\r | |
611 | // and resume layouts for this component...\r | |
612 | state.clearBoxWidth = 1;\r | |
613 | ++me.context.progressCount;\r | |
614 | me.markDirty();\r | |
615 | }\r | |
616 | },\r | |
617 | \r | |
618 | borderNames: [ 'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width'],\r | |
619 | marginNames: [ 'margin-top', 'margin-right', 'margin-bottom', 'margin-left' ],\r | |
620 | paddingNames: [ 'padding-top', 'padding-right', 'padding-bottom', 'padding-left' ],\r | |
621 | trblNames: [ 'top', 'right', 'bottom', 'left' ],\r | |
622 | \r | |
623 | cacheMissHandlers: {\r | |
624 | borderInfo: function (me) {\r | |
625 | var info = me.getStyles(me.borderNames, me.trblNames);\r | |
626 | \r | |
627 | info.width = info.left + info.right;\r | |
628 | info.height = info.top + info.bottom;\r | |
629 | \r | |
630 | return info;\r | |
631 | },\r | |
632 | \r | |
633 | marginInfo: function (me) {\r | |
634 | var info = me.getStyles(me.marginNames, me.trblNames);\r | |
635 | \r | |
636 | info.width = info.left + info.right;\r | |
637 | info.height = info.top + info.bottom;\r | |
638 | \r | |
639 | return info;\r | |
640 | },\r | |
641 | \r | |
642 | paddingInfo: function (me) {\r | |
643 | // if this context item's target is a framed component the padding is on the frameBody, not on the main el\r | |
644 | var item = me.frameBodyContext || me,\r | |
645 | info = item.getStyles(me.paddingNames, me.trblNames);\r | |
646 | \r | |
647 | info.width = info.left + info.right;\r | |
648 | info.height = info.top + info.bottom;\r | |
649 | \r | |
650 | return info;\r | |
651 | }\r | |
652 | },\r | |
653 | \r | |
654 | checkCache: function (entry) {\r | |
655 | return this.cacheMissHandlers[entry](this);\r | |
656 | },\r | |
657 | \r | |
658 | clearAllBlocks: function (name) {\r | |
659 | var collection = this[name],\r | |
660 | propName;\r | |
661 | \r | |
662 | if (collection) {\r | |
663 | for (propName in collection) {\r | |
664 | this.clearBlocks(name, propName);\r | |
665 | }\r | |
666 | }\r | |
667 | },\r | |
668 | \r | |
669 | /**\r | |
670 | * Removes any blocks on a property in the specified set. Any layouts that were blocked\r | |
671 | * by this property and are not still blocked (by other properties) will be rescheduled.\r | |
672 | * \r | |
673 | * @param {String} name The name of the block list ('blocks' or 'domBlocks').\r | |
674 | * @param {String} propName The property name that blocked the layout (e.g., 'width').\r | |
675 | * @private\r | |
676 | */\r | |
677 | clearBlocks: function (name, propName) {\r | |
678 | var collection = this[name],\r | |
679 | blockedLayouts = collection && collection[propName],\r | |
680 | context, layout, layoutId;\r | |
681 | \r | |
682 | if (blockedLayouts) {\r | |
683 | delete collection[propName];\r | |
684 | \r | |
685 | context = this.context;\r | |
686 | \r | |
687 | for (layoutId in blockedLayouts) {\r | |
688 | layout = blockedLayouts[layoutId];\r | |
689 | \r | |
690 | --context.blockCount;\r | |
691 | if (! --layout.blockCount && !layout.pending && !layout.done) {\r | |
692 | context.queueLayout(layout);\r | |
693 | }\r | |
694 | }\r | |
695 | }\r | |
696 | },\r | |
697 | \r | |
698 | /**\r | |
699 | * Registers a layout in the block list for the given property. Once the property is\r | |
700 | * set in the {@link Ext.layout.Context}, the layout is unblocked.\r | |
701 | * \r | |
702 | * @param {Ext.layout.Layout} layout\r | |
703 | * @param {String} propName The property name that blocked the layout (e.g., 'width').\r | |
704 | */\r | |
705 | block: function (layout, propName) {\r | |
706 | this.addBlock('blocks', layout, propName);\r | |
707 | },\r | |
708 | \r | |
709 | /**\r | |
710 | * Registers a layout in the DOM block list for the given property. Once the property\r | |
711 | * flushed to the DOM by the {@link Ext.layout.Context}, the layout is unblocked.\r | |
712 | * \r | |
713 | * @param {Ext.layout.Layout} layout\r | |
714 | * @param {String} propName The property name that blocked the layout (e.g., 'width').\r | |
715 | */\r | |
716 | domBlock: function (layout, propName) {\r | |
717 | this.addBlock('domBlocks', layout, propName);\r | |
718 | },\r | |
719 | \r | |
720 | /**\r | |
721 | * Reschedules any layouts associated with a given trigger.\r | |
722 | * \r | |
723 | * @param {String} name The name of the trigger list ('triggers' or 'domTriggers').\r | |
724 | * @param {String} propName The property name that triggers the layout (e.g., 'width').\r | |
725 | * @private\r | |
726 | */\r | |
727 | fireTriggers: function (name, propName) {\r | |
728 | var collection = this[name],\r | |
729 | triggers = collection && collection[propName],\r | |
730 | context = this.context,\r | |
731 | layout, layoutId;\r | |
732 | \r | |
733 | if (triggers) {\r | |
734 | for (layoutId in triggers) {\r | |
735 | layout = triggers[layoutId];\r | |
736 | ++layout.firedTriggers;\r | |
737 | if (!layout.done && !layout.blockCount && !layout.pending) {\r | |
738 | context.queueLayout(layout);\r | |
739 | }\r | |
740 | }\r | |
741 | }\r | |
742 | },\r | |
743 | \r | |
744 | /**\r | |
745 | * Flushes any updates in the dirty collection to the DOM. This is only called if there\r | |
746 | * are dirty entries because this object is only added to the flushQueue of the\r | |
747 | * {@link Ext.layout.Context} when entries become dirty.\r | |
748 | */\r | |
749 | flush: function () {\r | |
750 | var me = this,\r | |
751 | dirty = me.dirty,\r | |
752 | state = me.state,\r | |
753 | targetEl = me.el;\r | |
754 | \r | |
755 | me.dirtyCount = 0;\r | |
756 | \r | |
757 | // Set any queued DOM attributes\r | |
758 | if ('attributes' in me) {\r | |
759 | targetEl.set(me.attributes);\r | |
760 | delete me.attributes;\r | |
761 | }\r | |
762 | \r | |
763 | // Set any queued DOM HTML content\r | |
764 | if ('innerHTML' in me) {\r | |
765 | targetEl.innerHTML = me.innerHTML;\r | |
766 | delete me.innerHTML;\r | |
767 | }\r | |
768 | \r | |
769 | if (state && state.clearBoxWidth) {\r | |
770 | state.clearBoxWidth = 0;\r | |
771 | me.el.setStyle('width', null);\r | |
772 | \r | |
773 | if (! --state.blocks) {\r | |
774 | me.context.queueItemLayouts(me);\r | |
775 | }\r | |
776 | }\r | |
777 | \r | |
778 | if (dirty) {\r | |
779 | delete me.dirty;\r | |
780 | me.writeProps(dirty, true);\r | |
781 | }\r | |
782 | },\r | |
783 | \r | |
784 | /**\r | |
785 | * @private\r | |
786 | */\r | |
787 | flushAnimations: function() {\r | |
788 | var me = this,\r | |
789 | animateFrom = me.previousSize,\r | |
790 | target, targetAnim, duration, animateProps, anim,\r | |
791 | changeCount, j, propsLen, propName, oldValue, newValue;\r | |
792 | \r | |
793 | // Only animate if the Component has been previously layed out: first layout should not animate\r | |
794 | if (animateFrom) {\r | |
795 | target = me.target;\r | |
796 | targetAnim = target.getAnimationProps();\r | |
797 | duration = targetAnim.duration;\r | |
798 | animateProps = Ext.Object.getKeys(me.animatePolicy);\r | |
799 | \r | |
800 | // Create an animation block using the targetAnim configuration to provide defaults.\r | |
801 | // They may want custom duration, or easing, or listeners.\r | |
802 | anim = Ext.apply({}, {\r | |
803 | from: {},\r | |
804 | to: {},\r | |
805 | duration: duration || Ext.fx.Anim.prototype.duration\r | |
806 | }, targetAnim);\r | |
807 | \r | |
808 | for (changeCount = 0, j = 0, propsLen = animateProps.length; j < propsLen; j++) {\r | |
809 | propName = animateProps[j];\r | |
810 | oldValue = animateFrom[propName];\r | |
811 | newValue = me.peek(propName);\r | |
812 | if (oldValue !== newValue) {\r | |
813 | propName = me.translateProps[propName]||propName;\r | |
814 | anim.from[propName] = oldValue;\r | |
815 | anim.to[propName] = newValue;\r | |
816 | ++changeCount;\r | |
817 | }\r | |
818 | }\r | |
819 | \r | |
820 | // If any values have changed, kick off animation from the cached old values to the new values\r | |
821 | if (changeCount) {\r | |
822 | // It'a Panel being collapsed. rollback, and then fix the class name string\r | |
823 | if (me.isCollapsingOrExpanding === 1) {\r | |
824 | target.componentLayout.undoLayout(me);\r | |
825 | }\r | |
826 | \r | |
827 | // Otherwise, undo just the animated properties so the animation can proceed from the old layout.\r | |
828 | else {\r | |
829 | me.writeProps(anim.from);\r | |
830 | }\r | |
831 | me.el.animate(anim);\r | |
832 | anim = Ext.fx.Manager.getFxQueue(me.el.id)[0];\r | |
833 | target.$layoutAnim = anim;\r | |
834 | \r | |
835 | anim.on({\r | |
836 | afteranimate: function() {\r | |
837 | delete target.$layoutAnim;\r | |
838 | \r | |
839 | // afteranimate can fire when the target is being destroyed\r | |
840 | // and the animation queue is being stopped.\r | |
841 | if (target.destroying || target.destroyed) {\r | |
842 | return;\r | |
843 | }\r | |
844 | \r | |
845 | if (me.isCollapsingOrExpanding === 1) {\r | |
846 | target.componentLayout.redoLayout(me);\r | |
847 | target.afterCollapse(true);\r | |
848 | } else if (me.isCollapsingOrExpanding === 2) {\r | |
849 | target.afterExpand(true);\r | |
850 | }\r | |
851 | \r | |
852 | if (target.hasListeners.afterlayoutanimation) {\r | |
853 | target.fireEvent('afterlayoutanimation', target);\r | |
854 | }\r | |
855 | }\r | |
856 | });\r | |
857 | }\r | |
858 | }\r | |
859 | },\r | |
860 | \r | |
861 | /**\r | |
862 | * Gets the border information for the element as an object with left, top, right and\r | |
863 | * bottom properties holding border size in pixels. This object is only read from the\r | |
864 | * DOM on first request and is cached.\r | |
865 | * @return {Object}\r | |
866 | */\r | |
867 | getBorderInfo: function () {\r | |
868 | var me = this,\r | |
869 | info = me.borderInfo;\r | |
870 | \r | |
871 | if (!info) {\r | |
872 | me.borderInfo = info = me.checkCache('borderInfo');\r | |
873 | }\r | |
874 | \r | |
875 | return info;\r | |
876 | },\r | |
877 | \r | |
878 | /**\r | |
879 | * @member Ext.layout.ContextItem\r | |
880 | * Returns the context item for an owned element. This should only be called on a\r | |
881 | * component's item. The list of child items is used to manage invalidating calculated\r | |
882 | * results.\r | |
883 | * @param {String/Ext.dom.Element} nameOrEl The element or the name of an owned element\r | |
884 | * @param {Ext.layout.container.Container/Ext.Component} [owner] The owner of the\r | |
885 | * named element if the passed "nameOrEl" parameter is a String. Defaults to this\r | |
886 | * ContextItem's "target" property. For more details on owned elements see\r | |
887 | * {@link Ext.Component#cfg-childEls childEls} and\r | |
888 | * {@link Ext.Component#renderSelectors renderSelectors}\r | |
889 | * @return {Ext.layout.ContextItem}\r | |
890 | */\r | |
891 | getEl: function (nameOrEl, owner) {\r | |
892 | var me = this,\r | |
893 | src, el, elContext;\r | |
894 | \r | |
895 | if (nameOrEl) {\r | |
896 | if (nameOrEl.dom) {\r | |
897 | el = nameOrEl;\r | |
898 | } else {\r | |
899 | src = me.target;\r | |
900 | if (owner) {\r | |
901 | src = owner;\r | |
902 | }\r | |
903 | \r | |
904 | el = src[nameOrEl];\r | |
905 | if (typeof el === 'function') { // ex 'getTarget'\r | |
906 | el = el.call(src);\r | |
907 | if (el === me.el) {\r | |
908 | return this; // comp.getTarget() often returns comp.el\r | |
909 | }\r | |
910 | }\r | |
911 | }\r | |
912 | \r | |
913 | if (el) {\r | |
914 | elContext = me.context.getEl(me, el);\r | |
915 | }\r | |
916 | }\r | |
917 | \r | |
918 | return elContext || null;\r | |
919 | },\r | |
920 | \r | |
921 | /**\r | |
922 | * Gets the "frame" information for the element as an object with left, top, right and\r | |
923 | * bottom properties holding border+framing size in pixels. This object is calculated\r | |
924 | * on first request and is cached.\r | |
925 | * @return {Object}\r | |
926 | */\r | |
927 | getFrameInfo: function () {\r | |
928 | var me = this,\r | |
929 | info = me.frameInfo,\r | |
930 | framing, border;\r | |
931 | \r | |
932 | if (!info) {\r | |
933 | framing = me.framing;\r | |
934 | border = me.getBorderInfo();\r | |
935 | \r | |
936 | me.frameInfo = info = \r | |
937 | framing ? {\r | |
938 | top : framing.top + border.top,\r | |
939 | right : framing.right + border.right,\r | |
940 | bottom: framing.bottom + border.bottom,\r | |
941 | left : framing.left + border.left,\r | |
942 | width : framing.width + border.width,\r | |
943 | height: framing.height + border.height\r | |
944 | } : border;\r | |
945 | }\r | |
946 | \r | |
947 | return info;\r | |
948 | },\r | |
949 | \r | |
950 | /**\r | |
951 | * Gets the margin information for the element as an object with left, top, right and\r | |
952 | * bottom properties holding margin size in pixels. This object is only read from the\r | |
953 | * DOM on first request and is cached.\r | |
954 | * @return {Object}\r | |
955 | */\r | |
956 | getMarginInfo: function () {\r | |
957 | var me = this,\r | |
958 | info = me.marginInfo,\r | |
959 | comp, manageMargins, ownerLayout, ownerLayoutId;\r | |
960 | \r | |
961 | if (!info) {\r | |
962 | if (!me.wrapsComponent) {\r | |
963 | info = me.checkCache('marginInfo');\r | |
964 | } else {\r | |
965 | comp = me.target;\r | |
966 | ownerLayout = comp.ownerLayout;\r | |
967 | ownerLayoutId = ownerLayout ? ownerLayout.id : null;\r | |
968 | manageMargins = ownerLayout && ownerLayout.manageMargins;\r | |
969 | \r | |
970 | // TODO: stop caching margin$ on the component EXTJS-13359\r | |
971 | info = comp.margin$;\r | |
972 | if (info && info.ownerId !== ownerLayoutId) {\r | |
973 | // got one but from the wrong owner\r | |
974 | info = null;\r | |
975 | }\r | |
976 | \r | |
977 | if (!info) { // if (no cache)\r | |
978 | // CSS margins are only checked if there isn't a margin property on the component\r | |
979 | info = me.parseMargins(comp, comp.margin) || me.checkCache('marginInfo');\r | |
980 | \r | |
981 | if (manageMargins) {\r | |
982 | // TODO: Stop zeroing out the margins EXTJS-13359\r | |
983 | me.setProp('margin-top', 0);\r | |
984 | me.setProp('margin-right', 0);\r | |
985 | me.setProp('margin-bottom', 0);\r | |
986 | me.setProp('margin-left', 0);\r | |
987 | }\r | |
988 | \r | |
989 | // cache the layout margins and tag them with the layout id:\r | |
990 | info.ownerId = ownerLayoutId;\r | |
991 | comp.margin$ = info;\r | |
992 | }\r | |
993 | \r | |
994 | info.width = info.left + info.right;\r | |
995 | info.height = info.top + info.bottom;\r | |
996 | }\r | |
997 | \r | |
998 | me.marginInfo = info;\r | |
999 | }\r | |
1000 | \r | |
1001 | return info;\r | |
1002 | },\r | |
1003 | \r | |
1004 | /**\r | |
1005 | * clears the margin cache so that marginInfo get re-read from the dom on the next call to getMarginInfo()\r | |
1006 | * This is needed in some special cases where the margins have changed since the last layout, making the cached\r | |
1007 | * values invalid. For example collapsed window headers have different margin than expanded ones.\r | |
1008 | */\r | |
1009 | clearMarginCache: function() {\r | |
1010 | delete this.marginInfo;\r | |
1011 | delete this.target.margin$;\r | |
1012 | },\r | |
1013 | \r | |
1014 | /**\r | |
1015 | * Gets the padding information for the element as an object with left, top, right and\r | |
1016 | * bottom properties holding padding size in pixels. This object is only read from the\r | |
1017 | * DOM on first request and is cached.\r | |
1018 | * @return {Object}\r | |
1019 | */\r | |
1020 | getPaddingInfo: function () {\r | |
1021 | var me = this,\r | |
1022 | info = me.paddingInfo;\r | |
1023 | \r | |
1024 | if (!info) {\r | |
1025 | me.paddingInfo = info = me.checkCache('paddingInfo');\r | |
1026 | }\r | |
1027 | \r | |
1028 | return info;\r | |
1029 | },\r | |
1030 | \r | |
1031 | /**\r | |
1032 | * Gets a property of this object. Also tracks the current layout as dependent on this\r | |
1033 | * property so that changes to it will trigger the layout to be recalculated.\r | |
1034 | * @param {String} propName The property name that blocked the layout (e.g., 'width').\r | |
1035 | * @return {Object} The property value or undefined if not yet set.\r | |
1036 | */\r | |
1037 | getProp: function (propName) {\r | |
1038 | var me = this,\r | |
1039 | result = me.props[propName];\r | |
1040 | \r | |
1041 | me.addTrigger(propName);\r | |
1042 | return result;\r | |
1043 | },\r | |
1044 | \r | |
1045 | /**\r | |
1046 | * Gets a property of this object if it is correct in the DOM. Also tracks the current\r | |
1047 | * layout as dependent on this property so that DOM writes of it will trigger the\r | |
1048 | * layout to be recalculated.\r | |
1049 | * @param {String} propName The property name (e.g., 'width').\r | |
1050 | * @return {Object} The property value or undefined if not yet set or is dirty.\r | |
1051 | */\r | |
1052 | getDomProp: function (propName) {\r | |
1053 | var me = this,\r | |
1054 | result = (me.dirty && (propName in me.dirty)) ? undefined : me.props[propName];\r | |
1055 | \r | |
1056 | me.addTrigger(propName, true);\r | |
1057 | return result;\r | |
1058 | },\r | |
1059 | \r | |
1060 | /**\r | |
1061 | * Returns a style for this item. Each style is read from the DOM only once on first\r | |
1062 | * request and is then cached. If the value is an integer, it is parsed automatically\r | |
1063 | * (so '5px' is not returned, but rather 5).\r | |
1064 | *\r | |
1065 | * @param {String} styleName The CSS style name.\r | |
1066 | * @return {Object} The value of the DOM style (parsed as necessary).\r | |
1067 | */\r | |
1068 | getStyle: function (styleName) {\r | |
1069 | var me = this,\r | |
1070 | styles = me.styles,\r | |
1071 | info, value;\r | |
1072 | \r | |
1073 | if (styleName in styles) {\r | |
1074 | value = styles[styleName];\r | |
1075 | } else {\r | |
1076 | info = me.styleInfo[styleName];\r | |
1077 | value = me.el.getStyle(styleName);\r | |
1078 | \r | |
1079 | if (info && info.parseInt) {\r | |
1080 | value = parseInt(value, 10) || 0;\r | |
1081 | }\r | |
1082 | \r | |
1083 | styles[styleName] = value;\r | |
1084 | }\r | |
1085 | \r | |
1086 | return value;\r | |
1087 | },\r | |
1088 | \r | |
1089 | /**\r | |
1090 | * Returns styles for this item. Each style is read from the DOM only once on first\r | |
1091 | * request and is then cached. If the value is an integer, it is parsed automatically\r | |
1092 | * (so '5px' is not returned, but rather 5).\r | |
1093 | *\r | |
1094 | * @param {String[]} styleNames The CSS style names.\r | |
1095 | * @param {String[]} [altNames] The alternate names for the returned styles. If given,\r | |
1096 | * these names must correspond one-for-one to the `styleNames`.\r | |
1097 | * @return {Object} The values of the DOM styles (parsed as necessary).\r | |
1098 | */\r | |
1099 | getStyles: function (styleNames, altNames) {\r | |
1100 | var me = this,\r | |
1101 | styleCache = me.styles,\r | |
1102 | values = {},\r | |
1103 | hits = 0,\r | |
1104 | n = styleNames.length,\r | |
1105 | i, missing, missingAltNames, name, info, styleInfo, styles, value;\r | |
1106 | \r | |
1107 | altNames = altNames || styleNames;\r | |
1108 | \r | |
1109 | // We are optimizing this for all hits or all misses. If we hit on all styles, we\r | |
1110 | // don't create a missing[]. If we miss on all styles, we also don't create one.\r | |
1111 | for (i = 0; i < n; ++i) {\r | |
1112 | name = styleNames[i];\r | |
1113 | \r | |
1114 | if (name in styleCache) {\r | |
1115 | values[altNames[i]] = styleCache[name];\r | |
1116 | ++hits;\r | |
1117 | \r | |
1118 | if (i && hits === 1) { // if (first hit was after some misses)\r | |
1119 | missing = styleNames.slice(0, i);\r | |
1120 | missingAltNames = altNames.slice(0, i);\r | |
1121 | }\r | |
1122 | } else if (hits) {\r | |
1123 | (missing || (missing = [])).push(name);\r | |
1124 | (missingAltNames || (missingAltNames = [])).push(altNames[i]);\r | |
1125 | }\r | |
1126 | }\r | |
1127 | \r | |
1128 | if (hits < n) {\r | |
1129 | missing = missing || styleNames;\r | |
1130 | missingAltNames = missingAltNames || altNames;\r | |
1131 | styleInfo = me.styleInfo;\r | |
1132 | \r | |
1133 | styles = me.el.getStyle(missing);\r | |
1134 | \r | |
1135 | for (i = missing.length; i--; ) {\r | |
1136 | name = missing[i];\r | |
1137 | info = styleInfo[name];\r | |
1138 | value = styles[name];\r | |
1139 | \r | |
1140 | if (info && info.parseInt) {\r | |
1141 | value = parseInt(value, 10) || 0;\r | |
1142 | }\r | |
1143 | \r | |
1144 | values[missingAltNames[i]] = value;\r | |
1145 | styleCache[name] = value;\r | |
1146 | }\r | |
1147 | }\r | |
1148 | \r | |
1149 | return values;\r | |
1150 | },\r | |
1151 | \r | |
1152 | /**\r | |
1153 | * Returns true if the given property has been set. This is equivalent to calling\r | |
1154 | * {@link #getProp} and not getting an undefined result. In particular, this call\r | |
1155 | * registers the current layout to be triggered by changes to this property.\r | |
1156 | * \r | |
1157 | * @param {String} propName The property name (e.g., 'width').\r | |
1158 | * @return {Boolean}\r | |
1159 | */\r | |
1160 | hasProp: function (propName) {\r | |
1161 | return this.getProp(propName) != null;\r | |
1162 | },\r | |
1163 | \r | |
1164 | /**\r | |
1165 | * Returns true if the given property is correct in the DOM. This is equivalent to\r | |
1166 | * calling {@link #getDomProp} and not getting an undefined result. In particular,\r | |
1167 | * this call registers the current layout to be triggered by flushes of this property.\r | |
1168 | * \r | |
1169 | * @param {String} propName The property name (e.g., 'width').\r | |
1170 | * @return {Boolean}\r | |
1171 | */\r | |
1172 | hasDomProp: function (propName) {\r | |
1173 | return this.getDomProp(propName) != null;\r | |
1174 | },\r | |
1175 | \r | |
1176 | /**\r | |
1177 | * Invalidates the component associated with this item. The layouts for this component\r | |
1178 | * and all of its contained items will be re-run after first clearing any computed\r | |
1179 | * values.\r | |
1180 | * \r | |
1181 | * If state needs to be carried forward beyond the invalidation, the `options` parameter\r | |
1182 | * can be used.\r | |
1183 | *\r | |
1184 | * @param {Object} options An object describing how to handle the invalidation.\r | |
1185 | * @param {Object} options.state An object to {@link Ext#apply} to the {@link #state}\r | |
1186 | * of this item after invalidation clears all other properties.\r | |
1187 | * @param {Function} options.before A function to call after the context data is cleared\r | |
1188 | * and before the {@link Ext.layout.Layout#beginLayoutCycle} methods are called.\r | |
1189 | * @param {Ext.layout.ContextItem} options.before.item This ContextItem.\r | |
1190 | * @param {Object} options.before.options The options object passed to {@link #invalidate}.\r | |
1191 | * @param {Function} options.after A function to call after the context data is cleared\r | |
1192 | * and after the {@link Ext.layout.Layout#beginLayoutCycle} methods are called.\r | |
1193 | * @param {Ext.layout.ContextItem} options.after.item This ContextItem.\r | |
1194 | * @param {Object} options.after.options The options object passed to {@link #invalidate}.\r | |
1195 | * @param {Object} options.scope The scope to use when calling the callback functions.\r | |
1196 | */\r | |
1197 | invalidate: function (options) {\r | |
1198 | this.context.queueInvalidate(this, options);\r | |
1199 | },\r | |
1200 | \r | |
1201 | markDirty: function () {\r | |
1202 | if (++this.dirtyCount === 1) {\r | |
1203 | // our first dirty property... queue us for flush\r | |
1204 | this.context.queueFlush(this);\r | |
1205 | }\r | |
1206 | },\r | |
1207 | \r | |
1208 | onBoxMeasured: function () {\r | |
1209 | var boxParent = this.boxParent,\r | |
1210 | state = this.state;\r | |
1211 | \r | |
1212 | if (boxParent && boxParent.widthModel.shrinkWrap && !state.boxMeasured && this.measuresBox) {\r | |
1213 | // since an autoWidth boxParent is holding a width on itself to allow each\r | |
1214 | // child to measure\r | |
1215 | state.boxMeasured = 1; // best to only call once per child\r | |
1216 | boxParent.boxChildMeasured();\r | |
1217 | }\r | |
1218 | },\r | |
1219 | \r | |
1220 | parseMargins: function (comp, margins) {\r | |
1221 | if (margins === true) {\r | |
1222 | margins = 5;\r | |
1223 | }\r | |
1224 | \r | |
1225 | var type = typeof margins,\r | |
1226 | ret;\r | |
1227 | \r | |
1228 | if (type === 'string' || type === 'number') {\r | |
1229 | ret = comp.parseBox(margins);\r | |
1230 | } else if (margins) {\r | |
1231 | ret = { top: 0, right: 0, bottom: 0, left: 0 }; // base defaults\r | |
1232 | \r | |
1233 | if (margins) {\r | |
1234 | margins = Ext.apply(ret, comp.parseBox(margins)); // + config\r | |
1235 | }\r | |
1236 | }\r | |
1237 | \r | |
1238 | return ret;\r | |
1239 | },\r | |
1240 | \r | |
1241 | peek: function (propName) {\r | |
1242 | return this.props[propName];\r | |
1243 | },\r | |
1244 | \r | |
1245 | recalculateSizeModel: function() {\r | |
1246 | // See the constructor, this logic is very similar. Not broken out into\r | |
1247 | // a separate method for performance reasons\r | |
1248 | var me = this,\r | |
1249 | target = me.target,\r | |
1250 | componentLayout = target.componentLayout,\r | |
1251 | ownerCtContext = me.ownerCtContext,\r | |
1252 | oldContext = componentLayout.ownerContext,\r | |
1253 | sizeModel;\r | |
1254 | \r | |
1255 | // If the componentLayout has an ownerContext, it will just use the sizeModel that\r | |
1256 | // exists on the context. Instead, force it to recalculate\r | |
1257 | componentLayout.ownerContext = null;\r | |
1258 | \r | |
1259 | me.sizeModel = sizeModel = target.getSizeModel(ownerCtContext &&\r | |
1260 | ownerCtContext.widthModel.pairsByHeightOrdinal[ownerCtContext.heightModel.ordinal]);\r | |
1261 | \r | |
1262 | me.widthModel = sizeModel.width;\r | |
1263 | me.heightModel = sizeModel.height;\r | |
1264 | \r | |
1265 | if (oldContext) {\r | |
1266 | componentLayout.ownerContext = me;\r | |
1267 | }\r | |
1268 | },\r | |
1269 | \r | |
1270 | /**\r | |
1271 | * Recovers a property value from the last computation and restores its value and\r | |
1272 | * dirty state.\r | |
1273 | * \r | |
1274 | * @param {String} propName The name of the property to recover.\r | |
1275 | * @param {Object} oldProps The old "props" object from which to recover values.\r | |
1276 | * @param {Object} oldDirty The old "dirty" object from which to recover state.\r | |
1277 | */\r | |
1278 | recoverProp: function (propName, oldProps, oldDirty) {\r | |
1279 | var me = this,\r | |
1280 | props = me.props,\r | |
1281 | dirty;\r | |
1282 | \r | |
1283 | if (propName in oldProps) {\r | |
1284 | props[propName] = oldProps[propName];\r | |
1285 | \r | |
1286 | if (oldDirty && propName in oldDirty) {\r | |
1287 | dirty = me.dirty || (me.dirty = {});\r | |
1288 | dirty[propName] = oldDirty[propName];\r | |
1289 | }\r | |
1290 | }\r | |
1291 | },\r | |
1292 | \r | |
1293 | redo: function(deep) {\r | |
1294 | var me = this,\r | |
1295 | items, len, i;\r | |
1296 | \r | |
1297 | me.revertProps(me.props);\r | |
1298 | \r | |
1299 | if (deep && me.wrapsComponent) {\r | |
1300 | // Rollback the state of child Components\r | |
1301 | if (me.childItems) {\r | |
1302 | for (i = 0, items = me.childItems, len = items.length; i < len; i++) {\r | |
1303 | items[i].redo(deep);\r | |
1304 | }\r | |
1305 | }\r | |
1306 | \r | |
1307 | // Rollback the state of child Elements\r | |
1308 | for (i = 0, items = me.children, len = items.length; i < len; i++) {\r | |
1309 | items[i].redo();\r | |
1310 | }\r | |
1311 | }\r | |
1312 | },\r | |
1313 | \r | |
1314 | /**\r | |
1315 | * Removes a cached ContextItem that was created using {@link #getEl}. It may be\r | |
1316 | * necessary to call this method if the dom reference for owned element changes so \r | |
1317 | * that {@link #getEl} can be called again to reinitialize the ContextItem with the\r | |
1318 | * new element.\r | |
1319 | * @param {String/Ext.dom.Element} nameOrEl The element or the name of an owned element\r | |
1320 | * @param {Ext.layout.container.Container/Ext.Component} [owner] The owner of the\r | |
1321 | * named element if the passed "nameOrEl" parameter is a String. Defaults to this\r | |
1322 | * ContextItem's "target" property.\r | |
1323 | */\r | |
1324 | removeEl: function(nameOrEl, owner) {\r | |
1325 | var me = this,\r | |
1326 | src, el;\r | |
1327 | \r | |
1328 | if (nameOrEl) {\r | |
1329 | if (nameOrEl.dom) {\r | |
1330 | el = nameOrEl;\r | |
1331 | } else {\r | |
1332 | src = me.target;\r | |
1333 | if (owner) {\r | |
1334 | src = owner;\r | |
1335 | }\r | |
1336 | \r | |
1337 | el = src[nameOrEl];\r | |
1338 | if (typeof el === 'function') { // ex 'getTarget'\r | |
1339 | el = el.call(src);\r | |
1340 | if (el === me.el) {\r | |
1341 | return this; // comp.getTarget() often returns comp.el\r | |
1342 | }\r | |
1343 | }\r | |
1344 | }\r | |
1345 | \r | |
1346 | if (el) {\r | |
1347 | me.context.removeEl(el, me);\r | |
1348 | }\r | |
1349 | }\r | |
1350 | },\r | |
1351 | \r | |
1352 | revertProps: function (props) {\r | |
1353 | var name,\r | |
1354 | flushed = this.flushedProps,\r | |
1355 | reverted = {};\r | |
1356 | \r | |
1357 | for (name in props) {\r | |
1358 | if (flushed.hasOwnProperty(name)) {\r | |
1359 | reverted[name] = props[name];\r | |
1360 | }\r | |
1361 | }\r | |
1362 | \r | |
1363 | this.writeProps(reverted);\r | |
1364 | },\r | |
1365 | \r | |
1366 | /**\r | |
1367 | * Queue the setting of a DOM attribute on this ContextItem's target when next flushed.\r | |
1368 | */\r | |
1369 | setAttribute: function(name, value) {\r | |
1370 | var me = this;\r | |
1371 | if (!me.attributes) {\r | |
1372 | me.attributes = {};\r | |
1373 | }\r | |
1374 | me.attributes[name] = value;\r | |
1375 | me.markDirty();\r | |
1376 | },\r | |
1377 | \r | |
1378 | setBox: function (box) {\r | |
1379 | var me = this;\r | |
1380 | \r | |
1381 | if ('left' in box) {\r | |
1382 | me.setProp('x', box.left);\r | |
1383 | }\r | |
1384 | if ('top' in box) {\r | |
1385 | me.setProp('y', box.top);\r | |
1386 | }\r | |
1387 | \r | |
1388 | // if sizeModel says we should not be setting these, the appropriate calls will be\r | |
1389 | // null operations... otherwise, we must set these values, so what we have in box\r | |
1390 | // is what we go with (undefined, NaN and no change are handled at a lower level):\r | |
1391 | me.setSize(box.width, box.height);\r | |
1392 | },\r | |
1393 | \r | |
1394 | /**\r | |
1395 | * Sets the contentHeight property. If the component uses raw content, then only the\r | |
1396 | * measured height is acceptable.\r | |
1397 | *\r | |
1398 | * Calculated values can sometimes be NaN or undefined, which generally mean the\r | |
1399 | * calculation is not done. To indicate that such as value was passed, 0 is returned.\r | |
1400 | * Otherwise, 1 is returned.\r | |
1401 | *\r | |
1402 | * If the caller is not measuring (i.e., they are calculating) and the component has raw\r | |
1403 | * content, 1 is returned indicating that the caller is done.\r | |
1404 | */\r | |
1405 | setContentHeight: function (height, measured) {\r | |
1406 | if (!measured && this.hasRawContent) {\r | |
1407 | return 1;\r | |
1408 | }\r | |
1409 | \r | |
1410 | return this.setProp('contentHeight', height);\r | |
1411 | },\r | |
1412 | \r | |
1413 | /**\r | |
1414 | * Sets the contentWidth property. If the component uses raw content, then only the\r | |
1415 | * measured width is acceptable.\r | |
1416 | * \r | |
1417 | * Calculated values can sometimes be NaN or undefined, which generally means that the\r | |
1418 | * calculation is not done. To indicate that such as value was passed, 0 is returned.\r | |
1419 | * Otherwise, 1 is returned.\r | |
1420 | *\r | |
1421 | * If the caller is not measuring (i.e., they are calculating) and the component has raw\r | |
1422 | * content, 1 is returned indicating that the caller is done.\r | |
1423 | */\r | |
1424 | setContentWidth: function (width, measured) {\r | |
1425 | if (!measured && this.hasRawContent) {\r | |
1426 | return 1;\r | |
1427 | }\r | |
1428 | \r | |
1429 | return this.setProp('contentWidth', width);\r | |
1430 | },\r | |
1431 | \r | |
1432 | /**\r | |
1433 | * Sets the contentWidth and contentHeight properties. If the component uses raw content,\r | |
1434 | * then only the measured values are acceptable.\r | |
1435 | * \r | |
1436 | * Calculated values can sometimes be NaN or undefined, which generally means that the\r | |
1437 | * calculation is not done. To indicate that either passed value was such a value, false\r | |
1438 | * returned. Otherwise, true is returned.\r | |
1439 | *\r | |
1440 | * If the caller is not measuring (i.e., they are calculating) and the component has raw\r | |
1441 | * content, true is returned indicating that the caller is done.\r | |
1442 | */\r | |
1443 | setContentSize: function (width, height, measured) {\r | |
1444 | return this.setContentWidth(width, measured) +\r | |
1445 | this.setContentHeight(height, measured) === 2;\r | |
1446 | },\r | |
1447 | \r | |
1448 | /**\r | |
1449 | * Sets a property value. This will unblock and/or trigger dependent layouts if the\r | |
1450 | * property value is being changed. Values of NaN and undefined are not accepted by\r | |
1451 | * this method.\r | |
1452 | * \r | |
1453 | * @param {String} propName The property name (e.g., 'width').\r | |
1454 | * @param {Object} value The new value of the property.\r | |
1455 | * @param {Boolean} dirty Optionally specifies if the value is currently in the DOM\r | |
1456 | * (default is `true` which indicates the value is not in the DOM and must be flushed\r | |
1457 | * at some point).\r | |
1458 | * @return {Number} 1 if this call specified the property value, 0 if not.\r | |
1459 | */\r | |
1460 | setProp: function (propName, value, dirty) {\r | |
1461 | var me = this,\r | |
1462 | valueType = typeof value,\r | |
1463 | info;\r | |
1464 | \r | |
1465 | if (valueType === 'undefined' || (valueType === 'number' && isNaN(value))) {\r | |
1466 | return 0;\r | |
1467 | }\r | |
1468 | if (me.props[propName] === value) {\r | |
1469 | return 1;\r | |
1470 | }\r | |
1471 | \r | |
1472 | me.props[propName] = value;\r | |
1473 | ++me.context.progressCount;\r | |
1474 | \r | |
1475 | if (dirty === false) {\r | |
1476 | // if the prop is equivalent to what is in the DOM (we won't be writing it),\r | |
1477 | // we need to clear hard blocks (domBlocks) on that property.\r | |
1478 | me.fireTriggers('domTriggers', propName);\r | |
1479 | me.clearBlocks('domBlocks', propName);\r | |
1480 | } else {\r | |
1481 | info = me.styleInfo[propName];\r | |
1482 | if (info) {\r | |
1483 | if (!me.dirty) {\r | |
1484 | me.dirty = {};\r | |
1485 | }\r | |
1486 | \r | |
1487 | me.dirty[propName] = value;\r | |
1488 | me.markDirty();\r | |
1489 | }\r | |
1490 | }\r | |
1491 | \r | |
1492 | // we always clear soft blocks on set\r | |
1493 | me.fireTriggers('triggers', propName);\r | |
1494 | me.clearBlocks('blocks', propName);\r | |
1495 | return 1;\r | |
1496 | },\r | |
1497 | \r | |
1498 | /**\r | |
1499 | * Sets the height and constrains the height to min/maxHeight range.\r | |
1500 | * \r | |
1501 | * @param {Number} height The height.\r | |
1502 | * @param {Boolean} [dirty=true] Specifies if the value is currently in the DOM. A\r | |
1503 | * value of `false` indicates that the value is already in the DOM.\r | |
1504 | * @return {Number} The actual height after constraining.\r | |
1505 | */\r | |
1506 | setHeight: function (height, dirty /*, private {Boolean} force */) {\r | |
1507 | var me = this,\r | |
1508 | comp = me.target,\r | |
1509 | ownerCtContext = me.ownerCtContext,\r | |
1510 | frameBody, frameInfo, min, oldHeight, rem;\r | |
1511 | \r | |
1512 | if (height < 0) {\r | |
1513 | height = 0;\r | |
1514 | }\r | |
1515 | if (!me.wrapsComponent) {\r | |
1516 | if (!me.setProp('height', height, dirty)) {\r | |
1517 | return NaN;\r | |
1518 | }\r | |
1519 | } else {\r | |
1520 | min = me.collapsedVert ? 0 : (comp.minHeight || 0);\r | |
1521 | height = Ext.Number.constrain(height, min, comp.maxHeight);\r | |
1522 | oldHeight = me.props.height;\r | |
1523 | if (!me.setProp('height', height, dirty)) {\r | |
1524 | return NaN;\r | |
1525 | }\r | |
1526 | \r | |
1527 | // if we are a container child, since the height is now known we can decrement\r | |
1528 | // the number of remainingChildDimensions that the ownerCtContext is waiting on.\r | |
1529 | if (ownerCtContext && !me.isComponentChild && isNaN(oldHeight)) {\r | |
1530 | rem = --ownerCtContext.remainingChildDimensions;\r | |
1531 | if (!rem) {\r | |
1532 | // if there are 0 remainingChildDimensions set containerChildrenSizeDone\r | |
1533 | // on the ownerCtContext to indicate that all of its children's dimensions\r | |
1534 | // are known\r | |
1535 | ownerCtContext.setProp('containerChildrenSizeDone', true);\r | |
1536 | }\r | |
1537 | }\r | |
1538 | \r | |
1539 | frameBody = me.frameBodyContext;\r | |
1540 | if (frameBody){\r | |
1541 | frameInfo = me.getFrameInfo();\r | |
1542 | frameBody[me.el.vertical ? 'setWidth' : 'setHeight'](height - frameInfo.height, dirty);\r | |
1543 | }\r | |
1544 | }\r | |
1545 | \r | |
1546 | return height;\r | |
1547 | },\r | |
1548 | \r | |
1549 | /**\r | |
1550 | * Sets the height and constrains the width to min/maxWidth range.\r | |
1551 | * \r | |
1552 | * @param {Number} width The width.\r | |
1553 | * @param {Boolean} [dirty=true] Specifies if the value is currently in the DOM. A\r | |
1554 | * value of `false` indicates that the value is already in the DOM.\r | |
1555 | * @return {Number} The actual width after constraining.\r | |
1556 | */\r | |
1557 | setWidth: function (width, dirty /*, private {Boolean} force */) {\r | |
1558 | var me = this,\r | |
1559 | comp = me.target,\r | |
1560 | ownerCtContext = me.ownerCtContext,\r | |
1561 | frameBody, frameInfo, min, oldWidth, rem;\r | |
1562 | \r | |
1563 | if (width < 0) {\r | |
1564 | width = 0;\r | |
1565 | }\r | |
1566 | if (!me.wrapsComponent) {\r | |
1567 | if (!me.setProp('width', width, dirty)) {\r | |
1568 | return NaN;\r | |
1569 | }\r | |
1570 | } else {\r | |
1571 | min = me.collapsedHorz ? 0 : (comp.minWidth || 0);\r | |
1572 | width = Ext.Number.constrain(width, min, comp.maxWidth);\r | |
1573 | oldWidth = me.props.width;\r | |
1574 | if (!me.setProp('width', width, dirty)) {\r | |
1575 | return NaN;\r | |
1576 | }\r | |
1577 | \r | |
1578 | // if we are a container child, since the width is now known we can decrement\r | |
1579 | // the number of remainingChildDimensions that the ownerCtContext is waiting on.\r | |
1580 | if (ownerCtContext && !me.isComponentChild && isNaN(oldWidth)) {\r | |
1581 | rem = --ownerCtContext.remainingChildDimensions;\r | |
1582 | if (!rem) {\r | |
1583 | // if there are 0 remainingChildDimensions set containerChildrenSizeDone\r | |
1584 | // on the ownerCtContext to indicate that all of its children's dimensions\r | |
1585 | // are known\r | |
1586 | ownerCtContext.setProp('containerChildrenSizeDone', true);\r | |
1587 | }\r | |
1588 | }\r | |
1589 | \r | |
1590 | //if ((frameBody = me.target.frameBody) && (frameBody = me.getEl(frameBody))){\r | |
1591 | frameBody = me.frameBodyContext;\r | |
1592 | if (frameBody) {\r | |
1593 | frameInfo = me.getFrameInfo();\r | |
1594 | frameBody.setWidth(width - frameInfo.width, dirty);\r | |
1595 | }\r | |
1596 | \r | |
1597 | /*if (owner.frameBody) {\r | |
1598 | frameContext = ownerContext.frameContext ||\r | |
1599 | (ownerContext.frameContext = ownerContext.getEl('frameBody'));\r | |
1600 | width += (frameContext.paddingInfo || frameContext.getPaddingInfo()).width;\r | |
1601 | }*/\r | |
1602 | }\r | |
1603 | \r | |
1604 | return width;\r | |
1605 | },\r | |
1606 | \r | |
1607 | setSize: function (width, height, dirty) {\r | |
1608 | this.setWidth(width, dirty);\r | |
1609 | this.setHeight(height, dirty);\r | |
1610 | },\r | |
1611 | \r | |
1612 | translateProps: {\r | |
1613 | x: 'left',\r | |
1614 | y: 'top'\r | |
1615 | },\r | |
1616 | \r | |
1617 | undo: function(deep) {\r | |
1618 | var me = this,\r | |
1619 | items, len, i;\r | |
1620 | \r | |
1621 | me.revertProps(me.lastBox);\r | |
1622 | \r | |
1623 | if (deep && me.wrapsComponent) {\r | |
1624 | // Rollback the state of child Components\r | |
1625 | if (me.childItems) {\r | |
1626 | for (i = 0, items = me.childItems, len = items.length; i < len; i++) {\r | |
1627 | items[i].undo(deep);\r | |
1628 | }\r | |
1629 | }\r | |
1630 | \r | |
1631 | // Rollback the state of child Elements\r | |
1632 | for (i = 0, items = me.children, len = items.length; i < len; i++) {\r | |
1633 | items[i].undo();\r | |
1634 | }\r | |
1635 | }\r | |
1636 | },\r | |
1637 | \r | |
1638 | unsetProp: function (propName) {\r | |
1639 | var dirty = this.dirty;\r | |
1640 | \r | |
1641 | delete this.props[propName];\r | |
1642 | if (dirty) {\r | |
1643 | delete dirty[propName];\r | |
1644 | }\r | |
1645 | },\r | |
1646 | \r | |
1647 | writeProps: function(dirtyProps, flushing) {\r | |
1648 | if (!(dirtyProps && typeof dirtyProps === 'object')) {\r | |
1649 | //<debug>\r | |
1650 | Ext.Logger.warn('writeProps expected dirtyProps to be an object');\r | |
1651 | //</debug>\r | |
1652 | return;\r | |
1653 | }\r | |
1654 | \r | |
1655 | var me = this,\r | |
1656 | el = me.el,\r | |
1657 | styles = {},\r | |
1658 | styleCount = 0, // used as a boolean, the exact count doesn't matter\r | |
1659 | styleInfo = me.styleInfo,\r | |
1660 | \r | |
1661 | info,\r | |
1662 | propName,\r | |
1663 | numericValue,\r | |
1664 | width = dirtyProps.width,\r | |
1665 | height = dirtyProps.height,\r | |
1666 | target = me.target,\r | |
1667 | hasWidth, hasHeight, isAbsolute, scrollbarSize, style, targetEl;\r | |
1668 | \r | |
1669 | // Process non-style properties:\r | |
1670 | if ('displayed' in dirtyProps) {\r | |
1671 | el.setDisplayed(dirtyProps.displayed);\r | |
1672 | }\r | |
1673 | \r | |
1674 | // Unblock any hard blocks (domBlocks) and copy dom styles into 'styles'\r | |
1675 | for (propName in dirtyProps) {\r | |
1676 | if (flushing) {\r | |
1677 | me.fireTriggers('domTriggers', propName);\r | |
1678 | me.clearBlocks('domBlocks', propName);\r | |
1679 | me.flushedProps[propName] = 1;\r | |
1680 | }\r | |
1681 | \r | |
1682 | info = styleInfo[propName];\r | |
1683 | if (info && info.dom) {\r | |
1684 | // Numeric dirty values should have their associated suffix added\r | |
1685 | if (info.suffix && (numericValue = parseInt(dirtyProps[propName], 10))) {\r | |
1686 | styles[propName] = numericValue + info.suffix;\r | |
1687 | }\r | |
1688 | // Non-numeric (eg "auto") go in unchanged.\r | |
1689 | else {\r | |
1690 | styles[propName] = dirtyProps[propName];\r | |
1691 | }\r | |
1692 | ++styleCount;\r | |
1693 | }\r | |
1694 | }\r | |
1695 | \r | |
1696 | // convert x/y into setPosition (for a component) or left/top styles (for an el)\r | |
1697 | if ('x' in dirtyProps || 'y' in dirtyProps) {\r | |
1698 | if (target.isComponent) {\r | |
1699 | target.setPosition(dirtyProps.x, dirtyProps.y);\r | |
1700 | } else {\r | |
1701 | // we wrap an element, so convert x/y to styles:\r | |
1702 | styleCount += me.addPositionStyles(styles, dirtyProps);\r | |
1703 | }\r | |
1704 | }\r | |
1705 | \r | |
1706 | // IE9 subtracts the scrollbar size from the element size when the element\r | |
1707 | // is absolutely positioned and uses box-sizing: border-box. To workaround this\r | |
1708 | // issue we have to add the the scrollbar size.\r | |
1709 | // \r | |
1710 | // See http://social.msdn.microsoft.com/Forums/da-DK/iewebdevelopment/thread/47c5148f-a142-4a99-9542-5f230c78cb3b\r | |
1711 | //\r | |
1712 | if (me.wrapsComponent && Ext.isIE9) {\r | |
1713 | // when we set a width and we have a vertical scrollbar (overflowY), we need\r | |
1714 | // to add the scrollbar width... conversely for the height and overflowX\r | |
1715 | if ((hasWidth = width !== undefined && me.hasOverflowY) ||\r | |
1716 | (hasHeight = height !== undefined && me.hasOverflowX)) {\r | |
1717 | // check that the component is absolute positioned.\r | |
1718 | isAbsolute = me.isAbsolute;\r | |
1719 | if (isAbsolute === undefined) {\r | |
1720 | isAbsolute = false;\r | |
1721 | targetEl = me.target.getTargetEl();\r | |
1722 | style = targetEl.getStyle('position');\r | |
1723 | me.isAbsolute = isAbsolute = (style === 'absolute'); // cache it\r | |
1724 | }\r | |
1725 | \r | |
1726 | if (isAbsolute) {\r | |
1727 | scrollbarSize = Ext.getScrollbarSize();\r | |
1728 | \r | |
1729 | if (hasWidth) {\r | |
1730 | width = parseInt(width, 10) + scrollbarSize.width;\r | |
1731 | styles.width = width + 'px';\r | |
1732 | ++styleCount;\r | |
1733 | }\r | |
1734 | if (hasHeight) {\r | |
1735 | height = parseInt(height, 10) + scrollbarSize.height;\r | |
1736 | styles.height = height + 'px';\r | |
1737 | ++styleCount;\r | |
1738 | }\r | |
1739 | }\r | |
1740 | }\r | |
1741 | }\r | |
1742 | \r | |
1743 | // we make only one call to setStyle to allow it to optimize itself:\r | |
1744 | if (styleCount) {\r | |
1745 | el.setStyle(styles);\r | |
1746 | }\r | |
1747 | },\r | |
1748 | \r | |
1749 | //-------------------------------------------------------------------------\r | |
1750 | // Diagnostics\r | |
1751 | \r | |
1752 | debugHooks: {\r | |
1753 | $enabled: false, // Disable by default\r | |
1754 | \r | |
1755 | addBlock: function (name, layout, propName) {\r | |
1756 | //Ext.log(this.id,'.',propName,' ',name,': ',this.context.getLayoutName(layout));\r | |
1757 | (layout.blockedBy || (layout.blockedBy = {}))[\r | |
1758 | this.id+'.'+propName+(name.substring(0,3)==='dom' ? ':dom' : '')] = 1;\r | |
1759 | \r | |
1760 | return this.callParent(arguments);\r | |
1761 | },\r | |
1762 | \r | |
1763 | addBoxChild: function (boxChildItem) {\r | |
1764 | var ret = this.callParent(arguments),\r | |
1765 | boxChildren = this.boxChildren,\r | |
1766 | boxParents;\r | |
1767 | \r | |
1768 | if (boxChildren && boxChildren.length === 1) {\r | |
1769 | // the boxParent collection is created by the run override found in\r | |
1770 | // Ext.diag.layout.Context, but IE sometimes does not load that override, so\r | |
1771 | // we work around it for now\r | |
1772 | boxParents = this.context.boxParents ||\r | |
1773 | (this.context.boxParents = new Ext.util.MixedCollection());\r | |
1774 | boxParents.add(this);\r | |
1775 | }\r | |
1776 | \r | |
1777 | return ret;\r | |
1778 | },\r | |
1779 | \r | |
1780 | addTrigger: function (propName, inDom) {\r | |
1781 | var layout = this.context.currentLayout,\r | |
1782 | triggers;\r | |
1783 | \r | |
1784 | //Ext.log(this.id,'.',propName,' ',inDom ? ':dom' : '',' ',this.context.getLayoutName(layout));\r | |
1785 | this.callParent(arguments);\r | |
1786 | \r | |
1787 | triggers = this.context.triggersByLayoutId;\r | |
1788 | (triggers[layout.id] || (triggers[layout.id] = {}))[\r | |
1789 | this.id+'.'+propName+(inDom ? ':dom' : '')] = {\r | |
1790 | item: this,\r | |
1791 | name: propName\r | |
1792 | };\r | |
1793 | },\r | |
1794 | \r | |
1795 | checkAuthority: function (prop) {\r | |
1796 | var me = this,\r | |
1797 | model = me[prop + 'Model'], // not me.sizeModel[prop] since it is immutable\r | |
1798 | layout = me.context.currentLayout,\r | |
1799 | ok,\r | |
1800 | setBy;\r | |
1801 | \r | |
1802 | if (layout === me.target.ownerLayout) {\r | |
1803 | // the ownerLayout is only allowed to set calculated dimensions\r | |
1804 | ok = model.calculated;\r | |
1805 | } else if (layout.isComponentLayout) {\r | |
1806 | // the component's componentLayout (normally) is only allowed to set auto or\r | |
1807 | // configured dimensions. The exception is when a component is run w/o its\r | |
1808 | // ownerLayout in the picture (isTopLevel), someone must publish the lastBox\r | |
1809 | // values and that lucky layout is the componentLayout (kinda had to be since\r | |
1810 | // the ownerLayout is not running)\r | |
1811 | ok = me.isTopLevel || model.auto || model.configured;\r | |
1812 | }\r | |
1813 | \r | |
1814 | if (!ok) {\r | |
1815 | setBy = me.context.getLayoutName(layout);\r | |
1816 | \r | |
1817 | Ext.log(setBy + ' cannot set ' + prop);\r | |
1818 | }\r | |
1819 | },\r | |
1820 | \r | |
1821 | clearBlocks: function (name, propName) {\r | |
1822 | var collection = this[name],\r | |
1823 | blockedLayouts = collection && collection[propName],\r | |
1824 | key = this.id + '.' + propName + (name.substring(0,3)==='dom' ? ':dom' : ''),\r | |
1825 | layout, layoutId;\r | |
1826 | \r | |
1827 | if (blockedLayouts) {\r | |
1828 | for (layoutId in blockedLayouts) {\r | |
1829 | layout = blockedLayouts[layoutId];\r | |
1830 | delete layout.blockedBy[key];\r | |
1831 | }\r | |
1832 | }\r | |
1833 | return this.callParent(arguments);\r | |
1834 | },\r | |
1835 | \r | |
1836 | getEl: function (el) {\r | |
1837 | var child = this.callParent(arguments);\r | |
1838 | if (child && child !== this && child.parent !== this) {\r | |
1839 | Ext.raise({\r | |
1840 | msg: 'Got element from wrong component'\r | |
1841 | });\r | |
1842 | }\r | |
1843 | return child;\r | |
1844 | },\r | |
1845 | \r | |
1846 | init: function () {\r | |
1847 | var me = this,\r | |
1848 | ret;\r | |
1849 | \r | |
1850 | ret = me.callParent(arguments);\r | |
1851 | \r | |
1852 | if (me.context.logOn.initItem) {\r | |
1853 | Ext.log(me.id, ' consumers: content=', me.consumersContentWidth,'/',me.consumersContentHeight,\r | |
1854 | ', container=', me.consumersContainerWidth,'/',me.consumersContainerHeight,\r | |
1855 | ', size=', me.consumersWidth,'/',me.consumersHeight);\r | |
1856 | }\r | |
1857 | \r | |
1858 | return ret;\r | |
1859 | },\r | |
1860 | \r | |
1861 | invalidate: function () {\r | |
1862 | if (this.wrapsComponent) {\r | |
1863 | if (this.context.logOn.invalidate) {\r | |
1864 | Ext.log('invalidate: ', this.id);\r | |
1865 | }\r | |
1866 | } else {\r | |
1867 | Ext.raise({\r | |
1868 | msg: 'Cannot invalidate an element contextItem'\r | |
1869 | });\r | |
1870 | }\r | |
1871 | return this.callParent(arguments);\r | |
1872 | },\r | |
1873 | \r | |
1874 | setProp: function (propName, value, dirty) {\r | |
1875 | var me = this,\r | |
1876 | layout = me.context.currentLayout,\r | |
1877 | setBy = me.context.getLayoutName(layout),\r | |
1878 | fullName = me.id + '.' + propName,\r | |
1879 | setByProps;\r | |
1880 | \r | |
1881 | if (value !== null) {\r | |
1882 | setByProps = me.setBy || (me.setBy = {});\r | |
1883 | if (!setByProps[propName]) {\r | |
1884 | setByProps[propName] = setBy;\r | |
1885 | } else if (setByProps[propName] !== setBy) {\r | |
1886 | Ext.log({level: 'warn'}, 'BAD! ', fullName, ' set by ', setByProps[propName], ' and ', setBy);\r | |
1887 | }\r | |
1888 | }\r | |
1889 | \r | |
1890 | if (me.context.logOn.setProp) {\r | |
1891 | if (typeof value !== 'undefined' && !isNaN(value) && me.props[propName] !== value) {\r | |
1892 | Ext.log('set ', fullName, ' = ', value, ' (', dirty, ')');\r | |
1893 | }\r | |
1894 | }\r | |
1895 | \r | |
1896 | return this.callParent(arguments);\r | |
1897 | },\r | |
1898 | \r | |
1899 | setHeight: function (height, dirty, /* private */force) {\r | |
1900 | if (!force && this.wrapsComponent) {\r | |
1901 | this.checkAuthority('height');\r | |
1902 | }\r | |
1903 | \r | |
1904 | return this.callParent(arguments);\r | |
1905 | },\r | |
1906 | \r | |
1907 | setWidth: function (width, dirty, /* private */force) {\r | |
1908 | if (!force && this.wrapsComponent) {\r | |
1909 | this.checkAuthority('width');\r | |
1910 | }\r | |
1911 | \r | |
1912 | return this.callParent(arguments);\r | |
1913 | }\r | |
1914 | } // End Diagnostics\r | |
1915 | //-------------------------------------------------------------------------\r | |
1916 | }, function () {\r | |
1917 | var px = { dom: true, parseInt: true, suffix: 'px' },\r | |
1918 | isDom = { dom: true },\r | |
1919 | faux = { dom: false };\r | |
1920 | \r | |
1921 | // If a property exists in styleInfo, it participates in some way with the DOM. It may\r | |
1922 | // be virtualized (like 'x' and y') and be indirect, but still requires a flush cycle\r | |
1923 | // to reach the DOM. Properties (like 'contentWidth' and 'contentHeight') have no real\r | |
1924 | // presence in the DOM and hence have no flush intanglements.\r | |
1925 | // \r | |
1926 | // For simple styles, the object value on the right contains properties that help in\r | |
1927 | // decoding values read by getStyle and preparing values to pass to setStyle.\r | |
1928 | //\r | |
1929 | this.prototype.styleInfo = {\r | |
1930 | containerChildrenSizeDone: faux,\r | |
1931 | containerLayoutDone: faux,\r | |
1932 | displayed: faux,\r | |
1933 | done: faux,\r | |
1934 | x: faux,\r | |
1935 | y: faux,\r | |
1936 | \r | |
1937 | // For Ext.grid.ColumnLayout\r | |
1938 | columnsChanged: faux,\r | |
1939 | rowHeights: faux,\r | |
1940 | viewOverflowY: faux,\r | |
1941 | \r | |
1942 | left: px,\r | |
1943 | top: px,\r | |
1944 | right: px,\r | |
1945 | bottom: px,\r | |
1946 | width: px,\r | |
1947 | height: px,\r | |
1948 | \r | |
1949 | 'border-top-width': px,\r | |
1950 | 'border-right-width': px,\r | |
1951 | 'border-bottom-width': px,\r | |
1952 | 'border-left-width': px,\r | |
1953 | \r | |
1954 | 'margin-top': px,\r | |
1955 | 'margin-right': px,\r | |
1956 | 'margin-bottom': px,\r | |
1957 | 'margin-left': px,\r | |
1958 | \r | |
1959 | 'padding-top': px,\r | |
1960 | 'padding-right': px,\r | |
1961 | 'padding-bottom': px,\r | |
1962 | 'padding-left': px,\r | |
1963 | \r | |
1964 | 'line-height': isDom,\r | |
1965 | display: isDom,\r | |
1966 | clear: isDom\r | |
1967 | };\r | |
1968 | });\r |