/**
@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;
}