You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by fa...@apache.org on 2013/04/08 17:19:09 UTC

svn commit: r1465662 [6/26] - in /qpid/trunk/qpid/tools/src/java: ./ bin/ bin/qpid-web/ bin/qpid-web/authentication/ bin/qpid-web/web/ bin/qpid-web/web/itablet/ bin/qpid-web/web/itablet/css/ bin/qpid-web/web/itablet/images/ bin/qpid-web/web/itablet/ima...

Added: qpid/trunk/qpid/tools/src/java/bin/qpid-web/web/qmf-ui/scripts/qmf-ui.js
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/tools/src/java/bin/qpid-web/web/qmf-ui/scripts/qmf-ui.js?rev=1465662&view=auto
==============================================================================
--- qpid/trunk/qpid/tools/src/java/bin/qpid-web/web/qmf-ui/scripts/qmf-ui.js (added)
+++ qpid/trunk/qpid/tools/src/java/bin/qpid-web/web/qmf-ui/scripts/qmf-ui.js Mon Apr  8 15:19:04 2013
@@ -0,0 +1,3526 @@
+/**
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * This program implements the QMF Console User Interface logic for qmf.html
+ *
+ * It has dependencies on the following:
+ * qmf.css
+ * itablet.css
+ * iscroll.js
+ * jquery.js (jquery-1.7.1.min.js)
+ * itablet.js
+ * excanvas.js (for IE < 9 only)
+ * qpid.js
+ *
+ * author Fraser Adams
+ */
+
+//-------------------------------------------------------------------------------------------------------------------
+
+// Create a new namespace for the qmfui "package".
+var qmfui = {};
+qmfui.TOUCH_ENABLED = 'ontouchstart' in window && !((/hp-tablet/gi).test(navigator.appVersion));
+qmfui.END_EV   = (qmfui.TOUCH_ENABLED) ? "touchend"   : "mouseup";
+
+//-------------------------------------------------------------------------------------------------------------------
+
+/**
+ * This class holds the history of various key statistics that may be held for some QMF
+ * Management Objects so we may see how the state has changes over a particular time range.
+ */
+qmfui.Statistics = function(description) {
+    this.description = description; // Array describing the contents of each stored statistic
+    this.short  = new util.RingBuffer(60);  // Statistics for a 10 minute period (10*60/REFRESH_PERIOD)
+    this.medium = new util.RingBuffer(60);  // Statistics for a 1 hour period (Entries are updated every minute)
+    this.long   = new util.RingBuffer(144); // Statistics for a 1 day period (Entries are updated every 10 minutes)
+
+    /**
+     * Add an item to the end of each statistic buffer.
+     * @param item an array containing the current statistics for each property that we want to hold for a
+     * Management Object, the last item in the array is the Management Object's update timestamp.
+     * As an example for the connection Management Object we would do:
+     * stats.put([connection.msgsFromClient, connection.msgsToClient, connection._update_ts]);
+     * This approach is a little ugly and not terribly OO, but it's pretty memory efficient, which is 
+     * important as there could be lots of Management Objects on a heavily utilised broker.
+     */
+    this.put = function(item) {
+        var TIMESTAMP = item.length - 1; // The timestamp is stored as the last item of each sample.
+        var timestamp = item[TIMESTAMP];
+
+        var lastItem = this.short.getLast();
+        if (lastItem == null) {
+            this.short.put(item); // Update the 10 minute period statistics.
+        } else {
+            var lastTimestamp = lastItem[TIMESTAMP];
+            // 9000000000 is 9 seconds in nanoseconds. If the time delta is less than 9 seconds we hold off adding
+            // the sample otherwise the ring buffer will end up holding less than the full 10 minutes worth.
+            if ((timestamp - lastTimestamp) >= 9000000000) {
+                this.short.put(item); // Update the 10 minute period statistics.
+            }
+        }
+
+        var lastItem = this.medium.getLast();
+        if (lastItem == null) {
+            this.medium.put(item); // Update the 1 hour period statistics.
+        } else {
+            var lastTimestamp = lastItem[TIMESTAMP];
+            // 59000000000 is 59 seconds in nanoseconds. We use 59 seconds rather than 60 seconds because the
+            // update period has a modest +/i variance around 10 seconds.
+            if ((timestamp - lastTimestamp) >= 59000000000) {
+                this.medium.put(item); // Update the 1 hour period statistics.
+            }
+        }
+
+        lastItem = this.long.getLast();
+        if (lastItem == null) {
+            this.long.put(item); // Update the 1 day period statistics.
+        } else {
+            var lastTimestamp = lastItem[TIMESTAMP];
+            // 599000000000 is 599 seconds in nanoseconds (just short of 10 minutes). We use 599 seconds rather
+            // than 600 seconds because the update period has a modest +/ variance around 10 seconds. 
+            if ((timestamp - lastTimestamp) >= 599000000000) {
+                this.long.put(item); // Update the 1 day period statistics.
+            }
+        }
+    };
+
+    /**
+     * This method computes the most recent instantaneous rate for the property specified by the index.
+     * For example for the Connection Management Object an index of 1 would represent msgsToClient as
+     * described in the comments for put(). Note that the rate that is returned is the most recent
+     * instantaneous rate, which means that it uses the samples held in the ring buffer used to hold
+     * the ten minute window.
+     * @param the index of the property that we wish to obtain the rate for.
+     * @return the most recent intantaneous rate in items/s
+     */
+    this.getRate = function(index) {
+        var size = this.short.size();
+        if (size < 2) {
+            return 0;
+        }
+
+        var s1 = this.short.get(size - 2);
+        var t1 = s1[s1.length - 1];
+
+        var s2 = this.short.get(size - 1);
+        var t2 = s2[s2.length - 1];
+
+        var delta = (t2 == t1) ? 0.0000001 : t2 - t1; // Shouldn't happen, but this tries to avoid divide by zero.
+        var rate = ((s2[index] - s1[index]) * 1000000000)/delta
+        return rate;
+    };
+};
+
+
+//-------------------------------------------------------------------------------------------------------------------
+//                                            Main Console Class                                             
+//-------------------------------------------------------------------------------------------------------------------
+
+/**
+ * Create a Singleton instance of the main Console class.
+ * This class contains the QMF Console and and caches the core QMF management objects. Caching the getObjects()     
+ * results is obviously sensible and helps avoid the temptation to call getObjects() elsewhere which is rather
+ * inefficient as it is invoked using AMQP request/response but morever in the case of this UI is is called via     
+ * a REST proxy. Caching getObjects() calls also helps to abstract the asynchronous nature of JavaScript as the
+ * cache methods can be called synchronously which "feels" a more natural way to get the data.
+ *
+ * This class is also responsible for initialising the rest of the pages when jQuery.ready() fires and updating
+ * them when QMF object updates occur, in other words it might be considered "Main".
+ *
+ * This class handles the QMF Console Connection lifecycle management. It's worth pointing out that it's fairly
+ * subtle and complex particularly due to the asynchronous nature of JavaScript. It's made even more complex
+ * by the fact that there are two distinct ways used to decide when to get the QMF Management Objects. The default
+ * way is where QMF Event delivery is enabled, in this case the onEvent() method is triggered periodically by
+ * the underlying QMF Console's Event dispatcher and in this case the Event dispatcher takes care of reconnection
+ * attempts. However if disableEvents is selected then the QMF Management Objects are retrieved via a timed
+ * poll. In this case this method must correctly start the pollForData() but must also let it expire if the user
+ * selects a new Connection that has QMF Event deliver enabled.
+ */
+qmfui.Console = new function() {
+    /**
+     * This is an array of QMF Console Connections that the user is interested in. It gets initiated with the
+     * default connection (that is to say the connection that the REST API has been configured to use if no
+     * explicit URL has been supplied). Using a Connection URL of "" makes the REST API use its default.
+     * This property has been exposed as a "public" property of qmfui.Console because then it becomes possible
+     * to "configure" the initial set of consoleConnections via a trivial config.js file containing:
+     * qmfui.Console.consoleConnections = [{name: "default", url: ""},{name: "wildcard", url: "0.0.0.0:5672",  
+     * connectionOptions: {sasl_mechs:"ANONYMOUS"}, disableEvents: true}];
+     * i.e. a JSON array of Console Connection settings.
+     */
+    this.consoleConnections = [{name: "default", url: ""}];
+
+    var _objects = {};   // A map used to cache the QMF Management Object lists returned by getObjects().
+    var _disableEvents = false; // Set for Connections that have Events disabled and thus refresh via timed polling.
+    var _polling = false; // Set when the timed polling is active so we can avoid starting it multiple times.
+    var _receivedData = false; // This flag is used to tell the difference between a failure to connect and a disconnect.
+    var _console = null; // The QMF Console used to retrieve information from the broker.
+    var _connection = null;
+    var _activeConsoleConnection = 0; // The index of the currently active QMF Console Connection.
+
+    /**
+     * Resets the messages that get rendered if the REST API Server or the Qpid broker fail.
+     */
+    var resetErrorMessages = function() {
+        $("#restapi-disconnected").hide();
+        $("#broker-disconnected").hide();
+        $("#failed-to-connect").hide();
+    };
+
+    /**
+     * Show Rest API Disconnected Message, hide the others.
+     */
+    var showRestAPIDisconnected = function() {
+        resetErrorMessages();
+        $("#restapi-disconnected").show();
+    };
+
+    /**
+     * Show Broker Disconnected Message, hide the others.
+     */
+    var showBrokerDisconnected = function() {
+        resetErrorMessages();
+        $("#broker-disconnected").show();
+    };
+
+    /**
+     * Show Failed to Connect Message, hide the others.
+     */
+    var showFailedToConnect = function() {
+        resetErrorMessages();
+        $("#failed-to-connect").show();
+    };
+
+    /**
+     * QMF2 EventHandler that we register with the QMF2 Console.
+     * @param workItem a QMF2 API WorkItem.
+     */
+    var onEvent = function(workItem) {
+        if (workItem._type == "AGENT_DELETED") {
+            var agent = workItem._params.agent;
+
+            if (agent._product == "qpidd" && _connection != null) {
+                if (_receivedData) {
+                    showBrokerDisconnected();
+                } else {
+                    showFailedToConnect();
+                }
+            } else if (agent._product == "qpid.restapi") {
+                showRestAPIDisconnected();
+            }
+        } else {
+            _receivedData = true;
+            resetErrorMessages();
+            if (workItem._type == "EVENT_RECEIVED") {
+                qmfui.Events.update(workItem);
+            }
+        }
+
+        // onEvent() will be called periodically by the broker heartbeat events, so we use that fact to trigger
+        // a call to getAllObjects() which will itself trigger a call to updateState() when its results return.
+        getAllObjects();
+    };
+
+    /**
+     * This method is called if startConsole() has been called with disableEvents. In this state
+     * the onEvent() method won't be triggered by the QMF2 callback so we need to explicitly poll via a timer.
+     */
+    var pollForData = function() {
+        if (_connection != null && _disableEvents) {
+            _polling = true;
+            getAllObjects();
+            setTimeout(function() {
+                pollForData();
+            }, qmf.REFRESH_PERIOD);
+        } else {
+            _polling = false;
+        }
+    };
+
+    /**
+     * This method is called by the failure handler of getAllObjects(). If it is triggered it means there has
+     * been some form of Server side disconnection. If this occurs set an error banner then attempt to re-open
+     * the Qpid Connection. If the Connection reopens successfully updateState() will start getting called again.
+     * This method returns immediately if _disableEvents is false because the underlying QMF Console has its own 
+     * reconnection logic in its Event dispatcher, which we only need to replicate if that is disabled.
+     * @param xhr the jQuery XHR object.
+     */
+    var timeout = function(xhr) {
+        if (_disableEvents) {
+            if (xhr.status == 0) {
+                showRestAPIDisconnected();
+            } else {
+                if (_receivedData) {
+                    showBrokerDisconnected();
+                } else {
+                    showFailedToConnect();
+                }
+                if (xhr.status == 404 && _connection != null && _connection.open) {
+                    _connection.open();
+                }
+            }
+        }
+    };
+
+    /**
+     * This method is called when getAllObjects() completes, that is to say when all of the asynchronous responses
+     * for the Deferred XHR objects returned by getObjects() have all returned successfully. This method triggers
+     * the update() method on each of the user interface pages.
+     */
+    var updateState = function() {
+        _receivedData = true;
+        resetErrorMessages();
+
+        qmfui.Broker.update();
+        qmfui.Connections.update();
+        qmfui.Exchanges.update();
+        qmfui.Queues.update();
+        //qmfui.Links.update();         // TODO
+        //qmfui.RouteTopology.update(); // TODO
+
+        // Update sub-pages after main pages as these may require state (such as statistics) set in the main pages.
+        qmfui.SelectedConnection.update();
+        qmfui.SelectedQueue.update();
+        qmfui.QueueSubscriptions.update();
+        qmfui.ConnectionSubscriptions.update();
+        qmfui.SelectedExchange.update();
+        qmfui.Bindings.update();
+        qmfui.Graphs.update();
+    };
+
+    /**
+     * This method retrieves the QmfConsoleData Objects from the real QMF Console via AJAX calls to the REST API.
+     * Because the AJAX calls are all asynchronous we use jQuery.when(), which provides a way to execute callback   
+     * functions based on one or more objects, usually Deferred objects that represent asynchronous events.
+     * See http://api.jquery.com/jQuery.when/ and http://api.jquery.com/deferred.then/
+     */
+    var getAllObjects = function() {
+        $.when(
+            _console.getObjects("broker", function(data) {_objects.broker = data;}),
+            _console.getObjects("queue", function(data) {_objects.queue = data;}),
+            _console.getObjects("exchange", function(data) {_objects.exchange = data;}),
+            _console.getObjects("binding", function(data) {_objects.binding = data;}),
+            _console.getObjects("subscription", function(data) {_objects.subscription = data;}),
+            _console.getObjects("connection", function(data) {_objects.connection = data;}),
+//            _console.getObjects("link", function(data) {_objects.link = data;}),
+//            _console.getObjects("bridge", function(data) {_objects.bridge = data;}),
+            _console.getObjects("session", function(data) {_objects.session = data;})
+        ).then(updateState, timeout);
+    };
+
+    /**
+     * Handle the load event, triggered when the document has completely loaded.
+     */
+    var loadHandler = function() {
+        qmfui.Console.startConsole(0); // Start the default QMF Console Connection.
+    };
+
+    /**
+     * Handle the unload event, triggered when the document unloads or we navigate off the page. It's not 100%
+     * reliable, which is a shame as it's the best way to clear up Server state. If it fails the Server will
+     * eventually clear up unused Connections after a timeout period. TODO Opera seems to be especially bad at
+     * firing the unloadHandler, not sure why this is, something to look into.
+     * @param event the event that triggered the unloadHandler.
+     */
+    var unloadHandler = function(event) {
+        // For mobile Safari (at least) we get pagehide events when navigating off the page, but also when closing via
+        // home or locking the device. Fortunately this case has the persisted flag set as we don't want to close then.
+        var persisted = (event.type == "pagehide") ? event.originalEvent.persisted : false;
+        if (!persisted) {
+            qmfui.Console.stopConsole();
+        }
+    };
+
+    /**
+     * Callback handler triggered when _console.addConnection() fails. This should only occur if an actual
+     * exception occurs on the REST API Server due to invalid connectionOptions.
+     */
+    var handleConnectionFailure = function() {
+        qmfui.Console.stopConsole();
+        showFailedToConnect();
+    }
+
+    // ******************************************* Accessor Methods *******************************************
+
+    /**
+     * Retrieve the broker Management Object, optionally as a QmfConsoleData Object (with invokeMethod attached).
+     * @param makeConsoleData if true attach the invokeMethod method to the returned Management Object.
+     * @return the QMF broker Management Object optionally as a QmfConsoleData.
+     */
+    this.getBroker = function(makeConsoleData) {
+        var brokers = _objects.broker;
+
+        if (brokers == null || brokers.length == 0) {
+            // Return a fake QmfConsoleData Object with an invokeMethod that calls the callback handler with a
+            // response object containing error_text. It is actually pretty uncommon for the brokers array to
+            // be empty so this approach allows us to call invokeMethod without lots of checking for broker == null.
+            return {invokeMethod: function(name, inArgs, handler) {
+                handler({"error_text" : "Could not retrieve broker Management Object"});
+            }};
+        } else {
+            var broker = brokers[0];
+            if (makeConsoleData) {
+                _console.makeConsoleData(broker);
+            }
+            return broker;
+        }
+    };
+
+    /**
+     * These methods are basic accessors returning the cached lists of QmfData objects returned by getObjects()
+     */
+    this.getQueues = function() {
+        return _objects.queue;
+    };
+
+    this.getExchanges = function() {
+        return _objects.exchange;
+    };
+
+    this.getBindings = function() {
+        return _objects.binding;
+    };
+
+    this.getSubscriptions = function() {
+        return _objects.subscription;
+    };
+
+    this.getConnections = function() {
+        return _objects.connection;
+    };
+
+    this.getSessions = function() {
+        return _objects.session;
+    };
+
+/* TODO
+    this.getLinks = function() {
+        return _objects.link;
+    };
+
+    this.getBridges = function() {
+        return _objects.bridge;
+    };
+*/
+
+    /**
+     * Calls the underlying QMF Console's makeConsoleData(). This call turns a QmfData object into a QmfConsoleData
+     * object, which adds methods such as invokeMethod() to the object.
+     * @param the object that we want to turn into a QmfConsoleData.
+     */
+    this.makeConsoleData = function(object) {
+        _console.makeConsoleData(object);
+    };
+
+    /**
+     * @return the list of QMF Console Connections that the user is interested in. The returned list is a list of
+     * objects containing url, name and connectionOptions properties.
+     */
+    this.getConsoleConnectionList = function() {
+        return this.consoleConnections;
+    };
+
+    /**
+     * Note that in the following it's important to note that there is separation between adding/removing
+     * Console Connections and actually starting/stopping Console Connections, this is because a user may wish
+     * to add several QMF Console Connections to point to a number of different brokers before actually
+     * chosing to connect to a particular broker. Similarly a user may wish to delete a QMF Console Connection
+     * from the list (s)he is interested in independently from selecting a new connection.
+     */
+
+    /**
+     * Append a new Console Connection with the specified url, name and connectionOptions to the end of the
+     * list of QMF Console Connections the the user many be interested in. Note that this method *does not*
+     * actually start a connection to the new console for that we must call the startConsole() method.
+     * The name supplied in this method is simply a user friendly name and is not related to the name that
+     * may be applied to the Connection when it is stored on the REST API, that name is really best considered
+     * as an opaque "handle" and is intended to be unique for each connection.
+     *
+     * @param name a user friendly name for the Console Connection.
+     * @param url the broker Connection URL in one of the formats supported by the Java ConnectionHelper class
+     * namely an AMQP 0.10 URL, an extended AMQP 0-10 URL, a Broker URL or a Java Connection URL.
+     * @param connectionOptions a JSON string containing the Connection Options in the same form as used
+     * in the qpid::messaging API.
+     */
+    this.addConsoleConnection = function(name, url, connectionOptions, disableEvents) {
+        if (disableEvents) {
+            this.consoleConnections.push({name: name, url: url, connectionOptions: connectionOptions,
+                                          disableEvents: true});
+        } else {
+            this.consoleConnections.push({name: name, url: url, connectionOptions: connectionOptions});
+        }
+    };
+
+    /**
+     * Remove the Console Connection specified by the index. Note that this method *does not* actually stop
+     * a connection to the console for that we must call the stopConsole() method.
+     * @param index the index of the Console Connection that we want to remove.
+     */
+    this.removeConsoleConnection = function(index) {
+        if (_activeConsoleConnection > index) {
+            _activeConsoleConnection--;
+        }
+        this.consoleConnections.splice(index, 1);  // remove a single array item at the specified index.
+    };
+
+    /**
+     * Actually start the Qpid Connection and QMF Console for the Console Connection stored at the specified index.
+     * When the QMF Console successfully starts it will start sending QMF Events and updating the Management
+     * Objects automatically, this will in turn cause the User Interface pages to refresh.
+     * Alternatively if QMF Event delivery is disabled this method initiates the pollForData().
+     * @param index the index of the Console Connection that we wish to start. Index zero is the default Console.
+     */
+    this.startConsole = function(index) {
+        var connection = this.consoleConnections[index];
+
+        // Using a Connection URL of "" makes the REST API use its default configured broker connection.
+        var factory = new qpid.ConnectionFactory(connection.url, connection.connectionOptions);
+        _connection = factory.createConnection();
+
+        // Initialise QMF Console
+        _console = new qmf.Console(onEvent);
+        if (connection.disableEvents != null) {
+            _disableEvents = true;
+            _console.disableEvents();
+        } else {
+            _disableEvents = false;
+        }
+
+        _receivedData = false;
+        _console.addConnection(_connection, handleConnectionFailure);
+        _activeConsoleConnection = index;
+
+        // If disableEvents is set we have to use a timed poll to get the Management Objects. We check if the polling
+        // loop is already running, because if we try and start it multiple times we may get spurious refreshes.
+        if (_disableEvents && !_polling) {
+            pollForData();
+        }
+    };
+
+    /**
+     * Stops the currently running Console Connection and closes the Qpid Connection. This will result in the
+     * underlying Connection object on the REST API Server getting properly cleaned up. It's not essential
+     * to call stopConsole() before a call to startConsole() as the server Connection objects will eventually
+     * time out, but it's good practice to do it if at all possible.
+     */
+    this.stopConsole = function() {
+        if (_console) {
+            _console.destroy();
+        }
+
+        if (_connection) {
+            _connection.close();
+            _connection = null;
+        }
+    };
+
+    /**
+     * @return the index of the currently active (connected) QMF Console Connection.
+     */
+    this.getActiveConsoleConnection = function() {
+        return _activeConsoleConnection;
+    };
+
+    // *********************** Initialise class when the DOM loads using jQuery.ready() ***********************
+    $(function() {
+        // Create a fake logging console for browsers that don't have a real console.log - only for debugging.
+        if (!window.console) {
+            /* // A slightly hacky console.log() to help with debugging on old versions of IE.
+            console = window.open("", "console", "toolbar, menubar, status, width=500, height=500, scrollbars=yes");
+            console.document.open("text/plain");
+            console.log = function(text) {console.document.writeln(text);};*/
+
+            console = {log: function(text) {}}; // Dummy to avoid bad references in case logging accidentally added.
+        }
+
+        // Add a default show handler. Pages that bind update() to show should remove this by doing unbind("show").
+        $(".main").bind("show", function() {$("#resource-deleted").hide();});
+
+        // pagehide and unload each work better than the other in certain circumstances so we trigger on both.
+        $(window).bind("unload pagehide", unloadHandler);
+
+        // Iterate through each page calling its initialise method if one is present.
+        for (var i in qmfui) {
+            if (qmfui[i].initialise) {
+                qmfui[i].initialise();
+            }
+        }
+
+        // Send a synthesised click event to the settings-tab element to select the settings page on startup.
+        // We check if the left property of the main class is zero, if it is then the sidebar has been expanded
+        // to become the main menu (e.g. for mobile devices) otherwise we show the settings page.
+        if (parseInt($(".main").css('left'), 10) != 0) {
+            $("#settings-tab").click();
+        }
+
+        // Hide the splash page.
+        $("#splash").hide();
+    });
+
+    $(window).load(loadHandler);
+}; // End of qmfui.Console definition
+
+
+//-------------------------------------------------------------------------------------------------------------------
+//                                            Configure Settings                                             
+//-------------------------------------------------------------------------------------------------------------------
+
+/**
+ * Create a Singleton instance of the Settings class managing the id="settings" page.
+ */
+qmfui.Settings = new function() {
+    /**
+     * Show the Settings page, rendering any dynamic content if necessary.
+     */
+    var show = function() {
+        // Retrieve the currently configured QMF Console Connections.
+        var qmfConsoleConnections = qmfui.Console.getConsoleConnectionList();
+
+        iTablet.renderList($("#qmf-console-selector"), function(i) {
+            var qmfConsoleConnection = qmfConsoleConnections[i];
+            var name = qmfConsoleConnection.name;
+            var url = qmfConsoleConnection.url;
+            url = (url == null || url == "") ? "" : " (" + url + ")";
+            var label = (name == null || name == "") ? url : name + url;
+            var checked = (i == qmfui.Console.getActiveConsoleConnection()) ? "checked" : "";
+
+            return "<li class='arrow'><label for='qmf-console" + i + "'>" + label + "</label><input type='radio' id='qmf-console" + i + "' name='qmf-console-selector' value='" + i + "' " + checked + "/><a href='#selected-qmf-console-connection?index=" + i + "'></a></li>";
+        }, qmfConsoleConnections.length);
+
+        $("#qmf-console-selector input").change(changeConsole);
+    };
+
+    /**
+     * If the settings-hide-qmf-objects checkbox gets changed refresh the Queues and Exchanges pages to reflect this.
+     */
+    var changeHideQmf = function() {
+        qmfui.Queues.update();
+        qmfui.Exchanges.update();
+    };
+
+    /**
+     * Handles changes to the Console selection Radio buttons. If a change occurs the Console Connection is stopped
+     * and the newly selected Console Connection is started (we chose based on the index into the list)
+     */
+    var changeConsole = function() {
+        qmfui.Console.stopConsole();
+        qmfui.Console.startConsole($(this).val());
+    };
+
+    this.initialise = function() {
+        $("#settings").bind("show", show);
+        $("#settings-hide-qmf-objects").change(changeHideQmf);
+    };
+}; // End of qmfui.Settings definition
+
+
+/**
+ * Create a Singleton instance of the AddConsoleConnection class managing the id="add-console-connection" page.
+ */
+qmfui.AddConsoleConnection = new function() {
+    var submit = function() {
+        var consoleURL = $("#console-url");
+
+        // Check that a URL value has been supplied. TODO Probably worth doing some validation that the supplied
+        // Connection URL is at least syntactically valid too.
+        var url = consoleURL.val();
+        if (url == "") {
+            consoleURL.addClass("error");
+            return;
+        } else {
+            consoleURL.removeClass("error");
+        }
+
+        var name = $("#console-name").val();
+        try {
+            var connectionOptions = $.parseJSON($("#add-connection-options textarea").val());
+            // TODO worth checking that the connection options are valid and in a usable format. Connection Options
+            // is still a bit of a work in progressed. though it seems to work fine if sensible options are used.
+            qmfui.Console.addConsoleConnection(name, url, connectionOptions, $("#console-disable-events")[0].checked);
+            iTablet.location.back();
+        } catch(e) {
+            setTimeout(function() {
+                alert("Connection Options must be entered as a well-formed JSON string.");
+                return;
+            }, 0);
+        }
+    };
+
+    this.initialise = function() {
+        // Using END_EV avoids the 300ms delay responding to anchor click events that occurs on mobile browsers.
+        $("#add-console-connection .right.button").bind(qmfui.END_EV, submit);
+    };
+}; // End of qmfui.AddConsoleConnection definition
+
+
+/**
+ * Create a Singleton instance of the SelectedQMFConsoleConnection class managing the 
+ * id="selected-qmf-console-connection" page.
+ */
+qmfui.SelectedQMFConsoleConnection = new function() {
+    var _index = null;
+    var _name = "";
+    var _url = "";
+
+    /**
+     * This method deletes the selected QMFConsoleConnection.
+     */
+    var deleteHandler = function() {
+        // Flag to check if the Console we're trying to delete is the currently selected/connected one.
+        var currentlySelected = ($("#qmf-console-selector input[checked]").val() == _index);
+
+        // Text for the confirm dialogue with additional wording if it's currently connected.
+        var confirmText = 'Delete QMF Connection "' + _name + '" to ' + _url + '?';
+        confirmText += currentlySelected ? "\nNote that this is the current active Connection, so deleting it will cause a reconnection to the default Connection." : ""
+
+        // Wrap in a timeout call because confirm doesn't play nicely with touchend and causes it to trigger twice.
+        // Calling confirm within a timeout ensures things are placed correctly onto the event queue.
+        setTimeout(function() {
+            if (confirm(confirmText) == false) {
+                return;
+            } else {
+                qmfui.Console.removeConsoleConnection(_index);
+                // If the QMF Console Connection being deleted is the currently connected one we stop the Console
+                // and establish a connection to the default QMF Console Connection.
+                if (currentlySelected) {
+                    qmfui.Console.stopConsole();
+                    qmfui.Console.startConsole(0); // Start the default QMF Console Connection.
+                }
+
+                iTablet.location.back(); // Navigate to the previous page.
+            }
+        }, 0);
+    };
+
+    var show = function() {
+        var location = iTablet.location;
+        var data = location.data;
+        if (data == null || location.hash != "#selected-qmf-console-connection") {
+            return;
+        }
+
+        // Get the selected QMFConsoleConnection
+        _index = parseInt(data.index); // The parseInt is important! Without it the index lookup gives odd results..
+        var qmfConsoleConnections = qmfui.Console.getConsoleConnectionList();
+        var qmfConsoleConnection = qmfConsoleConnections[_index];
+        _name = qmfConsoleConnection.name;
+        _url = qmfConsoleConnection.url;
+        var connectionOptions = qmfConsoleConnection.connectionOptions;
+
+        var eventsDisabled = false;
+        if (qmfConsoleConnection.disableEvents != null) {
+            eventsDisabled = qmfConsoleConnection.disableEvents;
+        }
+
+        // If the selected Console is the default one hide the delete button, otherwise show it.
+        if (_index == 0) {
+            $("#selected-qmf-console-connection .header a.delete").hide();
+        } else {
+            $("#selected-qmf-console-connection .header a.delete").show();
+        }
+
+        // Populate the page header with the Console Connection name/url.
+        var urlText = (_url == null || _url == "") ? "" : " (" + _url + ")";
+        var label = (_name == null || _name == "") ? urlText : _name + urlText;
+        $("#selected-qmf-console-connection .header h1").text(label);
+
+        $("#selected-qmf-console-connection-url p").text(((_url == "") ? 'default' : _url));
+        $("#selected-qmf-console-connection-name p").text(((_name == "") ? '""' : _name));
+        $("#selected-qmf-console-connection-events-disabled p").text(eventsDisabled);
+
+        if (_url == "") {
+            $("#selected-qmf-console-connection-default-info").show();
+        } else {
+            $("#selected-qmf-console-connection-default-info").hide();
+        }
+
+        if (connectionOptions == "") {
+            $("#selected-qmf-console-connection-connection-options").hide();
+        } else {
+            $("#selected-qmf-console-connection-connection-options textarea").val(util.stringify(connectionOptions));
+            $("#selected-qmf-console-connection-connection-options").show();
+        }
+    };
+
+    this.initialise = function() {
+        $("#selected-qmf-console-connection").unbind("show").bind("show", show);
+
+        // Using END_EV avoids the 300ms delay responding to anchor click events that occurs on mobile browsers.
+        $("#selected-qmf-console-connection .header a.delete").bind(qmfui.END_EV, deleteHandler);
+    };
+}; // End of qmfui.SelectedQMFConsoleConnection definition
+
+
+//-------------------------------------------------------------------------------------------------------------------
+//                                            Broker Information                                             
+//-------------------------------------------------------------------------------------------------------------------
+
+/**
+ * Create a Singleton instance of the Broker class managing the id="broker" page.
+ */
+qmfui.Broker = new function() {
+    /**
+     * Convert nanoseconds into hours, minutes and seconds.
+     */
+    var convertTime = function(ns) {
+        var milliSecs = ns/1000000;
+        var msSecs = (1000);
+        var msMins = (msSecs * 60);
+        var msHours = (msMins * 60);
+        var numHours = Math.floor(milliSecs/msHours);
+        var numMins = Math.floor((milliSecs - (numHours * msHours)) / msMins);
+        var numSecs = Math.floor((milliSecs - (numHours * msHours) - (numMins * msMins))/ msSecs);
+        numSecs = numSecs < 10 ? numSecs = "0" + numSecs : numSecs;
+        numMins = numMins < 10 ? numMins = "0" + numMins : numMins;
+
+        return (numHours + ":" + numMins + ":" + numSecs);
+    };
+
+    this.update = function() {
+        var broker = qmfui.Console.getBroker();
+
+        // Render the selected broker properties to #broker-list.
+        var keys = ["name", "version", "uptime", "port", "maxConns", "connBacklog", "dataDir",
+                    "mgmtPublish", "mgmtPubInterval", "workerThreads",
+                    /* 0.20 publishes many more stats so include them if available */
+                    "queueCount", "acquires", "releases", "abandoned", "abandonedViaAlt"];
+        iTablet.renderList($("#broker-list"), function(i) {
+            var key = keys[i];
+            var value = broker[key];
+            if (value == null) { // Only show statistics that are actually available (later brokers publish more stats)
+                return false;
+            } else {
+                if (key == "uptime") {
+                    value = convertTime(value); // Convert uptime to a more human readable value
+                }
+                return "<li><a href='#'>" + key + "<p>" + value + "</p></a></li>";
+            }
+        }, keys.length);
+
+        // Render a number of 0.20 statistics in their own subsections to improve readability.
+
+        // Render the Message Input Output Statistics.
+        if (broker.msgDepth == null) {
+            $("#broker-msgio-container").hide();
+        } else {
+            $("#broker-msgio-container").show();
+            keys = ["msgDepth", "msgTotalEnqueues", "msgTotalDequeues"];
+            iTablet.renderList($("#broker-msgio"), function(i) {
+                var key = keys[i];
+                return "<li><a href='#'>" + key + "<p>" + broker[key] + "</p></a></li>";
+            }, keys.length);
+        }
+
+        // Render the Byte Input Output Statistics.
+        if (broker.byteDepth == null) {
+            $("#broker-byteio-container").hide();
+        } else {
+            $("#broker-byteio-container").show();
+            keys = ["byteDepth", "byteTotalEnqueues", "byteTotalDequeues"];
+            iTablet.renderList($("#broker-byteio"), function(i) {
+                var key = keys[i];
+                return "<li><a href='#'>" + key + "<p>" + broker[key] + "</p></a></li>";
+            }, keys.length);
+        }
+
+        var hideDetails = $("#settings-hide-details")[0].checked;
+
+        // Render the Flow-to-disk Statistics.
+        if (broker.msgFtdDepth == null || hideDetails) {
+            $("#broker-flow-to-disk-container").hide();
+        } else {
+            $("#broker-flow-to-disk-container").show();
+            keys = ["msgFtdDepth", "msgFtdEnqueues", "msgFtdDequeues",
+                    "byteFtdDepth", "byteFtdEnqueues", "byteFtdDequeues"];
+            iTablet.renderList($("#broker-flow-to-disk"), function(i) {
+                var key = keys[i];
+                return "<li><a href='#'>" + key + "<p>" + broker[key] + "</p></a></li>";
+            }, keys.length);
+        }
+
+        // Render the Dequeue Details.
+        if (broker.discardsTtl == null || hideDetails) {
+            $("#broker-dequeue-container").hide();
+        } else {
+            $("#broker-dequeue-container").show();
+            keys = ["discardsTtl", "discardsRing", "discardsLvq", "discardsOverflow",
+                    "discardsSubscriber", "discardsPurge", "reroutes"];
+            iTablet.renderList($("#broker-dequeue"), function(i) {
+                var key = keys[i];
+                return "<li><a href='#'>" + key + "<p>" + broker[key] + "</p></a></li>";
+            }, keys.length);
+        }
+    };
+
+    /**
+     * This click handler is triggered by a click on the broker-log-level radio button. It invokes the QMF
+     * setLogLevel method to set the log level of the connected broker to debug or normal.
+     */
+    var setLogLevel = function(e) {
+        var level = $(e.target).val();
+
+        // Set to the QMF2 levels for broker debug and normal.
+        level = (level == "debug") ? "debug+:Broker" : "notice+";
+
+        var broker = qmfui.Console.getBroker(true); // Retrieve broker QmfConsoleData object.
+        broker.invokeMethod("setLogLevel", {"level": level}, function(data) {
+            if (data.error_text) {
+                alert(data.error_text);
+            }
+        });
+    };
+
+    this.initialise = function() {
+        // Explicitly using a click handler rather than change as it's possible that another instance has changed
+        // the log level so we may want to be able to reset it by clicking the currently selected level.
+        $("#broker-log-level li input").click(setLogLevel);
+    };
+}; // End of qmfui.Broker definition
+
+
+//-------------------------------------------------------------------------------------------------------------------
+//                                            Connection Information                                             
+//-------------------------------------------------------------------------------------------------------------------
+
+/**
+ * Create a Singleton instance of the Connections class managing the id="connections" page.
+ */
+qmfui.Connections = new function() {
+    var _connectionMap = {};   // Connections indexed by ObjectId.
+    var _sessionMap = {};      // Sessions indexed by ObjectId.
+    var _subscriptionMap = {}; // Subscriptions indexed by ObjectId.
+    var _queueToSubscriptionAssociations = {}; // 0..* association between Queue and Subscription keyed by Queue ID.
+
+    /**
+     * Return the Connection object indexed by QMF ObjectId.
+     */
+    this.getConnection = function(oid) {
+        return _connectionMap[oid];
+    };
+
+    /**
+     * Return the Session object indexed by QMF ObjectId.
+     */
+    this.getSession = function(oid) {
+        return _sessionMap[oid];
+    };
+
+    /**
+     * Return the Subscription object indexed by QMF ObjectId.
+     */
+    this.getSubscription = function(oid) {
+        return _subscriptionMap[oid];
+    };
+
+    /**
+     * Return the Subscription association List indexed by a queue's QMF ObjectId.
+     */
+    this.getQueueSubscriptions = function(oid) {
+        var subs = _queueToSubscriptionAssociations[oid];
+        return (subs == null) ? [] : subs; // If it's null set it to an empty array.
+    };
+
+    /**
+     * The Connections update method includes a number of subtle complexities due to the way QMF Management
+     * Objects are associated with each other. For example a Subscription is associated with a single
+     * Session however a Session may have zero or more Subscriptions, similarly a Session is associated with
+     * a single Connection but a Connection may have zero or more Sessions.
+     *
+     * The QMF Management Objects maintain the single unidirectional associations, which generally makes sense
+     * but an unfortunate side-effect of this is that if one wishes to obtain information about Sessions and
+     * Subscriptions related to a given Connection it's somewhat of a pain as one has to do a multi-pass dereference.
+     *
+     * This method does the dereferencing and creates a 0..* association to Session in each Connection object and
+     * a 0..* association to Subscription in each Session object so other pages can avoid their own dereferencing.
+     * N.B. these added associations are references to actual Session or Subscription objects and NOT via ObjectIds
+     * This is because these associations are not *really* QmfData object properties and only exist within the local
+     * memory space of this application, so using actual memory references avoids an additional dereference.
+     */
+    this.update = function() {
+        _subscriptionMap = {}; // Clear _subscriptionMap.
+        _sessionMap = {};      // Clear _sessionMap.
+        _queueToSubscriptionAssociations = {}; // Clear _queueToSubscriptionAssociations.
+
+        var subscriptions = qmfui.Console.getSubscriptions();
+        for (var i in subscriptions) {
+            var subscription = subscriptions[i];
+            var subscriptionId = subscription._object_id;
+            var sessionRef = subscription.sessionRef;
+            var queueRef = subscription.queueRef;
+
+            // Create the Session->Subscriptions association array and store it keyed by the sessionRef, when we go
+            // to store the actual Session we can retrieve the association and store it as a property of the Session.
+            if (_sessionMap[sessionRef] == null) {
+                _sessionMap[sessionRef] = [subscription];
+            } else {
+                _sessionMap[sessionRef].push(subscription);
+            }
+
+            // Create the Queue->Subscriptions association array and store it keyed by the queueRef, when we go
+            // to store the actual Session we can retrieve the association and store it as a property of the Session.
+            if (_queueToSubscriptionAssociations[queueRef] == null) {
+                _queueToSubscriptionAssociations[queueRef] = [subscription];
+            } else {
+                _queueToSubscriptionAssociations[queueRef].push(subscription);
+            }
+
+            _subscriptionMap[subscriptionId] = subscription; // Index subscriptions by ObjectId.
+        }
+
+        var connectionToSessionAssociations = {};
+        var sessions = qmfui.Console.getSessions();
+        for (var i in sessions) {
+            var session = sessions[i];
+            var sessionId = session._object_id;
+            var connectionRef = session.connectionRef;
+
+            var subs = _sessionMap[sessionId]; // Retrieve the association array and store it as a property.
+            subs = (subs == null) ? [] : subs; // If it's null set it to an empty array.
+            session._subscriptions = subs;
+
+            // Create the Connection->Sessions association array and store it keyed by the connectionRef, when we go to
+            // store the actual Connection we can retrieve the association and store it as a property of the Connection.
+            if (connectionToSessionAssociations[connectionRef] == null) {
+                connectionToSessionAssociations[connectionRef] = [session];
+            } else {
+                connectionToSessionAssociations[connectionRef].push(session);
+            }
+            _sessionMap[sessionId] = session; // Index sessions by ObjectId.
+        }
+
+        // Temporary connections map, we move active connections to this so deleted connections wither and die.
+        var temp = {};
+        var connections = qmfui.Console.getConnections();
+        iTablet.renderList($("#connections-list"), function(i) {
+            var connection = connections[i];
+            var connectionId = connection._object_id;
+
+            // Look up the previous value for the indexed connection using its objectId.
+            var prev = _connectionMap[connectionId];
+            var stats = (prev == null) ? new qmfui.Statistics(["msgsFromClient", "msgsToClient"]) : prev._statistics;
+            stats.put([connection.msgsFromClient, connection.msgsToClient, connection._update_ts]);
+            connection._statistics = stats;
+
+            // Retrieve the association array and store it as a property.
+            var sessions = connectionToSessionAssociations[connectionId];
+            sessions = (sessions == null) ? [] : sessions; // If it's null set it to an empty array.
+            connection._sessions = sessions;
+            temp[connectionId] = connection;
+
+            // Calculate the total number of subscriptions for this connection, this is useful because a count of
+            // zero indicates that a connection is probably a producer only connection.
+            var connectionSubscriptions = 0;
+            for (var i in connection._sessions) {
+                var session = connection._sessions[i];
+                connectionSubscriptions += session._subscriptions.length;
+            }
+
+            var address = connection.address + " (" + connection.remoteProcessName + ")";
+            if (connectionSubscriptions == 0) {
+                return "<li class='arrow'><a href='#selected-connection?id=" + connectionId + "'>" +  address + 
+                        "<p>No Subscriptions</p></a></li>";
+            } else {
+                return "<li class='arrow'><a href='#selected-connection?id=" + connectionId + "'>" +  address + 
+                        "</a></li>";
+            }
+        }, connections.length);
+
+        // Replace the saved statistics with the newly populated temp instance, which only has active objects
+        // moved to it, this means that any deleted objects are no longer present.
+        _connectionMap = temp;
+    };
+
+}; // End of qmfui.Connections definition
+
+
+/**
+ * Create a Singleton instance of the SelectedConnection class managing the id="selected-connection" page.
+ */
+qmfui.SelectedConnection = new function() {
+    var _sessions = []; // Populate this with the ID of matching sessions enabling navigation to sessions.
+
+    this.update = function() {
+        var location = iTablet.location;
+        var data = location.data;
+        if (data == null || location.hash != "#selected-connection") {
+            return;
+        }
+
+        // Get the latest statistics update of the selected connection object.
+        var connectionId = data.id;
+        var connection = qmfui.Connections.getConnection(connectionId);
+        if (connection == null) {
+            $("#resource-deleted").show();
+        } else {
+            $("#resource-deleted").hide();
+
+            var name = connection.address + " (" + connection.remoteProcessName + ")";
+            $("#selected-connection .header h1").text(name);
+
+            // Populate the back button with "Subscription" or "Connections" depending on context
+            var backText = data.fromSubscription ? "Subscrip..." : "Connect...";
+
+            // Using $("#selected-connection .header a").text(backText) wipes all child elements so use the following.
+            $("#selected-connection .header a")[0].firstChild.nodeValue = backText;
+
+            // Render the connection message statistics to #selected-connection-msgio
+            var keys = ["msgsFromClient", "msgsToClient"];
+            iTablet.renderList($("#selected-connection-msgio"), function(i) {
+                var key = keys[i];
+                return "<li class='arrow'><a href='#graphs?connectionId=" + connectionId + "&property=" + i + "'>" + 
+                        key + "<p>" + connection[key] + "</p></a></li>";
+            }, keys.length);
+
+            // Render the connection byte statistics to #selected-connection-byteio
+            keys = ["bytesFromClient", "bytesToClient"];
+            iTablet.renderList($("#selected-connection-byteio"), function(i) {
+                var key = keys[i];
+                return "<li><a href='#'>" + key + "<p>" + connection[key] + "</p></a></li>";
+            }, keys.length);
+
+            // Render the connection frame statistics to #selected-connection-frameio
+            keys = ["framesFromClient", "framesToClient"];
+            iTablet.renderList($("#selected-connection-frameio"), function(i) {
+                var key = keys[i];
+                return "<li><a href='#'>" + key + "<p>" + connection[key] + "</p></a></li>";
+            }, keys.length);
+
+            // Render selected general connection properties to #selected-connection-general.
+            keys = ["federationLink","SystemConnection","incoming", "authIdentity", "userProxyAuth",
+                    "saslMechanism", "saslSsf", "remotePid", "shadow", "closing", "protocol"];
+            iTablet.renderList($("#selected-connection-general"), function(i) {
+                var key = keys[i];
+                var value = connection[key];
+                if (value == null) {
+                    return false;
+                } else {
+                    return "<li><a href='#'>" + key + "<p>" + value + "</p></a></li>";
+                }
+            }, keys.length);
+
+            // Render links to the sessions associated with this connection.
+            _sessions = connection._sessions;
+            if (_sessions.length == 0) { // Show a message if there are no sessions at all
+                $("#selected-connection-subscribed-sessions").hide();
+                $("#selected-connection-subscribed-sessions").prev().hide();
+                $("#selected-connection-unsubscribed-sessions").show();
+                $("#selected-connection-unsubscribed-sessions").prev().show();
+                iTablet.renderList($("#selected-connection-unsubscribed-sessions"), function(i) {
+                    return "<li class='grey'><a href='#'>There are currently no sessions attached to " + 
+                            name + "</a></li>";
+                });
+            } else {
+                var subscribed = [];
+                var unsubscribed = [];
+                for (var i in _sessions) {
+                    var session = _sessions[i];
+                    var id = session._object_id;
+                    var subscriptionCount = session._subscriptions.length;
+                    if (subscriptionCount == 0) {
+                        unsubscribed.push("<li><a href='#'>" + session.name + "</a></li>");
+                    } else {
+                        var plural = subscriptionCount > 1 ? " Subscriptions" : " Subscription";
+                        subscribed.push("<li class='multiline arrow'><a href='#connection-subscriptions?id=" + id + "'><div>" + 
+                                        session.name + "<p class='sub'>" + subscriptionCount + plural + "</p></div></a></li>");
+                    }
+                }
+
+                if (subscribed.length > 0) {
+                    $("#selected-connection-subscribed-sessions").show();
+                    $("#selected-connection-subscribed-sessions").prev().show();
+                    iTablet.renderList($("#selected-connection-subscribed-sessions"), function(i) {
+                        return subscribed[i];
+                    }, subscribed.length);
+                } else {
+                    $("#selected-connection-subscribed-sessions").hide();
+                    $("#selected-connection-subscribed-sessions").prev().hide();
+                }
+
+                if (unsubscribed.length > 0) {
+                    $("#selected-connection-unsubscribed-sessions").show();
+                    $("#selected-connection-unsubscribed-sessions").prev().show();
+                    iTablet.renderList($("#selected-connection-unsubscribed-sessions"), function(i) {
+                        return unsubscribed[i];
+                    }, unsubscribed.length);
+                } else {
+                    $("#selected-connection-unsubscribed-sessions").hide();
+                    $("#selected-connection-unsubscribed-sessions").prev().hide();
+                }
+            }
+        }
+    };
+
+    this.initialise = function() {
+        $("#selected-connection").unbind("show").bind("show", qmfui.SelectedConnection.update);
+    };
+}; // End of qmfui.SelectedConnection definition
+
+
+/**
+ * Create a Singleton instance of the ConnectionSubscriptions class managing the id="connection-subscriptions" page.
+ * This page is slightly different than most of the others as there can be multiple subscriptions and thus multiple
+ * arbitrary lists. Most pages have tried to reuse HTML list items for efficiency but in this page we clear and
+ * regenerate the contents of the page div each update as it's simpler than attempting to reuse elements.
+ */
+qmfui.ConnectionSubscriptions = new function() {
+    this.update = function() {
+        var location = iTablet.location;
+        var data = location.data;
+        if (data == null || location.hash != "#connection-subscriptions") {
+            return;
+        }
+
+        var sessionId = data.id;
+        var session = qmfui.Connections.getSession(sessionId);
+        if (session == null) {
+            $("#resource-deleted").show();
+        } else {
+            $("#resource-deleted").hide();
+
+            var hideQmfObjects = $("#settings-hide-qmf-objects")[0].checked;
+
+            // Populate the page header with the session name.
+            $("#connection-subscriptions .header h1").text(session.name);
+
+            var subscriptions = session._subscriptions;
+            var length = subscriptions.length;
+
+            var page = $("#connection-subscriptions .page");
+            page.children().remove(); // Clear the contents of the page div.
+
+            for (var i = 0; i < length; i++) {
+                var subscription = subscriptions[i];
+                var id = subscription.queueRef;
+                var queue = qmfui.Queues.getQueue(id);
+
+                if (i == 0) {
+                    page.append("<h1 class='first'>Subscription 1</h1>");
+                } else {
+                    page.append("<h1>Subscription " + (i + 1) + "</h1>");
+                }
+
+                var name = $("<ul id='connection-subscription-name" + i + "' class='list'></ul>");
+                page.append(name);
+
+                // Render the associated queue name to #connection-subscription-name.
+                var isQmfQueue = queue._isQmfQueue;
+                iTablet.renderList(name, function(i) {
+                    // If the associated Queue is a QNF Queue and hideQmfObjects has been selected render the
+                    // Queue name grey and make it non-navigable otherwise render normally.
+                    if (isQmfQueue && hideQmfObjects) {
+                        return "<li class='grey'><a href='#selected-queue?id=" + id + "&fromSubscriptions=true'>" + 
+                                queue.name + "</a></li>";
+                    } else {
+                        return "<li class='arrow'><a href='#selected-queue?id=" + id + "&fromSubscriptions=true'>" + 
+                               queue.name + "</a></li>";
+                    }
+                });
+
+                page.append("<p/>");
+
+                var list = $("<ul id='connection-subscription" + i + "' class='list'></ul>");
+                page.append(list);
+
+                // Render the useful subscription properties to #connection-subscriptions-list.
+                var keys = ["delivered", "browsing", "acknowledged", "exclusive", "creditMode"];
+                iTablet.renderList(list, function(i) {
+                    var key = keys[i];
+                    return "<li><a href='#'>" + key + "<p>" + subscription[key] + "</p></a></li>";
+                }, keys.length);
+            }
+
+            $("#connection-subscriptions").trigger("refresh"); // Make sure touch scroller is up-to-date.
+        }
+    };
+
+    this.initialise = function() {
+        $("#connection-subscriptions").unbind("show").bind("show", qmfui.ConnectionSubscriptions.update);
+    };
+}; // End of qmfui.ConnectionSubscriptions definition
+
+
+//-------------------------------------------------------------------------------------------------------------------
+//                                              Exchange Information                                             
+//-------------------------------------------------------------------------------------------------------------------
+
+/**
+ * Create a Singleton instance of the Exchanges class managing the id="exchanges" page.
+ */
+qmfui.Exchanges = new function() {
+    var QMF_EXCHANGES = {"qmf.default.direct": true, "qmf.default.topic": true, "qpid.management": true};
+    var _exchangeNameMap = {}; // Exchanges indexed by name.
+    var _exchangeMap = {};     // Exchanges indexed by ObjectId.
+
+    /**
+     * Return the Exchange object for the given QMF ObjectId. The Exchange object contains the latest update
+     * of the given object and a RingBuffer containing previous values of key statistics over a 24 hour period.
+     */
+    this.getExchange = function(oid) {
+        return _exchangeMap[oid];
+    };
+
+    /**
+     * Return the Exchange object with the given name. The Exchange object contains the latest update
+     * of the given object and a RingBuffer containing previous values of key statistics over a 24 hour period.
+     */
+    this.getExchangeByName = function(name) {
+        return _exchangeNameMap[name];
+    };
+
+    this.update = function() {
+        var hideQmfObjects = $("#settings-hide-qmf-objects")[0].checked;
+
+        // We move active exchanges to temp so deleted exchanges wither and die. We can't just do _exchangeMap = {}
+        // as we need to retrieve and update statistics from any active (non-deleted) exchange.
+        var temp = {};
+        _exchangeNameMap = {}; // We can simply clear the map of exchanges indexed by name though.
+        var exchanges = qmfui.Console.getExchanges();
+        iTablet.renderList($("#exchanges-list"), function(i) {
+            var exchange = exchanges[i];
+
+            // Look up the previous value for the indexed exchange using its objectId.
+            var prev = _exchangeMap[exchange._object_id];
+            var stats = (prev == null) ? new qmfui.Statistics(["msgReceives", "msgRoutes", "msgDrops"]) : 
+                                         prev._statistics;
+
+            stats.put([exchange.msgReceives, exchange.msgRoutes, exchange.msgDrops, exchange._update_ts]);
+            exchange._statistics = stats;
+
+            var id = exchange._object_id;
+            temp[id] = exchange;
+
+            var name = exchange.name;
+            name = (name == "") ? "'' (default direct)" : name;
+
+            _exchangeNameMap[name] = exchange;
+
+            if (QMF_EXCHANGES[name] && hideQmfObjects) {
+                return false; // Filter out any QMF related exchanges if the settings filter is checked.
+            } else {
+                return "<li class='arrow'><a href='#selected-exchange?id=" + id + "'>" + name + 
+                        "<p>" + exchange.type + "</p></a></li>";
+            }
+        }, exchanges.length);
+
+        // Replace the saved statistics with the newly populated temp instance, which only has active objects
+        // moved to it, this means that any deleted objects are no longer present.
+        _exchangeMap = temp;
+    };
+
+}; // End of qmfui.Exchanges definition
+
+
+/**
+ * Create a Singleton instance of the AddExchange class managing the id="add-exchange" page.
+ */
+qmfui.AddExchange = new function() {
+    // Protected Exchanges are exchanges that we don't permit binding to - default direct and the QMF exchanges.
+    var PROTECTED_EXCHANGES = {"": true, "''": true, "qmf.default.direct": true,
+                               "qmf.default.topic": true, "qpid.management": true};
+
+    var _alternateExchangeName = "None (default)";
+
+    var submit = function() {
+        var properties = {};
+
+        if ($("#exchange-durable")[0].checked) {
+            properties["durable"] = true;
+        } else {
+            properties["durable"] = false;
+        }
+
+        if ($("#sequence")[0].checked) {
+            properties["qpid.msg_sequence"] = 1;
+        }
+
+        if ($("#ive")[0].checked) {
+            properties["qpid.ive"] = 1;
+        }
+
+        properties["exchange-type"] = $("#exchange-type input[checked]").val();
+
+        if (_alternateExchangeName != "None (default)") {
+            properties["alternate-exchange"] = _alternateExchangeName;
+        }
+
+        var exchangeName = $("#exchange-name");
+        var name = exchangeName.val();
+        if (name == "") {
+            exchangeName.addClass("error");
+        } else {
+            exchangeName.removeClass("error");
+
+            var arguments = {"type": "exchange", "name": name, "properties": properties};
+            var broker = qmfui.Console.getBroker(true); // Retrieve broker QmfConsoleData object.
+            broker.invokeMethod("create", arguments, function(data) {
+                if (data.error_text) {
+                    alert(data.error_text);
+                } else {
+                    iTablet.location.back();
+                }
+            });
+        }
+    };
+
+    /**
+     * This method renders dynamic content in the AdditionalOptions page, it's really there to make sure that
+     * the AlternateExchange name gets properly rendered when it gets selected in the 
+     * "#add-exchange-select-alternate-exchange" page.
+     */
+    var showAdditionalOptions = function() {
+        _alternateExchangeName = $("#add-exchange-select-alternate-exchange-list input[checked]").val();
+        var exchange = qmfui.Exchanges.getExchangeByName(_alternateExchangeName);
+        var exchangeType = (exchange == null) ? "" : exchange.type; 
+        var typeText = (exchangeType == "") ? "" : " (" + exchangeType + ")";
+        $("#add-exchange-additional-alternate-exchange-name p").text(_alternateExchangeName + typeText);
+    };
+
+    /**
+     * This method renders the reroute-messages-select-exchange page when its show event is triggered. This page
+     * needs a show handler because it needs to be dynamically updated as the exchanges available in the radio
+     * button selection set will vary as exchanges get added or deleted.
+     */
+    var showSelectExchange = function() {
+        var exchanges = qmfui.Console.getExchanges();
+        var filteredExchanges = [];
+        var currentlySelected = $("#add-exchange-additional-alternate-exchange-name p").text();
+
+        // Check the status of any Exchange that the user may have previously selected prior to hitting "Done".
+        if (currentlySelected != "None (default)") {
+            // Remove the exchange type from the text before testing.
+            currentlySelected = currentlySelected.split(" (")[0];
+            if (qmfui.Exchanges.getExchangeByName(currentlySelected) == null) { // Check if it has been deleted.
+                alert('The currently selected Exchange "' + currentlySelected + '" appears to have been deleted.');
+                currentlySelected = "None (default)";
+            }
+        }
+
+        var checked = (currentlySelected == "None (default)") ? "checked" : "";
+        filteredExchanges.push("<li><label for='add-exchange-select-alternate-exchange-exchangeNone'>None (default)</label><input type='radio' id='add-exchange-select-alternate-exchange-exchangeNone' name='add-exchange-select-alternate-exchange' value='None (default)' " + checked + "/></li>");
+
+        var length = exchanges.length;
+        for (var i = 0; i < length; i++) {
+            var name = exchanges[i].name;
+            var type = exchanges[i].type;
+            checked = (currentlySelected == name) ? "checked" : "";
+
+            // Filter out default direct and QMF exchanges as we don't want to allow binding to those.
+            if (!PROTECTED_EXCHANGES[name]) {
+                filteredExchanges.push("<li><label for='add-exchange-select-alternate-exchange-exchange" + i + "'>" + name + " (" + type + ")</label><input type='radio' id='add-exchange-select-alternate-exchange-exchange" + i + "' name='add-exchange-select-alternate-exchange' value='" + name + "' " + checked + "/></li>");
+            }
+        }
+
+        iTablet.renderList($("#add-exchange-select-alternate-exchange-list"), function(i) {
+            return filteredExchanges[i];
+        }, filteredExchanges.length);
+    };
+
+    var changeType = function(e) {
+        var jthis = $(e.target);
+        if (jthis.attr("checked")) {
+            $("#add-exchange-exchange-type p").text(jthis.siblings("label").text());
+        }
+    };
+
+    this.initialise = function() {
+        $("#add-exchange-additional").unbind("show").bind("show", showAdditionalOptions);
+        $("#add-exchange-select-alternate-exchange").unbind("show").bind("show", showSelectExchange);
+
+        // Using END_EV avoids the 300ms delay responding to anchor click events that occurs on mobile browsers.
+        $("#add-exchange .right.button").bind(qmfui.END_EV, submit);
+        $("#exchange-type input").change(changeType);
+
+        // Always initialise to default value irrespective of browser caching.
+        $("#direct").click();
+    };
+}; // End of qmfui.AddExchange definition
+
+
+/**
+ * Create a Singleton instance of the SelectedExchange class managing the id="selected-exchange" page.
+ */
+qmfui.SelectedExchange = new function() {
+    // System Exchanges are exchanges that should not be deleted so we hide the delete button for those Exchanges.
+    var SYSTEM_EXCHANGES = {"''": true, "amq.direct": true, "amq.fanout": true, "amq.match": true, "amq.topic": true, 
+                            "qmf.default.direct": true, "qmf.default.topic": true, "qpid.management": true};
+
+    // Protected Exchanges are exchanges that we don't binding to - default direct and the QMF exchanges.
+    var PROTECTED_EXCHANGES = {"": true, "''": true, "qmf.default.direct": true,
+                               "qmf.default.topic": true, "qpid.management": true};
+    var _name = "";
+
+    /**
+     * This method deletes the selected exchange by invoking the QMF delete method.
+     */
+    var deleteHandler = function() {
+        // Wrap in a timeout call because confirm doesn't play nicely with touchend and causes it to trigger twice.
+        // Calling confirm within a timeout ensures things are placed correctly onto the event queue.
+        setTimeout(function() {
+            if (confirm('Delete Exchange "' + _name + '"?') == false) {
+                return;
+            } else {
+                var arguments = {"type": "exchange", "name": _name};
+                var broker = qmfui.Console.getBroker(true); // Retrieve broker QmfConsoleData object.
+                broker.invokeMethod("delete", arguments, function(data) {
+                    if (data.error_text) {
+                        alert(data.error_text);
+                    } else {
+                        iTablet.location.back();
+                    }
+                });
+            }
+        }, 0);
+    };
+
+    this.update = function() {
+        var location = iTablet.location;
+        var data = location.data;
+        if (data == null || location.hash != "#selected-exchange") {
+            return;
+        }
+
+        // Get the latest update of the selected exchange object.
+        var exchangeId = data.id;
+        var exchange = qmfui.Exchanges.getExchange(exchangeId);
+        if (exchange == null) {
+            $("#resource-deleted").show();
+        } else {
+            $("#resource-deleted").hide();
+
+            // Populate the page header with the exchange name and type
+            _name = exchange.name;
+            _name = (_name == "") ? "''" : _name;
+            $("#selected-exchange .header h1").text(_name + " (" + exchange.type + ")");
+
+            // If the selected Exchange is a system Exchange hide the delete button, otherwise show it.
+            if (SYSTEM_EXCHANGES[_name]) {
+                $("#selected-exchange .header a.delete").hide();
+            } else {
+                $("#selected-exchange .header a.delete").show();
+            }
+
+            // Populate the back button with "Exchanges" or "Bindings" depending on context
+            var backText = "Exchan...";
+            if (data.bindingKey) {
+                backText = "Bindings";
+                // Ensure that the binding that linked to this page gets correctly highlighted if we navigate back.
+                // If default direct add an extra "&" otherwise all ObjectIds will match in a simple string search.
+                qmfui.Bindings.setHighlightedBinding(exchangeId + (_name == "''" ? "&" : ""), 
+                                                     data.bindingKey);            
+            }
+
+            // Using $("#selected-exchange .header a").text(backText) wipes all child elements so use the following.
+            $("#selected-exchange .header a")[0].firstChild.nodeValue = backText;
+
+            // Render the bindingCount to #selected-exchange-bindings
+            if (exchange.bindingCount == 0) {
+                // We don't allow bindings to be added to default direct or QMF Exchanges.
+                if (PROTECTED_EXCHANGES[_name]) {
+                    iTablet.renderList($("#selected-exchange-bindings"), function(i) {
+                        return "<li class='grey'><a href='#'>There are currently no bindings to " + _name + "</a></li>";
+                    });
+                } else {
+                    iTablet.renderList($("#selected-exchange-bindings"), function(i) {
+                        return "<li class='pop'><a href='#add-binding?exchangeId=" + exchangeId + "'>Add Binding</a></li>";
+                    });
+                }
+            } else {
+                iTablet.renderList($("#selected-exchange-bindings"), function(i) {
+                    return "<li class='arrow'><a href='#bindings?exchangeId=" + exchangeId + "'>bindingCount<p>" + 
+                            exchange.bindingCount + "</p></a></li>";
+                });
+            }
+
+            // Render the exchange statistics to #selected-exchange-msgio
+            var keys = ["msgReceives", "msgRoutes", "msgDrops"];
+            iTablet.renderList($("#selected-exchange-msgio"), function(i) {
+                var key = keys[i];
+                return "<li class='arrow'><a href='#graphs?exchangeId=" + exchangeId + "&property=" + i + "'>" + 
+                        key + "<p>" + exchange[key] + "</p></a></li>";
+            }, keys.length);
+
+            // Render the exchange statistics to #selected-exchange-byteio
+            keys = ["byteReceives", "byteRoutes", "byteDrops"];
+            iTablet.renderList($("#selected-exchange-byteio"), function(i) {
+                var key = keys[i];
+                return "<li><a href='#'>" + key + "<p>" + exchange[key] + "</p></a></li>";
+            }, keys.length);
+
+            // Render selected general exchange properties and exchange.declare arguments to #selected-exchange-general.
+            keys = ["durable", "autoDelete", "producerCount"];
+            var general = [];
+
+            // Render any alternate exchange that may be attached to this exchange. Note that exchange.altExchange
+            // is a reference property so we need to dereference it before extracting the exchange name.
+            var altExchange = qmfui.Exchanges.getExchange(exchange.altExchange);
+            if (altExchange) {
+                general.push("<li><a href='#'>altExchange<p>" + altExchange.name + "</p></a></li>");
+            }
+
+            for (var i in keys) { // Populate with selected properties.
+                var key = keys[i];
+                general.push("<li><a href='#'>" + key + "<p>" + exchange[key] + "</p></a></li>");
+            }
+            for (var i in exchange.arguments) { // Populate with arguments.
+                general.push("<li><a href='#'>" + i + "<p>" + exchange.arguments[i] + "</p></a></li>");
+            }
+
+            iTablet.renderList($("#selected-exchange-general"), function(i) {
+                return general[i];
+            }, general.length);
+        }
+    };
+
+    this.initialise = function() {
+        $("#selected-exchange").unbind("show").bind("show", qmfui.SelectedExchange.update);
+
+        // Using END_EV avoids the 300ms delay responding to anchor click events that occurs on mobile browsers.
+        $("#selected-exchange .header a.delete").bind(qmfui.END_EV, deleteHandler);
+    };
+}; // End of qmfui.SelectedExchange definition
+
+
+//-------------------------------------------------------------------------------------------------------------------
+//                                               Queue Information                                               
+//-------------------------------------------------------------------------------------------------------------------
+
+/**
+ * Create a Singleton instance of the Queues class managing the id="queues" page.
+ */
+qmfui.Queues = new function() {
+    var QMF_EXCHANGES = { "qmf.default.direct": true, "qmf.default.topic": true, "qpid.management": true};
+    var _queueNameMap = {}; // Queues indexed by name.
+    var _queueMap = {};     // Queues indexed by ObjectId.
+
+    /**
+     * Return the Queue object for the given QMF ObjectId. The Queue object contains the latest update
+     * of the given object and a RingBuffer containing previous values of key statistics over a 24 hour period.
+     */
+    this.getQueue = function(oid) {
+        return _queueMap[oid];
+    };
+
+    /**
+     * Return the Queue object with the given name. The Queue object contains the latest update
+     * of the given object and a RingBuffer containing previous values of key statistics over a 24 hour period.
+     */
+    this.getQueueByName = function(name) {
+        return _queueNameMap[name];
+    };
+
+    this.update = function() {
+        var hideQmfObjects = $("#settings-hide-qmf-objects")[0].checked;
+
+        // We move active queues to temp so deleted queues wither and die. We can't just do _queueMap = {} as we
+        // need to retrieve and update statistics from any active (non-deleted) queue.
+        var temp = {}; 
+        _queueNameMap = {}; // We can simply clear the map of queues indexed by name though.
+        var queues = qmfui.Console.getQueues();
+        iTablet.renderList($("#queues-list"), function(i) {
+            var queue = queues[i];
+            var objectId = queue._object_id;
+
+            // Look up the previous value for the indexed queue using its objectId.
+            var prev = _queueMap[objectId];
+            var stats = (prev == null) ? new qmfui.Statistics(["msgDepth", "msgTotalEnqueues", "msgTotalDequeues"]) : 
+                                         prev._statistics;
+
+            stats.put([queue.msgDepth, queue.msgTotalEnqueues, queue.msgTotalDequeues, queue._update_ts]);
+
+            // Add statistics as an additional property of the queue object.
+            queue._statistics = stats;
+
+            /*
+             * Check if the queue is associated with a QMF exchange. Because we need to iterate through the
+             * bindings in an inner loop we only want to do this once for each queue, however we can't only
+             * do it when the queue is missing from the _queueMap, because there's a race condition with QMF
+             * properties getting asynchronously returned, so it's possible for a queue to exist without a
+             * binding object referencing it existing for a short period, so we don't add the _isQmfQueue
+             * property until a binding referencing the queue actually exists.
+             */
+            if (prev == null || prev._isQmfQueue == null) {
+                var bindings = qmfui.Console.getBindings(); // Get the cached list of binding objects.
+                for (var i in bindings) {
+                    var b = bindings[i];
+                    if (b.queueRef == objectId) {
+                        var exchange = qmfui.Exchanges.getExchange(b.exchangeRef); // Dereference the exchangeRef.
+                        if (exchange != null) {
+                            // If a binding referencing the queue and an exchange exists add isQmfQueue state as an 
+                            // additional property of the queue object.
+                            if (QMF_EXCHANGES[exchange.name]) {
+                                queue._isQmfQueue = true;
+                                break;
+                            } else {
+                                queue._isQmfQueue = false;
+                            }
+                        }
+                    }
+                }
+            } else {
+                // Add previous status of _isQmfQueue as an additional property of the queue object.
+                queue._isQmfQueue = prev._isQmfQueue;
+            }
+
+            temp[objectId] = queue;
+            _queueNameMap[queue.name] = queue;
+
+            if (queue._isQmfQueue && hideQmfObjects) {
+                return false; // Filter out any QMF related queues if the settings filter is checked.
+            } else {
+                return "<li class='arrow'><a href='#selected-queue?id=" + objectId + "'>" + queue.name + "</a></li>";
+            }
+        }, queues.length);
+
+        // Replace the saved statistics with the newly populated temp instance, which only has active objects
+        // moved to it, this means that any deleted objects are no longer present.
+        _queueMap = temp;
+    };
+
+}; // End of qmfui.Queues definition
+
+
+/**
+ * Create a Singleton instance of the AddQueue class managing the id="add-queue" page.
+ */
+qmfui.AddQueue = new function() {
+    // Protected Exchanges are exchanges that we don't permit binding to - default direct and the QMF exchanges.
+    var PROTECTED_EXCHANGES = {"": true, "''": true, "qmf.default.direct": true,
+                               "qmf.default.topic": true, "qpid.management": true};
+    var _properties = {};
+    var _alternateExchangeName = "None (default)";
+
+    var parseIntegerProperty = function(selector, name) {
+        var value = $(selector).removeClass("error").val();
+        if (value == "") {
+            return true; // "" doesn't populate the property, but it's still valid so return true;
+        } else {
+            if (value.search(/[kKmMgG]/) == (value.length - 1)) { // Does it end in K/M/G
+                _properties[name] = value;
+                return true;
+            } else {
+                var integer = parseInt(value);
+                if (isNaN(integer)) {
+                    $(selector).addClass("error");
+                    return false;
+                } else {
+                    _properties[name] = integer;
+                    return true;
+                }
+            }
+        }
+    };
+
+    var submit = function() {
+        _properties = {};
+
+        if (!parseIntegerProperty("#max-queue-size", "qpid.max_size")) {
+            return;
+        } else if (!parseIntegerProperty("#max-queue-count", "qpid.max_count")) {
+            return;
+        } else if (!parseIntegerProperty("#flow-stop-size", "qpid.flow_stop_size")) {
+            return;
+        } else if (!parseIntegerProperty("#flow-stop-count", "qpid.flow_stop_count")) {
+            return;
+        } else if (!parseIntegerProperty("#flow-resume-size", "qpid.flow_resume_size")) {
+            return;
+        } else if (!parseIntegerProperty("#flow-resume-count", "qpid.flow_resume_count")) {
+            return;
+        }
+
+        if ($("#queue-durable")[0].checked) {
+            _properties["durable"] = true;
+            if (!parseIntegerProperty("#file-size", "qpid.file_size")) {
+                return;
+            } else if (!parseIntegerProperty("#file-count", "qpid.file_count")) {
+                return;
+            }
+
+            if ($("#queue-cluster-durable")[0].checked) {
+                _properties["qpid.persist_last_node"] = 1;
+            }
+        } else {
+            _properties["durable"] = false;
+        }
+
+        var limitPolicy = $("#limit-policy input[checked]").val();
+        if (limitPolicy != "none") {
+            _properties["qpid.policy_type"] = limitPolicy;
+        }
+
+        var orderingPolicy = $("#ordering-policy input[checked]").val();
+        if (orderingPolicy == "lvq") {
+            _properties["qpid.last_value_queue"] = 1;
+        } else if (orderingPolicy == "lvq-no-browse") {
+            _properties["qpid.last_value_queue_no_browse"] = 1;
+        }
+
+        if (!parseIntegerProperty("#generate-queue-events input[checked]", "qpid.queue_event_generation")) {
+            return;
+        }
+
+        if (_alternateExchangeName != "None (default)") {
+            _properties["alternate-exchange"] = _alternateExchangeName;
+        }
+
+        var queueName = $("#queue-name");
+        var name = queueName.val();
+        if (name == "") {
+            queueName.addClass("error");
+        } else {
+            queueName.removeClass("error");
+
+            var arguments = {"type": "queue", "name": name, "properties": _properties};
+            var broker = qmfui.Console.getBroker(true); // Retrieve broker QmfConsoleData object.
+            broker.invokeMethod("create", arguments, function(data) {
+                if (data.error_text) {
+                    alert(data.error_text);
+                } else {
+                    iTablet.location.back();
+                }
+            });
+        }
+    };
+
+    /**
+     * This method renders dynamic content in the AdditionalOptions page, it's really there to make sure that
+     * the AlternateExchange name gets properly rendered when it gets selected in the 
+     * "#add-exchange-select-alternate-exchange" page.
+     */
+    var showAdditionalOptions = function() {
+        _alternateExchangeName = $("#add-queue-select-alternate-exchange-list input[checked]").val();
+        var exchange = qmfui.Exchanges.getExchangeByName(_alternateExchangeName);
+        var exchangeType = (exchange == null) ? "" : exchange.type; 
+        var typeText = (exchangeType == "") ? "" : " (" + exchangeType + ")";
+        $("#add-queue-additional-alternate-exchange-name p").text(_alternateExchangeName + typeText);
+    };
+
+    /**
+     * This method renders the reroute-messages-select-exchange page when its show event is triggered. This page
+     * needs a show handler because it needs to be dynamically updated as the exchanges available in the radio
+     * button selection set will vary as exchanges get added or deleted.
+     */
+    var showSelectExchange = function() {
+        var exchanges = qmfui.Console.getExchanges();
+        var filteredExchanges = [];
+        var currentlySelected = $("#add-queue-additional-alternate-exchange-name p").text();
+
+        // Check the status of any Exchange that the user may have previously selected prior to hitting "Done".
+        if (currentlySelected != "None (default)") {
+            // Remove the exchange type from the text before testing.
+            currentlySelected = currentlySelected.split(" (")[0];
+            if (qmfui.Exchanges.getExchangeByName(currentlySelected) == null) { // Check if it has been deleted.
+                alert('The currently selected Exchange "' + currentlySelected + '" appears to have been deleted.');
+                currentlySelected = "None (default)";
+            }
+        }
+
+        var checked = (currentlySelected == "None (default)") ? "checked" : "";
+        filteredExchanges.push("<li><label for='add-queue-select-alternate-exchange-exchangeNone'>None (default)</label><input type='radio' id='add-queue-select-alternate-exchange-exchangeNone' name='add-queue-select-alternate-exchange' value='None (default)' " + checked + "/></li>");
+
+        var length = exchanges.length;
+        for (var i = 0; i < length; i++) {
+            var name = exchanges[i].name;
+            var type = exchanges[i].type;
+            checked = (currentlySelected == name) ? "checked" : "";
+
+            // Filter out default direct and QMF exchanges as we don't want to allow binding to those.
+            if (!PROTECTED_EXCHANGES[name]) {
+                filteredExchanges.push("<li><label for='add-queue-select-alternate-exchange-exchange" + i + "'>" + name + " (" + type + ")</label><input type='radio' id='add-queue-select-alternate-exchange-exchange" + i + "' name='add-queue-select-alternate-exchange' value='" + name + "' " + checked + "/></li>");
+            }
+        }
+
+        iTablet.renderList($("#add-queue-select-alternate-exchange-list"), function(i) {
+            return filteredExchanges[i];
+        }, filteredExchanges.length);
+    };
+
+    var changeLimitPolicy = function(e) {
+        var jthis = $(e.target);
+        if (jthis.attr("checked")) {
+            $("#add-queue-limit-policy p").text(jthis.siblings("label").text());
+        }
+    };
+
+    var changeOrderingPolicy = function(e) {
+        var jthis = $(e.target);
+        if (jthis.attr("checked")) {
+            var value = jthis.attr("value");
+            if (value == "fifo") {
+                $("#add-queue-ordering-policy p").text("Fifo (default)");   
+            } else if (value == "lvq") {
+                $("#add-queue-ordering-policy p").text("LVQ");   
+            } else if (value == "lvq-no-browse") {
+                $("#add-queue-ordering-policy p").text("LVQ No Browse");   
+            }
+        }
+    };
+
+    var changeQueueEventGeneration = function(e) {
+        var jthis = $(e.target);
+        if (jthis.attr("checked")) {
+            $("#add-queue-generate-queue-events p").text(jthis.siblings("label").text());
+        }
+    };
+
+    var changeDurable = function(e) {
+        var durable = $("#queue-durable")[0].checked;

[... 1662 lines stripped ...]


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org