← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 15663: Merge reworked menu and update event-capture

 

Merge authors:
  Mark Polak (markpo)
------------------------------------------------------------
revno: 15663 [merge]
committer: Mark Polak <markpo@xxxxxxxxxx>
branch nick: dhis2
timestamp: Fri 2014-06-13 01:42:40 +0200
message:
  Merge reworked menu and update event-capture
added:
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.menu.manager.js
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.menu.ui.js
modified:
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/about/modules.vm
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/css/menu.css
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.menu.js
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.translate.js
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/main.vm
  dhis-2/dhis-web/dhis-web-event-capture/src/main/webapp/dhis-web-event-capture/event-capture.appcache
  dhis-2/dhis-web/dhis-web-event-capture/src/main/webapp/dhis-web-event-capture/i18n/en.json
  dhis-2/dhis-web/dhis-web-event-capture/src/main/webapp/dhis-web-event-capture/i18n/fr.json
  dhis-2/dhis-web/dhis-web-event-capture/src/main/webapp/dhis-web-event-capture/index.html
  dhis-2/dhis-web/dhis-web-event-capture/src/main/webapp/dhis-web-event-capture/scripts/app.js


--
lp:dhis2
https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk

Your team DHIS 2 developers is subscribed to branch lp:dhis2.
To unsubscribe from this branch go to https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk/+edit-subscription
=== modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/about/modules.vm'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/about/modules.vm	2014-04-01 20:39:26 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/about/modules.vm	2014-06-11 20:02:19 +0000
@@ -12,3 +12,5 @@
 </div>
 
 <div id="appsMenu"></div>
+
+<script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.menu.manager.js?_rev=$!{buildRevision}"></script>

=== modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/css/menu.css'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/css/menu.css	2014-05-29 08:50:53 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/css/menu.css	2014-06-12 23:32:40 +0000
@@ -1,3 +1,14 @@
+/**
+ * Bootstrap 3.0 box-sizing fix
+ */
+#menuLinkArea * {
+    box-sizing: content-box;
+    -webkit-box-sizing: content-box;
+    -moz-box-sizing:  content-box;
+    -o-box-sizing: content-box;
+    -m-box-sizing: content-box;
+}
+
 #menuLinkArea
 {
     list-style-type: none;
@@ -11,22 +22,33 @@
     float: right;
 }
 
-#apps-search {
+#menuLinkArea .app-menu-dropdown li {
+    float: left;
+}
+
+input.apps-search {
     border: 1px solid #ccc;
     border-radius: 3px;
+    box-sizing: content-box;
     outline: none;
+    padding: 4px 1px;
     padding-right: 25px;
     padding-left: 5px;
     width: 328px;
 }
 
+input.apps-search[type="text"] {
+    padding-right: 5px;
+    width: 348px;
+}
+
 .apps-search-wrap {
     padding-bottom: 10px;
     position: relative;
     width: 360px;
 }
 
-#apps-search-clear {
+.app-menu-dropdown .apps-search-clear {
     color: #404040;
     cursor: pointer;
     display: none;
@@ -60,6 +82,10 @@
     color: #000;
 }
 
+.menuDropDownBox li.selected a {
+    background-color: #f5f5f7;
+}
+
 .app-menu:after
 {
     clear: both;
@@ -238,6 +264,7 @@
 
 a.menu-link:hover
 {
+    color: #fff;
     text-decoration: none;
 }
 
@@ -251,18 +278,29 @@
     border-radius: 2px;
     box-shadow: rgba(0, 0, 0, 0.24) 0px 2px 8px 0px;
     color: #000;
-    display: none;
     font-size: 9pt;
     max-height: 610px;
     overflow-y: inherit;
     padding: 10px;
+    top: 10px;
+    position: relative;
     width: 360px;
     z-index: 10;
 }
 
+.app-menu-dropdown-wrap {
+    position: absolute;
+    left: -9999px;
+}
+
+.app-menu-hide {
+    display: none;
+}
+
 .app-menu-dropdown ul
 {
     margin: 0;
+    overflow: auto;
 }
 
 .app-menu-dropdown li
@@ -317,6 +355,7 @@
     display: block;
     height: 110px;
     padding: 0;
+    text-decoration: none;
     width: 120px;
 }
 
@@ -352,6 +391,14 @@
     margin-left: 5px;
 }
 
+.app-menu-dropdown:after {
+    content: " "; /* Older browser do not support empty content */
+    visibility: hidden;
+    display: block;
+    height: 0;
+    clear: both;
+}
+
 .apps-menu-bottom-button a:hover
 {
     color: #fff;
@@ -404,20 +451,14 @@
     width: 384px;
 }
 
-#appsDropDown ul.menuDropDownBox {
+#appsMenuDropDown ul.menuDropDownBox {
     height: 330px;
 }
 
-#appsDropDown .caret-up-background,
-#appsDropDown .caret-up-border
-{
-    left: 296px;
-}
-
-#profileDropDown .caret-up-background,
-#profileDropDown .caret-up-border
-{
-    left: 292px;
+.caret-up-background,
+.caret-up-border
+{
+    left: 298px;
 }
 
 .drop-down-menu-link

=== modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.menu.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.menu.js	2014-05-24 08:58:21 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.menu.js	2014-06-11 20:02:19 +0000
@@ -37,11 +37,47 @@
                 return Object.prototype.toString.call(obj) == '[object Function]';
             }
         },
+        getBaseUrl = (function () {
+            var href = window.location.origin;
+            return function () {
+                var urlParts = href.split("/"),
+                    baseUrl;
+
+                if (dhis2.settings.baseUrl === undefined) {
+                    return "..";
+                }
+
+                if (typeof dhis2.settings.baseUrl !== "string") {
+                    throw new TypeError("Dhis2 settings: baseUrl should be a string");
+                }
+
+                if (urlParts[urlParts.length - 1] !== "") {
+                    baseUrl = href + '/' + dhis2.settings.baseUrl;
+                } else {
+                    urlParts.pop();
+                    urlParts.push(dhis2.settings.baseUrl);
+                    baseUrl = urlParts.join('/');
+                }
+                return baseUrl;
+            }
+        })(),
+        /**
+         * Adjusts the url to include the baseUrl
+         *
+         * @param iconUrl
+         * @returns {String}
+         */
+         fixUrlIfNeeded = function (iconUrl) {
+            if (iconUrl.substring(0, 2) === "..") {
+                return getBaseUrl() + iconUrl.substring(2, iconUrl.length);
+            }
+            return iconUrl;
+        },
         /**
          * Object that represents the list of menu items
          * and managers the order of the items to be saved.
          */
-            menuItemsList = (function () {
+         menuItemsList = function () {
             var menuOrder = [],
                 menuItems = {};
 
@@ -69,14 +105,14 @@
                     return menuOrder;
                 }
             }
-        })();
+        };
 
     dhis2.menu = {};
 
-    dhis2.menu = function () {
+    dhis2.menu = function (nameKey, preLoadedData) {
         var that = {},
             menuReady = false,
-            menuItems = menuItemsList,
+            menuItems = menuItemsList(),
             callBacks = [], //Array of callbacks to call when serviced is updated
             onceCallBacks = [];
 
@@ -85,7 +121,9 @@
          **********************************************************************/
 
         function processTranslations(translations) {
-            var items = dhis2.menu.getApps();
+            var items = that.getApps();
+
+            that.name = translations[nameKey];
 
             items.forEach(function (element, index, items) {
                 if (element.id && translations[element.id]) {
@@ -112,7 +150,7 @@
          * Execute any callbacks that are set onto the callbacks array
          */
         function executeCallBacks() {
-            var onceCallBack, callBackIndex;
+            var onceCallBack;
 
             //If not ready or no menu items
             if ( ! isReady() || menuItems === {})
@@ -121,10 +159,10 @@
             //Execute the single time callbacks
             while (onceCallBacks.length !== 0) {
                 onceCallBack = onceCallBacks.pop();
-                onceCallBack(menuItems);
+                onceCallBack.apply(that, [that]);
             }
             callBacks.forEach(function (callback, index, callBacks) {
-                callback.apply(dhis2.menu, [menuItems]);
+                callback.apply(that, [that]);
             });
         }
 
@@ -170,6 +208,8 @@
          * Public methods
          **********************************************************************/
 
+        that.id = nameKey;
+        that.name = nameKey;
         that.displayOrder = 'custom';
 
         that.getMenuItems = function () {
@@ -234,12 +274,19 @@
         that.addMenuItems = function (items) {
             var keysToTranslate = [];
 
+            //Add the name of the menu to the translationList
+            keysToTranslate.push(nameKey);
+
             items.forEach(function (item, index, items) {
                 item.id = item.name;
                 keysToTranslate.push(item.name);
                 if(item.description === "") {
                     keysToTranslate.push("intro_" + item.name);
                 }
+
+                item.defaultAction = fixUrlIfNeeded(item.defaultAction);
+                item.icon = fixUrlIfNeeded(item.icon);
+
                 menuItems.setItem(item.id, item);
             });
 
@@ -257,11 +304,12 @@
             var once = onlyOnce ? true : false;
 
             if ( ! du.isFunction(callback)) {
+                setTimeout(executeCallBacks, 300);
                 return false;
             }
 
-            if (menuItems !== undefined) {
-                callback(menuItems);
+            if (isReady() && (menuItems !== undefined)) {
+                callback(that);
             }
 
             if (true === once) {
@@ -305,7 +353,7 @@
          * @returns {Array} Array of app objects
          */
         that.getOrderedAppList = function () {
-            var favApps = dhis2.menu.getFavorites(),
+            var favApps = that.getFavorites(),
                 nonFavApps = that.getNonFavoriteApps();
             switch (that.displayOrder) {
                 case 'name-asc':
@@ -319,7 +367,7 @@
         }
 
         that.updateOrder = function (reorderedApps) {
-            switch (dhis2.menu.displayOrder) {
+            switch (that.displayOrder) {
                 case 'name-asc':
                 case 'name-desc':
                     that.updateFavoritesFromList(reorderedApps);
@@ -340,199 +388,11 @@
             return saveMethod(that.getMenuItems().getOrder());
         }
 
-        return that;
-    }();
-})(dhis2 = dhis2 || {});
-
-/**
- * Created by Mark Polak on 28/01/14.
- *
- * @description jQuery part of the menu
- *
- * @see jQuery (http://jquery.com)
- * @see jQuery Template Plugin (http://github.com/jquery/jquery-tmpl)
- */
-
-/* Function used for checking dependencies for the menu
-(function (required_libs, undefined) {
-    var libraries = [
-        { name: "jQuery", variable: "jQuery", url: "http://jquery.com"; },
-        { name: "jQuery Template Plugin", variable: "jQuery.template", url: "http://github.com/jquery/jquery-tmpl"; }
-    ];
-
-    //In IE 8 we can not use console
-    if (typeof console === "undefined") {
-        return;
-    }
-
-    //Throw error for the required libraries
-    libraries.forEach(function (library, index, libraries) {
-        if (window[library] === undefined) {
-            console.error("Missing required library: " + library.name + ". Please see (" + library.url + ")");
-        }
-    });
-})();
-*/
-
-(function ($, menu, undefined) {
-    var markup = '',
-        selector = 'appsMenu';
-
-    markup += '<li data-id="${id}" data-app-name="${name}" data-app-action="${defaultAction}">';
-    markup += '  <a href="${defaultAction}" class="app-menu-item">';
-    markup += '    <img src="${icon}" onError="javascript: this.onerror=null; this.src = \'../icons/program.png\';">';
-    markup += '    <span>${name}</span>';
-    markup += '    <div class="app-menu-item-description"><span class="bold">${name}</span><i class="fa fa-arrows"></i><p>${description}</p></div>';
-    markup += '  </a>';
-    markup += '</li>';
-
-    $.template('appMenuItemTemplate', markup);
-
-    function renderDropDownFavorites() {
-        var selector = '#appsDropDown .menuDropDownBox',
-            apps = dhis2.menu.getOrderedAppList();
-
-        $('#appsDropDown').addClass('app-menu-dropdown ui-helper-clearfix');
-        $(selector).html('');
-        $.tmpl( "appMenuItemTemplate", apps).appendTo(selector);
-    }
-
-    function renderAppManager(selector) {
-        var apps = dhis2.menu.getOrderedAppList();
-        $('#' + selector).html('');
-        $('#' + selector).append($('<ul></ul><hr class="app-separator">').addClass('ui-helper-clearfix'));
-        $('#' + selector).addClass('app-menu');
-        $.tmpl( "appMenuItemTemplate", apps).appendTo('#' + selector + ' ul');
-
-        //Add favorites icon to all the menu items in the manager
-        $('#' + selector + ' ul li').each(function (index, item) {
-            $(item).children('a').append($('<i class="fa fa-bookmark"></i>'));
-        });
-
-        twoColumnRowFix();
-    }
-
-    /**
-     * Saves the given order to the server using jquery ajax
-     *
-     * @param menuOrder {Array}
-     */
-    function saveOrder(menuOrder) {
-        if (menuOrder.length !== 0) {
-            //Persist the order on the server
-            $.ajax({
-                contentType:"application/json; charset=utf-8",
-                data: JSON.stringify(menuOrder),
-                dataType: "json",
-                type:"POST",
-                url: "../api/menu/"
-            }).success(function () {
-                    //TODO: Give user feedback for successful save
-                }).error(function () {
-                    //TODO: Give user feedback for failure to save
-                });
-        }
-    }
-
-    /**
-     * Resets the app blocks margin in case of a resize or a sort update.
-     * This function adds a margin to the 9th element when the screen is using two columns to have a clear separation
-     * between the favorites and the other apps
-     *
-     * @param event
-     * @param ui
-     */
-    function twoColumnRowFix(event, ui) {
-        var self = $('.app-menu ul'),
-            elements = $(self).find('li:not(.ui-sortable-helper)');
-
-        elements.each(function (index, element) {
-            $(element).css('margin-right', '0px');
-            if ($(element).hasClass('app-menu-placeholder')) {
-                $(element).css('margin-right', '10px');
-            }
-            //Only fix the 9th element when we have a small enough screen
-            if (index === 8 && (self.width() < 808)) {
-                $(element).css('margin-right', '255px');
-            }
-        });
-
-    }
-
-    /**
-     * Render the menumanager and the dropdown menu and attach the update handler
-     */
-        //TODO: Rename this as the name is not very clear to what it does
-    function renderMenu() {
-        var options = {
-            placeholder: 'app-menu-placeholder',
-            connectWith: '.app-menu ul',
-            update: function (event, ui) {
-                var reorderedApps = $("#" + selector + " ul"). sortable('toArray', {attribute: "data-id"});
-
-                dhis2.menu.updateOrder(reorderedApps);
-                dhis2.menu.save(saveOrder);
-
-                //Render the dropdown menu
-                renderDropDownFavorites();
-            },
-            sort: twoColumnRowFix,
-            tolerance: "pointer",
-            cursorAt: { left: 55, top: 30 }
-        };
-
-        renderAppManager(selector);
-        renderDropDownFavorites();
-
-        $('.app-menu ul').sortable(options).disableSelection();
-    }
-
-    menu.subscribe(renderMenu);
-
-    /**
-     * jQuery events that communicate with the web api
-     * TODO: Check the urls (they seem to be specific to the dev location atm)
-     */
-    $(function () {
-        var menuTimeout = 500,
-            closeTimer = null,
-            dropDownId = null,
-            dropDownHooks = (function () {
-                var hook_library = {};
-
-                return {
-                  get: function (id) {
-                      if (hook_library[id] && hook_library[id].length > 0) {
-                        return hook_library[id];
-                      } else {
-                        return [];
-                      }
-                  },
-                  addHook: function (id, hook) {
-                      hook_library[id] = hook_library[id] || [];
-                      hook_library[id].push(hook);
-                  }
-                };
-            })();
-
-        function performSearch() {
-            var menuItems = [],
-                searchFor = $('#apps-search').val().toLowerCase(),
+        that.search = function (searchFor) {
+            //Get all the apps
+            var menuItems = that.getApps(),
                 searchMatches = [];
 
-            //Re-render all the apps
-            renderDropDownFavorites();
-
-            if (searchFor === '') {
-                $('#apps-search-clear').hide();
-                $('#apps-search').focus();
-                return;
-            }
-            $('#apps-search-clear').show();
-
-            //Get all the apps
-            menuItems = menu.getApps();
-
             //Find the matches
             menuItems.forEach(function (menuItem) {
                 var menuItemName = menuItem.name.toLowerCase(),
@@ -544,7 +404,7 @@
                 }
             });
 
-            //Order the search matches on occurance
+            //Order the search matches on occurrence
             searchMatches.sort(function (a, b) {
                 if (a.searchScore < b.searchScore)
                     return -1;
@@ -553,221 +413,16 @@
                 return 0;
             });
 
-            //Remove all the apps
-            $(dropDownId).find('ul').find('li').remove();
-
-            //Add the apps that match the search back to the menu
-            $.tmpl( "appMenuItemTemplate", searchMatches).appendTo(dropDownId + ' ul');
-        }
-
-        function goToFirstMenuItem() {
-            var menuItemUrl = $(dropDownId).find('ul li').first().find('a').attr('href');
-            if (menuItemUrl) {
-                window.location = menuItemUrl;
-            }
-        }
-
-        dropDownHooks.addHook('appsDropDown', function () {
-            var dropDownId = this;
-            $(dropDownId).find('#apps-search').focus();
-
-            $('#apps-search').keyup(function (event) {
-                if ( event.which == 13 ) {
-                    event.preventDefault();
-                    goToFirstMenuItem();
-                } else {
-                    performSearch();
-                }
-
-            });
-
-            $('#apps-search-clear').click(function () {
-                $('#apps-search').val("");
-                performSearch();
-            });
-        });
-
-        $.ajax('../dhis-web-commons/menu/getModules.action').success(function (data) {
-            if (typeof data.modules === 'object') {
-                menu.addMenuItems(data.modules);
-            }
-        }).error(function () {
-                //TODO: Give user feedback for failure to load items
-                //TODO: Translate this error message
-                var error_template = '<li class="app-menu-error"><a href="' + window.location.href +'">Unable to load your apps, click to refresh</a></li>';
-                $('#' + selector).addClass('app-menu').html('<ul>' + error_template + '</ul>');
-                $('#appsDropDown .menuDropDownBox').html(error_template);
-            });
-
-        /**
-         * Event handler for the sort order box
-         */
-        $('#menuOrderBy').change(function (event) {
-            var orderBy = $(event.target).val();
-
-            dhis2.menu.displayOrder = orderBy;
-
-            renderMenu();
-        });
-
-        /**
-         * Check if we need to fix columns when the window resizes
-         */
-        $(window).resize(twoColumnRowFix);
-
-        /**
-         * Adds a scrolling mechanism that makes space for the scrollbar and shows/hides the more apps button
-         */
-        $('.menu-drop-down-scroll').scroll(function (event) {
-            var self = $(this);
-
-            if (self.scrollTop() < 10) {
-                self.parent().css('width', '360px');
-                self.parent().parent().css('width', '360px');
-            } else {
-                if (self.innerHeight() === 375 ) {
-                    self.parent().css('width', '384px');
-                    self.parent().parent().css('width', '384px');
-                }
-            }
-
-        });
-
-        function executeDropDownHooks(dropDownId) {
-            var hooks = dropDownHooks.get(dropDownId);
-            hooks.forEach(function (hook) {
-                hook.call('#' + dropDownId);
-            });
-        }
-
-        function showDropDown( id )
-        {
-            var newDropDownId = "#" + id,
-                position = $(newDropDownId + '_button').position();
-
-            cancelHideDropDownTimeout();
-
-            $(newDropDownId).css('position', 'absolute');
-            $(newDropDownId).css('top', '55px');
-            $(newDropDownId).css('left', Math.ceil(position.left - Math.ceil(parseInt($(newDropDownId).innerWidth(), 10) - 108)) + 'px');
-
-            if ( dropDownId != newDropDownId ) {
-                hideDropDown();
-
-                dropDownId = newDropDownId;
-
-                $( dropDownId ).show();
-            }
-
-            executeDropDownHooks(id);
-        }
-
-        function hideDropDown() {
-            if ( dropDownId ) {
-                if ($( dropDownId ).attr( 'data-clicked-open' ) === 'true') {
-                    return;
-                }
-                $( dropDownId ).hide();
-
-                dropDownId = null;
-            }
-        }
-
-        function hideDropDownTimeout() {
-            closeTimer = window.setTimeout( hideDropDown, menuTimeout );
-        }
-
-        function cancelHideDropDownTimeout() {
-            if ( closeTimer ) {
-                window.clearTimeout( closeTimer );
-
-                closeTimer = null;
-            }
-        }
-
-        // Set show and hide drop down events on top menu
-        $( "#appsMenuLink" ).hover(function() {
-            showDropDown( "appsDropDown" );
-        }, function() {
-            hideDropDownTimeout();
-        });
-
-        $( "#profileMenuLink" ).hover(function() {
-            showDropDown( "profileDropDown" );
-        }, function() {
-            hideDropDownTimeout();
-        });
-
-        $( "#appsDropDown, #profileDropDown" ).hover(function() {
-            cancelHideDropDownTimeout();
-        }, function() {
-            hideDropDownTimeout();
-        });
-
-
-        $('.drop-down-menu-link').get().forEach(function (element, index, elements) {
-            var id = $(element).parent().attr('id'),
-                dropdown_menu = $('div#' + id.split('_')[0]);
-
-            function closeAllDropdowns() {
-                $('.app-menu-dropdown').each(function () {
-                    $(this).attr('data-clicked-open', 'false');
-                    $(this).hide();
-                });
-                hideDropDown();
-            }
-
-            $(element).click(function () {
-                return function () {
-                    var thisDropDownStatus = $(dropdown_menu).attr('data-clicked-open');
-                    closeAllDropdowns();
-
-                    if (thisDropDownStatus === 'true') {
-                        $(dropdown_menu).attr('data-clicked-open', 'false');
-                    } else {
-                        $(dropdown_menu).attr('data-clicked-open', 'true');
-                        showDropDown(dropdown_menu.attr('id'));
-                    }
-                }
-            }());
-        });
-
-        $(window).resize(function () {
-            $('.app-menu-dropdown').get().forEach(function (element, index, elements) {
-                var newDropDownId = '#' + $(element).attr('id'),
-                    position = $(newDropDownId + '_button').position();
-
-                $(newDropDownId).css('position', 'absolute');
-                $(newDropDownId).css('top', '55px');
-                $(newDropDownId).css('left', Math.ceil(position.left - Math.ceil(parseInt($(newDropDownId).innerWidth(), 10) - 108)) + 'px');
-            });
-        });
-
-        $('.apps-scroll-up').click(function (event) {
-            var scrollDistance = 330,
-                scrollTop = $('.menu-drop-down-scroll').scrollTop();
-
-            event.preventDefault();
-
-            $('.menu-drop-down-scroll').animate({
-                scrollTop: scrollTop - scrollDistance
-            }, 200);
-        });
-
-        $('.apps-scroll-down').click(function (event) {
-            var scrollDistance = 330,
-                scrollTop = $('.menu-drop-down-scroll').scrollTop();
-
-            event.preventDefault();
-
-            if (scrollTop < 110) {
-                scrollDistance += 40;
-            }
-            $('.menu-drop-down-scroll').animate({
-                scrollTop: scrollTop + scrollDistance
-            }, 200);
-        });
-
-    });
-
-})(jQuery, dhis2.menu);
+            return searchMatches;
+        }
+
+        if (typeof preLoadedData === 'object') {
+            that.addMenuItems(preLoadedData);
+        }
+
+        return that;
+    };
+
+    //Expose the fixUrl method so we can use externally
+    dhis2.menu.fixUrlIfNeeded = fixUrlIfNeeded;
+})(dhis2 = dhis2 || {});

=== added file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.menu.manager.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.menu.manager.js	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.menu.manager.js	2014-06-11 20:02:19 +0000
@@ -0,0 +1,191 @@
+"use strict";
+/*
+ * Copyright (c) 2004-2014, University of Oslo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * Neither the name of the HISP project nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * Created by mark on 11/06/14.
+ */
+/**
+ * Created by Mark Polak on 28/01/14.
+ *
+ * @description jQuery part of the menu
+ *
+ * @see jQuery (http://jquery.com)
+ * @see jQuery Template Plugin (http://github.com/jquery/jquery-tmpl)
+ */
+(function ($, menu, undefined) {
+    var markup = '',
+        selector = 'appsMenu';
+
+    markup += '<li data-id="${id}" data-app-name="${name}" data-app-action="${defaultAction}">';
+    markup += '  <a href="${defaultAction}" class="app-menu-item">';
+    markup += '    <img src="${icon}" onError="javascript: this.onerror=null; this.src = \'../icons/program.png\';">';
+    markup += '    <span>${name}</span>';
+    markup += '    <div class="app-menu-item-description"><span class="bold">${name}</span><i class="fa fa-arrows"></i><p>${description}</p></div>';
+    markup += '  </a>';
+    markup += '</li>';
+
+    $.template('appMenuItemTemplate', markup);
+
+    function renderAppManager(selector) {
+        var apps = menu.getOrderedAppList();
+        $('#' + selector).html('');
+        $('#' + selector).append($('<ul></ul><hr class="app-separator">').addClass('ui-helper-clearfix'));
+        $('#' + selector).addClass('app-menu');
+        $.tmpl( "appMenuItemTemplate", apps).appendTo('#' + selector + ' ul');
+
+        //Add favorites icon to all the menu items in the manager
+        $('#' + selector + ' ul li').each(function (index, item) {
+            $(item).children('a').append($('<i class="fa fa-bookmark"></i>'));
+        });
+
+        twoColumnRowFix();
+    }
+
+    /**
+     * Saves the given order to the server using jquery ajax
+     *
+     * @param menuOrder {Array}
+     */
+    function saveOrder(menuOrder) {
+        if (menuOrder.length !== 0) {
+            //Persist the order on the server
+            $.ajax({
+                contentType:"application/json; charset=utf-8",
+                data: JSON.stringify(menuOrder),
+                dataType: "json",
+                type:"POST",
+                url: "../api/menu/"
+            }).success(function () {
+                    //TODO: Give user feedback for successful save
+                }).error(function () {
+                    //TODO: Give user feedback for failure to save
+                });
+        }
+    }
+
+    /**
+     * Resets the app blocks margin in case of a resize or a sort update.
+     * This function adds a margin to the 9th element when the screen is using two columns to have a clear separation
+     * between the favorites and the other apps
+     *
+     * @param event
+     * @param ui
+     */
+    function twoColumnRowFix(event, ui) {
+        var self = $('.app-menu ul'),
+            elements = $(self).find('li:not(.ui-sortable-helper)');
+
+        elements.each(function (index, element) {
+            $(element).css('margin-right', '0px');
+            if ($(element).hasClass('app-menu-placeholder')) {
+                $(element).css('margin-right', '10px');
+            }
+            //Only fix the 9th element when we have a small enough screen
+            if (index === 8 && (self.width() < 808)) {
+                $(element).css('margin-right', '255px');
+            }
+        });
+
+    }
+
+    /**
+     * Render the menumanager and the dropdown menu and attach the update handler
+     */
+        //TODO: Rename this as the name is not very clear to what it does
+    function renderMenu() {
+        var options = {
+            placeholder: 'app-menu-placeholder',
+            connectWith: '.app-menu ul',
+            update: function (event, ui) {
+                var reorderedApps = $("#" + selector + " ul"). sortable('toArray', {attribute: "data-id"});
+
+                menu.updateOrder(reorderedApps);
+                menu.save(saveOrder);
+            },
+            sort: twoColumnRowFix,
+            tolerance: "pointer",
+            cursorAt: { left: 55, top: 30 }
+        };
+
+        renderAppManager(selector);
+
+        $('.app-menu ul').sortable(options).disableSelection();
+    }
+
+    menu.subscribe(renderMenu);
+
+    /**
+     * jQuery events that communicate with the web api
+     * TODO: Check the urls (they seem to be specific to the dev location atm)
+     */
+    $(function () {
+        /**
+         * Event handler for the sort order box
+         */
+        $('#menuOrderBy').change(function (event) {
+            var orderBy = $(event.target).val();
+
+            menu.displayOrder = orderBy;
+
+            renderMenu();
+        });
+
+        /**
+         * Check if we need to fix columns when the window resizes
+         */
+        $(window).resize(twoColumnRowFix);
+
+        $('.drop-down-menu-link').get().forEach(function (element, index, elements) {
+            var id = $(element).parent().attr('id'),
+                dropdown_menu = $('div#' + id.split('_')[0]);
+
+            function closeAllDropdowns() {
+                $('.app-menu-dropdown').each(function () {
+                    $(this).attr('data-clicked-open', 'false');
+                    $(this).hide();
+                });
+                hideDropDown();
+            }
+
+            $(element).click(function () {
+                return function () {
+                    var thisDropDownStatus = $(dropdown_menu).attr('data-clicked-open');
+                    closeAllDropdowns();
+
+                    if (thisDropDownStatus === 'true') {
+                        $(dropdown_menu).attr('data-clicked-open', 'false');
+                    } else {
+                        $(dropdown_menu).attr('data-clicked-open', 'true');
+                        showDropDown(dropdown_menu.attr('id'));
+                    }
+                }
+            }());
+        });
+    });
+
+})(jQuery, dhis2.menu.mainAppMenu.menuItems);

=== added file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.menu.ui.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.menu.ui.js	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.menu.ui.js	2014-06-12 23:12:59 +0000
@@ -0,0 +1,1057 @@
+"use strict";
+/*
+ * Copyright (c) 2004-2014, University of Oslo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * Neither the name of the HISP project nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * Created by Mark Polak on 28/01/14.
+ */
+(function (dhis2menu, settings, undefined) {
+
+    var jqLite = undefined, //Local jQuery variable to use for checking dependencies and switching jqLite and jQuery
+        templates = {},
+        cssDefaults = {},
+        getBaseUrl = (function () {
+            var href = window.location.origin;
+            return function () {
+                var urlParts = href.split("/"),
+                    baseUrl;
+
+                if (settings.baseUrl === undefined) {
+                    return "..";
+                }
+
+                if (typeof settings.baseUrl !== "string") {
+                    throw new TypeError("Dhis2 settings: baseUrl should be a string");
+                }
+
+                //Check if there is a filename at the end of the current url
+                //if so remove it and join the parts else just join the href and the base url
+                if (urlParts[urlParts.length - 1] === "") {
+                    urlParts.pop();
+                    urlParts.push(dhis2.settings.baseUrl);
+                    baseUrl = urlParts.join('/');
+                } else {
+                    baseUrl = href + '/' + dhis2.settings.baseUrl;
+                }
+                return baseUrl;
+            }
+        })();
+
+    cssDefaults = {
+        ulWrapId: "menuLinkArea",
+        aMenuLinkClasses: "menu-link drop-down-menu-link"
+    }
+
+    templates.itemItemplate = '' +
+        '<li data-id="{{id}}" data-app-name="{{name}}" data-app-action="{{baseUrl}}{{defaultAction}}">' +
+            '<a href="{{baseUrl}}{{defaultAction}}" class="app-menu-item">' +
+                '<img src="{{baseUrl}}{{icon}}" onError="javascript: this.onerror=null; this.src = \'' + getBaseUrl() + '/icons/program.png\';">' +
+                '<span>{{name}}</span>' +
+                '<div class="app-menu-item-description"><span class="bold">{{name}}</span><i class="fa fa-arrows"></i><p>{{description}}</p></div>' +
+            '</a>' +
+        '</li>';
+
+
+    templates.menuLink = '<li id="{{id}}_button">' +
+                            '<a id="{{id}}Link" class="{{classes}}"><i class="fa fa-{{iconName}}"></i>{{menuItemName}}</a>' +
+                            '<div class="app-menu-dropdown-wrap">' +
+                                '<div class="menuDropDownArea app-menu-dropdown appsMenuLink_menu ui-front">' +
+                                    '<div class="caret-up-border"></div><div class="caret-up-background"></div>' +
+                                    '<ul class="menuDropDownBox">{{menuItems}}</ul>' +
+                                    '<div class="menu-drop-down-buttons"></div>' +
+                                '</div>' +
+                            '</div>' +
+                         '</li>';
+
+    templates.menuLinkWithScroll = '<li id="{{id}}_button">' +
+                                        '<a id="{{id}}Link" class="{{classes}}"><i class="fa fa-{{iconName}}"></i>{{menuItemName}}</a>' +
+                                        '<div class="app-menu-dropdown-wrap">' +
+                                            '<div class="menuDropDownArea app-menu-dropdown appsMenuLink_menu ui-front">' +
+                                                '<div class="caret-up-border"></div><div class="caret-up-background"></div>' +
+                                                '<div class="menu-drop-down-wrap">' +
+                                                    '<div class="menu-drop-down-scroll">' +
+                                                        '<ul class="menuDropDownBox">{{menuItems}}</ul>' +
+                                                    '</div>' +
+                                                '</div>' +
+                                                '<div class="menu-drop-down-buttons">' +
+                                                    '<div class="apps-menu-bottom-button apps-scroll apps-scroll-up"><a class="fa fa-caret-up" href="#"></a></div>' +
+                                                    '<div class="apps-menu-bottom-button apps-scroll apps-scroll-down"><a class="fa fa-caret-down" href="#"></a></div>' +
+                                                '</div>' +
+                                            '</div>' +
+                                        '</div>' +
+                                    '</li>';
+
+    templates.search = '<div class="apps-search-wrap">' +
+                           '<input class="apps-search" type="text" placeholder="{{search_apps}}">' +
+                           '<span class="apps-search-clear fa fa-times-circle"></span>' +
+                       '</div>';
+
+    templates.extraLink = '<div class="apps-menu-bottom-button apps-menu-more"><a href="{{url}}">{{text}}</a></div>';
+
+    var template, defaultMenuUi, searchUi, linkButtonUi, scrollUi, shortCutUi, keys;
+
+    keys = {
+        ctrl: 17,
+        enter: 13,
+        slash: 191,
+        backslash: 220,
+        arrowLeft: 37,
+        arrowUp: 38,
+        arrowRight: 39,
+        arrowDown: 40,
+        m: 77,
+        comma: 188,
+        dot: 190,
+        isArrowKey: function (keyCode) {
+            return (keyCode === keys.arrowRight ||
+                keyCode === keys.arrowLeft ||
+                keyCode === keys.arrowDown ||
+                keyCode === keys.arrowUp);
+        }
+    }
+
+    /*
+     * Check for what type of jquery/jqLite we are using and assign it to jqLite.
+     * We name it jqLite so that whoever maintains this code is not confused by the selectors available
+     * Please note that this jqLite is the angular version of jqLite and this does not contain the full jqLite api but
+     * is a subset of.
+     *
+     * @see https://docs.angularjs.org/api/ng/function/angular.element
+     */
+    if (typeof angular !== 'undefined') {
+        jqLite = angular.element;
+    } else {
+        if (typeof jQuery !== 'undefined') {
+            jqLite = jQuery;
+        }
+    }
+
+    /**
+     * Utility function to check if an object is a function
+     *
+     * @param obj Value that should be checked
+     * @returns {boolean} Returns true when the passed object is a function
+     */
+    function isFunction(obj) {
+        return Object.prototype.toString.call(obj) == '[object Function]';
+    }
+
+    /**
+     * Load data from a dataUrl and return the modules that were found in that response
+     * Fires a http request for json content and takes the {modules} parameter from the returned json object
+     *
+     * @param {String} dataUrl Url of the data to be requested
+     * @param {Function} callback Callback to be fired when the data is recieved
+     * @param {Object} extra Extra information that gets passed to the callback function
+     *                       along side of the modules that are found.
+     */
+    function loadDataFromUrl(dataUrl, callback, extra) {
+        var http, url;
+
+        http = new XMLHttpRequest();
+        url = getBaseUrl() + dataUrl;
+
+        http.open("GET", url, true);
+
+        //Send the proper header information along with the request
+        http.setRequestHeader("Content-type", "application/json; charset=utf-8");
+
+        http.onreadystatechange = function() {//Call a function when the state changes.
+            if(http.readyState == 4 && http.status == 200) {
+                if (typeof callback === 'function') {
+                    callback(JSON.parse(http.responseText).modules, extra);
+                }
+            } else {
+                /*
+                 //TODO: Give user feedback for failure to load items
+                 //TODO: Translate this error message
+                 var error_template = '<li class="app-menu-error"><a href="' + window.location.href +'">Unable to load your apps, click to refresh</a></li>';
+                 $('#' + selector).addClass('app-menu').html('<ul>' + error_template + '</ul>');
+                 $('#appsDropDown .menuDropDownBox').html(error_template);
+                 */
+            }
+        }
+        http.send();
+    }
+
+    /**
+     * Creates a template object with methods to find and parse templates. This is
+     * used for managing menu templates within the various menu addons.
+     *
+     * @param templates
+     * @returns {}
+     */
+    template = function (templates) {
+        var template = {};
+
+        if (templates === undefined)
+            templates = {};
+
+        function findTemplateByName(templateName) {
+            if (templates[templateName])
+                return templates[templateName];
+
+            //Throw error when template does not exist
+            console.error("Template with name: " + templateName + " does not exist");
+        }
+
+        /**
+         * Parses a template
+         *
+         * @param {String} templateName The name of the template to be parsed
+         * @param {Object} data This is an object that holds the data to be placed into the placeholders
+         * @returns {String} Parsed template
+         */
+        template.parse = function (templateName, data) {
+            var regex = /\{\{([A-z]+?)\}\}/,
+                match,
+                template = findTemplateByName(templateName);
+
+            while(match = regex.exec(template)) {
+                template = template.replace('{{' + match[1] + '}}', data[match[1]] || '');
+            }
+
+            return template;
+        }
+
+        /**
+         * Gets a "raw" template. This returns the template as it was saved, without parsing it.
+         * @param {String} name The name of the template
+         * @returns {String}
+         */
+        template.get = function (name) {
+            if (templates[name] === undefined) {
+                console.error("Template " + name + " does not exist");
+            }
+            return templates[name];
+        }
+
+        /**
+         * Adds a template to the template cache
+         *
+         * A Template may contain placeholders. These placeholders are replaced with values when
+         * the template is parsed.
+         *
+         * Place holders are defined between double curly brackets like for example {{id}}.
+         * When parsing a template with this place holder the parse method will take the "id" property
+         * from the data object and place it instead of the placeholder.
+         *
+         * @param {String} name The name of the template, and how to identify it
+         * @param {String} template The template itself (This can be any type of string/html, possibly with placeholders)
+         */
+        template.add = function (name, template) {
+            if (templates[name]) {
+                console.error("Template not allowed to be overridden using the add method, use the replace method instead");
+            }
+            templates[name] = template;
+        }
+
+        /**
+         * Replace an already existing template with a different one
+         *
+         * @param {String} name The name of the template, and how to identify it
+         * @param {String} template The template itself (This can be any type of string/html, possibly with placeholders)
+         */
+        template.replace = function (name, template) {
+            if (templates[name] === undefined) {
+                console.error("No template to be replaced, use the add method to add templates")
+            }
+            templates[name] = template;
+        }
+
+        return template;
+    }
+
+    /**
+     * Creates an error object with that has the passed in message
+     *
+     * @param message
+     * @returns {MenuError}
+     * @constructor
+     */
+    function MenuError (message) {
+        var MenuError = function () {},
+            error;
+
+        MenuError.prototype = new Error;
+
+        error = new MenuError();
+
+        error.message = message;
+
+        error.toString = function () {
+            return "MenuError: " + this.message + " \n";
+        }
+
+        return error;
+    }
+
+    /**
+     * Creates a menu object with the menuBase as a prototype
+     *
+     * @param menuBase
+     * @returns {Menu}
+     */
+    function createMenu (menuBase) {
+        var Menu = function () {},
+            menu;
+
+        /**
+         * When the function is called with an empty menuBase
+         * we create a default menuBase with some essential variables
+         */
+        if (menuBase === undefined) {
+            menuBase = {
+                renderers: [],
+                eventsHandlers: [],
+                name: "",
+                hooks: {
+                    open: [],
+                    close: []
+                }
+            };
+            menuBase.hooks.call = function (name) {
+                if (menuBase.hooks[name]) {
+                    menuBase.hooks[name].forEach(function (callback) {
+                        if (isFunction(callback)) {
+                            callback.apply(name);
+                        }
+                    });
+                }
+            }
+        }
+
+        Menu.prototype = menuBase;
+        menu =  new Menu();
+
+        //TODO: Render function now gets added to all objects (Preferably we only need one)
+        menu.render = function (menuItems) {
+            jqLite(document).ready(function () {
+                menuBase.renderers.forEach(function (renderFunction) {
+                    if (isFunction(renderFunction)) {
+                        renderFunction(menuItems);
+                    }
+                });
+                //Add the event handlers only once
+                menuBase.eventsHandlers.forEach(function (eventFunction) {
+                    if (isFunction(eventFunction)) {
+                        eventFunction(document.querySelector('#' + menu.name + "_button"));
+                    }
+                });
+            });
+        };
+
+        return menu;
+    }
+
+    /**
+     * Menu with default functionality
+     *
+     * @returns {Menu}
+     */
+    defaultMenuUi = function (name, data, icon, container) {
+        var defaultMenu = createMenu(),
+            currentSelectedId = undefined;
+
+        defaultMenu.template = template();
+
+        defaultMenu.name = name;
+        defaultMenu.ajax = false;
+        defaultMenu.icon = icon;
+        defaultMenu.container = container;
+
+        if (typeof data === "string") {
+            //TODO: Implement this
+            loadDataFromUrl(data, function (data) {
+                defaultMenu.menuItems.addMenuItems(data)
+            });
+            defaultMenu.menuItems = dhis2.menu(name);
+        } else {
+            defaultMenu.menuItems = dhis2.menu(name, data);
+        }
+
+        defaultMenu.template.add('menuStructure', '<ul id="{{id}}"></ul>');
+        defaultMenu.template.add('linkItem', templates.menuLink);
+        defaultMenu.template.add('menuItem', templates.itemItemplate);
+
+        defaultMenu.isOpen = function () {
+            var dropdownElement = jqLite(document.querySelector("#" + defaultMenu.name + "_button div.app-menu-dropdown-wrap")),
+            display = jqLite(dropdownElement).css("display");
+            if (display === 'none') {
+                return false;
+            }
+            return true;
+        }
+
+        defaultMenu.isClosed = function () {
+            return ! defaultMenu.isOpen();
+        }
+
+        defaultMenu.open = function (hover) {
+            var dropdownElement = jqLite(document.querySelector("#" + defaultMenu.name + "_button div.app-menu-dropdown-wrap"));
+
+            //Set the dropdown position
+            jqLite(dropdownElement).css('left', defaultMenu.getDropDownPosition() + 'px');
+            dropdownElement.css('display', 'block');
+
+            if (! hover) {
+                dropdownElement.attr("data-display-clicked", "true");
+            }
+            defaultMenu.hooks.call('open');
+        }
+
+        defaultMenu.close = function (hover) {
+            var dropdownElement = jqLite(document.querySelector("#" + defaultMenu.name + "_button div.app-menu-dropdown-wrap"));
+
+            dropdownElement.css('display', 'none');
+            if ( ! hover) {
+                dropdownElement.attr("data-display-clicked", "false");
+            }
+            defaultMenu.hooks.call('close');
+        }
+
+        defaultMenu.closeAll = function () {
+            var menuDropDowns = document.querySelectorAll("#" + defaultMenu.container + " div.app-menu-dropdown-wrap");
+            jqLite(menuDropDowns).css('display', 'none');
+            jqLite(menuDropDowns).attr("data-display-clicked", "false");
+        }
+
+        defaultMenu.setCurrentId = function (id) {
+            currentSelectedId = id;
+        }
+
+        defaultMenu.getCurrentId = function () {
+            return currentSelectedId;
+        }
+
+        defaultMenu.goToMenuItem = function (menuElement) {
+            var link, url;
+
+            if (menuElement === undefined)
+                return;
+
+            link = menuElement.querySelector('a');
+            url = jqLite(link).attr('href');
+
+            //TODO: Check if it is an actual url?
+            if (url) {
+                window.location = url;
+            }
+        }
+
+        defaultMenu.renderMenuItems = function (menuItems) {
+            var result = '';
+            //Parse item template once for each of the menu items
+            menuItems.forEach(function (menuItem) {
+                result += defaultMenu.template.parse('menuItem', {
+                    "id": menuItem.id,
+                    "name": menuItem.name,
+                    "defaultAction": menuItem.defaultAction,
+                    "icon": menuItem.icon
+                });
+            });
+            return result;
+        }
+
+        defaultMenu.getDropDownPosition = function () {
+            var menuElement = document.querySelector("#" + defaultMenu.name  + "_button"),
+                dropdownElement = jqLite(menuElement.querySelector("div.app-menu-dropdown-wrap")),
+                dropdownPosition;
+
+            dropdownElement.css('display', 'block');
+
+            // Get the dropdown width and position
+            defaultMenu.dropdownWidth = dropdownElement[0].offsetWidth;
+            defaultMenu.linkPositionX = menuElement.offsetLeft;
+
+            // Calculate the dropdown position x
+            dropdownPosition = defaultMenu.linkPositionX - (defaultMenu.dropdownWidth - menuElement.offsetWidth);
+
+            //Hide the dropdown element
+            dropdownElement.css('display', 'none');
+
+            return dropdownPosition;
+        }
+
+        defaultMenu.renderers.push(function (menuData) {
+            var linkItem, menuItems;
+
+            menuItems = defaultMenu.renderMenuItems(menuData.getApps());
+
+            //Build the menu item and dropdown
+            linkItem = defaultMenu.template.parse('linkItem', {
+                "id": defaultMenu.name,
+                "iconName": defaultMenu.icon,
+                "menuItemName": menuData.name,
+                "classes": cssDefaults.aMenuLinkClasses,
+                "menuItems": menuItems
+            });
+
+            //Create menu wrapper if it does not exist
+            if (document.querySelector('#' + defaultMenu.container + ' ul') === null) {
+                jqLite(document.querySelector('#' + defaultMenu.container)).append(
+                    defaultMenu.template.parse('menuStructure', {"id": cssDefaults.ulWrapId})
+                )
+            }
+
+            //Add the linkItem to the menu
+            jqLite(document.querySelector('#' + defaultMenu.container + ' ul')).append(linkItem);
+        });
+
+        defaultMenu.eventsHandlers.push(function (menuElement) {
+            var dropdownElement = jqLite(menuElement.querySelector("div.app-menu-dropdown-wrap"));
+
+            //Add click to show dropdown event
+            jqLite(menuElement.querySelector("a.drop-down-menu-link")).on("click", function () {
+                if (dropdownElement.attr("data-display-clicked") === "true") {
+                    defaultMenu.close();
+                } else {
+                    defaultMenu.closeAll();
+                    defaultMenu.open();
+                }
+            });
+
+            //Hover event
+            jqLite(menuElement).on('mouseenter', function() {
+                defaultMenu.open(true);
+            });
+            jqLite(menuElement).on('mouseleave', function() {
+                if (dropdownElement.attr('data-display-clicked') === "true") {
+                    return;
+                }
+                defaultMenu.close(true);
+            });
+
+            jqLite(window).on('resize', function () {
+                defaultMenu.closeAll();
+            });
+        });
+
+        defaultMenu.menuItems.subscribe(defaultMenu.render, true);
+        defaultMenu.menuItems.subscribe(function (menu) {
+            var menuElementList = document.querySelector("#" + defaultMenu.name + "_button ul.menuDropDownBox"),
+                menuItemsHtml;
+
+            if (menuElementList === null)
+                return;
+
+            menuItemsHtml = defaultMenu.renderMenuItems(menu.getApps());
+
+            jqLite(menuElementList.querySelectorAll("li")).remove();
+            jqLite(menuElementList).append(menuItemsHtml);
+            defaultMenu.setCurrentId(undefined);
+        });
+
+        return createMenu(defaultMenu);
+    }
+
+    scrollUi = function (menu) {
+        var scrollMenu = menu;
+
+        scrollMenu.template.replace('linkItem', templates.menuLinkWithScroll);
+
+        scrollMenu.eventsHandlers.push(function (menuElement) {
+            var scrollElement = menuElement.querySelector('div.menu-drop-down-scroll'),
+                scrollUpElement = menuElement.querySelector('div.apps-scroll-up'),
+                scrollDownElement = menuElement.querySelector('div.apps-scroll-down');
+
+            jqLite(scrollElement).on('scroll', function () {
+                if (scrollElement.scrollTop < 10) {
+                    scrollMenu.menuWidth = 360;
+                } else {
+                    scrollMenu.menuWidth = 384;
+                }
+                jqLite(scrollElement).parent().css('width', scrollMenu.menuWidth + 'px');
+                jqLite(scrollElement).parent().parent().css('width',scrollMenu.menuWidth + 'px');
+            });
+
+            jqLite(scrollUpElement).on('click', function (event) {
+                event.preventDefault();
+                scrollElement.scrollTop = scrollElement.scrollTop - 330;
+            });
+
+            jqLite(scrollDownElement).on('click', function (event) {
+                var scrollDistance = 330;
+                event.preventDefault();
+
+                //TODO: We should only have to do this when there is a scrollbar
+                //Compensate on first scroll for searchbar
+                if (scrollElement.scrollTop === 0) {
+                    scrollDistance += 40;
+                }
+
+                scrollElement.scrollTop = scrollElement.scrollTop + scrollDistance;
+            });
+        });
+
+        return createMenu(scrollMenu);
+    }
+
+    /**
+     * Adds search functionality to the passed menu
+     *
+     * @param menu
+     * @returns {Menu}
+     */
+    searchUi = function (menu) {
+        var searchMenu = menu,
+            rendered = false,
+            searchAppsText = '';
+
+        function performSearch(menuElement) {
+            var menuItemsHtml,
+                searchFor = jqLite(menuElement.querySelector(".apps-search")).val().toLowerCase(),
+                searchMatches,
+                menuElementList = menuElement.querySelector('ul.menuDropDownBox');
+
+            if (searchFor === '') {
+                jqLite(menuElement.querySelector(".apps-search-clear")).css("display", "none");
+                menuElement.querySelector(".apps-search").focus();
+                menuItemsHtml = searchMenu.renderMenuItems(searchMenu.menuItems.getApps());
+            } else {
+                jqLite(menuElement.querySelector(".apps-search-clear")).css("display", "block");
+                searchMatches = searchMenu.menuItems.search(searchFor);
+                menuItemsHtml = searchMenu.renderMenuItems(searchMatches);
+            }
+
+            jqLite(menuElementList.querySelectorAll('li')).remove();
+            jqLite(menuElementList).append(menuItemsHtml);
+            searchMenu.setCurrentId(undefined);
+        }
+
+        searchMenu.template.add('search', templates.search);
+
+        //Translate the search apps name
+        dhis2.translate.get(['app_search_placeholder'], function (translations) {
+            var searchBoxElement = document.querySelector('#' + searchMenu.name + "_button input.apps-search");
+
+            searchAppsText = translations.get('app_search_placeholder');
+            if (rendered === true) {
+                jqLite(searchBoxElement).attr('placeholder', searchAppsText);
+            }
+        });
+
+        searchMenu.renderers.push(function () {
+            var dropdownWrap = document.querySelector('#' + searchMenu.name + "_button div.menu-drop-down-scroll");
+            jqLite(dropdownWrap).prepend(searchMenu.template.parse('search', { search_apps: searchAppsText }));
+            rendered = true;
+        });
+
+        searchMenu.eventsHandlers.push(function (menuElement) {
+            var searchBoxElement = menuElement.querySelector("input.apps-search");
+
+            searchMenu.hooks.open.push(function () {
+                searchBoxElement.focus();
+            });
+
+            jqLite(searchBoxElement).on('keyup', function (event) {
+                //Filter the menu items
+                if ( ! keys.isArrowKey(event.which) &&
+                     ! (event.which === keys.enter) &&
+                     ! (event.which === keys.ctrl)) {
+                    performSearch(menuElement);
+                }
+            });
+
+            jqLite(menuElement.querySelector(".apps-search-clear")).on('click', function () {
+                jqLite(menuElement.querySelector(".apps-search-clear")).css("display", "none");
+                jqLite(menuElement.querySelector(".apps-search")).val("");
+                menuElement.querySelector(".apps-search").focus();
+                performSearch(menuElement);
+            });
+        });
+
+        return createMenu(searchMenu);
+    }
+
+    linkButtonUi = function (menu) {
+        var linkButtonMenu = menu,
+            rendered = false;
+
+        linkButtonMenu.template.add('extraLink', templates.extraLink);
+
+        //Translate the link name
+        dhis2.translate.get([menu.extraLink.text], function (translations) {
+            menu.extraLink.text = translations.get(menu.extraLink.text);
+            if (rendered === true) {
+                //TODO change the class of this button to make it more general
+               jqLite(document.querySelector('#' + linkButtonMenu.name + 'div.apps-menu-bottom-button')).html(menu.extraLink.text);
+            }
+        });
+
+        linkButtonMenu.renderers.push(function () {
+            var buttonContainer = document.querySelector('#' + linkButtonMenu.name + "_button div.menu-drop-down-buttons");
+            menu.extraLink.url = dhis2.menu.fixUrlIfNeeded(menu.extraLink.url);
+            jqLite(buttonContainer).prepend(linkButtonMenu.template.parse('extraLink', menu.extraLink));
+            rendered = true;
+        });
+
+        return createMenu(linkButtonMenu);
+    }
+
+    shortCutUi = function (menu) {
+        var shortCutMenu = menu;
+
+        shortCutMenu.eventsHandlers.push(function (menuElement) {
+            var currentElement,
+                shortCutElements,
+                oldFocusedElement;
+
+            function changeCurrentSelected(currentElement) {
+
+                function animateScrollTo(scrollable, scrollto) {
+                    var modifier = 2;
+                    scrollto = scrollto - 49;
+
+                    function scrollDown() {
+                        if (scrollable.scrollTop >= scrollto || scrollable.offsetHeight + 49 === scrollable.scrollTop) {
+                            return;
+                        }
+                        scrollable.scrollTop = scrollable.scrollTop + modifier;
+                        setTimeout(scrollDown, 1);
+                    }
+
+                    function scrollUp() {
+                        if (scrollable.scrollTop <= scrollto || 0 === scrollable.scrollTop)
+                            return;
+                        scrollable.scrollTop = scrollable.scrollTop - modifier;
+                        setTimeout(scrollUp, 1);
+                    }
+
+                    if (scrollable.scrollTop > scrollto) {
+                        scrollUp();
+                    } else {
+                        scrollDown();
+                    }
+                }
+
+                jqLite(shortCutMenu.selectedElement).toggleClass("selected");
+                shortCutMenu.selectedElement = shortCutElements[currentElement];
+                jqLite(shortCutMenu.selectedElement).toggleClass("selected");
+
+                if (menuElement.querySelector("div.menu-drop-down-scroll")) {
+                    animateScrollTo(menuElement.querySelector("div.menu-drop-down-scroll"), shortCutMenu.selectedElement.offsetTop);
+                }
+
+                shortCutMenu.setCurrentId(currentElement);
+            }
+
+            shortCutMenu.hooks.close.push(function () {
+                shortCutMenu.setCurrentId(undefined);
+            });
+
+            jqLite(document).on("keyup", function (event) {
+                /**
+                 * Key combination using alt to control opening and closing
+                 */
+                if (event.which === shortCutMenu.shortCutKey && event.ctrlKey) {
+                    event.preventDefault();
+
+                    if (shortCutMenu.isOpen()) {
+                        shortCutMenu.close();
+                        if (oldFocusedElement)
+                            oldFocusedElement.focus();
+                    } else {
+                        oldFocusedElement = document.activeElement;
+                        document.activeElement.blur();
+
+                        shortCutMenu.closeAll();
+                        shortCutMenu.open();
+                    }
+                }
+            });
+
+            jqLite(menuElement.querySelectorAll('input')).on("keydown", function (event) {
+                if (keys.isArrowKey(event.which)) {
+                    return false;
+                }
+            });
+
+            jqLite(document).on("keyup", function (event) {
+                var goToElement;
+
+                /**
+                 * Calculate the number of positions we have available if we fill all the rows
+                 * @returns {number}
+                 */
+                function getPositionsNumber() {
+                        return Math.ceil(shortCutElements.length / 3) * 3;
+                }
+
+                //Don't run anything when the menu is not open
+                if (shortCutMenu.isClosed()) {
+                    return;
+                }
+
+                //Prevent default behavior for any of the bound keys when the menu is open
+                event.preventDefault();
+
+                //Get the menu elements available on the dom
+                shortCutElements = menuElement.querySelectorAll("ul.menuDropDownBox li");
+
+                /**
+                 * Movement keys
+                 */
+                if (keys.isArrowKey(event.which)) {
+                    currentElement = shortCutMenu.getCurrentId();
+
+                    event.preventDefault();
+
+                    //When the first arrow button is pressed but there is no selected element use the first one
+                    if (currentElement === undefined) {
+                        currentElement = 0;
+                        changeCurrentSelected(currentElement);
+                        return;
+                    }
+
+                    if (event.which === keys.arrowRight) {
+                        currentElement = currentElement + 1;
+                        if (shortCutElements[currentElement] === undefined) {
+                            currentElement = 0;
+                        }
+                        changeCurrentSelected(currentElement);
+                        return;
+                    }
+
+                    if (event.which === keys.arrowLeft) {
+                        currentElement = currentElement - 1;
+                        if (shortCutElements[currentElement] === undefined) {
+                            currentElement = shortCutElements.length - 1;
+                        }
+                        changeCurrentSelected(currentElement);
+                        return;
+                    }
+
+                    if (event.which === keys.arrowDown) {
+                        currentElement = currentElement + 3;
+                        if (shortCutElements[currentElement] === undefined) {
+                            if (currentElement >= shortCutElements.length) {
+                                currentElement = currentElement % 3;
+                            } else {
+                                currentElement = currentElement - shortCutElements.length;
+                            }
+                        }
+                        changeCurrentSelected(currentElement);
+                        return;
+                    }
+
+                    //TODO: Clean up this code a bit as it's very confusing to what it does now.
+                    if (event.which === keys.arrowUp) {
+                        currentElement = currentElement - 3;
+                        if (shortCutElements[currentElement] === undefined) {
+                            //Jump to the last
+                            if (!((shortCutElements.length % 3) === 0)) {
+                                currentElement = getPositionsNumber() - (-currentElement);
+                                if (shortCutElements[currentElement] === undefined) {
+                                    if (currentElement === -1)
+                                        currentElement = 0;
+                                    else
+                                        currentElement = currentElement - 3;
+                                }
+                            } else {
+                                currentElement = shortCutElements.length - (-currentElement);
+                            }
+                        }
+                        changeCurrentSelected(currentElement);
+                        return;
+                    }
+                }
+
+                /**
+                 * Key to go to the selected menu item if no item is selected go to the first one
+                 */
+                if (event.which === keys.enter) {
+                    goToElement = shortCutElements[shortCutMenu.getCurrentId()];
+                    if (goToElement === undefined) {
+                        goToElement = shortCutElements[0];
+                    }
+                    shortCutMenu.goToMenuItem(goToElement);
+                }
+            });
+        });
+
+        return createMenu(shortCutMenu);
+    }
+
+    /*******************************************************************************************************************
+     * Dhis2 menu ui functions
+     ******************************************************************************************************************/
+
+    /*
+     * Create the object that we will expose (This gets attached onto the dhis2.menu global variable as dhis2.menu.ui
+     * Generally one will use just the ui version but if there is a case where the ui does not need to be used a
+     * different wrapper can be build around dhis2.menu as all the menu logic is contained in there and this ui wrapper
+     * just creates an instance of the dhis2.menu object for each of the menus that are created.
+     */
+    dhis2menu.ui = {};
+    dhis2menu.ui.createMenu = function (menuName, menuData, options) {
+        var menu;
+
+        if (typeof menuName !== "string")
+            throw MenuError("Menu name needs to be a string");
+
+        //menuData is not a string and does not have any items
+        if (typeof menuData !== "string" && menuData.length <= 0) {
+            throw MenuError("Menu should have data to present in an array or be a url to fetch data from");
+        }
+
+        //Sets default options if non have been given
+        if (options == undefined)
+            options = {};
+
+        menu = defaultMenuUi(
+                menuName,
+                menuData,
+                options['icon'] || 'th', //th is the default font-awesome icon we use for menus
+                options['container'] || 'dhisDropDownMenu'); //dhisDropDownMenu is the default container for the menu
+
+        if ( !! options['shortCut'] && keys[options['shortCut']]) {
+            menu.shortCutKey = keys[options['shortCut']];
+            menu = shortCutUi(menu);
+        }
+
+        if ( !! options['scrollable']) {
+            menu = scrollUi(menu);
+        }
+
+        if ( !! options['scrollable'] && !! options['searchable']) {
+            menu = searchUi(menu);
+        }
+
+        if (typeof options['extraLink'] === 'object' && options.extraLink['url'] && options.extraLink['text']) {
+            menu.extraLink = options['extraLink'];
+            menu = linkButtonUi(menu);
+        }
+
+        return menu;
+    }
+
+})(window.dhis2.menu = window.dhis2.menu || {}, dhis2.settings = dhis2.settings || {});
+
+/**
+ * End of menu ui code. The code below creates the menu with the default profile and apps menus
+ */
+(function () {
+    dhis2.menu.ui.initMenu = function () {
+        try {
+            dhis2.menu.ui.createMenu("profile", [
+                {
+                    name: "settings",
+                    namespace: "/dhis-web-commons-about",
+                    defaultAction: "../dhis-web-commons-about/userSettings.action",
+                    icon: "../icons/usersettings.png",
+                    description: ""
+                },
+                {
+                    name: "profile",
+                    namespace: "/dhis-web-commons-about",
+                    defaultAction: "../dhis-web-commons-about/showUpdateUserProfileForm.action",
+                    icon: "../icons/function-profile.png",
+                    description: ""
+                },
+                {
+                    name: "account",
+                    namespace: "/dhis-web-commons-about",
+                    defaultAction: "../dhis-web-commons-about/showUpdateUserAccountForm.action",
+                    icon: "../icons/function-account.png",
+                    description: ""
+                },
+                {
+                    name: "help",
+                    namespace: "/dhis-web-commons-about",
+                    defaultAction: "../dhis-web-commons-about/help.action",
+                    icon: "../icons/function-account.png",
+                    description: ""
+                },
+                {
+                    name: "log_out",
+                    namespace: "/dhis-web-commons-about",
+                    defaultAction: "../dhis-web-commons-security/logout.action",
+                    icon: "../icons/function-log-out.png",
+                    description: ""
+                },
+                {
+                    name: "about_dhis2",
+                    namespace: "/dhis-web-commons-about",
+                    defaultAction: "../dhis-web-commons-about/about.action",
+                    icon: "../icons/function-about-dhis2.png",
+                    description: ""
+                }
+            ],
+                {
+                    icon: "user",
+                    shortCut: "comma"
+                }
+            );
+
+            dhis2.menu.mainAppMenu = dhis2.menu.ui.createMenu("applications",
+                "/dhis-web-commons/menu/getModules.action",
+                {
+                    searchable: true,
+                    scrollable: true,
+                    extraLink: {
+                        text: 'more_applications',
+                        url: '../dhis-web-commons-about/modules.action'
+                    },
+                    shortCut: "m"
+                }
+            );
+
+        } catch (e) {
+            if (console && console.error)
+                console.error(e.message, e.stack);
+        }
+    }
+
+    if (window['angular']) {
+
+        /**
+         * Angular directive for the menu.
+         */
+        angular.module('d2Menu', [])
+            /**
+             * The directive places a div element with the dhisDropDownMenu id and then calls the normal menu
+             * init method to run all the normal javascript code.
+            */
+            .directive('d2Menu', [function () {
+                return {
+                    restrict: 'A',
+                    replace: true,
+                    template: '<div id="dhisDropDownMenu"></div>',
+                    //TODO: This might not be proper use of a controller
+                    controller: function () {
+                        dhis2.menu.ui.initMenu();
+                    }
+                }
+            }]);
+
+    } else {
+        //If there is no angular we just run our normal init function to find tags ourselves
+        dhis2.menu.ui.initMenu();
+    }
+
+})();
\ No newline at end of file

=== modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.translate.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.translate.js	2014-04-14 02:24:15 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.translate.js	2014-05-12 08:02:12 +0000
@@ -34,18 +34,39 @@
 
 /**
  * Created by Mark Polak on 28/01/14.
- *
- * @see jQuery (http://jquery.com)
- * @see Underscore.js (http://underscorejs.org)
  */
-(function ($,  _, translate, undefined) {
+(function (translate, undefined) {
     var translationCache = {
-        get: function (key) {
-            if (this.hasOwnProperty(key))
-                return this[key];
-            return key;
-        }
-    };
+            get: function (key) {
+                if (this.hasOwnProperty(key))
+                    return this[key];
+                return key;
+            }
+        },
+        getBaseUrl = (function () {
+            var href = window.location.origin;
+            return function () {
+                var urlParts = href.split("/"),
+                    baseUrl;
+
+                if (dhis2.settings === undefined || dhis2.settings.baseUrl === undefined) {
+                    return "..";
+                }
+
+                if (typeof dhis2.settings.baseUrl !== "string") {
+                    throw new TypeError("Dhis2 settings: baseUrl should be a string");
+                }
+
+                if (urlParts[urlParts.length - 1] !== "") {
+                    baseUrl = href + '/' + dhis2.settings.baseUrl;
+                } else {
+                    urlParts.pop();
+                    urlParts.push(dhis2.settings.baseUrl);
+                    baseUrl = urlParts.join('/');
+                }
+                return baseUrl;
+            }
+        })();
 
     /**
      * Adds translations to the translation cache (overrides already existing ones)
@@ -53,7 +74,13 @@
      * @param translations {Object}
      */
     function  addToCache(translations) {
-        translationCache = _.extend(translationCache, translations);
+        var translationIndex;
+
+        for (translationIndex in translations) {
+            if (typeof translationIndex === 'string' && translationIndex !== 'get') {
+                translationCache[translationIndex] = translations[translationIndex];
+            }
+        }
     }
 
     /**
@@ -64,18 +91,23 @@
      * @param callback {function}
      */
     function getTranslationsFromServer(translateKeys, callback) {
-        $.ajax({
-            url:"../api/i18n",
-            type:"POST",
-            data: JSON.stringify(translateKeys),
-            contentType:"application/json; charset=utf-8",
-            dataType:"json"
-        }).success(function (data) {
-                addToCache(data);
+        var http = new XMLHttpRequest();
+        var url = getBaseUrl() + "/api/i18n";
+        var keysToTranslate = JSON.stringify(translateKeys);
+        http.open("POST", url, true);
+
+        //Send the proper header information along with the request
+        http.setRequestHeader("Content-type", "application/json; charset=utf-8");
+
+        http.onreadystatechange = function() {//Call a function when the state changes.
+            if(http.readyState == 4 && http.status == 200) {
+                addToCache(JSON.parse(http.responseText));
                 if (typeof callback === 'function') {
                     callback(translationCache);
                 }
-            });
+            }
+        }
+        http.send(keysToTranslate);
     }
 
     /**
@@ -86,8 +118,7 @@
      * @param callback {function}
      */
     translate.get = function (translate, callback) {
-        var translateKeys = [],
-            key;
+        var translateKeys = [];
 
         //Only ask for the translations that we do not already have
         translate.forEach(function (text, index, translate) {
@@ -106,4 +137,4 @@
 
     };
 
-})(jQuery, _, dhis2.translate);
+})(dhis2.translate);

=== modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/main.vm'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/main.vm	2014-05-24 08:58:21 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/main.vm	2014-06-04 14:37:34 +0000
@@ -80,6 +80,7 @@
     <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.appcache.js?_rev=$!{buildRevision}"></script>
     <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.translate.js?_rev=$!{buildRevision}"></script>
     <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.menu.js?_rev=$!{buildRevision}"></script>
+    <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.menu.ui.js?_rev=$!{buildRevision}"></script>
     <script type="text/javascript" src="../dhis-web-commons/i18nJavaScript.action?_rev=$!{buildRevision}"></script>
     <script type="text/javascript" src="../main.js?_rev=$!{buildRevision}"></script>
     <script type="text/javascript" src="../request.js?_rev=$!{buildRevision}"></script>
@@ -107,79 +108,8 @@
       <span id="headerText" onclick="window.location.href='../${startModule}/index.action'" style="cursor:pointer" title="$i18n.getString( 'view_home_page' )">
         $encoder.htmlEncode( $applicationTitle )
       </span>
-      
-      <ul id="menuLinkArea">
-        <li id="profileDropDown_button">
-            <a id="profileMenuLink" class="menu-link drop-down-menu-link">
-              <i class="fa fa-user"></i>$i18n.getString( "profile" )
-          </a>
-        </li>
-        <li id="appsDropDown_button">
-          <a id="appsMenuLink" class="menu-link drop-down-menu-link">
-            <i class="fa fa-th"></i>$i18n.getString( "applications" )
-          </a>
-          </li>
-      </ul>
-
-      <div id="appsDropDown" class="menuDropDownArea app-menu-dropdown appsMenuLink_menu ui-front" >
-        <div class="caret-up-border"></div>
-        <div class="caret-up-background"></div>
-        <div class="menu-drop-down-wrap">
-            <div class="menu-drop-down-scroll">
-                <div class="apps-search-wrap">
-                  <input id="apps-search" class="apps-search" type="text" placeholder="$i18n.getString( "app_search_placeholder" )">
-                  <span id="apps-search-clear" class="fa fa-times-circle"></span>
-                </div>
-              <ul class="menuDropDownBox"><li class="menu-placeholder"><img src="../images/ajax-loader-bar.gif"></li></ul>
-            </div>
-        </div>
-        <div class="apps-menu-bottom-button apps-menu-more"><a href="../dhis-web-commons-about/modules.action">$i18n.getString( "more_applications" )</a></div>
-          <div class="apps-menu-bottom-button apps-scroll apps-scroll-up"><a class="fa fa-caret-up" href="#"></a></div>
-          <div class="apps-menu-bottom-button apps-scroll apps-scroll-down"><a class="fa fa-caret-down" href="#"></a></div>
-      </div>
-
-	  <div id="profileDropDown" class="menuDropDownArea app-menu-dropdown ui-helper-clearfix profileMenuLink_menu">
-        <div class="caret-up-border"></div>
-        <div class="caret-up-background"></div>
-        <ul class="menuDropDownBox">
-          <li>
-              <a class="app-menu-item" href="../dhis-web-commons-about/userSettings.action">
-                <img src="../icons/usersettings.png" alt="$i18n.getString( "settings" )">
-                <span>$i18n.getString( "settings" )&nbsp;</span>
-              </a>
-          </li>
-          <li>
-              <a class="app-menu-item" href="../dhis-web-commons-about/showUpdateUserProfileForm.action">
-                  <img src="../icons/function-profile.png" alt="$i18n.getString( "profile" )">
-                  <span>$i18n.getString( "profile" )&nbsp;</span>
-              </a>
-          </li>
-          <li>
-              <a class="app-menu-item" href="../dhis-web-commons-about/showUpdateUserAccountForm.action">
-                <img src="../icons/function-account.png" alt="$i18n.getString( "account" )">
-                <span>$i18n.getString( "account" )&nbsp;</span>
-              </a>
-          </li>
-          <li>
-              <a class="app-menu-item" href="../dhis-web-commons-about/help.action">
-                <img src="../icons/function-help-center.png" alt="$i18n.getString( "help" )">
-                <span>$i18n.getString( "help" )&nbsp;</span>
-              </a>
-          </li>
-          <li>
-              <a class="app-menu-item" href="../dhis-web-commons-security/logout.action">
-                <img src="../icons/function-log-out.png" alt="$i18n.getString( "log_out" )">
-                <span>$i18n.getString( "log_out" )&nbsp;</span>
-              </a>
-          </li>
-          <li>
-              <a class="app-menu-item" href="../dhis-web-commons-about/about.action">
-                <img src="../icons/function-about-dhis2.png" alt="$i18n.getString( "about_dhis2" )">
-                <span>$i18n.getString( "about_dhis2" )&nbsp;</span>
-              </a>
-          </li>
-        </ul>
-	  </div>
+
+      <div id="dhisDropDownMenu"></div>
 	  
       <span id="showLeftBar">
         <a href="javascript:dhis2.leftBar.showAnimated()" title="$i18n.getString( 'show_menu' )">

=== modified file 'dhis-2/dhis-web/dhis-web-event-capture/src/main/webapp/dhis-web-event-capture/event-capture.appcache'
--- dhis-2/dhis-web/dhis-web-event-capture/src/main/webapp/dhis-web-event-capture/event-capture.appcache	2014-06-03 09:51:54 +0000
+++ dhis-2/dhis-web/dhis-web-event-capture/src/main/webapp/dhis-web-event-capture/event-capture.appcache	2014-06-12 23:12:59 +0000
@@ -85,7 +85,8 @@
 ../dhis-web-commons/javascripts/dhis2/dhis2.appcache.js
 ../dhis-web-commons/ouwt/ouwt.js
 ../dhis-web-commons/javascripts/dhis2/dhis2.translate.js
-../dhis-web-commons/javascripts/dhis2/dhis2.menu.js
+../dhis-web-commons/javascripts/dhis2/dhis.menu.js
+../dhis-web-commons/javascripts/dhis2/dhis.menu.ui.js
 
 scripts/event-capture.js  
 scripts/app.js

=== modified file 'dhis-2/dhis-web/dhis-web-event-capture/src/main/webapp/dhis-web-event-capture/i18n/en.json'
--- dhis-2/dhis-web/dhis-web-event-capture/src/main/webapp/dhis-web-event-capture/i18n/en.json	2014-06-02 10:50:49 +0000
+++ dhis-2/dhis-web/dhis-web-event-capture/src/main/webapp/dhis-web-event-capture/i18n/en.json	2014-06-12 23:12:59 +0000
@@ -71,14 +71,5 @@
     "no": "NO",
     "yes": "YES",   
     "offline_notification": "You are offline, data will be stored locally",
-    "online_nofification": "You are online",
-    "profile": "Profile",
-    "applications": "Apps",
-    "more_applications": "More apps",
-    "search_apps": "Search apps",
-    "settings": "Settings",
-    "account": "Account",
-    "help": "Help",
-    "log_out": "Log out",
-    "about_dhis2": "About DHIS 2"
+    "online_nofification": "You are online"
 }

=== modified file 'dhis-2/dhis-web/dhis-web-event-capture/src/main/webapp/dhis-web-event-capture/i18n/fr.json'
--- dhis-2/dhis-web/dhis-web-event-capture/src/main/webapp/dhis-web-event-capture/i18n/fr.json	2014-05-24 08:58:21 +0000
+++ dhis-2/dhis-web/dhis-web-event-capture/src/main/webapp/dhis-web-event-capture/i18n/fr.json	2014-06-12 23:12:59 +0000
@@ -68,14 +68,5 @@
     "no": "NON",
     "yes": "OUI",   
     "offline_notification": "Vous êtes actuellement hors connexion, vos données seront stockées localement",
-    "online_nofification": "Vous êtes connecté",
-    "profile": "Profil",
-    "applications": "Apps",
-    "more_applications": "Plus d'apps",
-    "search_apps": "Recherche d'apps",
-    "settings": "Paramètres",
-    "account": "Compte",
-    "help": "Aide",
-    "log_out": "Déconnexion",
-    "about_dhis2": "À propos de DHIS 2"
+    "online_nofification": "Vous êtes connecté"
 }
\ No newline at end of file

=== modified file 'dhis-2/dhis-web/dhis-web-event-capture/src/main/webapp/dhis-web-event-capture/index.html'
--- dhis-2/dhis-web/dhis-web-event-capture/src/main/webapp/dhis-web-event-capture/index.html	2014-06-03 09:51:54 +0000
+++ dhis-2/dhis-web/dhis-web-event-capture/src/main/webapp/dhis-web-event-capture/index.html	2014-06-12 23:12:59 +0000
@@ -64,6 +64,7 @@
         <!-- Menu scripts -->
         <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.translate.js"></script>
         <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.menu.js"></script>
+        <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.menu.ui.js"></script>
 
         <link type="text/css" rel="stylesheet" href="../dhis-web-commons/font-awesome/css/font-awesome.min.css"/>
         <link type="text/css" rel="stylesheet" media="screen,print" href="../dhis-web-commons/css/light_blue/light_blue.css"/>
@@ -80,81 +81,8 @@
                 <img ng-click="home()" id="headerBanner" src="../dhis-web-commons/css/light_blue/logo_banner.png" style="cursor:pointer" title="{{'dhis2_home'| translate}}">                    
             </div>            
             <div id="headerMessage" class="bold"></div>
-            
-            <!-- Menu html -->
-            <ul id="menuLinkArea">
-                <li id="profileDropDown_button">
-                    <a id="profileMenuLink" class="menu-link drop-down-menu-link">
-                        <i class="fa fa-user"></i>{{'profile'| translate}}
-                    </a>
-                </li>
-                <li id="appsDropDown_button">
-                    <a id="appsMenuLink" class="menu-link drop-down-menu-link">
-                        <i class="fa fa-th"></i>{{'applications'| translate}}
-                    </a>
-                </li>
-            </ul>
-
-            <div id="appsDropDown" class="menuDropDownArea app-menu-dropdown appsMenuLink_menu" >
-                <div class="caret-up-border"></div>
-                <div class="caret-up-background"></div>
-                <div class="menu-drop-down-wrap">
-                    <div class="menu-drop-down-scroll">
-                        <div class="apps-search-wrap">
-                            <input id="apps-search" class="apps-search" type="text" placeholder="{{'search_apps' | translate}}">
-                            <span id="apps-search-clear" class="fa fa-times-circle"></span>
-                        </div>
-                        <ul class="menuDropDownBox"><li class="menu-placeholder"><img src="../images/ajax-loader-bar.gif"></li></ul>
-                    </div>
-                </div>
-                <div class="apps-menu-bottom-button apps-menu-more"><a href="../dhis-web-commons-about/modules.action">{{'more_applications'| translate}}</a></div>
-                <div class="apps-menu-bottom-button apps-scroll apps-scroll-up"><a class="fa fa-caret-up" href="#"></a></div>
-                <div class="apps-menu-bottom-button apps-scroll apps-scroll-down"><a class="fa fa-caret-down" href="#"></a></div>
-            </div>
-
-            <div id="profileDropDown" class="menuDropDownArea app-menu-dropdown ui-helper-clearfix profileMenuLink_menu">
-                <div class="caret-up-border"></div>
-                <div class="caret-up-background"></div>
-                <ul class="menuDropDownBox">
-                    <li>
-                        <a class="app-menu-item" href="../dhis-web-commons-about/userSettings.action">
-                            <img src="../icons/usersettings.png" alt="{{'settings'| translate}}">
-                            <span>{{'settings'| translate}}&nbsp;</span>
-                        </a>
-                    </li>
-                    <li>
-                        <a class="app-menu-item" href="../dhis-web-commons-about/showUpdateUserProfileForm.action">
-                            <img src="../icons/function-profile.png" alt="{{'profile'| translate}}">
-                            <span>{{'settings'| translate}}&nbsp;</span>
-                        </a>
-                    </li>
-                    <li>
-                        <a class="app-menu-item" href="../dhis-web-commons-about/showUpdateUserAccountForm.action">
-                            <img src="../icons/function-account.png" alt="{{'account' | translate}}">
-                            <span>{{'account' | translate}}&nbsp;</span>
-                        </a>
-                    </li>
-                    <li>
-                        <a class="app-menu-item" href="../dhis-web-commons-about/help.action">
-                            <img src="../icons/function-help-center.png" alt="{{'help' | translate}}">
-                            <span>{{'help' | translate}}&nbsp;</span>
-                        </a>
-                    </li>
-                    <li>
-                        <a class="app-menu-item" href="../dhis-web-commons-security/logout.action">
-                            <img src="../icons/function-log-out.png" alt="{{'log_out' | translate}}">
-                            <span>{{'log_out' | translate}}&nbsp;</span>
-                        </a>
-                    </li>
-                    <li>
-                        <a class="app-menu-item" href="../dhis-web-commons-about/about.action">
-                            <img src="../icons/function-about-dhis2.png" alt="{{'about_dhis2' | translate}}">
-                            <span>{{'about_dhis2' | translate}}&nbsp;</span>
-                        </a>
-                    </li>
-                </ul>
-            </div>
-            <!-- /Menu html -->
+
+            <div d2-menu></div>
 
         </div>        
         

=== modified file 'dhis-2/dhis-web/dhis-web-event-capture/src/main/webapp/dhis-web-event-capture/scripts/app.js'
--- dhis-2/dhis-web/dhis-web-event-capture/src/main/webapp/dhis-web-event-capture/scripts/app.js	2014-04-02 09:43:14 +0000
+++ dhis-2/dhis-web/dhis-web-event-capture/src/main/webapp/dhis-web-event-capture/scripts/app.js	2014-06-12 23:12:59 +0000
@@ -11,7 +11,8 @@
 		  'eventCaptureServices',
 		  'eventCaptureFilters',
 		  'angularLocalStorage', 
-		  'pascalprecht.translate'])
+		  'pascalprecht.translate',
+          'd2Menu'])
               
 .value('DHIS2URL', '..')