plugin.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  1. /**
  2. * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or http://ckeditor.com/license
  4. */
  5. /**
  6. * @fileOverview The "Notification Aggregator" plugin.
  7. *
  8. */
  9. ( function() {
  10. 'use strict';
  11. CKEDITOR.plugins.add( 'notificationaggregator', {
  12. requires: 'notification'
  13. } );
  14. /**
  15. * An aggregator of multiple tasks (progresses) which should be displayed using one
  16. * {@link CKEDITOR.plugins.notification notification}.
  17. *
  18. * Once all the tasks are done, it means that the whole process is finished and the {@link #finished}
  19. * event will be fired.
  20. *
  21. * New tasks can be created after the previous set of tasks is finished. This will continue the process and
  22. * fire the {@link #finished} event again when it is done.
  23. *
  24. * Simple usage example:
  25. *
  26. * // Declare one aggregator that will be used for all tasks.
  27. * var aggregator;
  28. *
  29. * // ...
  30. *
  31. * // Create a new aggregator if the previous one finished all tasks.
  32. * if ( !aggregator || aggregator.isFinished() ) {
  33. * // Create a new notification aggregator instance.
  34. * aggregator = new CKEDITOR.plugins.notificationAggregator( editor, 'Loading process, step {current} of {max}...' );
  35. *
  36. * // Change the notification type to 'success' on finish.
  37. * aggregator.on( 'finished', function() {
  38. * aggregator.notification.update( { message: 'Done', type: 'success' } );
  39. * } );
  40. * }
  41. *
  42. * // Create 3 tasks.
  43. * var taskA = aggregator.createTask(),
  44. * taskB = aggregator.createTask(),
  45. * taskC = aggregator.createTask();
  46. *
  47. * // At this point the notification contains a message: "Loading process, step 0 of 3...".
  48. *
  49. * // Let's close the first one immediately.
  50. * taskA.done(); // "Loading process, step 1 of 3...".
  51. *
  52. * // One second later the message will be: "Loading process, step 2 of 3...".
  53. * setTimeout( function() {
  54. * taskB.done();
  55. * }, 1000 );
  56. *
  57. * // Two seconds after the previous message the last task will be completed, meaning that
  58. * // the notification will be closed.
  59. * setTimeout( function() {
  60. * taskC.done();
  61. * }, 3000 );
  62. *
  63. * @since 4.5
  64. * @class CKEDITOR.plugins.notificationAggregator
  65. * @mixins CKEDITOR.event
  66. * @constructor Creates a notification aggregator instance.
  67. * @param {CKEDITOR.editor} editor
  68. * @param {String} message The template for the message to be displayed in the notification. The template can use
  69. * the following variables:
  70. *
  71. * * `{current}` – The number of completed tasks.
  72. * * `{max}` – The number of tasks.
  73. * * `{percentage}` – The progress (number 0-100).
  74. *
  75. * @param {String/null} [singularMessage=null] An optional template for the message to be displayed in the notification
  76. * when there is only one task remaining. This template can use the same variables as the `message` template.
  77. * If `null`, then the `message` template will be used.
  78. */
  79. function Aggregator( editor, message, singularMessage ) {
  80. /**
  81. * @readonly
  82. * @property {CKEDITOR.editor} editor
  83. */
  84. this.editor = editor;
  85. /**
  86. * Notification created by the aggregator.
  87. *
  88. * The notification object is modified as aggregator tasks are being closed.
  89. *
  90. * @readonly
  91. * @property {CKEDITOR.plugins.notification/null}
  92. */
  93. this.notification = null;
  94. /**
  95. * A template for the notification message.
  96. *
  97. * The template can use the following variables:
  98. *
  99. * * `{current}` – The number of completed tasks.
  100. * * `{max}` – The number of tasks.
  101. * * `{percentage}` – The progress (number 0-100).
  102. *
  103. * @private
  104. * @property {CKEDITOR.template}
  105. */
  106. this._message = new CKEDITOR.template( message );
  107. /**
  108. * A template for the notification message used when only one task is loading.
  109. *
  110. * Sometimes there might be a need to specify a special message when there
  111. * is only one task loading, and to display standard messages in other cases.
  112. *
  113. * For example, you might want to show a message "Translating a widget" rather than
  114. * "Translating widgets (1 of 1)", but still you would want to have a message
  115. * "Translating widgets (2 of 3)" if more widgets are being translated at the same
  116. * time.
  117. *
  118. * Template variables are the same as in {@link #_message}.
  119. *
  120. * @private
  121. * @property {CKEDITOR.template/null}
  122. */
  123. this._singularMessage = singularMessage ? new CKEDITOR.template( singularMessage ) : null;
  124. // Set the _tasks, _totalWeights, _doneWeights, _doneTasks properties.
  125. this._tasks = [];
  126. this._totalWeights = 0;
  127. this._doneWeights = 0;
  128. this._doneTasks = 0;
  129. /**
  130. * Array of tasks tracked by the aggregator.
  131. *
  132. * @private
  133. * @property {CKEDITOR.plugins.notificationAggregator.task[]} _tasks
  134. */
  135. /**
  136. * Stores the sum of declared weights for all contained tasks.
  137. *
  138. * @private
  139. * @property {Number} _totalWeights
  140. */
  141. /**
  142. * Stores the sum of done weights for all contained tasks.
  143. *
  144. * @private
  145. * @property {Number} _doneWeights
  146. */
  147. /**
  148. * Stores the count of tasks done.
  149. *
  150. * @private
  151. * @property {Number} _doneTasks
  152. */
  153. }
  154. Aggregator.prototype = {
  155. /**
  156. * Creates a new task that can be updated to indicate the progress.
  157. *
  158. * @param [options] Options object for the task creation.
  159. * @param [options.weight] For more information about weight, see the
  160. * {@link CKEDITOR.plugins.notificationAggregator.task} overview.
  161. * @returns {CKEDITOR.plugins.notificationAggregator.task} An object that represents the task state, and allows
  162. * for its manipulation.
  163. */
  164. createTask: function( options ) {
  165. options = options || {};
  166. var initialTask = !this.notification,
  167. task;
  168. if ( initialTask ) {
  169. // It's a first call.
  170. this.notification = this._createNotification();
  171. }
  172. task = this._addTask( options );
  173. task.on( 'updated', this._onTaskUpdate, this );
  174. task.on( 'done', this._onTaskDone, this );
  175. task.on( 'canceled', function() {
  176. this._removeTask( task );
  177. }, this );
  178. // Update the aggregator.
  179. this.update();
  180. if ( initialTask ) {
  181. this.notification.show();
  182. }
  183. return task;
  184. },
  185. /**
  186. * Triggers an update on the aggregator, meaning that its UI will be refreshed.
  187. *
  188. * When all the tasks are done, the {@link #finished} event is fired.
  189. */
  190. update: function() {
  191. this._updateNotification();
  192. if ( this.isFinished() ) {
  193. this.fire( 'finished' );
  194. }
  195. },
  196. /**
  197. * Returns a number from `0` to `1` representing the done weights to total weights ratio
  198. * (showing how many of the tasks are done).
  199. *
  200. * Note: For an empty aggregator (without any tasks created) it will return `1`.
  201. *
  202. * @returns {Number} Returns the percentage of tasks done as a number ranging from `0` to `1`.
  203. */
  204. getPercentage: function() {
  205. // In case there are no weights at all we'll return 1.
  206. if ( this.getTaskCount() === 0 ) {
  207. return 1;
  208. }
  209. return this._doneWeights / this._totalWeights;
  210. },
  211. /**
  212. * @returns {Boolean} Returns `true` if all notification tasks are done
  213. * (or there are no tasks at all).
  214. */
  215. isFinished: function() {
  216. return this.getDoneTaskCount() === this.getTaskCount();
  217. },
  218. /**
  219. * @returns {Number} Returns a total tasks count.
  220. */
  221. getTaskCount: function() {
  222. return this._tasks.length;
  223. },
  224. /**
  225. * @returns {Number} Returns the number of tasks done.
  226. */
  227. getDoneTaskCount: function() {
  228. return this._doneTasks;
  229. },
  230. /**
  231. * Updates the notification content.
  232. *
  233. * @private
  234. */
  235. _updateNotification: function() {
  236. this.notification.update( {
  237. message: this._getNotificationMessage(),
  238. progress: this.getPercentage()
  239. } );
  240. },
  241. /**
  242. * Returns a message used in the notification.
  243. *
  244. * @private
  245. * @returns {String}
  246. */
  247. _getNotificationMessage: function() {
  248. var tasksCount = this.getTaskCount(),
  249. doneTasks = this.getDoneTaskCount(),
  250. templateParams = {
  251. current: doneTasks,
  252. max: tasksCount,
  253. percentage: Math.round( this.getPercentage() * 100 )
  254. },
  255. template;
  256. // If there's only one remaining task and we have a singular message, we should use it.
  257. if ( tasksCount == 1 && this._singularMessage ) {
  258. template = this._singularMessage;
  259. } else {
  260. template = this._message;
  261. }
  262. return template.output( templateParams );
  263. },
  264. /**
  265. * Creates a notification object.
  266. *
  267. * @private
  268. * @returns {CKEDITOR.plugins.notification}
  269. */
  270. _createNotification: function() {
  271. return new CKEDITOR.plugins.notification( this.editor, {
  272. type: 'progress'
  273. } );
  274. },
  275. /**
  276. * Creates a {@link CKEDITOR.plugins.notificationAggregator.task} instance based
  277. * on `options`, and adds it to the task list.
  278. *
  279. * @private
  280. * @param options Options object coming from the {@link #createTask} method.
  281. * @returns {CKEDITOR.plugins.notificationAggregator.task}
  282. */
  283. _addTask: function( options ) {
  284. var task = new Task( options.weight );
  285. this._tasks.push( task );
  286. this._totalWeights += task._weight;
  287. return task;
  288. },
  289. /**
  290. * Removes a given task from the {@link #_tasks} array and updates the UI.
  291. *
  292. * @private
  293. * @param {CKEDITOR.plugins.notificationAggregator.task} task Task to be removed.
  294. */
  295. _removeTask: function( task ) {
  296. var index = CKEDITOR.tools.indexOf( this._tasks, task );
  297. if ( index !== -1 ) {
  298. // If task was already updated with some weight, we need to remove
  299. // this weight from our cache.
  300. if ( task._doneWeight ) {
  301. this._doneWeights -= task._doneWeight;
  302. }
  303. this._totalWeights -= task._weight;
  304. this._tasks.splice( index, 1 );
  305. // And we also should inform the UI about this change.
  306. this.update();
  307. }
  308. },
  309. /**
  310. * A listener called on the {@link CKEDITOR.plugins.notificationAggregator.task#update} event.
  311. *
  312. * @private
  313. * @param {CKEDITOR.eventInfo} evt Event object of the {@link CKEDITOR.plugins.notificationAggregator.task#update} event.
  314. */
  315. _onTaskUpdate: function( evt ) {
  316. this._doneWeights += evt.data;
  317. this.update();
  318. },
  319. /**
  320. * A listener called on the {@link CKEDITOR.plugins.notificationAggregator.task#event-done} event.
  321. *
  322. * @private
  323. * @param {CKEDITOR.eventInfo} evt Event object of the {@link CKEDITOR.plugins.notificationAggregator.task#event-done} event.
  324. */
  325. _onTaskDone: function() {
  326. this._doneTasks += 1;
  327. this.update();
  328. }
  329. };
  330. CKEDITOR.event.implementOn( Aggregator.prototype );
  331. /**
  332. * # Overview
  333. *
  334. * This type represents a single task in the aggregator, and exposes methods to manipulate its state.
  335. *
  336. * ## Weights
  337. *
  338. * Task progess is based on its **weight**.
  339. *
  340. * As you create a task, you need to declare its weight. As you want the update to inform about the
  341. * progress, you will need to {@link #update} the task, telling how much of this weight is done.
  342. *
  343. * For example, if you declare that your task has a weight that equals `50` and then call `update` with `10`,
  344. * you will end up with telling that the task is done in 20%.
  345. *
  346. * ### Example Usage of Weights
  347. *
  348. * Let us say that you use tasks for file uploading.
  349. *
  350. * A single task is associated with a single file upload. You can use the file size in bytes as a weight,
  351. * and then as the file upload progresses you just call the `update` method with the number of bytes actually
  352. * downloaded.
  353. *
  354. * @since 4.5
  355. * @class CKEDITOR.plugins.notificationAggregator.task
  356. * @mixins CKEDITOR.event
  357. * @constructor Creates a task instance for notification aggregator.
  358. * @param {Number} [weight=1]
  359. */
  360. function Task( weight ) {
  361. /**
  362. * Total weight of the task.
  363. *
  364. * @private
  365. * @property {Number}
  366. */
  367. this._weight = weight || 1;
  368. /**
  369. * Done weight of the task.
  370. *
  371. * @private
  372. * @property {Number}
  373. */
  374. this._doneWeight = 0;
  375. /**
  376. * Indicates when the task is canceled.
  377. *
  378. * @private
  379. * @property {Boolean}
  380. */
  381. this._isCanceled = false;
  382. }
  383. Task.prototype = {
  384. /**
  385. * Marks the task as done.
  386. */
  387. done: function() {
  388. this.update( this._weight );
  389. },
  390. /**
  391. * Updates the done weight of a task.
  392. *
  393. * @param {Number} weight Number indicating how much of the total task {@link #_weight} is done.
  394. */
  395. update: function( weight ) {
  396. // If task is already done or canceled there is no need to update it, and we don't expect
  397. // progress to be reversed.
  398. if ( this.isDone() || this.isCanceled() ) {
  399. return;
  400. }
  401. // Note that newWeight can't be higher than _doneWeight.
  402. var newWeight = Math.min( this._weight, weight ),
  403. weightChange = newWeight - this._doneWeight;
  404. this._doneWeight = newWeight;
  405. // Fire updated event even if task is done in order to correctly trigger updating the
  406. // notification's message. If we wouldn't do this, then the last weight change would be ignored.
  407. this.fire( 'updated', weightChange );
  408. if ( this.isDone() ) {
  409. this.fire( 'done' );
  410. }
  411. },
  412. /**
  413. * Cancels the task (the task will be removed from the aggregator).
  414. */
  415. cancel: function() {
  416. // If task is already done or canceled.
  417. if ( this.isDone() || this.isCanceled() ) {
  418. return;
  419. }
  420. // Mark task as canceled.
  421. this._isCanceled = true;
  422. // We'll fire cancel event it's up to aggregator to listen for this event,
  423. // and remove the task.
  424. this.fire( 'canceled' );
  425. },
  426. /**
  427. * Checks if the task is done.
  428. *
  429. * @returns {Boolean}
  430. */
  431. isDone: function() {
  432. return this._weight === this._doneWeight;
  433. },
  434. /**
  435. * Checks if the task is canceled.
  436. *
  437. * @returns {Boolean}
  438. */
  439. isCanceled: function() {
  440. return this._isCanceled;
  441. }
  442. };
  443. CKEDITOR.event.implementOn( Task.prototype );
  444. /**
  445. * Fired when all tasks are done. When this event occurs, the notification may change its type to `success` or be hidden:
  446. *
  447. * aggregator.on( 'finished', function() {
  448. * if ( aggregator.getTaskCount() == 0 ) {
  449. * aggregator.notification.hide();
  450. * } else {
  451. * aggregator.notification.update( { message: 'Done', type: 'success' } );
  452. * }
  453. * } );
  454. *
  455. * @event finished
  456. * @member CKEDITOR.plugins.notificationAggregator
  457. */
  458. /**
  459. * Fired upon each weight update of the task.
  460. *
  461. * var myTask = new Task( 100 );
  462. * myTask.update( 30 );
  463. * // Fires updated event with evt.data = 30.
  464. * myTask.update( 40 );
  465. * // Fires updated event with evt.data = 10.
  466. * myTask.update( 20 );
  467. * // Fires updated event with evt.data = -20.
  468. *
  469. * @event updated
  470. * @param {Number} data The difference between the new weight and the previous one.
  471. * @member CKEDITOR.plugins.notificationAggregator.task
  472. */
  473. /**
  474. * Fired when the task is done.
  475. *
  476. * @event done
  477. * @member CKEDITOR.plugins.notificationAggregator.task
  478. */
  479. /**
  480. * Fired when the task is canceled.
  481. *
  482. * @event canceled
  483. * @member CKEDITOR.plugins.notificationAggregator.task
  484. */
  485. // Expose Aggregator type.
  486. CKEDITOR.plugins.notificationAggregator = Aggregator;
  487. CKEDITOR.plugins.notificationAggregator.task = Task;
  488. } )();