diff --git a/public/js/icinga/icinga.js b/public/js/icinga/icinga.js index a9791d9d0..9be6b9013 100755 --- a/public/js/icinga/icinga.js +++ b/public/js/icinga/icinga.js @@ -1,27 +1,25 @@ /*global Icinga:false, document: false, define:false require:false base_url:false console:false */ define([ 'jquery', - 'vendor/jquery.sparkline.min', 'logging', - 'icinga/behaviour', + 'icinga/module', 'icinga/util/async', 'icinga/container', 'modules/list' -], function ($,sparkline,log,behaviour,async,containerMgr, modules) { +], function ($, log, moduleMgr, async, containerMgr, modules) { 'use strict'; /** * Icinga prototype */ var Icinga = function() { - var internalBehaviours = ['icinga/behaviour/actionTable','icinga/behaviour/mainDetail']; + var internalModules = ['icinga/modules/actionTable','icinga/modules/mainDetail']; this.modules = {}; var failedModules = []; var initialize = function () { - require(['modules/list']); - enableDefaultBehaviour(); + enableInternalModules(); containerMgr.registerAsyncMgr(async); containerMgr.initializeContainers(document); @@ -30,9 +28,20 @@ define([ enableModules(); }; - var enableDefaultBehaviour = function() { - $.each(internalBehaviours,function(idx,behaviourImpl) { - behaviour.enableBehaviour(behaviourImpl,log.error); + + + var enableInternalModules = function() { + $.each(internalModules,function(idx,module) { + moduleMgr.enableModule(module, log.error); + }); + }; + + var loadModuleScript = function(name) { + moduleMgr.enableModule("modules/"+name, function(error) { + failedModules.push({ + name: name, + errorMessage: error + }); }); }; @@ -40,17 +49,10 @@ define([ moduleList = moduleList || modules; $.each(modules,function(idx,module) { - if(module.behaviour) { - behaviour.enableBehaviour(module.name+"/"+module.name,function(error) { - failedModules.push({name: module.name,errorMessage: error}); - }); - } + loadModuleScript(module.name); }); }; - var enableCb = function(behaviour) { - behaviour.enable(); - }; $(document).ready(initialize.bind(this)); @@ -60,10 +62,14 @@ define([ */ loadModule: function(blubb,bla) { behaviour.registerBehaviour(blubb,bla); - } , + }, loadIntoContainer: function(ctr) { + }, + + getFailedModules: function() { + return failedModules; } }; diff --git a/public/js/icinga/modules/actionTable.js b/public/js/icinga/modules/actionTable.js new file mode 100644 index 000000000..e8e61952b --- /dev/null +++ b/public/js/icinga/modules/actionTable.js @@ -0,0 +1,118 @@ +/*global Icinga:false, document: false, define:false require:false base_url:false console:false */ + +/** + * ActionTable behaviour as described in + * https://wiki.icinga.org/display/cranberry/Frontend+Components#FrontendComponents-ActionTable + * + * @TODO: Row selection + */ +define(['jquery','logging','icinga/util/async'],function($,log,async) { + "use strict"; + + var ActionTableBehaviour = function() { + var onTableHeaderClick; + + var TABLE_BASE_MATCHER = '.icinga-container table.action'; + var linksInActionTable = TABLE_BASE_MATCHER+" tbody tr > a"; + var actionTableRow = TABLE_BASE_MATCHER+" tbody tr"; + var headerRow = TABLE_BASE_MATCHER+" > th a"; + var searchField = ".icinga-container .actiontable.controls input[type=search]"; + + + onTableHeaderClick = function (ev) { + var target = ev.currentTarget, + href = $(target).attr('href'), + destination; + if ($(target).parents('.layout-main-detail').length) { + if ($(target).parents("#icinga-main").length) { + destination = 'icinga-main'; + } + else { + destination = 'icinga-detail'; + } + + } else { + destination = 'icinga-main'; + } + async.loadToTarget(destination, href); + ev.preventDefault(); + ev.stopImmediatePropagation(); + return false; + }; + + var onLinkTagClick = function(ev) { + + var target = ev.currentTarget, + href = $(target).attr('href'), + destination; + if ($(target).parents('.layout-main-detail').length) { + destination = 'icinga-detail'; + } else { + destination = 'icinga-main'; + } + async.loadToTarget(destination,href); + ev.preventDefault(); + ev.stopImmediatePropagation(); + return false; + + }; + + var onTableRowClick = function(ev) { + ev.stopImmediatePropagation(); + + var target = $(ev.currentTarget), + href = target.attr('href'), + destination; + $('tr.active',target.parent('tbody').first()).removeClass("active"); + target.addClass('active'); + + // When the tr has a href, act like it is a link + if(href) { + ev.currentTarget = target.first(); + return onLinkTagClick(ev); + } + // Try to find a designated row action + var links = $("a.row-action",target); + if(links.length) { + ev.currentTarget = links.first(); + return onLinkTagClick(ev); + } + + // otherwise use the first anchor tag + links = $("a",target); + if(links.length) { + ev.currentTarget = links.first(); + return onLinkTagClick(ev); + } + + log.debug("No target for this table row found"); + return false; + }; + + var onSearchInput = function(ev) { + ev.stopImmediatePropagation(); + var txt = $(this).val(); + }; + + this.eventHandler = {}; + this.eventHandler[linksInActionTable] = { + 'click' : onLinkTagClick + }; + this.eventHandler[actionTableRow] = { + 'click' : onTableRowClick + }; + this.eventHandler[headerRow] = { + 'click' : onTableHeaderClick + }; + this.eventHandler[searchField] = { + 'keyup' : onSearchInput + }; + + this.enable = function() { + + }; + }; + + return new ActionTableBehaviour(); + +}); \ No newline at end of file diff --git a/public/js/icinga/modules/mainDetail.js b/public/js/icinga/modules/mainDetail.js new file mode 100755 index 000000000..9a207c933 --- /dev/null +++ b/public/js/icinga/modules/mainDetail.js @@ -0,0 +1,94 @@ +/*global Icinga:false, document: false, define:false require:false base_url:false console:false */ + +/** + * Main-Detail layout behaviour as described in + * https://wiki.icinga.org/display/cranberry/Frontend+Components#FrontendComponents-Behaviour + * + */ +define(['jquery','logging','icinga/util/async'],function($,log,async) { + "use strict"; + + var MainDetailBehaviour = function() { + + var onOuterLinkClick = function(ev) { + var a = $(ev.currentTarget), + target = a.attr("target"), + href = a.attr("href"); + ev.stopImmediatePropagation(); + collapseDetailView(); + async.loadToTarget("icinga-main",href); + return false; + }; + + var onLinkTagClick = function(ev) { + + var a = $(ev.currentTarget), + target = a.attr("target"), + href = a.attr("href"); + + // check for protocol:// + if(/^[A-Z]{2,10}\:\/\//i.test(href)) { + window.open(href); + ev.stopImmediatePropagation(); + return false; + } + + // check for link in table header + if(a.parents('th').length > 0) { + ev.stopImmediatePropagation(); + return false; + } + + if(typeof target === "undefined") { + if(a.parents("#icinga-detail").length) { + async.loadToTarget("icinga-detail",href); + } else { + async.loadToTarget("icinga-main",href); + } + } else { + switch(target) { + case "main": + async.loadToTarget("icinga-main",href); + collapseDetailView(); + break; + case "detail": + async.loadToTarget("icinga-detail",href); + break; + case "popup": + async.loadToTarget(null,href); + break; + default: + return true; + } + } + ev.stopImmediatePropagation(); + return false; + }; + + var expandDetailView = function() { + $("#icinga-detail").parents(".collapsed").removeClass('collapsed'); + }; + + var collapseDetailView = function(elementInDetailView) { + $("#icinga-detail").parents(".layout-main-detail").addClass('collapsed'); + }; + + this.expandDetailView = expandDetailView; + this.collapseDetailView = collapseDetailView; + + this.eventHandler = { + '.layout-main-detail * a' : { + 'click' : onLinkTagClick + }, + 'a' : { + 'click' : onOuterLinkClick + }, + '.layout-main-detail .icinga-container#icinga-detail' : { + 'focus' : expandDetailView + } + }; + }; + + return new MainDetailBehaviour(); +}); + diff --git a/test/js/test/icinga/moduleTest.js b/test/js/test/icinga/moduleTest.js index da89f4558..6a446b5f4 100644 --- a/test/js/test/icinga/moduleTest.js +++ b/test/js/test/icinga/moduleTest.js @@ -14,7 +14,7 @@ var BASE = "../../../../public/js/"; require(BASE+"icinga/module.js"); var module = rjsmock.getDefine(); - +GLOBAL.document = $('body'); /** * Test module that only uses eventhandlers and * no custom logic @@ -186,5 +186,41 @@ describe('Module loader', function() { tearDownTestDOM(); $('body').unbind(); }); - +}); + + +describe('The icinga module bootstrap', function() { + it("Should automatically load all enabled modules", function() { + rjsmock.purgeDependencies(); + var testClick = false; + rjsmock.registerDependencies({ + "icinga/module": module, + "modules/test" : { + eventHandler: { + "a.test" : { + click : function() { + testClick = true; + } + } + } + }, + "icinga/container" : { + registerAsyncMgr: function() {}, + initializeContainers: function() {} + }, + "modules/list" : [ + { name: 'test' }, + { name: 'test2'} // this one fails + ] + }); + tearDownTestDOM(); + require(BASE+"icinga/icinga.js"); + var icinga = rjsmock.getDefine(); + $('body').append($("")); + $('a.test').click(); + should.equal(testClick, true, "Module wasn't automatically loaded!"); + icinga.getFailedModules().should.have.length(1); + should.equal(icinga.getFailedModules()[0].name, "test2"); + tearDownTestDOM(); + }); });