Container.js 12 KB

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