controllercollection.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. import Collection from '../utils/collection.js';
  6. import CKEditorError from '../utils/ckeditorerror.js';
  7. /**
  8. * Manages UI Controllers.
  9. *
  10. * @memberOf ui
  11. * @extends utils.Collection
  12. */
  13. export default class ControllerCollection extends Collection {
  14. /**
  15. * Creates an instance of the controller collection, initializing it with a name:
  16. *
  17. * const collection = new ControllerCollection( 'foo' );
  18. * collection.add( someController );
  19. *
  20. * **Note**: If needed, controller collection can stay in sync with a collection of models to
  21. * manage list–like components. See {@link ui.ControllerCollection#bind} to learn more.
  22. *
  23. * @param {String} name Name of the collection.
  24. * @param {utils.Locale} [locale] The {@link ckeditor5.Editor#locale editor's locale} instance.
  25. */
  26. constructor( name, locale ) {
  27. super();
  28. if ( !name ) {
  29. /**
  30. * ControllerCollection must be initialized with a name.
  31. *
  32. * @error ui-controllercollection-no-name
  33. */
  34. throw new CKEditorError( 'ui-controllercollection-no-name: ControllerCollection must be initialized with a name.' );
  35. }
  36. /**
  37. * Name of this collection.
  38. *
  39. * @member {String} ui.ControllerCollection#name
  40. */
  41. this.name = name;
  42. /**
  43. * See {@link ui.View#locale}
  44. *
  45. * @readonly
  46. * @member {utils.Locale} ui.ControllerCollection#locale
  47. */
  48. this.locale = locale;
  49. /**
  50. * Parent controller of this collection.
  51. *
  52. * @member {ui.Controller} ui.ControllerCollection#parent
  53. */
  54. this.parent = null;
  55. }
  56. /**
  57. * Adds a child controller to the collection. If {@link ui.ControllerCollection#parent} {@link ui.Controller}
  58. * instance is ready, the child view is initialized when added.
  59. *
  60. * @param {ui.Controller} controller A child controller.
  61. * @param {Number} [index] Index at which the child will be added to the collection.
  62. * @returns {Promise} A Promise resolved when the child {@link ui.Controller#init} is done.
  63. */
  64. add( controller, index ) {
  65. super.add( controller, index );
  66. // ChildController.init() returns Promise.
  67. let promise = Promise.resolve();
  68. if ( this.parent && this.parent.ready && !controller.ready ) {
  69. promise = promise.then( () => {
  70. return controller.init();
  71. } );
  72. }
  73. return promise;
  74. }
  75. /**
  76. * Synchronizes controller collection with a {@link utils.Collection} of {@link ui.Model} instances.
  77. * The entire collection of controllers reflects changes to the collection of the models, working as a factory.
  78. *
  79. * This method helps bringing complex list–like UI components to life out of the data (like models). The process
  80. * can be automatic:
  81. *
  82. * // This collection stores models.
  83. * const models = new Collection( { idProperty: 'label' } );
  84. *
  85. * // This controller collection will become a factory for the collection of models.
  86. * const controllers = new ControllerCollection( 'foo', locale );
  87. *
  88. * // Activate the binding – since now, this controller collection works like a **factory**.
  89. * controllers.bind( models ).as( FooController, FooView );
  90. *
  91. * // As new models arrive to the collection, each becomes an instance of FooController (FooView)
  92. * // in the controller collection.
  93. * models.add( new Model( { label: 'foo' } ) );
  94. * models.add( new Model( { label: 'bar' } ) );
  95. *
  96. * console.log( controllers.length == 2 );
  97. *
  98. * // Controller collection is updated as the model is removed.
  99. * models.remove( 0 );
  100. * console.log( controllers.length == 1 );
  101. *
  102. * or the factory can be driven by a custom callback:
  103. *
  104. * // This collection stores any kind of data.
  105. * const data = new Collection();
  106. *
  107. * // This controller collection will become a custom factory for the data.
  108. * const controllers = new ControllerCollection( 'foo', locale );
  109. *
  110. * // Activate the binding – the **factory** is driven by a custom callback.
  111. * controllers.bind( data ).as( ( item, locale ) => {
  112. * if ( item.foo == 'bar' ) {
  113. * return new BarController( ..., BarView( locale ) );
  114. * } else {
  115. * return new DifferentController( ..., DifferentView( locale ) );
  116. * }
  117. * } );
  118. *
  119. * // As new data arrive to the collection, each is handled individually by the callback.
  120. * // This will produce BarController.
  121. * data.add( { foo: 'bar' } );
  122. *
  123. * // And this one will become DifferentController.
  124. * data.add( { foo: 'baz' } );
  125. *
  126. * console.log( controllers.length == 2 );
  127. *
  128. * // Controller collection is updated as the data is removed.
  129. * data.remove( 0 );
  130. * console.log( controllers.length == 1 );
  131. *
  132. *
  133. * @param {utils.Collection.<ui.Model>} models Models to be synchronized with this controller collection.
  134. */
  135. bind( models ) {
  136. const idProperty = models._idProperty;
  137. return {
  138. /**
  139. * Activates factory using controller and view classes or uses a custom callback to produce
  140. * controller (view) instances.
  141. *
  142. * @method ui.ControllerCollection.bind#as
  143. * @param {Function} ControllerClassOrFunction Specifies the constructor of the controller to be used or
  144. * a custom callback function which produces controllers.
  145. * @param {Function} [ViewClass] Specifies constructor of the view to be used. If not specified,
  146. * `ControllerClassOrFunction` works as as custom callback function.
  147. */
  148. as: ( ControllerClassOrFunction, ViewClass ) => {
  149. const createController = ViewClass ?
  150. defaultControllerFactory( ControllerClassOrFunction, ViewClass, idProperty )
  151. :
  152. customControllerFactory( ControllerClassOrFunction, idProperty );
  153. for ( let model of models ) {
  154. this.add( createController( model, this.locale ) );
  155. }
  156. // Updated controller collection when a new model is added.
  157. models.on( 'add', ( evt, model, index ) => {
  158. this.add( createController( model, this.locale ), index );
  159. } );
  160. // Update controller collection when a model is removed.
  161. models.on( 'remove', ( evt, model ) => {
  162. this.remove( model[ idProperty ] );
  163. } );
  164. }
  165. };
  166. }
  167. }
  168. // Initializes controller factory with controller and view classes.
  169. //
  170. // @param {Function} ControllerClass Specifies the constructor of the controller to be used.
  171. // @param {Function} ViewClass Specifies constructor of the view.
  172. // @param {String} idProperty A property used to associate the controller with its model {@link utils.Collection._idProperty}.
  173. // @returns {Function}
  174. function defaultControllerFactory( ControllerClass, ViewClass, idProperty ) {
  175. // Returns a controller instance (and its view) for given model and class names.
  176. //
  177. // @param {ui.Model} model A model of the controller.
  178. // @param {utils.Locale} [locale] The {@link ckeditor5.Editor#locale editor's locale} instance.
  179. // @returns {ui.Controller}
  180. return ( model, locale ) => {
  181. return flagController( new ControllerClass( model, new ViewClass( locale ) ), idProperty );
  182. };
  183. }
  184. // Initializes controller factory which is fed by a custom callback.
  185. //
  186. // @param {Function} callback A callback which is to return an instance of {@link ui.Controller}.
  187. // @param {String} idProperty A property used to associate the controller with its model {@link utils.Collection._idProperty}.
  188. // @returns {Function}
  189. function customControllerFactory( callback, idProperty ) {
  190. // Returns a controller instance (and its view) produced by the custom callback.
  191. //
  192. // @param {ui.Model} model A model of the controller.
  193. // @param {utils.Locale} [locale] The {@link ckeditor5.Editor#locale editor's locale} instance.
  194. // @returns {ui.Controller}
  195. return ( ...args ) => {
  196. return flagController( callback( ...args ), idProperty );
  197. };
  198. }
  199. // Gives the controller an id corresponding with {@link utils.Collection#_idProperty} of the model.
  200. // It allows retrieving this controller instance by the model in the future
  201. // and avoids a brute–force search in the entire controller collection.
  202. //
  203. // @param {ui.Controller} controller An instance of controller.
  204. // @param {String} idProperty A property used to associate the controller with its model {@link utils.Collection._idProperty}.
  205. // @returns {ui.Controller}
  206. function flagController( controller, idProperty ) {
  207. controller.id = controller.model[ idProperty ];
  208. return controller;
  209. }