%PDF- %PDF-
| Direktori : /data/old/home/stash/atlassian/stash/3.7.1/atlassian-stash/static/util/ |
| Current File : //data/old/home/stash/atlassian/stash/3.7.1/atlassian-stash/static/util/widget.js |
define('util/widget', [
'lodash',
'util/mixin'
], function(
_,
mix
) {
'use strict';
function getListeners(obj, eventName) {
if (!obj._listeners) {
obj._listeners = {};
}
if (!obj._listeners[eventName]) {
obj._listeners[eventName] = [];
}
return obj._listeners[eventName];
}
// Not exposed separately to encourage people to use the full widget mixin. Can be reconsidered
var events = {
/**
* listen to an event
* @param {string} eventName
* @param {function} handler
* @returns {this}
*/
on : function(eventName, handler) {
var listeners = getListeners(this, eventName);
if (!_.contains(listeners, handler)) {
listeners.push(handler);
}
return this;
},
/**
* Stop listening to an event
* @param {string} eventName
* @param {function} handler
* @returns {this}
*/
off : function(eventName, handler) {
var listeners = getListeners(this, eventName);
var i = listeners.length;
while(i--) {
// if it's the callback, or the boundOff for the callback
if (listeners[i] === handler || listeners[i]._handler === handler) {
listeners.splice(i, 1);
}
}
return this;
},
/**
* Listen to an event once, then unbind it from future occurrences of the event
* @param {string} eventName
* @param {function} handler
* @returns {this}
*/
once : function(eventName, handler) {
var boundOff = this.off.bind(this, eventName, handler);
boundOff._handler = handler;
this.on(eventName, handler);
this.on(eventName, boundOff);
return this;
},
/**
* Call all listeners for an event.
* @param {string} eventName
* @param {...*} argument
* @returns {this}
*/
trigger : function(eventName/*, ...args */) {
var listeners = getListeners(this, eventName).slice();
var args = [].slice.call(arguments, 1);
var self = this;
listeners.forEach(function(fn) {
try {
fn.apply(self, args);
}
catch (e) {
_.defer(function() {
throw e;
});
}
});
return this;
}
};
// Not exposed separately to encourage people to use the full widget mixin. Can be reconsidered
var lifecycle = {
/**
* Call _init from your widget to bind all methods to this instance and populate this._options
* with incoming options and defaults.
*
* Use MyCtor.defaults = {} to specify default options.
*
* @param {Object} options - options for this widget.
* @param {Object} defaults - defaults for this widget.
* @private
*/
_init : function(options, defaults) {
_.bindAll.apply(_, [this].concat(_.functions(this)));
this._options = _.extend({}, defaults || this.constructor && this.constructor.defaults, options);
return this;
},
/**
* Add a thing to be destroyed when I am destroyed.
* @param {{ destroy : function }} destroyable - thing to destroy with me
* @private
*/
_addDestroyable : function(destroyable) {
if (!this._destroyables) {
this._destroyables = [];
}
if (_.isFunction(destroyable)) {
destroyable = {
destroy : destroyable
};
}
if (!destroyable.destroy) {
throw new Error("Argument is not destroyable");
}
this._destroyables.push(destroyable);
return this;
},
/**
* When called, the widget is no longer usable. All nested destroyables will have destroy called on them.
* If the event mixin is included, the destroy event will be fired.
*/
destroy : function() {
if (this._destroyables) {
_.invoke(this._destroyables, 'destroy');
this._destroyables = null;
}
if (this.trigger) {
this.trigger('destroy');
}
if (this._listeners) {
this._listeners = null;
}
}
};
var widget = mix(events, lifecycle).into({});
return {
mixInto : mix(widget).into
};
});