Add support for lazy module loading
When the X-Icinga-Module-Enable header is send, the modulemanager automatically tries to load javascript files for that module. This is realized by adding the 'registerHeaderListener' method to the async manager, which allows to listen to specific headers and firing callbacks if a response with the specified header is retrieved. Also the tests have changed a bit, requireNow should be used when using the requiremock, so a require always loads files new. refs #4092 refs #3753
This commit is contained in:
parent
c25ea37fd8
commit
e9ade7a339
|
@ -35,7 +35,11 @@ class ModulesController extends ActionController
|
|||
public function enableAction()
|
||||
{
|
||||
$this->manager->enableModule($this->_getParam('name'));
|
||||
$this->redirectNow('modules/overview?_render=body');
|
||||
$this->manager->loadModule($this->_getParam('name'));
|
||||
$this->getResponse()->setHeader('X-Icinga-Enable-Module', $this->_getParam('name'));
|
||||
$this->replaceLayout = true;
|
||||
$this->indexAction();
|
||||
|
||||
}
|
||||
|
||||
public function disableAction()
|
||||
|
|
|
@ -19,16 +19,19 @@ define([
|
|||
var failedModules = [];
|
||||
|
||||
var initialize = function () {
|
||||
registerLazyModuleLoading();
|
||||
enableInternalModules();
|
||||
|
||||
|
||||
containerMgr.registerAsyncMgr(async);
|
||||
containerMgr.initializeContainers(document);
|
||||
log.debug("Initialization finished");
|
||||
|
||||
enableModules();
|
||||
};
|
||||
|
||||
|
||||
var registerLazyModuleLoading = function() {
|
||||
async.registerHeaderListener("X-Icinga-Enable-Module", loadModuleScript, this);
|
||||
};
|
||||
|
||||
var enableInternalModules = function() {
|
||||
$.each(internalModules,function(idx,module) {
|
||||
|
@ -37,6 +40,7 @@ define([
|
|||
};
|
||||
|
||||
var loadModuleScript = function(name) {
|
||||
console.log("Loading ", name);
|
||||
moduleMgr.enableModule("modules/"+name+"/"+name, function(error) {
|
||||
failedModules.push({
|
||||
name: name,
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
"use strict";
|
||||
var asyncMgrInstance = null;
|
||||
|
||||
define(['icinga/container','logging','icinga/behaviour','jquery'],function(containerMgr,log,behaviour,$) {
|
||||
define(['icinga/container','logging','jquery'],function(containerMgr,log,$) {
|
||||
var headerListeners = {};
|
||||
|
||||
var pending = {
|
||||
|
||||
|
@ -18,12 +19,30 @@
|
|||
return target;
|
||||
};
|
||||
|
||||
var handleResponse = function(html) {
|
||||
var applyHeaderListeners = function(headers) {
|
||||
for (var header in headerListeners) {
|
||||
if (headers.getResponseHeader(header) === null) {
|
||||
// see if the browser/server converts headers to lowercase
|
||||
if (headers.getResponseHeader(header.toLowerCase()) === null) {
|
||||
continue;
|
||||
}
|
||||
header = header.toLowerCase();
|
||||
}
|
||||
var value = headers.getResponseHeader(header);
|
||||
var listeners = headerListeners[header];
|
||||
for (var i=0;i<listeners.length;i++) {
|
||||
listeners[i].fn.apply(listeners[i].scope, [value, header, headers]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var handleResponse = function(html, status, response) {
|
||||
applyHeaderListeners(response);
|
||||
if(this.destination) {
|
||||
containerMgr.updateContainer(this.destination,html,this);
|
||||
} else {
|
||||
containerMgr.createPopupContainer(html,this);
|
||||
// tbd
|
||||
// containerMgr.createPopupContainer(html,this);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -49,6 +68,8 @@
|
|||
|
||||
var CallInterface = function() {
|
||||
|
||||
this.__internalXHRImplementation = $.ajax;
|
||||
|
||||
this.clearPendingRequestsFor = function(destination) {
|
||||
if(!$.isArray(pending)) {
|
||||
pending = [];
|
||||
|
@ -68,7 +89,7 @@
|
|||
};
|
||||
|
||||
this.createRequest = function(url,data) {
|
||||
var req = $.ajax({
|
||||
var req = this.__internalXHRImplementation({
|
||||
type : data ? 'POST' : 'GET',
|
||||
url : url,
|
||||
data : data,
|
||||
|
@ -101,6 +122,11 @@
|
|||
this.loadCSS = function(name) {
|
||||
|
||||
};
|
||||
|
||||
this.registerHeaderListener = function(header, fn, scope) {
|
||||
headerListeners[header] = headerListeners[header] || [];
|
||||
headerListeners[header].push({fn: fn, scope:scope});
|
||||
};
|
||||
};
|
||||
return new CallInterface();
|
||||
});
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
// {{LICENSE_HEADER}}
|
||||
// {{LICENSE_HEADER}}
|
||||
var should = require("should");
|
||||
var rjsmock = require("requiremock.js");
|
||||
var asyncMock = require("asyncmock.js");
|
||||
|
||||
GLOBAL.document = $('body');
|
||||
|
||||
|
||||
describe('The async module', function() {
|
||||
it("Allows to react on specific headers", function(done) {
|
||||
rjsmock.purgeDependencies();
|
||||
rjsmock.registerDependencies({
|
||||
'icinga/container' : {
|
||||
updateContainer : function() {},
|
||||
createPopupContainer: function() {}
|
||||
}
|
||||
});
|
||||
|
||||
requireNew("icinga/util/async.js");
|
||||
var async = rjsmock.getDefine();
|
||||
var headerValue = null;
|
||||
asyncMock.setNextAsyncResult(async, "result", false, {
|
||||
'X-Dont-Care' : 'Ignore-me',
|
||||
'X-Test-Header' : 'Testme123'
|
||||
});
|
||||
async.registerHeaderListener("X-Test-Header", function(value, header) {
|
||||
should.equal("Testme123", value);
|
||||
done();
|
||||
},this);
|
||||
var test = async.createRequest();
|
||||
});
|
||||
|
||||
});
|
|
@ -9,12 +9,12 @@
|
|||
// {{LICENSE_HEADER}}
|
||||
var should = require("should");
|
||||
var rjsmock = require("requiremock.js");
|
||||
var asyncMock = require("asyncmock.js");
|
||||
|
||||
var BASE = "../../../../public/js/";
|
||||
require(BASE+"icinga/module.js");
|
||||
|
||||
requireNew("icinga/module.js");
|
||||
var module = rjsmock.getDefine();
|
||||
GLOBAL.document = $('body');
|
||||
|
||||
/**
|
||||
* Test module that only uses eventhandlers and
|
||||
* no custom logic
|
||||
|
@ -195,6 +195,9 @@ describe('The icinga module bootstrap', function() {
|
|||
var testClick = false;
|
||||
rjsmock.registerDependencies({
|
||||
"icinga/module": module,
|
||||
"icinga/util/async" : {
|
||||
registerHeaderListener: function() {}
|
||||
},
|
||||
"modules/test/test" : {
|
||||
eventHandler: {
|
||||
"a.test" : {
|
||||
|
@ -214,7 +217,7 @@ describe('The icinga module bootstrap', function() {
|
|||
]
|
||||
});
|
||||
tearDownTestDOM();
|
||||
require(BASE+"icinga/icinga.js");
|
||||
requireNew("icinga/icinga.js");
|
||||
var icinga = rjsmock.getDefine();
|
||||
$('body').append($("<a class='test'></a>"));
|
||||
$('a.test').click();
|
||||
|
@ -223,4 +226,55 @@ describe('The icinga module bootstrap', function() {
|
|||
should.equal(icinga.getFailedModules()[0].name, "test2");
|
||||
tearDownTestDOM();
|
||||
});
|
||||
|
||||
it("Should load modules lazily when discovering a X-Icinga-Enable-Module header", function() {
|
||||
rjsmock.purgeDependencies();
|
||||
|
||||
requireNew("icinga/util/async.js");
|
||||
var async = rjsmock.getDefine();
|
||||
|
||||
rjsmock.registerDependencies({
|
||||
"icinga/module": module,
|
||||
"icinga/util/async": async,
|
||||
"modules/test/test" : {
|
||||
eventHandler: {
|
||||
"a.test" : {
|
||||
click : function() {
|
||||
testClick = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"icinga/container" : {
|
||||
registerAsyncMgr: function() {},
|
||||
initializeContainers: function() {}
|
||||
},
|
||||
"modules/list" : [
|
||||
]
|
||||
});
|
||||
|
||||
tearDownTestDOM();
|
||||
|
||||
requireNew("icinga/icinga.js");
|
||||
var icinga = rjsmock.getDefine();
|
||||
|
||||
var testClick = false;
|
||||
// The module shouldn't be loaded
|
||||
$('body').append($("<a class='test'></a>"));
|
||||
$('a.test').click();
|
||||
should.equal(testClick, false, "Unregistered module was loaded");
|
||||
|
||||
asyncMock.setNextAsyncResult(async,"result", false, {
|
||||
"X-Icinga-Enable-Module" : "test"
|
||||
});
|
||||
async.createRequest();
|
||||
// The module shouldn't be loaded
|
||||
$('body').append($("<a class='test'></a>"));
|
||||
$('a.test').click();
|
||||
should.equal(testClick, true, "Module wasn't automatically loaded on header!");
|
||||
|
||||
|
||||
tearDownTestDOM();
|
||||
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* Helper for mocking $.async's XHR requests
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
var getCallback = function(empty, response, succeed, headers) {
|
||||
if (empty)
|
||||
return function() {};
|
||||
return function(callback) {
|
||||
callback(response, succeed, {
|
||||
getAllResponseHeaders: function() {
|
||||
return headers;
|
||||
},
|
||||
getResponseHeader: function(header) {
|
||||
return headers[header] || null;
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
setNextAsyncResult: function(async, response, fails, headers) {
|
||||
headers = headers || {};
|
||||
var succeed = fails ? "fail" : "success";
|
||||
async.__internalXHRImplementation = function(config) {
|
||||
return {
|
||||
done: getCallback(fails, response, succeed, headers),
|
||||
fail: getCallback(!fails, response, succeed, headers)
|
||||
};
|
||||
};
|
||||
}
|
||||
};
|
|
@ -13,6 +13,7 @@
|
|||
* to console.
|
||||
*
|
||||
**/
|
||||
var path = require('path');
|
||||
var registeredDependencies = {};
|
||||
|
||||
/**
|
||||
|
@ -21,11 +22,13 @@ var registeredDependencies = {};
|
|||
* in dependencies and calls fn with them as the parameter
|
||||
*
|
||||
**/
|
||||
var debug = false;
|
||||
var requireJsMock = function(dependencies, fn) {
|
||||
var fnArgs = [];
|
||||
for (var i=0;i<dependencies.length;i++) {
|
||||
if (typeof registeredDependencies[dependencies[i]] === "undefined") {
|
||||
console.warn("Unknown dependency "+dependencies[i]+" in define()");
|
||||
if (debug === true)
|
||||
console.warn("Unknown dependency "+dependencies[i]+" in define()");
|
||||
}
|
||||
fnArgs.push(registeredDependencies[dependencies[i]]);
|
||||
}
|
||||
|
@ -55,7 +58,7 @@ var defineMock = function() {
|
|||
var argList = arguments[currentArg];
|
||||
fn = arguments[currentArg+1];
|
||||
for (var i=0;i<argList.length;i++) {
|
||||
if (typeof registerDependencies[argList[i]] === "undefined") {
|
||||
if (typeof registerDependencies[argList[i]] === "undefined" && debug) {
|
||||
console.warn("Unknown dependency "+argList[i]+" in define()");
|
||||
}
|
||||
|
||||
|
@ -91,7 +94,6 @@ initRequireMethods();
|
|||
function purgeDependencies() {
|
||||
registeredDependencies = {
|
||||
'jquery' : GLOBAL.$,
|
||||
'__define__' : registeredDependencies.__define__,
|
||||
'logging' : console
|
||||
};
|
||||
}
|
||||
|
@ -107,6 +109,12 @@ function registerDependencies(obj) {
|
|||
registeredDependencies[name] = obj[name];
|
||||
}
|
||||
}
|
||||
var base = path.normalize(__dirname+"../../../../public/js");
|
||||
GLOBAL.requireNew = function(key) {
|
||||
key = path.normalize(base+"/"+key);
|
||||
delete require.cache[key];
|
||||
return require(key);
|
||||
};
|
||||
|
||||
/**
|
||||
* The API for this module
|
||||
|
@ -122,3 +130,4 @@ module.exports = {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue