]>
Commit | Line | Data |
---|---|---|
947f0963 TL |
1 | /** |
2 | * This layout extends `Ext.layout.container.Column` and adds splitters between adjacent | |
3 | * columns allowing the user to resize them. | |
4 | * @private | |
5 | */ | |
6 | Ext.define('Ext.layout.container.Dashboard', { | |
7 | extend: 'Ext.layout.container.Column', | |
8 | alias: 'layout.dashboard', | |
9 | ||
10 | requires: [ | |
11 | 'Ext.layout.container.ColumnSplitter' | |
12 | ], | |
13 | ||
14 | type: 'dashboard', | |
15 | ||
16 | firstColumnCls: Ext.baseCSSPrefix + 'dashboard-column-first', | |
17 | ||
18 | lastColumnCls: Ext.baseCSSPrefix + 'dashboard-column-last', | |
19 | ||
20 | /* | |
21 | * The geometry of a Column layout with splitters between respective items: | |
22 | * | |
23 | * 0 1 2 3 4 | |
24 | * +-----------------------------------------------+ | |
25 | * | +-----------+ || +---------+ || +-----------+ | \ | |
26 | * | | | || | | || | | | \ | |
27 | * | | | || | | || | | | \ | |
28 | * | | | || | | || | | | \ | |
29 | * | +-----------+ || | | || | | | row[0] | |
30 | * | || | | || | | | / | |
31 | * | || | | || | | | / | |
32 | * | || | | || +-----------+ | / | |
33 | * | || | | || | / | |
34 | * | || +---------+ || | | |
35 | * | +-------------------+ || +------------------+ | \ | |
36 | * | | | || | | | \ | |
37 | * | | | || | | | \ | |
38 | * | | | || | | | row[1] | |
39 | * | | | || | | | / | |
40 | * | | | || +------------------+ | / | |
41 | * | +-------------------+ || | / | |
42 | * +-----------------------------------------------+ | |
43 | * 6 7 8 | |
44 | * | |
45 | * The splitter between 4 and 6 will be hidden but still present in the items. It is | |
46 | * considered part of row[0]. | |
47 | */ | |
48 | ||
49 | getSplitterConfig: function() { | |
50 | return { | |
51 | xtype: 'columnsplitter' | |
52 | }; | |
53 | }, | |
54 | ||
55 | /** | |
56 | * @private | |
57 | * Returns a filtered item list sans splitters | |
58 | * @param items | |
59 | * @return {Array|*} | |
60 | */ | |
61 | getColumns: function(items) { | |
62 | var array = Ext.Array; | |
63 | ||
64 | return array.filter(array.from(items), function(item) { | |
65 | return item.target && item.target.isSplitter !== true; | |
66 | }); | |
67 | }, | |
68 | ||
69 | beginLayout: function(ownerContext) { | |
70 | var me = this; | |
71 | ||
72 | me.callParent([ownerContext]); | |
73 | ||
74 | // We need to reset the heights of the splitters so that they don't influence the | |
75 | // layout (mostly overflow management). | |
76 | // eslint-disable-next-line vars-on-top, one-var | |
77 | var childItems = ownerContext.childItems, | |
78 | rows = (ownerContext.rows = []), | |
79 | length = childItems.length, | |
80 | totalWidth = 2, | |
81 | columnTargets = 0, | |
82 | lastRow = 0, | |
83 | maxColumns = me.owner.getMaxColumns(), | |
84 | child, i, prev, row, splitter, target, width; | |
85 | ||
86 | for (i = 0; i < length; ++i) { | |
87 | target = (child = childItems[i]).target; | |
88 | splitter = target && target.isSplitter; | |
89 | columnTargets += (splitter ? 0 : 1); | |
90 | width = splitter ? 0 : target.columnWidth || 1; | |
91 | ||
92 | if (totalWidth + width > 1 || (maxColumns && (columnTargets > maxColumns))) { | |
93 | if (prev) { | |
94 | // We have wrapped and we have a previous item which is a splitter by | |
95 | // definition. We have previously seen that splitter and setHeight(0) | |
96 | // on it. We now setHeight(0) to effectively hide it. | |
97 | prev.orphan = 1; | |
98 | prev.el.setHeight(0); | |
99 | } | |
100 | ||
101 | totalWidth = 0; | |
102 | columnTargets = 1; | |
103 | ||
104 | if (rows.length) { | |
105 | // We have encountered a row break condition | |
106 | // As this is floating layout, classify the current row | |
107 | // before proceeding | |
108 | lastRow = rows.length - 1; | |
109 | me.syncFirstLast( | |
110 | me.getColumns(rows[lastRow].items) | |
111 | ); | |
112 | } | |
113 | ||
114 | rows.push(row = { | |
115 | index: rows.length, | |
116 | items: [], | |
117 | maxHeight: 0 | |
118 | }); | |
119 | } | |
120 | ||
121 | totalWidth += width; | |
122 | row.items.push(child); | |
123 | child.row = row; | |
124 | target.rowIndex = row.index; | |
125 | ||
126 | if (splitter) { | |
127 | child.el.setHeight(1); | |
128 | } | |
129 | ||
130 | prev = child; | |
131 | } | |
132 | ||
133 | if (rows.length) { | |
134 | me.syncFirstLast( | |
135 | me.getColumns(rows[rows.length - 1].items) | |
136 | ); | |
137 | } | |
138 | }, | |
139 | ||
140 | beforeLayoutCycle: function(ownerContext) { | |
141 | var me = this, | |
142 | items = me.owner.items; | |
143 | ||
144 | // We need to do this in beforeLayoutCycle because this changes the child items | |
145 | // and hence needs to be considered before recursing. | |
146 | if (me.splitterGen !== items.generation) { | |
147 | me.syncSplitters(); | |
148 | ||
149 | // The syncSplitters call will change items.generation so do this last. | |
150 | me.splitterGen = items.generation; | |
151 | } | |
152 | ||
153 | me.callParent(arguments); | |
154 | }, | |
155 | ||
156 | finishedLayout: function(ownerContext) { | |
157 | var items = ownerContext.childItems, | |
158 | len = items.length, | |
159 | box, child, i, target, row; | |
160 | ||
161 | this.callParent([ownerContext]); | |
162 | ||
163 | for (i = 0; i < len; i += 2) { | |
164 | target = (child = items[i]).target; | |
165 | box = target.lastBox; | |
166 | row = child.row; | |
167 | row.maxHeight = Math.max(row.maxHeight, box.height); | |
168 | ||
169 | // Put this on the component so that it gets saved (we use this to fix up | |
170 | // columnWidth on restore) | |
171 | target.width = box.width; | |
172 | } | |
173 | ||
174 | for (i = 0; i < len; i ++) { | |
175 | target = (child = items[i]).target; | |
176 | ||
177 | if (!child.orphan) { | |
178 | if (i % 2 === 0) { | |
179 | // Set min height of column to the max height | |
180 | // So that it can increase on adding placeholder | |
181 | target.el.setMinHeight(child.row.maxHeight); | |
182 | } | |
183 | else { | |
184 | // Set height for splitter | |
185 | target.el.setHeight(child.row.maxHeight); | |
186 | } | |
187 | } | |
188 | else { | |
189 | // since this is an orphan child, set its width to 0 | |
190 | target.el.setWidth(0); | |
191 | } | |
192 | } | |
193 | }, | |
194 | ||
195 | /** | |
196 | * This method synchronizes the splitters so that we have exactly one between each | |
197 | * column. | |
198 | * @private | |
199 | */ | |
200 | syncSplitters: function() { | |
201 | var me = this, | |
202 | owner = me.owner, | |
203 | items = owner.items.items, | |
204 | index = items.length, | |
205 | ok = true, | |
206 | shouldBeSplitter = false, | |
207 | item, splitter; // eslint-disable-line no-unused-vars | |
208 | ||
209 | // Walk backwards over the items so that an insertion index is stable. | |
210 | while (index-- > 0) { | |
211 | item = items[index]; | |
212 | ||
213 | if (shouldBeSplitter) { | |
214 | if (item.isSplitter) { | |
215 | shouldBeSplitter = false; | |
216 | } | |
217 | else { | |
218 | // An item is adjacent to an item, so inject a splitter beyond | |
219 | // the current item to separate the columns. Keep shouldBeSplitter | |
220 | // at true since we just encountered an item. | |
221 | if (ok) { | |
222 | ok = false; | |
223 | owner.suspendLayouts(); | |
224 | } | |
225 | ||
226 | splitter = owner.add(index + 1, me.getSplitterConfig()); | |
227 | } | |
228 | } | |
229 | else { | |
230 | if (item.isSplitter) { | |
231 | // A splitter is adjacent to a splitter so we remove this one. We | |
232 | // leave shouldBeSplitter at false because the next thing we see | |
233 | // should still not be a splitter. | |
234 | if (ok) { | |
235 | ok = false; | |
236 | owner.suspendLayouts(); | |
237 | } | |
238 | ||
239 | owner.remove(item); | |
240 | } | |
241 | else { | |
242 | shouldBeSplitter = true; | |
243 | } | |
244 | } | |
245 | } | |
246 | ||
247 | // It is possible to exit the above with a splitter as the first item, but | |
248 | // this is invalid so remove any such splitters. | |
249 | while (items.length && (item = items[0]).isSplitter) { | |
250 | if (ok) { | |
251 | ok = false; | |
252 | owner.suspendLayouts(); | |
253 | } | |
254 | ||
255 | owner.remove(item); | |
256 | } | |
257 | ||
258 | if (!ok) { | |
259 | owner.resumeLayouts(); | |
260 | } | |
261 | }, | |
262 | ||
263 | syncFirstLast: function(items) { | |
264 | var me = this, | |
265 | firstCls = me.firstColumnCls, | |
266 | lastCls = me.lastColumnCls, | |
267 | len, | |
268 | firstAndLast = [firstCls, lastCls], | |
269 | i, item, last; | |
270 | ||
271 | items = Ext.Array.from(items); | |
272 | len = items.length; | |
273 | ||
274 | for (i = 0; i < len; ++i) { | |
275 | item = items[i].target; | |
276 | last = (i === len - 1); | |
277 | ||
278 | if (!i) { // if (first) | |
279 | if (last) { | |
280 | item.addCls(firstAndLast); | |
281 | } | |
282 | else { | |
283 | item.addCls(firstCls); | |
284 | item.removeCls(lastCls); | |
285 | } | |
286 | } | |
287 | else if (last) { | |
288 | item.addCls(lastCls); | |
289 | item.removeCls(firstCls); | |
290 | } | |
291 | else { | |
292 | item.removeCls(firstAndLast); | |
293 | } | |
294 | } | |
295 | }, | |
296 | ||
297 | calculateItemSizeWithContent: function(availableWidth, contentWidth, items) { | |
298 | var itemMarginWidth, itemContext, | |
299 | splitterItemWidth = 0, | |
300 | halfSplitterItemWidth = 0, | |
301 | itemWidth, i, | |
302 | len = items.length, | |
303 | rowindex, rowLen; | |
304 | ||
305 | availableWidth = (availableWidth < contentWidth) ? 0 : availableWidth; | |
306 | ||
307 | for (i = 0; i < len; i += 2) { | |
308 | itemContext = items[i]; | |
309 | rowLen = itemContext.row.items.length; | |
310 | rowindex = itemContext.row.items.indexOf(itemContext); | |
311 | ||
312 | itemMarginWidth = itemContext.marginInfo.width; // always set by above loop | |
313 | itemWidth = itemContext.target.columnWidth; | |
314 | itemWidth = Math.floor(itemWidth * availableWidth) - itemMarginWidth; | |
315 | ||
316 | // Get the width of splitter item. We calculate the half value for width | |
317 | // since splitter must form a part of both items between which it lies equally. | |
318 | if (splitterItemWidth === 0 && (rowindex + 1 < rowLen)) { | |
319 | splitterItemWidth = items[i + 1].getProp('width'); | |
320 | halfSplitterItemWidth = Math.ceil(splitterItemWidth / 2); | |
321 | } | |
322 | ||
323 | if (halfSplitterItemWidth) { | |
324 | // if there exists a splitter to the right and this splitter | |
325 | // is not the last item of this row, reduce the width of the | |
326 | // column since splitter will take half width from the item | |
327 | if (rowindex + 2 < rowLen) { | |
328 | itemWidth -= halfSplitterItemWidth; | |
329 | } | |
330 | ||
331 | // if there exists a splitter to the left, reduce the width | |
332 | // of the column since splitter will take half width from the item | |
333 | if (rowindex > 0) { | |
334 | itemWidth -= halfSplitterItemWidth; | |
335 | } | |
336 | } | |
337 | ||
338 | itemWidth = itemContext.setWidth(itemWidth); // constrains to min/maxWidth | |
339 | contentWidth += itemWidth + itemMarginWidth; | |
340 | } | |
341 | ||
342 | return contentWidth; | |
343 | } | |
344 | }); |