From 95e4ebb76ac7c5953aa966af191a5f3e03d04944 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jannis=20Mo=C3=9Fhammer?= <jannis.mosshammer@netways.de>
Date: Thu, 13 Jun 2013 17:35:31 +0200
Subject: [PATCH 1/3] Add javascript files (all untested)

This commit just adds the javascript files from the incubator,
even if not fully functional.

refs #3753
---
 public/js/icinga/icinga.js                   | 365 -------------------
 public/js/icinga/module.js                   | 100 +++++
 public/js/vendor/namespaced_requirejs.min.js |  47 +++
 3 files changed, 147 insertions(+), 365 deletions(-)
 create mode 100644 public/js/icinga/module.js
 create mode 100644 public/js/vendor/namespaced_requirejs.min.js

diff --git a/public/js/icinga/icinga.js b/public/js/icinga/icinga.js
index d26c6f0cd..a9791d9d0 100755
--- a/public/js/icinga/icinga.js
+++ b/public/js/icinga/icinga.js
@@ -72,368 +72,3 @@ define([
 });
 
 
-
-        /**
-         *
-         */
-    /*    prepareContainers: function ()
-        {
-            $('#icinga-main').attr(
-                'icingaurl',
-                window.location.pathname + window.location.search
-            );
-            return Icinga;
-        },
-
-        initializeTimer: function (interval)
-        {
-            // This currently messes up the frontend
-            //Icinga.refresher = setInterval(Icinga.timer, interval);
-            return Icinga;
-        },
-
-        module: function (name)
-        {
-            return Icinga.modules[name];
-        },
-
-        timer: function ()
-        {
-            $('.icinga-container[icingaurl]').each(function (idx, el)
-            {
-                el = $(el);
-                // TODO: Find a better solution for this problem
-                if (el.find('.icinga-container').length) {
-                    return;
-                }
-                Icinga.loadUrl(el.attr('icingaurl'), el, el.data('icingaparams'));
-            });
-        },*/
-
-
-        /**
-         * Clicks on action table rows shall trigger the first link action found
-         * in that row
-         */
-     /*   prepareActionRow: function (idx, el)
-        {
-            var a = $(el).find('a.row-action'),
-                tr;
-            if (a.length < 1) {
-                a = $(el).find('a');
-            }
-            if (a.length < 1) { return; }
-            tr = a.closest('tr');
-            tr.attr('href', a.first().attr('href'));
-            // $(a).first().replaceWith($(a).html());
-            // tr.addClass('hasHref');
-        },*/
-
-        /**
-         * Apply event handlers within a given parent node
-         */
-  /*      applyEventHandlers: function (parent)
-        {
-            // Icinga.debug('Applying event handlers');
-            $('.dashboard.icinga-container', parent).each(function(idx, el) {
-                Icinga.loadUrl($(el).attr('icingaurl'), $(el));
-            });
-            $('div[icingamodule]', parent).each(function (idx, el) {
-                Icinga.loadModule($(el).attr('icingamodule'), el);
-            });
-            $('table.action tbody tr', parent).each(Icinga.prepareActionRow);
-<<<<<<< HEAD:public/js/icinga/icinga.js
-            $('a[title], span[title], td[title], img[title]', parent).qtip()
-                .each(Icinga.fixQtippedElement);*/
-/*            $('.inlinepie', parent).sparkline('html', {
-=======
-            $('*[title]').tooltip({placement:"bottom",container: "body"});
-            $('.inlinepie', parent).sparkline('html', {
->>>>>>> 3845f64dd2380974633fad30c3160944e9bff1d2:public/js/icinga.js
-                type:        'pie',
-                sliceColors: ['#0c0', '#f80', '#c00', '#dcd'],
-                width:       '16px',
-                height:      '16px'
-            });*//*
-            $('.inlinebullet', parent).sparkline('html', {
-                type:        'bullet',
-                targetColor: '#000',
-                rangeColors: ['#0c0', '#f80', '#c00'],
-                width:       '16px',
-                height:      '16px'
-            });
-            // Icinga.debug('Applying event handlers done');
-            return Icinga;
-        },
-*/
-        /**
-         * Apply some global event handlers
-         */
-  /*      applyGlobalEventHandlers: function ()
-        {
-            // TODO: replace document with deeper parent elements once they are
-            //       defined
-            $(document).on('click', 'a', Icinga.linkClicked);
-            $(document).on('click', 'tr[href]', Icinga.linkClicked);
-            $(document).on('keyup', 'form.auto input', Icinga.formChangeDelayed);
-            $(document).on('change', 'form.auto input', Icinga.formChanged);
-            $(document).on('change', 'form.auto select', Icinga.formChanged);
-            $(document).on('submit', 'form', Icinga.submitForm);
-            return Icinga;
-        },
-        applyModuleEventHandlers: function (name, el)
-        {
-            Icinga.debug('Applying handlers for ' + name);
-            $.each(Icinga.moduleHandlers[name], function (idx, event) {
-                if (event[1] === 'each') {
-                    $(event[0], $(el)).each(event[2]);
-                } else {
-                    $(event[0], $(el)).on(event[1], event[2]);
-                }
-            });
-        },
-        loadModule: function (name, el)
-        {
-            if (typeof Icinga.modules[name] !== 'undefined')
-            {
-                Icinga.applyModuleEventHandlers(name, $(el));
-                return;
-            }
-            try {
-                require([name], function (module) {
-                    Icinga.modules[name] = module;
-                    Icinga.moduleHandlers[name] = [];
-                    if (typeof module.eventHandlers !== 'undefined') {
-                        $.each(module.eventHandlers, function (filter, events) {
-                            $.each(events, function (event, handle) {
-                                var eventHandle = Icinga.modules[name][handle];
-                                Icinga.moduleHandlers[name].push([
-                                    filter,
-                                    event,
-                                    eventHandle
-                                ]);
-                            });
-                        });
-                    }
-                    Icinga.applyModuleEventHandlers(name, $(el));
-                    Icinga.debug('Module "' + name + '" has been loaded');
-                });
-            } catch (e) {
-                Icinga.debug(
-                    'Loading module "' + name + '" failed: ' +
-                        e.name + "\n" + e.message
-                );
-                return;
-            }
-        },
-*/
-        /**
-         * Load CSS file if not already done so.
-         *
-         * This is an unfinished prototype, we have to make sure to not load
-         * the same file multiple times
-         *
-         * TODO: Finish it, remember/discover what has been loaded, to it the
-         *       jQuery way
-         */
- /*       requireCss: function (url)
-        {
-            Icinga.debug('Should load CSS file: ' + url);
-        //    var link = document.createElement("link");
-        //    link.type = "text/css";
-        //    link.rel = "stylesheet";
-        //    link.href = url;
-        //    document.getElementsByTagName("head")[0].appendChild(link);
-        },
-*/
-        /**
-         * Load the given URL to the given target
-         *
-         * @param {string} url    URL to be loaded
-         * @param {object} target Target element
-         */
-  /*      loadUrl: function (url, target, data)
-        {
-            var req = $.ajax({
-                type   : 'POST',
-                url    :  url,
-                data   : data,
-                headers: { 'X-Icinga-Accept': 'text/html' }
-            });
-            req.done(Icinga.handleResponse);
-            req.IcingaTarget = target;
-            req.fail(Icinga.handleFailure);
-            return req;
-        },
-*/
-        /**
-         * Create an URL relative to the Icinga base Url, still unused
-         *
-         * @param {string} url Relative url
-         */
-  /*      url: function (url)
-        {
-            return base_url + url;
-        },
-*/
-        /**
-         * Smoothly render given HTML to given container
-         */
-  /*      renderContentToContainer: function (content, container)
-        {
-            Icinga.disableQtips();
-            Icinga.debug('fire');
-            container.html(content);
-            if (container.attr('id') === 'icinga-detail') {
-                container.closest('.layout-main-detail').removeClass('collapsed');
-            }
-            Icinga.applyEventHandlers(container);
-
-        },
-*/
-        /**
-         * Handle successful XHR response
-         */
-  /*      handleResponse: function (data, textStatus, jqXHR)
-        {
-            Icinga.debug('Got response: ' + this.url);
-            jqXHR.IcingaTarget.attr('icingaurl', this.url);
-            Icinga.renderContentToContainer(jqXHR.responseText, jqXHR.IcingaTarget);
-        },
-*/
-        /**
-         * Handle failed XHR response
-         */
-  /*      handleFailure: function (jqXHR, textStatus, errorThrown)
-        {
-            if (jqXHR.status > 0) {
-                Icinga.debug(jqXHR.responseText.slice(0, 100));
-                Icinga.renderContentToContainer(
-                    '<h1>' + jqXHR.status + ' ' + errorThrown + '</h1> ' +
-                        jqXHR.responseText,
-                    jqXHR.IcingaTarget
-                );
-
-                // Header example:
-                // Icinga.debug(jqXHR.getResponseHeader('X-Icinga-Redirect'));
-            } else {
-                if (errorThrown === 'abort') {
-                    Icinga.debug('Request to ' + this.url + ' has been aborted');
-                } else {
-                    Icinga.debug('Failed to contact web server');
-                }
-            }
-        },
-*/
-        /**
-         * A link has been clicked. Try to find out it's target and fire the XHR
-         * request
-         */
-  /*      linkClicked: function (event)
-        {
-            event.stopPropagation();
-            var target = event.currentTarget,
-                href = $(target).attr('href'),
-                destination;
-
-            if ($(target).closest('.pagination').length) {
-                Icinga.debug('Pagination link clicked');
-                destination = $(target).closest('.icinga-container');
-            } else if ($(target).closest('.nav-tabs').length) {
-                Icinga.debug('Nav tab link clicked');
-                destination = $(target).closest('.icinga-container');
-            } else if ($(target).closest('table.action').length ||
-                // TODO: define just one class name instead of this list
-                $(target).closest('table.pivot').length ||
-                $(target).closest('.bpapp').length ||
-                $(target).closest('.dashboard.icinga-container').length)
-            {
-                destination = $('#icinga-detail');
-                Icinga.debug('Clicked an action table / pivot / bpapp / dashboard link');
-            } else  {
-                Icinga.debug('Something else clicked');
-                // target = $(target).closest('.icinga-container');
-                destination = $(target).closest('.icinga-container');
-                if (!destination.length) {
-                    destination = $('#icinga-main');
-                }
-            }
-
-            Icinga.loadUrl(href, destination);
-            return false;
-        },
-*/
-        /* BEGIN form handling, still unfinished */
-  /*      formChanged: function (event)
-        {
-            // TODO: event.preventDefault();
-            // TODO: event.stopPropatagion();
-            if (Icinga.load_form !== false) {
-                // Already loading. TODO: make it multi-form-aware
-                // TODO: shorter timeout, but clear not before ajax call finished
-                return;
-            }
-            var target = event.currentTarget,
-                form   = $(target).closest('form');
-            Icinga.load_form = form;
-            Icinga.fireFormLoader();
-        },
-
-        formChangeDelayed: function (event)
-        {
-            if (Icinga.load_form !== false) {
-                // Already loading. TODO: make it multi-form-aware
-                // TODO: shorter timeout, but clear not before ajax call finished
-                return;
-            }
-            var target = event.currentTarget,
-                form   = $(target).closest('form');
-            Icinga.load_form = form;
-            setTimeout(Icinga.fireFormLoader, 200);
-        },
-
-        fireFormLoader: function ()
-        {
-            if (Icinga.load_form === false) {
-                return;
-            }
-            // Temporarily hardcoded for top search:
-            // Icinga.loadUrl(Icinga.load_form.attr('action') + '?' + Icinga.load_form.serialize(), $('#icinga-main'));
-            Icinga.debug(Icinga.load_form);
-            if ($('#icinga-main').find('.dashboard.icinga-container').length) {
-                $('#icinga-main .dashboard.icinga-container').each(function (idx, el) {
-                    Icinga.loadSearch($(el));
-                });
-            } else {
-                $('#icinga-main').each(function (idx, el) {
-                    Icinga.loadSearch($(el));
-                });
-            }
-            Icinga.load_form = false;
-
-        },
-        loadSearch: function(target)
-        {
-            var url = target.attr('icingaurl');
-            var params = url.split('?')[1] || '';
-            url = url.split('?')[0];
-            var param_list = params.split('&');
-            params = {};
-            var i, pairs;
-            for (i = 0; i < param_list.length; i++) {
-                pairs = param_list[i].split('=');
-                params[pairs[0]] = pairs[1]; 
-            }
-            var searchstring = $('input[name=search]', Icinga.load_form).val();
-            params['search'] = searchstring || '';
-            params = '?' + $.param(params);
-            Icinga.loadUrl(url + params, target);
-        },
-        submitForm: function (event)
-        {
-            event.stopPropagation();
-            var form = $(event.currentTarget);
-            Icinga.loadUrl(form.attr('action'), $('#icinga-main'), form.serializeArray());
-            return false;
-        },*/
-        /* END of form handling */
diff --git a/public/js/icinga/module.js b/public/js/icinga/module.js
new file mode 100644
index 000000000..6d0ac6080
--- /dev/null
+++ b/public/js/icinga/module.js
@@ -0,0 +1,100 @@
+/*global Icinga:false, $: false, document: false, define:false requirejs:false base_url:false console:false */
+
+/**
+ This prototype encapsulates the modules registered in the module folder
+ **/
+(function() {
+    "use strict";
+
+    var loaded = {};
+
+    define(['logging'],function(log) {
+
+        var registerModuleFunctions = function(module) {
+            var enableFn = module.enable, disableFn = module.disable;
+            
+            module.enable = (function(root) {
+                root = root || $('body');
+                for (var jqMatcher in this.eventHandler) {
+                    for (var ev in this.eventHandler[jqMatcher]) {
+                        log.debug("Registered module: ", "'"+ev+"'", jqMatcher);
+                        $(root).on(ev,jqMatcher,this.eventHandler[jqMatcher][ev]);
+                    }
+                }
+                if(enableFn) {
+                    enableFn.apply(this,arguments);
+                }
+            }).bind(module);
+
+            module.disable = (function(root) {
+                root = root || $('body');
+                for (var jqMatcher in this.eventHandler) {
+                    for (var ev in this.eventHandler[jqMatcher]) {
+                        log.debug("Unregistered module: ", "'"+ev+"'", jqMatcher);
+                        $(root).off(ev,jqMatcher,this.eventHandler[jqMatcher][ev]);
+                    }
+                }
+                if (disableFn) {
+                    disableFn.apply(this,arguments);
+                }
+            }).bind(module);
+
+
+        };
+
+        var CallInterface = function() {
+
+            /**
+             * Loads a module and calls successCallback with the module as the parameter on success, otherwise
+             * the errorCallback with the errorstring as the first parameter
+             *
+             * @param name
+             *  @param errorCallback
+             * @param successCallback
+             */
+            this.enableModule = function(name,errorCallback,successCallback) {
+                requirejs([name],function(module) {
+                    if (typeof module === "undefined") {
+                        return errorCallback(new Error("Unknown module: "+name));
+                    }
+                        
+                    if (typeof module.eventHandler === "object") {
+                        registerModuleFunctions(module);
+                    }
+                    if (typeof module.enable === "function") {
+                        module.enable();
+                    }
+                    loaded[name] = {
+                        module: module,
+                        active: true
+                    };
+                    if (typeof successCallback === "function") {
+                        successCallback(module);
+                    }
+                },function(err) {
+                    errorCallback("Could not load module "+name+" "+err,err);
+                });
+            };
+
+            this.disableModule = function(name) {
+                if(loaded[name] && loaded[name].active) {
+                    loaded[name].module.disable();
+                }
+            };
+            
+            /**
+            * This should *ONLY* be called in testcases
+            **/
+            this.resetHard = function() {
+                if (typeof describe !== "function") {
+                    return; 
+                }
+                loaded = {};
+            };
+        };
+
+        
+        return new CallInterface();
+    });
+
+})();
diff --git a/public/js/vendor/namespaced_requirejs.min.js b/public/js/vendor/namespaced_requirejs.min.js
new file mode 100644
index 000000000..dcc93072f
--- /dev/null
+++ b/public/js/vendor/namespaced_requirejs.min.js
@@ -0,0 +1,47 @@
+var requirejs = (function () {
+    //Define a require object here that has any
+    //default configuration you want for RequireJS. If
+    //you do not have any config options you want to set,
+    //just use an simple object literal, {}. You may need
+    //to at least set baseUrl.
+    var require = {
+        baseUrl: '..'
+    };
+    /*
+     RequireJS 2.1.6 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
+     Available via the MIT or new BSD license.
+     see: http://github.com/jrburke/requirejs for details
+    */
+    var requirejs,require,define;
+    (function(ba){function J(b){return"[object Function]"===N.call(b)}function K(b){return"[object Array]"===N.call(b)}function z(b,c){if(b){var d;for(d=0;d<b.length&&(!b[d]||!c(b[d],d,b));d+=1);}}function O(b,c){if(b){var d;for(d=b.length-1;-1<d&&(!b[d]||!c(b[d],d,b));d-=1);}}function t(b,c){return ha.call(b,c)}function m(b,c){return t(b,c)&&b[c]}function H(b,c){for(var d in b)if(t(b,d)&&c(b[d],d))break}function S(b,c,d,m){c&&H(c,function(c,l){if(d||!t(b,l))m&&"string"!==typeof c?(b[l]||(b[l]={}),S(b[l],
+    c,d,m)):b[l]=c});return b}function v(b,c){return function(){return c.apply(b,arguments)}}function ca(b){throw b;}function da(b){if(!b)return b;var c=ba;z(b.split("."),function(b){c=c[b]});return c}function B(b,c,d,m){c=Error(c+"\nhttp://requirejs.org/docs/errors.html#"+b);c.requireType=b;c.requireModules=m;d&&(c.originalError=d);return c}function ia(b){function c(a,f,C){var e,n,b,c,d,T,k,g=f&&f.split("/");e=g;var l=j.map,h=l&&l["*"];if(a&&"."===a.charAt(0))if(f){e=m(j.pkgs,f)?g=[f]:g.slice(0,g.length-
+    1);f=a=e.concat(a.split("/"));for(e=0;f[e];e+=1)if(n=f[e],"."===n)f.splice(e,1),e-=1;else if(".."===n)if(1===e&&(".."===f[2]||".."===f[0]))break;else 0<e&&(f.splice(e-1,2),e-=2);e=m(j.pkgs,f=a[0]);a=a.join("/");e&&a===f+"/"+e.main&&(a=f)}else 0===a.indexOf("./")&&(a=a.substring(2));if(C&&l&&(g||h)){f=a.split("/");for(e=f.length;0<e;e-=1){b=f.slice(0,e).join("/");if(g)for(n=g.length;0<n;n-=1)if(C=m(l,g.slice(0,n).join("/")))if(C=m(C,b)){c=C;d=e;break}if(c)break;!T&&(h&&m(h,b))&&(T=m(h,b),k=e)}!c&&
+    T&&(c=T,d=k);c&&(f.splice(0,d,c),a=f.join("/"))}return a}function d(a){A&&z(document.getElementsByTagName("script"),function(f){if(f.getAttribute("data-requiremodule")===a&&f.getAttribute("data-requirecontext")===k.contextName)return f.parentNode.removeChild(f),!0})}function p(a){var f=m(j.paths,a);if(f&&K(f)&&1<f.length)return d(a),f.shift(),k.require.undef(a),k.require([a]),!0}function g(a){var f,b=a?a.indexOf("!"):-1;-1<b&&(f=a.substring(0,b),a=a.substring(b+1,a.length));return[f,a]}function l(a,
+    f,b,e){var n,D,i=null,d=f?f.name:null,l=a,h=!0,j="";a||(h=!1,a="_@r"+(N+=1));a=g(a);i=a[0];a=a[1];i&&(i=c(i,d,e),D=m(r,i));a&&(i?j=D&&D.normalize?D.normalize(a,function(a){return c(a,d,e)}):c(a,d,e):(j=c(a,d,e),a=g(j),i=a[0],j=a[1],b=!0,n=k.nameToUrl(j)));b=i&&!D&&!b?"_unnormalized"+(O+=1):"";return{prefix:i,name:j,parentMap:f,unnormalized:!!b,url:n,originalName:l,isDefine:h,id:(i?i+"!"+j:j)+b}}function s(a){var f=a.id,b=m(q,f);b||(b=q[f]=new k.Module(a));return b}function u(a,f,b){var e=a.id,n=m(q,
+    e);if(t(r,e)&&(!n||n.defineEmitComplete))"defined"===f&&b(r[e]);else if(n=s(a),n.error&&"error"===f)b(n.error);else n.on(f,b)}function w(a,f){var b=a.requireModules,e=!1;if(f)f(a);else if(z(b,function(f){if(f=m(q,f))f.error=a,f.events.error&&(e=!0,f.emit("error",a))}),!e)h.onError(a)}function x(){U.length&&(ja.apply(I,[I.length-1,0].concat(U)),U=[])}function y(a){delete q[a];delete W[a]}function G(a,f,b){var e=a.map.id;a.error?a.emit("error",a.error):(f[e]=!0,z(a.depMaps,function(e,c){var d=e.id,
+    g=m(q,d);g&&(!a.depMatched[c]&&!b[d])&&(m(f,d)?(a.defineDep(c,r[d]),a.check()):G(g,f,b))}),b[e]=!0)}function E(){var a,f,b,e,n=(b=1E3*j.waitSeconds)&&k.startTime+b<(new Date).getTime(),c=[],i=[],g=!1,l=!0;if(!X){X=!0;H(W,function(b){a=b.map;f=a.id;if(b.enabled&&(a.isDefine||i.push(b),!b.error))if(!b.inited&&n)p(f)?g=e=!0:(c.push(f),d(f));else if(!b.inited&&(b.fetched&&a.isDefine)&&(g=!0,!a.prefix))return l=!1});if(n&&c.length)return b=B("timeout","Load timeout for modules: "+c,null,c),b.contextName=
+    k.contextName,w(b);l&&z(i,function(a){G(a,{},{})});if((!n||e)&&g)if((A||ea)&&!Y)Y=setTimeout(function(){Y=0;E()},50);X=!1}}function F(a){t(r,a[0])||s(l(a[0],null,!0)).init(a[1],a[2])}function L(a){var a=a.currentTarget||a.srcElement,b=k.onScriptLoad;a.detachEvent&&!Z?a.detachEvent("onreadystatechange",b):a.removeEventListener("load",b,!1);b=k.onScriptError;(!a.detachEvent||Z)&&a.removeEventListener("error",b,!1);return{node:a,id:a&&a.getAttribute("data-requiremodule")}}function M(){var a;for(x();I.length;){a=
+    I.shift();if(null===a[0])return w(B("mismatch","Mismatched anonymous define() module: "+a[a.length-1]));F(a)}}var X,$,k,P,Y,j={waitSeconds:7,baseUrl:"./",paths:{},pkgs:{},shim:{},config:{}},q={},W={},aa={},I=[],r={},V={},N=1,O=1;P={require:function(a){return a.require?a.require:a.require=k.makeRequire(a.map)},exports:function(a){a.usingExports=!0;if(a.map.isDefine)return a.exports?a.exports:a.exports=r[a.map.id]={}},module:function(a){return a.module?a.module:a.module={id:a.map.id,uri:a.map.url,config:function(){var b=
+    m(j.pkgs,a.map.id);return(b?m(j.config,a.map.id+"/"+b.main):m(j.config,a.map.id))||{}},exports:r[a.map.id]}}};$=function(a){this.events=m(aa,a.id)||{};this.map=a;this.shim=m(j.shim,a.id);this.depExports=[];this.depMaps=[];this.depMatched=[];this.pluginMaps={};this.depCount=0};$.prototype={init:function(a,b,c,e){e=e||{};if(!this.inited){this.factory=b;if(c)this.on("error",c);else this.events.error&&(c=v(this,function(a){this.emit("error",a)}));this.depMaps=a&&a.slice(0);this.errback=c;this.inited=
+    !0;this.ignore=e.ignore;e.enabled||this.enabled?this.enable():this.check()}},defineDep:function(a,b){this.depMatched[a]||(this.depMatched[a]=!0,this.depCount-=1,this.depExports[a]=b)},fetch:function(){if(!this.fetched){this.fetched=!0;k.startTime=(new Date).getTime();var a=this.map;if(this.shim)k.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],v(this,function(){return a.prefix?this.callPlugin():this.load()}));else return a.prefix?this.callPlugin():this.load()}},load:function(){var a=
+    this.map.url;V[a]||(V[a]=!0,k.load(this.map.id,a))},check:function(){if(this.enabled&&!this.enabling){var a,b,c=this.map.id;b=this.depExports;var e=this.exports,n=this.factory;if(this.inited)if(this.error)this.emit("error",this.error);else{if(!this.defining){this.defining=!0;if(1>this.depCount&&!this.defined){if(J(n)){if(this.events.error&&this.map.isDefine||h.onError!==ca)try{e=k.execCb(c,n,b,e)}catch(d){a=d}else e=k.execCb(c,n,b,e);this.map.isDefine&&((b=this.module)&&void 0!==b.exports&&b.exports!==
+    this.exports?e=b.exports:void 0===e&&this.usingExports&&(e=this.exports));if(a)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",w(this.error=a)}else e=n;this.exports=e;if(this.map.isDefine&&!this.ignore&&(r[c]=e,h.onResourceLoad))h.onResourceLoad(k,this.map,this.depMaps);y(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=
+    !0)}}else this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,d=l(a.prefix);this.depMaps.push(d);u(d,"defined",v(this,function(e){var n,d;d=this.map.name;var g=this.map.parentMap?this.map.parentMap.name:null,C=k.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(e.normalize&&(d=e.normalize(d,function(a){return c(a,g,!0)})||""),e=l(a.prefix+"!"+d,this.map.parentMap),u(e,"defined",v(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),
+    d=m(q,e.id)){this.depMaps.push(e);if(this.events.error)d.on("error",v(this,function(a){this.emit("error",a)}));d.enable()}}else n=v(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),n.error=v(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];H(q,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&y(a.map.id)});w(a)}),n.fromText=v(this,function(e,c){var d=a.name,g=l(d),i=Q;c&&(e=c);i&&(Q=!1);s(g);t(j.config,b)&&(j.config[d]=j.config[b]);try{h.exec(e)}catch(D){return w(B("fromtexteval",
+    "fromText eval for "+b+" failed: "+D,D,[b]))}i&&(Q=!0);this.depMaps.push(g);k.completeLoad(d);C([d],n)}),e.load(a.name,C,n,j)}));k.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){W[this.map.id]=this;this.enabling=this.enabled=!0;z(this.depMaps,v(this,function(a,b){var c,e;if("string"===typeof a){a=l(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=m(P,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;u(a,"defined",v(this,function(a){this.defineDep(b,
+    a);this.check()}));this.errback&&u(a,"error",v(this,this.errback))}c=a.id;e=q[c];!t(P,c)&&(e&&!e.enabled)&&k.enable(a,this)}));H(this.pluginMaps,v(this,function(a){var b=m(q,a.id);b&&!b.enabled&&k.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){z(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};k={config:j,contextName:b,registry:q,defined:r,urlFetched:V,defQueue:I,Module:$,makeModuleMap:l,
+    nextTick:h.nextTick,onError:w,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=j.pkgs,c=j.shim,e={paths:!0,config:!0,map:!0};H(a,function(a,b){e[b]?"map"===b?(j.map||(j.map={}),S(j[b],a,!0,!0)):S(j[b],a,!0):j[b]=a});a.shim&&(H(a.shim,function(a,b){K(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=k.makeShimExports(a);c[b]=a}),j.shim=c);a.packages&&(z(a.packages,function(a){a="string"===typeof a?{name:a}:a;b[a.name]={name:a.name,
+    location:a.location||a.name,main:(a.main||"main").replace(ka,"").replace(fa,"")}}),j.pkgs=b);H(q,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=l(b))});if(a.deps||a.callback)k.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(ba,arguments));return b||a.exports&&da(a.exports)}},makeRequire:function(a,f){function d(e,c,g){var i,j;f.enableBuildCallback&&(c&&J(c))&&(c.__requireJsBuild=!0);if("string"===typeof e){if(J(c))return w(B("requireargs",
+    "Invalid require call"),g);if(a&&t(P,e))return P[e](q[a.id]);if(h.get)return h.get(k,e,a,d);i=l(e,a,!1,!0);i=i.id;return!t(r,i)?w(B("notloaded",'Module name "'+i+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):r[i]}M();k.nextTick(function(){M();j=s(l(null,a));j.skipMap=f.skipMap;j.init(e,c,g,{enabled:!0});E()});return d}f=f||{};S(d,{isBrowser:A,toUrl:function(b){var d,f=b.lastIndexOf("."),g=b.split("/")[0];if(-1!==f&&(!("."===g||".."===g)||1<f))d=b.substring(f,b.length),b=
+    b.substring(0,f);return k.nameToUrl(c(b,a&&a.id,!0),d,!0)},defined:function(b){return t(r,l(b,a,!1,!0).id)},specified:function(b){b=l(b,a,!1,!0).id;return t(r,b)||t(q,b)}});a||(d.undef=function(b){x();var c=l(b,a,!0),d=m(q,b);delete r[b];delete V[c.url];delete aa[b];d&&(d.events.defined&&(aa[b]=d.events),y(b))});return d},enable:function(a){m(q,a.id)&&s(a).enable()},completeLoad:function(a){var b,c,e=m(j.shim,a)||{},d=e.exports;for(x();I.length;){c=I.shift();if(null===c[0]){c[0]=a;if(b)break;b=!0}else c[0]===
+    a&&(b=!0);F(c)}c=m(q,a);if(!b&&!t(r,a)&&c&&!c.inited){if(j.enforceDefine&&(!d||!da(d)))return p(a)?void 0:w(B("nodefine","No define call for "+a,null,[a]));F([a,e.deps||[],e.exportsFn])}E()},nameToUrl:function(a,b,c){var d,g,l,i,k,p;if(h.jsExtRegExp.test(a))i=a+(b||"");else{d=j.paths;g=j.pkgs;i=a.split("/");for(k=i.length;0<k;k-=1)if(p=i.slice(0,k).join("/"),l=m(g,p),p=m(d,p)){K(p)&&(p=p[0]);i.splice(0,k,p);break}else if(l){a=a===l.name?l.location+"/"+l.main:l.location;i.splice(0,k,a);break}i=i.join("/");
+    i+=b||(/\?/.test(i)||c?"":".js");i=("/"===i.charAt(0)||i.match(/^[\w\+\.\-]+:/)?"":j.baseUrl)+i}return j.urlArgs?i+((-1===i.indexOf("?")?"?":"&")+j.urlArgs):i},load:function(a,b){h.load(k,a,b)},execCb:function(a,b,c,d){return b.apply(d,c)},onScriptLoad:function(a){if("load"===a.type||la.test((a.currentTarget||a.srcElement).readyState))R=null,a=L(a),k.completeLoad(a.id)},onScriptError:function(a){var b=L(a);if(!p(b.id))return w(B("scripterror","Script error for: "+b.id,a,[b.id]))}};k.require=k.makeRequire();
+    return k}var h,x,y,E,L,F,R,M,s,ga,ma=/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,na=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,fa=/\.js$/,ka=/^\.\//;x=Object.prototype;var N=x.toString,ha=x.hasOwnProperty,ja=Array.prototype.splice,A=!!("undefined"!==typeof window&&navigator&&window.document),ea=!A&&"undefined"!==typeof importScripts,la=A&&"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,Z="undefined"!==typeof opera&&"[object Opera]"===opera.toString(),G={},u={},U=[],Q=
+    !1;if("undefined"===typeof define){if("undefined"!==typeof requirejs){if(J(requirejs))return;u=requirejs;requirejs=void 0}"undefined"!==typeof require&&!J(require)&&(u=require,require=void 0);h=requirejs=function(b,c,d,p){var g,l="_";!K(b)&&"string"!==typeof b&&(g=b,K(c)?(b=c,c=d,d=p):b=[]);g&&g.context&&(l=g.context);(p=m(G,l))||(p=G[l]=h.s.newContext(l));g&&p.configure(g);return p.require(b,c,d)};h.config=function(b){return h(b)};h.nextTick="undefined"!==typeof setTimeout?function(b){setTimeout(b,
+    4)}:function(b){b()};require||(require=h);h.version="2.1.6";h.jsExtRegExp=/^\/|:|\?|\.js$/;h.isBrowser=A;x=h.s={contexts:G,newContext:ia};h({});z(["toUrl","undef","defined","specified"],function(b){h[b]=function(){var c=G._;return c.require[b].apply(c,arguments)}});if(A&&(y=x.head=document.getElementsByTagName("head")[0],E=document.getElementsByTagName("base")[0]))y=x.head=E.parentNode;h.onError=ca;h.load=function(b,c,d){var h=b&&b.config||{},g;if(A)return g=h.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml",
+    "html:script"):document.createElement("script"),g.type=h.scriptType||"text/javascript",g.charset="utf-8",g.async=!0,g.setAttribute("data-requirecontext",b.contextName),g.setAttribute("data-requiremodule",c),g.attachEvent&&!(g.attachEvent.toString&&0>g.attachEvent.toString().indexOf("[native code"))&&!Z?(Q=!0,g.attachEvent("onreadystatechange",b.onScriptLoad)):(g.addEventListener("load",b.onScriptLoad,!1),g.addEventListener("error",b.onScriptError,!1)),g.src=d,M=g,E?y.insertBefore(g,E):y.appendChild(g),
+    M=null,g;if(ea)try{importScripts(d),b.completeLoad(c)}catch(l){b.onError(B("importscripts","importScripts failed for "+c+" at "+d,l,[c]))}};A&&O(document.getElementsByTagName("script"),function(b){y||(y=b.parentNode);if(L=b.getAttribute("data-main"))return s=L,u.baseUrl||(F=s.split("/"),s=F.pop(),ga=F.length?F.join("/")+"/":"./",u.baseUrl=ga),s=s.replace(fa,""),h.jsExtRegExp.test(s)&&(s=L),u.deps=u.deps?u.deps.concat(s):[s],!0});define=function(b,c,d){var h,g;"string"!==typeof b&&(d=c,c=b,b=null);
+    K(c)||(d=c,c=null);!c&&J(d)&&(c=[],d.length&&(d.toString().replace(ma,"").replace(na,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c)));if(Q){if(!(h=M))R&&"interactive"===R.readyState||O(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return R=b}),h=R;h&&(b||(b=h.getAttribute("data-requiremodule")),g=G[h.getAttribute("data-requirecontext")])}(g?g.defQueue:U).push([b,c,d])};define.amd={jQuery:!0};h.exec=function(b){return eval(b)};
+    h(u)}})(this);
+    return require;
+})();

From 63f7b8016e97f0c0f3f0923e9223405dbd9d1f2e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jannis=20Mo=C3=9Fhammer?= <jannis.mosshammer@netways.de>
Date: Thu, 13 Jun 2013 17:37:02 +0200
Subject: [PATCH 2/3] Add tests for javascript module implementation

This commit adds tests for the module loader and registry of
icinga2-web. It mainly registers event handlers and calls
custom enable/disable functions

refs #3753
---
 test/js/test/icinga/moduleTest.js | 190 ++++++++++++++++++++++++++++++
 1 file changed, 190 insertions(+)
 create mode 100644 test/js/test/icinga/moduleTest.js

diff --git a/test/js/test/icinga/moduleTest.js b/test/js/test/icinga/moduleTest.js
new file mode 100644
index 000000000..da89f4558
--- /dev/null
+++ b/test/js/test/icinga/moduleTest.js
@@ -0,0 +1,190 @@
+/**
+*   Test cases for the module loading implementation 
+*
+*
+**/
+
+
+// {{LICENSE_HEADER}}
+// {{LICENSE_HEADER}}
+var should = require("should");
+var rjsmock = require("requiremock.js");
+
+var BASE = "../../../../public/js/";
+require(BASE+"icinga/module.js");
+
+var module = rjsmock.getDefine(); 
+
+/**
+* Test module that only uses eventhandlers and 
+* no custom logic
+**/
+var eventOnlyModule = function() {
+    var thiz = this;
+    this.moduleLinkClick = false;
+    this.formFocus = false;
+    
+    var onModuleLinkClick = function() {
+        thiz.moduleLinkClick = true;
+    };
+    var onFormFocus = function() {
+        thiz.formFocus = true;
+    };
+    this.eventHandler = {
+        '.myModule a' : {
+           'click': onModuleLinkClick
+        },
+        '.myModule div.test input' : {
+            'focus' : onFormFocus
+        }
+    };
+};
+/**
+*   Module that defines an own enable and disable function
+*   that is called additionally to the eventhandler setup
+*
+**/
+var customLogicModule = function() {
+    var thiz = this;
+    this.clicked = false;
+    this.customEnable = false;
+    this.customDisable = false;
+
+    var onClick = function() {
+        thiz.clicked = true;
+    };
+    
+    this.enable = function() {
+        thiz.customEnable = true;
+    };
+
+    this.disable = function() {
+        thiz.customDisable = true;
+    };
+
+    this.eventHandler = {
+        '.myModule a' : {
+            'click' : onClick
+        }
+    };
+};
+
+var setupTestDOM = function() {
+    tearDownTestDOM();
+    $('body').append($('<div class="myModule" />')
+             .append($('<a href="test">funkey</a>'))
+             .append($('<div class="test" />')
+             .append('<input type="button" />')));
+};
+
+var tearDownTestDOM = function() {
+    $('body').off();
+    $('body').empty();
+};
+
+describe('Module loader', function() {
+    var err = null;
+    var errorCallback = function(error) {
+        err = error;
+    };
+
+
+    it('Should call the errorCallback when module isn\'t found', function() {
+        err = null;
+        rjsmock.purgeDependencies();
+        module.resetHard();
+        module.enableModule("testModule", errorCallback);
+        should.exist(err);
+    });
+
+
+    it('Should register model event handlers when an \'eventHandler\' attribute exists', function() {
+        rjsmock.purgeDependencies();
+        var testModule = new eventOnlyModule();
+        rjsmock.registerDependencies({
+            testModule: testModule
+        });
+        err = null;
+        var toTest = null;
+        
+        // Test event handler
+        setupTestDOM();
+
+        // Enable the module and asser it is recognized and enabled
+        module.enableModule("testModule", errorCallback, function(enabled) {
+            toTest = enabled;
+        });
+        should.not.exist(err, "An error occured during loading: "+err);
+        should.exist(toTest, "The injected test module didn't work!");
+        should.exist(toTest.enable, "Implicit enable method wasn't created"); 
+        $('.myModule a').click();
+        should.equal(toTest.moduleLinkClick, true, "Click on link should trigger handler");
+        
+        $('.myModule div.test input').focus();
+        should.equal(toTest.formFocus, true, "Form focus should trigger handler");
+
+        tearDownTestDOM();
+    });
+
+    it('Should be able to deregister events handlers when disable() is called', function() {
+        rjsmock.purgeDependencies();
+        var testModule = new eventOnlyModule();
+        rjsmock.registerDependencies({
+            testModule: testModule
+        });
+        err = null;
+        var toTest = null;
+        
+        setupTestDOM(); 
+
+        module.enableModule("testModule", errorCallback, function(enabled) {
+            toTest = enabled;
+        });
+        should.not.exist(err, "An error occured during loading: "+err);
+        should.exist(toTest, "The injected test module didn't work!");
+        should.exist(toTest.enable, "Implicit enable method wasn't created"); 
+        
+        $('.myModule a').click();
+        should.equal(toTest.moduleLinkClick, true, "Click on link should trigger handler");
+        toTest.moduleLinkClick = false;
+        
+        module.disableModule("testModule"); 
+        $('.myModule a').click();
+        should.equal(toTest.moduleLinkClick, false, "Click on link shouldn't trigger handler when module is disabled");
+        tearDownTestDOM();
+        $('body').unbind();
+    });
+
+    it('Should additionally call custom enable and disable functions', function() {
+
+        rjsmock.purgeDependencies();
+        var testModule = new customLogicModule();
+        rjsmock.registerDependencies({
+            testModule: testModule
+        });
+        err = null;
+        var toTest = null;
+        
+        // Test event handler
+        setupTestDOM(); 
+
+        module.enableModule("testModule", errorCallback, function(enabled) {
+            toTest = enabled;
+        });
+        should.not.exist(err, "An error occured during loading: "+err);
+        should.exist(toTest, "The injected test module didn't work!");
+        should.exist(toTest.enable, "Implicit enable method wasn't created"); 
+        should.equal(toTest.customEnable, true, "Custom enable method wasn't called"); 
+        $('.myModule a').click();
+        should.equal(toTest.clicked, true, "Click on link should trigger handler");
+        toTest.clicked = false;
+                
+        module.disableModule("testModule"); 
+        should.equal(toTest.customDisable, true, "Custom disable method wasn't called"); 
+        $('.myModule a').click();
+        should.equal(toTest.clicked, false, "Click on link shouldn't trigger handler when module is disabled");
+        tearDownTestDOM();
+        $('body').unbind();
+    });
+
+});

From 0d0db281f726d39db5388dfe3beea8de9738b2ca Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jannis=20Mo=C3=9Fhammer?= <jannis.mosshammer@netways.de>
Date: Fri, 14 Jun 2013 14:47:31 +0200
Subject: [PATCH 3/3] Test and icinga.js module setup procedure

Behaviours are now modules and icinga.js automatically
requests module/list (which should be served by the application
and is non static) and retrieves a list of modules to request and
enable.

refs #3753
---
 public/js/icinga/icinga.js              |  42 +++++----
 public/js/icinga/modules/actionTable.js | 118 ++++++++++++++++++++++++
 public/js/icinga/modules/mainDetail.js  |  94 +++++++++++++++++++
 test/js/test/icinga/moduleTest.js       |  40 +++++++-
 4 files changed, 274 insertions(+), 20 deletions(-)
 create mode 100644 public/js/icinga/modules/actionTable.js
 create mode 100755 public/js/icinga/modules/mainDetail.js

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 class='test'></a>"));
+        $('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(); 
+    });
 });