The view component of the roads framework
This project is maintained by Dashron
A node.js View library with support for asynchronous sub-views (aka partials) and interchangeable rendering systems.
npm install bifocals
var bifocals_module = require('bifocals');
var Bifocals = bifocals_module.Bifocals;
Note: As of 1.2.0 Bifocals requires node 0.10.0 or higher for streams2
templates/index.html
<html>
<head></head>
<body>
{{{header}}}
</body>
</html>
templates/header.html
<header>
{{title}}
</header>
server.js
// Each content type needs to be registered to a Renderer.
// This tells bifocals to render all text/html views via handlebars.
bifocals_module.addRenderer('text/html', require('./renderers/handlebars_renderer'));
http_module.createServer(function (request, response) {
// Create the parent:
var view = new Bifocals(response);
// Assign a content type.
view.content_type = 'text/html';
// Create the child:
var child = view.child("header");
child.set('title, "Hello World");
child.render("templates/header.html");
// Write the view to the response
view.render("templates/index.html");
}
That's it! When the client requests this page, the following html will be displayed:
<html>
<head></head>
<body>
<header>
Hello World
</header>
</body>
</html>
The previous example does not show the true benefits of the library. These benefits are apparent when performing asynchronous actions. Consider this more complex example:
http_module.createServer(function (request, response) {
// Create the parent:
var view = new Bifocals(response);
// Assign a content type.
view.content_type = 'text/html';
// Create multiple children:
var users_child = view.child("content");
var blog_child = view.child("blog");
// Retrieve some rows from the database, and call back to me later
// I will explain in more detail with the next database call
database.query('select * from posts', function (err, rows) {
if (err) {
view.statusError();
} else {
if (rows.length) {
blog_child.set('posts', rows);
blog_child.render('templates/blog/many.html');
} else {
blog_child.render('templates/blog/empty.html');
}
});
});
// Retrieve some more rows from the database, and call back to me later.
// Because both of the child views are handled in the callback, we have no idea which view will be rendered first. The view attached to the fastest query will be the first view to render.
// The library handles this all for you, the order does not matter.
database.query('select * from user', function (err, rows) {
if (err) {
// If an error occurred, notify the user and end the rendering immediately. This will not wait for the other database query to finish, so you can shave some time off of notifying the user.
view.statusError();
} else {
if (rows.length) {
user_child.set('users', rows);
user_child.render('templates/user/many.html');
} else {
user_child.render('templates/user/empty.html');
}
});
});
// Write the view to the response
// At this point, neither of the children will have rendered.
// The view is flagged as "ready", and whenever the final child has rendered this view will render.
view.render("templates/index.html");
});
app.set('views', __dirname + '/templates');
Second you need to configure bifocals with a text/html renderer. You can use the same rendering function you would for express. The following example uses the consolidate library, which was built so express could use any template system.
bifocals_module.addRenderer('text/html', require('consolidate').handlebars);
Third, you need to tell your app to use the middleware.
app.use(bifocals_module.__express({
app : app
}));
Once configured, a Bifocals object is created for each request. The response object provided to each route is modified in two ways to accomidate this.Child
function.You must extend the Renderer
class:
var FileRenderer = module.exports = function FileRenderer() {
Renderer.call(this);
}
util_module.inherits(FileRenderer, Renderer);
You must define a render function. It does not need to take any parameters, but you will be provided the proper template if possible. This template will be a string, defining the full path to the target template. If you wish to cache this file it should be handled via your renderer.
Each renderer will be assigned a "response" object. It may or may not be a ServerResponse
object, so you can only expect expect two functions, write(mixed)
and end()
.
write(mixed)
should be called any time data is complete and ready to be sent to the user, while end()
should be called to signify the end of data.
Additionally each renderer will have an "_error" function. This function will be provided with the view's error handler, it should be called any time an error occurs.
FileRenderer.prototype.render = function (template) {
var _self = this;
// Find the template and load it from disk
var stream = fs_module.createReadStream(template);
stream.on('data', function (data) {
// Write data to the response
_self.response.write(data);
});
stream.on('error', function (err) {
// Call the renderer's error function
_self._error(err);
});
stream.on('end', function () {
// Signal the end of rendering
_self.response.end();
});
}
content_type
: string
renderer
: Renderer
Registers a renderer object to a content type.
Every content type used must have an associated renderer. Included in the "renderers" folder are two examples, one for serving files directly, and one for rendering templates through Handlebars.
content_type
: string
Returns the appropriate renderer for the provided content_type parameter.
If no renderer has been registered to the content type, an error is thrown.
An object containing all of the states a bifocal object can be in.
// todo : provide explicit definitions of each state
response
: http response object
Constructor for the bifocal object. This should only be called for a root view, any child views should be spawned off the root via child.
When the render method is called, it will write out to the response object provided to this consstructor.
Your first task should be to assign an error handler.
Be careful with the error handler. If statusError
handles all failed templates and then fails, you will become locked in an infinite loop.
var view = new Bifocals(response);
view.error(function (error) {
console.log(error);
view.statusError(error);
});
The value to put in the responses content-type header. Also used to determine which renderer should be used.
A string that is provided along with all view data to the final render step. If this string is set, it will override the template provided to the render function.
When provided to the renderer, it is prefixed with the dir
property.
A string which is prefixed onto the "template" before it is send to a renderer.
This allows you to set a standard template directory early on, and keep it out of your render calls.
The bifocal object that created this object.
Changing this to a different object will likely break the rendering hierarchy entirely. // todo what does the parent element have here.
The original bifocal object, which started the whole series of child elements.
Changing this to a different object will likely break the rendering flow completely.
Returns true or false, depending on whether the view has completed its rendering process.
key
: string
value
: mixed
Stores a key value pair to be provided to the renderer.
This is useful for template engines.
key
: string
Retrieves a value that was assigned via set
, based on the values key.
Returns true or false, depending on whether the view is able to be rendered immediately.
template
: string (varies, depending on the renderer)
force
: optional boolean default false
Combines the data and template into a string, and writes it all out to the response.
The first parameter should be a path to your template. If you have set the dir
property, it will be prepended to the template parameter. For the Handlebars renderer, the final combined template needs to result in an absolute file path.
If you have already assigned a template to the view (via the template
property), the template
parameter will be ignored.
The second parameter is a boolean. If true, all child views will be canceled and the render will happen immediately. This is generally useful for handling errors.
This should be the last function you manually call on a view. If you want to use child views, they must be created before this render call, but you can keep using them after this render call.
Stops all child views from rendering, and ensures that this view will not be rendered.
Builds a renderer object from various data located within this view.
fn
: function(error)
Assigns an error handler to this view. Child elements will inherit this error handler. The callback will be provided one parameter: the error.
key
: string
template
: optional string (varies, depending on the renderer)
Creates a child view, which this view will depend on.
The first parameter is a key. The final rendered output of the child will be set to the parent view using this key. For example:
var view = new Bifocals();
var child = view.child('a');
child.set('content', 'hello');
// template.html is simply {{{content}}}
child.render('template.html');
view.get('a') === 'hello';
The second parameter is a template. If you provide a template here, it will be considered the same as child.template = template
. This means it will override the template provided to the render(template,force)
function call.
code
: number
This should only be called on the parent. This sets the status code in the response headers.
headers
: object
Sets a collection of headers, where each key:value pair is a Header:value pair.
template
: optional string (varies, depending on the renderer)
End rendering immediately with a 404: Not found status code, and renders the template if provided.
template
: optional string (varies, depending on the renderer)
End rendering immediately with a 401: Unauthorized status code, and renders the template if provided.
error
: optional error
template
: optional template (varies, depending on the renderer)
End rendering immediately with a 500: Error code, and renders the template if provided Additionally the error is assigned to the view with the key "error".
redirect_url
: string (url)
End rendering immediately with a 201: Created code. redirect_url
is sent via the Location header, and should point to the newly created resource.
redirect_url
: string (url)
End rendering immediately with a 302: Found code. redirect_url
is sent via the Location header, which the client should automatically follow.
todo: support other 300's
End rendering immediately with a 304: Not Modified code. This should be in response to a request with cache headers, and the client should respond by displaying the previously cached response. //todo : as a parameter take some headers to control this? date, etag, expires, cache control
supported_methods
: array
Ends rendering immediately with a 405: Unsupported Method code. This should be in response to a failed request, if the request would be successful when made with an alternative http method.
Constructor for the base Renderer
Automatically set when Bifocals builds a renderer. This will be an object with write(data)
and end()
functions.
Automatically set when Bifocals builds a renderer. This will be an object of key value pairs.
fn
: function(error)
returns : this
Assigns a function that will be called any time an error occurs in the renderer. The assigned function should be called via the renderer's _error
property.
fn
: function()
returns : this
Assigns a function that will be called when the renderer is complete. The assigned function should be called via the _end
property.
If an object property is prefixed with an underscore (_), ignore it. Changing these properties is not supported and has not been considered in the development of the library.
If an object property is not prefixed with an underscore (_), and is not a function, you may change it however you see fit. Just read the documentation and understand the side effects of changing the variable.
"Fill"