Skip to content

Commit

Permalink
hook up component scripts to the view
Browse files Browse the repository at this point in the history
  • Loading branch information
nateps committed May 14, 2012
1 parent 3fb601a commit 269b628
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 81 deletions.
13 changes: 4 additions & 9 deletions lib/Dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ var racer = require('racer')

module.exports = Dom;

function Dom(model, appExports) {
function Dom(model) {
var dom = this
, fns = this.fns

Expand Down Expand Up @@ -54,15 +54,10 @@ function Dom(model, appExports) {
});

function onTrigger(name, listener, id, e, el, next) {
var fn = listener.fn
, delay = listener.delay
, finish;
var delay = listener.delay
, finish = listener.fn;

if (fn != null) {
finish = fns[fn] || appExports[fn] || lookup(fn, appExports);
if (!finish) return;

} else {
if (!finish) {
// Update the model when the element's value changes
finish = function() {
var value = dom.getMethods[listener.method](el, listener.property)
Expand Down
66 changes: 49 additions & 17 deletions lib/View.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ var defaultSetFns = {
}
};

function View(libraries) {
function View(libraries, appExports) {
this._libraries = libraries || {};
this._appExports = appExports;
this._nonvoidComponents = {};
this.clear();
this.getFns = Object.create(defaultGetFns);
Expand Down Expand Up @@ -120,12 +121,12 @@ View.prototype = {
}
}

renderer = function(ctx) {
renderer = function(ctx, model, triggerPath, triggerId) {
renderer = parse(view, name, template, isString, onBind, boundMacro);
return renderer(ctx);
return renderer(ctx, model, triggerPath, triggerId);
}
render = function(ctx) {
return renderer(ctx);
render = function(ctx, model, triggerPath, triggerId) {
return renderer(ctx, model, triggerPath, triggerId);
}

this._views[name] = render;
Expand All @@ -142,6 +143,16 @@ View.prototype = {
}
}

, _makeComponents: function(components) {
var libraries = this._libraries
, name, component, view;
for (name in components) {
component = components[name];
view = libraries[name].view;
view._makeAll(component.templates, component.instances);
}
}

, _findItem: function(name, ns, prop) {
var items = this[prop]
, item, last, i, segments, testNs;
Expand Down Expand Up @@ -222,7 +233,7 @@ View.prototype = {
, bodyHtml = this.get('header', ns, ctx) +
this.get('body', ns, ctx) +
this.get('footer', ns, ctx);
if (silent) return;
// if (silent) return;

var doc = document
, documentElement = doc.documentElement
Expand Down Expand Up @@ -254,8 +265,15 @@ View.prototype = {
doc.title = title;
}

, _createComponent: function(view, model, name) {

, _createComponent: function(view, model, scope, script, ctx) {
var elements = ctx.$elements
, id;
for (name in elements) {
id = elements[name];
if (typeof id !== 'string') continue;
elements[name] = document.getElementById(id);
}
script.create(model, model.at(scope), view.dom, elements);
}

, escapeHtml: escapeHtml
Expand Down Expand Up @@ -509,17 +527,24 @@ function partialFn(view, name, type, alias, render, macroCtx, macro) {
if (type === 'partial') {
return function(ctx, model, triggerPath, triggerId, value, index, listener) {
var parentMacroCtx = ctx.$macroCtx
, renderCtx, name, out, createComponent;
, renderCtx, scope, createComponent, library, script, scoped;
if (alias) {
name = '_$component.' + view._uniqueId();
scope = '_$component.' + view._uniqueId();
renderCtx = extendCtx(ctx, null, scope, alias);
createComponent = view._createComponent;
if (createComponent) setTimeout(createComponent, 0, view, model, name);
renderCtx = extendCtx(ctx, null, name, alias);
if (createComponent) {
library = view._libraries[name[0]];
script = library && library.scripts[name[1]];
if (script) {
setTimeout(createComponent, 0, view, model, scope, script, renderCtx);
}
}
renderCtx.$fnCtx = script;
} else {
renderCtx = Object.create(ctx);
}
renderCtx.$macroCtx = parentMacroCtx ? extend(parentMacroCtx, macroCtx) : macroCtx;

return render(renderCtx, model, triggerPath);
}
}
Expand Down Expand Up @@ -672,11 +697,18 @@ function isPartial(view, partial) {
function splitPartial(view, partial, ns) {
var i = partial.indexOf(':')
, partialNs = partial.slice(0, i)
, partialName = partial.slice(i + 1);
, partialName = partial.slice(i + 1)
, partialView;
if (partialNs !== view._selfNs) {
view = view._libraries[partialNs].view;
partialView = view._libraries[partialNs].view;
partialView._uniqueId = function() {
return view._uniqueId();
};
partialView.dom = view.dom;
} else {
partialView = view;
}
return [partialNs, partialName, view];
return [partialNs, partialName, partialView];
}

function isNonvoid(view, partial, ns) {
Expand All @@ -699,7 +731,7 @@ function pushVar(view, ns, stack, events, boundMacro, remainder, match, fn) {
, partialName = arr[1]
, alias = partialNs === view._selfNs ? '' : 'self'
render = arr[2]._find(partialName, ns, boundMacro);
fn = partialFn(view, name, 'partial', alias, render, match.macroCtx);
fn = partialFn(view, arr, 'partial', alias, render, match.macroCtx);
}

if (isBound(boundMacro, match, name)) {
Expand Down
53 changes: 43 additions & 10 deletions lib/View.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ var EventDispatcher = require('./EventDispatcher')
, racer = require('racer')
, Promise = racer.util.Promise
, isProduction = racer.util.isProduction
, merge = racer.util.merge
, Model = racer["protected"].Model
, uglify = require('racer/node_modules/uglify-js')
, files = require('./files')
Expand All @@ -13,7 +14,6 @@ var EventDispatcher = require('./EventDispatcher')
, cssError = refresh.cssError
, templateError = refresh.templateError
, View = module.exports = require('./View')

, emptyRes = {
getHeader: empty
, setHeader: empty
Expand All @@ -31,22 +31,25 @@ var EventDispatcher = require('./EventDispatcher')
, emptyEventDispatcher = {
bind: empty
}
, emptyDom = {
bind: empty
}

function empty() {}

function escapeInlineScript(s) {
return s.replace(/<\//g, '<\\/');
}

function loadTemplatesScript(requirePath, templates, instances) {
return "(function() {\n require('" + requirePath + "').view._makeAll(\n " +
(JSON.stringify(templates, null, 2)) + ", " +
(JSON.stringify(instances, null, 2)) + "\n );\n})();";
function loadTemplatesScript(requirePath, templates, instances, libraryData) {
return '(function() {\n' +
'var view = require("' + requirePath + '").view;\n' +
'view._makeAll(\n' +
JSON.stringify(templates, null, 2) + ', ' +
JSON.stringify(instances, null, 2) + ');\n' +
'view._makeComponents(\n' +
JSON.stringify(libraryData, null, 2) + ');\n' +
'})();';
}

// Should only be executed in the browser
delete View.prototype._createComponent;

View.prototype.inline = function(fn) {
Expand All @@ -56,6 +59,7 @@ View.prototype.inline = function(fn) {
View.prototype._load = function(isStatic, callback) {
var view = this
, appFilename, clientName, count, errors, finish, instances, js, options
, libraries, libraryName, library, libraryData
, promise, requirePath, root, templates, fileInfo;

if (isProduction) {
Expand All @@ -78,13 +82,16 @@ View.prototype._load = function(isStatic, callback) {
delete view._loadPromise;
});

libraries = view._libraries;
libraryData = {};
templates = instances = js = null;
errors = {};

if (isStatic) {
root = this._root;
clientName = this._clientName;
count = 2;
for (libraryName in libraries) count++;
finish = function() {
if (--count) return;
promise.resolve();
Expand All @@ -100,13 +107,14 @@ View.prototype._load = function(isStatic, callback) {
if (!clientName) promise.resolve();

count = 3;
for (libraryName in libraries) count++;
finish = function() {
var loadTemplates;
if (--count) return;

// Templates are appended to the js bundle here so that it does
// not have to be regenerated if only the template files are modified
loadTemplates = loadTemplatesScript(requirePath, templates, instances);
loadTemplates = loadTemplatesScript(requirePath, templates, instances, libraryData);
if (isProduction) loadTemplates = uglify(loadTemplates);
js += ';' + loadTemplates;

Expand Down Expand Up @@ -158,6 +166,32 @@ View.prototype._load = function(isStatic, callback) {
view._makeAll(templates, instances);
finish();
});

for (libraryName in libraries) {
library = libraries[libraryName];
files.library(library.root, function(err, components) {
if (err) {
errors['Template'] = templateError(err);
return finish();
}
var libraryTemplates = {}
, libraryInstances = {}
, componentName, component;
for (componentName in components) {
component = components[componentName];
// TODO: Namespace component partials of each component
merge(libraryTemplates, component.templates);
merge(libraryInstances, component.instances);
}
libraryData[libraryName] = {
templates: libraryTemplates
, instances: libraryInstances
};
library.view._makeAll(libraryTemplates, libraryInstances);
finish();
});
}

};

View.prototype.render = function(res) {
Expand Down Expand Up @@ -193,7 +227,6 @@ View.prototype.render = function(res) {

View.prototype._init = function(model) {
// Initialize view & model for rendering
this.dom = emptyDom;
model.__events = emptyEventDispatcher;
model.__blockPaths = {};
model.__pathMap = emptyPathMap;
Expand Down
12 changes: 7 additions & 5 deletions lib/derby.browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ function derbyBrowser(derby) {
derbyBrowser.decorate = 'derby';
derbyBrowser.useWith = {server: false, browser: true};


function createApp(appModule) {
var appExports = appModule.exports
, view, model;
Expand All @@ -25,15 +24,17 @@ function createApp(appModule) {
// before the socket object is set
racer.on('init', function(_model) {
model = view.model = _model;
var dom = view.dom = new Dom(model, appExports);
var dom = view.dom = new Dom(model);
derbyModel.init(model, dom, view);
// Ignore errors thrown when rendering; these will also be thrown
// on the server, and throwing here causes the app not to connect
try {
// Render immediately upon initialization so that the page is in
// the same state it was when rendered on the server
view.render(model, ns, ctx, true);
} catch (err) {}
} catch (err) {
console.error(err);
}
});

// The ready event is fired after the model data is initialized and
Expand All @@ -50,7 +51,8 @@ function createApp(appModule) {
// Expose methods on the application module. Note that view must added
// to both appModule.exports and appExports, since it is used before
// the initialization function to make templates
appModule.exports.view = appExports.view = view = new View;
appModule.exports.view = appExports.view = view =
new View(this._libraries, appExports);

function createPage() {
return {
Expand All @@ -73,4 +75,4 @@ function createApp(appModule) {
racer.on('ready', fn);
};
return appExports;
};
}
24 changes: 23 additions & 1 deletion lib/derby.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,29 @@
var racer = require('racer')
var path = require('path')
, racer = require('racer')
, View = require('./View')
, derby = module.exports = Object.create(racer)
, derbyPlugin = racer.util.isServer ?
__dirname + '/derby.server' : require('./derby.browser');

// Allow derby object to be targeted via plugin.decorate
racer._makePlugable('derby', derby);

// Shared methods for both server and browser
derby._libraries = {};
derby.createLibrary = createLibrary;

// Add appropriate server-side or browser-side methods
derby.use(derbyPlugin);

function createLibrary(filename, scripts, options) {
if (!options) options = {};
var root = path.dirname(filename)
, name = options.name || path.basename(root)
, view = new View;

this._libraries[name] = {
root: root
, view: view
, scripts: scripts
};
}
Loading

0 comments on commit 269b628

Please sign in to comment.