/** @class Application is used to create new rio application classes. It provides functionality for dependency management, routing, history management and page management. @extends rio.Attr */ rio.Application = { /** Creates an instance of rio.Application. @param {String} name (optional) The name of this Application. Used primarily for testing reports. @param {Object} extends (optional) An Attr class to use as a superclass. @param {Object} args (optional) The definition of the class. @returns a new instance of type Application @type rio.Application @example rio.apps.example = rio.Application.create({ require: ["pages/example_page"], requireCss: ["css_reset", "example"], routes: { "": "examplePage" }, attrAccessors: [], attrReaders: [], methods: { initialize: function(options) { }, examplePage: function() { return new rio.pages.ExamplePage(); } } }); */ create: function() { var args = $A(arguments); if (args.length > 0 && args.last() != undefined && !args.last().ATTR) { args[args.size() - 1] = Object.extend({ noExtend: true }, args.last()); } var app = rio.Attr.create.apply(this, args); app.addMethods( /** @scope rio.Application.prototype */ { /** @private */ initHistory: function() { dhtmlHistory.initialize(); dhtmlHistory.addListener(this.applyHistoryEntry.bind(this)); }, /** @private */ applyHistoryEntry: function(location, historyData) { if (this.__revertingTransientHistoryEntry) { this.addHistoryEntry(this.__revertingTransientHistoryEntry[0], this.__revertingTransientHistoryEntry[1]); this.__revertingTransientHistoryEntry = false; } else { this.navigateTo(location, true); } }, /** @private */ addHistoryEntry: function(location, transient) { if (historyStorage.hasKey(this.getCurrentLocation()) && historyStorage.get(this.getCurrentLocation()).transient) { this.__revertingTransientHistoryEntry = [location, transient]; history.back(1); } else { dhtmlHistory.add(location, { transient: transient }); } }, /** @private */ resize: function() { this.getCurrentPage().resize(); }, /** @private */ keyPress: function(e) { var currentPage = this.getCurrentPage(); if (currentPage) { this.getCurrentPage().keyPress(e); } }, /** @private */ keyDown: function(e) { var keyMap = this.getKeyMap(); if (keyMap) { keyMap.handle(e); } var currentPage = this.getCurrentPage(); if (currentPage) { this.getCurrentPage()._keyDown(e); } }, /** This method is called just before the page is unloaded. This can be triggered by following a link, closing the window, using the back button, etc. This method is meant to be overriden */ unload: function() { // meant to be overriden }, /** @private */ getKeyMap: function() { if (this._keyMap) { return this._keyMap; } if (!this.keyMap) { return; } this._keyMap = rio.KeyMap.build(this.keyMap()); return this._keyMap; }, /** @private */ launch: function() { document.observe("keypress", this.keyPress.bind(this)); document.observe("keydown", this.keyDown.bind(this)); Event.observe(window, "beforeunload", this.unload.bind(this)); if (this.noRoutes()) { return; } this.initHistory(); this.navigateTo(this.getCurrentLocation()); Event.observe(window, "resize", this.resize.bind(this)); rio.Application._afterLaunchFunctions.each(function(fcn) { fcn(this); }); rio.Application._afterLaunchFunctions.clear(); this._launched = true; }, /** @private */ launched: function() { return this._launched || false; }, /** @private */ noRoutes: function() { return (app.__routes == undefined) || ($H(app.__routes).keys().size() == 0); }, /** @private */ avoidAnimation: function() { return Prototype.Browser.IE; }, /** @private */ matchRoutePath: function(path) { return Object.keys(app.__routes).detect(function(routePath) { if (routePath == "") { return true; } var routeParts = routePath.split("/"); var pathParts = path.split("/"); var match = true; for(var i=0; i "584/revisions" @returns the path of your rio application after the hash @type String */ getCurrentLocation: function() { return dhtmlHistory.getCurrentLocation(); }, /** Returns the instance of the currently loaded page in the app. @returns the instance of the currently loaded page @type rio.Page */ getCurrentPage: function() { return this._currentPage; }, /** @private */ setCurrentPage: function(page) { this._currentPage = page; }, /** @private */ rootUrl: function() { return document.location.protocol + "//" + document.location.host; }, toString: function() { return "[rio.apps.*]"; } }); Object.extend(app, /** @scope rio.Application */ { /** Specifies the lowest priority route for an application class. You are better off specifying routes when creating an application with a 'routes' parameter. */ route: function(path, target){ if (!this.__routes) { this.__routes = {}; } this.__routes[path] = target; var parts = path.split("/"); if (parts.length > 1) { for (var i=0; i 0 && args.last() != undefined && !args.last().ATTR) { var initializers = args.last(); if (initializers.requireCss) { rio.Application.includeCss(initializers.requireCss); } Object.keys(initializers.routes || {}).each(function(name) { app.route(name, initializers.routes[name]); }); rio.Application.extend(app, initializers.methods || {}); } return app; }, /** @private */ extend: function(app, extension) { rio.Attr.extend(app, extension); }, /** Alias of rio.Application.require @param {String} fileName The path to the javascript file that will be loaded. */ include: function(fileName) { this.require(fileName); // rio.boot.loadFile(fileName); }, /** Alias of rio.Application.require @param {String} fileName The path to the javascript file that will be loaded. */ require: function(fileName) { rio.require(fileName); }, /** @private */ injectCss: function() { var toLoad = []; rio.boot.loadedStylesheets.each(function(s) { if (!rio.preloadedStylesheets.include(s)) { toLoad.push(s); } }); if (toLoad.empty()) { return; } var query = toLoad.map(function(f) { return "files[]=" + f; }).join("&"); var linkHtml = rio.Tag.link("", { href: rio.url("/rio/stylesheets?" + query + "&" + rio.cacheKey), media: "screen", rel: "stylesheet", type: "text/css" }); Element.head().insert({ bottom: linkHtml }); if (rio.ContainerLayout) { rio.ContainerLayout.resize(); } }, /* Requires a css file @param {String} toInclude The path to the stylesheet that will be loaded. */ includeCss: function(toInclude) { // Because of a bug in IE, we need to remove and readd all link tags every time a new one is added. var include = function(fileName) { if (rio.boot.loadedStylesheets.include(fileName)) { return; } rio.boot.loadedStylesheets.push(fileName); if (rio.preloadedStylesheets.include(fileName)) { return; } if (rio.environment.autoConcatCss && !(rio.app && rio.app.launched())) { // Do nothing } else { var linkHtml = rio.Tag.link("", { href: rio.url("/stylesheets/" + fileName + ".css"), media: "screen", rel: "stylesheet", type: "text/css" }); Element.head().insert({ bottom: linkHtml }); } }.bind(this); if (Object.isString(toInclude)) { include(toInclude); } if (Object.isArray(toInclude)) { toInclude.reverse().each(function(css) { include(css); }); } }, /** @private */ getToken: function() { return this._token || rio.environment.railsToken; }, /** @private */ setToken: function(token) { this._token = token; }, /** @private */ _afterLaunchFunctions: [], /** @private */ afterLaunch: function(afterLoadFunction) { if (rio.app && rio.app.launched()) { afterLoadFunction(rio.app); } else { this._afterLaunchFunctions.push(afterLoadFunction); } }, /** This causes the application to fail and log a 'fail' error message. If the application class has a fail method, that method will be called with the message passed in here. @param {String} msg The application failure message @param {String} msg A more in depth description of the application failure */ fail: function(msg, description) { try { if (rio.app && rio.app.fail) { rio.app.fail(msg); rio.error("FAIL: " + msg, description || "", true); } else { alert("OOPS: " + msg + "\n\nTry refreshing the page or come back later."); } } catch(e) { // Ignore errors during fail } }, toString: function() { return "Application"; } }; if (!window.dhtmlHistoryCreated) { window.dhtmlHistory.create({ toJSON: function(o) { return Object.toJSON(o); }, fromJSON: function(s) { return s.evalJSON(); } }); window.dhtmlHistoryCreated = true; }