egroupware/phpgwapi/js/ckeditor/plugins/notificationaggregator/plugin.js
2015-08-06 14:52:25 +00:00

549 lines
15 KiB
JavaScript

/**
* @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or http://ckeditor.com/license
*/
/**
* @fileOverview The "Notification Aggregator" plugin.
*
*/
( function() {
'use strict';
CKEDITOR.plugins.add( 'notificationaggregator', {
requires: 'notification'
} );
/**
* An aggregator of multiple tasks (progresses) which should be displayed using one
* {@link CKEDITOR.plugins.notification notification}.
*
* Once all the tasks are done, it means that the whole process is finished and the {@link #finished}
* event will be fired.
*
* New tasks can be created after the previous set of tasks is finished. This will continue the process and
* fire the {@link #finished} event again when it is done.
*
* Simple usage example:
*
* // Declare one aggregator that will be used for all tasks.
* var aggregator;
*
* // ...
*
* // Create a new aggregator if the previous one finished all tasks.
* if ( !aggregator || aggregator.isFinished() ) {
* // Create a new notification aggregator instance.
* aggregator = new CKEDITOR.plugins.notificationAggregator( editor, 'Loading process, step {current} of {max}...' );
*
* // Change the notification type to 'success' on finish.
* aggregator.on( 'finished', function() {
* aggregator.notification.update( { message: 'Done', type: 'success' } );
* } );
* }
*
* // Create 3 tasks.
* var taskA = aggregator.createTask(),
* taskB = aggregator.createTask(),
* taskC = aggregator.createTask();
*
* // At this point the notification contains a message: "Loading process, step 0 of 3...".
*
* // Let's close the first one immediately.
* taskA.done(); // "Loading process, step 1 of 3...".
*
* // One second later the message will be: "Loading process, step 2 of 3...".
* setTimeout( function() {
* taskB.done();
* }, 1000 );
*
* // Two seconds after the previous message the last task will be completed, meaning that
* // the notification will be closed.
* setTimeout( function() {
* taskC.done();
* }, 3000 );
*
* @since 4.5
* @class CKEDITOR.plugins.notificationAggregator
* @mixins CKEDITOR.event
* @constructor Creates a notification aggregator instance.
* @param {CKEDITOR.editor} editor
* @param {String} message The template for the message to be displayed in the notification. The template can use
* the following variables:
*
* * `{current}` – The number of completed tasks.
* * `{max}` – The number of tasks.
* * `{percentage}` – The progress (number 0-100).
*
* @param {String/null} [singularMessage=null] An optional template for the message to be displayed in the notification
* when there is only one task remaining. This template can use the same variables as the `message` template.
* If `null`, then the `message` template will be used.
*/
function Aggregator( editor, message, singularMessage ) {
/**
* @readonly
* @property {CKEDITOR.editor} editor
*/
this.editor = editor;
/**
* Notification created by the aggregator.
*
* The notification object is modified as aggregator tasks are being closed.
*
* @readonly
* @property {CKEDITOR.plugins.notification/null}
*/
this.notification = null;
/**
* A template for the notification message.
*
* The template can use the following variables:
*
* * `{current}` – The number of completed tasks.
* * `{max}` – The number of tasks.
* * `{percentage}` – The progress (number 0-100).
*
* @private
* @property {CKEDITOR.template}
*/
this._message = new CKEDITOR.template( message );
/**
* A template for the notification message used when only one task is loading.
*
* Sometimes there might be a need to specify a special message when there
* is only one task loading, and to display standard messages in other cases.
*
* For example, you might want to show a message "Translating a widget" rather than
* "Translating widgets (1 of 1)", but still you would want to have a message
* "Translating widgets (2 of 3)" if more widgets are being translated at the same
* time.
*
* Template variables are the same as in {@link #_message}.
*
* @private
* @property {CKEDITOR.template/null}
*/
this._singularMessage = singularMessage ? new CKEDITOR.template( singularMessage ) : null;
// Set the _tasks, _totalWeights, _doneWeights, _doneTasks properties.
this._tasks = [];
this._totalWeights = 0;
this._doneWeights = 0;
this._doneTasks = 0;
/**
* Array of tasks tracked by the aggregator.
*
* @private
* @property {CKEDITOR.plugins.notificationAggregator.task[]} _tasks
*/
/**
* Stores the sum of declared weights for all contained tasks.
*
* @private
* @property {Number} _totalWeights
*/
/**
* Stores the sum of done weights for all contained tasks.
*
* @private
* @property {Number} _doneWeights
*/
/**
* Stores the count of tasks done.
*
* @private
* @property {Number} _doneTasks
*/
}
Aggregator.prototype = {
/**
* Creates a new task that can be updated to indicate the progress.
*
* @param [options] Options object for the task creation.
* @param [options.weight] For more information about weight, see the
* {@link CKEDITOR.plugins.notificationAggregator.task} overview.
* @returns {CKEDITOR.plugins.notificationAggregator.task} An object that represents the task state, and allows
* for its manipulation.
*/
createTask: function( options ) {
options = options || {};
var initialTask = !this.notification,
task;
if ( initialTask ) {
// It's a first call.
this.notification = this._createNotification();
}
task = this._addTask( options );
task.on( 'updated', this._onTaskUpdate, this );
task.on( 'done', this._onTaskDone, this );
task.on( 'canceled', function() {
this._removeTask( task );
}, this );
// Update the aggregator.
this.update();
if ( initialTask ) {
this.notification.show();
}
return task;
},
/**
* Triggers an update on the aggregator, meaning that its UI will be refreshed.
*
* When all the tasks are done, the {@link #finished} event is fired.
*/
update: function() {
this._updateNotification();
if ( this.isFinished() ) {
this.fire( 'finished' );
}
},
/**
* Returns a number from `0` to `1` representing the done weights to total weights ratio
* (showing how many of the tasks are done).
*
* Note: For an empty aggregator (without any tasks created) it will return `1`.
*
* @returns {Number} Returns the percentage of tasks done as a number ranging from `0` to `1`.
*/
getPercentage: function() {
// In case there are no weights at all we'll return 1.
if ( this.getTaskCount() === 0 ) {
return 1;
}
return this._doneWeights / this._totalWeights;
},
/**
* @returns {Boolean} Returns `true` if all notification tasks are done
* (or there are no tasks at all).
*/
isFinished: function() {
return this.getDoneTaskCount() === this.getTaskCount();
},
/**
* @returns {Number} Returns a total tasks count.
*/
getTaskCount: function() {
return this._tasks.length;
},
/**
* @returns {Number} Returns the number of tasks done.
*/
getDoneTaskCount: function() {
return this._doneTasks;
},
/**
* Updates the notification content.
*
* @private
*/
_updateNotification: function() {
this.notification.update( {
message: this._getNotificationMessage(),
progress: this.getPercentage()
} );
},
/**
* Returns a message used in the notification.
*
* @private
* @returns {String}
*/
_getNotificationMessage: function() {
var tasksCount = this.getTaskCount(),
doneTasks = this.getDoneTaskCount(),
templateParams = {
current: doneTasks,
max: tasksCount,
percentage: Math.round( this.getPercentage() * 100 )
},
template;
// If there's only one remaining task and we have a singular message, we should use it.
if ( tasksCount == 1 && this._singularMessage ) {
template = this._singularMessage;
} else {
template = this._message;
}
return template.output( templateParams );
},
/**
* Creates a notification object.
*
* @private
* @returns {CKEDITOR.plugins.notification}
*/
_createNotification: function() {
return new CKEDITOR.plugins.notification( this.editor, {
type: 'progress'
} );
},
/**
* Creates a {@link CKEDITOR.plugins.notificationAggregator.task} instance based
* on `options`, and adds it to the task list.
*
* @private
* @param options Options object coming from the {@link #createTask} method.
* @returns {CKEDITOR.plugins.notificationAggregator.task}
*/
_addTask: function( options ) {
var task = new Task( options.weight );
this._tasks.push( task );
this._totalWeights += task._weight;
return task;
},
/**
* Removes a given task from the {@link #_tasks} array and updates the UI.
*
* @private
* @param {CKEDITOR.plugins.notificationAggregator.task} task Task to be removed.
*/
_removeTask: function( task ) {
var index = CKEDITOR.tools.indexOf( this._tasks, task );
if ( index !== -1 ) {
// If task was already updated with some weight, we need to remove
// this weight from our cache.
if ( task._doneWeight ) {
this._doneWeights -= task._doneWeight;
}
this._totalWeights -= task._weight;
this._tasks.splice( index, 1 );
// And we also should inform the UI about this change.
this.update();
}
},
/**
* A listener called on the {@link CKEDITOR.plugins.notificationAggregator.task#update} event.
*
* @private
* @param {CKEDITOR.eventInfo} evt Event object of the {@link CKEDITOR.plugins.notificationAggregator.task#update} event.
*/
_onTaskUpdate: function( evt ) {
this._doneWeights += evt.data;
this.update();
},
/**
* A listener called on the {@link CKEDITOR.plugins.notificationAggregator.task#event-done} event.
*
* @private
* @param {CKEDITOR.eventInfo} evt Event object of the {@link CKEDITOR.plugins.notificationAggregator.task#event-done} event.
*/
_onTaskDone: function() {
this._doneTasks += 1;
this.update();
}
};
CKEDITOR.event.implementOn( Aggregator.prototype );
/**
* # Overview
*
* This type represents a single task in the aggregator, and exposes methods to manipulate its state.
*
* ## Weights
*
* Task progess is based on its **weight**.
*
* As you create a task, you need to declare its weight. As you want the update to inform about the
* progress, you will need to {@link #update} the task, telling how much of this weight is done.
*
* For example, if you declare that your task has a weight that equals `50` and then call `update` with `10`,
* you will end up with telling that the task is done in 20%.
*
* ### Example Usage of Weights
*
* Let us say that you use tasks for file uploading.
*
* A single task is associated with a single file upload. You can use the file size in bytes as a weight,
* and then as the file upload progresses you just call the `update` method with the number of bytes actually
* downloaded.
*
* @since 4.5
* @class CKEDITOR.plugins.notificationAggregator.task
* @mixins CKEDITOR.event
* @constructor Creates a task instance for notification aggregator.
* @param {Number} [weight=1]
*/
function Task( weight ) {
/**
* Total weight of the task.
*
* @private
* @property {Number}
*/
this._weight = weight || 1;
/**
* Done weight of the task.
*
* @private
* @property {Number}
*/
this._doneWeight = 0;
/**
* Indicates when the task is canceled.
*
* @private
* @property {Boolean}
*/
this._isCanceled = false;
}
Task.prototype = {
/**
* Marks the task as done.
*/
done: function() {
this.update( this._weight );
},
/**
* Updates the done weight of a task.
*
* @param {Number} weight Number indicating how much of the total task {@link #_weight} is done.
*/
update: function( weight ) {
// If task is already done or canceled there is no need to update it, and we don't expect
// progress to be reversed.
if ( this.isDone() || this.isCanceled() ) {
return;
}
// Note that newWeight can't be higher than _doneWeight.
var newWeight = Math.min( this._weight, weight ),
weightChange = newWeight - this._doneWeight;
this._doneWeight = newWeight;
// Fire updated event even if task is done in order to correctly trigger updating the
// notification's message. If we wouldn't do this, then the last weight change would be ignored.
this.fire( 'updated', weightChange );
if ( this.isDone() ) {
this.fire( 'done' );
}
},
/**
* Cancels the task (the task will be removed from the aggregator).
*/
cancel: function() {
// If task is already done or canceled.
if ( this.isDone() || this.isCanceled() ) {
return;
}
// Mark task as canceled.
this._isCanceled = true;
// We'll fire cancel event it's up to aggregator to listen for this event,
// and remove the task.
this.fire( 'canceled' );
},
/**
* Checks if the task is done.
*
* @returns {Boolean}
*/
isDone: function() {
return this._weight === this._doneWeight;
},
/**
* Checks if the task is canceled.
*
* @returns {Boolean}
*/
isCanceled: function() {
return this._isCanceled;
}
};
CKEDITOR.event.implementOn( Task.prototype );
/**
* Fired when all tasks are done. When this event occurs, the notification may change its type to `success` or be hidden:
*
* aggregator.on( 'finished', function() {
* if ( aggregator.getTaskCount() == 0 ) {
* aggregator.notification.hide();
* } else {
* aggregator.notification.update( { message: 'Done', type: 'success' } );
* }
* } );
*
* @event finished
* @member CKEDITOR.plugins.notificationAggregator
*/
/**
* Fired upon each weight update of the task.
*
* var myTask = new Task( 100 );
* myTask.update( 30 );
* // Fires updated event with evt.data = 30.
* myTask.update( 40 );
* // Fires updated event with evt.data = 10.
* myTask.update( 20 );
* // Fires updated event with evt.data = -20.
*
* @event updated
* @param {Number} data The difference between the new weight and the previous one.
* @member CKEDITOR.plugins.notificationAggregator.task
*/
/**
* Fired when the task is done.
*
* @event done
* @member CKEDITOR.plugins.notificationAggregator.task
*/
/**
* Fired when the task is canceled.
*
* @event canceled
* @member CKEDITOR.plugins.notificationAggregator.task
*/
// Expose Aggregator type.
CKEDITOR.plugins.notificationAggregator = Aggregator;
CKEDITOR.plugins.notificationAggregator.task = Task;
} )();