view.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. import CKEditorError from '../utils/ckeditorerror.js';
  6. import ViewCollection from './viewcollection.js';
  7. import Template from './template.js';
  8. import DOMEmitterMixin from './domemittermixin.js';
  9. import ObservableMixin from '../utils/observablemixin.js';
  10. import Collection from '../utils/collection.js';
  11. import mix from '../utils/mix.js';
  12. /**
  13. * Basic View class.
  14. *
  15. * @memberOf ui
  16. * @mixes DOMEmitterMixin
  17. * @mixes ObservableMixin
  18. */
  19. export default class View {
  20. /**
  21. * Creates an instance of the {@link ui.View} class.
  22. *
  23. * TODO: A simple example how to create one.
  24. *
  25. * @param {utils.Locale} [locale] The {@link core.editor.Editor#locale editor's locale} instance.
  26. */
  27. constructor( locale ) {
  28. /**
  29. * A set of tools to localize the user interface. See {@link core.editor.Editor#locale}.
  30. *
  31. * @readonly
  32. * @member {utils.Locale} ui.View#locale
  33. */
  34. this.locale = locale;
  35. /**
  36. * Shorthand for {@link utils.Locale#t}.
  37. *
  38. * Note: If locale instance hasn't been passed to the view this method may not be available.
  39. *
  40. * @see utils.Locale#t
  41. * @method ui.View#t
  42. */
  43. this.t = locale && locale.t;
  44. /**
  45. * Set `true` after {@link ui.View#init}, which can be asynchronous.
  46. *
  47. * @readonly
  48. * @observable
  49. * @member {Boolean} ui.View#ready
  50. */
  51. this.set( 'ready', false );
  52. /**
  53. * Collections registered with {@link ui.View#createCollection}.
  54. *
  55. * @protected
  56. * @member {Set.<ui.ViewCollection>} ui.view#_viewCollections
  57. */
  58. this._viewCollections = new Collection();
  59. // Let the new collection determine the {@link ui.View#ready} state of this view and,
  60. // accordingly, initialize (or not) children views as they are added in the future.
  61. this._viewCollections.on( 'add', ( evt, collection ) => {
  62. collection.bind( 'ready' ).to( this );
  63. collection.locale = locale;
  64. } );
  65. // Once the collection is removed from the view, the {@link ui.View#ready} binding
  66. // should be removed.
  67. this._viewCollections.on( 'remove', ( evt, collection ) => {
  68. collection.unbind( 'ready' );
  69. } );
  70. /**
  71. * A collection of view instances, which have been added directly
  72. * into the {@link ui.View.template#children}.
  73. *
  74. * @protected
  75. * @member {ui.ViewCollection} ui.view#_unboundChildren
  76. */
  77. this._unboundChildren = this.createCollection();
  78. /**
  79. * Template of this view.
  80. *
  81. * @member {ui.Template} ui.View#template
  82. */
  83. /**
  84. * Element of this view.
  85. *
  86. * @private
  87. * @member {HTMLElement} ui.View.#_element
  88. */
  89. /**
  90. * Cached {@link ui.Template} binder object specific for this instance.
  91. * See {@link ui.View#bindTemplate}.
  92. *
  93. * @private
  94. * @member {Object} ui.View.#_bindTemplate
  95. */
  96. }
  97. /**
  98. * Element of this view. The element is rendered on first reference
  99. * using {@link ui.View#template} definition.
  100. *
  101. * @type {HTMLElement}
  102. */
  103. get element() {
  104. if ( this._element ) {
  105. return this._element;
  106. }
  107. // No template means no element (a virtual view).
  108. if ( !this.template ) {
  109. return null;
  110. }
  111. return ( this._element = this.template.render() );
  112. }
  113. /**
  114. * @type {HTMLElement}
  115. */
  116. set element( el ) {
  117. this._element = el;
  118. }
  119. /**
  120. * Shorthand for {@link ui.Template#bind}, bound to {@link ui.View} on the first access.
  121. *
  122. * Cached {@link ui.Template#bind} object is stored in {@link ui.View.#_bindTemplate}.
  123. *
  124. * @method ui.View#bindTemplate
  125. */
  126. get bindTemplate() {
  127. if ( this._bindTemplate ) {
  128. return this._bindTemplate;
  129. }
  130. return ( this._bindTemplate = Template.bind( this, this ) );
  131. }
  132. /**
  133. * Creates a new collection of views, which can be used in this view instance
  134. * i.e. as a member of {@link ui.TemplateDefinition#children}.
  135. *
  136. * TODO: An example how to use created collection in a template definition.
  137. *
  138. * @returns {ui.ViewCollection} A new collection of view instances.
  139. */
  140. createCollection() {
  141. const collection = new ViewCollection();
  142. this._viewCollections.add( collection );
  143. return collection;
  144. }
  145. /**
  146. * Registers a new child view under this view instance. Once registered, a child
  147. * view is managed by its parent, including initialization ({@link ui.view#init})
  148. * and destruction ({@link ui.view#destroy}).
  149. *
  150. * @param {...ui.View} children Children views to be registered.
  151. */
  152. addChild( ...children ) {
  153. for ( let child of children ) {
  154. this._unboundChildren.add( child );
  155. }
  156. }
  157. /**
  158. * Initializes the view and child views located in {@link ui.View#_viewCollections}.
  159. *
  160. * @returns {Promise} A Promise resolved when the initialization process is finished.
  161. */
  162. init() {
  163. if ( this.ready ) {
  164. /**
  165. * This View has already been initialized.
  166. *
  167. * @error ui-view-init-reinit
  168. */
  169. throw new CKEditorError( 'ui-view-init-reinit: This View has already been initialized.' );
  170. }
  171. return Promise.resolve()
  172. // Initialize child views in #_viewCollections.
  173. .then( () => {
  174. const promises = [];
  175. for ( let collection of this._viewCollections ) {
  176. for ( let view of collection ) {
  177. promises.push( view.init() );
  178. }
  179. }
  180. return Promise.all( promises );
  181. } )
  182. // Spread the word that this view is ready!
  183. .then( () => {
  184. this.ready = true;
  185. } );
  186. }
  187. /**
  188. * Destroys the view instance and child views located in {@link ui.View#_viewCollections}.
  189. *
  190. * @returns {Promise} A Promise resolved when the destruction process is finished.
  191. */
  192. destroy() {
  193. this.stopListening();
  194. let promises = [];
  195. for ( let collection of this._viewCollections ) {
  196. for ( let view of collection ) {
  197. promises.push( view.destroy() );
  198. }
  199. }
  200. this._unboundChildren.clear();
  201. this._viewCollections.clear();
  202. if ( this.element ) {
  203. this.element.remove();
  204. }
  205. this.element = this.template = this.locale = this.t =
  206. this._viewCollections = this._unboundChildren = null;
  207. return Promise.all( promises );
  208. }
  209. }
  210. mix( View, DOMEmitterMixin );
  211. mix( View, ObservableMixin );