|
1 /** |
|
2 * Container.js |
|
3 * |
|
4 * Copyright, Moxiecode Systems AB |
|
5 * Released under LGPL License. |
|
6 * |
|
7 * License: http://www.tinymce.com/license |
|
8 * Contributing: http://www.tinymce.com/contributing |
|
9 */ |
|
10 |
|
11 /** |
|
12 * Container control. This is extended by all controls that can have |
|
13 * children such as panels etc. You can also use this class directly as an |
|
14 * generic container instance. The container doesn't have any specific role or style. |
|
15 * |
|
16 * @-x-less Container.less |
|
17 * @class tinymce.ui.Container |
|
18 * @extends tinymce.ui.Control |
|
19 */ |
|
20 define("tinymce/ui/Container", [ |
|
21 "tinymce/ui/Control", |
|
22 "tinymce/ui/Collection", |
|
23 "tinymce/ui/Selector", |
|
24 "tinymce/ui/Factory", |
|
25 "tinymce/ui/KeyboardNavigation", |
|
26 "tinymce/util/Tools", |
|
27 "tinymce/ui/DomUtils" |
|
28 ], function(Control, Collection, Selector, Factory, KeyboardNavigation, Tools, DomUtils) { |
|
29 "use strict"; |
|
30 |
|
31 var selectorCache = {}; |
|
32 |
|
33 return Control.extend({ |
|
34 layout: '', |
|
35 innerClass: 'container-inner', |
|
36 |
|
37 /** |
|
38 * Constructs a new control instance with the specified settings. |
|
39 * |
|
40 * @constructor |
|
41 * @param {Object} settings Name/value object with settings. |
|
42 * @setting {Array} items Items to add to container in JSON format or control instances. |
|
43 * @setting {String} layout Layout manager by name to use. |
|
44 * @setting {Object} defaults Default settings to apply to all items. |
|
45 */ |
|
46 init: function(settings) { |
|
47 var self = this; |
|
48 |
|
49 self._super(settings); |
|
50 settings = self.settings; |
|
51 self._fixed = settings.fixed; |
|
52 self._items = new Collection(); |
|
53 |
|
54 if (self.isRtl()) { |
|
55 self.addClass('rtl'); |
|
56 } |
|
57 |
|
58 self.addClass('container'); |
|
59 self.addClass('container-body', 'body'); |
|
60 |
|
61 if (settings.containerCls) { |
|
62 self.addClass(settings.containerCls); |
|
63 } |
|
64 |
|
65 self._layout = Factory.create((settings.layout || self.layout) + 'layout'); |
|
66 |
|
67 if (self.settings.items) { |
|
68 self.add(self.settings.items); |
|
69 } |
|
70 |
|
71 // TODO: Fix this! |
|
72 self._hasBody = true; |
|
73 }, |
|
74 |
|
75 /** |
|
76 * Returns a collection of child items that the container currently have. |
|
77 * |
|
78 * @method items |
|
79 * @return {tinymce.ui.Collection} Control collection direct child controls. |
|
80 */ |
|
81 items: function() { |
|
82 return this._items; |
|
83 }, |
|
84 |
|
85 /** |
|
86 * Find child controls by selector. |
|
87 * |
|
88 * @method find |
|
89 * @param {String} selector Selector CSS pattern to find children by. |
|
90 * @return {tinymce.ui.Collection} Control collection with child controls. |
|
91 */ |
|
92 find: function(selector) { |
|
93 selector = selectorCache[selector] = selectorCache[selector] || new Selector(selector); |
|
94 |
|
95 return selector.find(this); |
|
96 }, |
|
97 |
|
98 /** |
|
99 * Adds one or many items to the current container. This will create instances of |
|
100 * the object representations if needed. |
|
101 * |
|
102 * @method add |
|
103 * @param {Array/Object/tinymce.ui.Control} items Array or item that will be added to the container. |
|
104 * @return {tinymce.ui.Collection} Current collection control. |
|
105 */ |
|
106 add: function(items) { |
|
107 var self = this; |
|
108 |
|
109 self.items().add(self.create(items)).parent(self); |
|
110 |
|
111 return self; |
|
112 }, |
|
113 |
|
114 /** |
|
115 * Focuses the current container instance. This will look |
|
116 * for the first control in the container and focus that. |
|
117 * |
|
118 * @method focus |
|
119 * @param {Boolean} keyboard Optional true/false if the focus was a keyboard focus or not. |
|
120 * @return {tinymce.ui.Collection} Current instance. |
|
121 */ |
|
122 focus: function(keyboard) { |
|
123 var self = this, focusCtrl, keyboardNav, items; |
|
124 |
|
125 if (keyboard) { |
|
126 keyboardNav = self.keyboardNav || self.parents().eq(-1)[0].keyboardNav; |
|
127 |
|
128 if (keyboardNav) { |
|
129 keyboardNav.focusFirst(self); |
|
130 return; |
|
131 } |
|
132 } |
|
133 |
|
134 items = self.find('*'); |
|
135 |
|
136 // TODO: Figure out a better way to auto focus alert dialog buttons |
|
137 if (self.statusbar) { |
|
138 items.add(self.statusbar.items()); |
|
139 } |
|
140 |
|
141 items.each(function(ctrl) { |
|
142 if (ctrl.settings.autofocus) { |
|
143 focusCtrl = null; |
|
144 return false; |
|
145 } |
|
146 |
|
147 if (ctrl.canFocus) { |
|
148 focusCtrl = focusCtrl || ctrl; |
|
149 } |
|
150 }); |
|
151 |
|
152 if (focusCtrl) { |
|
153 focusCtrl.focus(); |
|
154 } |
|
155 |
|
156 return self; |
|
157 }, |
|
158 |
|
159 /** |
|
160 * Replaces the specified child control with a new control. |
|
161 * |
|
162 * @method replace |
|
163 * @param {tinymce.ui.Control} oldItem Old item to be replaced. |
|
164 * @param {tinymce.ui.Control} newItem New item to be inserted. |
|
165 */ |
|
166 replace: function(oldItem, newItem) { |
|
167 var ctrlElm, items = this.items(), i = items.length; |
|
168 |
|
169 // Replace the item in collection |
|
170 while (i--) { |
|
171 if (items[i] === oldItem) { |
|
172 items[i] = newItem; |
|
173 break; |
|
174 } |
|
175 } |
|
176 |
|
177 if (i >= 0) { |
|
178 // Remove new item from DOM |
|
179 ctrlElm = newItem.getEl(); |
|
180 if (ctrlElm) { |
|
181 ctrlElm.parentNode.removeChild(ctrlElm); |
|
182 } |
|
183 |
|
184 // Remove old item from DOM |
|
185 ctrlElm = oldItem.getEl(); |
|
186 if (ctrlElm) { |
|
187 ctrlElm.parentNode.removeChild(ctrlElm); |
|
188 } |
|
189 } |
|
190 |
|
191 // Adopt the item |
|
192 newItem.parent(this); |
|
193 }, |
|
194 |
|
195 /** |
|
196 * Creates the specified items. If any of the items is plain JSON style objects |
|
197 * it will convert these into real tinymce.ui.Control instances. |
|
198 * |
|
199 * @method create |
|
200 * @param {Array} items Array of items to convert into control instances. |
|
201 * @return {Array} Array with control instances. |
|
202 */ |
|
203 create: function(items) { |
|
204 var self = this, settings, ctrlItems = []; |
|
205 |
|
206 // Non array structure, then force it into an array |
|
207 if (!Tools.isArray(items)) { |
|
208 items = [items]; |
|
209 } |
|
210 |
|
211 // Add default type to each child control |
|
212 Tools.each(items, function(item) { |
|
213 if (item) { |
|
214 // Construct item if needed |
|
215 if (!(item instanceof Control)) { |
|
216 // Name only then convert it to an object |
|
217 if (typeof item == "string") { |
|
218 item = {type: item}; |
|
219 } |
|
220 |
|
221 // Create control instance based on input settings and default settings |
|
222 settings = Tools.extend({}, self.settings.defaults, item); |
|
223 item.type = settings.type = settings.type || item.type || self.settings.defaultType || |
|
224 (settings.defaults ? settings.defaults.type : null); |
|
225 item = Factory.create(settings); |
|
226 } |
|
227 |
|
228 ctrlItems.push(item); |
|
229 } |
|
230 }); |
|
231 |
|
232 return ctrlItems; |
|
233 }, |
|
234 |
|
235 /** |
|
236 * Renders new control instances. |
|
237 * |
|
238 * @private |
|
239 */ |
|
240 renderNew: function() { |
|
241 var self = this; |
|
242 |
|
243 // Render any new items |
|
244 self.items().each(function(ctrl, index) { |
|
245 var containerElm, fragment; |
|
246 |
|
247 ctrl.parent(self); |
|
248 |
|
249 if (!ctrl._rendered) { |
|
250 containerElm = self.getEl('body'); |
|
251 fragment = DomUtils.createFragment(ctrl.renderHtml()); |
|
252 |
|
253 // Insert or append the item |
|
254 if (containerElm.hasChildNodes() && index <= containerElm.childNodes.length - 1) { |
|
255 containerElm.insertBefore(fragment, containerElm.childNodes[index]); |
|
256 } else { |
|
257 containerElm.appendChild(fragment); |
|
258 } |
|
259 |
|
260 ctrl.postRender(); |
|
261 } |
|
262 }); |
|
263 |
|
264 self._layout.applyClasses(self); |
|
265 self._lastRect = null; |
|
266 |
|
267 return self; |
|
268 }, |
|
269 |
|
270 /** |
|
271 * Appends new instances to the current container. |
|
272 * |
|
273 * @method append |
|
274 * @param {Array/tinymce.ui.Collection} items Array if controls to append. |
|
275 * @return {tinymce.ui.Container} Current container instance. |
|
276 */ |
|
277 append: function(items) { |
|
278 return this.add(items).renderNew(); |
|
279 }, |
|
280 |
|
281 /** |
|
282 * Prepends new instances to the current container. |
|
283 * |
|
284 * @method prepend |
|
285 * @param {Array/tinymce.ui.Collection} items Array if controls to prepend. |
|
286 * @return {tinymce.ui.Container} Current container instance. |
|
287 */ |
|
288 prepend: function(items) { |
|
289 var self = this; |
|
290 |
|
291 self.items().set(self.create(items).concat(self.items().toArray())); |
|
292 |
|
293 return self.renderNew(); |
|
294 }, |
|
295 |
|
296 /** |
|
297 * Inserts an control at a specific index. |
|
298 * |
|
299 * @method insert |
|
300 * @param {Array/tinymce.ui.Collection} items Array if controls to insert. |
|
301 * @param {Number} index Index to insert controls at. |
|
302 * @param {Boolean} [before=false] Inserts controls before the index. |
|
303 */ |
|
304 insert: function(items, index, before) { |
|
305 var self = this, curItems, beforeItems, afterItems; |
|
306 |
|
307 items = self.create(items); |
|
308 curItems = self.items(); |
|
309 |
|
310 if (!before && index < curItems.length - 1) { |
|
311 index += 1; |
|
312 } |
|
313 |
|
314 if (index >= 0 && index < curItems.length) { |
|
315 beforeItems = curItems.slice(0, index).toArray(); |
|
316 afterItems = curItems.slice(index).toArray(); |
|
317 curItems.set(beforeItems.concat(items, afterItems)); |
|
318 } |
|
319 |
|
320 return self.renderNew(); |
|
321 }, |
|
322 |
|
323 /** |
|
324 * Populates the form fields from the specified JSON data object. |
|
325 * |
|
326 * Control items in the form that matches the data will have it's value set. |
|
327 * |
|
328 * @method fromJSON |
|
329 * @param {Object} data JSON data object to set control values by. |
|
330 * @return {tinymce.ui.Container} Current form instance. |
|
331 */ |
|
332 fromJSON: function(data) { |
|
333 var self = this; |
|
334 |
|
335 for (var name in data) { |
|
336 self.find('#' + name).value(data[name]); |
|
337 } |
|
338 |
|
339 return self; |
|
340 }, |
|
341 |
|
342 /** |
|
343 * Serializes the form into a JSON object by getting all items |
|
344 * that has a name and a value. |
|
345 * |
|
346 * @method toJSON |
|
347 * @return {Object} JSON object with form data. |
|
348 */ |
|
349 toJSON: function() { |
|
350 var self = this, data = {}; |
|
351 |
|
352 self.find('*').each(function(ctrl) { |
|
353 var name = ctrl.name(), value = ctrl.value(); |
|
354 |
|
355 if (name && typeof value != "undefined") { |
|
356 data[name] = value; |
|
357 } |
|
358 }); |
|
359 |
|
360 return data; |
|
361 }, |
|
362 |
|
363 preRender: function() { |
|
364 }, |
|
365 |
|
366 /** |
|
367 * Renders the control as a HTML string. |
|
368 * |
|
369 * @method renderHtml |
|
370 * @return {String} HTML representing the control. |
|
371 */ |
|
372 renderHtml: function() { |
|
373 var self = this, layout = self._layout, role = this.settings.role; |
|
374 |
|
375 self.preRender(); |
|
376 layout.preRender(self); |
|
377 |
|
378 return ( |
|
379 '<div id="' + self._id + '" class="' + self.classes() + '"' + (role ? ' role="' + this.settings.role + '"' : '') + '>' + |
|
380 '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' + |
|
381 (self.settings.html || '') + layout.renderHtml(self) + |
|
382 '</div>' + |
|
383 '</div>' |
|
384 ); |
|
385 }, |
|
386 |
|
387 /** |
|
388 * Post render method. Called after the control has been rendered to the target. |
|
389 * |
|
390 * @method postRender |
|
391 * @return {tinymce.ui.Container} Current combobox instance. |
|
392 */ |
|
393 postRender: function() { |
|
394 var self = this, box; |
|
395 |
|
396 self.items().exec('postRender'); |
|
397 self._super(); |
|
398 |
|
399 self._layout.postRender(self); |
|
400 self._rendered = true; |
|
401 |
|
402 if (self.settings.style) { |
|
403 DomUtils.css(self.getEl(), self.settings.style); |
|
404 } |
|
405 |
|
406 if (self.settings.border) { |
|
407 box = self.borderBox(); |
|
408 DomUtils.css(self.getEl(), { |
|
409 'border-top-width': box.top, |
|
410 'border-right-width': box.right, |
|
411 'border-bottom-width': box.bottom, |
|
412 'border-left-width': box.left |
|
413 }); |
|
414 } |
|
415 |
|
416 if (!self.parent()) { |
|
417 self.keyboardNav = new KeyboardNavigation({ |
|
418 root: self |
|
419 }); |
|
420 } |
|
421 |
|
422 return self; |
|
423 }, |
|
424 |
|
425 /** |
|
426 * Initializes the current controls layout rect. |
|
427 * This will be executed by the layout managers to determine the |
|
428 * default minWidth/minHeight etc. |
|
429 * |
|
430 * @method initLayoutRect |
|
431 * @return {Object} Layout rect instance. |
|
432 */ |
|
433 initLayoutRect: function() { |
|
434 var self = this, layoutRect = self._super(); |
|
435 |
|
436 // Recalc container size by asking layout manager |
|
437 self._layout.recalc(self); |
|
438 |
|
439 return layoutRect; |
|
440 }, |
|
441 |
|
442 /** |
|
443 * Recalculates the positions of the controls in the current container. |
|
444 * This is invoked by the reflow method and shouldn't be called directly. |
|
445 * |
|
446 * @method recalc |
|
447 */ |
|
448 recalc: function() { |
|
449 var self = this, rect = self._layoutRect, lastRect = self._lastRect; |
|
450 |
|
451 if (!lastRect || lastRect.w != rect.w || lastRect.h != rect.h) { |
|
452 self._layout.recalc(self); |
|
453 rect = self.layoutRect(); |
|
454 self._lastRect = {x: rect.x, y: rect.y, w: rect.w, h: rect.h}; |
|
455 return true; |
|
456 } |
|
457 }, |
|
458 |
|
459 /** |
|
460 * Reflows the current container and it's children and possible parents. |
|
461 * This should be used after you for example append children to the current control so |
|
462 * that the layout managers know that they need to reposition everything. |
|
463 * |
|
464 * @example |
|
465 * container.append({type: 'button', text: 'My button'}).reflow(); |
|
466 * |
|
467 * @method reflow |
|
468 * @return {tinymce.ui.Container} Current container instance. |
|
469 */ |
|
470 reflow: function() { |
|
471 var i; |
|
472 |
|
473 if (this.visible()) { |
|
474 Control.repaintControls = []; |
|
475 Control.repaintControls.map = {}; |
|
476 |
|
477 this.recalc(); |
|
478 i = Control.repaintControls.length; |
|
479 |
|
480 while (i--) { |
|
481 Control.repaintControls[i].repaint(); |
|
482 } |
|
483 |
|
484 // TODO: Fix me! |
|
485 if (this.settings.layout !== "flow" && this.settings.layout !== "stack") { |
|
486 this.repaint(); |
|
487 } |
|
488 |
|
489 Control.repaintControls = []; |
|
490 } |
|
491 |
|
492 return this; |
|
493 } |
|
494 }); |
|
495 }); |