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
36c8e0df4a
commit
35c43446d8
|
@ -35,7 +35,11 @@ class ModulesController extends ActionController
|
||||||
public function enableAction()
|
public function enableAction()
|
||||||
{
|
{
|
||||||
$this->manager->enableModule($this->_getParam('name'));
|
$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()
|
public function disableAction()
|
||||||
|
|
|
@ -19,6 +19,7 @@ define([
|
||||||
var failedModules = [];
|
var failedModules = [];
|
||||||
|
|
||||||
var initialize = function () {
|
var initialize = function () {
|
||||||
|
registerLazyModuleLoading();
|
||||||
enableInternalModules();
|
enableInternalModules();
|
||||||
|
|
||||||
containerMgr.registerAsyncMgr(async);
|
containerMgr.registerAsyncMgr(async);
|
||||||
|
@ -28,7 +29,9 @@ define([
|
||||||
enableModules();
|
enableModules();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var registerLazyModuleLoading = function() {
|
||||||
|
async.registerHeaderListener("X-Icinga-Enable-Module", loadModuleScript, this);
|
||||||
|
};
|
||||||
|
|
||||||
var enableInternalModules = function() {
|
var enableInternalModules = function() {
|
||||||
$.each(internalModules,function(idx,module) {
|
$.each(internalModules,function(idx,module) {
|
||||||
|
@ -37,6 +40,7 @@ define([
|
||||||
};
|
};
|
||||||
|
|
||||||
var loadModuleScript = function(name) {
|
var loadModuleScript = function(name) {
|
||||||
|
console.log("Loading ", name);
|
||||||
moduleMgr.enableModule("modules/"+name+"/"+name, function(error) {
|
moduleMgr.enableModule("modules/"+name+"/"+name, function(error) {
|
||||||
failedModules.push({
|
failedModules.push({
|
||||||
name: name,
|
name: name,
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
var asyncMgrInstance = null;
|
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 = {
|
var pending = {
|
||||||
|
|
||||||
|
@ -18,12 +19,30 @@
|
||||||
return target;
|
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) {
|
if(this.destination) {
|
||||||
containerMgr.updateContainer(this.destination,html,this);
|
containerMgr.updateContainer(this.destination,html,this);
|
||||||
} else {
|
} else {
|
||||||
containerMgr.createPopupContainer(html,this);
|
// tbd
|
||||||
|
// containerMgr.createPopupContainer(html,this);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -49,6 +68,8 @@
|
||||||
|
|
||||||
var CallInterface = function() {
|
var CallInterface = function() {
|
||||||
|
|
||||||
|
this.__internalXHRImplementation = $.ajax;
|
||||||
|
|
||||||
this.clearPendingRequestsFor = function(destination) {
|
this.clearPendingRequestsFor = function(destination) {
|
||||||
if(!$.isArray(pending)) {
|
if(!$.isArray(pending)) {
|
||||||
pending = [];
|
pending = [];
|
||||||
|
@ -68,7 +89,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
this.createRequest = function(url,data) {
|
this.createRequest = function(url,data) {
|
||||||
var req = $.ajax({
|
var req = this.__internalXHRImplementation({
|
||||||
type : data ? 'POST' : 'GET',
|
type : data ? 'POST' : 'GET',
|
||||||
url : url,
|
url : url,
|
||||||
data : data,
|
data : data,
|
||||||
|
@ -101,6 +122,11 @@
|
||||||
this.loadCSS = function(name) {
|
this.loadCSS = function(name) {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.registerHeaderListener = function(header, fn, scope) {
|
||||||
|
headerListeners[header] = headerListeners[header] || [];
|
||||||
|
headerListeners[header].push({fn: fn, scope:scope});
|
||||||
|
};
|
||||||
};
|
};
|
||||||
return new CallInterface();
|
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}}
|
// {{LICENSE_HEADER}}
|
||||||
var should = require("should");
|
var should = require("should");
|
||||||
var rjsmock = require("requiremock.js");
|
var rjsmock = require("requiremock.js");
|
||||||
|
var asyncMock = require("asyncmock.js");
|
||||||
|
|
||||||
var BASE = "../../../../public/js/";
|
requireNew("icinga/module.js");
|
||||||
require(BASE+"icinga/module.js");
|
|
||||||
|
|
||||||
var module = rjsmock.getDefine();
|
var module = rjsmock.getDefine();
|
||||||
GLOBAL.document = $('body');
|
GLOBAL.document = $('body');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test module that only uses eventhandlers and
|
* Test module that only uses eventhandlers and
|
||||||
* no custom logic
|
* no custom logic
|
||||||
|
@ -195,6 +195,9 @@ describe('The icinga module bootstrap', function() {
|
||||||
var testClick = false;
|
var testClick = false;
|
||||||
rjsmock.registerDependencies({
|
rjsmock.registerDependencies({
|
||||||
"icinga/module": module,
|
"icinga/module": module,
|
||||||
|
"icinga/util/async" : {
|
||||||
|
registerHeaderListener: function() {}
|
||||||
|
},
|
||||||
"modules/test/test" : {
|
"modules/test/test" : {
|
||||||
eventHandler: {
|
eventHandler: {
|
||||||
"a.test" : {
|
"a.test" : {
|
||||||
|
@ -214,7 +217,7 @@ describe('The icinga module bootstrap', function() {
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
tearDownTestDOM();
|
tearDownTestDOM();
|
||||||
require(BASE+"icinga/icinga.js");
|
requireNew("icinga/icinga.js");
|
||||||
var icinga = rjsmock.getDefine();
|
var icinga = rjsmock.getDefine();
|
||||||
$('body').append($("<a class='test'></a>"));
|
$('body').append($("<a class='test'></a>"));
|
||||||
$('a.test').click();
|
$('a.test').click();
|
||||||
|
@ -223,4 +226,55 @@ describe('The icinga module bootstrap', function() {
|
||||||
should.equal(icinga.getFailedModules()[0].name, "test2");
|
should.equal(icinga.getFailedModules()[0].name, "test2");
|
||||||
tearDownTestDOM();
|
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.
|
* to console.
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
|
var path = require('path');
|
||||||
var registeredDependencies = {};
|
var registeredDependencies = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,10 +22,12 @@ var registeredDependencies = {};
|
||||||
* in dependencies and calls fn with them as the parameter
|
* in dependencies and calls fn with them as the parameter
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
|
var debug = false;
|
||||||
var requireJsMock = function(dependencies, fn) {
|
var requireJsMock = function(dependencies, fn) {
|
||||||
var fnArgs = [];
|
var fnArgs = [];
|
||||||
for (var i=0;i<dependencies.length;i++) {
|
for (var i=0;i<dependencies.length;i++) {
|
||||||
if (typeof registeredDependencies[dependencies[i]] === "undefined") {
|
if (typeof registeredDependencies[dependencies[i]] === "undefined") {
|
||||||
|
if (debug === true)
|
||||||
console.warn("Unknown dependency "+dependencies[i]+" in define()");
|
console.warn("Unknown dependency "+dependencies[i]+" in define()");
|
||||||
}
|
}
|
||||||
fnArgs.push(registeredDependencies[dependencies[i]]);
|
fnArgs.push(registeredDependencies[dependencies[i]]);
|
||||||
|
@ -55,7 +58,7 @@ var defineMock = function() {
|
||||||
var argList = arguments[currentArg];
|
var argList = arguments[currentArg];
|
||||||
fn = arguments[currentArg+1];
|
fn = arguments[currentArg+1];
|
||||||
for (var i=0;i<argList.length;i++) {
|
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()");
|
console.warn("Unknown dependency "+argList[i]+" in define()");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +94,6 @@ initRequireMethods();
|
||||||
function purgeDependencies() {
|
function purgeDependencies() {
|
||||||
registeredDependencies = {
|
registeredDependencies = {
|
||||||
'jquery' : GLOBAL.$,
|
'jquery' : GLOBAL.$,
|
||||||
'__define__' : registeredDependencies.__define__,
|
|
||||||
'logging' : console
|
'logging' : console
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -107,6 +109,12 @@ function registerDependencies(obj) {
|
||||||
registeredDependencies[name] = obj[name];
|
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
|
* The API for this module
|
||||||
|
@ -122,3 +130,4 @@ module.exports = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue