/*
 * Copyright (C) 2005 - 2011 Jaspersoft Corporation. All rights reserved.
 * http://www.jaspersoft.com.
 *
 * Unless you have purchased  a commercial license agreement from Jaspersoft,
 * the following license terms  apply:
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License  as
 * published by the Free Software Foundation, either version 3 of  the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero  General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public  License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * ActionModel Object Originally written by Angus Croll.
 * Refactored for VFR project by Papanii Okai
 * @author Angus Croll
 * @author Papanii Okai (a.k.a The Code Pimp)
 */

var localContext = window;
var actionModel = {};

//suffix to be appended to dynamically created context names
actionModel.CONTEXT_SUFFIX = "Level";

actionModel.lastItemWasSeparator = false;
actionModel.noItemsYet = true;
actionModel.data = null;
actionModel.openedSubMenus = [];

//action constants
actionModel.SIMPLE_ACTION = "simpleAction";
actionModel.SELECT_ACTION = "selectAction";
actionModel.INPUT_ACTION = "inputAction";
actionModel.OPTION_ACTION = "optionAction";
actionModel.SEPARATOR = "separator";
actionModel.SINGLE_SELECT_CONSTRAINT = "singleSelect";
actionModel.HIGH_Z_INDEX = 99999;
actionModel.MENUOFFSET = 15;


//reserved variables
actionModel.RES_LABEL = "$label";
actionModel.RES_ID = "$id";
actionModel.RES_SELECTED = "$selected";
actionModel.EVENT = "$event";
actionModel.reservedVarMap = {};

if(typeof(window['selObjects']) != "undefined" ) {
    actionModel.reservedVarMap[actionModel.RES_SELECTED] = selObjects;
}


actionModel.tempDiv = null;
actionModel.parentMenu = "";
actionModel.menuDom = null;
actionModel.menuListDom = null;
actionModel.PARENT_MENU_CONTAINER = "menuList";
actionModel.PARENT_MENU_STYLE = "menu vertical context hidden";
actionModel.SIMPLE_ACTION_DOM_ID = "menuList_simpleAction";
actionModel.SEPARATOR_DOM_ID = "menuList_separator";
actionModel.FLYOUT_PARENT_DOM_ID = "menuList_flyout";
actionModel.EXTRA_INPUT_DOM_ID = "menuList_extraInput";
actionModel.LIST_ITEM_DOM_ID = "menuList_listItem";
actionModel.DISPLAY_STYLE = "inline";

//patterns
actionModel.DROP_DOWN_MENU = ".dropDown";


////////////////////////////////////////////////////////////////////
// Menu building
////////////////////////////////////////////////////////////////////


/**
 * Used to present the context menu to the user.
 * @param menuContext
 * @param event the event tied to the menu.
 * @param className class name a caller may wish to use instead of the default.
 * @param coordinates used for menu positioning.
 * @actionModelScriptTag
 * @updateContextActionModel - optional method which can update context action model
 */
actionModel.showDynamicMenu = function(menuContext, event, className, coordinates, actionModelScriptTag, updateContextActionModel){
    if(menuContext == null){
        if(isProVersion()){
            var selectionObj = designerBase.getSelectedObject();
            if (selectionObj && selectionObj.menuLevel) {
                menuContext = selectionObj.menuLevel;
            }else {
                return; //no context - nothing to show
            }
        } else {
            return;
        }
    }

    actionModel.resetMenu();
    actionModel.updateCSSClass(className);
    actionModel.assembleMenuFromActionModel(menuContext, event, actionModel.menuListDom, actionModelScriptTag, updateContextActionModel);
    actionModel.setMenuPosition(actionModel.menuDom, event, coordinates);
    actionModel.setMenuEventHandlers(actionModel.menuDom);
	actionModel.makeMenuVisible(actionModel.menuDom);
    isIE7() && setWidthStyleByReflection(actionModel.menuDom, '.content');
    actionModel.adjustMenuPosition(actionModel.menuDom);
    actionModel.focusMenu();
};


/**
 * Used to create a simple temp div
 */
actionModel.createTempDiv = function(){
    actionModel.tempDiv = document.createElement("div");
};


/**
 * Check if part of menu object/DOM.
 * @param obj Object we are investigating.
 */
actionModel.isTopAncestorTheMenu = function(obj){
    var ancestors = $(obj).ancestors();
    var bool = false;
    ancestors.each(function(parent){
        if(parent.readAttribute("id") == "menu"){
            bool = true;
        }
    });
    return bool;
};


/**
 * Hide any menus created by the action model.
 * This means removing all child nodes and setting it back to default state.
 */
actionModel.resetMenu = function(){
    var dom = $("menu");
    dom.parentId = undefined;
    dom.setAttribute("tabIndex",-1);
    dom.childElements().each(function(child){Element.remove(child);});
    //add common template
    dom.innerHTML = $('commonMenu').cloneNode(true).innerHTML;
    var menuList = dom.down("ul");
    var menuListId = menuList.readAttribute("id");
    menuList.writeAttribute("id", actionModel.updateTemplateId(menuListId));
    //remove any generated styles.
    dom.className = actionModel.PARENT_MENU_STYLE;
    dom.writeAttribute("style", "");
    actionModel.menuDom = dom;
    actionModel.menuListDom = menuList;
};


/**
 * Used to update a template's id
 */
actionModel.updateTemplateId = function(templateId){
    var re = new RegExp("_template");
    return templateId.replace(re, "");
};


/**
 * Method used to update the menu's css className
 * @param className
 */
actionModel.updateCSSClass = function(className){
    if(isNotNullORUndefined(className)){
        $(actionModel.menuDom).className = className + " hidden";
    }
};



/**
 *  Determine position of menu based on mouse position..
 * @param event
 */
actionModel.setMenuPosition = function(menuObj, event, coordinates){
    var ev = event ? event : window.event;
    var location = Event.pointer(ev);

    if (isSupportsTouch() && ev.changedTouches) {
        location = {
            x: ev.changedTouches[0].pageX + 20,
            y: ev.changedTouches[0].pageY
        }
    }

    var leftOffset;
    var topOffset;

    //if coordinates are provided, override.
    if(isNotNullORUndefined(coordinates)){
        if(isNotNullORUndefined(coordinates.menuLeft)){
            leftOffset = coordinates.menuLeft;
        }else{
            leftOffset = location.x + actionModel.MENUOFFSET;
        }
        if(isNotNullORUndefined(coordinates.menuTop)){
            topOffset = coordinates.menuTop;
        }else{
            topOffset = location.y + actionModel.MENUOFFSET;
        }
    }else{
        leftOffset = location.x;
        topOffset = location.y;
    }

    //set top left to the mouse click
    menuObj.setStyle({
        'left': leftOffset + 'px',
        'top' : topOffset +'px'
    });

};

actionModel.adjustMenuPosition = function(menu, left, top, width, height){
    if(!$(menu).hasClassName("dropDown")){
        fitObjectIntoScreen(menu, null, top, null, height);
    }
};

/**
 * construct the menu dynamically based on the client side action model
 * @param menuContext
 * @param event
 * @updateContextActionModel - optional method which can update context action model
 */
actionModel.assembleMenuFromActionModel = function(menuContext, event, contentParent, actionModelScriptTag, updateContextActionModel) {
    actionModel.data = null;
    actionModel.data = $(actionModelScriptTag).text;
    if(actionModelScriptTag === "navigationActionModel"){
        actionModel.data = primaryNavModule.JSON;
    }else{
        actionModel.data = ($(actionModelScriptTag).text.evalJSON()).evalJSON();
    }
    actionModel.noItemsYet = true;
    var contextActionModel = actionModel.data[menuContext];
    //In this place we can update action model for specified context on the fly.
    //This is necessary to have ability to construct fully dynamic menus which items can be calculated only
    //right before showing menu
    updateContextActionModel && (contextActionModel = updateContextActionModel(menuContext, contextActionModel));
    contextActionModel.each(function(thisAction){actionModel.appendToMenu(thisAction, event, contentParent)});
    actionModel.removeTrailingSeparator();
};


/**
 * Helper method used in the construction of the menu
 * @param thisAction
 * @param event
 */
actionModel.appendToMenu = function(thisAction, event, contentParent) {
    if (!actionModel.passesClientTest(thisAction)) {
        return; //does not satisfy client-side condition to appear in this menu
    }

    var mouseUpFunction = getMenuMouseupFunction(
            getAsFunction(thisAction.action),
            thisAction.actionArgs,
            thisAction.text,
            thisAction.id,
            event);

    actionModel.appendDesiredRowType(thisAction, event, mouseUpFunction, contentParent);
};


/**
 * Append desired row type to menu
 * @param thisAction
 * @param mouseUpFunction
 * @param contentParent
 * @param event
 */
actionModel.appendDesiredRowType = function(thisAction, event, mouseUpFunction, contentParent){
    //add new <li> element
    if (thisAction.type == actionModel.SIMPLE_ACTION) {
        actionModel.addSimpleActionRow(thisAction, mouseUpFunction, contentParent);
    }
    //add new <li> separator
    if (thisAction.type == actionModel.SEPARATOR && !actionModel.lastItemWasSeparator) {
        actionModel.addSeparatorRows(thisAction, contentParent);
    }
    //add new <li> flyout or new window?
    if (thisAction.type == actionModel.SELECT_ACTION) {
        actionModel.addSelector(thisAction, contentParent, event);
    }
    //add new <li> option
    if (thisAction.type == actionModel.OPTION_ACTION) {
        actionModel.addOption(thisAction, mouseUpFunction, contentParent);
    }
};


/**
 * Add a simple action row to the menu
 * @param thisAction
 * @param mouseUpFunction
 * @param contentParent
 */
actionModel.addSimpleActionRow = function(thisAction, mouseUpFunction, contentParent) {
    var newMenuRow = null;
    if(thisAction.className && (thisAction.className == "requiresInput")){
        newMenuRow = $(actionModel.EXTRA_INPUT_DOM_ID).cloneNode(true);
    }else{
        newMenuRow = $(actionModel.SIMPLE_ACTION_DOM_ID).cloneNode(true);
    }
    if (isSupportsTouch()) {
        newMenuRow.ontouchend = mouseUpFunction;
    } else {
        newMenuRow.onmouseup = mouseUpFunction;
    }
    newMenuRow.onmouseup_saved = mouseUpFunction;
    var textPlacement = $(newMenuRow).down(".button");
    var newText = document.createTextNode(thisAction.text);
    newText.nodeValue = replaceNbsps(newText.nodeValue);
    textPlacement.appendChild(newText);

    //set new id to make it unique.
    var menuId = newMenuRow.readAttribute("id") + "." + thisAction.action;
    newMenuRow.writeAttribute("id", menuId);


    actionModel.insertMenuRow(contentParent, newMenuRow, false, thisAction.isDisabled, thisAction.id, null);
};


/**
 * Add a separator row to the menu
 * @param thisAction
 * @param contentParent
 */
actionModel.addSeparatorRows = function(thisAction, contentParent){
    var cssOverride = null;
    if (actionModel.noItemsYet) {
        return; //don't need separator at very top;
    }
    var separator = $(actionModel.SEPARATOR_DOM_ID).cloneNode(true);
    var separatorId = separator.readAttribute("id") + getRandomId();
    separator.writeAttribute("id", separatorId);
    if(thisAction.className){
        cssOverride = thisAction.className;
    }
    actionModel.insertMenuRow(contentParent, separator, true, thisAction.isDisabled, null, cssOverride);
};

/**
 * Used to add a option
 * @param thisAction
 * @param mouseUpFunction
 * @param contentParent
 */
actionModel.addOption = function(thisAction, mouseUpFunction, contentParent){
    var cssOverride = null;
    var newOptionRow = $(actionModel.LIST_ITEM_DOM_ID).cloneNode(true);
    thisAction.button = (thisAction.button && String(thisAction.button).toLowerCase()=="true") ? true : false;
    if (isSupportsTouch()) {
        newOptionRow.ontouchend = mouseUpFunction;
    } else {
        newOptionRow.onmouseup = mouseUpFunction;
    }
    newOptionRow.onmouseup_saved = mouseUpFunction;
    var textPlacement = $(newOptionRow).down(".button");
    var newText = document.createTextNode(thisAction.text);
    newText.nodeValue = replaceNbsps(newText.nodeValue);

    textPlacement.appendChild(newText);
    var newOptionRowId = newOptionRow.readAttribute("id") + getRandomId() + "_" + thisAction.action;
    newOptionRow.writeAttribute("id", newOptionRowId);//update dom id

    if(thisAction.className){
        cssOverride = thisAction.className;
    }

    if (!thisAction.button && evaluateTest(thisAction.isSelectedTest, thisAction.isSelectedTestArgs, thisAction.text, thisAction.id, false, false)) {
        textPlacement.addClassName("down");

    }

    actionModel.insertMenuRow(contentParent, newOptionRow, false, thisAction.isDisabled, thisAction.id, cssOverride);
};




/**
 * Creates selector row and all sub menus related to that row
 * @param thisAction
 * @param contentParent
 * @param event
 */
actionModel.addSelector = function(thisAction, contentParent, event) {
    //since in the action model we specify the type of menu using the class nomenclature, we can check what
    //type of selector we want by simply checking the class in the action object
    var parentSelector;
    var className = thisAction.className;
    if(className == "flyout"){
        //do not render flyout list item if no submenu items exist
        if (thisAction && (thisAction.children.length > 0)) {
            parentSelector = $(actionModel.FLYOUT_PARENT_DOM_ID).cloneNode(true);
            var textPlacement = $(parentSelector).down(".button");
            var newText = document.createTextNode(thisAction.text);
            newText.nodeValue = replaceNbsps(newText.nodeValue);
            textPlacement.appendChild(newText);
            var parentSelectorId = parentSelector.readAttribute("id") + getRandomId();
            parentSelector.writeAttribute("id", parentSelectorId);
            actionModel.insertMenuRow(contentParent, parentSelector, false, thisAction.isDisabled, thisAction.id, null);
            //now create submenu
            var submenu = actionModel.menuDom.cloneNode(false);
            submenu.writeAttribute("id", parentSelector.readAttribute("id") + "_subMenu");
            //add common template
            submenu.innerHTML = $('commonMenu').cloneNode(true).innerHTML;
            var submenuList = submenu.down("ul");
            var submenuListId = submenuList.readAttribute("id");
            submenuList.writeAttribute("id", actionModel.updateTemplateId(submenuListId));
            //get containers width.
            parentSelector.appendChild(submenu);
            var subMenuContainer = submenu.select('ul')[0];
            var subMenuContainerId = subMenuContainer.writeAttribute("id", subMenuContainer.readAttribute("id") + "_subMenu");
            actionModel.buildSubMenu(thisAction, subMenuContainerId, event);
        }
    } else {
        thisAction.children.each(function(childAction){
            var mouseUpFunction = getMenuMouseupFunction(
                    getAsFunction(childAction.action),
                    childAction.actionArgs,
                    childAction.text,
                    childAction.id,
                    event);
            actionModel.appendDesiredRowType(childAction, event, mouseUpFunction, contentParent);
        });
    }
};




/**
 * This method gets the left value for a submenu. It calculates if by getting the with of the parents content and adds
 * the parents left value to it since all menu objects are absolute.
 * @param selectorObj
 */
actionModel.getSubMenuLeft = function(selectorObj){
    //<div menu>
    var width = $(selectorObj).up(2).clientWidth; //width of parent window
    var leftOffSet = Math.abs(parseInt($(selectorObj).up(1).offsetLeft)); //offset
    return (width + leftOffSet);
};


actionModel.getSubMenuTop = function(selectorObj){
    return Math.abs(parseInt($(selectorObj).offsetTop)); //offset
};



/**
 * Used to position the submenu with respect to its parent.
 * @param parent
 */
actionModel.showChildSubmenu = function(parent){
    var submenuTop = null;
    var submenuHeight = null;
    var submenuWidth = null;
    var bottomPadding = 20; //hack to prevent  bug20863
    var submenu = parent.childElements()[1];

    //applying style for flyout submenu....
    submenu.setStyle({display: actionModel.DISPLAY_STYLE});
    submenu.setStyle({position:'absolute'});
    var leftValue = actionModel.getSubMenuLeft(parent);
    var topValue = 0;
    //    var topValue = actionModel.getSubMenuTop(parent) + "px";
    //set top left to the mouse click
    submenu.setStyle({
        'left': leftValue + "px",
        'top' : topValue + "px"
    });
    actionModel.makeMenuVisible(submenu);
    isIE7() && setWidthStyleByReflection(submenu, '.content');	
    submenuTop  = $(submenu).cumulativeOffset()[1];
    submenuHeight = $(submenu).getHeight();

    //hack to prevent  bug20863  (Due to Tim's markup change)
    var windowHeight = getWindowHeight();
    var submenuBottom = submenuTop + submenuHeight;
    if(windowHeight < submenuBottom){
        topValue = (windowHeight - submenuBottom - bottomPadding);
        submenu.setStyle({
            'top' : topValue + "px"
        });
    }

    var submenuLeftOffset = submenu.cumulativeOffset()[0];
    submenuWidth = submenu.getWidth();

    if(getWindowWidth() < (submenuLeftOffset + submenuWidth)){
        submenu.setStyle({
            'left' : "-" + submenuWidth + "px"
        });
    }
    actionModel.openedSubMenus.push(parent);
};


/**
 * Used to hide submenu
 * @param parent
 */
actionModel.hideChildSubmenu = function(parent){
    var submenu = parent.childElements()[1];
    //applying style for fly-out submenu....
    submenu.setStyle({display: "none"});
    submenu.setStyle({position:'absolute'});
    submenu.setStyle({left: 0});
    actionModel.makeMenuInVisible($(submenu));

    if(isSupportsTouch()) {
        var positionInOpened = actionModel.openedSubMenus.indexOf(parent);
        if (positionInOpened > -1) {
            if((positionInOpened + 1) < actionModel.openedSubMenus.length) {
                var item = actionModel.openedSubMenus[positionInOpened + 1];
                item && actionModel.hideChildSubmenu(item);
            }
            actionModel.openedSubMenus = actionModel.openedSubMenus.without(parent);
        }
    }
};





/**
 * Used to change the css display value to make it visible.
 * @param menu object we changing the display style for.
 */
actionModel.makeMenuVisible = function(menu){
    //test to see if we have any items to show
    var list = menu.down("ul");
    if(list.childElements().length > 0){
        menu.setStyle({zIndex: actionModel.HIGH_Z_INDEX});
        $(menu).removeClassName("hidden");
    }
};


/**
 * Used to change the css display value to make it invisible
 * @param menu object we changing the display style for.
 */
actionModel.makeMenuInVisible = function(menu){
    menu.addClassName("hidden");
};


/**
 * Used to build a the logical structure for a flyout menu
 * @param thisAction
 * @param parentId
 * @param event
 */
actionModel.buildSubMenu = function(thisAction, parentId, event){
    thisAction.children.each(function(childAction){
        actionModel.appendToMenu(childAction, event, $(parentId));
    });
};


actionModel.isMenuShowing = function(){
    return !($("menu").getStyle("display") == "none" || $('menu').hasClassName("hidden"));
};

/**
 * Helper method to update row object
 * @param domObject
 * @param actionText
 */
actionModel.updateRowDom = function(domObject, actionText){
    if(domObject){
        domObject.innerHTML = actionText;
        var domObjectId = domObject.readAttribute("id") + getRandomId();
        domObject.writeAttribute("id", domObjectId);
    }
    return domObject;
};


/**
 * Used to launch new menu (E.g. Calculated fields.)
 */
actionModel.launchNewMenu = function(){
    actionModel.hideMenu();
    alert("action not implemented...");
};




/**
 * Helper to insert row in to menu
 * @param container
 * @param newMenuRow
 * @param isSeparator
 * @param isDisabled
 * @param userId
 */
actionModel.insertMenuRow = function(container, newMenuRow, isSeparator, isDisabled, userId, className) {
    //check to see if item has a id, if not set one..
    if(newMenuRow.readAttribute("id") == null){
        if (userId) {
            newMenuRow.setAttribute('id',userId);
        } else {
            newMenuRow.identify(); //sets unique id
        }
    }

    if (isDisabled) {
        actionModel.disableMenuOption(newMenuRow);
    }
    if(className){
        newMenuRow.addClassName(className);
    }
    container.appendChild(newMenuRow);
    actionModel.lastItemWasSeparator = isSeparator;
    actionModel.noItemsYet = false;
};




actionModel.disableMenuOption = function(menuOption){
    //currently does nothing.
};




/**
 * Checking success of test.
 * @param action
 */
actionModel.passesClientTest = function(action) {
    var clientTestFunction;
    var result = true; //default to true because if there are no tests, we passed by default!
    //passes explicit test?
    if (action.clientTest) {
        clientTestFunction = action.clientTest;
        var testForNegative = false;
        if (clientTestFunction.startsWith("!")) {
            //negative test
            clientTestFunction  =  action.clientTest.sub("!","");
            testForNegative = true;
        }
        result = evaluateTest(clientTestFunction, action.clientTestArgs, action.text, action.id, testForNegative, null);
    }
    //number of selections ok?
    if ((typeof(window['selObjects']) != "undefined" )) {
        if (result && action.selectionConstraint) {
            result = action.selectionConstraint == actionModel.SINGLE_SELECT_CONSTRAINT ? selObjects.length == 1 : selObjects.length > 1;
        }
    }
    return result;
};



/**
 * If last row is a separator, remove it
 */
actionModel.removeTrailingSeparator = function(){
    var lastRow = actionModel.menuListDom.lastChild;
    if ($(lastRow) && !actionModel.noItemsYet) {
        var lastRowId = lastRow.identify();
        var reg = new RegExp("\w*separator\w*");
        if (reg.test(lastRowId)) {
            lastRow.remove();
        }
    }
};

/**
 * Only applies to certain menus (e.g. naviagtion menu has a parent mutton)
 */
actionModel.getMenuParent = function() {
    return $(this.menuDom.parentId);
};



actionModel.getFirstMenuButton = function() {
    return this.menuDom.down(layoutModule.BUTTON_PATTERN, 0);
};

actionModel.focusMenu = function() {
    //will restore old focus when menu is closed
    actionModel.lastFocused = document.activeElement;
    !this.menuDom.hasClassName(layoutModule.HIDDEN_CLASS) && this.menuDom.focus();

    if (!this.menuDom.match(actionModel.DROP_DOWN_MENU) && !isSupportsTouch()) {
        var childItems = this.menuDom.select("li");
        if (childItems[0]) {
            var firstButton = childItems[0].down(layoutModule.BUTTON_PATTERN);
            firstButton && buttonManager.over(firstButton);
        }
    }
};

////////////////////////////////////////////////////////////
// General section for menu events
////////////////////////////////////////////////////////////

/**
 * Register event handlers to menu objects
 */
actionModel.setMenuEventHandlers = function(menu){
    menu.select("li").each(function(object){
        //special mouse-enter and mouse-leave events for fly-outs
        if ((!object.hasClassName("disabled"))) {
            if (object.hasClassName("node")) {
                if (isSupportsTouch()) {
                    Event.observe(object, 'touchstart', function(event) {
                        if (!event.firstCall) {
                            actionModel.hideChildSubmenu(object);
                            actionModel.showChildSubmenu(object);
                        }
                        event.firstCall = true;
                    });
                    Event.observe(object, 'touchend', function(event) {
                        var child = object.down();
                        buttonManager.up(child);
                        buttonManager.out(child);

                        event.stop();
                    });
                } else {
                    Event.observe(object, 'mouseenter', function(event) {
                        actionModel.showChildSubmenu(object);
                    });
                    Event.observe(object, 'mouseleave', function(event) {
                        actionModel.hideChildSubmenu(object);
                    });
                }
            } else {
                Event.observe(object, 'touchstart', function(event) {
                    (actionModel.openedSubMenus.length > 0) && actionModel.hideChildSubmenu(actionModel.openedSubMenus[0]);
                });
            }
        }
    });
};

actionModel.initializeOneTimeMenuHandlers = function() {
    /**
     * generic mouseenter for menu - to initialize for mouse use
     */
    $('menu').observe('mouseenter', function(event) {
        var selected = this.select("." + layoutModule.HOVERED_CLASS)[0];
        selected && buttonManager.out(selected);
    }.bind($('menu')))
};

/**
 * Public method used to hide a menu.
 */
actionModel.hideMenu = function(){
    if (actionModel.isMenuShowing()) {
        var setNextFocus = function(){
            var nextFocus = actionModel.lastFocused;
            try {
                nextFocus && nextFocus.focus();
            } catch(ex) {
                //IE gets bothered if you try to focus something that it can't
            }
        };
        !isSupportsTouch() && setTimeout(setNextFocus, 0); //timeout so that any current key action misses this focus
        actionModel.makeMenuInVisible($("menu"));
    }
};



/**
 * Utility method which helps to create menu element
 * @param type type of menu element, one of ["simpleAction", "selectAction", "optionAction"]
 *  this is the only mandatory parameter to create manu item.
 * @param options - object with optional arguments.
 *  Possible object structure is:
 * {
 *  text: test,
 *  clientTest: clientTest,
 *  clientTestArgs: clientTestArgs,
 *  action: action,
 *  actionArgs: actionArgs,
 *  children: children,
 *  isSelectedTest: isSelectedTest,
 *  isSelectedTestArgs: isSelectedTestArgs,
 *  selectionConstraint: selectionConstraint,
 *  className: className
 *  }
 */
actionModel.createMenuElement = function(type, options){
    if (!type) {
        throw new Error("Can not construct actionModel object: type can not be empty");
    }

    return Object.extend({type: type}, options);
};


////////////////////////////////////////////////////////////
// function utils
////////////////////////////////////////////////////////////

function replaceNbsps(str) {
    var re = new RegExp("&nbsp;", "g");
    return str.replace(re, " ");
}



function fireMenuAction(myFunction, when, event) {
    if(isIPad() && actionModel && actionModel.isMenuShowing()){
        actionModel.hideMenu();
    }
    
    myFunction = getAsFunction(myFunction);
    if(when)
        myFunction();

    if(actionModel && actionModel.isMenuShowing()){
        actionModel.hideMenu();
    }
}

function fireMenuActionWithEvent(args, myFunction, when, event) {
    var belongsToLocalContext = localContext && localContext[myFunction];
    myFunction = getAsFunction(myFunction);
    if (when) {
        if (args && isArray(args)) {
            myFunction.apply(belongsToLocalContext ? localContext : null, args);
        } else {
            myFunction.call(belongsToLocalContext ? localContext : null, args);
        }
    }

    if(actionModel && actionModel.isMenuShowing()){
        actionModel.hideMenu();
    }

}

function getMenuMouseupFunction(actionFunction, argsToInvokeWith, label, id, event) {
    if (!actionFunction) {
        return null;
    }
    if (!argsToInvokeWith) {
        return function(){
            fireMenuAction(actionFunction, true, event);
        }
    } else {
        var args = resolveReservedStringsInArray(argsToInvokeWith, label, id, event);

        return function(){
            fireMenuActionWithEvent(args, actionFunction, true, event);
        }
    }
}


function evaluateTest(thisFunctionName, functionArgs, label, id, testForNegative, defaultResult) {
    var result = (defaultResult!=null) ? defaultResult : true; //if no default assume true;
    var theFunction = getAsFunction(thisFunctionName);
    if (theFunction) {
        result =  theFunction.apply(this,resolveReservedStringsInArray(functionArgs, label, id, null));
        if (testForNegative) {
            result = !result;
        }
    }
    return result;
}



function resolveReservedStringsInArray(oldArray, label, id, ev) {
    if (!oldArray) {
        return [];
    }
    return oldArray.collect(function(thisString) {return resolveReservedStrings(thisString, label, id, ev)});
}



function resolveReservedStrings(thisString, label, id, ev) {
    var directMapping = actionModel.reservedVarMap[thisString];
    if (directMapping) {
        return directMapping;
    }
    if (thisString==actionModel.RES_LABEL) {
        return label;
    }
    if (thisString==actionModel.RES_ID) {
        return id;
    }
    if (thisString==actionModel.EVENT) {
        return ev;
    }
    return thisString;
}


/**
 * Get a random number.
 * May need to move this to common utils
 */
function getRandomId(){
    var rand =  Math.round(Math.random(10) * 50);
    return "_" + rand;
}





