=== 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 @@
+ + === 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 += '
  • '; - markup += ' '; - markup += ' '; - markup += ' ${name}'; - markup += '
    ${name}

    ${description}

    '; - markup += '
    '; - markup += '
  • '; - - $.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($('
    ').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($('')); - }); - - 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 = '
  • Unable to load your apps, click to refresh
  • '; - $('#' + selector).addClass('app-menu').html(''); - $('#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 += '
  • '; + markup += ' '; + markup += ' '; + markup += ' ${name}'; + markup += '
    ${name}

    ${description}

    '; + markup += '
    '; + markup += '
  • '; + + $.template('appMenuItemTemplate', markup); + + function renderAppManager(selector) { + var apps = menu.getOrderedAppList(); + $('#' + selector).html(''); + $('#' + selector).append($('
    ').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($('')); + }); + + 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 = '' + + '
  • ' + + '' + + '' + + '{{name}}' + + '
    {{name}}

    {{description}}

    ' + + '
    ' + + '
  • '; + + + templates.menuLink = '
  • ' + + '{{menuItemName}}' + + '
    ' + + '' + + '
    ' + + '
  • '; + + templates.menuLinkWithScroll = '
  • ' + + '{{menuItemName}}' + + '
    ' + + '' + + '
    ' + + '
  • '; + + templates.search = '
    ' + + '' + + '' + + '
    '; + + templates.extraLink = '
    {{text}}
    '; + + 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 = '
  • Unable to load your apps, click to refresh
  • '; + $('#' + selector).addClass('app-menu').html(''); + $('#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', ''); + 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: '
    ', + //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 @@ + @@ -107,79 +108,8 @@ $encoder.htmlEncode( $applicationTitle ) - - - - - - + +
    === 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 @@ + @@ -80,81 +81,8 @@
    - - -
    - - - - - + +
    === 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', '..')