UNPKG

este-library-oldschool

Version:

Library for github.com/steida/este.git

441 lines (400 loc) 10.8 kB
// 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); };