diff --git a/.eslintrc b/.eslintrc
index d6b5f88bf..07e17b382 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -9,6 +9,7 @@
"window": false,
"document": false
},
+ "root": true,
"rules": {
"comma-style": [
2,
@@ -33,7 +34,9 @@
"no-cond-assign": [
2,
"except-parens"
- ]
+ ],
+ "no-unused-vars": ["off", { "argsIgnorePattern": "^_" }],
+ "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }]
},
"overrides": [
{
@@ -47,6 +50,40 @@
{
"files": ["test/**/*.js"],
"env": {"mocha": true, "node": true}
+ },
+ {
+ "files": ["src/**/*.ts"],
+ "parser": "@typescript-eslint/parser",
+ "plugins": ["@typescript-eslint", "prettier", "import"],
+ "extends": [
+ "eslint:recommended",
+ "plugin:@typescript-eslint/eslint-recommended",
+ "plugin:@typescript-eslint/recommended",
+ "plugin:import/recommended",
+ "plugin:import/typescript"
+ ],
+ "rules": {
+ "@typescript-eslint/no-explicit-any": ["warn", { "ignoreRestArgs": false }],
+ "import/no-unresolved": "error",
+ "import/order": [
+ "error",
+ {
+ "groups": [
+ "builtin",
+ "external",
+ "internal",
+ ["sibling", "parent"],
+ "index",
+ "unknown"
+ ],
+ "newlines-between": "always",
+ "alphabetize": {
+ "order": "asc",
+ "caseInsensitive": true
+ }
+ }
+ ]
+ }
}
]
}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 52ed55ca8..4dcf1a832 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -17,9 +17,9 @@ jobs:
strategy:
matrix:
node:
- - 14
- 16
- 18
+ - 20
timeout-minutes: 10
steps:
- uses: actions/checkout@v2
diff --git a/.gitignore b/.gitignore
index ef7d85ff1..6967f2a90 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
*.swp
node_modules
.vscode
+dist/
diff --git a/index.js b/index.js
deleted file mode 100644
index dfac0a173..000000000
--- a/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-var serverRequire = require('racer').util.serverRequire;
-var Derby = serverRequire(module, './lib/DerbyForServer') || require('./lib/Derby');
-module.exports = new Derby();
diff --git a/lib/App.js b/lib/App.js
deleted file mode 100644
index 6120aec4b..000000000
--- a/lib/App.js
+++ /dev/null
@@ -1,417 +0,0 @@
-/*
- * App.js
- *
- * Provides the glue between views, controllers, and routes for an
- * application's functionality. Apps are responsible for creating pages.
- *
- */
-
-var path = require('path');
-var EventEmitter = require('events').EventEmitter;
-var tracks = require('tracks');
-var util = require('racer/lib/util');
-var derbyTemplates = require('./templates');
-var templates = derbyTemplates.templates;
-var components = require('./components');
-var PageBase = require('./Page');
-
-module.exports = App;
-
-// TODO: Change to Map once we officially drop support for ES5.
-global.APPS = global.APPS || {};
-
-function App(derby, name, filename, options) {
- EventEmitter.call(this);
- this.derby = derby;
- this.name = name;
- this.filename = filename;
- this.scriptHash = process.env.DERBY_SCRIPT_HASH || '{{DERBY_SCRIPT_HASH}}';
- this.bundledAt = process.env.DERBY_BUNDLED_AT || '{{DERBY_BUNDLED_AT}}';
- this.buildVersion = process.env.DERBY_BUILD_VERSION;
- this.Page = createAppPage(derby);
- this.proto = this.Page.prototype;
- this.views = new templates.Views();
- this.tracksRoutes = tracks.setup(this);
- this.model = null;
- this.page = null;
- this._pendingComponentMap = {};
- this._init(options);
-}
-
-function createAppPage(derby) {
- var Page = (derby && derby.Page) || PageBase;
- // Inherit from Page/PageForServer so that we can add controller functions as prototype
- // methods on this app's pages
- function AppPage() {
- Page.apply(this, arguments);
- }
- AppPage.prototype = Object.create(Page.prototype);
- return AppPage;
-}
-
-util.mergeInto(App.prototype, EventEmitter.prototype);
-
-// Overriden on server
-App.prototype._init = function() {
- this._waitForAttach = true;
- this._cancelAttach = false;
- this.model = new this.derby.Model();
- var serializedViews = this._views();
- serializedViews(derbyTemplates, this.views);
- // Must init async so that app.on('model') listeners can be added.
- // Must also wait for content ready so that bundle is fully downloaded.
- this._contentReady();
-};
-
-App.prototype._views = function () {
- return require('./_views');
-}
-
-App.prototype._finishInit = function() {
- var data = this._getAppData();
- util.isProduction = data.nodeEnv === 'production';
-
- var previousAppInfo;
- if (!util.isProduction) {
- previousAppInfo = global.APPS[this.name];
- if (previousAppInfo) {
- previousAppInfo.app._destroyCurrentPage();
- }
- global.APPS[this.name] = {
- app: this,
- initialState: data,
- };
- }
-
- this.model.createConnection(data);
- this.emit('model', this.model);
-
- if (!util.isProduction) this._autoRefresh();
-
- this.model.unbundle(data);
-
- var page = this.createPage();
- page.params = this.model.get('$render.params');
- this.emit('ready', page);
-
- this._waitForAttach = false;
- // Instead of attaching, do a route and render if a link was clicked before
- // the page finished attaching, or if this is a new app from hot reload.
- if (this._cancelAttach || previousAppInfo) {
- this.history.refresh();
- return;
- }
- // Since an attachment failure is *fatal* and could happen as a result of a
- // browser extension like AdBlock, an invalid template, or a small bug in
- // Derby or Saddle, re-render from scratch on production failures
- if (util.isProduction) {
- try {
- page.attach();
- } catch (err) {
- this.history.refresh();
- console.warn('attachment error', err.stack);
- }
- } else {
- page.attach();
- }
- this.emit('load', page);
-};
-
-App.prototype._getAppData = function () {
- var script = this._getAppStateScript();
- if (script) {
- return App._parseInitialData(script.textContent);
- } else {
- return global.APPS[this.name].initialState;
- }
-}
-
-// Modified from: https://github.com/addyosmani/jquery.parts/blob/master/jquery.documentReady.js
-App.prototype._contentReady = function() {
- // Is the DOM ready to be used? Set to true once it occurs.
- var isReady = false;
- var app = this;
-
- // The ready event handler
- function onDOMContentLoaded() {
- if (document.addEventListener) {
- document.removeEventListener('DOMContentLoaded', onDOMContentLoaded, false);
- } else {
- // we're here because readyState !== 'loading' in oldIE
- // which is good enough for us to call the dom ready!
- document.detachEvent('onreadystatechange', onDOMContentLoaded);
- }
- onDOMReady();
- }
-
- // Handle when the DOM is ready
- function onDOMReady() {
- // Make sure that the DOM is not already loaded
- if (isReady) return;
- // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
- if (!document.body) return setTimeout(onDOMReady, 0);
- // Remember that the DOM is ready
- isReady = true;
- // Make sure this is always async and then finishin init
- setTimeout(function() {
- app._finishInit();
- }, 0);
- }
-
- // The DOM ready check for Internet Explorer
- function doScrollCheck() {
- if (isReady) return;
- try {
- // If IE is used, use the trick by Diego Perini
- // http://javascript.nwbox.com/IEContentLoaded/
- document.documentElement.doScroll('left');
- } catch (err) {
- setTimeout(doScrollCheck, 0);
- return;
- }
- // and execute any waiting functions
- onDOMReady();
- }
-
- // Catch cases where called after the browser event has already occurred.
- if (document.readyState !== 'loading') return onDOMReady();
-
- // Mozilla, Opera and webkit nightlies currently support this event
- if (document.addEventListener) {
- // Use the handy event callback
- document.addEventListener('DOMContentLoaded', onDOMContentLoaded, false);
- // A fallback to window.onload, that will always work
- window.addEventListener('load', onDOMContentLoaded, false);
- // If IE event model is used
- } else if (document.attachEvent) {
- // ensure firing before onload,
- // maybe late but safe also for iframes
- document.attachEvent('onreadystatechange', onDOMContentLoaded);
- // A fallback to window.onload, that will always work
- window.attachEvent('onload', onDOMContentLoaded);
- // If IE and not a frame
- // continually check to see if the document is ready
- var toplevel;
- try {
- toplevel = window.frameElement == null;
- } catch (err) {}
- if (document.documentElement.doScroll && toplevel) {
- doScrollCheck();
- }
- }
-};
-
-App.prototype._getAppStateScript = function() {
- return document.querySelector('script[data-derby-app-state]');
-};
-
-App.prototype.use = util.use;
-App.prototype.serverUse = util.serverUse;
-
-App.prototype.loadViews = function() {};
-
-App.prototype.loadStyles = function() {};
-
-// This function is overriden by requiring 'derby/parsing'
-App.prototype.addViews = function() {
- throw new Error(
- 'Parsing not available. Registering a view from source should not be used ' +
- 'in application code. Instead, specify a filename with view.file.'
- );
-};
-
-App.prototype.component = function(name, constructor, isDependency) {
- if (typeof name === 'function') {
- constructor = name;
- name = null;
- }
- if (typeof constructor !== 'function') {
- throw new Error('Missing component constructor argument');
- }
-
- var viewProp = constructor.view;
- var viewIs, viewFilename, viewSource, viewDependencies;
- // Always using an object for the static `view` property is preferred
- if (viewProp && typeof viewProp === 'object') {
- viewIs = viewProp.is;
- viewFilename = viewProp.file;
- viewSource = viewProp.source;
- viewDependencies = viewProp.dependencies;
- } else {
- // Ignore other properties when `view` is an object. It is possible that
- // properties could be inherited from a parent component when extending it.
- //
- // DEPRECATED: constructor.prototype.name and constructor.prototype.view
- // use the equivalent static properties instead
- viewIs = constructor.is || constructor.prototype.name;
- viewFilename = constructor.view || constructor.prototype.view;
- }
- var viewName = name || viewIs ||
- (viewFilename && path.basename(viewFilename, '.html'));
-
- if (!viewName) {
- throw new Error('No view specified for component');
- }
- if (viewFilename && viewSource) {
- throw new Error('Component may not specify both a view file and source');
- }
-
- // TODO: DRY. This is copy-pasted from ./templates
- var mapName = viewName.replace(/:index$/, '');
- var currentView = this.views.nameMap[mapName];
- var currentConstructor = (currentView && currentView.componentFactory) ?
- currentView.componentFactory.constructor :
- this._pendingComponentMap[mapName];
-
- // Avoid registering the same component twice; we want to avoid the overhead
- // of loading view files from disk again. This is also what prevents
- // circular dependencies from infinite looping
- if (currentConstructor === constructor) return;
-
- // Calling app.component() overrides existing views or components. Prevent
- // dependencies from doing this without warning
- if (isDependency && currentView && !currentView.fromSerialized) {
- throw new Error('Dependencies cannot override existing views. Already registered "' + viewName + '"');
- }
-
- // This map is used to prevent infinite loops from circular dependencies
- this._pendingComponentMap[mapName] = constructor;
-
- // Recursively register component dependencies
- if (viewDependencies) {
- for (var i = 0; i < viewDependencies.length; i++) {
- var dependency = viewDependencies[i];
- if (Array.isArray(dependency)) {
- this.component(dependency[0], dependency[1], true);
- } else {
- this.component(null, dependency, true);
- }
- }
- }
-
- // Register or find views specified by the component
- var view;
- if (viewFilename) {
- this.loadViews(viewFilename, viewName);
- view = this.views.find(viewName);
-
- } else if (viewSource) {
- this.addViews(viewSource, viewName);
- view = this.views.find(viewName);
-
- } else if (name) {
- view = this.views.find(viewName);
-
- } else {
- view = this.views.register(viewName, '');
- }
- if (!view) {
- var message = this.views.findErrorMessage(viewName);
- throw new Error(message);
- }
-
- // Inherit from Component
- components.extendComponent(constructor);
- // Associate the appropriate view with the component constructor
- view.componentFactory = components.createFactory(constructor);
-
- delete this._pendingComponentMap[mapName];
-
- // Make chainable
- return this;
-};
-
-App.prototype.createPage = function() {
- this._destroyCurrentPage();
- var page = new this.Page(this, this.model);
- this.page = page;
- return page;
-};
-
-App.prototype._destroyCurrentPage = function() {
- if (this.page) {
- this.emit('destroyPage', this.page);
- this.page.destroy();
- }
-};
-
-App.prototype.onRoute = function(callback, page, next, done) {
- if (this._waitForAttach) {
- // Cancel any routing before the initial page attachment. Instead, do a
- // render once derby is ready
- this._cancelAttach = true;
- return;
- }
- this.emit('route', page);
- // HACK: To update render in transitional routes
- page.model.set('$render.params', page.params);
- page.model.set('$render.url', page.params.url);
- page.model.set('$render.query', page.params.query);
- // If transitional
- if (done) {
- var app = this;
- var _done = function() {
- app.emit('routeDone', page, 'transition');
- done();
- };
- callback.call(page, page, page.model, page.params, next, _done);
- return;
- }
- callback.call(page, page, page.model, page.params, next);
-};
-
-App.prototype._autoRefresh = function() {
- var app = this;
- var connection = this.model.connection;
- connection.on('connected', function() {
- connection.send({
- derby: 'app',
- name: app.name,
- hash: app.scriptHash
- });
- });
- connection.on('receive', function(request) {
- if (request.data.derby) {
- var message = request.data;
- request.data = null;
- app._handleMessage(message.derby, message);
- }
- });
-};
-
-App.prototype._handleMessage = function(action, message) {
- if (action === 'refreshViews') {
- var fn = new Function('return ' + message.views)(); // jshint ignore:line
- fn(derbyTemplates, this.views);
- var ns = this.model.get('$render.ns');
- this.page.render(ns);
-
- } else if (action === 'refreshStyles') {
- var styleElement = document.querySelector('style[data-filename="' +
- message.filename + '"]');
- if (styleElement) styleElement.innerHTML = message.css;
-
- } else if (action === 'reload') {
- this.model.whenNothingPending(function() {
- window.location = window.location;
- });
- }
-};
-
-App._parseInitialData = function _parseInitialData(jsonString) {
- try {
- return JSON.parse(jsonString);
- } catch (error) {
- var message = error.message || '';
- var match = message.match(/Unexpected token (.) in JSON at position (\d+)/);
- if (match) {
- var p = parseInt(match[2], 10);
- var stringContext = jsonString.substring(
- Math.min(0, p - 30),
- Math.max(p + 30, jsonString.length - 1)
- );
- throw new Error('Parse failure: ' + error.message + ' context: \'' + stringContext + '\'');
- }
- throw error;
- }
-};
diff --git a/lib/AppForServer.js b/lib/AppForServer.js
deleted file mode 100644
index b96171db0..000000000
--- a/lib/AppForServer.js
+++ /dev/null
@@ -1,349 +0,0 @@
-/*
- * App.server.js
- *
- * Application level functionality that is
- * only applicable to the server.
- *
- */
-
-var racer = require('racer');
-var util = racer.util;
-var App = require('./App');
-var parsing = require('../parsing');
-var derbyTemplates = require('../templates');
-
-// Avoid Browserifying these dependencies
-var chokidar, crypto, files, fs, path, through;
-if (module.require) {
- chokidar = module.require('chokidar');
- crypto = module.require('crypto');
- files = module.require('./files');
- fs = module.require('fs');
- path = module.require('path');
- through = module.require('through');
-}
-
-var STYLE_EXTENSIONS = ['.css'];
-var VIEW_EXTENSIONS = ['.html'];
-var COMPILERS = {
- '.css': cssCompiler,
- '.html': htmlCompiler
-};
-function cssCompiler(file, filename, options) {
- return {css: file, files: [filename]};
-}
-function htmlCompiler(file) {
- return file;
-}
-
-module.exports = AppForServer;
-
-function AppForServer(derby, name, filename, options) {
- App.call(this, derby, name, filename, options);
-}
-AppForServer.prototype = Object.create(App.prototype);
-AppForServer.prototype.constructor = AppForServer;
-
-AppForServer.prototype._init = function(options) {
- this._initBundle(options);
- this._initRefresh();
- this._initLoad();
- this._initViews();
-};
-AppForServer.prototype._initBundle = function(options) {
- this.scriptFilename = null;
- this.scriptMapFilename = null;
- this.scriptBaseUrl = (options && options.scriptBaseUrl) || '';
- this.scriptMapBaseUrl = (options && options.scriptMapBaseUrl) || '';
- this.scriptCrossOrigin = (options && options.scriptCrossOrigin) || false;
- this.scriptUrl = null;
- this.scriptMapUrl = null;
-};
-AppForServer.prototype._initRefresh = function() {
- this.watchFiles = !util.isProduction;
- this.agents = null;
-};
-AppForServer.prototype._initLoad = function() {
- this.styleExtensions = STYLE_EXTENSIONS.slice();
- this.viewExtensions = VIEW_EXTENSIONS.slice();
- this.compilers = util.copyObject(COMPILERS);
-};
-AppForServer.prototype._initViews = function() {
- this.serializedDir = path.dirname(this.filename || '') + '/derby-serialized';
- this.serializedBase = this.serializedDir + '/' + this.name;
- if (fs.existsSync(this.serializedBase + '.json')) {
- this.deserialize();
- this.loadViews = function() {};
- this.loadStyles = function() {};
- return;
- }
-
- this.views.register('Page',
- '' +
- '' +
- '' +
- '' +
- '' +
- '',
- {serverOnly: true}
- );
- this.views.register('TitleElement',
- '
'
- );
- this.views.register('BodyElement',
- '' +
- ''
- );
- this.views.register('Title', 'Derby App');
- this.views.register('Styles', '', {serverOnly: true});
- this.views.register('Head', '', {serverOnly: true});
- this.views.register('Body', '');
- this.views.register('Tail', '');
-};
-
-AppForServer.prototype.createPage = function(req, res, next) {
- var model = req.model || new racer.Model();
- this.emit('model', model);
- var page = new this.Page(this, model, req, res);
- if (next) {
- model.on('error', function(err){
- model.hasErrored = true;
- next(err);
- });
- page.on('error', next);
- }
- return page;
-};
-
-AppForServer.prototype.bundle = function(backend, options, cb) {
- throw new Error(
- 'bundle implementation missing; use racer-bundler for implementation, or remove call to this method and use another bundler',
- );
-};
-
-AppForServer.prototype.writeScripts = function(backend, dir, options, cb) {
- throw new Error(
- 'writeScripts implementation missing; use racer-bundler for implementation, or remove call to this method and use another bundler',
- );
-};
-
-AppForServer.prototype._viewsSource = function(options) {
- return `/*DERBY_SERIALIZED_VIEWS ${this.name}*/\n` +
- 'module.exports = ' + this.views.serialize(options) + ';\n' +
- `/*DERBY_SERIALIZED_VIEWS_END ${this.name}*/\n`;
-};
-
-AppForServer.prototype.serialize = function() {
- if (!fs.existsSync(this.serializedDir)) {
- fs.mkdirSync(this.serializedDir);
- }
- // Don't minify the views (which doesn't include template source), since this
- // is for use on the server
- var viewsSource = this._viewsSource({server: true, minify: true});
- fs.writeFileSync(this.serializedBase + '.views.js', viewsSource, 'utf8');
- var scriptUrl = (this.scriptUrl.indexOf(this.scriptBaseUrl) === 0) ?
- this.scriptUrl.slice(this.scriptBaseUrl.length) :
- this.scriptUrl;
- var scriptMapUrl = (this.scriptMapUrl.indexOf(this.scriptMapBaseUrl) === 0) ?
- this.scriptMapUrl.slice(this.scriptMapBaseUrl.length) :
- this.scriptMapUrl;
- var serialized = JSON.stringify({
- scriptBaseUrl: this.scriptBaseUrl,
- scriptMapBaseUrl: this.scriptMapBaseUrl,
- scriptUrl: scriptUrl,
- scriptMapUrl: scriptMapUrl
- });
- fs.writeFileSync(this.serializedBase + '.json', serialized, 'utf8');
-};
-
-AppForServer.prototype.deserialize = function() {
- var serializedViews = require(this.serializedBase + '.views.js');
- var serialized = require(this.serializedBase + '.json');
- serializedViews(derbyTemplates, this.views);
- this.scriptUrl = (this.scriptBaseUrl || serialized.scriptBaseUrl) + serialized.scriptUrl;
- this.scriptMapUrl = (this.scriptMapBaseUrl || serialized.scriptMapBaseUrl) + serialized.scriptMapUrl;
-};
-
-AppForServer.prototype.loadViews = function(filename, namespace) {
- var data = files.loadViewsSync(this, filename, namespace);
- parsing.registerParsedViews(this, data.views);
- if (this.watchFiles) this._watchViews(data.files, filename, namespace);
- // Make chainable
- return this;
-};
-
-AppForServer.prototype.loadStyles = function(filename, options) {
- this._loadStyles(filename, options);
- var stylesView = this.views.find('Styles');
- stylesView.source += '';
- // Make chainable
- return this;
-};
-
-AppForServer.prototype._loadStyles = function(filename, options) {
- var styles = files.loadStylesSync(this, filename, options);
-
- var filepath = '';
- if (this.watchFiles) {
- /**
- * Mark the path to file as an attribute
- * Used in development to add event watchers and autorefreshing of styles
- * SEE: local file, method this._watchStyles
- * SEE: file ./App.js, method App._autoRefresh()
- */
- filepath = ' data-filename="' + filename + '"';
- }
- var source = '';
-
- this.views.register(filename, source, {
- serverOnly: true
- });
-
- if (this.watchFiles) {
- this._watchStyles(styles.files, filename, options);
- }
-
- return styles;
-};
-
-AppForServer.prototype._watchViews = function(filenames, filename, namespace) {
- var app = this;
- watchOnce(filenames, function() {
- app.loadViews(filename, namespace);
- app._updateScriptViews();
- app._refreshClients();
- });
-};
-
-AppForServer.prototype._watchStyles = function(filenames, filename, options) {
- var app = this;
- watchOnce(filenames, function() {
- var styles = app._loadStyles(filename, options);
- app._updateScriptViews();
- app._refreshStyles(filename, styles);
- });
-};
-
-AppForServer.prototype._watchBundle = function(filenames) {
- if (!process.send) return;
- var app = this;
- watchOnce(filenames, function() {
- process.send({type: 'reload'});
- });
-};
-
-function watchOnce(filenames, callback) {
- var watcher = chokidar.watch(filenames);
- var closed = false;
- watcher.on('change', function() {
- if (closed) return;
- closed = true;
- // HACK: chokidar 3.1.1 crashes when you synchronously call close
- // in the change event. Delaying appears to prevent the crash
- process.nextTick(function() {
- watcher.close();
- });
- callback();
- });
-}
-
-AppForServer.prototype._updateScriptViews = function() {
- if (!this.scriptFilename) return;
- var script = fs.readFileSync(this.scriptFilename, 'utf8');
- var i = script.indexOf('/*DERBY_SERIALIZED_VIEWS*/');
- var before = script.slice(0, i);
- var i = script.indexOf('/*DERBY_SERIALIZED_VIEWS_END*/');
- var after = script.slice(i + 30);
- var viewsSource = this._viewsSource();
- fs.writeFileSync(this.scriptFilename, before + viewsSource + after, 'utf8');
-};
-
-AppForServer.prototype._autoRefresh = function(backend) {
- // already been setup if agents is defined
- if (this.agents) return;
- this.agents = {};
- var app = this;
-
- // Auto-refresh is implemented on top of ShareDB's messaging layer.
- //
- // However, ShareDB wasn't originally designed to support custom message types, so ShareDB's
- // Agent class will log out "Invalid or unknown message" warnings if it encounters a message
- // it doesn't recognize.
- //
- // A workaround is to register a "receive" middleware, which fires when a ShareDB server
- // receives a message from a client. If the message is Derby-related, the middleware will
- // "exit" the middleware chain early by not calling `next()`. That way, the custom message never
- // gets to the ShareDB Agent and won't result in warnings.
- //
- // However, multiple Derby apps can run together on the same ShareDB backend, each adding a
- // "receive" middleware, and they all need to be notified of incoming Derby messages. This
- // solution combines the exit-early approach with a custom event to accomplish that.
- backend.use('receive', function(request, next) {
- var data = request.data;
- if (data.derby) {
- // Derby-related message, emit custom event and "exit" middleware chain early.
- backend.emit('derby:_messageReceived', request.agent, data.derby, data);
- return;
- } else {
- // Not a Derby-related message, pass to next middleware.
- next();
- }
- });
-
- backend.on('derby:_messageReceived', function(agent, action, message) {
- app._handleMessage(agent, action, message);
- });
-};
-
-AppForServer.prototype._handleMessage = function(agent, action, message) {
- if (action === 'app') {
- if (message.name !== this.name) {
- return;
- }
- if (message.hash !== this.scriptHash) {
- return agent.send({derby: 'reload'});
- }
- this._addAgent(agent);
- }
-};
-
-AppForServer.prototype._addAgent = function(agent) {
- this.agents[agent.clientId] = agent;
- var app = this;
- agent.stream.once('end', function() {
- delete app.agents[agent.clientId];
- });
-};
-
-AppForServer.prototype._refreshClients = function() {
- if (!this.agents) return;
- var views = this.views.serialize({minify: true});
- var message = {
- derby: 'refreshViews',
- views: views
- };
- for (var id in this.agents) {
- this.agents[id].send(message);
- }
-};
-
-AppForServer.prototype._refreshStyles = function(filename, styles) {
- if (!this.agents) return;
- var data = {filename: filename, css: styles.css};
- var message = {
- derby: 'refreshStyles',
- filename: filename,
- css: styles.css
- };
- for (var id in this.agents) {
- this.agents[id].send(message);
- }
-};
-
-AppForServer.prototype.middleware = function(backend) {
- return [backend.modelMiddware(), this.router()];
-}
-
-AppForServer.prototype.initAutoRefresh = function(backend) {
- this._autoRefresh(backend);
-}
diff --git a/lib/Controller.js b/lib/Controller.js
deleted file mode 100644
index 511976d5d..000000000
--- a/lib/Controller.js
+++ /dev/null
@@ -1,46 +0,0 @@
-var EventEmitter = require('events').EventEmitter;
-var util = require('racer/lib/util');
-var Dom = require('./Dom');
-
-module.exports = Controller;
-
-function Controller(app, page, model) {
- EventEmitter.call(this);
- this.dom = new Dom(this);
- this.app = app;
- this.page = page;
- this.model = model;
- model.data.$controller = this;
-}
-
-util.mergeInto(Controller.prototype, EventEmitter.prototype);
-
-Controller.prototype.emitCancellable = function() {
- var cancelled = false;
- function cancel() {
- cancelled = true;
- }
-
- var args = Array.prototype.slice.call(arguments);
- args.push(cancel);
- this.emit.apply(this, args);
-
- return cancelled;
-};
-
-Controller.prototype.emitDelayable = function() {
- var args = Array.prototype.slice.call(arguments);
- var callback = args.pop();
-
- var delayed = false;
- function delay() {
- delayed = true;
- return callback;
- }
-
- args.push(delay);
- this.emit.apply(this, args);
- if (!delayed) callback();
-
- return delayed;
-};
diff --git a/lib/Derby.js b/lib/Derby.js
deleted file mode 100644
index 2f5db2201..000000000
--- a/lib/Derby.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Derby.js
- * Meant to be the entry point for the framework.
- *
- */
-var racer = require('racer');
-
-module.exports = Derby;
-
-function Derby() {}
-Derby.prototype = Object.create(racer);
-Derby.prototype.constructor = Derby;
-
-Derby.prototype.App = require('./App');
-Derby.prototype.Page = require('./Page');
-Derby.prototype.Component = require('./components').Component;
-
-Derby.prototype.createApp = function(name, filename, options) {
- return new this.App(this, name, filename, options);
-};
-
-if (!racer.util.isServer) {
- require('./documentListeners').add(document);
-}
diff --git a/lib/DerbyForServer.js b/lib/DerbyForServer.js
deleted file mode 100644
index 8401959f8..000000000
--- a/lib/DerbyForServer.js
+++ /dev/null
@@ -1,40 +0,0 @@
-var cluster = require('cluster');
-var Derby = require('./Derby');
-var util = require('racer/lib/util');
-
-util.isProduction = process.env.NODE_ENV === 'production';
-
-module.exports = DerbyForServer;
-function DerbyForServer() {}
-DerbyForServer.prototype = Object.create(Derby.prototype);
-DerbyForServer.prototype.constructor = DerbyForServer;
-
-DerbyForServer.prototype.App = require('./AppForServer');
-DerbyForServer.prototype.Page = require('./PageForServer');
-
-DerbyForServer.prototype.run = function(createServer) {
- // In production
- if (this.util.isProduction) return createServer();
- if (cluster.isMaster) {
- console.log('Master pid ', process.pid);
- startWorker();
- } else {
- createServer();
- }
-};
-
-function startWorker() {
- var worker = cluster.fork();
- worker.once('disconnect', function () {
- worker.process.kill();
- });
- worker.on('message', function(message) {
- if (message.type === 'reload') {
- if (worker.disconnecting) return;
- console.log('Killing %d', worker.process.pid);
- worker.process.kill();
- worker.disconnecting = true;
- startWorker();
- }
- });
-}
diff --git a/lib/DerbyStandalone.js b/lib/DerbyStandalone.js
deleted file mode 100644
index a083da55a..000000000
--- a/lib/DerbyStandalone.js
+++ /dev/null
@@ -1,38 +0,0 @@
-var EventEmitter = require('events').EventEmitter;
-var Model = require('racer/lib/Model/ModelStandalone');
-var util = require('racer/lib/util');
-var App = require('./App');
-var Page = require('./Page');
-var components = require('./components');
-
-module.exports = DerbyStandalone;
-
-require('./documentListeners').add(document);
-
-// Standard Derby inherits from Racer, but we only set up the event emitter and
-// expose the Model and util here instead
-function DerbyStandalone() {
- EventEmitter.call(this);
-}
-util.mergeInto(DerbyStandalone.prototype, EventEmitter.prototype);
-DerbyStandalone.prototype.Model = Model;
-DerbyStandalone.prototype.util = util;
-
-DerbyStandalone.prototype.App = AppStandalone;
-DerbyStandalone.prototype.Page = Page;
-DerbyStandalone.prototype.Component = components.Component;
-
-DerbyStandalone.prototype.createApp = function() {
- return new this.App(this);
-};
-
-function AppStandalone(derby) {
- App.call(this, derby);
-}
-AppStandalone.prototype = Object.create(App.prototype);
-AppStandalone.prototype.constructor = AppStandalone;
-
-AppStandalone.prototype._init = function() {
- this.model = new this.derby.Model();
- this.createPage();
-};
diff --git a/lib/Dom.js b/lib/Dom.js
deleted file mode 100644
index ed582d6ca..000000000
--- a/lib/Dom.js
+++ /dev/null
@@ -1,109 +0,0 @@
-module.exports = Dom;
-
-function Dom(controller) {
- this.controller = controller;
- this._listeners = null;
-}
-
-Dom.prototype._initListeners = function() {
- var dom = this;
- this.controller.on('destroy', function domOnDestroy() {
- var listeners = dom._listeners;
- if (!listeners) return;
- for (var i = listeners.length; i--;) {
- listeners[i].remove();
- }
- dom._listeners = null;
- });
- return this._listeners = [];
-};
-
-Dom.prototype._listenerIndex = function(domListener) {
- var listeners = this._listeners;
- if (!listeners) return -1;
- for (var i = listeners.length; i--;) {
- if (listeners[i].equals(domListener)) return i;
- }
- return -1;
-};
-
-Dom.prototype.addListener = function(type, target, listener, useCapture) {
- if (typeof target === 'function') {
- useCapture = listener;
- listener = target;
- target = document;
- }
- var domListener =
- (type === 'destroy') ? new DestroyListener(target, listener) :
- new DomListener(type, target, listener, useCapture);
- if (-1 === this._listenerIndex(domListener)) {
- var listeners = this._listeners || this._initListeners();
- listeners.push(domListener);
- }
- domListener.add();
-};
-Dom.prototype.on = Dom.prototype.addListener;
-
-Dom.prototype.once = function(type, target, listener, useCapture) {
- if (typeof target === 'function') {
- useCapture = listener;
- listener = target;
- target = document;
- }
- this.addListener(type, target, wrappedListener, useCapture);
- var dom = this;
- function wrappedListener() {
- dom.removeListener(type, target, wrappedListener, useCapture);
- return listener.apply(this, arguments);
- }
-};
-
-Dom.prototype.removeListener = function(type, target, listener, useCapture) {
- if (typeof target === 'function') {
- useCapture = listener;
- listener = target;
- target = document;
- }
- var domListener = new DomListener(type, target, listener, useCapture);
- domListener.remove();
- var i = this._listenerIndex(domListener);
- if (i > -1) this._listeners.splice(i, 1);
-};
-
-function DomListener(type, target, listener, useCapture) {
- this.type = type;
- this.target = target;
- this.listener = listener;
- this.useCapture = !!useCapture;
-}
-DomListener.prototype.equals = function(domListener) {
- return this.listener === domListener.listener &&
- this.target === domListener.target &&
- this.type === domListener.type &&
- this.useCapture === domListener.useCapture;
-};
-DomListener.prototype.add = function() {
- this.target.addEventListener(this.type, this.listener, this.useCapture);
-};
-DomListener.prototype.remove = function() {
- this.target.removeEventListener(this.type, this.listener, this.useCapture);
-};
-
-function DestroyListener(target, listener) {
- DomListener.call(this, 'destroy', target, listener);
-}
-DestroyListener.prototype = new DomListener();
-DestroyListener.prototype.add = function() {
- var listeners = this.target.$destroyListeners || (this.target.$destroyListeners = []);
- if (listeners.indexOf(this.listener) === -1) {
- listeners.push(this.listener);
- }
-};
-DestroyListener.prototype.remove = function() {
- var listeners = this.target.$destroyListeners;
- if (!listeners) return;
- var index = listeners.indexOf(this.listener);
- if (index !== -1) {
- listeners.splice(index, 1);
- }
-};
diff --git a/lib/Page.js b/lib/Page.js
deleted file mode 100644
index 30622a43b..000000000
--- a/lib/Page.js
+++ /dev/null
@@ -1,414 +0,0 @@
-var derbyTemplates = require('./templates');
-var contexts = derbyTemplates.contexts;
-var expressions = derbyTemplates.expressions;
-var templates = derbyTemplates.templates;
-var DependencyOptions = derbyTemplates.options.DependencyOptions;
-var util = require('racer/lib/util');
-var components = require('./components');
-var EventModel = require('./eventmodel');
-var textDiff = require('./textDiff');
-var Controller = require('./Controller');
-var documentListeners = require('./documentListeners');
-
-module.exports = Page;
-
-function Page(app, model) {
- Controller.call(this, app, this, model);
- this.params = null;
- if (this.init) this.init(model);
- this.context = this._createContext();
- this._eventModel = null;
- this._removeModelListeners = null;
- this._components = {};
- this._addListeners();
-}
-
-util.mergeInto(Page.prototype, Controller.prototype);
-
-Page.prototype.$bodyClass = function(ns) {
- if (!ns) return;
- var classNames = [];
- var segments = ns.split(':');
- for (var i = 0, len = segments.length; i < len; i++) {
- var className = segments.slice(0, i + 1).join('-');
- classNames.push(className);
- }
- return classNames.join(' ');
-};
-
-Page.prototype.$preventDefault = function(e) {
- e.preventDefault();
-};
-
-Page.prototype.$stopPropagation = function(e) {
- e.stopPropagation();
-};
-
-Page.prototype._setRenderParams = function(ns) {
- this.model.set('$render.ns', ns);
- this.model.set('$render.params', this.params);
- this.model.set('$render.url', this.params && this.params.url);
- this.model.set('$render.query', this.params && this.params.query);
-};
-
-Page.prototype._setRenderPrefix = function(ns) {
- var prefix = (ns) ? ns + ':' : '';
- this.model.set('$render.prefix', prefix);
-};
-
-Page.prototype.get = function(viewName, ns, unescaped) {
- this._setRenderPrefix(ns);
- var view = this.getView(viewName, ns);
- return view.get(this.context, unescaped);
-};
-
-Page.prototype.getFragment = function(viewName, ns) {
- this._setRenderPrefix(ns);
- var view = this.getView(viewName, ns);
- return view.getFragment(this.context);
-};
-
-Page.prototype.getView = function(viewName, ns) {
- return this.app.views.find(viewName, ns);
-};
-
-Page.prototype.render = function(ns) {
- this.app.emit('render', this);
- this.context.pause();
- this._setRenderParams(ns);
- var titleFragment = this.getFragment('TitleElement', ns);
- var bodyFragment = this.getFragment('BodyElement', ns);
- var titleElement = document.getElementsByTagName('title')[0];
- titleElement.parentNode.replaceChild(titleFragment, titleElement);
- document.body.parentNode.replaceChild(bodyFragment, document.body);
- this.context.unpause();
- if (this.create) this.create(this.model, this.dom);
- this.app.emit('routeDone', this, 'render');
-};
-
-Page.prototype.attach = function() {
- this.context.pause();
- var ns = this.model.get('$render.ns');
- var titleView = this.getView('TitleElement', ns);
- var bodyView = this.getView('BodyElement', ns);
- var titleElement = document.getElementsByTagName('title')[0];
- titleView.attachTo(titleElement.parentNode, titleElement, this.context);
- bodyView.attachTo(document.body.parentNode, document.body, this.context);
- this.context.unpause();
- if (this.create) this.create(this.model, this.dom);
-};
-
-Page.prototype._createContext = function() {
- var contextMeta = new contexts.ContextMeta();
- contextMeta.views = this.app && this.app.views;
- var context = new contexts.Context(contextMeta, this);
- context.expression = new expressions.PathExpression([]);
- context.alias = '#root';
- return context;
-};
-
-Page.prototype._addListeners = function() {
- var eventModel = this._eventModel = new EventModel();
- this._addModelListeners(eventModel);
- this._addContextListeners(eventModel);
-};
-
-Page.prototype.destroy = function() {
- this.emit('destroy');
- this._removeModelListeners();
- for (var id in this._components) {
- var component = this._components[id];
- component.destroy();
- }
- // Remove all data, refs, listeners, and reactive functions
- // for the previous page
- var silentModel = this.model.silent();
- silentModel.destroy('_page');
- silentModel.destroy('$components');
- // Unfetch and unsubscribe from all queries and documents
- if (silentModel.unloadAll) {
- silentModel.unloadAll();
- }
-};
-
-Page.prototype._addModelListeners = function(eventModel) {
- var model = this.model;
- if (!model) return;
- // Registering model listeners with the *Immediate events helps to prevent
- // a bug with binding updates where a model listener causes a change to the
- // path being listened on, directly or indirectly.
-
- // TODO: Remove this when upgrading Racer to the next major version. Feature
- // detect which type of event listener to register by emitting a test event
- if (useLegacyListeners(model)) {
- return this._addModelListenersLegacy(eventModel);
- }
-
- // `util.castSegments(segments)` is needed to cast string segments into
- // numbers, since EventModel#child does typeof checks against segments. This
- // could be done once in Racer's Model#emit, instead of in every listener.
- var changeListener = model.on('changeImmediate', function onChange(segments, event) {
- // The pass parameter is passed in for special handling of updates
- // resulting from stringInsert or stringRemove
- segments = util.castSegments(segments.slice());
- eventModel.set(segments, event.previous, event.passed);
- });
- var loadListener = model.on('loadImmediate', function onLoad(segments) {
- segments = util.castSegments(segments.slice());
- eventModel.set(segments);
- });
- var unloadListener = model.on('unloadImmediate', function onUnload(segments, event) {
- segments = util.castSegments(segments.slice());
- eventModel.set(segments, event.previous);
- });
- var insertListener = model.on('insertImmediate', function onInsert(segments, event) {
- segments = util.castSegments(segments.slice());
- eventModel.insert(segments, event.index, event.values.length);
- });
- var removeListener = model.on('removeImmediate', function onRemove(segments, event) {
- segments = util.castSegments(segments.slice());
- eventModel.remove(segments, event.index, event.values.length);
- });
- var moveListener = model.on('moveImmediate', function onMove(segments, event) {
- segments = util.castSegments(segments.slice());
- eventModel.move(segments, event.from, event.to, event.howMany);
- });
-
- this._removeModelListeners = function() {
- model.removeListener('changeImmediate', changeListener);
- model.removeListener('loadImmediate', loadListener);
- model.removeListener('unloadImmediate', unloadListener);
- model.removeListener('insertImmediate', insertListener);
- model.removeListener('removeImmediate', removeListener);
- model.removeListener('moveImmediate', moveListener);
- };
-};
-function useLegacyListeners(model) {
- var useLegacy = true;
- // model.once is broken in older racer, so manually remove event
- var listener = model.on('changeImmediate', function(segments, event) {
- model.removeListener('changeImmediate', listener);
- // Older Racer emits an array of eventArgs, whereas newer racer emits an event object
- useLegacy = Array.isArray(event);
- });
- model.set('$derby.testEvent', true);
- return useLegacy;
-}
-Page.prototype._addModelListenersLegacy = function(eventModel) {
- var model = this.model;
- if (!model) return;
-
- // `util.castSegments(segments)` is needed to cast string segments into
- // numbers, since EventModel#child does typeof checks against segments. This
- // could be done once in Racer's Model#emit, instead of in every listener.
- var changeListener = model.on('changeImmediate', function onChange(segments, eventArgs) {
- // eventArgs[0] is the new value, which Derby bindings don't use directly.
- var previous = eventArgs[1];
- // The pass parameter is passed in for special handling of updates
- // resulting from stringInsert or stringRemove
- var pass = eventArgs[2];
- segments = util.castSegments(segments.slice());
- eventModel.set(segments, previous, pass);
- });
- var loadListener = model.on('loadImmediate', function onLoad(segments) {
- segments = util.castSegments(segments.slice());
- eventModel.set(segments);
- });
- var unloadListener = model.on('unloadImmediate', function onUnload(segments) {
- segments = util.castSegments(segments.slice());
- eventModel.set(segments);
- });
- var insertListener = model.on('insertImmediate', function onInsert(segments, eventArgs) {
- var index = eventArgs[0];
- var values = eventArgs[1];
- segments = util.castSegments(segments.slice());
- eventModel.insert(segments, index, values.length);
- });
- var removeListener = model.on('removeImmediate', function onRemove(segments, eventArgs) {
- var index = eventArgs[0];
- var values = eventArgs[1];
- segments = util.castSegments(segments.slice());
- eventModel.remove(segments, index, values.length);
- });
- var moveListener = model.on('moveImmediate', function onMove(segments, eventArgs) {
- var from = eventArgs[0];
- var to = eventArgs[1];
- var howMany = eventArgs[2];
- segments = util.castSegments(segments.slice());
- eventModel.move(segments, from, to, howMany);
- });
-
- this._removeModelListeners = function() {
- model.removeListener('changeImmediate', changeListener);
- model.removeListener('loadImmediate', loadListener);
- model.removeListener('unloadImmediate', unloadListener);
- model.removeListener('insertImmediate', insertListener);
- model.removeListener('removeImmediate', removeListener);
- model.removeListener('moveImmediate', moveListener);
- };
-};
-
-Page.prototype._addContextListeners = function(eventModel) {
- this.context.meta.addBinding = addBinding;
- this.context.meta.removeBinding = removeBinding;
- this.context.meta.removeNode = removeNode;
- this.context.meta.addItemContext = addItemContext;
- this.context.meta.removeItemContext = removeItemContext;
-
- function addItemContext(context) {
- var segments = context.expression.resolve(context);
- eventModel.addItemContext(segments, context);
- }
- function removeItemContext(context) {
- // TODO
- }
- function addBinding(binding) {
- patchTextBinding(binding);
- var expressions = binding.template.expressions;
- if (expressions) {
- for (var i = 0, len = expressions.length; i < len; i++) {
- addDependencies(eventModel, expressions[i], binding);
- }
- } else {
- var expression = binding.template.expression;
- addDependencies(eventModel, expression, binding);
- }
- }
- function removeBinding(binding) {
- var bindingWrappers = binding.meta;
- if (!bindingWrappers) return;
- for (var i = bindingWrappers.length; i--;) {
- eventModel.removeBinding(bindingWrappers[i]);
- }
- }
- function removeNode(node) {
- var component = node.$component;
- if (component) component.destroy();
- var destroyListeners = node.$destroyListeners;
- if (destroyListeners) {
- for (var i = 0; i < destroyListeners.length; i++) {
- destroyListeners[i]();
- }
- }
- }
-};
-
-function addDependencies(eventModel, expression, binding) {
- var bindingWrapper = new BindingWrapper(eventModel, expression, binding);
- bindingWrapper.updateDependencies();
-}
-
-// The code here uses object-based set pattern where objects are keyed using
-// sequentially generated IDs.
-var nextId = 1;
-function BindingWrapper(eventModel, expression, binding) {
- this.eventModel = eventModel;
- this.expression = expression;
- this.binding = binding;
- this.id = nextId++;
- this.eventModels = null;
- this.dependencies = null;
- this.ignoreTemplateDependency = (
- binding instanceof components.ComponentAttributeBinding
- ) || (
- (binding.template instanceof templates.DynamicText) &&
- (binding instanceof templates.RangeBinding)
- );
- if (binding.meta) {
- binding.meta.push(this);
- } else {
- binding.meta = [this];
- }
-}
-BindingWrapper.prototype.updateDependencies = function() {
- var dependencyOptions;
- if (this.ignoreTemplateDependency && this.binding.condition instanceof templates.Template) {
- dependencyOptions = new DependencyOptions();
- dependencyOptions.setIgnoreTemplate(this.binding.condition);
- }
- var dependencies = this.expression.dependencies(this.binding.context, dependencyOptions);
- if (this.dependencies) {
- // Do nothing if dependencies haven't changed
- if (equalDependencies(this.dependencies, dependencies)) return;
- // Otherwise, remove current dependencies
- this.eventModel.removeBinding(this);
- }
- // Add new dependencies
- if (!dependencies) return;
- this.dependencies = dependencies;
- for (var i = 0, len = dependencies.length; i < len; i++) {
- var dependency = dependencies[i];
- if (dependency) this.eventModel.addBinding(dependency, this);
- }
-};
-BindingWrapper.prototype.update = function(previous, pass) {
- this.binding.update(previous, pass);
- this.updateDependencies();
-};
-BindingWrapper.prototype.insert = function(index, howMany) {
- this.binding.insert(index, howMany);
- this.updateDependencies();
-};
-BindingWrapper.prototype.remove = function(index, howMany) {
- this.binding.remove(index, howMany);
- this.updateDependencies();
-};
-BindingWrapper.prototype.move = function(from, to, howMany) {
- this.binding.move(from, to, howMany);
- this.updateDependencies();
-};
-
-function equalDependencies(a, b) {
- var lenA = a ? a.length : -1;
- var lenB = b ? b.length : -1;
- if (lenA !== lenB) return false;
- for (var i = 0; i < lenA; i++) {
- var itemA = a[i];
- var itemB = b[i];
- var lenItemA = itemA ? itemA.length : -1;
- var lenItemB = itemB ? itemB.length : -1;
- if (lenItemA !== lenItemB) return false;
- for (var j = 0; j < lenItemB; j++) {
- if (itemA[j] !== itemB[j]) return false;
- }
- }
- return true;
-}
-
-function patchTextBinding(binding) {
- if (
- binding instanceof templates.AttributeBinding &&
- binding.name === 'value' &&
- (binding.element.tagName === 'INPUT' || binding.element.tagName === 'TEXTAREA') &&
- documentListeners.inputSupportsSelection(binding.element) &&
- binding.template.expression.resolve(binding.context)
- ) {
- binding.update = textInputUpdate;
- }
-}
-
-function textInputUpdate(previous, pass) {
- textUpdate(this, this.element, previous, pass);
-}
-function textUpdate(binding, element, previous, pass) {
- if (pass) {
- if (pass.$event && pass.$event.target === element) {
- return;
- } else if (pass.$stringInsert) {
- return textDiff.onStringInsert(
- element,
- previous,
- pass.$stringInsert.index,
- pass.$stringInsert.text
- );
- } else if (pass.$stringRemove) {
- return textDiff.onStringRemove(
- element,
- previous,
- pass.$stringRemove.index,
- pass.$stringRemove.howMany
- );
- }
- }
- binding.template.update(binding.context, binding);
-}
diff --git a/lib/PageForServer.js b/lib/PageForServer.js
deleted file mode 100644
index 20d44b085..000000000
--- a/lib/PageForServer.js
+++ /dev/null
@@ -1,93 +0,0 @@
-var Page = require('./Page');
-
-module.exports = PageForServer;
-function PageForServer(app, model, req, res) {
- Page.call(this, app, model);
- this.req = req;
- this.res = res;
-}
-
-PageForServer.prototype = Object.create(Page.prototype);
-PageForServer.prototype.constructor = PageForServer;
-
-PageForServer.prototype.render = function(status, ns) {
- if (typeof status !== 'number') {
- ns = status;
- status = null;
- }
- this.app.emit('render', this);
-
- if (status) this.res.statusCode = status;
- // Prevent the browser from storing the HTML response in its back cache, since
- // that will cause it to render with the data from the initial load first
- this.res.setHeader('Cache-Control', 'no-store');
- // Set HTML utf-8 content type unless already set
- if (!this.res.getHeader('Content-Type')) {
- this.res.setHeader('Content-Type', 'text/html; charset=utf-8');
- }
-
- this._setRenderParams(ns);
- var pageHtml = this.get('Page', ns);
- this.res.write(pageHtml);
- this.app.emit('htmlDone', this);
-
- this.res.write('' + tailHtml);
- page.app.emit('routeDone', page, 'render');
- });
-};
-
-PageForServer.prototype.renderStatic = function(status, ns) {
- if (typeof status !== 'number') {
- ns = status;
- status = null;
- }
- this.app.emit('renderStatic', this);
-
- if (status) this.res.statusCode = status;
- this.params = pageParams(this.req);
- this._setRenderParams(ns);
- var pageHtml = this.get('Page', ns);
- var tailHtml = this.get('Tail', ns);
- this.res.send(pageHtml + tailHtml);
- this.app.emit('routeDone', this, 'renderStatic');
-};
-
-// Don't register any listeners on the server
-PageForServer.prototype._addListeners = function() {};
-
-function stringifyBundle(bundle) {
- var json = JSON.stringify(bundle);
- return json.replace(/<[\/!]/g, function(match) {
- // Replace the end tag sequence with an equivalent JSON string to make
- // sure the script is not prematurely closed
- if (match === '') return '<\\/';
- // Replace the start of an HTML comment tag sequence with an equivalent
- // JSON string
- if (match === ' 0 : !!value;
-}
-
-function pathSegments(segments) {
- var result = [];
- for (var i = 0; i < segments.length; i++) {
- var segment = segments[i];
- result[i] = (typeof segment === 'object') ? segment.item : segment;
- }
- return result;
-}
-
-function renderValue(value, context) {
- return (typeof value !== 'object') ? value :
- (value instanceof Template) ? renderTemplate(value, context) :
- (Array.isArray(value)) ? renderArray(value, context) :
- renderObject(value, context);
-}
-function renderTemplate(value, context) {
- var i = 1000;
- while (value instanceof Template) {
- if (--i < 0) throw new Error('Maximum template render passes exceeded');
- value = value.get(context, true);
- }
- return value;
-}
-function renderArray(array, context) {
- for (var i = 0; i < array.length; i++) {
- if (hasTemplateProperty(array[i])) {
- return renderArrayProperties(array, context);
- }
- }
- return array;
-}
-function renderObject(object, context) {
- return (hasTemplateProperty(object)) ?
- renderObjectProperties(object, context) : object;
-}
-function hasTemplateProperty(object) {
- if (!object) return false;
- if (object.constructor !== Object) return false;
- for (var key in object) {
- if (object[key] instanceof Template) return true;
- }
- return false;
-}
-function renderArrayProperties(array, context) {
- var out = new Array(array.length);
- for (var i = 0; i < array.length; i++) {
- out[i] = renderValue(array[i], context);
- }
- return out;
-}
-function renderObjectProperties(object, context) {
- var out = {};
- for (var key in object) {
- out[key] = renderValue(object[key], context);
- }
- return out;
-}
-
-function ExpressionMeta(source, blockType, isEnd, as, keyAs, unescaped, bindType, valueType) {
- this.source = source;
- this.blockType = blockType;
- this.isEnd = isEnd;
- this.as = as;
- this.keyAs = keyAs;
- this.unescaped = unescaped;
- this.bindType = bindType;
- this.valueType = valueType;
-}
-ExpressionMeta.prototype.module = 'expressions';
-ExpressionMeta.prototype.type = 'ExpressionMeta';
-ExpressionMeta.prototype.serialize = function() {
- return serializeObject.instance(
- this,
- this.source,
- this.blockType,
- this.isEnd,
- this.as,
- this.keyAs,
- this.unescaped,
- this.bindType,
- this.valueType
- );
-};
-
-function Expression(meta) {
- this.meta = meta;
-}
-Expression.prototype.module = 'expressions';
-Expression.prototype.type = 'Expression';
-Expression.prototype.serialize = function() {
- return serializeObject.instance(this, this.meta);
-};
-Expression.prototype.toString = function() {
- return this.meta && this.meta.source;
-};
-Expression.prototype.truthy = function(context) {
- var blockType = this.meta.blockType;
- if (blockType === 'else') return true;
- var value = this.get(context, true);
- var truthy = templateTruthy(value);
- return (blockType === 'unless') ? !truthy : truthy;
-};
-Expression.prototype.get = function() {};
-// Return the expression's segment list with context objects
-Expression.prototype.resolve = function() {};
-// Return a list of segment lists or null
-Expression.prototype.dependencies = function() {};
-// Return the pathSegments that the expression currently resolves to or null
-Expression.prototype.pathSegments = function(context) {
- var segments = this.resolve(context);
- return segments && pathSegments(segments);
-};
-Expression.prototype.set = function(context, value) {
- var segments = this.pathSegments(context);
- if (!segments) throw new Error('Expression does not support setting');
- context.controller.model._set(segments, value);
-};
-Expression.prototype._resolvePatch = function(context, segments) {
- return (context && context.expression === this && context.item != null) ?
- segments.concat(context) : segments;
-};
-Expression.prototype.isUnbound = function(context) {
- // If the template being rendered has an explicit bindType keyword, such as:
- // {{unbound #item.text}}
- var bindType = this.meta && this.meta.bindType;
- if (bindType === 'unbound') return true;
- if (bindType === 'bound') return false;
- // Otherwise, inherit from the context
- return context.unbound;
-};
-Expression.prototype._lookupAndContextifyValue = function(value, context) {
- if (this.segments && this.segments.length) {
- // If expression has segments, e.g. `bar.baz` in `#foo.bar.baz`, then
- // render the base value (e.g. `#foo`) if it's a template and look up the
- // value at the indicated path.
- value = renderTemplate(value, context);
- value = lookup(this.segments, value);
- }
- if (value instanceof Template && !(value instanceof templates.ContextClosure)) {
- // If we're not immediately rendering the template, then create a ContextClosure
- // so that the value renders with the correct context later.
- value = new templates.ContextClosure(value, context);
- }
- return value;
-};
-
-
-function LiteralExpression(value, meta) {
- this.value = value;
- this.meta = meta;
-}
-LiteralExpression.prototype = Object.create(Expression.prototype);
-LiteralExpression.prototype.constructor = LiteralExpression;
-LiteralExpression.prototype.type = 'LiteralExpression';
-LiteralExpression.prototype.serialize = function() {
- return serializeObject.instance(this, this.value, this.meta);
-};
-LiteralExpression.prototype.get = function() {
- return this.value;
-};
-
-function PathExpression(segments, meta) {
- this.segments = segments;
- this.meta = meta;
-}
-PathExpression.prototype = Object.create(Expression.prototype);
-PathExpression.prototype.constructor = PathExpression;
-PathExpression.prototype.type = 'PathExpression';
-PathExpression.prototype.serialize = function() {
- return serializeObject.instance(this, this.segments, this.meta);
-};
-PathExpression.prototype.get = function(context) {
- // See View::dependencies. This is needed in order to handle the case of
- // getting dependencies within a component template, in which case we cannot
- // access model data separate from rendering.
- if (!context.controller) return;
- return lookup(this.segments, context.controller.model.data);
-};
-PathExpression.prototype.resolve = function(context) {
- // See View::dependencies. This is needed in order to handle the case of
- // getting dependencies within a component template, in which case we cannot
- // access model data separate from rendering.
- if (!context.controller) return;
- var segments = concat(context.controller._scope, this.segments);
- return this._resolvePatch(context, segments);
-};
-PathExpression.prototype.dependencies = function(context, options) {
- // See View::dependencies. This is needed in order to handle the case of
- // getting dependencies within a component template, in which case we cannot
- // access model data separate from rendering.
- if (!context.controller) return;
- var value = lookup(this.segments, context.controller.model.data);
- var dependencies = getDependencies(value, context, options);
- return appendDependency(dependencies, this, context);
-};
-
-function RelativePathExpression(segments, meta) {
- this.segments = segments;
- this.meta = meta;
-}
-RelativePathExpression.prototype = Object.create(Expression.prototype);
-RelativePathExpression.prototype.constructor = RelativePathExpression;
-RelativePathExpression.prototype.type = 'RelativePathExpression';
-RelativePathExpression.prototype.serialize = function() {
- return serializeObject.instance(this, this.segments, this.meta);
-};
-RelativePathExpression.prototype.get = function(context) {
- var relativeContext = context.forRelative(this);
- var value = relativeContext.get();
- return this._lookupAndContextifyValue(value, relativeContext);
-};
-RelativePathExpression.prototype.resolve = function(context) {
- var relativeContext = context.forRelative(this);
- var base = (relativeContext.expression) ?
- relativeContext.expression.resolve(relativeContext) :
- [];
- if (!base) return;
- var segments = base.concat(this.segments);
- return this._resolvePatch(context, segments);
-};
-RelativePathExpression.prototype.dependencies = function(context, options) {
- // Return inner dependencies from our ancestor
- // (e.g., {{ with foo[bar] }} ... {{ this.x }} has 'bar' as a dependency.)
- var relativeContext = context.forRelative(this);
- var dependencies = relativeContext.expression &&
- relativeContext.expression.dependencies(relativeContext, options);
- return swapLastDependency(dependencies, this, context);
-};
-
-function AliasPathExpression(alias, segments, meta) {
- this.alias = alias;
- this.segments = segments;
- this.meta = meta;
-}
-AliasPathExpression.prototype = Object.create(Expression.prototype);
-AliasPathExpression.prototype.constructor = AliasPathExpression;
-AliasPathExpression.prototype.type = 'AliasPathExpression';
-AliasPathExpression.prototype.serialize = function() {
- return serializeObject.instance(this, this.alias, this.segments, this.meta);
-};
-AliasPathExpression.prototype.get = function(context) {
- var aliasContext = context.forAlias(this.alias);
- if (!aliasContext) return;
- if (aliasContext.keyAlias === this.alias) {
- return aliasContext.item;
- }
- var value = aliasContext.get();
- return this._lookupAndContextifyValue(value, aliasContext);
-};
-AliasPathExpression.prototype.resolve = function(context) {
- var aliasContext = context.forAlias(this.alias);
- if (!aliasContext) return;
- if (aliasContext.keyAlias === this.alias) return;
- var base = aliasContext.expression.resolve(aliasContext);
- if (!base) return;
- var segments = base.concat(this.segments);
- return this._resolvePatch(context, segments);
-};
-AliasPathExpression.prototype.dependencies = function(context, options) {
- var aliasContext = context.forAlias(this.alias);
- if (!aliasContext) return;
- if (aliasContext.keyAlias === this.alias) {
- // For keyAliases, use a dependency of the entire list, so that it will
- // always update when the list itself changes. This is over-binding, but
- // would otherwise be much more complex
- var base = aliasContext.expression.resolve(aliasContext.parent);
- if (!base) return;
- return [base];
- }
-
- var dependencies = aliasContext.expression.dependencies(aliasContext, options);
- return swapLastDependency(dependencies, this, context);
-};
-
-function AttributePathExpression(attribute, segments, meta) {
- this.attribute = attribute;
- this.segments = segments;
- this.meta = meta;
-}
-AttributePathExpression.prototype = Object.create(Expression.prototype);
-AttributePathExpression.prototype.constructor = AttributePathExpression;
-AttributePathExpression.prototype.type = 'AttributePathExpression';
-AttributePathExpression.prototype.serialize = function() {
- return serializeObject.instance(this, this.attribute, this.segments, this.meta);
-};
-AttributePathExpression.prototype.get = function(context) {
- var attributeContext = context.forAttribute(this.attribute);
- if (!attributeContext) return;
- var value = attributeContext.attributes[this.attribute];
- if (value instanceof Expression) {
- value = value.get(attributeContext);
- }
- return this._lookupAndContextifyValue(value, attributeContext);
-};
-AttributePathExpression.prototype.resolve = function(context) {
- var attributeContext = context.forAttribute(this.attribute);
- if (!attributeContext) return;
- // Attributes may be a template, an expression, or a literal value
- var base;
- var value = attributeContext.attributes[this.attribute];
- if (value instanceof Expression || value instanceof Template) {
- base = value.resolve(attributeContext);
- }
- if (!base) return;
- var segments = base.concat(this.segments);
- return this._resolvePatch(context, segments);
-};
-AttributePathExpression.prototype.dependencies = function(context, options) {
- var attributeContext = context.forAttribute(this.attribute);
- if (!attributeContext) return;
-
- // Attributes may be a template, an expression, or a literal value
- var value = attributeContext.attributes[this.attribute];
- var dependencies = getDependencies(value, attributeContext, options);
- return swapLastDependency(dependencies, this, context);
-};
-
-function BracketsExpression(before, inside, afterSegments, meta) {
- this.before = before;
- this.inside = inside;
- this.afterSegments = afterSegments;
- this.meta = meta;
-}
-BracketsExpression.prototype = Object.create(Expression.prototype);
-BracketsExpression.prototype.constructor = BracketsExpression;
-BracketsExpression.prototype.type = 'BracketsExpression';
-BracketsExpression.prototype.serialize = function() {
- return serializeObject.instance(this, this.before, this.inside, this.afterSegments, this.meta);
-};
-BracketsExpression.prototype.get = function(context) {
- var inside = this.inside.get(context);
- if (inside == null) return;
- var before = this.before.get(context);
- if (!before) return;
- var base = before[inside];
- return (this.afterSegments) ? lookup(this.afterSegments, base) : base;
-};
-BracketsExpression.prototype.resolve = function(context) {
- // Get and split the current value of the expression inside the brackets
- var inside = this.inside.get(context);
- if (inside == null) return;
-
- // Concat the before, inside, and optional after segments
- var base = this.before.resolve(context);
- if (!base) return;
- var segments = (this.afterSegments) ?
- base.concat(inside, this.afterSegments) :
- base.concat(inside);
- return this._resolvePatch(context, segments);
-};
-BracketsExpression.prototype.dependencies = function(context, options) {
- var before = this.before.dependencies(context, options);
- if (before) before.pop();
- var inner = this.inside.dependencies(context, options);
- var dependencies = concat(before, inner);
- return appendDependency(dependencies, this, context);
-};
-
-// This Expression is used to wrap a template so that when its containing
-// Expression--such as an ObjectExpression or ArrayExpression--is evaluated,
-// it returns the template unrendered and wrapped in the current context.
-// Separating evaluation of the containing expression from template rendering
-// is used to support array attributes of views. This way, we can evaluate an
-// array and iterate through it separately from rendering template content
-function DeferRenderExpression(template, meta) {
- if (!(template instanceof Template)) {
- throw new Error('DeferRenderExpression requires a Template argument');
- }
- this.template = template;
- this.meta = meta;
-}
-DeferRenderExpression.prototype = Object.create(Expression.prototype);
-DeferRenderExpression.prototype.constructor = DeferRenderExpression;
-DeferRenderExpression.prototype.type = 'DeferRenderExpression';
-DeferRenderExpression.prototype.serialize = function() {
- return serializeObject.instance(this, this.template, this.meta);
-};
-DeferRenderExpression.prototype.get = function(context) {
- return new templates.ContextClosure(this.template, context);
-};
-
-function ArrayExpression(items, afterSegments, meta) {
- this.items = items;
- this.afterSegments = afterSegments;
- this.meta = meta;
-}
-ArrayExpression.prototype = Object.create(Expression.prototype);
-ArrayExpression.prototype.constructor = ArrayExpression;
-ArrayExpression.prototype.type = 'ArrayExpression';
-ArrayExpression.prototype.serialize = function() {
- return serializeObject.instance(this, this.items, this.afterSegments, this.meta);
-};
-ArrayExpression.prototype.get = function(context) {
- var items = new Array(this.items.length);
- for (var i = 0; i < this.items.length; i++) {
- var value = this.items[i].get(context);
- items[i] = value;
- }
- return (this.afterSegments) ? lookup(this.afterSegments, items) : items;
-};
-ArrayExpression.prototype.dependencies = function(context, options) {
- if (!this.items) return;
- var dependencies;
- for (var i = 0; i < this.items.length; i++) {
- var itemDependencies = this.items[i].dependencies(context, options);
- dependencies = concat(dependencies, itemDependencies);
- }
- return dependencies;
-};
-
-function ObjectExpression(properties, afterSegments, meta) {
- this.properties = properties;
- this.afterSegments = afterSegments;
- this.meta = meta;
-}
-ObjectExpression.prototype = Object.create(Expression.prototype);
-ObjectExpression.prototype.constructor = ObjectExpression;
-ObjectExpression.prototype.type = 'ObjectExpression';
-ObjectExpression.prototype.serialize = function() {
- return serializeObject.instance(this, this.properties, this.afterSegments, this.meta);
-};
-ObjectExpression.prototype.get = function(context) {
- var object = {};
- for (var key in this.properties) {
- var value = this.properties[key].get(context);
- object[key] = value;
- }
- return (this.afterSegments) ? lookup(this.afterSegments, object) : object;
-};
-ObjectExpression.prototype.dependencies = function(context, options) {
- if (!this.properties) return;
- var dependencies;
- for (var key in this.properties) {
- var propertyDependencies = this.properties[key].dependencies(context, options);
- dependencies = concat(dependencies, propertyDependencies);
- }
- return dependencies;
-};
-
-function FnExpression(segments, args, afterSegments, meta) {
- this.segments = segments;
- this.args = args;
- this.afterSegments = afterSegments;
- this.meta = meta;
- var parentSegments = segments && segments.slice();
- this.lastSegment = parentSegments && parentSegments.pop();
- this.parentSegments = (parentSegments && parentSegments.length) ? parentSegments : null;
-}
-FnExpression.prototype = Object.create(Expression.prototype);
-FnExpression.prototype.constructor = FnExpression;
-FnExpression.prototype.type = 'FnExpression';
-FnExpression.prototype.serialize = function() {
- return serializeObject.instance(this, this.segments, this.args, this.afterSegments, this.meta);
-};
-FnExpression.prototype.get = function(context) {
- var value = this.apply(context);
- // Lookup property underneath computed value if needed
- return (this.afterSegments) ? lookup(this.afterSegments, value) : value;
-};
-FnExpression.prototype.apply = function(context, extraInputs) {
- // See View::dependencies. This is needed in order to handle the case of
- // getting dependencies within a component template, in which case we cannot
- // access model data separate from rendering.
- if (!context.controller) return;
- var parent = this._lookupParent(context);
- var fn = parent[this.lastSegment];
- var getFn = fn.get || fn;
- var out = this._applyFn(getFn, context, extraInputs, parent);
- return out;
-};
-FnExpression.prototype._lookupParent = function(context) {
- // Lookup function on current controller
- var controller = context.controller;
- var segments = this.parentSegments;
- var parent = (segments) ? lookup(segments, controller) : controller;
- if (parent && parent[this.lastSegment]) return parent;
- // Otherwise lookup function on page
- var page = controller.page;
- if (controller !== page) {
- parent = (segments) ? lookup(segments, page) : page;
- if (parent && parent[this.lastSegment]) return parent;
- }
- // Otherwise lookup function on global
- parent = (segments) ? lookup(segments, global) : global;
- if (parent && parent[this.lastSegment]) return parent;
- // Throw if not found
- throw new Error('Function not found for: ' + this.segments.join('.'));
-};
-FnExpression.prototype._getInputs = function(context) {
- var inputs = [];
- for (var i = 0, len = this.args.length; i < len; i++) {
- var value = this.args[i].get(context);
- inputs.push(renderValue(value, context));
- }
- return inputs;
-};
-FnExpression.prototype._applyFn = function(fn, context, extraInputs, thisArg) {
- // Apply if there are no path inputs
- if (!this.args) {
- return (extraInputs) ?
- fn.apply(thisArg, extraInputs) :
- fn.call(thisArg);
- }
- // Otherwise, get the current value for path inputs and apply
- var inputs = this._getInputs(context);
- if (extraInputs) {
- for (var i = 0, len = extraInputs.length; i < len; i++) {
- inputs.push(extraInputs[i]);
- }
- }
- return fn.apply(thisArg, inputs);
-};
-FnExpression.prototype.dependencies = function(context, options) {
- var dependencies = [];
- if (!this.args) return dependencies;
- for (var i = 0, len = this.args.length; i < len; i++) {
- var argDependencies = this.args[i].dependencies(context, options);
- if (!argDependencies || argDependencies.length < 1) continue;
- var end = argDependencies.length - 1;
- for (var j = 0; j < end; j++) {
- dependencies.push(argDependencies[j]);
- }
- var last = argDependencies[end];
- if (last[last.length - 1] !== '*') {
- last = last.concat('*');
- }
- dependencies.push(last);
- }
- return dependencies;
-};
-FnExpression.prototype.set = function(context, value) {
- var controller = context.controller;
- var fn, parent;
- while (controller) {
- parent = (this.parentSegments) ?
- lookup(this.parentSegments, controller) :
- controller;
- fn = parent && parent[this.lastSegment];
- if (fn) break;
- controller = controller.parent;
- }
- var setFn = fn && fn.set;
- if (!setFn) throw new Error('No setter function for: ' + this.segments.join('.'));
- var inputs = this._getInputs(context);
- inputs.unshift(value);
- var out = setFn.apply(parent, inputs);
- for (var i in out) {
- this.args[i].set(context, out[i]);
- }
-};
-
-function NewExpression(segments, args, afterSegments, meta) {
- FnExpression.call(this, segments, args, afterSegments, meta);
-}
-NewExpression.prototype = Object.create(FnExpression.prototype);
-NewExpression.prototype.constructor = NewExpression;
-NewExpression.prototype.type = 'NewExpression';
-NewExpression.prototype._applyFn = function(Fn, context) {
- // Apply if there are no path inputs
- if (!this.args) return new Fn();
- // Otherwise, get the current value for path inputs and apply
- var inputs = this._getInputs(context);
- inputs.unshift(null);
- return new (Fn.bind.apply(Fn, inputs))();
-};
-
-function OperatorExpression(name, args, afterSegments, meta) {
- this.name = name;
- this.args = args;
- this.afterSegments = afterSegments;
- this.meta = meta;
- this.getFn = operatorFns.get[name];
- this.setFn = operatorFns.set[name];
-}
-OperatorExpression.prototype = Object.create(FnExpression.prototype);
-OperatorExpression.prototype.constructor = OperatorExpression;
-OperatorExpression.prototype.type = 'OperatorExpression';
-OperatorExpression.prototype.serialize = function() {
- return serializeObject.instance(this, this.name, this.args, this.afterSegments, this.meta);
-};
-OperatorExpression.prototype.apply = function(context) {
- var inputs = this._getInputs(context);
- return this.getFn.apply(null, inputs);
-};
-OperatorExpression.prototype.set = function(context, value) {
- var inputs = this._getInputs(context);
- inputs.unshift(value);
- var out = this.setFn.apply(null, inputs);
- for (var i in out) {
- this.args[i].set(context, out[i]);
- }
-};
-
-function SequenceExpression(args, afterSegments, meta) {
- this.args = args;
- this.afterSegments = afterSegments;
- this.meta = meta;
-}
-SequenceExpression.prototype = Object.create(OperatorExpression.prototype);
-SequenceExpression.prototype.constructor = SequenceExpression;
-SequenceExpression.prototype.type = 'SequenceExpression';
-SequenceExpression.prototype.serialize = function() {
- return serializeObject.instance(this, this.args, this.afterSegments, this.meta);
-};
-SequenceExpression.prototype.name = ',';
-SequenceExpression.prototype.getFn = operatorFns.get[','];
-SequenceExpression.prototype.resolve = function(context) {
- var last = this.args[this.args.length - 1];
- return last.resolve(context);
-};
-SequenceExpression.prototype.dependencies = function(context, options) {
- var dependencies = [];
- for (var i = 0, len = this.args.length; i < len; i++) {
- var argDependencies = this.args[i].dependencies(context, options);
- for (var j = 0, jLen = argDependencies.length; j < jLen; j++) {
- dependencies.push(argDependencies[j]);
- }
- }
- return dependencies;
-};
-
-// For each method that takes a context argument, get the nearest parent view
-// context, then delegate methods to the inner expression
-function ViewParentExpression(expression, meta) {
- this.expression = expression;
- this.meta = meta;
-}
-ViewParentExpression.prototype = Object.create(Expression.prototype);
-ViewParentExpression.prototype.constructor = ViewParentExpression;
-ViewParentExpression.prototype.type = 'ViewParentExpression';
-ViewParentExpression.prototype.serialize = function() {
- return serializeObject.instance(this, this.expression, this.meta);
-};
-ViewParentExpression.prototype.get = function(context) {
- var parentContext = context.forViewParent();
- return this.expression.get(parentContext);
-};
-ViewParentExpression.prototype.resolve = function(context) {
- var parentContext = context.forViewParent();
- return this.expression.resolve(parentContext);
-};
-ViewParentExpression.prototype.dependencies = function(context, options) {
- var parentContext = context.forViewParent();
- return this.expression.dependencies(parentContext, options);
-};
-ViewParentExpression.prototype.pathSegments = function(context) {
- var parentContext = context.forViewParent();
- return this.expression.pathSegments(parentContext);
-};
-ViewParentExpression.prototype.set = function(context, value) {
- var parentContext = context.forViewParent();
- return this.expression.set(parentContext, value);
-};
-
-function ScopedModelExpression(expression, meta) {
- this.expression = expression;
- this.meta = meta;
-}
-ScopedModelExpression.prototype = Object.create(Expression.prototype);
-ScopedModelExpression.prototype.constructor = ScopedModelExpression;
-ScopedModelExpression.prototype.type = 'ScopedModelExpression';
-ScopedModelExpression.prototype.serialize = function() {
- return serializeObject.instance(this, this.expression, this.meta);
-};
-// Return a scoped model instead of the value
-ScopedModelExpression.prototype.get = function(context) {
- var segments = this.pathSegments(context);
- if (!segments) return;
- return context.controller.model.scope(segments.join('.'));
-};
-// Delegate other methods to the inner expression
-ScopedModelExpression.prototype.resolve = function(context) {
- return this.expression.resolve(context);
-};
-ScopedModelExpression.prototype.dependencies = function(context, options) {
- return this.expression.dependencies(context, options);
-};
-ScopedModelExpression.prototype.pathSegments = function(context) {
- return this.expression.pathSegments(context);
-};
-ScopedModelExpression.prototype.set = function(context, value) {
- return this.expression.set(context, value);
-};
-
-function getDependencies(value, context, options) {
- if (value instanceof Expression || value instanceof Template) {
- return value.dependencies(context, options);
- }
-}
-
-function appendDependency(dependencies, expression, context) {
- var segments = expression.resolve(context);
- if (!segments) return dependencies;
- if (dependencies) {
- dependencies.push(segments);
- return dependencies;
- }
- return [segments];
-}
-
-function swapLastDependency(dependencies, expression, context) {
- if (!expression.segments.length) {
- return dependencies;
- }
- var segments = expression.resolve(context);
- if (!segments) return dependencies;
- if (dependencies) {
- dependencies.pop();
- dependencies.push(segments);
- return dependencies;
- }
- return [segments];
-}
diff --git a/lib/templates/index.js b/lib/templates/index.js
deleted file mode 100644
index 22ee76465..000000000
--- a/lib/templates/index.js
+++ /dev/null
@@ -1,5 +0,0 @@
-exports.contexts = require('./contexts');
-exports.expressions = require('./expressions');
-exports.operatorFns = require('./operatorFns');
-exports.options = require('./dependencyOptions');
-exports.templates = require('./templates');
diff --git a/lib/templates/templates.js b/lib/templates/templates.js
deleted file mode 100644
index bc7793837..000000000
--- a/lib/templates/templates.js
+++ /dev/null
@@ -1,2107 +0,0 @@
-if (typeof require === 'function') {
- var serializeObject = require('serialize-object');
-}
-var DependencyOptions = require('./dependencyOptions').DependencyOptions;
-var util = require('./util');
-var concat = util.concat;
-var hasKeys = util.hasKeys;
-var traverseAndCreate = util.traverseAndCreate;
-
-// UPDATE_PROPERTIES map HTML attribute names to an Element DOM property that
-// should be used for setting on bindings updates instead of setAttribute.
-//
-// https://github.com/jquery/jquery/blob/1.x-master/src/attributes/prop.js
-// https://github.com/jquery/jquery/blob/master/src/attributes/prop.js
-// http://webbugtrack.blogspot.com/2007/08/bug-242-setattribute-doesnt-always-work.html
-var BOOLEAN_PROPERTIES = {
- checked: 'checked',
- disabled: 'disabled',
- indeterminate: 'indeterminate',
- readonly: 'readOnly',
- selected: 'selected'
-};
-var INTEGER_PROPERTIES = {
- colspan: 'colSpan',
- maxlength: 'maxLength',
- rowspan: 'rowSpan',
- tabindex: 'tabIndex'
-};
-var STRING_PROPERTIES = {
- cellpadding: 'cellPadding',
- cellspacing: 'cellSpacing',
- 'class': 'className',
- contenteditable: 'contentEditable',
- enctype: 'encoding',
- 'for': 'htmlFor',
- frameborder: 'frameBorder',
- id: 'id',
- title: 'title',
- type: 'type',
- usemap: 'useMap',
- value: 'value'
-};
-var UPDATE_PROPERTIES = {};
-mergeInto(BOOLEAN_PROPERTIES, UPDATE_PROPERTIES);
-mergeInto(INTEGER_PROPERTIES, UPDATE_PROPERTIES);
-mergeInto(STRING_PROPERTIES, UPDATE_PROPERTIES);
-
-// CREATE_PROPERTIES map HTML attribute names to an Element DOM property that
-// should be used for setting on Element rendering instead of setAttribute.
-// input.defaultChecked and input.defaultValue affect the attribute, so we want
-// to use these for initial dynamic rendering. For binding updates,
-// input.checked and input.value are modified.
-var CREATE_PROPERTIES = {};
-mergeInto(UPDATE_PROPERTIES, CREATE_PROPERTIES);
-CREATE_PROPERTIES.checked = 'defaultChecked';
-CREATE_PROPERTIES.value = 'defaultValue';
-
-// http://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements
-var VOID_ELEMENTS = {
- area: true,
- base: true,
- br: true,
- col: true,
- embed: true,
- hr: true,
- img: true,
- input: true,
- keygen: true,
- link: true,
- menuitem: true,
- meta: true,
- param: true,
- source: true,
- track: true,
- wbr: true
-};
-
-var NAMESPACE_URIS = {
- svg: 'http://www.w3.org/2000/svg',
- xlink: 'http://www.w3.org/1999/xlink',
- xmlns: 'http://www.w3.org/2000/xmlns/'
-};
-
-exports.CREATE_PROPERTIES = CREATE_PROPERTIES;
-exports.BOOLEAN_PROPERTIES = BOOLEAN_PROPERTIES;
-exports.INTEGER_PROPERTIES = INTEGER_PROPERTIES;
-exports.STRING_PROPERTIES = STRING_PROPERTIES;
-exports.UPDATE_PROPERTIES = UPDATE_PROPERTIES;
-exports.VOID_ELEMENTS = VOID_ELEMENTS;
-exports.NAMESPACE_URIS = NAMESPACE_URIS;
-
-// Template Classes
-exports.Template = Template;
-exports.Doctype = Doctype;
-exports.Text = Text;
-exports.DynamicText = DynamicText;
-exports.Comment = Comment;
-exports.DynamicComment = DynamicComment;
-exports.Html = Html;
-exports.DynamicHtml = DynamicHtml;
-exports.Element = Element;
-exports.DynamicElement = DynamicElement;
-exports.Block = Block;
-exports.ConditionalBlock = ConditionalBlock;
-exports.EachBlock = EachBlock;
-
-exports.Attribute = Attribute;
-exports.DynamicAttribute = DynamicAttribute;
-
-// Binding Classes
-exports.Binding = Binding;
-exports.NodeBinding = NodeBinding;
-exports.AttributeBinding = AttributeBinding;
-exports.RangeBinding = RangeBinding;
-
-function Template(content, source) {
- this.content = content;
- this.source = source;
-}
-Template.prototype.toString = function() {
- return this.source;
-};
-Template.prototype.get = function(context, unescaped) {
- return contentHtml(this.content, context, unescaped);
-};
-Template.prototype.getFragment = function(context, binding) {
- var fragment = document.createDocumentFragment();
- this.appendTo(fragment, context, binding);
- return fragment;
-};
-Template.prototype.appendTo = function(parent, context) {
- context.pause();
- appendContent(parent, this.content, context);
- context.unpause();
-};
-Template.prototype.attachTo = function(parent, node, context) {
- context.pause();
- var node = attachContent(parent, node, this.content, context);
- context.unpause();
- return node;
-};
-Template.prototype.update = function() {};
-Template.prototype.stringify = function(value) {
- return (value == null) ? '' : value + '';
-};
-Template.prototype.equals = function(other) {
- return this === other;
-};
-Template.prototype.module = 'templates';
-Template.prototype.type = 'Template';
-Template.prototype.serialize = function() {
- return serializeObject.instance(this, this.content, this.source);
-};
-
-
-function Doctype(name, publicId, systemId) {
- this.name = name;
- this.publicId = publicId;
- this.systemId = systemId;
-}
-Doctype.prototype = Object.create(Template.prototype);
-Doctype.prototype.constructor = Doctype;
-Doctype.prototype.get = function() {
- var publicText = (this.publicId) ?
- ' PUBLIC "' + this.publicId + '"' :
- '';
- var systemText = (this.systemId) ?
- (this.publicId) ?
- ' "' + this.systemId + '"' :
- ' SYSTEM "' + this.systemId + '"' :
- '';
- return '';
-};
-Doctype.prototype.appendTo = function() {
- // Doctype could be created via:
- // document.implementation.createDocumentType(this.name, this.publicId, this.systemId)
- // However, it does not appear possible or useful to append it to the
- // document fragment. Therefore, just don't render it in the browser
-};
-Doctype.prototype.attachTo = function(parent, node) {
- if (!node || node.nodeType !== 10) {
- throw attachError(parent, node);
- }
- return node.nextSibling;
-};
-Doctype.prototype.type = 'Doctype';
-Doctype.prototype.serialize = function() {
- return serializeObject.instance(this, this.name, this.publicId, this.systemId);
-};
-
-function Text(data) {
- this.data = data;
- this.escaped = escapeHtml(data);
-}
-Text.prototype = Object.create(Template.prototype);
-Text.prototype.constructor = Text;
-Text.prototype.get = function(context, unescaped) {
- return (unescaped) ? this.data : this.escaped;
-};
-Text.prototype.appendTo = function(parent) {
- var node = document.createTextNode(this.data);
- parent.appendChild(node);
-};
-Text.prototype.attachTo = function(parent, node) {
- return attachText(parent, node, this.data, this);
-};
-Text.prototype.type = 'Text';
-Text.prototype.serialize = function() {
- return serializeObject.instance(this, this.data);
-};
-
-// DynamicText might be more accurately named DynamicContent. When its
-// expression returns a template, it acts similar to a Block, and it renders
-// the template surrounded by comment markers for range replacement. When its
-// expression returns any other type, it renders a DOM Text node with no
-// markers. Text nodes are bound by updating their data property dynamically.
-// The update method must take care to switch between these types of bindings
-// in case the expression return type changes dynamically.
-function DynamicText(expression) {
- this.expression = expression;
- this.unbound = false;
-}
-DynamicText.prototype = Object.create(Template.prototype);
-DynamicText.prototype.constructor = DynamicText;
-DynamicText.prototype.get = function(context, unescaped) {
- var value = this.expression.get(context);
- if (value instanceof Template) {
- do {
- value = value.get(context, unescaped);
- } while (value instanceof Template);
- return value;
- }
- var data = this.stringify(value);
- return (unescaped) ? data : escapeHtml(data);
-};
-DynamicText.prototype.appendTo = function(parent, context, binding) {
- var value = this.expression.get(context);
- if (value instanceof Template) {
- var start = document.createComment(this.expression);
- var end = document.createComment('/' + this.expression);
- var condition = this.getCondition(context);
- parent.appendChild(start);
- value.appendTo(parent, context);
- parent.appendChild(end);
- updateRange(context, binding, this, start, end, null, condition);
- return;
- }
- var data = this.stringify(value);
- var node = document.createTextNode(data);
- parent.appendChild(node);
- addNodeBinding(this, context, node);
-};
-DynamicText.prototype.attachTo = function(parent, node, context) {
- var value = this.expression.get(context);
- if (value instanceof Template) {
- var start = document.createComment(this.expression);
- var end = document.createComment('/' + this.expression);
- var condition = this.getCondition(context);
- parent.insertBefore(start, node || null);
- node = value.attachTo(parent, node, context);
- parent.insertBefore(end, node || null);
- updateRange(context, null, this, start, end, null, condition);
- return node;
- }
- var data = this.stringify(value);
- return attachText(parent, node, data, this, context);
-};
-DynamicText.prototype.update = function(context, binding) {
- if (binding instanceof RangeBinding) {
- this._blockUpdate(context, binding);
- return;
- }
- var value = this.expression.get(context);
- if (value instanceof Template) {
- var start = binding.node;
- if (!start.parentNode) return;
- var end = start;
- var fragment = this.getFragment(context);
- replaceRange(context, start, end, fragment, binding);
- return;
- }
- binding.node.data = this.stringify(value);
-};
-DynamicText.prototype.getCondition = function(context) {
- return this.expression.get(context);
-};
-DynamicText.prototype.type = 'DynamicText';
-DynamicText.prototype.serialize = function() {
- return serializeObject.instance(this, this.expression);
-};
-
-function attachText(parent, node, data, template, context) {
- if (!node) {
- var newNode = document.createTextNode(data);
- parent.appendChild(newNode);
- addNodeBinding(template, context, newNode);
- return;
- }
- if (node.nodeType === 3) {
- // Proceed if nodes already match
- if (node.data === data) {
- addNodeBinding(template, context, node);
- return node.nextSibling;
- }
- data = normalizeLineBreaks(data);
- // Split adjacent text nodes that would have been merged together in HTML
- var nextNode = splitData(node, data.length);
- if (node.data !== data) {
- throw attachError(parent, node);
- }
- addNodeBinding(template, context, node);
- return nextNode;
- }
- // An empty text node might not be created at the end of some text
- if (data === '') {
- var newNode = document.createTextNode('');
- parent.insertBefore(newNode, node || null);
- addNodeBinding(template, context, newNode);
- return node;
- }
- throw attachError(parent, node);
-}
-
-function Comment(data, hooks) {
- this.data = data;
- this.hooks = hooks;
-}
-Comment.prototype = Object.create(Template.prototype);
-Comment.prototype.constructor = Comment;
-Comment.prototype.get = function() {
- return '';
-};
-Comment.prototype.appendTo = function(parent, context) {
- var node = document.createComment(this.data);
- parent.appendChild(node);
- emitHooks(this.hooks, context, node);
-};
-Comment.prototype.attachTo = function(parent, node, context) {
- return attachComment(parent, node, this.data, this, context);
-};
-Comment.prototype.type = 'Comment';
-Comment.prototype.serialize = function() {
- return serializeObject.instance(this, this.data, this.hooks);
-}
-
-function DynamicComment(expression, hooks) {
- this.expression = expression;
- this.hooks = hooks;
-}
-DynamicComment.prototype = Object.create(Template.prototype);
-DynamicComment.prototype.constructor = DynamicComment;
-DynamicComment.prototype.get = function(context) {
- var value = getUnescapedValue(this.expression, context);
- var data = this.stringify(value);
- return '';
-};
-DynamicComment.prototype.appendTo = function(parent, context) {
- var value = getUnescapedValue(this.expression, context);
- var data = this.stringify(value);
- var node = document.createComment(data);
- parent.appendChild(node);
- addNodeBinding(this, context, node);
-};
-DynamicComment.prototype.attachTo = function(parent, node, context) {
- var value = getUnescapedValue(this.expression, context);
- var data = this.stringify(value);
- return attachComment(parent, node, data, this, context);
-};
-DynamicComment.prototype.update = function(context, binding) {
- var value = getUnescapedValue(this.expression, context);
- binding.node.data = this.stringify(value);
-};
-DynamicComment.prototype.type = 'DynamicComment';
-DynamicComment.prototype.serialize = function() {
- return serializeObject.instance(this, this.expression, this.hooks);
-}
-
-function attachComment(parent, node, data, template, context) {
- // Sometimes IE fails to create Comment nodes from HTML or innerHTML.
- // This is an issue inside of