Skip to content
Browse files

Created Jade engine wrapper that mimics the layout for ejs, and vario…

…us other changes/improvements
  • Loading branch information...
1 parent 884d921 commit 6f273877e780bc49b3c151a79b2c61f5c0e13a22 @larzconwell larzconwell committed
View
4 Makefile
@@ -15,7 +15,7 @@
# limitations under the License.
#
-.PHONY: all build install clean uninstall
+.PHONY: all build install clean uninstall reinstall
PREFIX=/usr/local
DESTDIR=
@@ -40,3 +40,5 @@ uninstall:
@rm -f $(DESTDIR)$(PREFIX)/bin/geddy && \
rm -fr $(DESTDIR)$(PREFIX)/lib/node_modules/geddy/ && \
echo 'Geddy uninstalled.'
+
+reinstall: uninstall install
View
5 examples/todo_app_jade/app/views/todos/add.html.jade
@@ -1,3 +1,4 @@
-extends layout
+extends ../layouts/application.html
-.hero-unit= partial('_form', {params: params, todo: todo})
+block content
+ .hero-unit= partial('_form', {params: params, todo: todo})
View
5 examples/todo_app_jade/app/views/todos/edit.html.jade
@@ -1,3 +1,4 @@
-extends layout
+extends ../layouts/application.html
-.hero-unit= partial('_form', {params: params, todo: todo})
+block content
+ .hero-unit= partial('_form', {params: params, todo: todo})
View
30 examples/todo_app_jade/app/views/todos/index.html.jade
@@ -1,16 +1,18 @@
-extends layout
+extends ../layouts/application.html
-.hero-unit
- h2 To Do List
- a.btn.pull-right(href="/todos/add") Create a new To Do
+block content
+ .hero-unit
+ h2 To Do List
+ a.btn.pull-right(href="/todos/add") Create a new To Do
-- if (todos && todos.length)
- - for (var i in todos)
- .row.todo-item
- .span8
- h3
- a(href="/todos/" + todos[i].id + "/edit")= todos[i].title
- .span4
- h3=
- i.icon-list-alt
- todos[i].status
+ //-Currently commented out so I can test rendering
+ - if (todos && todos.length)
+ - for (var i in todos)
+ .row.todo-item
+ .span8
+ h3
+ a(href="/todos/" + todos[i].id + "/edit")= todos[i].title
+ .span4
+ h3=
+ i.icon-list-alt
+ todos[i].status
View
13 examples/todo_app_jade/app/views/todos/show.html.jade
@@ -1,7 +1,8 @@
-extends layout
+extends ../layouts/application.html
-.hero-unit
- h3 Params
- ul
- - for (var p in params)
- li= p + ': ' + params[p]
+block content
+ .hero-unit
+ h3 Params
+ ul
+ - for (var p in params)
+ li= p + ': ' + params[p]
View
36 lib/app.js
@@ -403,26 +403,34 @@ var App = function () {
, entry = geddy.inFlight.getEntry(id)
, req = entry.request
, stat = resp.statusCode
- , level = parseInt(stat, 10) > 499 ? 'error' : 'access'
- , endTime = new Date();
+ , endTime = new Date()
+ , level = parseInt(stat, 10);
+
+ // Status code representation for logging
+ if(level > 499) {
+ level = 'error';
+ } else level = 'access';
+
// Apache extended log-format
geddy.log[level](req.connection.remoteAddress + ' ' +
- '- ' +
- '- ' +
- '[' + new Date(entry.accessTime) + '] ' +
- '"' + entry.method + ' ' + req.url + ' ' +
- req.httpVersion + '" ' +
- stat + ' ' +
- (resp._length || '-') + ' ' +
- '"' + (req.headers['referer'] || '-') + '" ' +
- '"' + (req.headers['user-agent'] || '-') + '" ');
+ '- ' +
+ '- ' +
+ '[' + new Date(entry.accessTime) + '] ' +
+ '"' + entry.method + ' ' + req.url + ' ' +
+ req.httpVersion + '" ' +
+ stat + ' ' +
+ (resp._length || '-') + ' ' +
+ '"' + (req.headers['referer'] || '-') + '" ' +
+ '"' + (req.headers['user-agent'] || '-') + '" ');
+
geddy.inFlight.removeEntry(id);
+
if (geddy.config.metrics) {
allRequestTimer.update(endTime - accessTime);
- controllerActionTimers[controllerName] = controllerActionTimers[controller] || {}
+ controllerActionTimers[controllerName] = controllerActionTimers[controller] || {};
controllerActionTimers[controllerName][actionName] =
- controllerActionTimers[controllerName][actionName] ||
- geddy.metrics.Timer("Geddy."+controllerName + '.' + actionName)
+ controllerActionTimers[controllerName][actionName] ||
+ geddy.metrics.Timer("Geddy."+controllerName + '.' + actionName);
controllerActionTimers[controllerName][actionName].update(endTime - accessTime);
}
});
View
736 lib/base_controller.js
@@ -111,263 +111,257 @@ controller.BaseController = function () {
};
controller.BaseController.prototype = new (function () {
+ var _addFilter
+ , _execFilters
+ , _negotiateContent
+ , _doResponse
+ , _throwUndefinedFormatError
+ , _generateSameOriginToken
+ , _protectFromForgery;
+
+ /*
+ *
+ * Private utility methods
+ *
+ */
+ _addFilter = function(phase, filter, opts) {
+ var obj = {def: filter};
+
+ obj.except = opts.except;
+ obj.only = opts.only;
+ obj.async = opts.async;
+
+ this['_' + phase + 'Filters'].push(obj);
+ };
+
+ _execFilters = function(action, phase, callback) {
+ var self = this
+ , filters = this['_' + phase + 'Filters']
+ , list = []
+ , applyFilter = true // Default
+ , filter
+ , func;
+
+ if(!filters) {
+ callback();
+ return; // Not sure if we need this since we're calling a function above
+ }
+
+ for(var i = 0, len = filters.length; i < len; i++) {
+ filter = filters[i];
- // Private methods, utility methods
- // -------------------
- var _addFilter = function (phase, filter, opts) {
- var obj = {def: filter};
- obj.except = opts.except;
- obj.only = opts.only;
- obj.async = opts.async;
- this['_' + phase + 'Filters'].push(obj);
+ if(filter.only && (filter.only != action || filter.only.indexOf(action) == -1)) {
+ applyFilter = false;
+ }
+ if(filter.except && (filter.except == action || filter.except.indexOf(action) > -1)) {
+ applyFilter = false;
}
- , _execFilters = function (action, phase, callback) {
- var self = this;
- var filters = this['_' + phase + 'Filters'];
- var filter;
- var list = [];
- var func;
- var applyFilter;
- if (!filters) {
- callback();
- return;
- }
- for (var i = 0; i < filters.length; i++) {
- filter = filters[i];
- applyFilter = true;
- if (filter.only &&
- (filter.only != action || filter.only.indexOf(action) == -1)) {
- applyFilter = false;
- }
- if (filter.except &&
- (filter.except == action || filter.except.indexOf(action) > -1)) {
- applyFilter = false;
- }
- if (applyFilter) {
-
- // TODO: Wrap filters to prevent further execution when
- // a req/resp cycle is already completed (e.g., with a
- // redirect
-
- // Create an async wrapper for any sync filters
- if (!filter.async) {
- func = function (callback) {
- filter.def.apply(self, []);
- callback();
- };
- }
- else {
- func = filter.def
- }
- list.push({
- func: func
- , args: []
- , callback: null
- , context: self
- });
- }
- }
- var chain = new geddy.async.AsyncChain(list);
- chain.last = callback;
- chain.run();
+ if(applyFilter) {
+ // TODO: Wrap filters to prevent further execution when
+ // a req/resp cycle is already completed (e.g., with a
+ // redirect
+
+ // Create an async wrapper for sync filters
+ if(!filter.async) {
+ func = function(callback) {
+ filter.def.apply(self, []);
+ callback();
+ };
+ } else func = filter.def;
+
+ list.push({
+ func: func
+ , args: []
+ , callback: null
+ , context: self
+ });
}
+ }
+ var chain = new geddy.async.AsyncChain(list);
- , _negotiateContent = function (frmt) {
- var format
- , contentType
- , types = []
- , match
- , params = this.params
- , err
- , accepts = this.request.headers.accept
- , accept
- , pat
- , wildcard = false;
-
- // If the client provides an Accept header, split on comma
- // Some user-agents may include whitespace with the comma
- if (accepts) {
- accepts = accepts.split(/\s*,\s*/);
- }
- // If the client doesn't provide an Accept header, assume
- // it's happy with anything
- else {
- accepts = ['*/*'];
- }
+ chain.last = callback;
+ chain.run();
+ };
- if (frmt) {
- types = [frmt];
- }
- else if (params.format) {
- var f = params.format;
- // See if we can actually respond with this format,
- // i.e., that this one is in the list
- if (f && ('|' + this.respondsWith.join('|') + '|').indexOf(
- '|' + f + '|') > -1) {
- types = [f];
- }
- }
- else {
- types = this.respondsWith;
- }
+ _negotiateContent = function(frmt) {
+ var format
+ , contentType
+ , types = []
+ , params = this.params
+ , accepts = this.request.headers.accept
+ , wildcard = false
+ , match
+ , err
+ , accept
+ , pat;
+
+ // If client provides an Accept header, split on comma
+ // - some user-agents include whitespace with the comma
+ if(accepts) {
+ accepts = accepts.split(/\s*,\s*/);
+ }
+ // If no Accept header is found, assume it's happy with anything
+ else accepts = ['*/*'];
- // Okay, we have some format-types, let's see if anything matches
- if (types.length) {
- for (var i = 0, ii = accepts.length; i < ii; i++) {
- // Ignore quality factors for now
- accept = accepts[i].split(';')[0];
- if (accept == '*/*') {
- wildcard = true;
- break;
- }
- }
+ if(frmt) {
+ types = [frmt];
+ }
+ else if(params.format) {
+ var f = params.format;
+ // TODO test var with formats
- // If agent accepts anything, respond with the controller's first choice
- if (wildcard) {
- var t = types[0];
- format = t;
- contentType = response.getContentTypeForFormat(t);
- // Controllers should have a least one format with a valid contentType
- if (!contentType) {
- _throwUndefinedFormatError.call(this);
- return;
- }
- }
- // Otherwise look through the acceptable formats and see if
- // Geddy knows about any of them.
- else {
- for (var i = 0, ii = types.length; i < ii; i++) {
- match = response.matchAcceptHeaderContentType(accepts, types[i]);
- if (match) {
- format = types[i];
- contentType = match;
- }
- // If respondsWith contains some random format that Geddy doesn't know
- else {
- _throwUndefinedFormatError.call(this);
- return;
- }
- // Don't look at any more formats if there's a match
- if (match) {
- break;
- }
- }
- }
- }
- else {
- _throwUndefinedFormatError.call(this);
- return;
+ // If we can respond with the requested format then assign it to types
+ if(('|' + this.respondsWith.join('|') + '|').indexOf('|' + f + '|') > -1) {
+ types = [f];
+ }
+ }
+ else types = this.respondsWith;
+
+ // See if any format types match
+ if(types.length) {
+ for(var i = 0, len = accepts.length; i < len; i++) {
+ accept = accepts[i].split(';')[0]; // Ignore quality factors
+
+ if(accept == '*/*') {
+ wildcard = true;
+ break;
}
- return {format: format, contentType: contentType};
}
- , _doResponse = function (stat, headers, content) {
+ // If agent accepts anything, respond with controller's first choice
+ if(wildcard) {
+ var t = types[0];
- // Response-preemption, as in the case of a redirect, should mean
- // it's only possible to respond once
- if (this.completed) {
+ format = t;
+ contentType = response.getContentTypeForFormat(t);
+
+ // Controllers should at least one format with a valid contentType
+ if(!contentType) {
+ _throwUndefinedFormatError.call(this);
return;
}
- this.completed = true;
-
- var self = this
- , r = new response.Response(this.response)
- , action = this.action
- , callback = function () {
- // Set status and headers, can be overridden in after-filters
- if (self.cookies) {
- headers['Set-Cookie'] = self.cookies.toArray();
- }
- r.setHeaders(stat, headers);
-
- // Run after-filters, then finish out the response
- _execFilters.apply(self, [action, 'after', function () {
- if (self.method == 'HEAD') {
- r.finish();
- }
- else {
- r.finalize(content);
- }
- }]);
- };
-
- if (this.session) {
- // Save to accessTime into session for expiry and
- // verifying sameOriginToken
- this.session.set('accessTime', this.accessTime);
-
- this.session.close(callback);
- }
- else {
- callback();
+ }
+ // Otherwise look through acceptable formats and see if Geddy knows about them
+ else {
+ for(var i = 0, len = types.length; i< len; i++) {
+ match = response.matchAcceptHeaderContentType(accepts, types[i]);
+
+ if(match) {
+ format = types[i];
+ contentType = match;
+ break;
+ } else {
+ // Geddy doesn't know about this format
+ _throwUndefinedFormatError.call(this);
+ return;
+ }
}
}
+ } else {
+ _throwUndefinedFormatError.call(this);
+ return;
+ }
- , _throwUndefinedFormatError = function () {
- err = new geddy.errors.InternalServerError(
- 'Format not defined in response.formats.');
- this.error(err);
- }
+ return { format: format, contentType: contentType };
+ };
+
+ _doResponse = function(stat, headers, content) {
+ // Only allow response once
+ if(this.completed) return;
- , _generateSameOriginToken = function (t) {
- var time = t || this.accessTime
- , sha = crypto.createHash('sha1');
- sha.update(geddy.config.secret);
- sha.update(this.session.id);
- sha.update(time.toString());
- return sha.digest('hex');
+ this.completed = true;
+
+ var self = this
+ , r = new response.Response(this.response)
+ , action = this.action
+ , callback;
+
+ callback = function() {
+ // Set status and headers, can be overridded with after filters
+ if(self.cookies) {
+ headers['Set-Cookie'] = self.cookies.toArray();
}
+ r.setHeaders(stat, headers);
+
+ // Run after filters, then finish out the response
+ _execFilters.apply(self, [action, 'after', function() {
+ if(self.method == 'HEAD') {
+ r.finish();
+ } else r.finalize(content);
+ }]);
+ };
- , _protectFromForgery = function (complete) {
- var methods = {
- PUT: true
- , POST: true
- , DELETE: true
- }
- , lastAccess = this.session.get('accessTime')
- , params = this.params
- , token = params.same_origin_token || params.sameOriginToken
- , forbidden = false;
-
- if (methods[this.method]) {
- if (!token) {
- forbidden = true;
- }
- else {
- if (_generateSameOriginToken.call(this, lastAccess) != token) {
- forbidden = true;
- }
- }
- }
- if (forbidden) {
- err = new geddy.errors.ForbiddenError(
- 'Cross-site request not allowed.');
- this.error(err);
- }
- else {
- complete();
+ if(this.session) {
+ // Save access time into session for expiry and
+ // - verifying sameOriginToken
+ this.session.set('accessTime', this.accessTime);
+ this.session.close(callback);
+ } else callback();
+ };
+
+ _throwUndefinedFormatError = function() {
+ err = new geddy.errors.InternalServerError(
+ 'Format not defined in response.formats.');
+ this.error(err);
+ };
+
+ _generateSameOriginToken = function(time) {
+ var time = time || this.accessTime
+ , sha = crypto.createHash('sha1');
+
+ sha.update(geddy.config.secret);
+ sha.update(this.session.id);
+ sha.update(time.toString());
+
+ return sha.digest('hex');
+ };
+
+ _protectFromForgery = function(complete) {
+ var methods = {PUT: true, POST: true, DELETE: true}
+ , lastAccess = this.session.get('accessTime')
+ , params = this.params
+ , token = params.same_origin_token || params.sameOriginToken
+ , forbidden = false;
+
+ if(methods[this.method]) {
+ if(!token) {
+ forbidden = true;
+ } else {
+ if(_generateSameOriginToken.call(this, lastAccess) != token) {
+ forbidden = true;
}
- };
+ }
+ }
- // Pseudo-private, non-API
- // -------------------
- /**
- * Primary entry point for calling the action on a controller
- * Wraps the action so befores and afters can be run
- */
- this._handleAction = function (action) {
- var self = this;
- // Wrap the actual action-handling in a callback to use as the 'last'
- // method in the async chain of before-filters
- var callback = function () {
- if (!self.completed) {
+ if(forbidden) {
+ err = new geddy.errors.ForbiddenError(
+ 'Cross-site request not allowed.');
+ this.error(err);
+ } else complete();
+ };
+
+ /*
+ *
+ * Pseudo-private, non-API
+ *
+ */
+ // Primary entry point for calling the action on a controller
+ // Wraps the action so before and after filters can be run
+ this._handleAction = function(action) {
+ var self = this
+ , callback;
+
+ // Wrap the actual action handling in a callback to use as the last
+ // - method in the async chain of before filters
+ callback = function() {
+ if(!self.completed) {
self[action].apply(self, [self.request, self.response, self.params]);
}
};
- // Generate the anti-CSRF token
- if (geddy.config.secret && this.session) {
+ // Generate an anti-CSRF token
+ if(geddy.config.secret && this.session) {
this.sameOriginToken = _generateSameOriginToken.call(this);
}
@@ -377,223 +371,215 @@ controller.BaseController.prototype = new (function () {
if (this._beforeFilters.length) {
_execFilters.apply(this, [action, 'before', callback]);
}
- else {
- callback();
- }
+ else callback();
};
- // Public methods
- // -------------------
- /**
- @name controller.BaseController#before
- @public
- @function
- @description Adds an action to the beforeFilters list.
- @param {Function} filter Action to add to the beforeFilter list of
- actions to be performed before a response is rendered.
- @param {Object} [opts]
- @param {Array} [opts.except=null] List of actions where the
- before-filter should not be performed.
- @param {Array} [opts.only=null] This list of actions are the
- only actions where this before-filter should be performed.
- */
- this.before = function (filter, options) {
- _addFilter.apply(this, ['before', filter, options || {}]);
+ /*
+ *
+ * Public methods
+ *
+ */
+ this.before = function(filter, options) {
+ /*
+ @name controller.BaseController#before
+ @public
+ @function
+ @description Adds an action to the beforeFilters list.
+ @param {Function} filter Action to add to the beforeFilter list of
+ actions to be performed before a response is rendered.
+ @param {Object} [opts]
+ @param {Array} [opts.except=null] List of actions where the
+ before-filter should not be performed.
+ @param {Array} [opts.only=null] This list of actions are the
+ only actions where this before-filter should be performed
+ */
+ options = options || {};
+ _addFilter.apply(this, ['before', filter, options]);
};
- /**
- @name controller.BaseController#after
- @public
- @function
- @description Adds an action to the afterFilters list of actions
- to be performed after a response is rendered.
- @param {Function} filter Action to add to the afterFilter list.
- @param {Object} [opts]
- @param {Array} [opts.except=null] List of actions where the
- after-filter should not be performed.
- @param {Array} [opts.only=null] This list of actions are the
- only actions where this after-filter should be performed.
- */
- this.after = function (filter, options) {
- _addFilter.apply(this, ['after', filter, options || {}]);
+ this.after = function(filter, options) {
+ /*
+ @name controller.BaseController#after
+ @public
+ @function
+ @description Adds an action to the afterFilters list of actions
+ to be performed after a response is rendered.
+ @param {Function} filter Action to add to the afterFilter list.
+ @param {Object} [opts]
+ @param {Array} [opts.except=null] List of actions where the
+ after-filter should not be performed.
+ @param {Array} [opts.only=null] This list of actions are the
+ only actions where this after-filter should be performed.
+ */
+ options = options || {};
+ _addFilter.apply(this, ['after', filter, options]);
};
- this.protectFromForgery = function (options) {
- var opts = options || {};
- if (!geddy.config.secret) {
+ this.protectFromForgery = function(options) {
+ options = options || {};
+
+ if(!geddy.config.secret) {
geddy.log.error('protectFromForgery requires an app-secret. ' +
'Run `geddy secret` in your app.');
}
- if (!geddy.config.sessions) {
+ if(!geddy.config.sessions) {
geddy.log.error('protectFromForgery requires sessions.');
}
- if (typeof opts.async != 'undefined' && !opts.async) {
+ if(typeof options.async != 'undefined' && !options.async) {
geddy.log.error('protectFromForgery requires the async flag set to true.');
}
- opts.async = true;
- this.before(_protectFromForgery, opts);
+
+ options.async = true;
+ // Add a before filter
+ this.before(_protectFromForgery, options);
};
- /**
- @name controller.BaseController#redirect
- @public
- @function
- @description Sends a 302 redirect to the client, based on either a
- simple string-URL, or a controller/action/format combination.
- @param {String|Object} target Either an URL, or an object literal containing
- controller/action/format attributes to base the redirect on.
- */
- this.redirect = function (target) {
+ this.redirect = function(target) {
+ /*
+ @name controller.BaseController#redirect
+ @public
+ @function
+ @description Sends a 302 redirect to the client, based on either a
+ simple string-URL, or a controller/action/format combination.
+ @param {String|Object} target Either an URL, or an object literal containing
+ controller/action/format attributes to base the redirect on.
+ */
var url;
- if (typeof target == 'string') {
- url = target;
- }
- else if (typeof this.app.router.url == 'function') {
- if (this.name && !target.controller) {
- target.controller = this.name;
- }
- if (this.params.format && !target.format) {
- target.format = this.params.format;
- }
+
+ if(typeof target == 'string') {
+ url = target
+ } else if(typeof this.app.router.url == 'function') {
+ if(this.name && !target.controller) target.controller = this.name;
+ if(this.params.format && !target.format) target.format = this.params.format;
url = this.app.router.url(target);
}
- if (!url) {
- var contr = target.controller || this.name;
- var act = target.action;
- var ext = target.format || this.params.format;
- var id = target.id;
+ if(!url) {
+ var contr = target.controller || this.name
+ , act = target.action
+ , ext = target.format || this.params.format
+ , id = target.id;
+
contr = geddy.string.decamelize(contr);
url = '/' + contr;
url += act ? '/' + act : '';
url += id ? '/' + id : '';
- if (ext) {
- url += '.' + ext;
- }
+ if(ext) url += '.' + ext;
}
- _doResponse.apply(this, [302, {'Location': url}], '');
+ _doResponse.apply(this, [302, { 'Location': url }, '']);
};
- /**
- @name controller.BaseController#error
- @public
- @function
- @description Respond to a request with an appropriate HTTP error-code.
- If a status-code is set on the error object, uses that as the error's
- status-code. Otherwise, responds with a 500 for the status-code.
- @param {Object} err The error to use as the basis for the response.
- */
- this.error = function (err) {
+ this.error = function(err) {
+ /*
+ @name controller.BaseController#error
+ @public
+ @function
+ @description Respond to a request with an appropriate HTTP error-code.
+ If a status-code is set on the error object, uses that as the error's
+ status-code. Otherwise, responds with a 500 for the status-code.
+ @param {Object} err The error to use as the basis for the response.
+ */
this.completed = true;
geddy.errors.respond(this.response, err);
};
- /**
- @name controller.BaseController#transfer
- @public
- @function
- @description Transfer a request from its original action to a new one. The
- entire request cycle is repeated, including before-filters.
- @param {Object} action The new action designated to handle the request.
- */
- this.transfer = function (action) {
+ this.transfer = function(action) {
+ /*
+ @name controller.BaseController#transfer
+ @public
+ @function
+ @description Transfer a request from its original action to a new one. The
+ entire request cycle is repeated, including before-filters.
+ @param {Object} action The new action designated to handle the request.
+ */
this.params.action = action;
this._handleAction(action);
};
- /**
- @name controller.BaseController#respond
- @public
- @function
- @description Performs content-negotiation, and renders a response.
- @param {Object|String} content The content to use in the response.
- @param {Object} [opts] Options.
- @param {String} [opts.format] The desired format for the response.
- @param {String} [opts.template] The path (without file extensions)
- to the template to use to render this response.
- @param {String} [opts.layout] The path (without file extensions)
- to the layout to use to render the template for this response.
- */
- this.respond = function (content, opts) {
+ this.respond = function(content, options) {
+ /*
+ @name controller.BaseController#respond
+ @public
+ @function
+ @description Performs content-negotiation, and renders a response.
+ @param {Object|String} content The content to use in the response.
+ @param {Object} [opts] Options.
+ @param {String} [options.format] The desired format for the response.
+ @param {String} [options.template] The path (without file extensions)
+ to the template to use to render this response.
+ @param {String} [options.layout] The path (without file extensions)
+ to the layout to use to render the template for this response.
+ */
var self = this
- , options = opts || {}
+ , options = options || {}
, formatParam = typeof opts == 'string' ? options : options.format
, negotiated = _negotiateContent.call(this, formatParam)
, format
, contentType
- , callback = function (formattedContent) {
- _doResponse.apply(self, [200, {'Content-Type': contentType},
- formattedContent]);
- };
-
- // Error during content-negotiation may result in an error response;
- // do not continue
- if (this.completed) {
- return;
- }
+ , callback;
+
+ callback = function(formattedContent) {
+ _doResponse.apply(self, [200, {'Content-Type': contentType}, formattedContent]);
+ };
+
+ // Error during content negotiation may result in an error response, so
+ // - don't continue
+ if(this.completed) return;
format = negotiated.format;
contentType = negotiated.contentType;
- if (!contentType) {
+ if(!contentType) {
var err = new geddy.errors.NotAcceptableError('Not an acceptable media type.');
this.error(err);
return;
}
- else if (!format) {
+ if(!format) {
_throwUndefinedFormatError.call(this);
return;
}
- if (options.template) {
- this.template = options.template;
- }
- if (options.layout) {
- this.layout = 'app/views/' + options.layout;
- }
+ if(options.template) this.template = options.template;
+ if(options.layout) this.layout = 'app/views/' + options.layout;
// Hand content off to formatting along with callback for writing out
// the formatted respnse
response.formatContent(format, content, this, callback);
};
+ this.render = this.respond; // Keep backwards compat
- // Backward-compat shim
- this.render = this.respond;
-
- this.renderTemplate = function (data, callback) {
+ this.renderTemplate = function(data, callback) {
var self = this
+ , templater = new Templater()
+ , content = ''
, dirName;
+ // Format directory name
dirName = geddy.inflection.pluralize(this.name);
dirName = geddy.string.snakeize(dirName);
- // Calculate the template if not set
+ // Get template if not set
this.template = this.template ||
- 'app/views/' + dirName + '/' + this.params.action;
-
- // Calculate the layout if not set
- this.layout = this.layout ||
- 'app/views/layouts/' + dirName;
+ 'app/views/' + dirName + '/' + this.params.action;
- var templater = new Templater();
- var content = '';
+ // Get layout if not set
+ this.layout = this.layout || 'app/views/layouts/' + dirName;
- templater.addListener('data', function (d) {
- // Buffer for now, but could stream
- content += d;
+ templater.addListener('data', function(data) {
+ // Buffer for now, but we could stream
+ content += data;
});
-
- templater.addListener('end', function () {
+ templater.addListener('end', function() {
callback(content);
});
templater.render(data, {
- layout: this.layout
- , template: this.template
- , controller: this.name
- , action: this.params.action
+ layout: this.layout
+ , template: this.template
+ , controller: this.name
+ , action: this.params.action
});
};
View
4 lib/response/errors.js
@@ -69,6 +69,10 @@ var errors = new function () {
this.coffeeError = function() {
throw "If you'd like to use CoffeeScript with Geddy, you will need to install the module([sudo] npm install -g coffee-script)";
};
+ this.viewError = function(engine) {
+ var engineCap = geddy.utils.string.capitalize(engine);
+ throw "If you'd like to use "+engineCap+" with Geddy, you will need to install the module([sudo] npm install -g "+engine+")";
+ };
}();
View
167 lib/template/adapters/ejs.js
@@ -19,21 +19,21 @@
var utils = require('../../utils')
, ejs = {};
-ejs.Template = function (p) {
+ejs.Template = function(params) {
var UNDEF;
- var params = p || {};
+ var params = params || {};
this.mode = null;
this.truncate = false;
- this.templateText = params.text ||
- // If you don't want to use Fleegix.js,
- // override getTemplateTextFromNode to use
- // textarea node value for template text
- this.getTemplateTextFromNode(params.node);
+ // Note: If you don't want to use Fleegix.js,
+ // override getTemplateTextFromNode to use
+ // textarea node value for template text
+ this.templateText = params.text || this.getTemplateTextFromNode(params.node);
this.afterLoaded = params.afterLoaded;
this.source = '';
this.markup = UNDEF;
- // Try to get from URL
+
+ // Try to get from URL if no template text
if (typeof this.templateText == 'undefined') {
// If you don't want to use Fleegix.js,
// override getTemplateTextFromUrl to use
@@ -42,136 +42,147 @@ ejs.Template = function (p) {
}
};
-ejs.Template.prototype = new function () {
+ejs.Template.prototype = new function() {
var _REGEX = /(<%%)|(%%>)|(<%=)|(<%-)|(<%#)|(<%)|(%>)|(-%>)/;
this.modes = {
- EVAL: 'eval'
- , ESCAPED: 'escaped'
- , RAW: 'raw'
- , APPEND: 'append'
- , COMMENT: 'comment'
- , LITERAL: 'literal'
+ EVAL: 'eval'
+ , ESCAPED: 'escaped'
+ , RAW: 'raw'
+ , APPEND: 'append'
+ , COMMENT: 'comment'
+ , LITERAL: 'literal'
};
- this.getTemplateTextFromNode = function (node) {
+
+ this.getTemplateTextFromNode = function(node) {
// Requires the fleegix.xhr module
if (typeof fleegix.string == 'undefined') {
- throw('Requires fleegix.string module.'); }
+ throw 'Requires fleegix.string module.';
+ }
var ret;
- if (node) {
+
+ if(node) {
ret = node.value;
ret = fleegix.string.unescapeXML(ret);
ret = fleegix.string.trim(ret);
}
return ret;
};
- this.getTemplateTextFromUrl = function (params) {
+
+ this.getTemplateTextFromUrl = function(params) {
// Requires the fleegix.xhr module
if (typeof fleegix.xhr == 'undefined') {
- throw('Requires fleegix.xhr module.'); }
- var self = this;
- var url = params.url;
- var noCache = params.preventCache || false;
- // Found text in cache, and caching is turned on
+ throw 'Requires fleegix.xhr module.';
+ }
+ var self = this
+ , url = params.url
+ , noCache = params.preventCache || false;
+
+ // If cache is found and caching is on
if (text && !noCache) {
this.templateText = text;
}
- // Otherwise go grab the text
+ // Otherwise get the text
else {
- // Callback for setting templateText and caching --
- // used for both sync and async loading
- var callback = function (s) {
+ var opts;
+ var callback = function(s) {
+ // Callback for setting templateText and caching
+ // - used for both sync and async loading
self.templateText = s;
ejs.templateTextCache[url] = s;
+
// Use afterLoaded hook if set
if (typeof self.afterLoaded == 'function') {
self.afterLoaded();
}
};
- var opts;
- if (params.async) {
+
+ if(params.async) {
opts = {
- url: url,
- method: 'GET',
- preventCache: noCache,
- async: true,
- handleSuccess: callback
+ url: url
+ , method: 'GET'
+ , preventCache: noCache
+ , async: true
+ , handleSuccess: callback
};
- // Get templ text asynchronously, wait for
+ // Get template text asynchronously, wait for
// loading to exec the callback
fleegix.xhr.send(opts);
}
else {
opts = {
- url: url,
- method: 'GET',
- preventCache: noCache,
- async: false
+ url: url
+ , method: 'GET'
+ , preventCache: noCache
+ , async: false
};
- // Get the templ text inline and pass directly to
+ // Get the template text inline and pass directly to
// the callback
text = fleegix.xhr.send(opts);
callback(text);
}
}
};
- this.process = function (p) {
- var params = p || {};
+
+ this.process = function(params) {
+ var params = params || {}
+ , domNode = params.node
+ , _output;
+
this.data = params.data || {};
- var domNode = params.node;
- // Cache/reuse the generated template source for speed
- this.source = this.source || '';
- if (!this.source) { this.generateSource(); }
+ this.source = this.source || ''; // Cache generated template for speed
+
+ // If no cache is available generate template
+ if(!this.source) this.generateSource();
// Eval the template with the passed data
- // Use 'with' to give local scoping to data obj props
- // ========================
- var _output = ''; // Inner scope var for eval output
- with (this.data) {
- eval(this.source);
- }
+ // Use 'with' to give local scope to data object
+ _output = ''; // Inner scope var for eval output
+ with (this.data) eval(this.source);
this.markup = _output;
- if (domNode) {
- domNode.innerHTML = this.markup;
- }
+ if (domNode) domNode.innerHTML = this.markup;
+
return this.markup;
};
- this.generateSource = function () {
- var line = '';
- var matches = this.parseTemplateText();
- if (matches) {
- for (var i = 0; i < matches.length; i++) {
+
+ this.generateSource = function() {
+ var matches = this.parseTemplateText()
+ , line = '';
+
+ if(matches) {
+ for(var i = 0, len = matches.length; i < len; i++) {
line = matches[i];
- if (line) {
- this.scanLine(line);
- }
+ if(line) this.scanLine(line);
}
}
};
+
this.parseTemplateText = function() {
- var str = this.templateText;
- var pat = _REGEX;
- var result = pat.exec(str);
- var arr = [];
- while (result) {
- var firstPos = result.index;
- var lastPos = pat.lastIndex;
- if (firstPos !== 0) {
+ var str = this.templateText
+ , par = _REGEX
+ , result = par.exec(str)
+ , arr = [];
+
+ while(result) {
+ var firstPos = result.index
+ , lastPos = par.lastIndex;
+
+ if(firstPos !== 0) {
arr.push(str.substring(0, firstPos));
str = str.slice(firstPos);
}
arr.push(result[0]);
str = str.slice(result[0].length);
- result = pat.exec(str);
- }
- if (str !== '') {
- arr.push(str);
+ result = par.exec(str);
}
+ if(str !== '') arr.push(str);
+
return arr;
};
- this.scanLine = function (line) {
+
+ this.scanLine = function(line) {
var self = this
- , _addOutput = function () {
+ , _addOutput = function() {
if (self.truncate) {
line = line.replace('\n', '');
}
View
174 lib/template/adapters/jade.js
@@ -0,0 +1,174 @@
+/*
+ * Geddy JavaScript Web development framework
+ * Copyright 2112 Matthew Eernisse (mde@fleegix.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+*/
+
+var utils = require('../../utils')
+ , errors = require('../../response/errors')
+ , jade = {};
+
+jade.Template = function(params) {
+ var UNDEF;
+ var params = params || {};
+
+ this.mode = null;
+ this.truncate = false;
+ // Note: If you don't want to use Fleegix.js,
+ // override getTemplateTextFromNode to use
+ // textarea node value for template text
+ this.templateText = params.text || this.getTemplateTextFromNode(params.node);
+ this.afterLoaded = params.afterLoaded;
+ this.source = '';
+ this.markup = UNDEF;
+
+ // Try to get from URL if no template text
+ if (typeof this.templateText == 'undefined') {
+ // If you don't want to use Fleegix.js,
+ // override getTemplateTextFromUrl to use
+ // files for template text
+ this.getTemplateTextFromUrl(params);
+ }
+};
+
+jade.Template.prototype = new function() {
+
+ this.getTemplateTextFromNode = function(node) {
+ // Requires the fleegix.xhr module
+ if (typeof fleegix.string == 'undefined') {
+ throw 'Requires fleegix.string module.';
+ }
+ var ret;
+
+ if(node) {
+ ret = node.value;
+ ret = fleegix.string.unescapeXML(ret);
+ ret = fleegix.string.trim(ret);
+ }
+ return ret;
+ };
+
+ this.getTemplateTextFromUrl = function(params) {
+ // Requires the fleegix.xhr module
+ if (typeof fleegix.xhr == 'undefined') {
+ throw 'Requires fleegix.xhr module.';
+ }
+ var self = this
+ , url = params.url
+ , noCache = params.preventCache || false;
+
+ // If cache is found and caching is on
+ if (text && !noCache) {
+ this.templateText = text;
+ }
+ // Otherwise get the text
+ else {
+ var opts;
+ var callback = function(s) {
+ // Callback for setting templateText and caching
+ // - used for both sync and async loading
+ self.templateText = s;
+ ejs.templateTextCache[url] = s;
+
+ // Use afterLoaded hook if set
+ if (typeof self.afterLoaded == 'function') {
+ self.afterLoaded();
+ }
+ };
+
+ if(params.async) {
+ opts = {
+ url: url
+ , method: 'GET'
+ , preventCache: noCache
+ , async: true
+ , handleSuccess: callback
+ };
+ // Get template text asynchronously, wait for
+ // loading to exec the callback
+ fleegix.xhr.send(opts);
+ }
+ else {
+ opts = {
+ url: url
+ , method: 'GET'
+ , preventCache: noCache
+ , async: false
+ };
+ // Get the template text inline and pass directly to
+ // the callback
+ text = fleegix.xhr.send(opts);
+ callback(text);
+ }
+ }
+ };
+
+ this.process = function(params) {
+ var params = params || {}
+ , domNode = params.node
+ , _output;
+
+ this.data = params.data || {};
+ this.source = this.source || ''; // Cache generated template for speed
+
+ // If no cache is available generate template
+ if(!this.source) this.source = this.generateSource();
+
+ // Assign the source to the template markup
+ this.markup = this.source;
+
+ if (domNode) domNode.innerHTML = this.markup;
+
+ return this.markup;
+ };
+
+ this.generateSource = function() {
+ try {
+ this.engine = this.engine || require('jade');
+ } catch(err) {
+ new errors.viewError('jade');
+ }
+ var str = this.templateText
+ , template
+ , options = {cache: true, filename: this.data.template.file};
+
+ // TODO:
+ // The way our EJS engine is working won't work for other engines,
+ // it splits the layout and template into two different template rendering sections
+ // - (template then layout) so it injects the template into the layout and
+ // renders that output.
+ // Jade has it's own injection solution through blocks and extends,
+ // so I need to find a way to keep it in one set(Not sending the layout and template in different sequences)
+ // or something else..
+
+ // Also for Jade when using extends it gets the extends path, then appends .jade to it,
+ // but out generated files user .html.jade so I think we'll have to remove that, or
+ // try to inject .html to the end of the extends statement
+
+ // When consoling the output for 'data' below, it does render the template correctly(Including layout)
+ // But not exactly sure if it'll include needed data and other things, still
+ // trying to see if rendering will work correctly
+ this.engine.render(str, options, function(err, data) {
+ // We shouldn't throw I don't think, but it'll work for now while developing
+ if(err) throw err;
+ template = data;
+ });
+
+ return template;
+ };
+
+};
+
+exports.Template = jade.Template;
View
5 lib/template/index.js
@@ -56,7 +56,7 @@ Templater.prototype.render = function(data, config) {
templaterContent.addListener('end', function() {
// Define `yield` method
- // TODO: May remove if EJS supports it, or maybe create a helper module
+ // TODO: Move into some external module called 'helpers'
data.yield = function() {
return contentPartial;
};
@@ -126,8 +126,6 @@ var getTemplateUrl = function(templateRoot, partialUrl, parentNode, isLayout) {
if(!templateData) {
// If we're looking for a layout look for the default layout
if (isLayout) {
- // TODO: Not sure how this works with other template engines yet
- // Jade, uses a built in system for handling layouts(block, extends)
templateData = path.normalize('app/views/layouts/application');
// Should be true if they have the default layout still present
if(templateData in geddy.templateRegistry) {
@@ -156,7 +154,6 @@ Templater.prototype.partial = function(partialUrl, renderContextParam, parentNod
templateData = getTemplateUrl(this.templateRoot, partialUrl, parentNode, this.isLayout);
// Create the current node, with reference to parent, if parent exists
- // TODO: See how the template_node module works, may need to remove
node = new TemplateNode(partialId, templateData, renderContext, parentNode);
// Create an alias to the partial method that uses the current node as the
View
1 lib/template/template_node.js
@@ -107,6 +107,7 @@ engine.TemplateNode.prototype = new function() {
// Execute the template with the content as the data-object
// This may execute "partial" method calls that create child
// nodes for this one.
+ self.params.template = self.data; // Include the template data in params
templ.process({ data: self.params });
// Write final markup to content

0 comments on commit 6f27387

Please sign in to comment.
Something went wrong with that request. Please try again.