%PDF- %PDF-
| Direktori : /data/old/home/stash/stash/atlassian-stash/static/util/ |
| Current File : //data/old/home/stash/stash/atlassian-stash/static/util/feature-loader.js |
define('util/feature-loader', [
'jquery',
'require',
'underscore',
'util/events'
], function (
$,
require,
_,
events
) {
'use strict';
/**
* Checks that the shape of module fits the interface we expect.
* @param module {Object} An object to be verified
* @param handlerName {String} The name of the module's linked handler, for use in error messages.
*/
function ensureValidModule(module, handlerName) {
if (typeof module.load !== 'function' ||
typeof module.unload !== 'function') {
throw new Error("Modules require both a load and unload callback. Please use:\n" + registerExample(handlerName));
}
}
/**
* Return an example usage of loader.
* @param handlerName {String} the example seciton name to use.
* @return {String} example text
*/
function registerExample(handlerName) {
return "FeatureLoader.registerHandler('" + handlerName + "', /urlMatcher/, {\n" +
" load : loadFn,\n" +
" unload : unloadFn,\n" +
" keyboardShortcutContexts : [ 'changeset', ... ]" +
"});";
}
/**
* A class for loading handlers of the page from different AMD modules.
* @constructor
*/
function FeatureLoader(options) {
if (!(this instanceof FeatureLoader)) {
return new FeatureLoader(options);
}
options = $.extend({}, FeatureLoader.defaults, options);
var currentUrl = window.location.href;
var currentHandlers = [];
var handlerData = {};
var loadingPromise = $.Deferred().resolve();
var hasPending;
var inited = false;
var el;
var keyboardShortcuts;
function setElement(newEl) {
el = newEl;
}
function setKeyboardShortcuts(newKeyboardShortcuts) {
keyboardShortcuts = newKeyboardShortcuts;
}
function registerHandler(handlerName, urlRegex, moduleOrModuleName) {
var handler;
if (_.has(handlerData, handlerName)) {
throw new Error("A handler with the name '" + handlerName + "' already exists.");
}
if (!moduleOrModuleName) {
throw new Error("No module or module name was provided. Please use:\n" + registerExample(handlerName));
}
if (typeof moduleOrModuleName === 'string') {
handler = handlerData[handlerName] = {
name : handlerName,
urlRegex : urlRegex,
moduleName : moduleOrModuleName
};
} else {
ensureValidModule(moduleOrModuleName, handlerName);
handler = handlerData[handlerName] = {
name : handlerName,
urlRegex : urlRegex,
module : moduleOrModuleName
};
}
// if it should be currently loaded and the current stuff isn't about to be unloaded,
// load it immediately.
if (inited && !hasPending && _.contains(getHandlersForUrl(window.location.href), handler)) {
load(handler);
}
return this;
}
function unload(handler) {
if (!_.contains(currentHandlers, handler)) {
return $.Deferred().resolve();
}
var maybePromise = handler.module.unload(el);
currentHandlers = _.without(currentHandlers, handler);
function afterUnload() {
events.trigger(options.unloadedEvent, null, handler);
}
if (maybePromise && maybePromise.then) {
return maybePromise.then(afterUnload);
} else {
afterUnload();
return $.Deferred().resolve();
}
}
/**
* Loads a module. Will first unload the current module if there is one.
* @param handlerName {String} name of the handler to load.
* @return {$.Deferred.promise}
*/
function load(handler) {
if (_.contains(currentHandlers, handler)) {
return $.Deferred().resolve();
}
if (!handler.module) {
handler.module = require(handler.moduleName);
ensureValidModule(handler.module, handler.name);
}
var maybePromise = handler.module.load(el);
currentHandlers.push(handler);
function afterLoad() {
events.trigger(options.loadedEvent, null, handler);
}
if (maybePromise && maybePromise.then) {
return maybePromise.then(afterLoad);
} else {
afterLoad();
return $.Deferred().resolve();
}
}
/**
* Accept a request to load new content. The url/handler may never actually be loaded (newer requests will
* supercede it if they come in).
* Fallback to reloading the page if the requested url isn't associated with a handler.
*/
function loadForCurrentUrl() {
if (inited && currentUrl === window.location.href) {
return;
}
currentUrl = window.location.href;
var handlers = getHandlersForUrl(currentUrl);
if (handlers.length) {
_.each(handlers, function(handler) {
events.trigger(options.requestedEvent, null, handler.name);
});
if (!hasPending) {
hasPending = true;
loadingPromise.then(onReadyForRequest);
}
} else if(!inited) {
// This is loading the initial URL.
// We don't want to get into an infinite reload loop, so just send an error event to whoever is in charge of this loader.
events.trigger(options.errorEvent, null, {
message : AJS.I18n.getText('stash.web.featureloader.nohandler'),
code : FeatureLoader.NO_HANDLER
});
} else {
window.location.reload();
}
}
/**
* Executed when we are ready to load a pending module.
*
* Load a new handler's content. Fire pushState if a new url is provided.
*/
function onReadyForRequest() {
hasPending = false;
var nextHandlers = getHandlersForUrl(window.location.href);
var newHandlers = _.difference(nextHandlers, currentHandlers);
var oldHandlers = _.difference(currentHandlers, nextHandlers);
var handlersChanged = newHandlers.length || oldHandlers.length;
if (handlersChanged) {
var unloadAll = function() {
if (!currentHandlers.length) {
// this page has no handlers, which is a bit of an error case. Empty the element for now.
$(el).empty();
return $.Deferred().resolve();
}
return $.when.apply($, _.map(oldHandlers, unload));
};
var loadAll = function() {
return $.when.apply($, _.map(newHandlers, load));
};
var unloadThenLoadAll = function() {
return unloadAll().then(loadAll);
};
loadingPromise = unloadThenLoadAll().then(function() {
// per-context disabling needs to be implemented in the plugin...FUUUUUUUU
// https://studio.atlassian.com/browse/AKS-14
// disable everything and reenable active contexts for now.
if (keyboardShortcuts) {
keyboardShortcuts.resetContexts();
}
});
}
}
function getHandlersForUrl(url) {
return _.filter(handlerData, function(handler) {
return handler.urlRegex && handler.urlRegex.test(url);
});
}
function current() {
return currentHandlers.slice();
}
function changeStateHandler() {
loadForCurrentUrl();
}
function getKeyboardContextsForHandler(handler) {
return handler.module && handler.module.keyboardShortcutContexts || [];
}
function keyboardShortcutHandler(keyboardShortcuts) {
var contexts = _.chain(currentHandlers)
.map(getKeyboardContextsForHandler)
.flatten()
.uniq()
.value();
_.each(contexts, function(context) {
keyboardShortcuts.enableContext(context);
});
}
function init(el) {
setElement(el);
loadForCurrentUrl();
events.on('memoir.changestate', changeStateHandler);
events.on('stash.widget.keyboard-shortcuts.register-contexts', keyboardShortcutHandler);
inited = true;
}
function destroy() {
events.off('memoir.changestate', changeStateHandler);
events.off('stash.widget.keyboard-shortcuts.register-contexts', keyboardShortcutHandler);
}
this.registerHandler = registerHandler;
this.setElement = setElement;
this.setKeyboardShortcuts = setKeyboardShortcuts;
this.current = current;
this.init = init;
this.destroy = destroy;
return this; // stop IDEA complaining about inconsistent return points
}
FeatureLoader.defaults = {
unloadedEvent : 'stash.util.feature-loader.unloaded',
loadedEvent : 'stash.util.feature-loader.loaded',
requestedEvent : 'stash.util.feature-loader.loadRequested',
errorEvent : 'stash.util.feature-loader.errorOccurred'
};
FeatureLoader.NO_HANDLER = 'NO_HANDLER';
return FeatureLoader;
});