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:
Jannis Moßhammer 2013-06-21 15:33:06 +02:00 committed by Marius Hein
parent 33b4a2ced0
commit 5e5b301d0d
4 changed files with 99 additions and 11 deletions

View File

@ -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()

View File

@ -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,

View File

@ -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();
});

View File

@ -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();
});
});