icloudtweetdeckhipchattelegramhangoutsslackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinboxwhatsapp
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1794 lines
68 KiB
1794 lines
68 KiB
/** |
|
* Manages context information during a layout. |
|
* |
|
* # Algorithm |
|
* |
|
* This class performs the following jobs: |
|
* |
|
* - Cache DOM reads to avoid reading the same values repeatedly. |
|
* - Buffer DOM writes and flush them as a block to avoid read/write interleaving. |
|
* - Track layout dependencies so each layout does not have to figure out the source of |
|
* its dependent values. |
|
* - Intelligently run layouts when the values on which they depend change (a trigger). |
|
* - Allow layouts to avoid processing when required values are unavailable (a block). |
|
* |
|
* Work done during layout falls into either a "read phase" or a "write phase" and it is |
|
* essential to always be aware of the current phase. Most methods in |
|
* {@link Ext.layout.Layout Layout} are called during a read phase: |
|
* {@link Ext.layout.Layout#calculate calculate}, |
|
* {@link Ext.layout.Layout#completeLayout completeLayout} and |
|
* {@link Ext.layout.Layout#finalizeLayout finalizeLayout}. The exceptions to this are |
|
* {@link Ext.layout.Layout#beginLayout beginLayout}, |
|
* {@link Ext.layout.Layout#beginLayoutCycle beginLayoutCycle} and |
|
* {@link Ext.layout.Layout#finishedLayout finishedLayout} which are called during |
|
* a write phase. While {@link Ext.layout.Layout#finishedLayout finishedLayout} is called |
|
* a write phase, it is really intended to be a catch-all for post-processing after a |
|
* layout run. |
|
* |
|
* In a read phase, it is OK to read the DOM but this should be done using the appropriate |
|
* {@link Ext.layout.ContextItem ContextItem} where possible since that provides a cache |
|
* to avoid redundant reads. No writes should be made to the DOM in a read phase! Instead, |
|
* the values should be written to the proper ContextItem for later write-back. |
|
* |
|
* The rules flip-flop in a write phase. The only difference is that ContextItem methods |
|
* like {@link Ext.layout.ContextItem#getStyle getStyle} will still read the DOM unless the |
|
* value was previously read. This detail is unknowable from the outside of ContextItem, so |
|
* read calls to ContextItem should also be avoided in a write phase. |
|
* |
|
* Calculating interdependent layouts requires a certain amount of iteration. In a given |
|
* cycle, some layouts will contribute results that allow other layouts to proceed. The |
|
* general flow then is to gather all of the layouts (both component and container) in a |
|
* component tree and queue them all for processing. The initial queue order is bottom-up |
|
* and component layout first, then container layout (if applicable) for each component. |
|
* |
|
* This initial step also calls the beginLayout method on all layouts to clear any values |
|
* from the DOM that might interfere with calculations and measurements. In other words, |
|
* this is a "write phase" and reads from the DOM should be strictly avoided. |
|
* |
|
* Next the layout enters into its iterations or "cycles". Each cycle consists of calling |
|
* the {@link Ext.layout.Layout#calculate calculate} method on all layouts in the |
|
* {@link #layoutQueue}. These calls are part of a "read phase" and writes to the DOM should |
|
* be strictly avoided. |
|
* |
|
* # Considerations |
|
* |
|
* **RULE 1**: Respect the read/write cycles. Always use the {@link Ext.layout.ContextItem#getProp getProp} |
|
* or {@link Ext.layout.ContextItem#getDomProp getDomProp} methods to get calculated values; |
|
* only use the {@link Ext.layout.ContextItem#getStyle getStyle} method to read styles; use |
|
* {@link Ext.layout.ContextItem#setProp setProp} to set DOM values. Some reads will, of |
|
* course, still go directly to the DOM, but if there is a method in |
|
* {@link Ext.layout.ContextItem ContextItem} to do a certain job, it should be used instead |
|
* of a lower-level equivalent. |
|
* |
|
* The basic logic flow in {@link Ext.layout.Layout#calculate calculate} consists of gathering |
|
* values by calling {@link Ext.layout.ContextItem#getProp getProp} or |
|
* {@link Ext.layout.ContextItem#getDomProp getDomProp}, calculating results and publishing |
|
* them by calling {@link Ext.layout.ContextItem#setProp setProp}. It is important to realize |
|
* that {@link Ext.layout.ContextItem#getProp getProp} will return `undefined` if the value |
|
* is not yet known. But the act of calling the method is enough to track the fact that the |
|
* calling layout depends (in some way) on this value. In other words, the calling layout is |
|
* "triggered" by the properties it requests. |
|
* |
|
* **RULE 2**: Avoid calling {@link Ext.layout.ContextItem#getProp getProp} unless the value |
|
* is needed. Gratuitous calls cause inefficiency because the layout will appear to depend on |
|
* values that it never actually uses. This applies equally to |
|
* {@link Ext.layout.ContextItem#getDomProp getDomProp} and the test-only methods |
|
* {@link Ext.layout.ContextItem#hasProp hasProp} and {@link Ext.layout.ContextItem#hasDomProp hasDomProp}. |
|
* |
|
* Because {@link Ext.layout.ContextItem#getProp getProp} can return `undefined`, it is often |
|
* the case that subsequent math will produce NaN's. This is usually not a problem as the |
|
* NaN's simply propagate along and result in final results that are NaN. Both `undefined` |
|
* and NaN are ignored by {@link Ext.layout.ContextItem#setProp}, so it is often not necessary |
|
* to even know that this is happening. It does become important for determining if a layout |
|
* is not done or if it might lead to publishing an incorrect (but not NaN or `undefined`) |
|
* value. |
|
* |
|
* **RULE 3**: If a layout has not calculated all the values it is required to calculate, it |
|
* must set {@link Ext.layout.Layout#done done} to `false` before returning from |
|
* {@link Ext.layout.Layout#calculate calculate}. This value is always `true` on entry because |
|
* it is simpler to detect the incomplete state rather than the complete state (especially up |
|
* and down a class hierarchy). |
|
* |
|
* **RULE 4**: A layout must never publish an incomplete (wrong) result. Doing so would cause |
|
* dependent layouts to run their calculations on those wrong values, producing more wrong |
|
* values and some layouts may even incorrectly flag themselves as {@link Ext.layout.Layout#done done} |
|
* before the correct values are determined and republished. Doing this will poison the |
|
* calculations. |
|
* |
|
* **RULE 5**: Each value should only be published by one layout. If multiple layouts attempt |
|
* to publish the same values, it would be nearly impossible to avoid breaking **RULE 4**. To |
|
* help detect this problem, the layout diagnostics will trap on an attempt to set a value |
|
* from different layouts. |
|
* |
|
* Complex layouts can produce many results as part of their calculations. These values are |
|
* important for other layouts to proceed and need to be published by the earliest possible |
|
* call to {@link Ext.layout.Layout#calculate} to avoid unnecessary cycles and poor performance. It is |
|
* also possible, however, for some results to be related in a way such that publishing them |
|
* may be an all-or-none proposition (typically to avoid breaking *RULE 4*). |
|
* |
|
* **RULE 6**: Publish results as soon as they are known to be correct rather than wait for |
|
* all values to be calculated. Waiting for everything to be complete can lead to deadlock. |
|
* The key here is not to forget **RULE 4** in the process. |
|
* |
|
* Some layouts depend on certain critical values as part of their calculations. For example, |
|
* HBox depends on width and cannot do anything until the width is known. In these cases, it |
|
* is best to use {@link Ext.layout.ContextItem#block block} or |
|
* {@link Ext.layout.ContextItem#domBlock domBlock} and thereby avoid processing the layout |
|
* until the needed value is available. |
|
* |
|
* **RULE 7**: Use {@link Ext.layout.ContextItem#block block} or |
|
* {@link Ext.layout.ContextItem#domBlock domBlock} when values are required to make progress. |
|
* This will mimize wasted recalculations. |
|
* |
|
* **RULE 8**: Blocks should only be used when no forward progress can be made. If even one |
|
* value could still be calculated, a block could result in a deadlock. |
|
* |
|
* Historically, layouts have been invoked directly by component code, sometimes in places |
|
* like an `afterLayout` method for a child component. With the flexibility now available |
|
* to solve complex, iterative issues, such things should be done in a responsible layout |
|
* (be it component or container). |
|
* |
|
* **RULE 9**: Use layouts to solve layout issues and don't wait for the layout to finish to |
|
* perform further layouts. This is especially important now that layouts process entire |
|
* component trees and not each layout in isolation. |
|
* |
|
* # Sequence Diagram |
|
* |
|
* The simplest sequence diagram for a layout run looks roughly like this: |
|
* |
|
* 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 | | | | | |
|
* - - - - - |
|
* |
|
* |
|
* Notes: |
|
* |
|
* **A.** This is a call from the {@link #run} method to the {@link #run} method. |
|
* Each layout in the queue will have its {@link Ext.layout.Layout#calculate calculate} |
|
* method called. |
|
* |
|
* **B.** After each {@link Ext.layout.Layout#calculate calculate} method is called the |
|
* {@link Ext.layout.Layout#done done} flag is checked to see if the Layout has completed. |
|
* If it has completed and that layout object implements a |
|
* {@link Ext.layout.Layout#completeLayout completeLayout} method, this layout is queued to |
|
* receive its call. Otherwise, the layout will be queued again unless there are blocks or |
|
* triggers that govern its requeueing. |
|
* |
|
* **C.** The call to {@link Ext.layout.ContextItem#getProp getProp} is made to the Item |
|
* and that will be tracked as a trigger (keyed by the name of the property being requested). |
|
* Changes to this property will cause this layout to be requeued. The call to |
|
* {@link Ext.layout.ContextItem#setProp setProp} will place a value in the item and not |
|
* directly into the DOM. |
|
* |
|
* **D.** Call the other layouts now in the first cycle (repeat **B** and **C** for each |
|
* layout). |
|
* |
|
* **E.** After completing a cycle, if progress was made (new properties were written to |
|
* the context) and if the {@link #layoutQueue} is not empty, the next cycle is run. If no |
|
* progress was made or no layouts are ready to run, all buffered values are written to |
|
* the DOM (a flush). |
|
* |
|
* **F.** After flushing, any layouts that were marked as {@link Ext.layout.Layout#done done} |
|
* that also have a {@link Ext.layout.Layout#completeLayout completeLayout} method are called. |
|
* This can cause them to become no longer done (see {@link #invalidate}). As with |
|
* {@link Ext.layout.Layout#calculate calculate}, this is considered a "read phase" and |
|
* direct DOM writes should be avoided. |
|
* |
|
* **G.** Flushing and calling any pending {@link Ext.layout.Layout#completeLayout completeLayout} |
|
* methods will likely trigger layouts that called {@link Ext.layout.ContextItem#getDomProp getDomProp} |
|
* and unblock layouts that have called {@link Ext.layout.ContextItem#domBlock domBlock}. |
|
* These variants are used when a layout needs the value to be correct in the DOM and not |
|
* simply known. If this does not cause at least one layout to enter the queue, we have a |
|
* layout FAILURE. Otherwise, we continue with the next cycle. |
|
* |
|
* **H.** Call {@link Ext.layout.Layout#calculate calculate} on any layouts in the queue |
|
* at the start of this cycle. Just a repeat of **B** through **G**. |
|
* |
|
* **I.** Once the layout has calculated all that it is resposible for, it can leave itself |
|
* in the {@link Ext.layout.Layout#done done} state. This is the value on entry to |
|
* {@link Ext.layout.Layout#calculate calculate} and must be cleared in that call if the |
|
* layout has more work to do. |
|
* |
|
* **J.** Now that all layouts are done, flush any DOM values and |
|
* {@link Ext.layout.Layout#completeLayout completeLayout} calls. This can again cause |
|
* layouts to become not done, and so we will be back on another cycle if that happens. |
|
* |
|
* **K.** After all layouts are done, call the {@link Ext.layout.Layout#finalizeLayout finalizeLayout} |
|
* method on any layouts that have one. As with {@link Ext.layout.Layout#completeLayout completeLayout}, |
|
* this can cause layouts to become no longer done. This is less desirable than using |
|
* {@link Ext.layout.Layout#completeLayout completeLayout} because it will cause all |
|
* {@link Ext.layout.Layout#finalizeLayout finalizeLayout} methods to be called again |
|
* when we think things are all wrapped up. |
|
* |
|
* **L.** After finishing the last iteration, layouts that have a |
|
* {@link Ext.layout.Layout#finishedLayout finishedLayout} method will be called. This |
|
* call will only happen once per run and cannot cause layouts to be run further. |
|
* |
|
* **M.** After calling finahedLayout, layouts that have a |
|
* {@link Ext.layout.Layout#notifyOwner notifyOwner} method will be called. This |
|
* call will only happen once per run and cannot cause layouts to be run further. |
|
* |
|
* **N.** One last flush to make sure everything has been written to the DOM. |
|
* |
|
* # Inter-Layout Collaboration |
|
* |
|
* Many layout problems require collaboration between multiple layouts. In some cases, this |
|
* is as simple as a component's container layout providing results used by its component |
|
* layout or vise-versa. A slightly more distant collaboration occurs in a box layout when |
|
* stretchmax is used: the child item's component layout provides results that are consumed |
|
* by the ownerCt's box layout to determine the size of the children. |
|
* |
|
* The various forms of interdependence between a container and its children are described by |
|
* each components' {@link Ext.Component#getSizeModel size model}. |
|
* |
|
* To facilitate this collaboration, the following pairs of properties are published to the |
|
* component's {@link Ext.layout.ContextItem ContextItem}: |
|
* |
|
* - width/height: These hold the final size of the component. The layout indicated by the |
|
* {@link Ext.Component#getSizeModel size model} is responsible for setting these. |
|
* - contentWidth/contentHeight: These hold size information published by the container |
|
* layout or from DOM measurement. These describe the content only. These values are |
|
* used by the component layout to determine the outer width/height when that component |
|
* is {@link Ext.Component#shrinkWrap shrink-wrapped}. They are also used to |
|
* determine overflow. All container layouts must publish these values for dimensions |
|
* that are shrink-wrapped. If a component has raw content (not container items), the |
|
* componentLayout must publish these values instead. |
|
* |
|
* @private |
|
*/ |
|
Ext.define('Ext.layout.Context', { |
|
requires: [ |
|
//<debug> |
|
'Ext.perf.Monitor', |
|
//</debug> |
|
'Ext.util.Queue', |
|
'Ext.layout.ContextItem', |
|
'Ext.layout.Layout', |
|
'Ext.fx.Anim', |
|
'Ext.fx.Manager' |
|
], |
|
|
|
remainingLayouts: 0, |
|
|
|
/** |
|
* @property {Number} state One of these values: |
|
* |
|
* - 0 - Before run |
|
* - 1 - Running |
|
* - 2 - Run complete |
|
*/ |
|
state: 0, |
|
|
|
/** |
|
* @property {Number} cycleWatchDog |
|
* This value is used to detect layouts that cannot progress by checking the amount of |
|
* cycles processed. The value should be large enough to satisfy all but exceptionally large |
|
* layout structures. When the amount of cycles is reached, the layout will fail. This should |
|
* only be used for debugging, layout failures should be considered as an exceptional occurrence. |
|
* @private |
|
* @since 5.1.1 |
|
*/ |
|
cycleWatchDog: 200, |
|
|
|
constructor: function (config) { |
|
var me = this; |
|
|
|
Ext.apply(me, config); |
|
|
|
// holds the ContextItem collection, keyed by element id |
|
me.items = {}; |
|
|
|
// a collection of layouts keyed by layout id |
|
me.layouts = {}; |
|
|
|
// the number of blocks of any kind: |
|
me.blockCount = 0; |
|
// the number of cycles that have been run: |
|
me.cycleCount = 0; |
|
// the number of flushes to the DOM: |
|
me.flushCount = 0; |
|
// the number of layout calculate calls: |
|
me.calcCount = 0; |
|
|
|
me.animateQueue = me.newQueue(); |
|
me.completionQueue = me.newQueue(); |
|
me.finalizeQueue = me.newQueue(); |
|
me.finishQueue = me.newQueue(); |
|
me.flushQueue = me.newQueue(); |
|
|
|
me.invalidateData = {}; |
|
|
|
/** |
|
* @property {Ext.util.Queue} layoutQueue |
|
* List of layouts to perform. |
|
*/ |
|
me.layoutQueue = me.newQueue(); |
|
|
|
// this collection is special because we ensure that there are no parent/child pairs |
|
// present, only distinct top-level components |
|
me.invalidQueue = []; |
|
|
|
me.triggers = { |
|
data: { |
|
/* |
|
layoutId: [ |
|
{ item: contextItem, prop: propertyName } |
|
] |
|
*/ |
|
}, |
|
dom: {} |
|
}; |
|
}, |
|
|
|
callLayout: function (layout, methodName) { |
|
this.currentLayout = layout; |
|
layout[methodName](this.getCmp(layout.owner)); |
|
}, |
|
|
|
cancelComponent: function (comp, isChild, isDestroying) { |
|
var me = this, |
|
components = comp, |
|
isArray = !comp.isComponent, |
|
length = isArray ? components.length : 1, |
|
i, k, klen, items, layout, newQueue, oldQueue, entry, temp, |
|
ownerCtContext; |
|
|
|
for (i = 0; i < length; ++i) { |
|
if (isArray) { |
|
comp = components[i]; |
|
} |
|
|
|
// If the component is being destroyed, remove the component's ContextItem from its parent's contextItem.childItems array |
|
if (isDestroying && comp.ownerCt) { |
|
ownerCtContext = this.items[comp.ownerCt.el.id]; |
|
if (ownerCtContext) { |
|
Ext.Array.remove(ownerCtContext.childItems, me.getCmp(comp)); |
|
} |
|
} |
|
|
|
if (!isChild) { |
|
oldQueue = me.invalidQueue; |
|
klen = oldQueue.length; |
|
|
|
if (klen) { |
|
me.invalidQueue = newQueue = []; |
|
for (k = 0; k < klen; ++k) { |
|
entry = oldQueue[k]; |
|
temp = entry.item.target; |
|
if (temp !== comp && !temp.up(comp)) { |
|
newQueue.push(entry); |
|
} |
|
} |
|
} |
|
} |
|
|
|
layout = comp.componentLayout; |
|
me.cancelLayout(layout); |
|
|
|
if (layout.getLayoutItems) { |
|
items = layout.getLayoutItems(); |
|
if (items.length) { |
|
me.cancelComponent(items, true); |
|
} |
|
} |
|
|
|
if (comp.isContainer && !comp.collapsed) { |
|
layout = comp.layout; |
|
me.cancelLayout(layout); |
|
|
|
items = layout.getVisibleItems(); |
|
if (items.length) { |
|
me.cancelComponent(items, true); |
|
} |
|
} |
|
} |
|
}, |
|
|
|
cancelLayout: function (layout) { |
|
var me = this; |
|
|
|
me.completionQueue.remove(layout); |
|
me.finalizeQueue.remove(layout); |
|
me.finishQueue.remove(layout); |
|
me.layoutQueue.remove(layout); |
|
|
|
if (layout.running) { |
|
me.layoutDone(layout); |
|
} |
|
|
|
layout.ownerContext = null; |
|
}, |
|
|
|
clearTriggers: function (layout, inDom) { |
|
var id = layout.id, |
|
collection = this.triggers[inDom ? 'dom' : 'data'], |
|
triggers = collection && collection[id], |
|
length = (triggers && triggers.length) || 0, |
|
i, item, trigger; |
|
|
|
for (i = 0; i < length; ++i) { |
|
trigger = triggers[i]; |
|
item = trigger.item; |
|
|
|
collection = inDom ? item.domTriggers : item.triggers; |
|
delete collection[trigger.prop][id]; |
|
} |
|
}, |
|
|
|
/** |
|
* Flushes any pending writes to the DOM by calling each ContextItem in the flushQueue. |
|
*/ |
|
flush: function () { |
|
var me = this, |
|
items = me.flushQueue.clear(), |
|
length = items.length, i; |
|
|
|
if (length) { |
|
++me.flushCount; |
|
|
|
for (i = 0; i < length; ++i) { |
|
items[i].flush(); |
|
} |
|
} |
|
}, |
|
|
|
flushAnimations: function() { |
|
var me = this, |
|
items = me.animateQueue.clear(), |
|
len = items.length, |
|
i; |
|
|
|
if (len) { |
|
for (i = 0; i < len; i++) { |
|
// Each Component may refuse to participate in animations. |
|
// This is used by the BoxReorder plugin which drags a Component, |
|
// during which that Component must be exempted from layout positioning. |
|
if (items[i].target.animate !== false) { |
|
items[i].flushAnimations(); |
|
} |
|
} |
|
|
|
// Ensure the first frame fires now to avoid a browser repaint with the elements in the "to" state |
|
// before they are returned to their "from" state by the animation. |
|
Ext.fx.Manager.runner(); |
|
} |
|
}, |
|
|
|
flushInvalidates: function () { |
|
var me = this, |
|
queue = me.invalidQueue, |
|
length = queue && queue.length, |
|
comp, components, entry, i; |
|
|
|
me.invalidQueue = []; |
|
|
|
if (length) { |
|
components = []; |
|
for (i = 0; i < length; ++i) { |
|
comp = (entry = queue[i]).item.target; |
|
// we filter out-of-body components here but allow them into the queue to |
|
// ensure that their child components are coalesced out (w/no additional |
|
// cost beyond our normal effort to avoid parent/child components in the |
|
// queue) |
|
if (!comp.container.isDetachedBody) { |
|
components.push(comp); |
|
|
|
if (entry.options) { |
|
me.invalidateData[comp.id] = entry.options; |
|
} |
|
} |
|
} |
|
|
|
me.invalidate(components, null); |
|
} |
|
}, |
|
|
|
flushLayouts: function (queueName, methodName, dontClear) { |
|
var me = this, |
|
layouts = dontClear ? me[queueName].items : me[queueName].clear(), |
|
length = layouts.length, |
|
i, layout; |
|
|
|
if (length) { |
|
for (i = 0; i < length; ++i) { |
|
layout = layouts[i]; |
|
if (!layout.running) { |
|
me.callLayout(layout, methodName); |
|
} |
|
} |
|
me.currentLayout = null; |
|
} |
|
}, |
|
|
|
/** |
|
* Returns the ContextItem for a component. |
|
* @param {Ext.Component} cmp |
|
*/ |
|
getCmp: function (cmp) { |
|
return this.getItem(cmp, cmp.el); |
|
}, |
|
|
|
/** |
|
* Returns the ContextItem for an element. |
|
* @param {Ext.layout.ContextItem} parent |
|
* @param {Ext.dom.Element} el |
|
*/ |
|
getEl: function (parent, el) { |
|
var item = this.getItem(el, el); |
|
|
|
if (!item.parent) { |
|
item.parent = parent; |
|
|
|
// all items share an empty children array (to avoid null checks), so we can |
|
// only push on to the children array if there is already something there (we |
|
// copy-on-write): |
|
if (parent.children.length) { |
|
parent.children.push(item); |
|
} else { |
|
parent.children = [ item ]; // now parent has its own children[] (length=1) |
|
} |
|
} |
|
|
|
return item; |
|
}, |
|
|
|
getItem: function (target, el) { |
|
var id = el.id, |
|
items = this.items, |
|
item = items[id] || |
|
(items[id] = new Ext.layout.ContextItem({ |
|
context: this, |
|
target: target, |
|
el: el |
|
})); |
|
|
|
return item; |
|
}, |
|
|
|
handleFailure: function () { |
|
// This method should never be called, but is need when layouts fail (hence the |
|
// "should never"). We just disconnect any of the layouts from the run and return |
|
// them to the state they would be in had the layout completed properly. |
|
var layouts = this.layouts, |
|
layout, key; |
|
|
|
Ext.failedLayouts = (Ext.failedLayouts || 0) + 1; |
|
|
|
for (key in layouts) { |
|
layout = layouts[key]; |
|
|
|
if (layouts.hasOwnProperty(key)) { |
|
layout.running = false; |
|
layout.ownerContext = null; |
|
} |
|
} |
|
|
|
//<debug> |
|
if (Ext.repoDevMode && !this.pageAnalyzerMode) { |
|
Ext.Error.raise('Layout run failed'); |
|
} else { |
|
Ext.log.error('Layout run failed'); |
|
} |
|
//</debug> |
|
}, |
|
|
|
/** |
|
* Invalidates one or more components' layouts (component and container). This can be |
|
* called before run to identify the components that need layout or during the run to |
|
* restart the layout of a component. This is called internally to flush any queued |
|
* invalidations at the start of a cycle. If called during a run, it is not expected |
|
* that new components will be introduced to the layout. |
|
* |
|
* @param {Ext.Component/Array} components An array of Components or a single Component. |
|
* @param {Boolean} full True if all properties should be invalidated, otherwise only |
|
* those calculated by the component should be invalidated. |
|
*/ |
|
invalidate: function (components, full) { |
|
var me = this, |
|
isArray = !components.isComponent, |
|
containerLayoutDone, ownerLayout, |
|
firstTime, i, comp, item, items, length, componentLayout, layout, |
|
invalidateOptions, token, skipLayout; |
|
|
|
for (i = 0, length = isArray ? components.length : 1; i < length; ++i) { |
|
comp = isArray ? components[i] : components; |
|
|
|
if (comp.rendered && !comp.hidden) { |
|
ownerLayout = comp.ownerLayout; |
|
componentLayout = comp.componentLayout; |
|
firstTime = !componentLayout.ownerContext; |
|
skipLayout = false; |
|
|
|
if ((!ownerLayout || !ownerLayout.needsItemSize) && comp.liquidLayout) { |
|
// our owning layout doesn't need us to run, and our componentLayout |
|
// wants to opt out because it uses liquid CSS layout. |
|
// We can skip invalidation for this component. |
|
skipLayout = true; |
|
} |
|
|
|
// if we are skipping layout, we can also skip creation of the context |
|
// item, unless our owner layout needs it to set our size. |
|
if (!skipLayout || (ownerLayout && ownerLayout.setsItemSize)) { |
|
item = me.getCmp(comp); |
|
|
|
// If the component has had no changes which necessitate a layout, do not lay it out. |
|
// Temporarily disabled because this breaks dock layout (see EXTJSIV-10251) |
|
// if (item.optOut) { |
|
// skipLayout = true; |
|
// } |
|
|
|
layout = (comp.isContainer && !comp.collapsed) ? comp.layout : null; |
|
|
|
// Extract any invalidate() options for this item. |
|
invalidateOptions = me.invalidateData[item.id]; |
|
delete me.invalidateData[item.id]; |
|
|
|
// We invalidate the contextItem's in a top-down manner so that SizeModel |
|
// info for containers is available to their children. This is a critical |
|
// optimization since sizeModel determination often requires knowing the |
|
// sizeModel of the ownerCt. If this weren't cached as we descend, this |
|
// would be an O(N^2) operation! (where N=number of components, or 300+/- |
|
// in Themes) |
|
token = item.init(full, invalidateOptions); |
|
} |
|
|
|
if (skipLayout) { |
|
continue; |
|
} |
|
|
|
if (invalidateOptions) { |
|
me.processInvalidate(invalidateOptions, item, 'before'); |
|
} |
|
|
|
// Allow the component layout a chance to effect its size model before we |
|
// recurse down the component hierarchy (since children need to know the |
|
// size model of their ownerCt). |
|
if (componentLayout.beforeLayoutCycle) { |
|
componentLayout.beforeLayoutCycle(item); |
|
} |
|
|
|
if (layout && layout.beforeLayoutCycle) { |
|
// allow the container layout take a peek as well. Table layout can |
|
// influence its children's styling due to the interaction of nesting |
|
// table-layout:fixed and auto inside each other without intervening |
|
// elements of known size. |
|
layout.beforeLayoutCycle(item); |
|
} |
|
|
|
// Finish up the item-level processing that is based on the size model of |
|
// the component. |
|
token = item.initContinue(token); |
|
|
|
// Start this state variable at true, since that is the value we want if |
|
// they do not apply (i.e., no work of this kind on which to wait). |
|
containerLayoutDone = true; |
|
|
|
// A ComponentLayout MUST implement getLayoutItems to allow its children |
|
// to be collected. Ext.container.Container does this, but non-Container |
|
// Components which manage Components as part of their structure (e.g., |
|
// HtmlEditor) must still return child Components via getLayoutItems. |
|
if (componentLayout.getLayoutItems) { |
|
componentLayout.renderChildren(); |
|
|
|
items = componentLayout.getLayoutItems(); |
|
if (items.length) { |
|
me.invalidate(items, true); |
|
} |
|
} |
|
|
|
if (layout) { |
|
containerLayoutDone = false; |
|
layout.renderChildren(); |
|
|
|
if (layout.needsItemSize || layout.activeItemCount) { |
|
// if the layout specified that it needs the layouts of its children |
|
// to run, or if the number of "liquid" child layouts is greater |
|
// than 0, we need to recurse into the children, since some or |
|
// all of them may need their layouts to run. |
|
items = layout.getVisibleItems(); |
|
if (items.length) { |
|
me.invalidate(items, true); |
|
} |
|
} |
|
} |
|
|
|
// Finish the processing that requires the size models of child items to |
|
// be determined (and some misc other stuff). |
|
item.initDone(containerLayoutDone); |
|
|
|
// Inform the layouts that we are about to begin (or begin again) now that |
|
// the size models of the component and its children are setup. |
|
me.resetLayout(componentLayout, item, firstTime); |
|
if (layout) { |
|
me.resetLayout(layout, item, firstTime); |
|
} |
|
|
|
// This has to occur after the component layout has had a chance to begin |
|
// so that we can determine what kind of animation might be needed. TODO- |
|
// move this determination into the layout itself. |
|
item.initAnimation(); |
|
|
|
if (invalidateOptions) { |
|
me.processInvalidate(invalidateOptions, item, 'after'); |
|
} |
|
} |
|
} |
|
|
|
me.currentLayout = null; |
|
}, |
|
|
|
// Returns true is descendant is a descendant of ancestor |
|
isDescendant: function(ancestor, descendant) { |
|
if (ancestor.isContainer) { |
|
for (var c = descendant.ownerCt; c; c = c.ownerCt) { |
|
if (c === ancestor) { |
|
return true; |
|
} |
|
} |
|
} |
|
return false; |
|
}, |
|
|
|
layoutDone: function (layout) { |
|
var ownerContext = layout.ownerContext; |
|
|
|
layout.running = false; |
|
|
|
// Once a component layout completes, we can mark it as "done". |
|
if (layout.isComponentLayout) { |
|
if (ownerContext.measuresBox) { |
|
ownerContext.onBoxMeasured(); // be sure to release our boxParent |
|
} |
|
|
|
ownerContext.setProp('done', true); |
|
} else { |
|
ownerContext.setProp('containerLayoutDone', true); |
|
} |
|
|
|
--this.remainingLayouts; |
|
++this.progressCount; // a layout completion is progress |
|
}, |
|
|
|
newQueue: function () { |
|
return new Ext.util.Queue(); |
|
}, |
|
|
|
processInvalidate: function (options, item, name) { |
|
// When calling a callback, the currentLayout needs to be adjusted so |
|
// that whichever layout caused the invalidate is the currentLayout... |
|
if (options[name]) { |
|
var me = this, |
|
currentLayout = me.currentLayout; |
|
|
|
me.currentLayout = options.layout || null; |
|
|
|
options[name](item, options); |
|
|
|
me.currentLayout = currentLayout; |
|
} |
|
}, |
|
|
|
/** |
|
* Queues a ContextItem to have its {@link Ext.layout.ContextItem#flushAnimations} method called. |
|
* |
|
* @param {Ext.layout.ContextItem} item |
|
* @private |
|
*/ |
|
queueAnimation: function (item) { |
|
this.animateQueue.add(item); |
|
}, |
|
|
|
/** |
|
* Queues a layout to have its {@link Ext.layout.Layout#completeLayout} method called. |
|
* |
|
* @param {Ext.layout.Layout} layout |
|
* @private |
|
*/ |
|
queueCompletion: function (layout) { |
|
this.completionQueue.add(layout); |
|
}, |
|
|
|
/** |
|
* Queues a layout to have its {@link Ext.layout.Layout#finalizeLayout} method called. |
|
* |
|
* @param {Ext.layout.Layout} layout |
|
* @private |
|
*/ |
|
queueFinalize: function (layout) { |
|
this.finalizeQueue.add(layout); |
|
}, |
|
|
|
/** |
|
* Queues a ContextItem for the next flush to the DOM. This should only be called by |
|
* the {@link Ext.layout.ContextItem} class. |
|
* |
|
* @param {Ext.layout.ContextItem} item |
|
* @private |
|
*/ |
|
queueFlush: function (item) { |
|
this.flushQueue.add(item); |
|
}, |
|
|
|
chainFns: function (oldOptions, newOptions, funcName) { |
|
var me = this, |
|
oldLayout = oldOptions.layout, |
|
newLayout = newOptions.layout, |
|
oldFn = oldOptions[funcName], |
|
newFn = newOptions[funcName]; |
|
|
|
// Call newFn last so it can get the final word on things... also, the "this" |
|
// pointer will be passed correctly by createSequence with oldFn first. |
|
return function (contextItem) { |
|
var prev = me.currentLayout; |
|
if (oldFn) { |
|
me.currentLayout = oldLayout; |
|
oldFn.call(oldOptions.scope || oldOptions, contextItem, oldOptions); |
|
} |
|
me.currentLayout = newLayout; |
|
newFn.call(newOptions.scope || newOptions, contextItem, newOptions); |
|
me.currentLayout = prev; |
|
}; |
|
}, |
|
|
|
purgeInvalidates: function () { |
|
var me = this, |
|
newQueue = [], |
|
oldQueue = me.invalidQueue, |
|
oldLength = oldQueue.length, |
|
oldIndex, newIndex, newEntry, newComp, oldEntry, oldComp, keep; |
|
|
|
for (oldIndex = 0; oldIndex < oldLength; ++oldIndex) { |
|
oldEntry = oldQueue[oldIndex]; |
|
oldComp = oldEntry.item.target; |
|
|
|
keep = true; |
|
for (newIndex = newQueue.length; newIndex--;) { |
|
newEntry = newQueue[newIndex]; |
|
newComp = newEntry.item.target; |
|
|
|
if (oldComp.isLayoutChild(newComp)) { |
|
keep = false; |
|
break; |
|
} |
|
|
|
if (newComp.isLayoutChild(oldComp)) { |
|
Ext.Array.erase(newQueue, newIndex, 1); |
|
} |
|
} |
|
|
|
if (keep) { |
|
newQueue.push(oldEntry); |
|
} |
|
} |
|
|
|
me.invalidQueue = newQueue; |
|
}, |
|
|
|
/** |
|
* Queue a component (and its tree) to be invalidated on the next cycle. |
|
* |
|
* @param {Ext.Component/Ext.layout.ContextItem} item The component or ContextItem to invalidate. |
|
* @param {Object} options An object describing how to handle the invalidation (see |
|
* {@link Ext.layout.ContextItem#invalidate} for details). |
|
* @private |
|
*/ |
|
queueInvalidate: function (item, options) { |
|
var me = this, |
|
newQueue = [], |
|
oldQueue = me.invalidQueue, |
|
index = oldQueue.length, |
|
comp, old, oldComp, oldOptions, oldState; |
|
|
|
if (item.isComponent) { |
|
item = me.getCmp(comp = item); |
|
} else { |
|
comp = item.target; |
|
} |
|
|
|
item.invalid = true; |
|
|
|
// See if comp is contained by any component already in the queue (ignore comp if |
|
// that is the case). Eliminate any components in the queue that are contained by |
|
// comp (by not adding them to newQueue). |
|
while (index--) { |
|
old = oldQueue[index]; |
|
oldComp = old.item.target; |
|
|
|
if (!comp.isFloating && comp.up(oldComp)) { |
|
return; // oldComp contains comp, so this invalidate is redundant |
|
} |
|
|
|
if (oldComp === comp) { |
|
// if already in the queue, update the options... |
|
if (!(oldOptions = old.options)) { |
|
old.options = options; |
|
} else if (options) { |
|
if (options.widthModel) { |
|
oldOptions.widthModel = options.widthModel; |
|
} |
|
if (options.heightModel) { |
|
oldOptions.heightModel = options.heightModel; |
|
} |
|
if (!(oldState = oldOptions.state)) { |
|
oldOptions.state = options.state; |
|
} else if (options.state) { |
|
Ext.apply(oldState, options.state); |
|
} |
|
|
|
if (options.before) { |
|
oldOptions.before = me.chainFns(oldOptions, options, 'before'); |
|
} |
|
if (options.after) { |
|
oldOptions.after = me.chainFns(oldOptions, options, 'after'); |
|
} |
|
} |
|
|
|
// leave the old queue alone now that we've update this comp's entry... |
|
return; |
|
} |
|
|
|
if (!oldComp.isLayoutChild(comp)) { |
|
newQueue.push(old); // comp does not contain oldComp |
|
} |
|
// else if (oldComp isDescendant of comp) skip |
|
} |
|
// newQueue contains only those components not a descendant of comp |
|
|
|
// to get here, comp must not be a child of anything already in the queue, so it |
|
// needs to be added along with its "options": |
|
newQueue.push({ item: item, options: options }); |
|
|
|
me.invalidQueue = newQueue; |
|
}, |
|
|
|
queueItemLayouts: function (item) { |
|
var comp = item.isComponent ? item : item.target, |
|
layout = comp.componentLayout; |
|
|
|
if (!layout.pending && !layout.invalid && !layout.done) { |
|
this.queueLayout(layout); |
|
} |
|
|
|
layout = comp.layout; |
|
if (layout && !layout.pending && !layout.invalid && !layout.done && !comp.collapsed) { |
|
this.queueLayout(layout); |
|
} |
|
}, |
|
|
|
/** |
|
* Queues a layout for the next calculation cycle. This should not be called if the |
|
* layout is done, blocked or already in the queue. The only classes that should call |
|
* this method are this class and {@link Ext.layout.ContextItem}. |
|
* |
|
* @param {Ext.layout.Layout} layout The layout to add to the queue. |
|
* @private |
|
*/ |
|
queueLayout: function (layout) { |
|
this.layoutQueue.add(layout); |
|
layout.pending = true; |
|
}, |
|
|
|
/** |
|
* Removes the ContextItem for an element from the cache and from the parent's |
|
* "children" array. |
|
* @param {Ext.layout.ContextItem} parent |
|
* @param {Ext.dom.Element} el |
|
*/ |
|
removeEl: function (parent, el) { |
|
var id = el.id, |
|
children = parent.children, |
|
items = this.items; |
|
|
|
if(children) { |
|
Ext.Array.remove(children, items[id]); |
|
} |
|
delete items[id]; |
|
}, |
|
|
|
/** |
|
* Resets the given layout object. This is called at the start of the run and can also |
|
* be called during the run by calling {@link #invalidate}. |
|
*/ |
|
resetLayout: function (layout, ownerContext, firstTime) { |
|
var me = this; |
|
|
|
me.currentLayout = layout; |
|
|
|
layout.done = false; |
|
layout.pending = true; |
|
layout.firedTriggers = 0; |
|
|
|
me.layoutQueue.add(layout); |
|
|
|
if (firstTime) { |
|
me.layouts[layout.id] = layout; // track the layout for this run by its id |
|
layout.running = true; |
|
|
|
if (layout.finishedLayout) { |
|
me.finishQueue.add(layout); |
|
} |
|
|
|
// reset or update per-run counters: |
|
|
|
++me.remainingLayouts; |
|
++layout.layoutCount; // the number of whole layouts run for the layout |
|
|
|
layout.ownerContext = ownerContext; |
|
layout.beginCount = 0; // the number of beginLayout calls |
|
layout.blockCount = 0; // the number of blocks set for the layout |
|
layout.calcCount = 0; // the number of times calculate is called |
|
layout.triggerCount = 0; // the number of triggers set for the layout |
|
|
|
if (!layout.initialized) { |
|
layout.initLayout(); |
|
} |
|
|
|
layout.beginLayout(ownerContext); |
|
} else { |
|
++layout.beginCount; |
|
|
|
if (!layout.running) { |
|
// back into the mahem with this one: |
|
++me.remainingLayouts; |
|
layout.running = true; |
|
|
|
if (layout.isComponentLayout) { |
|
// this one is fun... if we call setProp('done', false) that would still |
|
// trigger/unblock layouts, but what layouts are really looking for with |
|
// this property is for it to go to true, not just be set to a value... |
|
ownerContext.unsetProp('done'); |
|
} |
|
|
|
// and it needs to be removed from the completion and/or finalize queues... |
|
me.completionQueue.remove(layout); |
|
me.finalizeQueue.remove(layout); |
|
} |
|
} |
|
|
|
layout.beginLayoutCycle(ownerContext, firstTime); |
|
}, |
|
|
|
/** |
|
* Runs the layout calculations. This can be called only once on this object. |
|
* @return {Boolean} True if all layouts were completed, false if not. |
|
*/ |
|
run: function () { |
|
var me = this, |
|
flushed = false, |
|
watchDog = me.cycleWatchDog; |
|
|
|
me.purgeInvalidates(); |
|
me.flushInvalidates(); |
|
|
|
me.state = 1; |
|
me.totalCount = me.layoutQueue.getCount(); |
|
|
|
// We may start with unflushed data placed by beginLayout calls. Since layouts may |
|
// use setProp as a convenience, even in a write phase, we don't want to transition |
|
// to a read phase with unflushed data since we can write it now "cheaply". Also, |
|
// these value could easily be needed in the DOM in order to really get going with |
|
// the calculations. In particular, fixed (configured) dimensions fall into this |
|
// category. |
|
me.flush(); |
|
|
|
// While we have layouts that have not completed... |
|
while ((me.remainingLayouts || me.invalidQueue.length) && watchDog--) { |
|
if (me.invalidQueue.length) { |
|
me.flushInvalidates(); |
|
} |
|
|
|
// if any of them can run right now, run them |
|
if (me.runCycle()) { |
|
flushed = false; // progress means we probably need to flush something |
|
// but not all progress appears in the flushQueue (e.g. 'contentHeight') |
|
} else if (!flushed) { |
|
// as long as we are making progress, flush updates to the DOM and see if |
|
// that triggers or unblocks any layouts... |
|
me.flush(); |
|
flushed = true; // all flushed now, so more progress is required |
|
|
|
me.flushLayouts('completionQueue', 'completeLayout'); |
|
} else if (!me.invalidQueue.length) { |
|
// after a flush, we must make progress or something is WRONG |
|
me.state = 2; |
|
break; |
|
} |
|
|
|
if (!(me.remainingLayouts || me.invalidQueue.length)) { |
|
me.flush(); |
|
me.flushLayouts('completionQueue', 'completeLayout'); |
|
me.flushLayouts('finalizeQueue', 'finalizeLayout'); |
|
} |
|
} |
|
|
|
return me.runComplete(); |
|
}, |
|
|
|
runComplete: function () { |
|
var me = this; |
|
|
|
me.state = 2; |
|
|
|
if (me.remainingLayouts) { |
|
me.handleFailure(); |
|
return false; |
|
} |
|
|
|
me.flush(); |
|
|
|
// Call finishedLayout on all layouts, but do not clear the queue. |
|
me.flushLayouts('finishQueue', 'finishedLayout', true); |
|
|
|
// Call notifyOwner on all layouts and then clear the queue. |
|
me.flushLayouts('finishQueue', 'notifyOwner'); |
|
|
|
me.flush(); // in case any setProp calls were made |
|
|
|
me.flushAnimations(); |
|
|
|
return true; |
|
}, |
|
|
|
/** |
|
* Performs one layout cycle by calling each layout in the layout queue. |
|
* @return {Boolean} True if some progress was made, false if not. |
|
* @protected |
|
*/ |
|
runCycle: function () { |
|
var me = this, |
|
layouts = me.layoutQueue.clear(), |
|
length = layouts.length, |
|
i; |
|
|
|
++me.cycleCount; |
|
|
|
// This value is incremented by ContextItem#setProp whenever new values are set |
|
// (thereby detecting forward progress): |
|
me.progressCount = 0; |
|
|
|
for (i = 0; i < length; ++i) { |
|
me.runLayout(me.currentLayout = layouts[i]); |
|
} |
|
|
|
me.currentLayout = null; |
|
|
|
return me.progressCount > 0; |
|
}, |
|
|
|
/** |
|
* Runs one layout as part of a cycle. |
|
* @private |
|
*/ |
|
runLayout: function (layout) { |
|
var me = this, |
|
ownerContext = me.getCmp(layout.owner); |
|
|
|
layout.pending = false; |
|
|
|
if (ownerContext.state.blocks) { |
|
return; |
|
} |
|
|
|
// We start with the assumption that the layout will finish and if it does not, it |
|
// must clear this flag. It turns out this is much simpler than knowing when a layout |
|
// is done (100% correctly) when base classes and derived classes are collaborating. |
|
// Knowing that some part of the layout is not done is much more obvious. |
|
layout.done = true; |
|
|
|
++layout.calcCount; |
|
++me.calcCount; |
|
|
|
layout.calculate(ownerContext); |
|
|
|
if (layout.done) { |
|
me.layoutDone(layout); |
|
|
|
if (layout.completeLayout) { |
|
me.queueCompletion(layout); |
|
} |
|
if (layout.finalizeLayout) { |
|
me.queueFinalize(layout); |
|
} |
|
} else if (!layout.pending && !layout.invalid && |
|
!(layout.blockCount + layout.triggerCount - layout.firedTriggers)) { // jshint ignore:line |
|
// A layout that is not done and has no blocks or triggers that will queue it |
|
// automatically, must be queued now: |
|
me.queueLayout(layout); |
|
} |
|
}, |
|
|
|
/** |
|
* Set the size of a component, element or composite or an array of components or elements. |
|
* @param {Ext.Component/Ext.Component[]/Ext.dom.Element/Ext.dom.Element[]/Ext.dom.CompositeElement} item |
|
* The item(s) to size. |
|
* @param {Number} width The new width to set (ignored if undefined or NaN). |
|
* @param {Number} height The new height to set (ignored if undefined or NaN). |
|
*/ |
|
setItemSize: function(item, width, height) { |
|
var items = item, |
|
len = 1, |
|
contextItem, i; |
|
|
|
// NOTE: we don't pre-check for validity because: |
|
// - maybe only one dimension is valid |
|
// - the diagnostics layer will track the setProp call to help find who is trying |
|
// (but failing) to set a property |
|
// - setProp already checks this anyway |
|
|
|
if (item.isComposite) { |
|
items = item.elements; |
|
len = items.length; |
|
item = items[0]; |
|
} else if (!item.dom && !item.el) { // array by process of elimination |
|
len = items.length; |
|
item = items[0]; |
|
} |
|
// else len = 1 and items = item (to avoid error on "items[++i]") |
|
|
|
for (i = 0; i < len; ) { |
|
contextItem = this.get(item); |
|
contextItem.setSize(width, height); |
|
|
|
item = items[++i]; // this accomodation avoids making an array of 1 |
|
} |
|
}, |
|
|
|
//------------------------------------------------------------------------- |
|
// Diagnostics |
|
|
|
debugHooks: { |
|
$enabled: false, // off by default |
|
|
|
pageAnalyzerMode: true, |
|
|
|
logOn: { |
|
//boxParent: true, |
|
//calculate: true, |
|
//cancelComponent: true, |
|
//cancelLayout: true, |
|
//doInvalidate: true, |
|
//flush: true, |
|
//flushInvalidate: true, |
|
//invalidate: true, |
|
//initItem: true, |
|
//layoutDone: true, |
|
//queueLayout: true, |
|
//resetLayout: true, |
|
//runCycle: true, |
|
//setProp: true, |
|
0:0 |
|
}, |
|
|
|
//profileLayoutsByType: true, |
|
|
|
//reportOnSuccess: true, |
|
|
|
cancelComponent: function (comp) { |
|
if (this.logOn.cancelComponent) { |
|
Ext.log('cancelCmp: ', comp.id); |
|
} |
|
this.callParent(arguments); |
|
}, |
|
|
|
cancelLayout: function (layout) { |
|
if (this.logOn.cancelLayout) { |
|
Ext.log('cancelLayout: ', this.getLayoutName(layout)); |
|
} |
|
this.callParent(arguments); |
|
}, |
|
|
|
callLayout: function (layout, methodName) { |
|
var accum = this.accumByType[layout.type], |
|
frame = accum && accum.enter(); |
|
|
|
this.callParent(arguments); |
|
|
|
if (accum) { |
|
frame.leave(); |
|
} |
|
}, |
|
|
|
checkRemainingLayouts: function () { |
|
var me = this, |
|
expected = 0, |
|
key, layout; |
|
|
|
for (key in me.layouts) { |
|
layout = me.layouts[key]; |
|
|
|
if (me.layouts.hasOwnProperty(key) && layout.running) { |
|
++expected; |
|
} |
|
} |
|
|
|
if (me.remainingLayouts !== expected) { |
|
Ext.Error.raise({ |
|
msg: 'Bookkeeping error me.remainingLayouts' |
|
}); |
|
} |
|
}, |
|
|
|
flush: function () { |
|
if (this.logOn.flush) { |
|
var items = this.flushQueue; |
|
Ext.log('--- Flush ', items && items.getCount()); |
|
} |
|
|
|
return this.callParent(arguments); |
|
}, |
|
|
|
flushInvalidates: function () { |
|
if (this.logOn.flushInvalidate) { |
|
Ext.log('>> flushInvalidates'); |
|
} |
|
|
|
var ret = this.callParent(arguments); |
|
|
|
if (this.logOn.flushInvalidate) { |
|
Ext.log('<< flushInvalidates'); |
|
} |
|
|
|
return ret; |
|
}, |
|
|
|
getCmp: function (target) { |
|
var ret = this.callParent(arguments); |
|
if (!ret.wrapsComponent) { |
|
Ext.Error.raise({ |
|
msg: target.id + ' is not a component' |
|
}); |
|
} |
|
return ret; |
|
}, |
|
|
|
getEl: function (parent, target) { |
|
var ret = this.callParent(arguments); |
|
if (ret && ret.wrapsComponent) { |
|
Ext.Error.raise({ |
|
msg: parent.id + '/' + target.id + ' is a component (expected element)' |
|
}); |
|
} |
|
return ret; |
|
}, |
|
|
|
getLayoutName: function (layout) { |
|
return layout.owner.id + '<' + layout.type + '>'; |
|
}, |
|
|
|
layoutDone: function (layout) { |
|
var me = this, |
|
name = me.getLayoutName(layout); |
|
|
|
if (me.logOn.layoutDone) { |
|
Ext.log('layoutDone: ', name, ' ( ', me.remainingLayouts, ' running)'); |
|
} |
|
|
|
if (!layout.running) { |
|
Ext.Error.raise({ |
|
msg: name + ' is already done' |
|
}); |
|
} |
|
if (!me.remainingLayouts) { |
|
Ext.Error.raise({ |
|
msg: name + ' finished but no layouts are running' |
|
}); |
|
} |
|
|
|
me.callParent(arguments); |
|
}, |
|
|
|
layoutTreeHasFailures: function (layout, reported) { |
|
var me = this; |
|
|
|
function hasFailure (lo) { |
|
var failure = !lo.done, |
|
key, childLayout; |
|
|
|
if (lo.done) { |
|
for (key in me.layouts) { |
|
if (me.layouts.hasOwnProperty(key)) { |
|
childLayout = me.layouts[key]; |
|
|
|
if (childLayout.owner.ownerLayout === lo) { |
|
if (hasFailure(childLayout)) { |
|
failure = true; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
return failure; |
|
} |
|
|
|
if (hasFailure(layout)) { |
|
return true; |
|
} |
|
|
|
function markReported (lo) { |
|
var key, childLayout; |
|
|
|
reported[lo.id] = 1; |
|
|
|
for (key in me.layouts) { |
|
if (me.layouts.hasOwnProperty(key)) { |
|
childLayout = me.layouts[key]; |
|
|
|
if (childLayout.owner.ownerLayout === lo) { |
|
markReported(childLayout); |
|
} |
|
} |
|
} |
|
} |
|
|
|
markReported(layout); |
|
return false; |
|
}, |
|
|
|
queueLayout: function (layout) { |
|
if (layout.done || layout.blockCount || layout.pending) { |
|
Ext.Error.raise({ |
|
msg: this.getLayoutName(layout) + ' should not be queued for layout' |
|
}); |
|
} |
|
if (this.logOn.queueLayout) { |
|
Ext.log('Queue ', this.getLayoutName(layout)); |
|
} |
|
return this.callParent(arguments); |
|
}, |
|
|
|
reportLayoutResult: function (layout, reported) { |
|
var me = this, |
|
owner = layout.owner, |
|
ownerContext = me.getCmp(owner), |
|
blockedBy = [], |
|
triggeredBy = [], |
|
key, value, i, length, childLayout, |
|
item, setBy, info; |
|
|
|
reported[layout.id] = 1; |
|
|
|
for (key in layout.blockedBy) { |
|
if (layout.blockedBy.hasOwnProperty(key)) { |
|
blockedBy.push(layout.blockedBy[key]); |
|
} |
|
} |
|
blockedBy.sort(); |
|
|
|
for (key in me.triggersByLayoutId[layout.id]) { |
|
if (me.triggersByLayoutId[layout.id].hasOwnProperty(key)) { |
|
value = me.triggersByLayoutId[layout.id][key]; |
|
triggeredBy.push({ name: key, info: value }); |
|
} |
|
} |
|
triggeredBy.sort(function (a, b) { |
|
return a.name < b.name ? -1 : (b.name < a.name ? 1 : 0); |
|
}); |
|
|
|
Ext.log({indent: 1}, (layout.done ? '++' : '--'), me.getLayoutName(layout), |
|
(ownerContext.isBoxParent ? ' [isBoxParent]' : ''), |
|
(ownerContext.boxChildren ? ' - boxChildren: ' + ownerContext.state.boxesMeasured + '/' + ownerContext.boxChildren.length : ''), |
|
ownerContext.boxParent ? (' - boxParent: ' + ownerContext.boxParent.id) : '', |
|
' - size: ', ownerContext.widthModel.name, '/', ownerContext.heightModel.name); |
|
|
|
if (!layout.done || me.reportOnSuccess) { |
|
if (blockedBy.length) { |
|
++Ext.log.indent; |
|
Ext.log({indent: 1}, 'blockedBy: count=',layout.blockCount); |
|
|
|
length = blockedBy.length; |
|
for (i = 0; i < length; i++) { |
|
Ext.log(blockedBy[i]); |
|
} |
|
|
|
Ext.log.indent -= 2; |
|
} |
|
if (triggeredBy.length) { |
|
++Ext.log.indent; |
|
Ext.log({indent: 1}, 'triggeredBy: count='+layout.triggerCount); |
|
|
|
length = triggeredBy.length; |
|
for (i = 0; i < length; i++) { |
|
info = value.info || value; |
|
item = info.item; |
|
setBy = (item.setBy && item.setBy[info.name]) || '?'; |
|
|
|
value = triggeredBy[i]; |
|
|
|
Ext.log( |
|
value.name, |
|
' (', |
|
item.props[info.name], |
|
') dirty: ', |
|
(item.dirty ? !!item.dirty[info.name] : false), |
|
', setBy: ', |
|
setBy |
|
); |
|
} |
|
|
|
Ext.log.indent -= 2; |
|
} |
|
} |
|
|
|
for (key in me.layouts) { |
|
if (me.layouts.hasOwnProperty(key)) { |
|
childLayout = me.layouts[key]; |
|
|
|
if (!childLayout.done && childLayout.owner.ownerLayout === layout) { |
|
me.reportLayoutResult(childLayout, reported); |
|
} |
|
} |
|
} |
|
|
|
for (key in me.layouts) { |
|
if (me.layouts.hasOwnProperty(key)) { |
|
childLayout = me.layouts[key]; |
|
|
|
if (childLayout.done && childLayout.owner.ownerLayout === layout) { |
|
me.reportLayoutResult(childLayout, reported); |
|
} |
|
} |
|
} |
|
|
|
--Ext.log.indent; |
|
}, |
|
|
|
resetLayout: function (layout) { |
|
var me = this, |
|
type = layout.type, |
|
name = me.getLayoutName(layout), |
|
accum = me.accumByType[type], |
|
frame; |
|
|
|
if (me.logOn.resetLayout) { |
|
Ext.log('resetLayout: ', name, ' ( ', me.remainingLayouts, ' running)'); |
|
} |
|
|
|
if (!me.state) { // if (first time ... before run) |
|
if (!accum && me.profileLayoutsByType) { |
|
me.accumByType[type] = accum = Ext.Perf.get('layout_' + layout.type); |
|
} |
|
me.numByType[type] = (me.numByType[type] || 0) + 1; |
|
} |
|
|
|
frame = accum && accum.enter(); |
|
me.callParent(arguments); |
|
if (accum) { |
|
frame.leave(); |
|
} |
|
|
|
me.checkRemainingLayouts(); |
|
}, |
|
|
|
round: function (t) { |
|
return Math.round(t * 1000) / 1000; |
|
}, |
|
|
|
run: function () { |
|
var me = this, |
|
ret, time, key, i, layout, |
|
boxParent, children, n, |
|
reported, unreported, |
|
calcs, total, |
|
calcsLength, calc; |
|
|
|
me.accumByType = {}; |
|
me.calcsByType = {}; |
|
me.numByType = {}; |
|
me.timesByType = {}; |
|
me.triggersByLayoutId = {}; |
|
|
|
Ext.log.indentSize = 3; |
|
Ext.log('==================== LAYOUT ===================='); |
|
|
|
time = Ext.perf.getTimestamp(); |
|
ret = me.callParent(arguments); |
|
time = Ext.perf.getTimestamp() - time; |
|
|
|
if (me.logOn.boxParent && me.boxParents) { |
|
for (key in me.boxParents) { |
|
if (me.boxParents.hasOwnProperty(key)) { |
|
boxParent = me.boxParents[key]; |
|
children = boxParent.boxChildren; |
|
n = children.length; |
|
|
|
Ext.log('boxParent: ', boxParent.id); |
|
for (i = 0; i < n; ++i) { |
|
Ext.log(' --> ', children[i].id); |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (ret) { |
|
Ext.log('----------------- SUCCESS -----------------'); |
|
} else { |
|
Ext.log( |
|
{level: 'error' }, |
|
'----------------- FAILURE -----------------' |
|
); |
|
} |
|
|
|
for (key in me.layouts) { |
|
if (me.layouts.hasOwnProperty(key)) { |
|
layout = me.layouts[key]; |
|
|
|
if (layout.running) { |
|
Ext.log.error('Layout left running: ', me.getLayoutName(layout)); |
|
} |
|
if (layout.ownerContext) { |
|
Ext.log.error('Layout left connected: ', me.getLayoutName(layout)); |
|
} |
|
} |
|
} |
|
|
|
if (!ret || me.reportOnSuccess) { |
|
reported = {}; |
|
unreported = 0; |
|
|
|
for (key in me.layouts) { |
|
if (me.layouts.hasOwnProperty(key)) { |
|
layout = me.layouts[key]; |
|
|
|
if (me.items[layout.owner.el.id].isTopLevel) { |
|
if (me.reportOnSuccess || me.layoutTreeHasFailures(layout, reported)) { |
|
me.reportLayoutResult(layout, reported); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Just in case we missed any layouts... |
|
for (key in me.layouts) { |
|
if (me.layouts.hasOwnProperty(key)) { |
|
layout = me.layouts[key]; |
|
|
|
if (!reported[layout.id]) { |
|
if (!unreported) { |
|
Ext.log('----- Unreported!! -----'); |
|
} |
|
++unreported; |
|
me.reportLayoutResult(layout, reported); |
|
} |
|
} |
|
} |
|
} |
|
|
|
Ext.log('Cycles: ', me.cycleCount, ', Flushes: ', me.flushCount, |
|
', Calculates: ', me.calcCount, ' in ', me.round(time), ' msec'); |
|
|
|
Ext.log('Calculates by type:'); |
|
/*Ext.Object.each(me.numByType, function (type, total) { |
|
Ext.log(type, ': ', total, ' in ', me.calcsByType[type], ' tries (', |
|
Math.round(me.calcsByType[type] / total * 10) / 10, 'x) at ', |
|
me.round(me.timesByType[type]), ' msec (avg ', |
|
me.round(me.timesByType[type] / me.calcsByType[type]), ' msec)'); |
|
});*/ |
|
calcs = []; |
|
for (key in me.numByType) { |
|
if (me.numByType.hasOwnProperty(key)) { |
|
total = me.numByType[key]; |
|
|
|
calcs.push({ |
|
type : key, |
|
total : total, |
|
calcs : me.calcsByType[key], |
|
multiple : Math.round(me.calcsByType[key] / total * 10) / 10, |
|
calcTime : me.round(me.timesByType[key]), |
|
avgCalcTime: me.round(me.timesByType[key] / me.calcsByType[key]) |
|
}); |
|
} |
|
} |
|
|
|
|
|
calcs.sort(function (a,b) { |
|
return b.calcTime - a.calcTime; |
|
}); |
|
|
|
calcsLength = calcs.length; |
|
for (i=0; i<calcsLength; i++) { |
|
calc = calcs[i]; |
|
|
|
Ext.log( |
|
calc.type, |
|
': ', |
|
calc.total, |
|
' in ', |
|
calc.calcs, |
|
' tries (', |
|
calc.multiple, |
|
'x) at ', |
|
calc.calcTime, |
|
' msec (avg ', |
|
calc.avgCalcTime, |
|
' msec)' |
|
); |
|
} |
|
|
|
return ret; |
|
}, |
|
|
|
runCycle: function () { |
|
if (this.logOn.runCycle) { |
|
Ext.log('>>> Cycle ', this.cycleCount, ' (queue length: ', this.layoutQueue.length, ')'); |
|
} |
|
|
|
return this.callParent(arguments); |
|
}, |
|
|
|
runLayout: function (layout) { |
|
var me = this, |
|
type = layout.type, |
|
accum = me.accumByType[type], |
|
frame, ret, time; |
|
|
|
if (me.logOn.calculate) { |
|
Ext.log('-- calculate ', this.getLayoutName(layout)); |
|
} |
|
|
|
frame = accum && accum.enter(); |
|
|
|
time = Ext.perf.getTimestamp(); |
|
ret = me.callParent(arguments); |
|
time = Ext.perf.getTimestamp() - time; |
|
if (accum) { |
|
frame.leave(); |
|
} |
|
|
|
me.calcsByType[type] = (me.calcsByType[type] || 0) + 1; |
|
me.timesByType[type] = (me.timesByType[type] || 0) + time; |
|
|
|
/* add a / to the front of this line to enable layout completion logging |
|
if (layout.done) { |
|
var ownerContext = me.getCmp(layout.owner), |
|
props = ownerContext.props; |
|
|
|
if (layout.isComponentLayout) { |
|
Ext.log('complete ', layout.owner.id, ':', type, ' w=',props.width, ' h=', props.height); |
|
} else { |
|
Ext.log('complete ', layout.owner.id, ':', type, ' cw=',props.contentWidth, ' ch=', props.contentHeight); |
|
} |
|
}**/ |
|
|
|
return ret; |
|
} |
|
} // End Diagnostics |
|
});
|
|
|