(function() { /** * Ext.space.Window is an API that allows you to create child web views and do * simple management with them (e.g., open, close, navigate). * * To create a child web view: * * var child = window.open("http://example.com/"); * * child.open("http://another.example.com/somewhere/somehow.html"); * // ... later: * child.close(); * * // close all open children * Ext.space.Window.closeAll(); * */Ext.define("Ext.space.Window", { singleton: true, /** * Internal collection of child web views, by name * * @type {Object} * @private */ namedWindows: null, /** * Internal collection of child web views, by ID * * @type {Object} * @private */ windows: null, /** * @private */ _ready: null, /** * @private */ constructor: function() { this.namedWindows = {}; this.windows = {}; this._ready = new Ext.space.Promise(); Ext.onSpaceReady().then(this.init.bind(this)); }, /** * @private */ init: function() { // populate our list, in case we reloaded the app after creating some children this.getChildren().then(function(children) { this._ready.fulfill(); }.bind(this)); return this._ready; }, /** * Transform from a window.open()-style comma separated string of features to * a regular object hash. * * It turns something like "foo=bar,spam=eggs" into {foo: "bar", spam: "eggs"}, * but note that it doesn't normalize different styles of boolean values, so, * e.g., "true", "TRUE", "on", 1, etc. don't all get turned to a boolean true * or anything like that. * * @private * @param {String|Object} featuresString Comma separated features string, or an * object hash that will be passed through unmodified. * @return {Object} Object hash version of the features specified. */ _featuresToObject: function(featuresString) { var features, vars; if (!featuresString) { // default features is just empty features = {}; } else if (typeof featuresString == "string") { // convert it to an object; note that we currently don't bother // normalizing variations like "on", "true", 1, etc., into standardized // boolean values features = {}; vars = featuresString.split(",").map(function(s) { return s.split("="); }); vars.forEach(function(prop) { features[prop[0]] = (prop.length > 1) ? prop[1] : true; }); } else { // actually a hash already features = featuresString; } return features; }, /** * Open a child web view. * * In Sencha Web Application Client, window.open() is aliased to this function, * so while you can call the explicitly namespaced version here, typically you * just do something like: * * var child = window.open("http://example.com/"); * * @param {String} url URL to open * @param {String} name (optional) Application-specific name to give the child * web view. Just like in a regular browser context, calling * window.open twice with the same name will reuse the same * child web view. Similarly, specifying the name as "_blank" * will always open a new child web view. * @param {String|Object} featuresStringOrObject Comma separated features string of the * form normally expected by window.open ("foo=bar,spam=eggs", etc...), * or an object hash containing the same information ({foo: "bar", spam: "eggs"}). * @return {Ext.space.window.Child} Child web view, or null if the URL was invalid. */ open: function(url, name, featuresStringOrObject) { // // TODO: support animations, and document them above // var child, features, isNamed = false; if (url && typeof url == "string") { features = this._featuresToObject(featuresStringOrObject); isNamed = !!name && name != "_blank"; if (isNamed && this.namedWindows[name]) { // window so named exists; reuse it child = this.namedWindows[name]; this.navigate(child, url); } else { // create a new one child = new Ext.space.window.Child(url, isNamed ? name : null, features); // cache by name, if necessary if (isNamed) { this.namedWindows[name] = child; } // cache by id, once we have it child.getId().then(function() { this.windows[child.id] = child; }.bind(this)); } return child; } else { return null; } }, /** * Fetch an array of child web views currently open. * * @return {Ext.space.Promise} Promise that resolves with an array of Ext.space.window.Child objects */ getChildren: function() { var result = new Ext.space.Promise(); var self = this; Ext.space.Communicator.send({ command: "ChildView#getChildWebViews", callbacks: { onSuccess: function(webviews) { result.fulfill(webviews.map(function(webview) { var id = ""+webview.tabId; if (!this.windows[id]) { // isn't cached; could be due to app reload or something; // we need to recreate the child var child = this.windows[id] = new Ext.space.window.Child(webview.url, webview.name, {id: id}, true); if (webview.name) { this.namedWindows[webview.name] = child; } } else { // cached; update the URL to reflect the latest this.windows[id].url = webview.url; } return this.windows[id]; }.bind(self))); }, onError: function(error) { result.reject(error); } } }); return result; }, /** * Navigate the provided child web view to the given URL. * * @param {String|Ext.space.window.Child} childOrHandle Child web view, or ID thereof * @param {String} url URL to open * @return {Ext.space.Promise} Promise that resolves once the navigation operation is * dispatched; note that this is not the same thing as the * URL being completely loaded. For that, you need to add * a listener for the child view's 'loadstop' event. */ navigate: function(childOrHandle, url) { var result = new Ext.space.Promise(); var self = this; var handle = childOrHandle.hasOwnProperty("id") ? childOrHandle.id : childOrHandle; if (url) { Ext.space.Communicator.send({ command: "ChildView#navigate", id: handle, url: url, callbacks: { onSuccess: function() { // if the tab isn't in our window registry, attempt to load it if (typeof self.windows[handle] === "undefined") { self.getChildren().then(function(children) { if (typeof self.windows[handle] === "undefined") { result.reject("Tab not found"); } else { self.windows[handle].url = url; result.fulfill(); } }); } else { self.windows[handle].url = url; result.fulfill(); } }, onError: function(error) { result.reject(error); } } }); } else { result.reject("Missing URL"); } return result; }, /** * Close the given child web view. * * @param {String|Ext.space.window.Child} childOrHandle Child web view, or ID thereof * @return {Ext.space.Promise} Promise that resolves when the child view is closed */ close: function(childOrHandle) { var result = new Ext.space.Promise(); var self = this; var handle = childOrHandle.hasOwnProperty("id") ? childOrHandle.id : childOrHandle; Ext.space.Communicator.send({ id: handle, command: "ChildView#close", callbacks: { onSuccess: function() { var name = self.windows[handle].name; if (self.namedWindows[name]) { delete self.namedWindows[name]; } delete self.windows[handle]; result.fulfill(); }, onError: function(error) { result.reject(error); } } }); return result; }, /** * Close all open child web views. * * @return {Ext.space.Promise} Promise that resolves when the child views are all closed */ closeAll: function() { var result = new Ext.space.Promise(); var self = this; Ext.space.Communicator.send({ command: "ChildView#closeAll", callbacks: { onSuccess: function() { self.namedWindows = {}; self.windows = {}; result.fulfill(); }, onError: function(error) { result.reject(error); } } }); return result; }}); /* * Override window.open to use Sencha Web Application Client's API * * (we currently just obliterate the original since we don't need it) */window.open = Ext.space.Window.open.bind(Ext.space.Window); })();