/* Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved. */
/* ------------------------------------------------------ */
/* ------------------- amx-core.js ---------------------- */
/* ------------------------------------------------------ */

var amx = amx || {}; /* deprecated */

var adf             = adf                 || {};
adf.mf              = adf.mf              || {};
adf.mf              = adf.mf              || {};
adf.mf.api          = adf.mf.api          || {};
adf.mf.api.amx      = adf.mf.api.amx      || {};
adf.mf.internal     = adf.mf.internal     || {};
adf.mf.internal.amx = adf.mf.internal.amx || {};
// TODO finish the migration from "amx.*" to "adf.mf.api.amx.*" and "adf.mf.internal.amx.*"

// --------- Config Initialization --------- //
(function($){
  // define the names of the 2 known message bundles here
  adf.mf.api.amx.AMXErrorMessageBundleName = "AMXErrorMessageBundle";
  adf.mf.api.amx.AMXInfoMessageBundleName = "AMXInfoMessageBundle";
})(jQuery);
// --------- /Config Initialization --------- //

// --------- Utilities --------- //
(function()
{
  // --------- Private helper methods --------- //
  function splitLocale(locale){
    var ret = [null,null,null];
    var start = 0;
    var end = locale.indexOf("_");
    if (end != -1){
      ret[0] = locale.substring(start, end);
    }else{
      ret[0] = locale;
      return ret;
    }
    start = ++end;
    end = locale.indexOf("_", start);
    if (end != -1){
      ret[1] = locale.substring(start, end);
    }else{
      ret[1] = locale.substring(start);
      return ret;
    }
    start = ++end;
    ret[2] = locale.substring(start);
    return ret;
  }

  function loadJavaScript(url, success, failure){
    var head = document.getElementsByTagName("head")[0];
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.src = url;
    script.async = false;
    script.onload = success;
    script.onerror = failure;
    head.appendChild(script);
  }

  function loadJavaScriptByLocaleImpl(localeList, constructor, predicate, callback){
    if (localeList.length == 0){
      callback(null);
      return;
    }
    var locale = localeList.pop();
    var url = constructor(locale);
    var failure = function(){
      // for this low-level method, always send in the english string
      if(adf.mf.log.Framework.isLoggable(adf.mf.log.level.WARNING)) {
        adf.mf.log.Framework.logp(adf.mf.log.level.WARNING, "amx", "loadJavaScriptByLocaleImpl", "Failed to load " + url);
      }
      loadJavaScriptByLocaleImpl(localeList, constructor, predicate, callback);
    };
    var success = function(){
      if (predicate(locale)){
        // for this low-level method, always send in the english string
        if(adf.mf.log.Framework.isLoggable(adf.mf.log.level.INFO)) {
          adf.mf.log.Framework.logp(adf.mf.log.level.INFO, "amx", "loadJavaScriptByLocaleImpl", "Loaded " + url);
        }
        callback(locale);
      }else{
        failure();
      }
    };
    loadJavaScript(url, success, failure);
  }

  function loadTrinidadLocaleElements(baseUrl, localeList, callback){
    var localeElementsUrlConstructor = function(locale){
      return baseUrl + "/resource/LocaleElements" + "_" + getJavaLanguage(locale) + ".js";
    };
    var localeElementsLoaded = function(locale){
      var suffix = "_" + getJavaLanguage(locale);
      if (typeof window["LocaleSymbols" + suffix] !== "undefined"){
        return true;
      }
      return false;
    };

    adf.mf.api.amx.loadJavaScriptByLocale(localeList, localeElementsUrlConstructor, localeElementsLoaded, callback);
  }

  function loadTrinidadMessageBundle(baseUrl, localeList, callback){
    var messageBundleUrlConstructor = function(locale){
      var url = baseUrl + "/resource/MessageBundle";
      if (locale.indexOf("en") == 0){
        return url + ".js";
      }
      return url + "_" + getJavaLanguage(locale) + ".js";
    }
    var messageBundleLoaded = function(locale){
      return typeof TrMessageFactory._TRANSLATIONS !== "undefined";
    };
    adf.mf.api.amx.loadJavaScriptByLocale(localeList, messageBundleUrlConstructor, messageBundleLoaded, callback);
  }

  function generateLocaleList(locale, useVariant){
    var localeJava = getJavaLanguage(locale); // will convert "-" to "_"
    var localeArray = splitLocale(localeJava);
    var language = localeArray[0];
    var country = localeArray[1];
    var variant = localeArray[2];
    var localeList = [];
    if (locale.indexOf("en") != 0){
      localeList.push("en-US");
    }
    if (language != null){
      localeList.push(language);
      if (country != null){
        localeList.push(language+"-"+country);
        if (variant != null && useVariant){
          localeList.push(language+"-"+country+"-"+variant);
        }
      }
    }
    return localeList;
  }

  function loadOtherMessageBundles(baseUrl, localeList)
  {
    loadMessageBundle(adf.mf.api.amx.AMXErrorMessageBundleName, baseUrl, localeList);
    loadMessageBundle(adf.mf.api.amx.AMXInfoMessageBundleName, baseUrl, localeList);
  }

  function loadMessageBundle(bundleName, baseUrl, localeList)
  {
    var callback = function(locale)
    {
      if (locale === null)
      {
        // for this low-level method, always send in the english string
        if (adf.mf.log.Framework.isLoggable(adf.mf.log.level.WARNING))
        {
          adf.mf.log.Framework.logp(adf.mf.log.level.WARNING, "amx", "loadMessageBundle", "Failed to load " + bundleName);
        }
      }
    };
    var messageBundleLoaded = function(locale)
    {
      return adf.mf.api.amx[bundleName] !== undefined;
    };
    loadGenericMessageBundle(bundleName, baseUrl, localeList, messageBundleLoaded, callback);
  }

  function loadGenericMessageBundle(bundleName, baseUrl, localeList, messageBundleLoaded, callback)
  {
    var messageBundleUrlConstructor = function(locale)
    {
      var url = baseUrl + "/resource/" + bundleName;
      if (locale.indexOf("en") == 0)
      {
        return url + ".js";
      }
      return url + "_" + getJavaLanguage(locale) + ".js";
    }
    adf.mf.api.amx.loadJavaScriptByLocale(localeList, messageBundleUrlConstructor, messageBundleLoaded, callback);
  }

  function getResourceStringImpl(bundleName, key, args)
  {
    var errorMsg;

    var bundleObj = adf.mf.api.amx[bundleName];
    if (bundleObj !== undefined)
    {
      var msg = bundleObj[key];
      if (msg !== undefined)
      {
        var argArray = [msg];
        return adf.mf.log.format.apply(this, argArray.concat(args));
      }

      errorMsg = "Unable to find message key " + key + " for bundle " + bundleName;
    }
    else
    {
      errorMsg = "Unable to find message bundle " + bundleName;
    }

    // for this low-level method, always send in the english string
    if (adf.mf.log.Framework.isLoggable(adf.mf.log.level.SEVERE))
    {
      adf.mf.log.Framework.logp(adf.mf.log.level.SEVERE, "amx", "getResourceStringImpl", errorMsg);
    }

    return null;
  }

  function logResourceImpl(level, methodName, bundleName, key, args)
  {
    if (adf.mf.log.Framework.isLoggable(level))
    {
      adf.mf.log.Framework.logp(level, "amx", methodName, getResourceStringImpl(bundleName, key, args));
    }
  }
  // --------- /Private helper methods --------- //

  // --------- Public methods --------- //
  /**
   * @deprecated
   */
  amx.loadTrinidadResources = function(baseUrl) /* used by bootstrap.html TODO delete this once bootstrap.html has been fixed */
  {
    // for this low-level method, always send in the english string
    if(adf.mf.log.Framework.isLoggable(adf.mf.log.level.WARNING)) {
      adf.mf.log.Framework.logp(adf.mf.log.level.WARNING, "amx", "loadTrinidadResources", "amx.loadTrinidadResources is deprecated, use adf.mf.api.amx.loadTrinidadResources instead");
    }

    adf.mf.api.amx.loadTrinidadResources.apply(this, arguments);
  };

  adf.mf.api.amx.loadJavaScriptByLocale = function(localeList, constructor, predicate, callback){
    // clone the array before calling the load method since it will actually
    // modify the array as it searches
    var clonedList = $.merge([], localeList);
    loadJavaScriptByLocaleImpl(clonedList, constructor, predicate, callback);
  }

  adf.mf.api.amx.loadTrinidadResources = function(baseUrl) /* used by bootstrap.html */
  {
    // Bootstrap the Trinidad locale globals
    _df2DYS = null;
    _locale = null;
    _locale = getUserLanguage();
    var localeList = generateLocaleList(_locale, true);
    var localeListNoVariants = generateLocaleList(_locale, false);
    loadTrinidadLocaleElements(baseUrl, localeList, function(locale){
      if (locale === null){
        // for this low-level method, always send in the english string
        if(adf.mf.log.Framework.isLoggable(adf.mf.log.level.WARNING)) {
          adf.mf.log.Framework.logp(adf.mf.log.level.WARNING, "amx", "loadTrinidadResources", "Failed to load LocaleElements");
        }
      }else{
        // Reassign global locale (necessary since Trinidad does not fallback to en-US).
        _locale = locale;
        loadTrinidadMessageBundle(baseUrl, localeListNoVariants, function(locale){
          if (locale === null){
            // for this low-level method, always send in the english string
            if(adf.mf.log.Framework.isLoggable(adf.mf.log.level.WARNING)) {
              adf.mf.log.Framework.logp(adf.mf.log.level.WARNING, "amx", "loadTrinidadResources", "Failed to load MessageBundle");
            }
          }
        });
      }
    });

    // load any other message bundles that the js system depends on
    loadOtherMessageBundles(baseUrl, localeListNoVariants);
  }

  adf.mf.api.amx.getInfoString = function(key)
  {
    var args = Array.prototype.slice.call(arguments, 1);
    return getResourceStringImpl(adf.mf.api.amx.AMXInfoMessageBundleName, key, args);
  }

  adf.mf.api.amx.getErrorString = function(key)
  {
    var args = Array.prototype.slice.call(arguments, 1);
    return getResourceStringImpl(adf.mf.api.amx.AMXErrorMessageBundleName, key, args);
  }

  adf.mf.api.amx.getErrorId = function(key)
  {
    return getResourceStringImpl(adf.mf.api.amx.AMXErrorMessageBundleName, key + "__ID");
  }

  adf.mf.api.amx.getErrorCause = function(key)
  {
    var args = Array.prototype.slice.call(arguments, 1);
    return getResourceStringImpl(adf.mf.api.amx.AMXErrorMessageBundleName, key + "_CAUSE", args);
  }

  adf.mf.api.amx.getErrorAction = function(key)
  {
    var args = Array.prototype.slice.call(arguments, 1);
    return getResourceStringImpl(adf.mf.api.amx.AMXErrorMessageBundleName, key + "_ACTION", args);
  }

  adf.mf.api.amx.logAndThrowErrorResource = function(methodName, key)
  {
    var args = Array.prototype.slice.call(arguments, 2);
    var msg = getResourceStringImpl(adf.mf.api.amx.AMXErrorMessageBundleName, key, args);

    if (adf.mf.log.Framework.isLoggable(adf.mf.log.level.SEVERE))
    {
      adf.mf.log.Framework.logp(adf.mf.log.level.SEVERE, "amx", methodName, msg);
    }

    throw adf.mf.api.amx.getErrorId(key) + ": " + msg;
  }

  adf.mf.api.amx.logInfoResource = function(level, methodName, key)
  {
    var args = Array.prototype.slice.call(arguments, 3);
    logResourceImpl(level, methodName, adf.mf.api.amx.AMXInfoMessageBundleName, key, args);
  }

  adf.mf.api.amx.logResource = function(level, methodName, bundleName, key)
  {
    var args = Array.prototype.slice.call(arguments, 4);
    logResourceImpl(level, methodName, bundleName, key, args);
  }

  adf.mf.api.amx.getResourceString = function(bundleName, key)
  {
    var args = Array.prototype.slice.call(arguments, 2);
    return getResourceStringImpl(bundleName, key, args);
  }
  // --------- /Public methods --------- //

}) ();
//--------- /Utilities --------- //
/* Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved. */
/* ------------------------------------------------------ */
/* ------------------- amx-core.js ---------------------- */
/* ------------------------------------------------------ */

var amx = amx || {}; /* deprecated */

var adf             = adf                 || {};
adf.mf              = adf.mf              || {};
adf.mf              = adf.mf              || {};
adf.mf.api          = adf.mf.api          || {};
adf.mf.api.amx      = adf.mf.api.amx      || {};
adf.mf.internal     = adf.mf.internal     || {};
adf.mf.internal.amx = adf.mf.internal.amx || {};
// TODO finish the migration from "amx.*" to "adf.mf.api.amx.*" and "adf.mf.internal.amx.*"

// --------- Config Initialization --------- //
(function($){

  amx.dtmode = false;
  amx.testmode = false;

  // GREGOR
  amx.CALL_IDX = 1;

  if (window.location.href.indexOf("amx_dtmode=true") != -1)
  {
    amx.dtmode = true;
    console.log("amx - dtmode on");
  }

  if (window.location.href.indexOf("amx_testmode=true") != -1)
  {
    console.log("amx - testmode on");
    amx.testmode = true;
  }

  amx.config =
  {
    debug:
    {
      enable: false,
      onScreen: false
    }
  };

  amx.log = {};

  amx.log.debug = function(text)
  {
    if (amx.config.debug.enable)
    {
      if (amx.config.debug.onScreen && !amx.$amxDebug)
      {
        amx.$amxDebug = $("<div id='amxDebug'></div>").appendTo("body");
      }
      if (amx.$amxDebug)
      {
        amx.$amxDebug.prepend(text + "<br />");
      }
      else
      {
        console.log(text);
      }
    }
  }

  amx.log.error = function(text)
  {
    text = "AMX-ERROR: " + text;
    if (amx.config.debug.onScreen && !amx.$amxDebug)
    {
      amx.$amxDebug = $("<div id='amxDebug'></div>").appendTo("body");
    }
    if (amx.$amxDebug)
    {
      amx.$amxDebug.prepend(text + "<br />");
    }
    console.log(text);
  }


  amx.log.warn = function(text)
  {
    text = "AMX-WARN: " + text;
    if (amx.$amxDebug)
    {
      amx.$amxDebug.prepend(text + "<br />");
    }
    if (console.log)
    {
      console.log(text);
    }
  }

})(jQuery);
// --------- /Config Initialization --------- //


// --------- On Ready --------- //
$(function(){

  amx.$bodyPage = $("#bodyPage");
  var $body = $("body");

  $body.bind("touchmove", function (event)
  {
    // TODO: need to change this when we have the 1 touch scrolling.
    if (event.originalEvent.touches && event.originalEvent.touches.length < 2)
    {
      event.preventDefault();
    }
  });

  //$("body").append("<div id='amxUIFlush'><div>");
  //amx.$UIFlush = $("#amxUIFlush");
  //amx.UIFlushVal = -1;
  // config application override
  if (window.location.href.indexOf("amx.config.debug.enabled=true") > -1)
  {
    amx.config.debug.enable=true;
  }

  // Apply the HTML[dir], HTML[lang], and document.dir based on some defaults or via the query string.
  try
  {
    var theLang = navigator.language; // generally this is enough
    var theDir = document.dir; // generally this is empty
    if (theDir == "")
      theDir = "ltr"; // default to LTR
    var queryString = document.location.search;
    if ((queryString != null) && (queryString.length > 0))
    {
      // The Objective-C code should provide us the system's lang and dir settings and we should use them.
      newLang = adfc.internal.UrlUtil.getUrlParamValue(queryString, "lang");
      if (newLang != null)
      {
        theLang = unescape(newLang);
      }
      newDir = adfc.internal.UrlUtil.getUrlParamValue(queryString, "dir");
      if (newDir != null)
      {
        theDir = unescape(newDir);
      }
    }
    document.documentElement.setAttribute("lang", theLang);
    document.documentElement.setAttribute("dir", theDir);
    document.dir = theDir;
  }
  catch (problem)
  {
    throw new Error("Problem applying the document language and direction: " + problem);
  }

});
// --------- /On Ready --------- //

// ------ amx UI ------ //
(function(){

  // experimental
  amx.uiFlush = function()
  {
    amx.UIFlushVal = -1 * amx.UIFlushVal;
    amx.$UIFlush.html(amx.UIFlushVal);
    amx.$UIFlush.css("width", 30 + amx.UIFlushVal + "px");
  }

  // this tell if the app is transitioning something (event should be frozen when doing so)
  amx.transitioning = false;

  amx.acceptEvent = function()
  {
    return !amx.transitioning && !amx.dtmode;
  }

  amx.getCurrentPageName = function()
  {
    return $(".amx-view-container.current").attr("data-pagename");
  }

  amx.hooks = {};
  // ------ Public API ------ //
  var isFirstPage = true;
  var initDfd;

  //  Maintain a stack of view history so we know where we are right now
  //  and where to return to if back-button naivation occurrs.
  //
  //  Each entry in the stack has three properties:
  //           itemId: unique id for the viewHistory item instance.
  //           viewId: the logical viewId.
  //          amxPage: the name of the actual AMX page being displayed.
  //   transitionType: the transition animation type used to display the page.
  amx.viewHistory = new Array();

  /**
   * @deprecated
   */
  amx.pushViewHistory = function(viewId, amxPage, transType) /* used by bootstrap.html TODO delete this once bootstrap.html has been fixed */
  {
    adf.mf.api.amx.logInfoResource(adf.mf.log.level.WARNING, "pushViewHistory", "MSG_DEPRECATED", "amx.pushViewHistory", "adf.mf.api.amx.pushViewHistory");
    adf.mf.api.amx.pushViewHistory.apply(this, arguments);
  };

  adf.mf.api.amx.pushViewHistory = function(viewId, amxPage, transType) /* used by bootstrap.html */
  {
    var newViewItem = {};
    
    adf.mf.internal.perf.start("pushViewHistory", viewId);
    newViewItem.itemId = amx.uuid(); // this is the mfContextInstance ID
    newViewItem.viewId = viewId;
    newViewItem.amxPage = amxPage;
    newViewItem.transitionType = transType;

    var currentPageName = amx.getCurrentPageName();
    if (currentPageName !== amxPage)
    {
      amx.viewHistory.push(newViewItem);

      // Construct a new mfContext instance:
      adf.mf.internal.amx._setMfContextInstance(newViewItem, true);
    }
    adf.mf.internal.perf.stop("pushViewHistory", viewId);
  };

  amx.popViewHistory = function()
  {
	  adf.mf.internal.perf.start("popViewHistory");
    var item = amx.viewHistory.pop();
    adf.mf.internal.perf.stop("popViewHistory", item);
    return item;
  };

  amx.peekViewHistory = function()
  {
    var item = amx.viewHistory[amx.viewHistory.length-1];
    return item;
  }

  /**
   * Fetches the AMX client state for the specified AMX node ID that was previously stored.
   * Renderers would call this to retrieve old state in create() or postDisplay().
   * @param {String} amxNodeId the amxNode.id that uniquely identifies the stored data
   * @return {Object} undefined or the client state data that was previously stored in this view instance
   */
  adf.mf.api.amx.getClientState = function(amxNodeId)
  {
    var stateValue = adf.mf.internal.amx._getClientStateMap();
    if (amxNodeId == null)
    {
      throw new Error("Client state access requires an amxNodeId but was given: " + amxNodeId);
    }
    else if (stateValue == null)
    {
      throw new Error("Client state access failed to establish a place for storage: " + stateValue);
    }
    else
    {
      var payloadJsonObject = stateValue[amxNodeId];
      return payloadJsonObject;
    }
  };

  /**
   * Stores/replaces the AMX client state for the specified AMX node ID.
   * Preferrably, renderers would call this whenever a state change happens (e.g. result of
   * spinning the carousel.
   * However, it is not always feasible to detect when a state change happens so you may need
   * to update the state for your component whenever just before the view is going to be
   * discarded.  There are 2 possible scenarios that you will need to account for:
   * - Renderer refresh() (for navigating to the same view again)
   * - Renderer preDestroy() (for navigating to a new view and navigating to "__back" at a later time)
   * @param {String} amxNodeId the amxNode.id that uniquely identifies the stored data
   * @param {Object} payloadJsonObject the client state data to store for the lifetime of this view instance
   */
  adf.mf.api.amx.setClientState = function(amxNodeId, payloadJsonObject)
  {
    var stateValue = adf.mf.internal.amx._getClientStateMap();
    if (amxNodeId == null)
    {
      throw new Error("Client state access requires an amxNodeId but was given: " + amxNodeId);
    }
    else if (stateValue == null)
    {
      throw new Error("Client state access failed to establish a place for storage: " + stateValue);
    }
    else
    {
      stateValue[amxNodeId] = payloadJsonObject;
    }
  };

  /**
   * Using true for the following flag results in "oracle.adfmf.framework - adf.mf.el - setValue] Since the
   * java is not available we will skip the remote write." failure messages when attempting to navigate.
   */
  adf.mf.internal.amx._useBruceApproach = (window.location.search.indexOf("useBruceWay=true") != -1);

  adf.mf.internal.amx._getClientStateMap = function()
  {
    var stateValue;
    if (adf.mf.internal.amx._useBruceApproach)
    {
      var stateName = "#{bindings.amxInternalClientState}";
      if ((stateValue = adf.mf.el.getLocalValue(stateName)) === undefined)
      {
        stateValue = {};
        var getFailed  = function(req, message)
        {
          throw new Error("Client state access failed: " + message);
        };
        adf.mf.el.setLocalValue({"name":stateName, "value":stateValue}, function(){}, getFailed);
      }
    }
    else // don't use Bruce's way
    {
      stateValue = adf.mf.internal.amx._nonBruceClientStateMap;
    }

    return stateValue;
  };

  /**
   * Establish (or re-establish) the mfContext instance for the page that the user will now be interacting with.
   */
  adf.mf.internal.amx._setMfContextInstance = function(viewHistoryItem, brandNewInstance)
  {
    if (adf.mf.internal.amx._useBruceApproach)
    {
      var pageDef    = viewHistoryItem.amxPage;
      var instanceId = viewHistoryItem.itemId;
      var resetState = false; // per Bruce, use false here (may in the future consider how brandNewInstance plays into it)
      var reSync     = false; // per Bruce, use false here (may in the future consider how brandNewInstance plays into it)
      var setFailed  = function(req, message)
      {
        throw new Error("Navigation failed: (mfContext set) " + message);
      };
      adf.mf.api.setContextInstance(pageDef, instanceId, resetState, reSync, function(){}, setFailed);
    }
    else // don't use Bruce's way
    {
      if (viewHistoryItem._nonBruceClientStateMap == null)
      {
        viewHistoryItem._nonBruceClientStateMap = {};
      }
      adf.mf.internal.amx._nonBruceClientStateMap = viewHistoryItem._nonBruceClientStateMap;
    }
  };

  /**
   * Remove the mfContext instance for the page that the user will now be leaving.
   */
  adf.mf.internal.amx._removeMfContextInstance = function(viewHistoryItem)
  {
    if (adf.mf.internal.amx._useBruceApproach)
    {
      var pageDef      = viewHistoryItem.amxPage;
      var instanceId   = viewHistoryItem.itemId;
      var removeFailed = function(req, message)
      {
        throw new Error("Navigation failed: (mfContext removal) " + message);
      };
      adf.mf.api.removeContextInstance(pageDef, instanceId, function(){}, removeFailed);
    }
    else // don't use Bruce's way
    {
      adf.mf.internal.amx._nonBruceClientStateMap = null;
    }
  };

  /**
   * @deprecated
   */
  amx.doNavigation = function(outcome)
  {
    adf.mf.api.amx.logInfoResource(adf.mf.log.level.WARNING, "doNavigation", "MSG_DEPRECATED", "amx.doNavigation", "adf.mf.api.amx.doNavigation");
    adf.mf.api.amx.doNavigation.apply(this, arguments);
  };

  adf.mf.api.amx.doNavigation = function(outcome)
  {
    adf.mf.internal.perf.start("amx.doNavigation", outcome.toString() );

    var navRequest = {};
    navRequest.currentViewId = amx.peekViewHistory().viewId;
    navRequest.outcome = outcome;

    var navSuccess = function(req, result)
    {
      var transitionType;
      var amxPage;
      var isBack;

      // See if we navigated to a new viewId.
      if (result.isNewViewId())
      {
        var newViewId = result.getViewId();
        var newAmxPage = amxPage = result.getVdlDocumentPath();
        transitionType = result.getTransitionType();

        // Record the view history.
        adf.mf.api.amx.pushViewHistory(newViewId, newAmxPage, transitionType);

        isBack = false;

      }
      else if (result.isBackNavigation())
      {
        // See if there was a back-button navigation.
        if (amx.viewHistory.length == 1)
        {
          throw new Error("Cannot navigate back any further; already at the last page.");
        }
        else
        {
          // Remove the currently-shown view item:
          var poppedViewItem = amx.popViewHistory();
          transitionType = poppedViewItem.transitionType;

          // Remove its mfContext:
          adf.mf.internal.amx._removeMfContextInstance(poppedViewItem);

          // Restore the peeked view item:
          var peekedViewItem = amx.peekViewHistory();
          amxPage = peekedViewItem.amxPage;
          adf.mf.internal.amx._setMfContextInstance(peekedViewItem, false);

          isBack = true;
        }
      }
      adf.mf.internal.perf.stop("amx.doNavigation", outcome.toString() );


      adf.mf.api.amx.displayAmxPage(amxPage).done(function($page)
      {
        adf.mf.internal.perf.start("displayAmxPage(amxPage).done", amxPage);

        var $current = amx.$bodyPage.children(".current");
        var $new = $page;
        // for now, doing the without the transition

        //TODO: Needs to put all the transition in a amx.fx component (allow to register custom fx)
        var transitionHandler = amx.getTransitionHandler(transitionType);
        if (transitionHandler)
        {
            // adf.mf.internal.perf.start("navigation", "with transition");

          amx.transitioning = true;

          // NOTE: technically it should be after the transition is over, however, the $node.data("amxNode") will be undefined then (probably jquery removes it on delete).
          //       So, for now, we put this before the transition.
          adf.mf.internal.amx.processDestroy($current);
          transitionHandler($current,$new,isBack).done(function()
          {
            amx.transitioning = false;
            // adf.mf.internal.perf.stop("navigation", "with transition");
          });
        }
        else
        {
          // adf.mf.internal.perf.start("navigation", "with no transition");

          adf.mf.api.amx.logInfoResource(adf.mf.log.level.WARNING, "doNavigation", "MSG_NOT_TRANSITIONING");
          amx.transitioning = true;
          // we just show it.
          $current.removeClass("current").addClass("old");
          $new.removeClass("new").addClass("current");
          adf.mf.internal.amx.processDestroy($current);
          $current.remove();
          amx.transitioning = false;
          // adf.mf.internal.perf.stop("navigation", "with no transition");
        }

        //TODO: need to clean the "old" page

        adf.mf.internal.perf.stop("displayAmxPage(amxPage).done", amxPage);
      });
    };
    var navFailed = function(req, message)
    {
      adf.mf.internal.perf.stop("amx.doNavigation", outcome.toString() );
      throw new Error("Navigation failed: " + message);
    };

    // Give renderers a chance to save off anything they want to preserve (e.g. scroll positions):
    var $current = amx.$bodyPage.children(".current");
    adf.mf.internal.amx.processPreDestroy($current);

    adfc.NavigationHandler.handleNavigation(navRequest, navSuccess, navFailed);

  };

  var initQueue = [];
  var postDisplayQueue = [];

  adf.mf.internal.amx.processPreDestroy = function($parent)
  {
    $parent.find(".amx-has-predestroy").each(function()
    {
      var $node = $(this);
      var amxNode = $node.data("amxNode");
      var nodeTypeHandler = amx.getNodeTypeHandler(amxNode);
      if (nodeTypeHandler && nodeTypeHandler.preDestroy)
      {
        /**
         * Renderer function so you can be notified just before the current view is destroyed;
         * when about to navigate to a new view.
         * @param $node the root JQuery node associated with this renderer
         * @param amxNode the AMX component object associated with this renderer
         */
        nodeTypeHandler.preDestroy($node,amxNode);
      }
    });
  }

  adf.mf.internal.amx.processDestroy = function($parent)
  {
    $parent.find(".amx-has-destroy").each(function()
    {
      var $node = $(this);
      var amxNode = $node.data("amxNode");
      var nodeTypeHandler = amx.getNodeTypeHandler(amxNode);
      if (nodeTypeHandler && nodeTypeHandler.destroy)
      {
        nodeTypeHandler.destroy($node,amxNode);
      }
    });
  }

  amx.processAndCleanInitQueue = function()
  {
    if (initQueue.length > 0)
    {
      $.each(initQueue,function(i,$nodeToInit)
      {
        var amxNode = $nodeToInit.data("amxNode");
        var nodeTypeHandler = amx.getNodeTypeHandler(amxNode);
        if (amxNode && nodeTypeHandler && nodeTypeHandler.init)
        {
          nodeTypeHandler.init($nodeToInit,amxNode);
        }
        else
        {
          adf.mf.api.amx.logInfoResource(adf.mf.log.level.WARNING, "processAndCleanInitQueue", "MSG_CANT_INIT_NODE", $nodeToInit);
        }
      });

      // cleanup the initQueue
      delete initQueue;
      initQueue = [];
    }
  }

  amx.processAndCleanPostDisplayQueue = function()
  {
    if (postDisplayQueue.length > 0)
    {
      $.each(postDisplayQueue,function(i,$nodeToPostDisplay)
      {
        var amxNode = $nodeToPostDisplay.data("amxNode");
        var nodeTypeHandler = amx.getNodeTypeHandler(amxNode);
        if (amxNode && nodeTypeHandler && nodeTypeHandler.postDisplay)
        {
          nodeTypeHandler.postDisplay($nodeToPostDisplay,amxNode);
        }
        else
        {
          adf.mf.api.amx.logInfoResource(adf.mf.log.level.WARNING, "processAndCleanPostDisplayQueue", "MSG_CANT_POSTDISPLAY_NODE", $nodeToPostDisplay);
        }
      });

      // cleanup the initQueue
      delete postDisplayQueue;
      postDisplayQueue = [];
    }
  }

  amx.queueForInit = function($node)
  {
    initQueue.push($node);
  }

  amx.queueForPostDisplay = function($node)
  {
    postDisplayQueue.push($node);
  }

  // flag that set who should process the queues
  amx.mustProcessQueues = true;

  /**
   * @deprecated
   */
  amx.displayAmxPage = function(amxPageName) /* used by bootstrap.html TODO delete this once bootstrap.html has been fixed */
  {
    adf.mf.api.amx.logInfoResource(adf.mf.log.level.WARNING, "displayAmxPage", "MSG_DEPRECATED", "amx.displayAmxPage", "adf.mf.api.amx.displayAmxPage");
    return adf.mf.api.amx.displayAmxPage.apply(this, arguments);
  };

  /**
   * Load and Display amxPage
   * @param {Object} amxPageName
   */
  adf.mf.api.amx.displayAmxPage = function(amxPageName) /* used by bootstrap.html */
  {

    var dfd = $.Deferred();
    adf.mf.internal.perf.start("GetPagetInitContext", amxPageName);
    //we get the page
    var dfdAmxPage = getAmxPage(amxPageName);

    var dfdData = initData(amxPageName);

    // clear the bindings
    amx.clearBindings();

    // set to false so that every sub renders does not process the queues
    amx.mustProcessQueues = false;

    // When amxPage, initData, and initUI resolve, we continue:
    $.when(dfdAmxPage, dfdData, initUI()).done(function(amxPage, dataSuccess)
    {
      adf.mf.internal.perf.stop("GetPagetInitContext", amxPageName);

      // We render the page
      amx.render(amxPage).done(function($pageContent)
      {
        adf.mf.internal.perf.start("amx.displayAmxPage-amx.render.done", amxPageName);
        // if it is the first page, clean the $bodyPage
        if (isFirstPage)
        {
          amx.$bodyPage.empty();
        }

        //TODO: Might need to get
        var $viewContainer = $("<div data-pageName='" + amxPageName + "' class='amx-view-container'></div>");
        $viewContainer.addClass("new");

        amx.$bodyPage.append($viewContainer);

        //make sure the class are consistent
        $viewContainer.removeClass("old").addClass("new");

        $viewContainer.empty().append($pageContent);

        //TODO: needs to move this refresh above
        // We process and clean the initQueue
        amx.processAndCleanInitQueue();

        //If it is the first page, we handler the display
        if (isFirstPage)
        {
          $viewContainer.removeClass("new").addClass("current");
          isFirstPage = false;
        }
        else
        {
          // otherwise, we let the doDisplay
        }

        amx.processAndCleanPostDisplayQueue();

        // reset to true
        amx.mustProcessQueues = true;

        // adf.mf.internal.perf.start("resolve");
        dfd.resolve($viewContainer);
        // adf.mf.internal.perf.stop("resolve");

        adf.mf.internal.perf.stop("amx.displayAmxPage-amx.render.done", amxPageName);
      });

    });

    return dfd.promise();
  };

  // ------ resource loading ------ //
  var loadedJavaScriptResources = {};
  var loadedCssResources = {};
  var loadingCssLinks = [];
  var cssLoadingCheckInterval = null;
  var cssLoadingWaitStarted = 0;
  var cssLastCheckSheetCount = 0;

  /**
   * Function to load a JavaScript file
   * @param {String} src the URI to the JavaScript file
   * @return {Object} jQuery deferred object that is resolved once the script has been loaded.
   */
  amx.includeJs = function(src)
  {
    if (loadedJavaScriptResources[src])
    {
      return $.Deferred().resolve();
    }

    var dfd = $.Deferred();
    // Use an XHR to retrieve the JavaScript. Usage of an XHR allows us to be notified
    // of when the script has been loaded, or has failed to load to be able to correctly
    // invoke the correct method on the deferred jQuery object.
    $.ajax(
      {
        "type": "GET",
        "url": src,
        "dataType": "script",
        "async": false,
        "error":
          function(jqXHR, textStatus, errorThrown)
          {
            adf.mf.api.amx.logInfoResource(adf.mf.log.level.SEVERE,
              "amx.includeJs",
              "MSG_FAILED_TO_LOAD", src);
            dfd.reject();
          },
        "success":
          function(data, textStatus, jqXHR)
          {
            amx.log.debug("Successfully loaded JavaScript "+src);
            dfd.resolve();
          },
        "dataFilter":
          function (data, type)
          {
            // Permit debugging of the source (currently only works in firebug, google chrome
            // and webkit nightly):
            data += "\n//@ sourceURL="+src;
            return data;
          }
      });

    amx.log.debug("Sent request for JS script source: " + src);
    loadedJavaScriptResources[src] = true;

    return dfd;
  }

  /**
   * Function that checks for the completion of loading CSS files
   * (polls from a callback from a window interval)
   */
  function waitTillCssLoaded()
  {
    var styleSheets = document.styleSheets;
    var numStyleSheets = styleSheets.length;

    // Don't bother checking if the count has not changed from the last poll
    if (cssLastCheckSheetCount == numStyleSheets)
    {
      return;
    }
    cssLastCheckSheetCount = numStyleSheets;
    // Loop through all the nodes that we are still waiting to finish loading
    for (var i = 0; i < loadingCssLinks.length; ++i)
    {
      var obj = loadingCssLinks[i];
      var nonLoadedNode = obj["node"];

      for (var j = 0; j < numStyleSheets; ++j)
      {
        var linkNode = styleSheets[j].ownerNode;
        // See if this style sheet is for the node we are waiting to be loaded.
        if (nonLoadedNode == linkNode)
        {
          // When the style sheet appears in the styleSheets collection,
          // it has finished loading
          var dfd = obj["deferred"];
          dfd.resolve(linkNode);

          // Remove the item from the array
          loadingCssLinks.splice(i--, 1);
          amx.log.debug("CSS resource loaded: " + obj["path"]);

          if (loadingCssLinks.length == 0)
          {
            // We are not waiting on any more nodes
            window.clearInterval(cssLoadingCheckInterval);
            cssLoadingCheckInterval = null;
            return;
          }

          break;
        }
      }
    }

    var timeWaiting = new Date().getMilliseconds() - cssLoadingWaitStarted;
    // Since the code is not notified of CSS files that failed to load, only way for a maximum
    // of 5 seconds for all CSS files to load and then throw an error
    if (timeWaiting >= 5000)
    {
      for (var index in loadingCssLinks)
      {
        var obj = loadingCssLinks[index];
        // Notify the listener that the resource failed to load
        obj["deferred"].reject();
        adf.mf.api.amx.logInfoResource(adf.mf.log.level.SEVERE,
          "amx.includeCss",
          "MSG_FAILED_TO_LOAD", obj["path"]);
      }
      loadingCssLinks = [];
      window.clearInterval(cssLoadingCheckInterval);
      cssLoadingCheckInterval = null;
    }
  }

  /**
   * Function to load a CSS file
   * @param {String} path the URI to the CSS file
   * @return {Object} jQuery deferred object that is resolved once the style sheet has been loaded.
   */
  amx.includeCss = function(path)
  {
    if (loadedCssResources[path])
    {
      // Return a deferred object already resolved to indicate that the source
      // has already been loaded
      return $.Deferred().resolve();
    }

    // Currently, the only supported means in WebKit browsers to determine if a style sheet has
    // finished loading is to check for the style sheet to appear in the document.styleSheets
    // collection. There are no events that are associated with the loading, so polling this
    // collection is the only means, at the moment, to determine this information.
    //
    // We need to use a <link> tag so that the URLs in the CSS are preserved. If we were to
    // attempt to use a <style> tag and inject the content from the CSS file into the page, the
    // relative URLs would no longer work.

    // First add the
    var node = $("<link rel='stylesheet' type='text/css' />")
      .attr("href", path)
      .appendTo(document.head).get(0);

    amx.log.debug("Added CSS resource: " + path);
    loadedCssResources[path] = true;

    var loadedDfd = $.Deferred();

    // Store an object with the deferred object to be able to notify when loaded,
    // and the node to check to see when the loading has completed.
    loadingCssLinks.push(
      {
        "path": path,
        "node": node,
        "deferred": loadedDfd
      });

    // See if a timer has already been started, if not start one to poll the document.styleSheets
    // collection
    if (cssLoadingCheckInterval == null)
    {
      // Use a 10ms timeout. These resources are local to the device, so it should not take long
      // for them to be loaded.
      cssLoadingCheckInterval = window.setInterval(waitTillCssLoaded, 10);
    }

    // Set or reset when we started to wait for the CSS to load. This leaves a maximum wait time
    // of 5 seconds from the last CSS file added.
    cssLoadingWaitStarted = new Date().getMilliseconds();

    // Return the deferred object so that the caller may be notified once the CSS file has been
    // completely loaded
    return loadedDfd;
  }
  // ------ /resource loading ------ //

  /**
   * Convenient method to sequentialy resolve each item of an array. If the itemResolver method returns a deferred, it will wait until resolved before processing the next element.
   * If the itemResolver returns the direct value, it will to the next item;
   * @param {array || TreeNodeIterator} items is the array or the TreeNodeIterator to iterate threw
   * @param {function} itemResolver(item) function that will resolve the item. Can return the value or a deferred that will resolve with the value
   * @return {Deferred} Deferred that will resolve with the array of values returned by the item resolver
   */
  amx.serialResolve = function(items,itemResolver)
  {
	  var _type;
	  
	try {
		  if (adf.mf.log.Performance.isLoggable(adf.mf.log.level.FINEST))
		  { 
			  adf.mf.internal.perf.trace("amx.serialResolve", "items is of type: " + items.constructor.name + " or " + items[".type"]);
		  }
		  _type = items.constructor.name || items[".type"];
	}
	catch(te) 
	{
		/* ignore */
	}

	if (_type === "TreeNodeIterator")
    {
      return amx.iteratorResolve(items,itemResolver);
    }
    else
    {
      var dfd = $.Deferred();
      var results = [];
      var i = 0;

      function resolveAndNext()
      {
        if (i < items.length)
        {
          var item = items[i];
          var itemResolverResult = itemResolver(item,i);

          // if it is a promise (but not a jquery object, which is also a promise), then, pipe it
          if (typeof itemResolverResult !== "undefined" && itemResolverResult !== null && $.isFunction(itemResolverResult.promise) && !itemResolverResult.jquery)
          {
            itemResolverResult.done(function(result)
            {
              results.push(result);
              i += 1;
              resolveAndNext();
            });
          }
          else
          {
            // if it is a normal object or a jqueryObject, then, just push the value and move to the next
            results.push(itemResolverResult);
            i += 1;
            resolveAndNext();
          }
        }
        else
        {
          // once we run out
          dfd.resolve(results);
        }
      }
      resolveAndNext();
      return dfd.promise();
    }
  }
  
  /**
   * Determine if parameter is a finite number
   * @param {Object} n is the object to check
   */  
  adf.mf.internal.amx.isFiniteNumber = function (n)
  {
    return !isNaN(parseFloat(n)) && isFinite(n);
  }

  /**
   * Iterates over items provided by elNodeIterator and executes itemResolver for each item.  The number of iterations can be
   * limited by providing maxIterations.  If the itemResolver method returns a deferred, it will wait until resolved before processing the next element.
   * If the itemResolver returns the direct value, it will do the next item.
   * @param {TreeNodeIterator} elNodeIterator is the iterator to iterate over
   * @param {function} itemResolver(item) function that will resolve the item. Can return the value or a deferred that will resolve with the value
   * @param {Object} maxIterations specifies the maximum number of iterations to perform
   */
  amx.iteratorResolve = function(elNodeIterator,itemResolver,maxIterations)
  {
    var dfd = $.Deferred();
    var results = [];
    var methodNext = "first";
    var rowCount = 0;
    var _maxIterations = Infinity;
    if (maxIterations)
    {
      _maxIterations = maxIterations;
    }

    function resolveNext()
    {
      if (elNodeIterator.hasNext() && rowCount < _maxIterations)
      {
        elNodeIterator[methodNext](function(a,b)
        {
          methodNext = "next";
          var item = b[0].value;

          //FIXME: for now, turn this off for debugging
          var itemResolverResult = itemResolver(item,elNodeIterator.getCurrentIndex());

          // if it is a promise (but not a jquery object, which is also a promise), then, pipe it
          if (typeof itemResolverResult !== "undefined" && itemResolverResult !== null && $.isFunction(itemResolverResult.promise) && !itemResolverResult.jquery)
          {
            itemResolverResult.done(function(result)
            {
              results.push(result);
              ++rowCount;
              resolveNext();
            });
          }
          else
          {
            // if it is a normal object or a jqueryObject, then, just push the value and move to the next
            results.push(itemResolverResult);
            ++rowCount;
            resolveNext();
          }
        }, function(a,b)
        {
          adf.mf.api.amx.logInfoResource(adf.mf.log.level.SEVERE, "resolveNext", "MSG_ITERATOR_FIRST_NEXT_ERROR", a, b);
          ttr = a;
          tte = b;
        });
      }
      else
      {
        dfd.resolve(results);
      }
    }
    resolveNext();

    return dfd.promise();
  }

  /*TreeNodeIterator
k: id
k: treeNodeBindings
k: index
k: currentKey
k: createRow
k: first
k: getCurrentIndex
k: getCurrentKey
k: getCurrentRow
k: hasNext
k: hasPrevious
k: last
k: next
k: nextSet
k: previous
k: previousSet
k: refresh
k: setCurrentIndex
k: fetch
k: getKeys
k: removeCurrentRow
k: setCurrentRowWithKey
k: fetchSet
k: fetchProviderByKey
k: updateKeys
   */

  // ------ /Public API ------ //

  function initUI()
  {
    if (initDfd)
    {
      return initDfd;
    }
    else
    {
      initDfd = $.Deferred();
      // forceMockData=true for the DT as well as the RT test harness mode. The DT
      // requires that featureLevelIncludes.jso be in the feature root, NOT relative
      // to the directory of the AMX
      if(forceMockData)
      {
        $.ajax(
        {
          url: adfc.Util.addFeatureRootPrefix("featureLevelIncludes.jso"),
          dataType: "json",
          async: true
        }).done(function(includes)
        {
          loadIncludes(includes);
          initDfd.resolve();
        }).fail(function(e)
        {
          // do nothing, no config.
          initDfd.resolve();
        });
      }
      else
      {
        adfmf.getAmxIncludeList(function(includes)
        {
          loadIncludes(includes);
          initDfd.resolve();
        }, function(er)
        {
          adf.mf.api.amx.logInfoResource(adf.mf.log.level.SEVERE, "initUI", "MSG_AMX_INCLUDE_FAILED", er);
          initDfd.resolve();
        });
      }
      return initDfd;
    }
  }

  function loadIncludes(includes)
  {
    if (includes)
    {
      $.each(includes, function(idx, include)
      {
        var file = include.file;
        if (!amx.hasProtocol(file))
        {
          file = adfc.Util.addFeatureRootPrefix(include.file);
        }
        if (include.type == "StyleSheet")
        {
          amx.includeCss(file);
        }
        else if (include.type == "JavaScript")
        {
          amx.includeJs(file);
        }
      });
    }
  }

  function initData(amxPageName)
  {
    return mockInitData(amxPageName)
  }

  var initDataDone = false;

  function mockInitData(pagename)
  {
    // adf.mf.internal.perf.start("amx.mockInitData");

    var mockInitDataDfd = $.Deferred();
    // if the data has not been initialized, and we are not in the Oracle Shell (ADFMobile undefined), then, we load the model.jso
    //TODO: need to add condition for :  typeof ADFMobile === "undefined" & and !forceInitDataMock
    if (!initDataDone && forceMockData)
    {
      // before we do anything, make sure all of the el gets set up by trying to retrieve the application scope
      // this will allow any data in model.jso that isn't bindings related to not get over-written
      adf.mf.el.getLocalValue("#{applicationScope}");
      pagename = pagename || "nopage";
      // forceMockData=true for the DT as well as the RT test harness mode. The DT
      // requires that model.jso be in the feature root, NOT relative
      // to the directory of the AMX
      $.ajax(
      {
        url: adfc.Util.addFeatureRootPrefix("model.jso"),
        data:
        {
          pagename: pagename
        },
        async: true,
        dataType: "json",
        success: function(data)
        {
          if (data)
          {
            //model = data;
            //adf.mf.el.addVariable("bindings", data);
            $.each(data,function(key,value)
            {
              adf.mf.el.addVariable(key, value);
            });

            adf.mf.el.addVariable("applicationScope", {});
            adf.mf.el.addVariable("pageFlowScope", {});
            initDataDone = true;
            adf.mf.api.amx.logInfoResource(adf.mf.log.level.INFO, "mockInitData", "MSG_AMX_MODEL_JSO_LOADED");
            mockInitDataDfd.resolve();
          }
          else
          {
          adf.mf.api.amx.logInfoResource(adf.mf.log.level.INFO, "mockInitData", "MSG_NO_MODEL_JSO_FOUND");
          forceMockData = false;
          mockInitDataDfd.resolve();
          }
        },
        error: function(arg)
        {
          adf.mf.api.amx.logInfoResource(adf.mf.log.level.INFO, "mockInitData", "MSG_NO_MODEL_JSO_FOUND");
          forceMockData = false;
          mockInitDataDfd.resolve();
        }
      });
    }
    else
    {
      mockInitDataDfd.resolve();
    }

    // adf.mf.internal.perf.stop("amx.mockInitData");

    //return mockInitDataDfd.pipe(initContext(pagename));
    return mockInitDataDfd.pipe(function()
    {
      return initContext(pagename);
    });
  }

  function initContext(amxPageName)
  {
    var dfd = $.Deferred();
    //FIXME: Workaround from Bruce
    /* Per Bruce, we do not need this work around anymore
    if (!forceMockData)
    {
      adf.setContext(
        amxPageName,
        function(){dfd.resolve();},
        function(){console.log("amx adf.setContext('" + amxPageName + "') failed");  dfd.resolve();});
    }
    else
    {
      dfd.resolve();
    }
    return dfd.promise();
    */

    //if (true){ // This DOES NOT WORK on MOCK??
    if (!forceMockData)
    {
      // Per Bruce instruction, first, we setContext "" to reset
      adf.setContext(
        "",
        function()
        {
          // then, we setContext with the pageName
          adf.setContext(
            amxPageName,
            function()
            {
              dfd.resolve();
            },
            function(ex)
            {
              adf.mf.api.amx.logInfoResource(adf.mf.log.level.WARNING, "initContext", "MSG_SET_CONTEXT_FAILED", amxPageName, ex);
              dfd.resolve();
           });
        },
        function(ex)
        {
          adf.mf.api.amx.logInfoResource(adf.mf.log.level.SEVERE, "initContext", "MSG_SET_CONTEXT_EMPTY_STRING_FAILED", ex);
          dfd.resolve();
        });
    }
    else
    {
      dfd.resolve();
    }
    return dfd.promise();
  }

  // pageStructAndDef data by pageName
  var amxPages = {};

  // data by pageName
  var amxData = {};

  /**
   * Load a new instance of the amxPage JSON structure.
   * Returns a Deferred that will resolve with the amxPage JSON Structure. <br />
   *
   * @param {Object} amxPageName
   */
  function loadAmxPage(amxPageName)
  {

    var dfd = $.Deferred();

    var pageFullPath = adfc.Util.addFeatureRootPrefix(amxPageName);

    $.ajax({url : pageFullPath,
      async : true,
      dataType : "text",
      success : function (data)
      {
        var parser = new DOMParser();
        var amxPage = parser.parseFromString(data,"text/xml");
        dfd.resolve(amxPage);
      },
      error : function (jqXHR, textStatus, errorThrown)
      {
        dfd.fail(textStatus);
      }
    });

    return dfd.promise();
  }

  /**
   * Return a Deferred object that will get resolved with the amxPage JSON structure.
   * This will first try to get it from the cache, or load it if needed.
   *
   * @param {Object} amxPageName
   */
  function getAmxPage(amxPageName)
  {

    var dfd = $.Deferred(), loaderDfd;

    var amxPage = amxPages[amxPageName];

    if (amxPage)
    {
      dfd.resolve(amxPage);
    }
    else
    {
      var loaderDfd = loadAmxPage(amxPageName);

      // adf.mf.internal.perf.start("loadAmxPage", amxPageName );

      loaderDfd.done(function(amxPage)
      {
        amxPages[amxPageName] = amxPage;
        dfd.resolve(amxPage);
      }).fail(function()
      {
          // forward the failure argument to the dfd
        var args = Array.prototype.slice.call(arguments);
        dfd.fail.apply(dfd, args);
      });
    }
    
    return dfd.promise();
  }

  var transitionDic = {};

  amx.registerTransitionHandler = function(name,func)
  {
    transitionDic[name] = func;
  }

  amx.getTransitionHandler = function(name)
  {
    return transitionDic[name];
  }

})();

// --------- Rendering Logic --------- //
(function(){

  var rendererDic = {};

  /**
   * Singleton object for maintaining a stack of prefixes for IDs on HTML elements inside of
   * iterating AMX nodes.
   */
  var iterationIdStack =
  {
    _prefix: "",
    _lengthStack: [],
    // Valid ID characters are everything that NMTOKEN allows from XML minus ":" since we are using
    // colons as separators. See http://www.w3.org/TR/2000/WD-xml-2e-20000814#NT-Nmtoken
    // For now just check a sub-set of NMTOKEN as the list is quite lengthy of allowed unicode
    // characters.
    _invalidCharsRe: /[^\w\.\-]/g,

    /**
     * Get the current prefix.
     * @return {String} a non-null string to use as a prefix for node IDs
     */
    getCurrentPrefix: function()
    {
      return this._prefix;
    },

    /**
     * Push an iterator prefix onto the stack.
     * @param {String} baseId the ID of the iterating AMX node to use as the base of the ID prefix
     *                 for the iterator's children nodes.
     * @param {Object} iterationKey the object to convert to a string to uniquely identify items
     *                 in the iterator.
     */
    pushIterator: function(baseId, iterationKey)
    {
      // Save off the old prefix length so that we know the length to truncate to during the
      // pop call.
      var oldLength = this._prefix.length;
      this._lengthStack.push(oldLength);

      // Create the new prefix
      var newPrefix = baseId + ":" + this._escapeIterationKey(iterationKey);

      this._prefix += newPrefix + ":";
    },

    /**
     * Pop the prefix back to the value before the current iteration.
     */
    popIterator: function()
    {
      var newLength = this._lengthStack.pop();
      if (newLength > 0)
      {
        this._prefix = this._prefix.substr(0, newLength);
      }
      else
      {
        this._prefix = "";
      }
    },

    /**
     * Escape an iteration key for usage in an HTML ID attribute.
     * @param {Object} iterationKey the key for the current iteration
     * @return {String} an ID-safe string that may be used to identify the current iteration
     */
    _escapeIterationKey: function(iterationKey)
    {
      // Note that we may want to consider using an ID token cache to improve memory usage
      // so that smaller strings are used. The disadvantage is that the token generation would
      // have to be repeatable so that the node state would be correctly re-applied. For now,
      // we just wish to ensure there are no invalid characters
      if (iterationKey == null)
      {
        adf.mf.api.amx.logInfoResource(adf.mf.log.level.SEVERE, "_escapeIterationKey",
          "MSG_INVALID_ITERATION_KEY", iterationKey);
        return "null";
      }
      var strVal = iterationKey.toString();
      // Replace any non-ID friendly values with an underscore. This assumes that most characters
      // of the iterationKey will be valid and therefore produce a unique key. Using a token
      // cache would address this if this assumption becomes an issue.
      return strVal.replace(this._invalidCharsRe, "_");
    },
    
    /**
     * Determine if there is an iteration container.  Searches the prefix for ":" and returns true
     * if the character is found.
     * @return {Boolean} true if an iteration container has been set; false otherwise.
     */
    hasTopIterationContainer: function()
    {
      if (this._prefix.indexOf(":") > -1)
      {
        return true;
      }
      return false;
    },
    
    /**
     * Returns ID of top-most iteration container.  Finds first occurence of ":" in prefix and returns
     * substring leading up to it.
     * @return {String} ID of top-most iteration container
     */
    getTopIterationContainer: function()
    {
      if (this._prefix.indexOf(":") > -1)
      {
        return this._prefix.substr(0,this._prefix.indexOf(":"));
      }
      return "";
    }
  };

  // ------ resource loading ------ //
  var resourcesData;
  // Load the resources.json file that contains the mapping of the resources
  // needed for AMX nodes:
  $.ajax(
    {
      "url": "js/amx-resources.json",
      "dataType": "json",
      "async": false,
      "success":
        function (data)
        {
          amx.log.debug("Successfully loaded the resources JSON file");
          resourcesData = data;
        },
      "error":
        function (jqxhr, textStatus, errorThrown)
        {
          amx.log.error("Unable to load the resources JSON file. Error: "+errorThrown);
        }
    });

  /**
   * Called from loadResourcesForNode to load resources for a given namespace object and tag name.
   * @param {Object} nsObj resources object for a namespace from the JSON object.
   * @param {String} tagName the local AMX node name or "*" for resources global to the namespace.
   * @param {Array} dfds array of deferred objects to collect.
   */
  function processNodeNsResources(nsObj, tagName, dfds)
  {
    var tagObj = nsObj[tagName];
    if (tagObj == null)
    {
      return;
    }
    var js = tagObj["js"];
    var css = tagObj["css"];

    if (js != null)
    {
      // Load any required javascript files:
      if (Array.isArray(js))
      {
        for (var index in js)
        {
          dfds.push(amx.includeJs(js[index]));
        }
      }
      else
      {
        dfds.push(amx.includeJs(js));
      }
    }

    if (css != null)
    {
      // Load any required style sheet files:
      if (Array.isArray(css))
      {
        for (var index in css)
        {
          dfds.push(amx.includeCss(css[index]));
        }
      }
      else
      {
        dfds.push(amx.includeCss(css));
      }
    }
  }
 
  /**
   * Function to load any resources for an AMX XML node.
   * @param {Node} node the XML node
   * @param {Array} dfds array of jQuery deferred objects to append to for any resources to be
   *                loaded to allow the calling function to determine when all the resources
   *                have been loaded.
   */
  function loadResourcesForNode(node, dfds)
  {
    // The first level of objects are keyed by the namespace URI of the XML node:
    var ns = node.namespaceURI;
    var nsObj = resourcesData[ns];
    if (nsObj == null)
    {
      return;
    }

    // Load any resources for all tags in this namespace:
    processNodeNsResources(nsObj, "*", dfds);

    // Second level are keyed by the node's local name:
    processNodeNsResources(nsObj, node.localName, dfds);
  }
  // ------ /resource loading ------ //

  // ------ API for adf.mf.api.amx.displayAmxPage ------ //
  /**
   * Render a jQuery object from a amxPage XML document.
   * @param {Object} amxPage
   * @return {Deferred} which will get resolved with the $node of the page
   */
  amx.render = function(amxPage)
  {
    return preProcessAllXmlNodes(amxPage.firstChild).pipe(function()
    {
      var $amxPage = $(amxPage);

      // --- loadBundles --- //
      var $loadBundles = $amxPage.find("loadBundle");

      return amx.serialResolve($loadBundles,function(loadBundle)
      {
        if (!amx.dtmode)
        {
          var $loadBundle = $(loadBundle);
          var basename = $loadBundle.attr("basename");
          var variable = $loadBundle.attr("var");

          return amx.loadBundle(basename,variable);
        }
      });
      // --- /loadBundles --- //
    }).pipe(function()
    {
      return amx.renderNode(amxPage.firstChild);
    });
  }

  // ------ /API for adf.mf.api.amx.displayAmxPage ------ //

  // ------ API for Renderers ------ //

  amx.registerRenderers = function(namespace,renderers)
  {
    $.each(renderers,function(key,val)
    {
      var id = (namespace)?(namespace + ":"):"";
      id += key;
      rendererDic[id] = val;
    });
  }

  /**
   * Notify the framework that an iteration node is being processed. Should be called by iterating
   * renderers for each stamp.
   * @param {String} amxNodeId the ID of the iterating AMX node to use as the base of the ID prefix
   *                 for the iterator's children nodes.
   * @param {Object} iterationKey the object to convert to a string to uniquely identify items
   *                 in the iterator.
   */
  amx.beginIterationContainer = function(amxNodeId, iterationKey)
  {
    iterationIdStack.pushIterator(amxNodeId, iterationKey);
  };

  /**
   * Notify the framework that an iteration node has finished being processed. Should be called by
   * iterating renderers after each stamp. Must correspond to a call to beginIterationContainer.
   */
  amx.endIterationContainer = function()
  {
    iterationIdStack.popIterator();
  };

  /**
   * Render the subNodes of an amxNode and add the rendered HTML Element to the parent and return the deferred that will
   * be resolved with the $parent
   * @param {AMXNode} amxNode The direct parent of the subNodes to be rendered (this cannot be an XMLNode)
   * @param {JQueryObject || function} $parent the parent to add the subnodes to (will use $parent.append(...)) or the
   */
  amx.renderSubNodes = function(amxNode,$parent)
  {
    var renderSubNodesDfd = $.Deferred();
    var mustProcessQueues = false;
    if (amx.mustProcessQueues)
    {
      mustProcessQueues = true;
      // turn it off so that the child calls does not process it.
      amx.mustProcessQueues = false;
    }

    if (amxNode && amxNode._xmlNode)
    {
      var children = $(amxNode._xmlNode).children();

      amx.serialResolve(children,function(xmlChild)
      {
        return amx.renderNode(xmlChild).pipe(function($subNode,amxSubNode,subNodeTypeHandler)
        {
          if ($subNode)
          {
            // if the $parent is a function, then, call it, it will take the responsibility to add the $subNode
            if ($.isFunction($parent))
            {
              $parent($subNode);
            }
            else
            {
              // otherwise, just add the new $subNode to the $parent
              $parent.append($subNode);
            }
          }
          return $subNode;
        });
      }).done(function()
      {
        renderSubNodesDfd.resolve($parent);

        if (mustProcessQueues)
        {
          //TODO: need to do the deferred way for both.
          amx.processAndCleanInitQueue();
          amx.processAndCleanPostDisplayQueue();
          amx.mustProcessQueues = true;
        }
      });
    }

    return renderSubNodesDfd.promise();
  }

  amx.nonUITags = {"amx:facet":true,"amx:setPropertyListener":true,"amx:actionListener":true,"amx:loadBundle":true,"amx:convertNumber":true,"amx:convertDateTime":true,"amx:validationBehavior":true,"amx:showPopupBehavior":true,"amx:closePopupBehavior":true};

  amx.isUITag = function(tagName)
  {
    var isUITag = true;

    isUITag = isUITag && (tagName.indexOf("Listener") === -1);

    isUITag = isUITag && !amx.nonUITags[tagName];

    return isUITag;
  }

  /**
   * Render a amxNode or the xmlNode. If it is an xmlNode, then, it will be processed before rendering it.
   * @param {xmlNode or AMXNode} The node for a given element. Can be the process AMXNode or the XMLNode.
   */
  amx.renderNode = function(node)
  {
    //GREGOR
    // Keep index so recursion can be tracked, and we can match up the func starts with stops
    amx.CALL_IDX++;
    var loc_call_idx = amx.CALL_IDX;
    // adf.mf.internal.perf.start("amx.renderNode", " ", "Func ID " + loc_call_idx);

    var renderNodeDfd = $.Deferred();

    var amxNodeDfd;

    // if the node is the xmlNode, then, we need to process it
    // TODO: needs to do a more bullet proof test
    if (node.nodeType)
    {
      adf.mf.internal.perf.start("amx.renderNode", ("xml node - " + node.tagName));
      amxNodeDfd = processXmlNode(node);
    }
    else
    {
      adf.mf.internal.perf.start("amx.renderNode", ("amx node - " + node.id));
      // if it is not a xmlNode, then, assume it is the amxNode, and resolved
      amxNodeDfd = $.Deferred();
      amxNodeDfd.resolve(node);
    }

    amxNodeDfd.done(function(amxNode)
    {
      var tagName = amxNode.tagName;
      var $node = null;

      var mustProcessQueues = false;
      if (amx.mustProcessQueues)
      {
        mustProcessQueues = true;
        // turn it off so that the child calls does not process it.
        amx.mustProcessQueues = false;
      }

      // facet are not rendered as they should be handled by the parent
      if (tagName && amx.isUITag(tagName))
      {
        // if there are a "rendered" property set to false, then, we do not render
        if (amx.isValueFalse(amxNode.rendered))
        {
          renderNodeDfd.resolve(null);
        }
        else
        {
          var nodeTypeHandler = getNodeTypeHandler(amxNode);

          // if rendererFun found
          if (nodeTypeHandler && nodeTypeHandler.create )
          {
            // render node (could be the $node itself or a deferred that will be resolved with the $node)
            var renderResult;
            try
            {
              renderResult = nodeTypeHandler.create(amxNode);
            }
            catch(ex)
            {
              adf.mf.api.amx.logInfoResource(adf.mf.log.level.SEVERE, "renderNode", "MSG_CREATE_FAILED", amxNode.tagName, ex);
            }

            $.when(renderResult).done(function($node)
            {
              // Add node to init and post display queues
              if (nodeTypeHandler.init)
              {
                amx.queueForInit($node);
              }
              if (nodeTypeHandler.postDisplay)
              {
                amx.queueForPostDisplay($node);
              }

              $node.data("amxNode",amxNode);
              // add the amx classes
              $node.addClass("amx-node " + amxNode.tagName.replace(":","-"));

              // add the eventual inlineStyle
              var inlineStyle = amxNode["inlineStyle"];
              if (inlineStyle)
              {
                if (amx.dtmode)
                {
                  // if dtmode, remove el
                  inlineStyle = inlineStyle.replace(/#\{(.*?)\}/ig, ' ');
                }
                $node.attr("style",inlineStyle);
              }
              var styleClass = amxNode["styleClass"];
              if (styleClass)
              {
                if (amx.dtmode)
                {
                  // if dtmode, remove el
                  styleClass = styleClass.replace(/#\{(.*?)\}/ig, ' ');
                }
                $node.addClass(styleClass);
              }

              if (amx.isValueTrue(amxNode.readOnly))
              {
                $node.addClass("amx-readOnly");
              }
              if (amx.isValueTrue(amxNode.disabled))
              {
                $node.addClass("amx-disabled");
              }

              var id = amxNode["id"];
              if (typeof id !== "undefined")
              {
                var prefix = iterationIdStack.getCurrentPrefix();
                $node.attr("id", prefix + id);
                // bug 13856522: if this control is inside an iteration container, store the top-most iteration container's ID
                if (iterationIdStack.hasTopIterationContainer())
                {
                  $node.data("iterationContainer", iterationIdStack.getTopIterationContainer());
                }
              }

              if ($.isFunction(nodeTypeHandler.destroy))
              {
                $node.addClass("amx-has-destroy");
              }

              if ($.isFunction(nodeTypeHandler.preDestroy))
              {
                $node.addClass("amx-has-predestroy");
              }

              renderNodeDfd.resolve($node,amxNode,nodeTypeHandler);

              // once we are done resolving this node, go through and add any children that
              // have an el expression for rendered
              var children = $(amxNode._xmlNode).children("[rendered]");
              for (var childIndex = 0; childIndex < children.length; ++childIndex)
              {
                var $xmlChild = $(children[childIndex]);
                // if this control has a rendered expression, add it to the parent node
                // so that it will get redrawn fully when rendered changes
                var childRendered = $xmlChild.attr("rendered");
                if (childRendered != null)
                {
                  // now make sure that this gets added to the el watch array
                  var els = amx.getElsFromString(childRendered);
                  for (var elIndex = 0; elIndex < els.length; ++elIndex)
                  {
                    bindElTo$node(els[elIndex],$node);
                  }
                }
              }

              if (mustProcessQueues)
              {
                //TODO: need to do the deferred way for both.
                amx.processAndCleanInitQueue();
                amx.processAndCleanPostDisplayQueue();
                amx.mustProcessQueues = true;
              }

            }).fail(function(ex)
            {
              adf.mf.api.amx.logInfoResource(adf.mf.log.level.SEVERE, "renderNode", "MSG_CREATE_FAILED", amxNode.tagName, ex);
            });
          }
          else
          {
            adf.mf.api.amx.logInfoResource(adf.mf.log.level.WARNING, "renderNode", "MSG_NO_RENDERER", amxNode.tagName);
            renderNodeDfd.resolve(null);
          }
        }
      }
      else
      {
        renderNodeDfd.resolve(null);
      }

      adf.mf.internal.perf.stop("amx.renderNode", ("xml node - " + node.tagName));
      // adf.mf.internal.perf.stop("amx.renderNode", amxNode.id, "Func ID " + loc_call_idx);
    });

    // assign the
    $.when(amxNodeDfd,renderNodeDfd).done(function(amxNode,renderArgs)
    {
      if (amxNode && renderArgs && renderArgs.length > 0)
      {
        var $node = renderArgs[0];
        bindElsTo$node(amxNode,$node);
      }
    });

    return renderNodeDfd.promise();
  }

  // --------- Data Change Logic --------- //
  var $nodeArrayByEl = {};

  amx.clearBindings = function()
  {
    delete $nodeArrayByEl;
    $nodeArrayByEl = {};
  }

  amx.get$nodeArrayForEl = function(el)
  {
    return $nodeArrayByEl[el];
  }

  // single data change hander
  function dataChangeHandler(el)
  {
    var $nodeArray = $nodeArrayByEl[el];
    
    if (adf.mf.log.Performance.isLoggable(adf.mf.log.level.FINEST))
    {
    	adf.mf.internal.perf.trace("dataChangeHandler", adf.mf.util.stringify(el));
    }

    if ($nodeArray && $nodeArray.length > 0)
    {
      // we clone it, because $nodeArray might change when called renderNode
      $nodeArray = $nodeArray.slice(0);
      
      /**
       * bug 13856522:  When a control is inside an iteration container (listView, iterator, carousel),
       * it may contain #{row.*} EL expressions.  In that case, we don't want to simply refresh the
       * control by itself since it will fail to resolve any "row" expressions.  Instead, we are going
       * to refresh its parent iteration container.
       * We store the top most iteration container's ID as data on the node itself.  The code below then
       * locates the parent with the given ID and replaces $node with that container.  After we are done,
       * $newArray only contains controls that are not inside iteration containers.
       */
      var set = {};
      var i = 0;
      var $newArray = [];
      for (; i < $nodeArray.length; ++i)
      {
        var $node = $nodeArray[i];
        var iteratorContainerId = $node.data("iterationContainer");
        if (typeof iteratorContainerId !== "undefined")
        {
          if ((iteratorContainerId in set) == false)
          {
            var iteratorParent = $node.parents("#" + iteratorContainerId);
            set[iteratorContainerId] = "";
            $newArray.push(iteratorParent);
          }
        }
        else
        {
          $newArray.push($node);
        }
      }
      
      amx.serialResolve($newArray, adf.mf.internal.amx.dataChangeHandlerByNode(el, false));
    }
  }
  
  /**
   * Create dataChangeHandler function for given EL expression.  The returned function takes as parameter the node that should be 
   * updated as a result of the data change.  If forceNodeRecreate is true, the node is always recreated.
   */
  adf.mf.internal.amx.dataChangeHandlerByNode = function(elString, forceNodeRecreate)
  {
    var el = elString;
    var _forceNodeRecreate = forceNodeRecreate;
    return function($node, idx)
    {
      var amxNode = $node.data("amxNode");

      // NOTE: Here if the amxNode is not defined, this mee that this $node has been replace already but the $newNodeArray has not finished to be updated.
      //       So, the old $nodeArray still point to a deleted $node. So, for now, we just do nothing when we have an "invalid" node.
      if (amxNode)
      {
    		adf.mf.internal.perf.trace("amx.serialResolve", amxNode.id);

        var nodeTypeHandler = amx.getNodeTypeHandler(amxNode);
        var recreateNode = true; // by default true
        // we process the refresh if there is one.
        var refreshDfd; // by default, undefined.
        if (nodeTypeHandler.refresh && _forceNodeRecreate == false)
        {
          refreshDfd = $.Deferred();
          var amxNodeDfd = amx.processXmlNode(amxNode._xmlNode);
          amxNodeDfd.done(function(newAmxNode)
          {
            // compute the changedProperties
            var changedProperties = {};
            $.each(newAmxNode._xmlNode.attributes,function(idx,attr)
            {
              if (attr.nodeValue && attr.nodeValue.indexOf(el) > -1)
              {
                changedProperties[attr.name] = true;
              }
            });
            var refreshResult = nodeTypeHandler.refresh($node,newAmxNode,changedProperties);

            if (refreshResult === false)
            {
              // if the refresh return boolean false, then, we need to create the node
              recreateNode = true;
            }
            else
            {
              // otherwise, assume the refresh took care of the dataChange and turn the recreateNode to false
              recreateNode = false;
              $node.data("amxNode",newAmxNode);
            }
            refreshDfd.resolve();
          });
        }

        // we process the the createNode if needed
        // Note: if the refreshDfd is undefined the $.when will get triggered as well, so the function in the pipe
        //       will always get called
        return $.when(refreshDfd).pipe(function()
        {
          if (recreateNode)
          {
            // unbind this node from any el expression that it is bound to
            unbind$nodeFromEl($node);
            // we make sure that amx.mustProcessQueues is off so that no sub calls (i.e. renderNode) process the queues
            amx.mustProcessQueues = false;

            return amx.renderNode(amxNode._xmlNode).done(function($newNode)
            {
              // on recreate node, we destroy the old node.
              if (nodeTypeHandler.destroy)
              {
                nodeTypeHandler.destroy($node,$node.data("amxNode"));
              }

              // --- refresh the $node --- //
              // it is possible that the new node isn't rendered, so handle that here
              if ($newNode != null)
              {
                $newNode.insertAfter($node);
              }
              $node.remove();

              // we process and clean the Queues
              // Note: Today init and postDisplay are call at the same time. For optimization, init should be called before the node is visible to the user.
              //       In the case of a recreatenode might be hard to have the init before the UI is display to the user (also, the value might be minimum)
              amx.processAndCleanInitQueue();
              amx.processAndCleanPostDisplayQueue();
              // we turn back on the amx.mustProcessQueues
              amx.mustProcessQueues = true;
              // --- /refresh the $node --- //
            });
          }
          else
          {
            return null;
          }
        });
      } // if (amxNode)
      else
      {
        // We do nothing if it is an invalid node
      }
    };
  }

  function bindElsTo$node(amxNode,$node)
  {
    var xmlNode = amxNode._xmlNode;

    $.each(xmlNode.attributes,function(idx,attr)
    {
      // IMPORTANT: just bind the els that can be evaluated (for example, we do not want to bind the ***Listeners)
      if (acceptAttributeForElProcessing(attr.name,attr.nodeValue))
      {
        var els = amx.getElsFromString(attr.nodeValue);
        $.each(els,function(elIdx,el)
        {
          bindElTo$node(el,$node);
        });
      }
    });
  }

  function bindElTo$node(el,$node)
  {
    var $nodeArray = $nodeArrayByEl[el];
    
    // if no $nodeArray, it is the first.
    if (!$nodeArray)
    {
      adf.addDataChangeListeners(el,dataChangeHandler);
      $nodeArray = $nodeArrayByEl[el] = [];
    }

    // since we call this from renderSubNodes now, we
    // would get into a situation where we are adding the
    // same node twice, so make sure we don't do that
    if ($nodeArray.indexOf($node) < 0)
    {
    	if (adf.mf.log.Performance.isLoggable(adf.mf.log.level.FINEST))
    	{
    		adf.mf.internal.perf.trace("bindElTo$node", (el + " ==> " + $node[0].tagName));
    	}
      //console.log("binding el " + el + " $node " + $node[0].tagName);
      $nodeArray.push($node);
    }
  }

  // we must remove $node from any el expression that we have previously bound it to since $node
  // is about to be replaced with a brand new node
  function unbind$nodeFromEl($node)
  {
    for(var el in $nodeArrayByEl)
    {
      if($nodeArrayByEl.hasOwnProperty(el) == false)
      {
        continue;
      }

      var $nodeArray = $nodeArrayByEl[el];

      // if no $nodeArray, just continue
      if (!$nodeArray)
      {
        continue;
      }

      // make sure that this actually exists before trying to remove it
      var index = $nodeArray.indexOf($node);
      if (index < 0)
      {
        continue;
      }

      $nodeArray.splice(index, 1 );
    }
  }

  // Helper method
  // return an array
  amx.getElsFromString = function(elString)
  {
    var result = [];

    if (elString != null)
    {
      var regEx = /[#,$]{.*?}/g;
      var m = regEx.exec(elString);
      while (m != null)
      {
        if (result.indexOf(m[0]) < 0)
        {
          result.push(m[0]);
        }
        m = regEx.exec(elString);
      }
    }

    return result;
  }
  // --------- /Data Change Logic --------- //

  amx.ValueChangeEvent = ValueChangeEvent;
  function ValueChangeEvent(oldValue, newValue)
  {
    this.oldValue = oldValue;
    this.newValue = newValue;
    this[".type"] = "oracle.adfmf.amx.event.ValueChangeEvent";
  }

  amx.SelectionEvent = SelectionEvent;
  function SelectionEvent(oldRowKey)
  {
    this.oldRowKey = oldRowKey;
    this[".type"] = "oracle.adfmf.amx.event.SelectionEvent";
  }

  amx.ActionEvent = ActionEvent;
  function ActionEvent()
  {
    this[".type"] = "oracle.adfmf.amx.event.ActionEvent";
  }

  /**
   * Process an AMX Event. Change the value if attributeValueName is defined, process the appropriate
   * setPropertyListener and actionListener sub tags and then process the [amxEventType]Listener attribute.
   * @param  {amxNode}  The node to process the event on.
   * @param  {String}   String that represents the event type that triggered the call.
   * @param  {String}   The name of the attribute whose value will be changed.
   * @param  {String}   The nwe value to be applied to the attribute sent in.
   * @param  {amxEvent} Not sure what this is.
   * @return Nothing is retuned from this function.
   */
  amx.processAmxEvent = function($amxNode,amxEventType,attributeValueName, newValue, amxEvent)
  {
    adf.mf.internal.perf.start("Event processing, event: ", amxEventType);
    // Need a wrapper DFD incase we are in design time and we will resolve this either in the else or end of phase 4.
    var dfd = $.Deferred();

    var amxNode = $amxNode.data("amxNode");

    var $validationGroup;
    if (!amx.dtmode)
    {
      // detect if we might need to refresh the validation message area
      if (amxEventType === "valueChange" && amxNode.__attrToValidate !== undefined && attributeValueName === amxNode.__attrToValidate)
      {
        // we need to retrieve this here because calling setElValue will cause this $amxNode to potentially
        // be swapped out with a new one if the control doesn't support the refresh method
        $validationGroup = $amxNode.closest(".amx-validationGroup");
      }
      // If this is a value change event then we need to convert the new value first before we continue to process.
      if (amxNode.converter && amxEventType === "valueChange" && attributeValueName === "value")
      {
        newValue = amxNode.converter.getAsObject(newValue);
      }
      // Phase 1) Set the new value on the attribute. We need to first fetch the current value of the attribute (this 
      //          has to be an EL Expression so we assume it is and let the setELValue figure out where it really needs 
      //          to go. For our part we just get the value for this attribute that get returned and assume it is an 
      //          EL expression for this attribute. This is  different from the rich client. First we 
      //          know what the type (EL Expression or literal) and set the value immediatly if this is a literal. Once 
      //          the "value" has been fetched for this attribute then the assumed EL expression and send off in another 
      //          request for to be updated with the new value.
      // TODO: I do not know what this means for input values as they are most likly never EL bound. Another question 
      //       is what does this mean for disclosure state. In the rich client EL driven disclosure state is only driven
      //       the first time it is evaluated and from then on it is controlled by the component (or the developer) who
      //       set the value in JS.
      
      // Need a new DFD to represent when the set has completed. Since this can go to Java that means this operation
      // may happen asynchronously and we need to wait for it to finish. This will be initialized later but we need this
      // defined here for scoping purposes as this is used below in the $.when.
      var setValueDfd;
      // Make sure we have an attribute value name we are looking to update.
      if (attributeValueName)
      {
        // Retrieve the value currently set on this attribute (could be EL expression or the real value.
        var attributeValueEl = amxNode._xmlNode.getAttribute(attributeValueName);
        // Make sure we have a value for the attribute. 
        if (attributeValueEl)
        {
          //  Create a DFD to be used to deteremine when the set value has finished.
          setValueDfd = $.Deferred()
          // Call into the set value code with the new value and the expression to where this value should be stored.
          // This will return a new DFD that we will atttach a done function to that will then move the new DFD we 
          // created to a reolved state allowing any logic waiting on this to continue to execute.
          amx.setElValue({name:attributeValueEl,value:newValue}).done(function()
          {
            // The set value has returned and now we need to reoslve our DFD to allow us to continue to execute the 
            // other phases of the 
            setValueDfd.resolve();
          });
        }
      }
      // Wiat for Phase 1 to complete. 
      // Note: when setValueDfd is undefined, then, the $.when will resolve immediately (which is what we want). 
      // Other wise we will continue to wait until the set value has completed.
      var childrenDfd;
      $.when(setValueDfd).always(function()
      {
        // Phase 2) Process the setPropertyListeners and actionListeners of this node passed in. Since any component 
        //          can have other types of components we are going to be looking for specific component types. We 
        //          need to create a childrenDfd to make sure we can wait on this before we go to the next phase.
        
        // Get all the child components from the amx node.
        var children = $(amxNode._xmlNode).children();
        //  Need a new DFD variable as proccessing the child components action, set property, show popup, close popu
        //  behaviors may require calls into the Java engine and this will always be done Asynchronously.
        if (children.length > 0)
        {
          // Restore the child variables if this is an iterator or stampe component. This is required in order to 
          // process the specific children in the same and have thier attribute EL or values.
          var restoredVariables = amx.restoreVariables($amxNode);
          // Call serialResolve on the chidlren and pass it the anonyos function to be applied to all the children.
          // The returned DFD will  be used to make sure to wait on it before perfriming the next phase.
          // TODO: Break this our into its own function. This shoudl be a simple case statement that calls the specific
          //       function for the type of component.
          childrenDfd = amx.serialResolve(children,function(subTag,i)
          {
            // Get the attribute type. If none is specified assume an action attribute.
            // TODO: Not sure thie assumption is correct as it assumes a type when none existis. I would expect a type
            //       would alwats be specified but because you are looking at AMX XML nodes we only see what is defined.
            var attrType;
            if (subTag.hasAttribute("type"))
            {
              attrType = subTag.getAttribute("type");
            }
            else
            {
              // use default type
              attrType = "action";
            }
            // Looking for one of four specific AMX tags here.
            //   1) amx:setPropertyListener
            //   2) amx:actionListener
            //   3) amx:showPopupBehavior
            //   4) amx:closePopupBehavior
            // TODO: this code could explode as more behaviors are added. This needs to be broken out into a core
            //       behavior class that is subclassed by the specific behaviors. and then just call the function on the
            //       behavior. Maybe this should be an interface that we look for and then execute the function if it is 
            //       defined.
            if (subTag.tagName === "amx:setPropertyListener" && attrType === amxEventType)
            {
              var setPropertyListener = subTag;
              var propDfd = $.Deferred(); // Need a new deffered as set propert listener has to phases. One to retrieve
                                          // the data "from" and one to set the "to".
              // Get the from expression.
              var fromEl = setPropertyListener.getAttribute("from");
              // Get the value and when it has been retieved the always function will be invoked and this is where 
              // we will set the value we just retrieved.
              amx.getElValue(fromEl).always(function(request,response)
              {
                // Have the new value now set it based on the EL binding for the element.
                amx.setElValue({name:setPropertyListener.getAttribute("to"), value:response[0].value}).always(function()
                {
                  // The set has completed and now we can let everyone know who is interested in this that it has 
                  // completed.
                  propDfd.resolve();
                });
              });
              // Return the the the promise DFD to the calling function (This is within the amx.serialResolve. It needs 
              // this as it will wait for this to finish before going to the next child in the hiearchy.
              return propDfd.promise();
            }
            else if (subTag.tagName === "amx:actionListener")
            {
              // Process the action listener tag if there is an amxEventType passed matches the attribute type.
              if (attrType == amxEventType)
              {
                // Create the arrays of paramaters and and paramater types.
                var params     = [];
                var paramTypes = [];
                if (amxEvent)
                {
                  params.push(amxEvent);
                  paramTypes.push(amxEvent[".type"]);
                }
                // Invoke the action event. This returns a promise DFD.
                return amx.invokeEl(subTag.getAttribute("binding"), params, null, paramTypes);
              }
              else
              {
                // returning null. This allows any calling function to resolve immediatly.
                return null;
              }
            }
            else if (subTag.tagName === "amx:showPopupBehavior")
            {
              // Process the show popup behavior tag if there is an amxEventType passed in matches the attribute type.
              if (attrType === amxEventType)
              {
                 // Invoke the show popup behavior. This returns a promise DFD to the calling function.
                return amx.processShowPopupBehavior(subTag);
              }
              else
              {
                // returning null. This allows any calling function to resolve immediatly.
                return null;
              }
            }
            else if (subTag.tagName === "amx:closePopupBehavior")
            {
              // Process the close popup behavior tag if there is an amxEventType passed in matches the attribute type.
              if (attrType === amxEventType)
              {
                return amx.processClosePopupBehavior($amxNode,subTag);
              }
              else
              {
                // returning null. This allows any calling function to resolve immediatly.
                return null;
              }
            }
            else
            {
              // returning null if there are no match to any tag (this is the catch all). This allows any calling 
              // function to resolve immediatly.
              return null;
            }
          }).always(function()
          {
            // Once we have finished proccessing all the children we need to release all the variables that we 
            // created for the satmped components.
            amx.cleanVariables(restoredVariables);
          });
        }
        else
        {
          // There are no children so we need to create an empty DFD and reolve it. This is because we will be waiting 
          // on this before going to the next phase. 
          childrenDfd = $.Deferred();
          childrenDfd.resolve();
        }
      });

      // Phase 3) process the listeners. First we will wait for the previous phase to finish before we continue on.
      
      // Need to create another DFD for the listeners. This is required to be able to wait for the this phase to 
      // complete.
      var listenerDfd = $.Deferred();
      // Wait for phase 2 to complete.
      $.when(childrenDfd).always(function()
      {
        // Start of Phase 3.
        // Process the istener if there is an amxEvent passed in.
        if (amxEvent)
        {
          var params     = [];
          var paramTypes = [];
          params.push(amxEvent);
          paramTypes.push(amxEvent[".type"]);

          amx.invokeEl(amxNode._xmlNode.getAttribute(amxEventType + "Listener"), params, null, paramTypes).always(function()
          {
            listenerDfd.resolve();
          });
        }
        else
        {
          listenerDfd.resolve();
        }
      });

      // Phase 4) Required Validations process the required validators. First we will wait for the previous phase to 
      //          complete.
      $.when(listenerDfd).always(function()
      {
        // detect if we need to refresh the validation message area
        if ($validationGroup !== undefined && amx.isValueTrue(amxNode.required))
        {
          // this is a required value, so refresh the messages for this group
          // let the validation context know that this group has been modified
          // this method is defined in amx-validation.js
          amx.requiredControlValueChanged($validationGroup);
        }

        // resolve the root dfd
        dfd.resolve();
        
      });

    }
    else
    {
      // if amx.dtmode, just resolve the deferred
      dfd.resolve();
    }
    // Wait for phase 4 to complete before we return.
   return $.when(dfd).always(function(){adf.mf.internal.perf.stop("Done processing of the event processing, event: ", amxEventType);});
  }

  /**
   * adf.mf.el.getValue wrapper using the $.Deferred for asynchronous
   * .done(request,response)  - response is an array of the values in the
   *                            same order as the el values passed in
   * .fail(request,exception) - never invoked
   */
  amx.getElValue = function (singleOrArrayOfEls)
  {
    var dfd = $.Deferred();
    if (!amx.dtmode)
    {
      var arrayOfEls = (adf.mf.internal.util.is_array(singleOrArrayOfEls))? singleOrArrayOfEls : [singleOrArrayOfEls];

      adf.mf.internal.perf.start("amx.getElValue", ("getting " + arrayOfEls.length + " entries"));
      
      // this function will help convert the response from a call to adf.mf.el.getValue
      // to an array of objects that is exactly the length of the request el.
      // This handles full failure, full success, and partial success situations
      var makeResponseArray = function(partialResponses)
      {
        // in a full failure case, the partialResponses will be undefined
        if (partialResponses === undefined)
        {
          partialResponses = [];
        }

        var partialResponseIndex = 0;

        var resultArray = [];

        for (var i = 0; i < arrayOfEls.length; ++i)
        {
          var currentEl = arrayOfEls[i];
          var val;
          // we are guaranteed that any successes will be in the order of the request/ However,
          // we are not guaranteed that the length of the response array is the length of the request
          // array, so we keep state to know which partial response index we are on and we will use
          // that value instead of making the slightly more costly call of getLocalValue
          if (partialResponseIndex < partialResponses.length && partialResponses[partialResponseIndex].name == currentEl)
          {
            val = partialResponses[partialResponseIndex].value;
            // we found a match, so incremement the partial response index for when we loop back around
            ++partialResponseIndex;
          }
          else
          {
            try
            {
              // we don't have any data for what this el is, so call getLocalValue and use what is cached
              val = adf.mf.el.getLocalValue(currentEl);
            }
            catch(innerEx)
            {
              // if this throws an exception, then do nothing, since val will be undefined
              // and we will set it to null in the check below
              ;
            }
          }

          // make sure we never return an "undefined" value - make sure it is just a json null struct
          if (val === undefined)
          {
            val = {".null" : true};
          }
          resultArray.push({name: currentEl, value: val});
        }

        return resultArray;
      };

      var successFunc = function(request,response)
      {
          adf.mf.internal.perf.stop("amx.getElValue", "success");
        var resultArray = makeResponseArray(response);
        dfd.resolve(request,resultArray);
      };

      var failureFunc = function(request,exception)
      {
        var resultArray = makeResponseArray();
        dfd.resolve(request,resultArray);
        adf.mf.internal.perf.stop("amx.getElValue", "failed");
      };

      try
      {
        adf.mf.el.getValue(arrayOfEls, successFunc, function(request ,exception)
        {
          adf.mf.api.amx.logInfoResource(adf.mf.log.level.SEVERE, "getElValue", "MSG_GETVALUE_FAILED", request, exception);
          // call the failure function to handle resolving the Deferred object
          failureFunc(request, exception);
        });
      }
      catch (ex)
      {
        adf.mf.api.amx.logInfoResource(adf.mf.log.level.SEVERE, "getElValue", "MSG_GETVALUE_EXCEPTION", arrayOfEls, ex);
        // call the failure function to handle resolving the Deferred object
        failureFunc(arrayOfEls, ex);
      }
    }
    else
    {
      // if dtmode then, return the result
      var response = [{value:singleOrArrayOfEls}];
      dfd.resolve(singleOrArrayOfEls,response);
    }
    return dfd.promise();
  }

  /**
   * adf.mf.el.setValue wrapper using the $.Deferred for asynchronous
   */
  amx.setElValue = function(nameValues)
  {
    var dfd = $.Deferred();
    if (!amx.dtmode)
    {
      try
      {
        adf.mf.el.setValue(nameValues,function(request,response)
        {
          dfd.resolve(request,response);
        }, function(request,exception)
        {
          adf.mf.api.amx.logInfoResource(adf.mf.log.level.SEVERE, "setElValue", "MSG_SETVALUE_FAILED", nameValues, exception);
          dfd.reject(request,exception);
        });
      }
      catch (ex)
      {
        adf.mf.api.amx.logInfoResource(adf.mf.log.level.SEVERE, "setElValue", "MSG_SETVALUE_EXCEPTION", nameValues.name, ex);
        dfd.resolve();
      }
    }
    else
    {
      // if dtmode, just resolve
      dfd.resolve();
    }

    return dfd.promise();
  }

  amx.loadBundle = function(basename,variable)
  {
    var dfd = $.Deferred();
    if (!forceMockData)
    {
      try
      {
        adf.invokeMethod('oracle.adfmf.framework.api.Model', 'loadBundle',basename,variable,function()
        {
          dfd.resolve();
        },function(req,ex)
        {
          adf.mf.api.amx.logInfoResource(adf.mf.log.level.SEVERE, "loadBundle", "MSG_LOADBUNDLE_FAILED", basename, variable, ex);
          dfd.resolve();
        });
      }
      catch (ex)
      {
        adf.mf.api.amx.logInfoResource(adf.mf.log.level.SEVERE, "loadBundle", "MSG_LOADBUNDLE_EXCEPTION", basename, variable, ex);
      }

      return dfd.promise();
    }
    else
    {
      adf.mf.api.amx.logInfoResource(adf.mf.log.level.WARNING, "loadBundle", "MSG_LOADBUNDLE_SKIPPED", basename, variable);
      return;
    }
  }

  /**
   * adf.mf.el.invoke wrapper using the $.Deferred for asynchronous
   */
  amx.invokeEl = function(expression, params, returnType, types)
  {
    var dfd = $.Deferred();
    try
    {
      if (expression && !amx.dtmode)
      {
        if (!forceMockData)
        {
          //TODO: needs to inject correct params, and handle return type
          adf.mf.el.invoke(expression,params,"void",types,function(req,res)
          {
            dfd.resolve(res);
          },function(req,exp)
          {
            dfd.fail(exp);
          });
        }
        else
        {
          adf.mf.api.amx.logInfoResource(adf.mf.log.level.INFO, "invokeEl", "MSG_AMX_DO_NOT_CALL_ADFMF_EL_INVOKE", expression);
          dfd.resolve();
        }
      }
      else
      {
        dfd.resolve();
      }
    }
    catch (ex)
    {
      adf.mf.api.amx.logInfoResource(adf.mf.log.level.SEVERE, "invokeEl", "MSG_INVOKEEL_EXCEPTION", expression, ex);
      dfd.resolve();
    }
    return dfd.promise();
  }

  /**
   * Take the varName and varValue and store it for this $amxNode.
   */
  amx.storeVariable = function($amxNode,varName,varValue)
  {
    var amxVar = $amxNode.data("amxVar");
    $amxNode.addClass("amxVar");
    if (!amxVar)
    {
      amxVar = {};
      $amxNode.data("amxVar",amxVar);
    }
    amxVar.name = varName;
    amxVar.value = varValue;
  }

  //FIXME: remove this (for backward compability with the dvt team)
  amx.storeVarNameValue = amx.storeVariable;

  /**
   * INTERNAL DO NOT USE.
   * Restore iterator stamp variables for use during listener invocation.
   * @return a non-null (but possibly empty) array of amxVar data objects that were restored
   * @see cleanVariables
   */
  amx.restoreVariables = function($amxNode)
  {
    var amxVars = getAmxVars($amxNode);
    $.each(amxVars,function(idx,amxVar)
    {
      adf.mf.el.addVariable(amxVar.name,amxVar.value);
    });
    return amxVars;
  }

  /**
   * INTERNAL DO NOT USE.
   * Clean up iterator stamp variables after listener invocation.
   * @param varsToClean a non-null (but possibly empty) array of amxVar data objects that need to be removed
   * @see restoreVariables
   */
  amx.cleanVariables = function(varsToClean)
  {
    $.each(varsToClean,function(idx,amxVar)
    {
      adf.mf.el.removeVariable(amxVar.name);
    });
  }

  /**
   * Return true if the value is boolean "false" or string "false"
   * if undefined, return false
   */
  amx.isValueFalse = function(value)
  {
    if (typeof value !== "undefined")
    {
      if (value === false || value === "false" || value === 0 || value === "0")
      {
        return true;
      }
      else
      {
        return false;
      }
    }
    else
    {
      return false;
    }
  }

  /**
   * Return true if the value is boolean true or string "true"
   * If undefine return false
   */
  amx.isValueTrue = function(value)
  {
    if (typeof value !== "undefined")
    {
      if (value === true || value === "true" || value === 1 || value === "1")
      {
        return true;
      }
      else
      {
        return false;
      }
    }
    else
    {
      return false;
    }
  }

  /**
   * Return the list of var object {name:..,value:..} starting from this $node up to the root document.
   */
  function getAmxVars($node)
  {
    var amxVars = [];
    if ($node.is(".amxVar"))
    {
      amxVars.push($node.data("amxVar"));
    }
    $node.parents(".amxVar").each(function()
    {
      var $parent = $(this);
      amxVars.push($parent.data("amxVar"));
    });
    return amxVars;
  }
  // ------ API for Renderers ------ //

  amx.processXmlNode = processXmlNode;

  // return the Deferred that will resolve with the amxNode resolved version of the xmlNode (with the ELs attribute resolved)
  function processXmlNode(xmlNode)
  {
    var amxNodeDfd = $.Deferred();

    var amxNode =
    {
      _xmlNode: xmlNode,
      tagName: xmlNode.tagName
    };

    // first thing to do is check to see if this xmlnode has a rendered value
    // and do NOT proceed if rendered is false
    checkForRendered(xmlNode).done(function()
    {
      // populate the properties
      amx.serialResolve(xmlNode.attributes,function(attr)
      {
        var resolveValDfd = $.Deferred();

        var resolvedVal = attrRawVal = attr.nodeValue;
        if ( acceptAttributeForElProcessing(attr.name,attrRawVal))
        {
          // if this property is a an iterator value
          if (attr.name === "value" && xmlNode.hasAttribute("var"))
          {
            attrRawVal = attrRawVal.substring(0,attrRawVal.length - 1) + ".iterator}";
          }

          amx.getElValue(attrRawVal).done(function(request,response)
          {
    		  adf.mf.internal.perf.trace("processXmlNode.getElValue(...).done", "success");
            var val = response[0].value;
            // handle the case with [".null"]
            if (typeof val !== "undefined" && val != null && val[".null"])
            {
              val = null;
            }
            amxNode[attr.name] = val;
            
            if (adf.mf.log.Performance.isLoggable(adf.mf.log.level.FINEST))
            {
            	adf.mf.internal.perf.trace("processXmlNode.getElValue(...).done", ("amxNode[" + attr.name + "] = " + val));
            }
            
            resolveValDfd.resolve();
            
        	adf.mf.internal.perf.trace("processXmlNode.getElValue(...).done", "resolve is done");
          }).fail(function(req,ex)
          {
      		adf.mf.internal.perf.trace("processXmlNode.getElValue(...).done", "fail");
        	
        	adf.mf.api.amx.logAndThrowErrorResource ("serialResolve", "ERROR_RESOLVING_ATTRIBUTE", attrRawVal);
          });
        }
        else
        {
          amxNode[attr.name] = resolvedVal;
          resolveValDfd.resolve();
        }

        return resolveValDfd.promise();
      }).done(function()
      {
        var variable = amxNode["var"];
        if (variable)
        {
          amxNode.variable = variable;
        }
        // add the facets
        var facets = amxNode.facets = {};
        $(xmlNode).children("amx\\:facet").each(function()
        {
          var xmlFacetNode = this;
          var facetNode =
          {
            _xmlNode : xmlFacetNode,
            tagName : "amx:facet"
          };
          var facetName = xmlFacetNode.getAttribute("name");
          facets[facetName] = facetNode;
        });
        // add the converter if not in DT mode
        if (!amx.dtmode)
        {
          var $convertNode = $(xmlNode).children("amx\\:convertNumber").first();
          if ($convertNode.length > 0)
          {
            if (amx.createNumberConverter)
            {
              amxNode.converter = amx.createNumberConverter($convertNode);
            }
          }
          else
          {
            $convertNode = $(xmlNode).children("amx\\:convertDateTime").first();
            if ($convertNode.length > 0)
            {
              if (amx.createDateTimeConverter)
              {
                amxNode.converter = amx.createDateTimeConverter($convertNode);
              }
            }
          }
        }
        if (amxNode.converter)
        {
          amxNode.value = amxNode.converter.getAsString(amxNode.value);
        }

        // for now, we just resolve it in the flow, but later, the amx.evalEl will be deferred
        amxNodeDfd.resolve(amxNode);
      });
    }).fail(function()
    {
        // for now, we just resolve it
        amxNode["rendered"] = false;
        amxNodeDfd.resolve(amxNode);
    });

    return amxNodeDfd;
  }

  // return true if this attribute/value needs to be EL resolved
  acceptAttributeForElProcessing.noProcessAttributes =
  {
    valueChangeListener:true,
    from:true,
    to:true,
    selectionChangeListener:true,
    actionListener:true,
    binding:true
  };

  function acceptAttributeForElProcessing(attrName,attrValue)
  {
    var accept = (!acceptAttributeForElProcessing.noProcessAttributes[attrName] && attrValue.indexOf("#{") > -1);
    accept = accept && (attrName.indexOf("Listener") === -1);
    return accept;
  }

  function acceptXMLNodeForSubNodeElPreProcessing(xmlNode)
  {
    // for now, just exclude the xmlNode with var (which is usually the iterators)
    return (xmlNode.getAttribute("var") === null);
  }

  function checkForRendered(xmlNode)
  {
    // first thing to do is check to see if this xmlnode has a rendered value
    // and do NOT proceed if rendered is false
    var renderedDfd = $.Deferred();
    var renderedAttr = xmlNode.getAttribute("rendered");
    if (renderedAttr !== undefined && renderedAttr != null)
    {
      amx.getElValue(renderedAttr).done(function(request, response)
      {
        if (amx.isValueFalse(response[0].value))
        {
          renderedDfd.reject();
        }
        else
        {
          renderedDfd.resolve();
        }
      });
    }
    else
    {
      renderedDfd.resolve();
    }

    return renderedDfd.promise();
  }

  // this is for prefetching the data in the adf.mf.el cache
  // Called on the amx root XML Node
  function preProcessAllXmlNodes(rootXmlNode)
  {
      var dfd = $.Deferred();
      var allEls = [];
      var dfdArray = [];
      var autoGenCounter = 0;


      function preProcessNode(xmlNode, performElPreprocessing)
      {
        var innerDfd = $.Deferred();
        dfdArray.push(innerDfd);
        // Inject an auto-generated ID if the xmlNode doesn't already have one.
        // Try to do this in a reproducible manner where the same ID will be added to the same node
        // if the XML DOM structure is exactly the same across multiple visits (e.g. start at zero and count up)
        if (xmlNode.getAttribute("id") == null)
          xmlNode.setAttribute("id", "_auto" + (autoGenCounter++));

        loadResourcesForNode(xmlNode, dfdArray);

        // first thing to do is check to see if this xmlnode has a rendered value
        // and do NOT proceed if rendered is false
        checkForRendered(xmlNode).done(function()
        {
          if (performElPreprocessing === true)
          {
            // add the attribute els as appropriate
            if (xmlNode.attributes)
            {
              $.each(xmlNode.attributes,function(idx,attr)
              {
                var nodeValue = attr.nodeValue;
                // if this property is a an iterator value
                //if (attr.name === "value" && xmlNode.hasAttribute("var"))
                //{
                //  nodeValue = nodeValue.substring(0,nodeValue.length - 1) + ".iterator}";
                //}

                if(acceptAttributeForElProcessing(attr.name,nodeValue))
                {
                  allEls.push(nodeValue);
                }
              });
            }
          }

          // process the subNode
          $(xmlNode).children().each(function()
          {
            var subXmlNode = this;
            preProcessNode(subXmlNode, performElPreprocessing &&
              acceptXMLNodeForSubNodeElPreProcessing(xmlNode));
          });

          innerDfd.resolve();
        }).fail(function()
        {
          // log the fact that this node is NOT rendered
          innerDfd.resolve();
        });
      }
      preProcessNode(rootXmlNode, true);

      $.when.apply(null, dfdArray).pipe(function()
      {
        amx.getElValue(allEls).done(function(request, response)
        {
          dfd.resolve(request, response);
        });
      });

      return dfd.promise();
  }

  function processCssLinks(amxNode)
  {
    cssNodes = [];

    // We build the list of cssNodes
    $.each(amxNode.nodes,function(idx,node)
    {
      if (node.tagName === "amx:CSSInclude")
      {
        cssNodes.push(node);
      }
    });

    // We add them to the <head /> document
    // TODO: needs to check if the css was already added
    // TODO: probably needs to try to return a dfd that will resolve when the css is loaded.
    $.each(cssNodes,function(idx,cssNode)
    {
      amx.includeCss(cssNode.file);
    });
  }

  amx.getNodeTypeHandler = getNodeTypeHandler;
  function getNodeTypeHandler(amxNode)
  {
    var tagName = amxNode.tagName;
    var nodeTypeHandler = null;

    if (tagName)
    {
      nodeTypeHandler = rendererDic[tagName];
      // if it is a function, then, it is actually the "create" of the NodeTypeHandler
      if (nodeTypeHandler && $.isFunction(nodeTypeHandler))
      {
        nodeTypeHandler =
        {
          create: nodeTypeHandler
        };
      }
    }

    return nodeTypeHandler;
  }

})();
// ------ /amx UI ------ //

// --------- amx UA --------- //
(function(){

  var _hasTouch = null;

  amx.hasTouch = function()
  {
    if (_hasTouch === null)
    {
      _hasTouch = isEventSupported("touchstart");
    }
    return _hasTouch;
  }

  var isEventSupported = (function()
  {
    var TAGNAMES =
    {
      'select' : 'input',
      'change' : 'input',
      'submit' : 'form',
      'reset' : 'form',
      'error' : 'img',
      'load' : 'img',
      'abort' : 'img'
    };

    function isEventSupported(eventName)
    {
      var el = document.createElement(TAGNAMES[eventName] || 'div');
      eventName = 'on' + eventName;
      var isSupported = (eventName in el);
      if (!isSupported)
      {
        el.setAttribute(eventName, 'return;');
        isSupported = typeof el[eventName] == 'function';
      }
      el = null;
      return isSupported;
    }
    return isEventSupported;
  })();

})();
// --------- /amx UA --------- //

// --------- Utilities --------- //
(function() {

  // Private array of chars to use
  var CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');

  amx.uuid = function(len)
  {
    len = len || 10;
    var chars = CHARS, uuid = [];
    var radix = chars.length;

    for (var i = 0; i < len; i++)
      uuid[i] = chars[0 | Math.random()*radix];

    return uuid.join('');
  }

  amx.arrayRemove = function(a, from, to)
  {
    var rest = a.slice((to || from) + 1 || a.length);
    a.length = from < 0 ? a.length + from : from;
    return a.push.apply(a, rest);
  }

  amx.registerInputValue = function(amxNode, attrName)
  {
    // create the field that will hold the attribute name to be validated
    if (amxNode.__attrToValidate === undefined)
    {
      amxNode.__attrToValidate = attrName;
    }
  }

  /**
   * Rendrer would call this function to change the style of showRequired attribute
   * @param {Object} amxNode This is the amxNode object
   * @param {Object} field This object is returned from createField method has $label and $node properties
   * @see See also the definition of amx.createField method inside amx-commonTags.js
   */
  adf.mf.api.amx.applyRequiredMarker = function (amxNode, field)
  {
    var $divNode = field.$node;
    if (amx.isValueTrue(amxNode.showRequired) || amx.isValueTrue(amxNode.required))
    {
       $divNode.addClass("required");
    }
  }

  // safely return the value, handling json null objects,
  // undefined objects, and null objects by returning null
  amx.getObjectValue = function(value)
  {
    if (value == null)
    {
      return null;
    }
    if (typeof value === "undefined")
    {
      return null;
    }

    if (typeof value[".null"] !== "undefined")
    {
      adf.mf.api.amx.logInfoResource(adf.mf.log.level.INFO, "getObjectValue", "MSG_UNHANDLED_NULL");
      if (value[".null"] == true)
      {
        return null;
      }
    }
    return value;
  }

  // safely return the value, handling json null objects
  // by returning the empty string
  amx.getTextValue = function(value)
  {
    value = amx.getObjectValue(value);
    if (value == null)
    {
      return "";
    }
    return value;
  }

  // Gets the amx_dtfolderpath if it is on the URL
  amx.getDtFolderPath = function()
  {
    var amx_dtfolderpath = null;
    var queryString = document.location.search;
    if ((queryString != null) && (queryString.length > 0))
    {
      amx_dtfolderpath = adfc.internal.UrlUtil.getUrlParamValue(queryString, "amx_dtfolderpath");
      if (amx_dtfolderpath != null)
      {
        amx_dtfolderpath = unescape(amx_dtfolderpath);
      }
    }
    return amx_dtfolderpath;
  }

  // Builds a string that is the relative path to
  // the folder containing the amx page we are currently
  // viewing.
  amx.currentPageContainingFolder = function()
  {
    try
    {
      // Check for DT folder path
      var amx_dtfolderpath = amx.getDtFolderPath();
      if(amx_dtfolderpath !== null)
      {
        return amx_dtfolderpath;
      }
      // Get current amx filename
      var amxPage = amx.peekViewHistory().amxPage;
      // Break up the filename so we can get the length
      // of just the filename.
      var parts = amxPage.split("/");
      // Add the feature root prefix to the filename
      var amxPageFullPath = adfc.Util.addFeatureRootPrefix(amxPage);
    }
    catch (ex)
    {
      adf.mf.api.amx.logInfoResource(adf.mf.log.level.SEVERE, "currentPageContainingFolder", "MSG_CURRENT_PAGE_FOLDER_FAILED", ex);
      return "";
    }
    // Strip off the filename
    return amxPageFullPath.substr(0,amxPageFullPath.length-parts[parts.length-1].length);
  }

  // Determines if the tartget string has a protocol
  amx.hasProtocol = function(url)
  {
    return /^(:?\w+:)/.test(url);
  }

  // Builds the relative path based to the specified
  // resource assuming it is relative to the current
  // amx page.  If there is a protocol on the resource
  // then it is assumed to be an absolute path and
  // left unmodified
  amx.buildRelativePath = function(url)
  {
    if(amx.hasProtocol(url))
    {
      return url;
    }


    url = url.replace("\\", "/");
    if(url.charAt(0) == "/")
    {
        // Check for DT folder path
        var amx_dtfolderpath = amx.getDtFolderPath();
        if (amx_dtfolderpath !== null)
        {
            var publicHtmlString = 'public_html/';
            var publicHtmlIndex    = amx_dtfolderpath.indexOf(publicHtmlString);

            return (amx_dtfolderpath.substring(0, publicHtmlIndex + publicHtmlString.length)) + url;
        }
        else
        {
            return adfc.Util.addFeatureRootPrefix(url);
        }
    }


    return amx.currentPageContainingFolder() + url;
  }
  
  /**
   * Adds padding to a number string.  Does nothing if number is longer than paddingLength.
   * @param {number} number to be padded
   * @param {number} paddingLength specifies length to which to pad
   * @return {String} padded number at least paddingLength long
   */
  adf.mf.internal.amx.addPadding = function(number, paddingLength)
  {
    var padded = "" + number;
    var i = padded.length;
    for (; i < paddingLength; ++i)
    {
      padded = "0" + padded;
    }
    return padded;
  }
  
  /**
   * Extracts time portion from date object and returns it as "HH:mm:ss"
   * @param {Date} dateObject
   * @return {String} returns time as "HH:mm:ss"
   */
  adf.mf.internal.amx.extractTimeFromDateObject = function(dateObject)
  {
    var time = adf.mf.internal.amx.addPadding(dateObject.getHours(), 2) + ":" + adf.mf.internal.amx.addPadding(dateObject.getMinutes(), 2) + ":" + adf.mf.internal.amx.addPadding(dateObject.getSeconds(), 2) + "." + adf.mf.internal.amx.addPadding(dateObject.getMilliseconds(), 3);
    return time;
  }

  /**
   * Extracts date portion from date object and returns it as "yyyy-MM-dd"
   * @param {Date} dateObject
   * @return {String} returns date as "yyyy-MM-dd"
   */  
  adf.mf.internal.amx.extractDateFromDateObject = function(dateObject)
  {
    var time = adf.mf.internal.amx.addPadding(dateObject.getFullYear(), 4) + "-" + adf.mf.internal.amx.addPadding(dateObject.getMonth() + 1, 2) + "-" + adf.mf.internal.amx.addPadding(dateObject.getDate(), 2);
    return time;
  }
  
  /**
   * Updates time portion of date object with given time.
   * @param {Date} dateObject is the Date to be updated
   * @param {String} time is a string with this format: "hh:mm"
   */
  adf.mf.internal.amx.updateTime = function(dateObject, time)
  {
    if (time != null && typeof time !== "undefined" && time.length > 4)
    {
      var h = time.substring(0,2);
      var m = time.substring(3,5);
      dateObject.setHours(h);
      dateObject.setMinutes(m);
    }
  }
  
  /**
   * Updates date portion of date object with given date.
   * @param {Date} dateObject is the Date to be updated
   * @param {String} date is a string with this format: "yyyy-MM-dd".  The year must be at least 4 characters but can be longer
   */
  adf.mf.internal.amx.updateDate = function(dateObject, date)
  {
    if (date != null && typeof date !== "undefined" && date.length > 9)
    {
      var i = date.indexOf("-");
      if (i > 3)
      {
        var year = date.substring(0, i);
        var j = date.indexOf("-", i+1);
        if (j > -1)
        {
          var month = date.substring(i+1, j) - 1;
          var day = date.substring(j+1, date.length);
          dateObject.setYear(year);
          dateObject.setMonth(month);
          dateObject.setDate(day);
        }
      }
    }
  }
  
}) ();
//--------- /Utilities --------- //
/* Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved. */
/* ------------------------------------------------------ */
/* --------------------- amx-event.js ------------------- */
/* ------------------------------------------------------ */

(function(){
// TODO finish the migration from "amx.*" to "adf.mf.api.amx.*" and "adf.mf.internal.amx.*"

  var tapPendingIds = {};

  function cancelPendingTap(){
    tapPendingIds = {};
  }
  // --------- Tap Event --------- //
  var mouseTapEvents = {
        start: "mousedown",
        end: "mouseup"
    }

    var touchTapEvents = {
        start: "touchstart",
        end: "touchend"
    }



    $.fn.tap = function(arg0, arg1){

      var tapEvents = (amx.hasTouch()) ? touchTapEvents : mouseTapEvents;

        var callback = arg1 || arg0;
        var delegate = (arg1) ? arg0 : null;

        return this.each(function(){

            var $this = $(this);

            var tapId = null;

            // If we do not have touch, then, just click event.
            // Note: this is where we will do the hack for the amx.tap_to_click flag (for selenium testing)
            if (!amx.testmode){
              // tap start
              $this.on(tapEvents.start,delegate,function(event){
                var $tapped = $(this);
                tapId = amx.uuid();
                tapPendingIds[tapId] = true;
              });

              // tap end
              $this.on(tapEvents.end,delegate,function(event){
                if (tapPendingIds[tapId]){
                  callback.call(this,event);
                  delete tapPendingIds[tapId];
                }

              });
            }else{
              $this.on("click",delegate,function(event){
                callback.call(this,event);
              });
            }
        });
    };
  // --------- /Tap Event --------- //


  // --------- Tap Hold --------- //
  var tapHoldPendingIds = {};

  function cancelPendingTapHold(){
    tapHoldPendingIds = {};
  }

  var holdThreshold = 800;
    $.fn.tapHold = function(arg0, arg1){

      var tapEvents = (amx.hasTouch()) ? touchTapEvents : mouseTapEvents;

        var callback = arg1 || arg0;
        var delegate = (arg1) ? arg0 : null;

        return this.each(function(){

            var $this = $(this);

            var tapId = null;

          // tap start
          $this.on(tapEvents.start,delegate,function(event){
            var tapped = this;
            var $tapped = $(this);
            tapId = amx.uuid();
            tapHoldPendingIds[tapId] = new Date().getTime();

            setTimeout(function(){
              // Note: here we double check if the time is greater than the threshold. This is useful since sometime timeout
              //       is not really reliable.
              if (tapHoldPendingIds[tapId] > 0){
                var timeOffset = new Date().getTime() - tapHoldPendingIds[tapId];
                if (timeOffset >= holdThreshold){
                  var result = callback.call(tapped,event);

                  // if the handler consume the tapHold, remove it from the tapPendingIds so that it does not count like a tap
                  if (result === "consumeTapHold"){
                    cancelPendingTap();
                    cancelPendingTapHold();
                    cancelPendingDrag();
                  }
                }
                delete tapHoldPendingIds[tapId];
              }

            },holdThreshold)
          });

          // tapHold end (remove the tapPendingIs)
          $this.on(tapEvents.end,delegate,function(event){
            if (tapHoldPendingIds[tapId]){
              delete tapHoldPendingIds[tapId];
            }
          });
        });
    };
  // --------- /Tap Hold --------- //

  // --------- Drag Event --------- //
  var dragPendingIds = {};

  function cancelPendingDrag(){
    dragPendingIds = {};
  }

  var DRAGSTART="dragstart",DRAGDRAG="drag",DRAGEND="dragend";

    var defaultOptions = {
          threshold: 5
    }
    /**
     *
     * Options optional method implementation:
     *
     *
     */
    var mouseDragEvents = {
        start: "mousedown",
        drag: "mousemove",
        end: "mouseup"
    }

    var touchDragEvents = {
        start: "touchstart",
        drag: "touchmove",
        end: "touchend"
    }
  
  $.fn.drag = function(delegate, opts)
  {
    var hasTouch = amx.hasTouch();
    var options = opts || delegate;
    var delegate = (opts) ? delegate : null;

    options = $.extend({}, defaultOptions, options);

    var dragEvents = (hasTouch) ? touchDragEvents : mouseDragEvents;

    return this.each(
      function()
      {
        var $this = $(this);
        if (delegate == null) 
        {
          (options.start) ? $this.bind(DRAGSTART,options.start) : null;
          (options.drag) ? $this.bind(DRAGDRAG,options.drag) : null;
          (options.end) ? $this.bind(DRAGEND,options.end) : null;

          $this.bind(dragEvents.start, 
            function(e)
            {
              handleDragEvent.call(this,e,options);
            });
        }
        else
        {
          (options.start) ? $this.delegate(delegate,DRAGSTART,options.start) : null;
          (options.drag) ? $this.delegate(delegate,DRAGDRAG,options.drag) : null;
          (options.end) ? $this.delegate(delegate,DRAGEND,options.end) : null;

          $this.delegate(delegate, dragEvents.start,
            function(e)
            {
              handleDragEvent.call(this, e, options);
            });
        }
      });

    // Handler the event
    // "this" of this function will be the element
    function handleDragEvent(e, options)
    {
      var $elem = $(this);

      var $document = $(document);
      var id = "_" + amx.uuid();

      dragPendingIds[id] = true;

      var dragStarted = false;
      var startEvent = e;
      var startPagePos = eventPagePosition(startEvent);

      // create the $helper if it is a draggable event.
      var $helper;

      // so far, we prevent the default, otherwise, we see some text select which can be of a distracting
      // since we create "meta events" we consume this one
      // e.preventDefault();
      e.stopPropagation();

      // drag
      $document.bind(dragEvents.drag + "." + id, 
        function(e)
        {
          // if the drag has not started, check if we need to start it
          if (!dragStarted && dragPendingIds[id])
          {
            var currentPagePos = eventPagePosition(e);
            var offsetX = (startPagePos.pageX - currentPagePos.pageX);
            var offsetY = (startPagePos.pageY - currentPagePos.pageY);

            // if the diff > threshold, then, we start the drag
            if (Math.abs(offsetX) > options.threshold || Math.abs(offsetY) > options.threshold)
            {
              dragStarted = true;

              // we cancel any pending tap event
              cancelPendingTap();
              cancelPendingTapHold();

              //create the bDragCtx
              $elem.data("dragCtx",{});

              var dragStartExtra = buildDragExtra(startEvent, $elem, DRAGSTART, startPagePos);
              $elem.trigger(DRAGSTART,[dragStartExtra]);
            }
          }

          if (dragStarted && dragPendingIds[id])
          {
            // making sure they they are canceled
            cancelPendingTap();
            cancelPendingTapHold();

            var dragExtra = buildDragExtra(e, $elem, DRAGDRAG);

            var overElem;
            if (options.draggable === true)
            {
              overElem = findOverElement($helper, dragExtra);
              dragExtra.overElement = overElem;
            }

            $elem.trigger(DRAGDRAG,[dragExtra]);

            // since we create "meta events" we consume this one
            e.preventDefault();
            e.stopPropagation();
          }
        });

      // drag end
      $document.bind(dragEvents.end + "." + id,
        function(e)
        {
          if (dragStarted && dragPendingIds[id])
          {
            var extra = buildDragExtra(e, $elem, DRAGEND);
            $elem.trigger(DRAGEND,[extra]);

            // delete the dragContext
            $elem.data("dragCtx",null);

            // since we create "meta events" we consume this one
            e.preventDefault();
            e.stopPropagation();
          }

          // unbind the document event
          $(document).unbind(dragEvents.drag + "." + id);
          $(document).unbind(dragEvents.end + "." + id);
          delete dragPendingIds[id];
        });
    } // /function handleDragEvent(..)
  }; // /$.fn.drag(..)

  /**
   * Build the extra event info for the drag event.
   * @param {Object} pagePos optional argument with pageX and pageY properties
   */
  function buildDragExtra(event, $elem, dragType, pagePos)
  {
    fixTouchEvent(event);
    var hasTouch = amx.hasTouch();
    var extra = {
      "eventSource": event,
      "pageX": event.pageX,
      "pageY": event.pageY
    };

    var oe = event.originalEvent;
    if (hasTouch)
    {
      extra.touches = oe.touches;
    }

    var dragCtx = $elem.data("dragCtx");
    if (dragCtx)
    {
      if (dragType === DRAGSTART)
      {
        dragCtx.startPageX = extra.startPageX = extra.pageX;
        dragCtx.startPageY = extra.startPageY = extra.pageY;

        dragCtx.lastPageX = dragCtx.startPageX = extra.startPageX;
        dragCtx.lastPageY = dragCtx.startPageY = extra.startPageY;
      }
      else if (dragType === DRAGEND)
      {
        // because, on iOs, the touchEnd event does not have the .touches[0].pageX
        extra.pageX = dragCtx.lastPageX;
        extra.pageY = dragCtx.lastPageY;
      }

      if (pagePos != null && dragCtx.originalAngle == null)
      {
        // Calculate, using the start page event location, the angle that the user moved their
        // finger. Allows callers to determine the directionality that the user intends to scroll.
        var diffX = dragCtx.startPageX - pagePos.pageX;
        var diffY = pagePos.pageY - dragCtx.startPageY; // Y direction is reversed

        // Determine the angle
        // angle = arctan(opposite/adjacent) (converted from radians to degrees)
        // Note that this computation uses 0 degrees as east, 90 is north.
        // Angles to the south and west are negative (-90 is south)
        dragCtx.originalAngle = Math.atan2(diffY, diffX) * 180 / Math.PI; 
      }

      extra.originalAngle = dragCtx.originalAngle;
      extra.startPageX = dragCtx.startPageX;
      extra.startPageY = dragCtx.startPageY;
      extra.deltaPageX = extra.pageX - dragCtx.lastPageX;
      extra.deltaPageY = extra.pageY - dragCtx.lastPageY;

      dragCtx.lastPageX = extra.pageX;
      dragCtx.lastPageY = extra.pageY;
    }
    else
    {
      adf.mf.api.amx.logInfoResource(adf.mf.log.level.WARNING, "buildDragExtra", "MSG_DRAG_CTX_NULL");
    }
    
    return extra;
  }

  function eventPagePosition(e)
  {
    var pageX, pageY;
    if (e.originalEvent && e.originalEvent.touches)
    {
      pageX = e.originalEvent.touches[0].pageX;
      pageY = e.originalEvent.touches[0].pageY;
    }
    else
    {
      pageX = e.pageX;
      pageY = e.pageY;
    }

    return {
      "pageX": pageX,
      "pageY": pageY
    };
  }

  function fixTouchEvent(e)
  {
    if (amx.hasTouch())
    {
      var oe = e.originalEvent;

      if (oe.touches.length > 0)
      {
        e.pageX = oe.touches[0].pageX;
        e.pageY = oe.touches[0].pageY;
      }
    }
    return e;
  }
  // --------- /Drag Event --------- //

    // -------- Swipe Event --------- //

    var swipeThreshold = 5;
    /**
     * jQuery extension for swipe gesture.
     * Can be call with (delegate,handler) or (handler)
     *
     * @param delegate {string} a jQuery delegate/selector string
     * @param handler {function} will be call with (event,swipeExtra).
     *
     * swipeExtra has the following properties:
     *     swipeExtra.swipeType {string}: swipeLeft, swipeRight, swipeUp, swipeDown
     *
     *
     * if the Handler return "false" it means it has consumed the event and it should not be bubbling (should prevent drag or such)
     */
    $.fn.swipe = function(arg0, arg1){

      var tapEvents = (amx.hasTouch()) ? touchTapEvents : mouseTapEvents;

        var handler = arg1 || arg0;
        var delegate = (arg1) ? arg0 : null;

        return this.each(function(){

            var $this = $(this);

      $this.drag(delegate,{
        start: function(event,dragExtra){

        },

        drag: function(event,dragExtra){
          var $node = $(this);

          var swipeExtra = buildSwipeExtra($node,event,dragExtra);
          if (swipeExtra){
            var result = handler.call(this,event,swipeExtra);
            if (result === "consumeSwipe"){
                cancelPendingDrag();
                $node.data("swipeDone",null);
              }
          }
        },

        end: function(event,dragExtra){
          var $node = $(this);
          $node.data("swipeDone",null);
        },

        threshold: 5

      });

        });



    };

    /**
     * Determine if it is a swipe event, and if yes, build the swipeExtra
     */
    function buildSwipeExtra($node,event,dragExtra){
      var swipeExtra = null;

      var swipeDone = $node.data("swipeDone");

      if (swipeDone !== true && dragExtra){
        var offsetX = (dragExtra.pageX - dragExtra.startPageX);
        var offsetY = (dragExtra.pageY - dragExtra.startPageY);
        if (Math.abs(offsetX) > swipeThreshold){
          swipeExtra = {};
          swipeExtra.swipeType = (offsetX > -1)?"swipeRight":"swipeLeft";
          $node.data("swipeDone",true);
        }else if (Math.abs(offsetY) > swipeThreshold){
          swipeExtra = {};
          swipeExtra.swipeType = (offsetY > -1)?"swipeDown":"swipeUp";
          $node.data("swipeDone",true);
        }

      }

      return swipeExtra;
    }
    // -------- /Swipe Event --------- //

})();


// --------- /events --------- //


// --------- Event Enabler --------- //
(function(){

  /**
   * Enable the swipe event and wire the listeners for a given $node or the item delegate of this $node.
   * Note: delegate is optional, if not defined, $node will be directly bound.
   */
  amx.enableSwipe = function($node,delegate){
    // handle the swipe
    $node.swipe(delegate,function(event,swipeExtra){
      var $item = $(this);
      var $xmlNode = $($item.data("amxNode")._xmlNode);
      var swipeType = swipeExtra.swipeType;

      // check that we have at least one action with this type
      if ($xmlNode.children().filter("[type='" + swipeType + "']").length > 0){
        var event = new amx.ActionEvent();
        amx.processAmxEvent($item, swipeType, undefined, undefined, event);
        return "consumeSwipe";
      }
    });
  }


  amx.enableTapHold = function($node,delegate){
    $node.tapHold(delegate,function(event){
      var $item = $(this);
      var $xmlNode = $($item.data("amxNode")._xmlNode);

      // check that we have at least one action with this type
      if ($xmlNode.children().filter("[type='tapHold']").length > 0){
        var event = new amx.ActionEvent();
        amx.processAmxEvent($item, "tapHold", undefined, undefined, event);
        return "consumeTapHold";
      }
    });
  }


})();
/* Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved. */
/* ------------------------------------------------------ */
/* ------------------- amx-validation.js ---------------------- */
/* ------------------------------------------------------ */

var amx = amx || {};



// ------ amx validations ------ //
(function(){
// TODO finish the migration from "amx.*" to "adf.mf.api.amx.*" and "adf.mf.internal.amx.*"
  var amxRenderers =
  {

    validationGroup : function(amxNode)
    {
      var $node = $("<div></div>");

      return amx.renderSubNodes(amxNode, $node).pipe(function()
      {
        return $node;
      });
    },

    messages :
    {
      create : function(amxNode)
      {
        var $node = $("<div></div>");
        return $node;
      },
      init : function($node, amxNode)
      {

        messagesUpdate($node, amxNode);
      },

      refresh: function($node,newAmxNode,propertiesChanged){
        // re-render the whole messages node.
        var oldAmxNode = $node.data("amxNode");
        messagesUpdate($node, oldAmxNode);
        return true;
      }

    }
  }; // /var amxRenderers

  function messagesUpdate($node, amxNode, validationData)
  {
    // first thing we do is remove all of the previous elements
    $node.empty();

    if (amx.isValueTrue(amxNode.inline) == false)
    {
      return;
    }

    var message = amxNode.message;
    if (message === undefined || message === null)
    {
      message = "";
    }

    amx.getElValue(message).done(function(request, response)
    {
      var message = response[0].value;
      // get the validationGroup that we are a child of
      var $groupNode = $node.closest(".amx-validationGroup");

      var dataDfd = $.Deferred();
      if (validationData === undefined)
      {
        amx.buildValidationData([$groupNode]).always(function(validationData)
        {
          dataDfd.resolve(validationData);
        });
      }
      else
      {
        dataDfd.resolve(validationData);
      }


      dataDfd.always(function(validationData)
      {
        if (validationData === undefined)
        {
          // nothing to add
          return;
        }
        var addLabel = function($node, amxNode, message, needsMessageLabel)
        {
          if (needsMessageLabel != true)
          {
            return false;
          }

          if (amxNode.message !== undefined && amxNode.message !== null)
          {
            // now add the message label
            var $headerNode = $("<div class='amx-messages-header'></div>");
            var $labelNode = $("<div class='amx-messages-header-text'>" + message + "</div>");
            $headerNode.append($labelNode);
            $node.append($headerNode);
          }

          return false;
        }

        var addText = function($node, name, text)
        {
          var $itemNode = $("<div class='amx-messages-item'></div>");
          $itemNode.append("<div class='amx-messages-text amx-messages-text-" + name + "'>" + text + "</div>");
          $itemNode.append("<div class='amx-messages-icon amx-messages-icon-" + name + "'></div>");
          $node.append($itemNode);
        }

        var addFooter = function($node, needsMessageLabel)
        {
          if (needsMessageLabel == true)
          {
            return;
          }
          var $textNode = $("<div class='amx-messages-footer'></div>");
          $node.append($textNode);
        }

        var needsMessageLabel = true;
        var groupId = $groupNode.data("amxNode").id;

        var groupValidationData = validationData[groupId];
        if (groupValidationData !== undefined)
        {
          // check for invalid list entries
          var groupInvalid = groupValidationData.invalid;
          for (item in groupInvalid)
          {
            var nvp = groupInvalid[item];

            needsMessageLabel = addLabel($node, amxNode, message, needsMessageLabel);

            var arrayList = groupInvalid[item];
            for(var index in arrayList)
            {
              var nvp= arrayList[index];
              addText($node, nvp.name, nvp.value);
            }
          }

          // check for required failures
          var groupRequired = groupValidationData.required;
          for (item in groupRequired)
          {
            var text = groupRequired[item];
            needsMessageLabel = addLabel($node, amxNode, message, needsMessageLabel);
            addText($node, "error", text);
          }

          addFooter($node, needsMessageLabel);
        }
      });
    });
  }

  // data change handler for when the validationList changes
  function validationsDataChangeHandler(el){
    updateValidationMessages();
  }

  function updateValidationMessages(groupsToCheck)
  {
    if (amx.isValidating())
    {
      return;
    }

    var $view = amx.$bodyPage.find(".amx-view:first");
    // get all of the groups and all of the messages
    var group$nodeArray = $view.find(".amx-validationGroup");
    var messages$nodeArray = $view.find(".amx-messages");

    var $popupArray = amx.$bodyPage.children(".amx-popup");
    for (var i = 0; i < $popupArray.length; ++i)
    {
      // a popup is being shown, so add all of the groups from this as well
      var $popup = $($popupArray[i]);
      var $popupGroupArray = $popup.find(".amx-validationGroup");
      var $messagesArray = $popupGroupArray.find(".amx-messages");
      group$nodeArray = $.merge($popupGroupArray, group$nodeArray);
      messages$nodeArray = $.merge($messagesArray, messages$nodeArray);
    }

    if (group$nodeArray.length == 0)
    {
      // do nothing
      return;
    }
    amx.buildValidationData(group$nodeArray).always(function(validationData)
    {
      // now iterate through the messages and refresh them
      $.each(messages$nodeArray,function(idx,msg){
        var $msgNode = $(msg);
        var amxNode = $msgNode.data("amxNode");
        messagesUpdate($msgNode, amxNode, validationData);
      });

      // now show the message box
      if (groupsToCheck !== undefined && groupsToCheck != null && groupsToCheck.length > 0)
      {
        var groupsToShow = [];
        for (var i = 0; i < groupsToCheck.length; ++i)
        {
          var groupId = groupsToCheck[i];

          groupValidationData = validationData[groupId];
          if (groupValidationData === undefined)
          {
            // no validation data present
            continue;
          }

          if (groupValidationData.messages !== undefined && groupValidationData.messages.inline == true)
          {
            // already has an inline messages tag
            continue;
          }

          groupsToShow.push(groupId);
        }

        if (groupsToShow.length > 0)
        {
          // check to see which elements in this group actually need a message box
          messagesPopupCreate(groupsToShow, validationData);
        }
      }
    });
  }

  amx.validationsUnsetListEl = "#{validationScope.unsetList}";
  amx.validationsInvalidListExpr = "validationScope.invalidList";
  amx.validationsInvalidListEl = "#{" + amx.validationsInvalidListExpr + "}";
  amx.validationsValidListEl = "#{validationScope.validList}";

  // this keeps track of the groups that have been validated
  // so that we know if we should be showing the required failures
  // or not
  var validatedGroups = {};

  function getCurrentPageGroup(id)
  {
    var thisPage = amx.peekViewHistory().viewId;
    if (validatedGroups[thisPage] === undefined)
    {
      validatedGroups[thisPage] = {};
    }

    return validatedGroups[thisPage];
  }

  function setGroupValidated(id)
  {
    var pageGroups = getCurrentPageGroup(id);
    pageGroups[id] = true;
  }

  function isGroupValidated(id)
  {
    var pageGroups = getCurrentPageGroup(id);
    return pageGroups[id] === true;
  }


  // detect if the xmlNode is rendered, visible, and shown on the screen
  amx.isNodeRendered = function(amxNode)
  {
    if(amxNode.tagName == "amx:popup")
    {
      if(amxNode._xmlNode._renderPopup)
      {
        return true;
      }
      return false;
    }

    var attr = amxNode.rendered;

    if(typeof attr !== "undefined")
    {
      if(amx.isValueFalse(attr))
      {
        return false;
      }
    }
    attr = amxNode.visible;
    if(typeof attr !== "undefined")
    {
      if(amx.isValueFalse(attr))
      {
        return false;
      }
    }

    return true;
  }

  function setValidationWatchData(groupId, amxNode, watchData, addRequired)
  {
    if (amxNode.__attrToValidate === undefined)
    {
      return;
    }

    var attributeXmlValue = amxNode._xmlNode.getAttribute(amxNode.__attrToValidate);

    var els = amx.getElsFromString(attributeXmlValue);
    if (els.length > 0)
    {
      if (watchData.el[attributeXmlValue] === undefined)
      {
        watchData.el[attributeXmlValue] = [];
      }

      if(watchData.el[attributeXmlValue].indexOf(groupId) < 0)
      {
        watchData.el[attributeXmlValue].push(groupId);
      }
    }

    // now check to see if this is required
    if (amx.isValueTrue(amxNode.required) == false)
    {
      return;
    }

    var nodeValue = amxNode[amxNode.__attrToValidate];
    if (amx.getTextValue(nodeValue) !== "")
    {
      return;
    }

    if (addRequired == false)
    {
      // this groups has not been validated yet, so disregard
      return;
    }

    var key;
    if (amxNode.tagName == "amx:selectOneChoice")
    {
      key = "MSG_MAKE_A_SELECTION";
    }
    else
    {
      key = "MSG_ENTER_A_VALUE";
    }

    var msg = adf.mf.api.amx.getInfoString(key);
    var label = amxNode.label;
    if (label === undefined || label == null)
    {
      label = "";
    }
    var text = label + ": " + msg;

    if (watchData.required[groupId] === undefined)
    {
      watchData.required[groupId] = [];
    }

    watchData.required[groupId].push(text);
  }

  // add to the passed in list an el expressions that this node
  // and this node's descendants are watching
  function buildValidationWatchData(groupId, $node, watchData, addRequired)
  {
    var watchArrayDfd = $.Deferred();

    var $nodes = $node.children();

    if($nodes !== undefined && $nodes.length > 0)
    {
      var childDfdArray = [];
      // for each node
      for (var i = 0; i < $nodes.length; ++i)
      {
        var $childNode = $($nodes[i]);

        // only check for node info if this is an amx-node
        // if not, just assume this is a container for actual amx-nodes
        if ($childNode.hasClass("amx-node"))
        {
          var amxNode = $childNode.data("amxNode");
          if (amxNode === undefined || amxNode._xmlNode === undefined)
          {
            continue;
          }

          if(amx.isNodeRendered(amxNode) == false)
          {
            continue;
          }

          if(amxNode.tagName == "amx:messages")
          {
            if ((watchData.messages[groupId] === undefined))
            {
              watchData.messages[groupId] = {inline : false, message : amxNode.message};
            }

            if (amx.isValueTrue(amxNode.inline))
            {
              // if this is a messages node, set the watch data so we know we don't need
              // to show a message box for this group
              if (watchData.messages[groupId].inline == false)
              {
                watchData.messages[groupId].inline = true;
                watchData.messages[groupId].message = amxNode.message;
              }
            }

            continue;
          }

          setValidationWatchData(groupId, amxNode, watchData, addRequired)
        }

        var childDfd = buildValidationWatchData(groupId, $childNode, watchData, addRequired);
        childDfdArray.push(childDfd);
      }
      amx.serialResolve(childDfdArray, function(){}).done(function()
      {
            watchArrayDfd.resolve();
      });
    }
    else
    {
      watchArrayDfd.resolve();
    }

    return watchArrayDfd.promise();
  }

  // build a list of all the el expressions that this group/array of groups are watching
  function buildValidationGroupWatchData($nodeArray, isValidating)
  {
    var groupWatchArrayDfd = $.Deferred();
    var dfd = [];
    var watchData = {el : {}, required: {}, messages: {}};

    for(var i = 0; i < $nodeArray.length; ++i)
    {
      var $node = $($nodeArray[i]);
      var addRequired;
      var id =  $node.data("amxNode").id;
      if (isValidating == true)
      {
        // add this to the list so that buildElWatchArray will return any required
        // failures for this group
        setGroupValidated(id);
        addRequired = true;
      }
      else
      {
        addRequired = isGroupValidated(id);
      }

      dfd.push(buildValidationWatchData(id, $node, watchData, addRequired));
    }

    $.when(dfd).done(function()
    {
      groupWatchArrayDfd.resolve(watchData);
    });
    return groupWatchArrayDfd.promise();
  }

  function getValidationDataForGroup(validationData, groupId, watchData)
  {
    if(validationData[groupId] === undefined)
    {
      var messages = watchData.messages[groupId];
      validationData[groupId] = {invalid: [], required: [], messages: messages};
    }

    return validationData[groupId];
  }

  function buildValidationDataInternal(group$nodes, isValidating, validationWatchData)
  {
    var groupDfd = $.Deferred();
    if (group$nodes.length == 0)
    {
      // nothing to do here, so just resolve it
      groupDfd.resolve();
      return groupDfd.promise();
    }
    amx.getElValue(amx.validationsInvalidListEl).done(function(request, response)
    {
      var invalidList = response[0].value;
      var elWatchDfd = $.Deferred();
      if(validationWatchData == null)
      {
        buildValidationGroupWatchData(group$nodes, isValidating).always(function(watchData)
        {
          validationWatchData = watchData;
          elWatchDfd.resolve();
        });
      }
      else
      {
        elWatchDfd.resolve();
      }

      elWatchDfd.always(function()
      {
        var hasError = false;
        var hasWarning = false;
        var validationData = {};

        for(var item in validationWatchData.required)
        {
          if(validationWatchData.required.hasOwnProperty(item))
          {
            hasError = true;

            var groupValidationData = getValidationDataForGroup(validationData, item, validationWatchData);
            // add all of these to the required list
            groupValidationData.required = validationWatchData.required[item];
          }
        }

        // iterate through the invalid el expressions and determine if
        // the expression is in the list of el expressions that are
        // defined in descendants of the validationGroup tag
        for(var item in invalidList)
        {
          if(invalidList.hasOwnProperty(item))
          {
            var elInfo = validationWatchData.el[item];
            if(elInfo === undefined)
            {
              // not in the list
              continue;
            }

            var arrayList = invalidList[item];
            if (hasError == false)
            {
              for(var index in arrayList)
              {
                var nvp = arrayList[index];
                if (nvp.name == "error")
                {
                  hasError = true;
                  break;
                }

                if (nvp.name == "warning")
                {
                  hasWarning = true;
                }
              }
            }

            for (var group in elInfo)
            {
              var groupId = elInfo[group];
              var groupValidationData = getValidationDataForGroup(validationData, groupId, validationWatchData);
              groupValidationData.invalid.push(arrayList);
            }
          }
        }

        if (hasError)
        {
          // let the caller know that navigation should fail
          groupDfd.reject(validationData);
          return;
        }

        // succeeded, but with possible warnings, so send in the data array
        groupDfd.resolve(validationData);
      });
    }).fail(function(request, response)
    {
      // failed to retrieve the invalid list - allow navigation to proceed
      groupDfd.resolve();
    });

    return groupDfd.promise();
  }

  amx.buildValidationData = function($nodeArray)
  {
    return buildValidationDataInternal($nodeArray, false, null);
  }

  function getGroupsById($node)
  {
    var popup$node = $node.closest(".amx-popup");
    var top$node;
    if (popup$node.length > 0)
    {
      // we are inside a popup, so we need to get a list of all of the groups in this popup
      top$node = popup$node;
    }
    else
    {
      top$node = $node.closest(".amx-view");
    }
    var group$nodeArray = top$node.find(".amx-validationGroup");

    var groupsById = {};
    for(var i = 0; i < group$nodeArray.length; ++i)
    {
      var $group = $(group$nodeArray[i]);
      var amxNode = $group.data("amxNode");
      var id = amxNode.id;
      groupsById[id] = $group;
    }

    return groupsById;
  }

  // get the list of all of the groups that this control validates against
  function getValidationGroupList($node)
  {
    var groupsListDfd = $.Deferred();
    var groups = [];

    var amxNode = $node.data("amxNode");

    var children = $(amxNode._xmlNode).children();
    if(children.length > 0)
    {
      var groupIdArray = [];
      amx.serialResolve(children, function(subTag, i)
      {
        // if it is a validationBehavior
        if(subTag.tagName !== "amx:validationBehavior")
        {
          return null;
        }

        var disabledEl = subTag.getAttribute("disabled");
        if (disabledEl !== null)
        {
          var propDfd = $.Deferred();
          amx.getElValue(disabledEl).done(function(request,response){

            var resp = response[0];
            if (amx.isValueTrue(resp.value))
            {
              return;
            }
            var groupId = subTag.getAttribute("group");
            if(groupIdArray.indexOf(groupId) < 0)
            {
              groupIdArray.push(groupId);
            }

          }).always(function(){
            propDfd.resolve();
          });

          return propDfd;
        }
        else
        {
          var groupId = subTag.getAttribute("group");
          if(groupIdArray.indexOf(groupId) < 0)
          {
            groupIdArray.push(groupId);
          }

          return null;
        }
      }).done(function()
      {
        var groupsById = getGroupsById($node);
        for(var i = 0; i < groupIdArray.length; ++i)
        {
          var $group = groupsById[groupIdArray[i]];
          if( typeof $group !== "undefined")
          {
            groups.push($group);
          }
        }

        groupsListDfd.resolve(groups);
      });
    }
    else
    {
      groupsListDfd.resolve(groups);
    }

    return groupsListDfd.promise();
  }

  amx.requiredControlValueChanged = function($validationGroup)
  {
    if (amx.isValidating())
    {
      return;
    }

    // mark this group as validated
    var amxNode = $validationGroup.data("amxNode");
    if (amxNode)
    {
      setGroupValidated(amxNode.id);
      updateValidationMessages();
    }
  }

  var __isValidating = false;
  function validateBegin()
  {
    __isValidating = true;
  }

  function validateEnd(group$nodes,validationData)
  {
    __isValidating = false;

    if (group$nodes === undefined || group$nodes.length == 0)
    {
      // no groups were validated, exit early
      return;
    }

    var groupsToCheck = [];

    if (validationData !== undefined)
    {
      // if the validationData is not undefined, then there was a failure
      // go through and create a list of the groups that may need a message box
      for (groupId in validationData)
      {
        if(validationData.hasOwnProperty(groupId) == false)
        {
          continue;
        }

        groupsToCheck.push(groupId);
      }
    }

    updateValidationMessages(groupsToCheck);
  }

  amx.isValidating = function()
  {
    return __isValidating;
  }

  function validateInternal($node)
  {
    var validateDfd = $.Deferred();
    // attempt to retrieve the ancestor group
    var amxNode = $node.data("amxNode");

    getValidationGroupList($node).always(function(group$nodes)
    {
      // we now have the list of groups that we need to validate, so go
      // through them all and verify every el expressions is in the valid state
      if(group$nodes.length == 0)
      {
        // everything is valid since there are no groups
        validateDfd.resolve(group$nodes);
        return;
      }

      // make sure all of the unset values are validated
      amx.getElValue(amx.validationsUnsetListEl).done(function(request, response)
      {
        var unsetList = response[0].value;
        // set this to null so that we only build up the node list when we want to
        // figure out if an item is valid or not
        var validationWatchData = null;
        var elToResolve = [];
        var unsetDfd = $.Deferred();
        if(unsetList.length > 0)
        {
          buildValidationGroupWatchData(group$nodes, true).done(function(watchData)
          {
            validationWatchData = watchData;
            for(var i = 0; i < unsetList.length; ++i)
            {
              var item = unsetList[i];
              if(validationWatchData.el[item] === undefined)
              {
                // not in the list
                continue;
              }

              elToResolve.push(item);
            }

            if(elToResolve.length > 0)
            {
              amx.getElValue(elToResolve).done(function(request, response)
              {
                // all the el expressions are resolved, so create an
                // array of values to set
                var setList = [];
                // we might have more responses than just our requests, so make sure we handle that here
                for(var i = 0; i < response.length; ++i)
                {
                  var item = response[i];
                  if(elToResolve.indexOf(item.name) < 0)
                  {
                    // not part of what we requested
                    continue;
                  }

                  setList.push({name:item.name, value:amx.getObjectValue(item.value)});
                }
                amx.setElValue(setList).done(function(request, response)
                {
                  // success (but with possible failures), just continue
                  // and check the invalid list later
                  unsetDfd.resolve();
                }).fail(function(request, response)
                {
                  // failure, just continue and check the invalid list later
                  unsetDfd.resolve();
                });
              }).fail(function(request, response)
              {
                // failure, just continue and check the invalid list later
                unsetDfd.resolve();
              });
            }
            else
            {
              unsetDfd.resolve();
            }
          }).fail(function()
          {
            unsetDfd.resolve();
          });
        }
        else
        {
          unsetDfd.resolve();
        }

        unsetDfd.done(function()
        {
          buildValidationDataInternal(group$nodes, true, validationWatchData).done(function(validationData)
          {
            // we can navigate (may have warnings)
            validateDfd.resolve(group$nodes,validationData);
          }).fail(function(validationData)
          {
            // we can NOT navigate
            validateDfd.reject(group$nodes,validationData);
          });
        });
      }).fail(function(request, response)
      {
        // failed to retrieve the unset list - allow navigation to proceed
        validateDfd.resolve(group$nodes);
      });
    });

    return validateDfd.promise();
  }

  amx.validate = function($node)
  {
    adf.mf.internal.perf.start("validation processing" );

    var validateDfd = $.Deferred();

    validateBegin();
    validateInternal($node).done(function(group$nodes,validationData){
      validateEnd(group$nodes);
      validateDfd.resolve();
      adf.mf.internal.perf.stop("validation processing", "done");
    }).fail(function(group$nodes,validationData){
      validateEnd(group$nodes,validationData);
      validateDfd.reject();
      adf.mf.internal.perf.stop("validation processing", "failed");
    });

    return validateDfd.promise();
  };

  // add this renderer
  amx.registerRenderers("amx",amxRenderers);

  $(function(){
    // register the data change handler
    adf.addDataChangeListeners(amx.validationsInvalidListEl,validationsDataChangeHandler);
  });

  //--------- MessagesPopup ---------//
  function MessagesPopup(){};

  MessagesPopup.prototype.addLabel = function(groupValidationData, $textNode)
  {
    if ($textNode != null)
    {
      return;
    }

    var $headerNode = $("<div class='amx-messages-header'></div>");
    if (groupValidationData.messages !== undefined)
    {
      var message = groupValidationData.messages.message;

      if (message !== undefined && message !== null && message !== "")
      {
        // now add the message label
        var $labelNode = $("<div class='amx-messages-header-text'>" + message + "</div>");
        $headerNode.append($labelNode);
      }
    }
    this.$content.append($headerNode);
  }

  MessagesPopup.prototype.addText = function(groupValidationData, $prevTextNode, name, text)
  {
    // make sure the label is added if this is the first node for this particular group
    this.addLabel(groupValidationData, $prevTextNode);

    var $itemNode = $("<div class='amx-messages-item'></div>");
    var $textItem = $("<div class='amx-messages-text amx-messages-text-" + name + "'>" + text + "</div>");
    $textItem.addClass("amx-messages-" + name);
    if ($prevTextNode == null)
    {
      $textItem.addClass("amx-messages-first");
    }
    $itemNode.append($textItem);
    $itemNode.append("<div class='amx-messages-icon amx-messages-icon-" + name + "'></div>");
    this.$content.append($itemNode);

    return $textItem;
  }

  MessagesPopup.prototype.addFooter = function()
  {
    var $textNode = $("<div class='amx-messages-footer'></div>");
    var $btnNode = $("<div class='amx-messages-btn'></div>");
    $btnNode.append("<div class='amx-messages-btn-label'>OK</div>");
    $textNode.append($btnNode);
    var mousedown = "mousedown";
    var mouseup = "mouseup";
    if(amx.hasTouch()){
      mousedown = "touchstart";
      mouseup = "touchend";
    }
    $btnNode.bind(mousedown,function(){
      $btnNode.addClass("amx-selected");
    });
    $btnNode.bind(mouseup,function(){
      $btnNode.removeClass("amx-selected");
    });
    $btnNode.bind("mouseout",function(){
      $btnNode.removeClass("amx-selected");
    });

    this.$content.append($textNode);
  }

  MessagesPopup.prototype.create = function(){
    var $bodyPage = $("#bodyPage");
    $messagesPopup = $("<div class='messagesPopup'></div>");
    // make sure this responds to dragging for scrolling purposes
    $messagesPopup.addClass("amx-scrollable");
    $messagesPopupScreen = $("<div class='transparentScreen messagesPopupScreen'></div>");
    $bodyPage.append($messagesPopupScreen);
    $bodyPage.append($messagesPopup);
    $messagesPopup.data("messagesPopup",this);
    var messagesPopup = this;
    this.$e = $messagesPopup;
    this.$screen = $messagesPopupScreen;
    $messagesPopup.hide();
    var $messagesPopupContainer = $("<div class='messagesPopupContainer'></div>");
    $messagesPopup.append($messagesPopupContainer);
    var $messagesPopupContent = $("<div class='messagesPopupContent'></div>");
    $messagesPopupContainer.append($messagesPopupContent);
    this.$content = $messagesPopupContent;

    $messagesPopupScreen.tap(function(){
      // messagesPopup.hide();
    });

    $messagesPopup.tap(".messagesPopupContainer .amx-messages-btn",function(){
      messagesPopup.hide();
    });

    return $messagesPopup;
  }

  MessagesPopup.prototype.show = function(groupsToShow, validationData){
    var $messagesPopup = this.$e;
    var $messagesPopupScreen = this.$screen;
    var $messagesPopupContent = this.$content;

    $messagesPopupContent.empty();

    for (var i = 0; i < groupsToShow.length; ++i)
    {
      groupValidationData = validationData[groupsToShow[i]];
      if (groupValidationData === undefined)
      {
        continue;
      }

      var $textNode = null;

      var groupInvalid = groupValidationData.invalid;
      for (item in groupInvalid)
      {
        var nvp = groupInvalid[item];

        var arrayList = groupInvalid[item];
        for(var index in arrayList)
        {
          var nvp = arrayList[index];
          $textNode = this.addText(groupValidationData, $textNode, nvp.name, nvp.value);
        }
      }

      // check for required failures
      var groupRequired = groupValidationData.required;
      for (item in groupRequired)
      {
        var text = groupRequired[item];
        $textNode = this.addText(groupValidationData, $textNode, "error", text);
      }

      if ($textNode != null)
      {
        $textNode.addClass("amx-messages-last");
      }
    }

    this.addFooter();

    $messagesPopupScreen.show();
    $messagesPopup.show();
  }

  MessagesPopup.prototype.hide = function(){
    var $messagesPopup = this.$e;
    var $messagesPopupScreen = this.$screen;

    $messagesPopup.remove();
    $messagesPopupScreen.remove();
  }


  function messagesPopupCreate(groupsToShow, validationData){
    var $messagesPopup = null;
    var messagesPopup = null;
    var $bodyPage = $("#bodyPage");
    if($bodyPage.find(".messagesPopup").size() > 0){
      $messagesPopup = $bodyPage.find(".messagesPopup");
      messagesPopup = $messagesPopup.data("messagesPopup");
    }else{
      messagesPopup = new MessagesPopup();
      $messagesPopup = messagesPopup.create();
    }
    messagesPopup.show(groupsToShow, validationData);
    return messagesPopup;
  }

  //--------- /MessagesPopup ---------//
})();




