este-library-oldschool
Version:
Library for github.com/steida/este.git
441 lines (400 loc) • 10.8 kB
JavaScript
// Generated by github.com/steida/coffee2closure 900.1.18
/**
@fileoverview Este MVC application. The base class for all Este client side
MVC applications. It has several unique features inspired by excellent
[Beyond pushState](https://medium.com/joys-of-javascript/4353246f4480)
article. Architecture is similar to Backbone. For views, Facebook React or
Closure Templates can be used.
Why yet another MVC framework?
- this one is build on top of the Google Closure, the most robust JS library
- statically typed CoffeeScript, beautiful syntax with code check now
- several nice UX features which other frameworks does not implement
- designed for mobile and tablets from inception
Example:
```coffee
todoApp = este.app.create 'todo-app'
todoApp.storage = new este.storage.Local 'todos-este'
todoApp.addRoutes
'/:filter?': new app.todos.list.Presenter
todoApp.start()
```
@see /demos/app/layout
@see /demos/app/simple
@see /demos/app/todomvc
*/
var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.superClass_ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
goog.provide('este.App');
goog.provide('este.App.EventType');
goog.require('este.app.Event');
goog.require('este.app.Request');
goog.require('este.app.request.Queue');
goog.require('este.app.Route');
goog.require('este.Base');
goog.require('este.router.Route');
goog.require('goog.async.Delay');
goog.require('goog.result');
/**
@param {este.Router} router
@param {este.app.screen.Base} screen
@constructor
@extends {este.Base}
*/
este.App = function(router, screen) {
this.router = router;
this.screen = screen;
este.App.superClass_.constructor.call(this);
this.routes = [];
this.queue = new este.app.request.Queue;
}
extend(este.App, superClass);
/**
@enum {string}
*/
este.App.EventType = {
LOAD: 'load',
SHOW: 'show',
HIDE: 'hide',
TIMEOUT: 'timeout',
ERROR: 'error'
};
/**
@desc App request timed out message.
*/
este.App.MSG_REQUEST_TIMEOUT = goog.getMsg('Sorry, the request timed out. Please try again later.');
/**
@desc App request error message.
*/
este.App.MSG_REQUEST_ERROR = goog.getMsg('Sorry, server error. Please try again later.');
/**
Default timeout used for async presenters loading.
@type {number}
*/
este.App.prototype.timeoutMs = 10000;
/**
@type {boolean}
*/
este.App.prototype.urlProjectionEnabled = true;
/**
@type {este.storage.Base}
*/
este.App.prototype.storage = null;
/**
@type {boolean}
*/
este.App.prototype.showAlertOnError = true;
/**
@type {Window}
@protected
*/
este.App.prototype.window = window;
/**
@type {este.Router}
@protected
*/
este.App.prototype.router = null;
/**
@type {este.app.screen.Base}
@protected
*/
este.App.prototype.screen = null;
/**
@type {Array.<este.app.Route>}
@protected
*/
este.App.prototype.routes = null;
/**
@type {este.app.request.Queue}
@protected
*/
este.App.prototype.queue = null;
/**
@type {goog.result.Result}
@protected
*/
este.App.prototype.lastLoadResult = null;
/**
@type {este.app.Request}
@protected
*/
este.App.prototype.previousRequest = null;
/**
@type {boolean}
@protected
*/
este.App.prototype.locationUpdated = false;
/**
Example.
myApp.addRoutes
'/': new app.songs.list.Presenter user, songs
@param {string} path
@param {este.app.Presenter} presenter
*/
este.App.prototype.addRoute = function(path, presenter) {
var route;
route = new este.app.Route(path, presenter);
this.routes.push(route);
this.preparePresenter(route.presenter);
if (!this.urlProjectionEnabled) {
return;
}
return this.router.add(route.path, goog.bind(this.onRouteMatch, this, route.presenter));
};
/**
@param {Object.<string, este.app.Presenter>} routes
*/
este.App.prototype.addRoutes = function(routes) {
var mask, presenter;
for (mask in routes) {
presenter = routes[mask];
this.addRoute(mask, presenter);
}
};
/**
Run application.
*/
este.App.prototype.run = function() {
goog.asserts.assert(this.routes && this.routes.length, 'At least one route has to be defined.');
if (!this.urlProjectionEnabled) {
this.load(this.routes[0].presenter);
return;
}
return this.startRouter();
};
/**
@param {function(new:este.app.Presenter)} presenterClass
@param {Object=} params
@return {string}
*/
este.App.prototype.createUrl = function(presenterClass, params) {
var route, url;
route = this.findRouteByPresenterClass(presenterClass);
url = este.router.Route.createUrl(route.path, params);
if (this.router.isHtml5historyEnabled()) {
return url;
}
return '#' + url;
};
/**
@param {function(new:este.app.Presenter)} presenterClass
@param {Object=} params
@return {string}
*/
este.App.prototype.redirect = function(presenterClass, params) {
var route;
route = this.findRouteByPresenterClass(presenterClass);
return this.load(route.presenter, params);
};
/**
@param {function(new:este.app.Presenter)} presenterClass
@return {este.app.Route}
*/
este.App.prototype.findRouteByPresenterClass = function(presenterClass) {
return goog.array.find(this.routes, function(route) {
return route.presenter instanceof presenterClass;
});
};
/**
@param {este.app.Presenter} presenter
@return {este.app.Route}
@protected
*/
este.App.prototype.findRouteByPresenter = function(presenter) {
return goog.array.find(this.routes, function(route) {
return route.presenter === presenter;
});
};
/**
@param {este.app.Presenter} presenter
@protected
*/
este.App.prototype.preparePresenter = function(presenter) {
if (presenter.storage == null) {
presenter.storage = this.storage;
}
if (presenter.screen == null) {
presenter.screen = this.screen;
}
if (presenter.createUrl == null) {
presenter.createUrl = goog.bind(this.createUrl, this);
}
return presenter.redirect != null ? presenter.redirect : presenter.redirect = goog.bind(this.redirect, this);
};
/**
@param {este.app.Presenter} presenter
@param {Object=} params
@param {boolean=} isNavigation
@protected
*/
este.App.prototype.onRouteMatch = function(presenter, params, isNavigation) {
return this.load(presenter, params, isNavigation);
};
/**
@protected
*/
este.App.prototype.startRouter = function() {
this.router.navigateImmediately = false;
return this.router.start();
};
/**
@param {este.app.Presenter} presenter
@param {Object=} params
@param {boolean=} isNavigation
@protected
*/
este.App.prototype.load = function(presenter, params, isNavigation) {
var delay, request;
if (isNavigation) {
this.queue.clear();
}
request = new este.app.Request(presenter, params, isNavigation);
if (this.queue.contains(request)) {
return;
}
this.queue.add(request);
this.dispatchAppEvent(este.App.EventType.LOAD, request);
this.lastLoadResult = presenter.load(params);
delay = this.getResultTimeoutDelay(this.lastLoadResult);
return goog.result.wait(this.lastLoadResult, goog.bind(this.onLoadResultWait, this, request, delay));
};
/**
@param {goog.result.Result} result
@return {goog.async.Delay}
@protected
*/
este.App.prototype.getResultTimeoutDelay = function(result) {
var delay;
delay = new goog.async.Delay(function() {
return result.cancel();
}, this.timeoutMs, this);
delay.start();
return delay;
};
/**
@param {este.app.Request} request
@param {goog.async.Delay} delay
@param {goog.result.Result} result
@protected
*/
este.App.prototype.onLoadResultWait = function(request, delay, result) {
delay.dispose();
if (this.lastLoadResult !== result) {
return;
}
this.queue.clear();
switch (result.getState()) {
case goog.result.Result.State.SUCCESS:
this.onSuccessLoad(request, result);
break;
case goog.result.Result.State.ERROR:
this.onFailedLoad(request, result);
}
};
/**
@param {este.app.Request} request
@param {goog.result.Result} result
@protected
*/
este.App.prototype.onSuccessLoad = function(request, result) {
this.handlePreviousRequest();
this.previousRequest = request;
this.dispatchAppEvent(este.App.EventType.SHOW, request);
request.presenter.beforeShow(request.isNavigation, result);
return this.updateLocation(request);
};
/**
@param {este.app.Request} request
@param {goog.result.Result} result
@protected
*/
este.App.prototype.onFailedLoad = function(request, result) {
var error, notFoundRoute;
error = result.getError();
if (result.isCanceled()) {
if (this.showAlertOnError) {
this.window.alert(este.App.MSG_REQUEST_TIMEOUT);
}
this.dispatchAppEvent(este.App.EventType.TIMEOUT, request, error);
return;
}
notFoundRoute = this.tryGetNotFoundRoute(error);
if (notFoundRoute) {
this.load(notFoundRoute.presenter, null, true);
return;
}
if (this.showAlertOnError) {
this.window.alert(este.App.MSG_REQUEST_ERROR);
}
return this.dispatchAppEvent(este.App.EventType.ERROR, request, error);
};
/**
@param {*} error
@return {este.app.Route}
@protected
*/
este.App.prototype.tryGetNotFoundRoute = function(error) {
var route;
if (error !== 404) {
return null;
}
route = this.routes[this.routes.length - 1];
if (route.path !== '*') {
return null;
}
return route;
};
/**
@protected
*/
este.App.prototype.handlePreviousRequest = function() {
if (!this.previousRequest) {
return;
}
this.dispatchAppEvent(este.App.EventType.HIDE, this.previousRequest);
return this.previousRequest.presenter.beforeHide();
};
/**
@param {este.app.Request} request
@protected
*/
este.App.prototype.updateLocation = function(request) {
var path, route;
if (!this.locationUpdated) {
this.locationUpdated = true;
return;
}
if (!this.urlProjectionEnabled || request.isNavigation) {
return;
}
route = this.findRouteByPresenter(request.presenter);
path = route.path;
if (!path) {
return;
}
return this.router.pathNavigate(path, request.params, true);
};
/**
@param {este.App.EventType} type
@param {este.app.Request} request
@param {*=} error
@protected
*/
este.App.prototype.dispatchAppEvent = function(type, request, error) {
var event;
event = new este.app.Event(type, request, error);
return this.dispatchEvent(event);
};
/**
@override
*/
este.App.prototype.disposeInternal = function() {
var i, len, ref, route;
this.router.dispose();
ref = this.routes;
for (i = 0, len = ref.length; i < len; i++) {
route = ref[i];
route.dispose();
}
this.screen.dispose();
este.App.superClass_.disposeInternal.call(this);
};