diff --git a/js/dashboard.js b/js/dashboard.js
index 13785abca..2f353c5a6 100644
--- a/js/dashboard.js
+++ b/js/dashboard.js
@@ -27,7 +27,6 @@ $(function()
.addClass('itop-dashboard');
this.ajax_div = $('
').appendTo(this.element);
- $('.itop-dashboard').bind('keyup.dashboard_editor', function(event) { me._on_keyup(event); } );
this._make_draggable();
},
@@ -189,10 +188,6 @@ $(function()
}
},
});
- },
- _on_keyup: function(event)
- {
- console.log('Key pressed in Dashlet');
}
});
});
\ No newline at end of file
diff --git a/js/fg.menu.js b/js/fg.menu.js
new file mode 100755
index 000000000..2c22fc97e
--- /dev/null
+++ b/js/fg.menu.js
@@ -0,0 +1,653 @@
+/*--------------------------------------------------------------------
+Scripts for creating and manipulating custom menus based on standard markup
+Version: 3.0, 03.31.2009
+
+By: Maggie Costello Wachs (maggie@filamentgroup.com) and Scott Jehl (scott@filamentgroup.com)
+ http://www.filamentgroup.com
+ * reference articles: http://www.filamentgroup.com/lab/jquery_ipod_style_drilldown_menu/
+
+Copyright (c) 2009 Filament Group
+Dual licensed under the MIT (filamentgroup.com/examples/mit-license.txt) and GPL (filamentgroup.com/examples/gpl-license.txt) licenses.
+--------------------------------------------------------------------*/
+
+
+var allUIMenus = [];
+
+$.fn.menu = function(options){
+ var caller = this;
+ var options = options;
+ var m = new Menu(caller, options);
+ allUIMenus.push(m);
+
+ $(this)
+ .mousedown(function(){
+ if (!m.menuOpen) { m.showLoading(); };
+ })
+ .click(function(){
+ if (m.menuOpen == false) { m.showMenu(); }
+ else { m.kill(); };
+ return false;
+ });
+};
+
+function Menu(caller, options){
+ var menu = this;
+ var caller = $(caller);
+ var container = $('');
+
+ this.menuOpen = false;
+ this.menuExists = false;
+
+ var options = jQuery.extend({
+ content: null,
+ width: 180, // width of menu container, must be set or passed in to calculate widths of child menus
+ maxHeight: 180, // max height of menu (if a drilldown: height does not include breadcrumb)
+ positionOpts: {
+ posX: 'left',
+ posY: 'bottom',
+ offsetX: 0,
+ offsetY: 0,
+ directionH: 'right',
+ directionV: 'down',
+ detectH: true, // do horizontal collision detection
+ detectV: true, // do vertical collision detection
+ linkToFront: false
+ },
+ showSpeed: 200, // show/hide speed in milliseconds
+ callerOnState: 'ui-state-active', // class to change the appearance of the link/button when the menu is showing
+ loadingState: 'ui-state-loading', // class added to the link/button while the menu is created
+ linkHover: 'ui-state-hover', // class for menu option hover state
+ linkHoverSecondary: 'li-hover', // alternate class, may be used for multi-level menus
+ // ----- multi-level menu defaults -----
+ crossSpeed: 200, // cross-fade speed for multi-level menus
+ crumbDefaultText: 'Choose an option:',
+ backLink: true, // in the ipod-style menu: instead of breadcrumbs, show only a 'back' link
+ backLinkText: 'Back',
+ flyOut: false, // multi-level menus are ipod-style by default; this parameter overrides to make a flyout instead
+ flyOutOnState: 'ui-state-default',
+ nextMenuLink: 'ui-icon-triangle-1-e', // class to style the link (specifically, a span within the link) used in the multi-level menu to show the next level
+ topLinkText: 'All',
+ nextCrumbLink: 'ui-icon-carat-1-e'
+ }, options);
+
+ var killAllMenus = function(){
+ $.each(allUIMenus, function(i){
+ if (allUIMenus[i].menuOpen) { allUIMenus[i].kill(); };
+ });
+ };
+
+ this.kill = function(){
+ caller
+ .removeClass(options.loadingState)
+ .removeClass('fg-menu-open')
+ .removeClass(options.callerOnState);
+ container.find('li').removeClass(options.linkHoverSecondary).find('a').removeClass(options.linkHover);
+ if (options.flyOutOnState) { container.find('li a').removeClass(options.flyOutOnState); };
+ if (options.callerOnState) { caller.removeClass(options.callerOnState); };
+ if (container.is('.fg-menu-ipod')) { menu.resetDrilldownMenu(); };
+ if (container.is('.fg-menu-flyout')) { menu.resetFlyoutMenu(); };
+ container.parent().hide();
+ menu.menuOpen = false;
+ $(document).unbind('click', killAllMenus);
+ $(document).unbind('keydown');
+ };
+
+ this.showLoading = function(){
+ caller.addClass(options.loadingState);
+ };
+
+ this.showMenu = function(){
+ killAllMenus();
+ if (!menu.menuExists) { menu.create() };
+ caller
+ .addClass('fg-menu-open')
+ .addClass(options.callerOnState);
+ container.parent().show().click(function(){ menu.kill(); return false; });
+ container.hide().slideDown(options.showSpeed).find('.fg-menu:eq(0)');
+ menu.menuOpen = true;
+ caller.removeClass(options.loadingState);
+ $(document).click(killAllMenus);
+
+ // assign key events
+ $(document).keydown(function(event){
+ var e;
+ if (event.which !="") { e = event.which; }
+ else if (event.charCode != "") { e = event.charCode; }
+ else if (event.keyCode != "") { e = event.keyCode; }
+
+ var menuType = ($(event.target).parents('div').is('.fg-menu-flyout')) ? 'flyout' : 'ipod' ;
+
+ switch(e) {
+ case 37: // left arrow
+ if (menuType == 'flyout') {
+ $(event.target).trigger('mouseout');
+ if ($('.'+options.flyOutOnState).size() > 0) { $('.'+options.flyOutOnState).trigger('mouseover'); };
+ };
+
+ if (menuType == 'ipod') {
+ $(event.target).trigger('mouseout');
+ if ($('.fg-menu-footer').find('a').size() > 0) { $('.fg-menu-footer').find('a').trigger('click'); };
+ if ($('.fg-menu-header').find('a').size() > 0) { $('.fg-menu-current-crumb').prev().find('a').trigger('click'); };
+ if ($('.fg-menu-current').prev().is('.fg-menu-indicator')) {
+ $('.fg-menu-current').prev().trigger('mouseover');
+ };
+ };
+ return false;
+ break;
+
+ case 38: // up arrow
+ if ($(event.target).is('.' + options.linkHover)) {
+ var prevLink = $(event.target).parent().prev().find('a:eq(0)');
+ if (prevLink.size() > 0) {
+ $(event.target).trigger('mouseout');
+ prevLink.trigger('mouseover');
+ };
+ }
+ else { container.find('a:eq(0)').trigger('mouseover'); }
+ return false;
+ break;
+
+ case 39: // right arrow
+ if ($(event.target).is('.fg-menu-indicator')) {
+ if (menuType == 'flyout') {
+ $(event.target).next().find('a:eq(0)').trigger('mouseover');
+ }
+ else if (menuType == 'ipod') {
+ $(event.target).trigger('click');
+ setTimeout(function(){
+ $(event.target).next().find('a:eq(0)').trigger('mouseover');
+ }, options.crossSpeed);
+ };
+ };
+ return false;
+ break;
+
+ case 40: // down arrow
+ if ($(event.target).is('.' + options.linkHover)) {
+ var nextLink = $(event.target).parent().next().find('a:eq(0)');
+ if (nextLink.size() > 0) {
+ $(event.target).trigger('mouseout');
+ nextLink.trigger('mouseover');
+ };
+ }
+ else { container.find('a:eq(0)').trigger('mouseover'); }
+ return false;
+ break;
+
+ case 27: // escape
+ killAllMenus();
+ break;
+
+ case 13: // enter
+ if ($(event.target).is('.fg-menu-indicator') && menuType == 'ipod') {
+ $(event.target).trigger('click');
+ setTimeout(function(){
+ $(event.target).next().find('a:eq(0)').trigger('mouseover');
+ }, options.crossSpeed);
+ };
+ break;
+ };
+ });
+ };
+
+ this.create = function(){
+ container.css({ width: options.width, 'max-height': options.maxHeight, 'overflow': 'auto' }).appendTo('body').find('ul:first').not('.fg-menu-breadcrumb').addClass('fg-menu');
+ container.find('ul, li a').addClass('ui-corner-all');
+
+ // aria roles & attributes
+ container.find('ul').attr('role', 'menu').eq(0).attr('aria-activedescendant','active-menuitem').attr('aria-labelledby', caller.attr('id'));
+ container.find('li').attr('role', 'menuitem');
+ container.find('li:has(ul)').attr('aria-haspopup', 'true').find('ul').attr('aria-expanded', 'false');
+ container.find('a').attr('tabindex', '-1');
+
+ // when there are multiple levels of hierarchy, create flyout or drilldown menu
+ if (container.find('ul').size() > 1) {
+ if (options.flyOut) { menu.flyout(container, options); }
+ else { menu.drilldown(container, options); }
+ }
+ else {
+ container.find('a').click(function(){
+ menu.chooseItem(this);
+ return false;
+ });
+ };
+
+ if (options.linkHover) {
+ var allLinks = container.find('.fg-menu li a');
+ allLinks.hover(
+ function(){
+ var menuitem = $(this);
+ $('.'+options.linkHover).removeClass(options.linkHover).blur().parent().removeAttr('id');
+ $(this).addClass(options.linkHover).focus().parent().attr('id','active-menuitem');
+ },
+ function(){
+ $(this).removeClass(options.linkHover).blur().parent().removeAttr('id');
+ }
+ );
+ };
+
+ if (options.linkHoverSecondary) {
+ container.find('.fg-menu li').hover(
+ function(){
+ $(this).siblings('li').removeClass(options.linkHoverSecondary);
+ if (options.flyOutOnState) { $(this).siblings('li').find('a').removeClass(options.flyOutOnState); }
+ $(this).addClass(options.linkHoverSecondary);
+ },
+ function(){ $(this).removeClass(options.linkHoverSecondary); }
+ );
+ };
+
+ menu.setPosition(container, caller, options);
+ menu.menuExists = true;
+ };
+
+ this.chooseItem = function(item){
+ menu.kill();
+ if (options.callback)
+ {
+ options.callback({item: $(item), text: $(item).text() })
+ }
+ };
+};
+
+Menu.prototype.flyout = function(container, options) {
+ var menu = this;
+
+ this.resetFlyoutMenu = function(){
+ var allLists = container.find('ul ul');
+ allLists.removeClass('ui-widget-content').hide();
+ };
+
+ container.addClass('fg-menu-flyout').find('li:has(ul)').each(function(){
+ var linkWidth = container.width();
+ var showTimer, hideTimer;
+ var allSubLists = $(this).find('ul');
+
+ allSubLists.css({ left: linkWidth, width: linkWidth }).hide();
+
+ $(this).find('a:eq(0)').addClass('fg-menu-indicator').html('' + $(this).find('a:eq(0)').text() + '').hover(
+ function(){
+ clearTimeout(hideTimer);
+ var subList = $(this).next();
+ if (!fitVertical(subList, $(this).offset().top)) { subList.css({ top: 'auto', bottom: 0 }); };
+ if (!fitHorizontal(subList, $(this).offset().left + 100)) { subList.css({ left: 'auto', right: linkWidth, 'z-index': 999 }); };
+ showTimer = setTimeout(function(){
+ subList.addClass('ui-widget-content').show(options.showSpeed).attr('aria-expanded', 'true');
+ }, 300);
+ },
+ function(){
+ clearTimeout(showTimer);
+ var subList = $(this).next();
+ hideTimer = setTimeout(function(){
+ subList.removeClass('ui-widget-content').hide(options.showSpeed).attr('aria-expanded', 'false');
+ }, 400);
+ }
+ );
+
+ $(this).find('ul a').hover(
+ function(){
+ clearTimeout(hideTimer);
+ if ($(this).parents('ul').prev().is('a.fg-menu-indicator')) {
+ $(this).parents('ul').prev().addClass(options.flyOutOnState);
+ }
+ },
+ function(){
+ hideTimer = setTimeout(function(){
+ allSubLists.hide(options.showSpeed);
+ container.find(options.flyOutOnState).removeClass(options.flyOutOnState);
+ }, 500);
+ }
+ );
+ });
+
+ container.find('a').click(function(){
+ menu.chooseItem(this);
+ return false;
+ });
+};
+
+
+Menu.prototype.drilldown = function(container, options) {
+ var menu = this;
+ var topList = container.find('.fg-menu');
+ var breadcrumb = $('');
+ var crumbDefaultHeader = $('');
+ var firstCrumbText = (options.backLink) ? options.backLinkText : options.topLinkText;
+ var firstCrumbClass = (options.backLink) ? 'fg-menu-prev-list' : 'fg-menu-all-lists';
+ var firstCrumbLinkClass = (options.backLink) ? 'ui-state-default ui-corner-all' : '';
+ var firstCrumbIcon = (options.backLink) ? '' : '';
+ var firstCrumb = $('- '+firstCrumbIcon+firstCrumbText+'
');
+
+ container.addClass('fg-menu-ipod');
+
+ if (options.backLink) { breadcrumb.addClass('fg-menu-footer').appendTo(container).hide(); }
+ else { breadcrumb.addClass('fg-menu-header').prependTo(container); };
+ breadcrumb.append(crumbDefaultHeader);
+
+ var checkMenuHeight = function(el){
+ if (el.height() > options.maxHeight) { el.addClass('fg-menu-scroll') };
+ el.css({ height: options.maxHeight });
+ };
+
+ var resetChildMenu = function(el){ el.removeClass('fg-menu-scroll').removeClass('fg-menu-current').height('auto'); };
+
+ this.resetDrilldownMenu = function(){
+ $('.fg-menu-current').removeClass('fg-menu-current');
+ topList.animate({ left: 0 }, options.crossSpeed, function(){
+ $(this).find('ul').each(function(){
+ $(this).hide();
+ resetChildMenu($(this));
+ });
+ topList.addClass('fg-menu-current');
+ });
+ $('.fg-menu-all-lists').find('span').remove();
+ breadcrumb.empty().append(crumbDefaultHeader);
+ $('.fg-menu-footer').empty().hide();
+ checkMenuHeight(topList);
+ };
+
+ topList
+ .addClass('fg-menu-content fg-menu-current ui-widget-content ui-helper-clearfix')
+ .css({ width: container.width() })
+ .find('ul')
+ .css({ width: container.width(), left: container.width() })
+ .addClass('ui-widget-content')
+ .hide();
+ checkMenuHeight(topList);
+
+ topList.find('a').each(function(){
+ // if the link opens a child menu:
+ if ($(this).next().is('ul')) {
+ $(this)
+ .addClass('fg-menu-indicator')
+ .each(function(){ $(this).html('' + $(this).text() + ''); })
+ .click(function(){ // ----- show the next menu
+ var nextList = $(this).next();
+ var parentUl = $(this).parents('ul:eq(0)');
+ var parentLeft = (parentUl.is('.fg-menu-content')) ? 0 : parseFloat(topList.css('left'));
+ var nextLeftVal = Math.round(parentLeft - parseFloat(container.width()));
+ var footer = $('.fg-menu-footer');
+
+ // show next menu
+ resetChildMenu(parentUl);
+ checkMenuHeight(nextList);
+ topList.animate({ left: nextLeftVal }, options.crossSpeed);
+ nextList.show().addClass('fg-menu-current').attr('aria-expanded', 'true');
+
+ var setPrevMenu = function(backlink){
+ var b = backlink;
+ var c = $('.fg-menu-current');
+ var prevList = c.parents('ul:eq(0)');
+ c.hide().attr('aria-expanded', 'false');
+ resetChildMenu(c);
+ checkMenuHeight(prevList);
+ prevList.addClass('fg-menu-current').attr('aria-expanded', 'true');
+ if (prevList.hasClass('fg-menu-content')) { b.remove(); footer.hide(); };
+ };
+
+ // initialize "back" link
+ if (options.backLink) {
+ if (footer.find('a').size() == 0) {
+ footer.show();
+ $(' Back')
+ .appendTo(footer)
+ .click(function(){ // ----- show the previous menu
+ var b = $(this);
+ var prevLeftVal = parseFloat(topList.css('left')) + container.width();
+ topList.animate({ left: prevLeftVal }, options.crossSpeed, function(){
+ setPrevMenu(b);
+ });
+ return false;
+ });
+ }
+ }
+ // or initialize top breadcrumb
+ else {
+ if (breadcrumb.find('li').size() == 1){
+ breadcrumb.empty().append(firstCrumb);
+ firstCrumb.find('a').click(function(){
+ menu.resetDrilldownMenu();
+ return false;
+ });
+ }
+ $('.fg-menu-current-crumb').removeClass('fg-menu-current-crumb');
+ var crumbText = $(this).find('span:eq(0)').text();
+ var newCrumb = $('');
+ newCrumb
+ .appendTo(breadcrumb)
+ .find('a').click(function(){
+ if ($(this).parent().is('.fg-menu-current-crumb')){
+ menu.chooseItem(this);
+ }
+ else {
+ var newLeftVal = - ($('.fg-menu-current').parents('ul').size() - 1) * 180;
+ topList.animate({ left: newLeftVal }, options.crossSpeed, function(){
+ setPrevMenu();
+ });
+
+ // make this the current crumb, delete all breadcrumbs after this one, and navigate to the relevant menu
+ $(this).parent().addClass('fg-menu-current-crumb').find('span').remove();
+ $(this).parent().nextAll().remove();
+ };
+ return false;
+ });
+ newCrumb.prev().append(' ');
+ };
+ return false;
+ });
+ }
+ // if the link is a leaf node (doesn't open a child menu)
+ else {
+ $(this).click(function(){
+ menu.chooseItem(this);
+ return false;
+ });
+ };
+ });
+};
+
+
+/* Menu.prototype.setPosition parameters (defaults noted with *):
+ referrer = the link (or other element) used to show the overlaid object
+ settings = can override the defaults:
+ - posX/Y: where the top left corner of the object should be positioned in relation to its referrer.
+ X: left*, center, right
+ Y: top, center, bottom*
+ - offsetX/Y: the number of pixels to be offset from the x or y position. Can be a positive or negative number.
+ - directionH/V: where the entire menu should appear in relation to its referrer.
+ Horizontal: left*, right
+ Vertical: up, down*
+ - detectH/V: detect the viewport horizontally / vertically
+ - linkToFront: copy the menu link and place it on top of the menu (visual effect to make it look like it overlaps the object) */
+
+Menu.prototype.setPosition = function(widget, caller, options) {
+ var el = widget;
+ var referrer = caller;
+ var dims = {
+ refX: referrer.offset().left,
+ refY: referrer.offset().top,
+ refW: referrer.getTotalWidth(),
+ refH: referrer.getTotalHeight()
+ };
+ var options = options;
+ var xVal, yVal;
+
+ var helper = $('');
+ helper.css({ position: 'absolute', left: dims.refX, top: dims.refY, width: dims.refW, height: dims.refH });
+ el.wrap(helper);
+
+ // get X pos
+ switch(options.positionOpts.posX) {
+ case 'left': xVal = 0;
+ break;
+ case 'center': xVal = dims.refW / 2;
+ break;
+ case 'right': xVal = dims.refW;
+ break;
+ };
+
+ // get Y pos
+ switch(options.positionOpts.posY) {
+ case 'top': yVal = 0;
+ break;
+ case 'center': yVal = dims.refH / 2;
+ break;
+ case 'bottom': yVal = dims.refH;
+ break;
+ };
+
+ // add the offsets (zero by default)
+ xVal += options.positionOpts.offsetX;
+ yVal += options.positionOpts.offsetY;
+
+ // position the object vertically
+ if (options.positionOpts.directionV == 'up') {
+ el.css({ top: 'auto', bottom: yVal });
+ if (options.positionOpts.detectV && !fitVertical(el)) {
+ el.css({ bottom: 'auto', top: yVal });
+ }
+ }
+ else {
+ el.css({ bottom: 'auto', top: yVal });
+ if (options.positionOpts.detectV && !fitVertical(el)) {
+ el.css({ top: 'auto', bottom: yVal });
+ }
+ };
+
+ // and horizontally
+ if (options.positionOpts.directionH == 'left') {
+ el.css({ left: 'auto', right: xVal });
+ if (options.positionOpts.detectH && !fitHorizontal(el)) {
+ el.css({ right: 'auto', left: xVal });
+ }
+ }
+ else {
+ el.css({ right: 'auto', left: xVal });
+ if (options.positionOpts.detectH && !fitHorizontal(el)) {
+ el.css({ left: 'auto', right: xVal });
+ }
+ };
+
+ // if specified, clone the referring element and position it so that it appears on top of the menu
+ if (options.positionOpts.linkToFront) {
+ referrer.clone().addClass('linkClone').css({
+ position: 'absolute',
+ top: 0,
+ right: 'auto',
+ bottom: 'auto',
+ left: 0,
+ width: referrer.width(),
+ height: referrer.height()
+ }).insertAfter(el);
+ };
+};
+
+
+/* Utilities to sort and find viewport dimensions */
+
+function sortBigToSmall(a, b) { return b - a; };
+
+jQuery.fn.getTotalWidth = function(){
+ return $(this).width() + parseInt($(this).css('paddingRight')) + parseInt($(this).css('paddingLeft')) + parseInt($(this).css('borderRightWidth')) + parseInt($(this).css('borderLeftWidth'));
+};
+
+jQuery.fn.getTotalHeight = function(){
+ return $(this).height() + parseInt($(this).css('paddingTop')) + parseInt($(this).css('paddingBottom')) + parseInt($(this).css('borderTopWidth')) + parseInt($(this).css('borderBottomWidth'));
+};
+
+function getScrollTop(){
+ return self.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
+};
+
+function getScrollLeft(){
+ return self.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft;
+};
+
+function getWindowHeight(){
+ var de = document.documentElement;
+ return self.innerHeight || (de && de.clientHeight) || document.body.clientHeight;
+};
+
+function getWindowWidth(){
+ var de = document.documentElement;
+ return self.innerWidth || (de && de.clientWidth) || document.body.clientWidth;
+};
+
+/* Utilities to test whether an element will fit in the viewport
+ Parameters:
+ el = element to position, required
+ leftOffset / topOffset = optional parameter if the offset cannot be calculated (i.e., if the object is in the DOM but is set to display: 'none') */
+
+function fitHorizontal(el, leftOffset){
+ var leftVal = parseInt(leftOffset) || $(el).offset().left;
+ return (leftVal + $(el).width() <= getWindowWidth() + getScrollLeft() && leftVal - getScrollLeft() >= 0);
+};
+
+function fitVertical(el, topOffset){
+ var topVal = parseInt(topOffset) || $(el).offset().top;
+ return (topVal + $(el).height() <= getWindowHeight() + getScrollTop() && topVal - getScrollTop() >= 0);
+};
+
+/*--------------------------------------------------------------------
+ * javascript method: "pxToEm"
+ * by:
+ Scott Jehl (scott@filamentgroup.com)
+ Maggie Wachs (maggie@filamentgroup.com)
+ http://www.filamentgroup.com
+ *
+ * Copyright (c) 2008 Filament Group
+ * Dual licensed under the MIT (filamentgroup.com/examples/mit-license.txt) and GPL (filamentgroup.com/examples/gpl-license.txt) licenses.
+ *
+ * Description: Extends the native Number and String objects with pxToEm method. pxToEm converts a pixel value to ems depending on inherited font size.
+ * Article: http://www.filamentgroup.com/lab/retaining_scalable_interfaces_with_pixel_to_em_conversion/
+ * Demo: http://www.filamentgroup.com/examples/pxToEm/
+ *
+ * Options:
+ scope: string or jQuery selector for font-size scoping
+ reverse: Boolean, true reverses the conversion to em-px
+ * Dependencies: jQuery library
+ * Usage Example: myPixelValue.pxToEm(); or myPixelValue.pxToEm({'scope':'#navigation', reverse: true});
+ *
+ * Version: 2.0, 08.01.2008
+ * Changelog:
+ * 08.02.2007 initial Version 1.0
+ * 08.01.2008 - fixed font-size calculation for IE
+--------------------------------------------------------------------*/
+
+Number.prototype.pxToEm = String.prototype.pxToEm = function(settings){
+ //set defaults
+ settings = jQuery.extend({
+ scope: 'body',
+ reverse: false
+ }, settings);
+
+ var pxVal = (this == '') ? 0 : parseFloat(this);
+ var scopeVal;
+ var getWindowWidth = function(){
+ var de = document.documentElement;
+ return self.innerWidth || (de && de.clientWidth) || document.body.clientWidth;
+ };
+
+ /* When a percentage-based font-size is set on the body, IE returns that percent of the window width as the font-size.
+ For example, if the body font-size is 62.5% and the window width is 1000px, IE will return 625px as the font-size.
+ When this happens, we calculate the correct body font-size (%) and multiply it by 16 (the standard browser font size)
+ to get an accurate em value. */
+
+ if (settings.scope == 'body' && $.browser.msie && (parseFloat($('body').css('font-size')) / getWindowWidth()).toFixed(1) > 0.0) {
+ var calcFontSize = function(){
+ return (parseFloat($('body').css('font-size'))/getWindowWidth()).toFixed(3) * 16;
+ };
+ scopeVal = calcFontSize();
+ }
+ else { scopeVal = parseFloat(jQuery(settings.scope).css("font-size")); };
+
+ var result = (settings.reverse == true) ? (pxVal * scopeVal).toFixed(2) + 'px' : (pxVal / scopeVal).toFixed(2) + 'em';
+ return result;
+};
+
+function KillAllMenus()
+{
+ $.each(allUIMenus, function(i){
+ if (allUIMenus[i].menuOpen) { allUIMenus[i].kill(); };
+ });
+};
\ No newline at end of file
diff --git a/js/icon_select.js b/js/icon_select.js
new file mode 100644
index 000000000..0438ba793
--- /dev/null
+++ b/js/icon_select.js
@@ -0,0 +1,127 @@
+//iTop Designer combo box for icons
+$(function()
+{
+ // the widget definition, where "itop" is the namespace,
+ // "icon_select" the widget name
+ $.widget( "itop.icon_select",
+ {
+ // default options
+ options:
+ {
+ items: [],
+ current_idx: 0
+ },
+
+ // the constructor
+ _create: function()
+ {
+ var me = this;
+ var sLabel = '';
+ var sIcon = '';
+ if (this.options.items.length > 0)
+ {
+ sIcon = this.options.items[this.options.current_idx].icon;
+ sLabel = this.options.items[this.options.current_idx].label;
+ }
+ this.oImg = $('
');
+ this.oLabel = $(''+sLabel+'');
+ this.oButton = $('');
+ this.oButton.prepend(this.oLabel).prepend(this.oImg);
+ this.element.after(this.oButton);
+ this.element.addClass( "itop-icon-select" ).button();
+ this.element.bind( "reverted.itop-icon-select", function(ev, data) {
+ var idx = me._find_item(data.previous_value);
+ if (idx != null)
+ {
+ me.oImg.attr('src', me.options.items[idx].icon);
+ me.oLabel.text(me.options.items[idx].label);
+ }
+ });
+
+ this._refresh();
+ },
+
+ // called when created, and later when changing options
+ _refresh: function()
+ {
+ if (this.options.items.length > 0)
+ {
+ this.element.val(this.options.items[this.options.current_idx].value);
+ this.oImg.attr('src', this.options.items[this.options.current_idx].icon);
+ this.oLabel.text(this.options.items[this.options.current_idx].label);
+ }
+ this._create_menu();
+ },
+ _create_menu: function()
+ {
+ var me = this;
+ var sMenu = '';
+ var iWidth = Math.max(250, this.oButton.width());
+ this.oMenu = this.oButton.menu({ content: sMenu, callback: function(data) {me._on_icon_selection(data);}, showSpeed: 0, maxHeight: 300, flyOut: true, width: iWidth, positionOpts: {posX: 'left', posY: 'top', offsetX: 0, offsetY: 0} });
+ },
+
+ // events bound via _bind are removed automatically
+ // revert other modifications here
+ _destroy: function()
+ {
+ this.element.removeClass( "itop-icon-select" );
+ this.oButton.destroy();
+ },
+
+ // _setOptions is called with a hash of all options that are changing
+ // always refresh when changing options
+ _setOptions: function()
+ {
+ // in 1.9 would use _superApply
+ $.Widget.prototype._setOptions.apply( this, arguments );
+ this._refresh();
+ },
+
+ // _setOption is called for each individual option that is changing
+ _setOption: function( key, value )
+ {
+ if (key == 'current_idx')
+ {
+ this.element.val(this.options.items[value].value).trigger('change');
+ }
+
+ // in 1.9 would use _super
+ $.Widget.prototype._setOption.call( this, key, value );
+ },
+ _on_icon_selection: function(data)
+ {
+ this._setOptions({current_idx: data.item.attr('value')});
+ },
+ _find_item: function(value)
+ {
+ var res = null;
+ for(var idx in this.options.items)
+ {
+ if (value == this.options.items[idx].value)
+ {
+ res = idx;
+ break;
+ }
+ }
+ return res;
+ },
+ add_item: function(value, label, position)
+ {
+ if (position == 'bottom')
+ {
+ this.options.items.push({value: value, label: label });
+ }
+ else
+ {
+ // Assume 'top'
+ this.options.items.unshift({value: value, label: label });
+ }
+ this._refresh();
+ }
+ });
+});
diff --git a/js/property_field.js b/js/property_field.js
index 700defdb8..2f9d07864 100644
--- a/js/property_field.js
+++ b/js/property_field.js
@@ -103,7 +103,13 @@ $(function()
if (this.options.do_apply)
{
// specific behavior...
- this.options.do_apply();
+ if (this.options.do_apply())
+ {
+ this.bModified = false;
+ this.previous_value = this.value;
+ this.value = this._get_field_value();
+ this._refresh();
+ }
}
else
{