Socket
Socket
Sign inDemoInstall

blade

Package Overview
Dependencies
18
Maintainers
1
Versions
113
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 3.0.0-alpha1 to 3.0.0-alpha13

test/output/attributes_interpolation.html

57

lib/blade.js

@@ -9,12 +9,8 @@ /** Blade Public API and Middleware

*/
var fs = require('fs'),
path = require('path'),
url = require('url'),
Compiler = require('./compiler'),
uglifyjs = null;
var fs = require("fs"),
path = require("path"),
url = require("url"),
Compiler = require("./compiler"),
bladeutil = require("./util");
try {
uglifyjs = require('uglify-js');
} catch(e) {};
exports.compile = compile;

@@ -36,3 +32,3 @@ exports.compileFile = compileFile;

if(options.cache && !options.filename)
cb(new Error('The `filename` option is required for caching'));
cb(new Error("The `filename` option is required for caching"));
else if(options.cache && cache[options.filename])

@@ -95,12 +91,12 @@ cb(null, cache[options.filename]);

options = options || {};
options.mount = options.mount || '/views/';
options.mount = options.mount || "/views/";
if(typeof options.runtimeMount == "undefined")
options.runtimeMount = '/blade/blade.js';
options.runtimeMount = "/blade/blade.js";
if(typeof options.pluginsMount == "undefined")
options.pluginsMount = '/blade/plugins/';
options.pluginsMount = "/blade/plugins/";
if(options.compileOptions == null)
options.compileOptions = {
'cache': process.env.NODE_ENV == "production",
'minify': process.env.NODE_ENV == "production",
'includeSource': process.env.NODE_ENV == "development"
"cache": process.env.NODE_ENV == "production",
"minify": process.env.NODE_ENV == "production",
"includeSource": process.env.NODE_ENV == "development"
};

@@ -114,10 +110,10 @@ sourcePath = path.resolve(sourcePath);

{
if(fileCache['runtime'])
res.type('application/javascript').send(fileCache['runtime']);
if(fileCache["runtime"])
res.type("application/javascript").send(fileCache["runtime"]);
else
fs.readFile(__dirname + "/../lib/runtime.js", function(err, data) {
if(err) return next(err);
data = uglify(data.toString() );
fileCache['runtime'] = data;
res.type('application/javascript').send(data);
data = bladeutil.uglify(data.toString(), true);
fileCache["runtime"] = data;
res.type("application/javascript").send(data);
});

@@ -134,9 +130,9 @@ }

else if(fileCache[fullPath])
res.type('application/javascript').send(fileCache[fullPath]);
res.type("application/javascript").send(fileCache[fullPath]);
else
fs.readFile(fullPath, function(err, data) {
if(err) return next(err);
data = uglify(data.toString() );
data = bladeutil.uglify(data.toString(), true);
fileCache[fullPath] = data;
res.type('application/javascript').send(data);
res.type("application/javascript").send(data);
});

@@ -153,3 +149,3 @@ }

if(err) return next(err);
res.type('application/javascript');
res.type("application/javascript");
res.send("blade._cachedViews[" + JSON.stringify(filename) + "]=" + tmpl.toString() +

@@ -164,12 +160,1 @@ ";if(blade._cb[" + JSON.stringify(filename) + "])blade._cb[" +

}
function uglify(str) {
if(uglifyjs)
{
var ast = uglifyjs.parser.parse(str),
ugly = uglifyjs.uglify;
ast = ugly.ast_mangle(ast);
ast = ugly.ast_squeeze(ast);
str = ugly.gen_code(ast);
}
return str;
}

@@ -15,9 +15,4 @@ /** Blade Compiler

selfClosingTags = require('./self-closing-tags'),
filters = require('./filters'),
uglifyjs = null;
filters = require('./filters');
try {
uglifyjs = require('uglify-js');
} catch(e) {}
module.exports = Compiler;

@@ -99,7 +94,12 @@

var baseRelStart = this.buf.length;
//Expose the filename no matter what; this is needed for branch labels if a
//"live page update engine" is used
this._pushOff(this.options.filename ? ns + ".filename = " +
JSON.stringify(this.options.filename) + ";" : "");
//Only include error handling and source code, if needed
if(this.options.minify !== true && this.options.includeSource)
this._pushOff(ns + '.source = ' + JSON.stringify(this.string) + ";");
if(this.options.minify !== true)
this._pushOff((this.options.filename ? ns + ".filename = " +
JSON.stringify(this.options.filename) + ";" : "") + "\ntry {");
this._pushOff("\ntry {");
//Now compile the template
this._pushOff('with(' + ns + '.locals) {');

@@ -150,15 +150,3 @@ if(this.ast.doctype != null)

//Try to use uglify-js
var str = this.template.toString();
if(uglifyjs)
{
var ast = uglifyjs.parser.parse(str),
ugly = uglifyjs.uglify;
if(this.minify)
{
ast = ugly.ast_mangle(ast);
ast = ugly.ast_squeeze(ast);
}
str = ugly.gen_code(ast, {'beautify': !this.minify});
}
return str;
return bladeutil.uglify(this.template.toString(), this.minify);
};

@@ -243,8 +231,21 @@ } catch(e) {

//id attribute
if(!attrs.id && node.id)
attrs.id = {'escape': false, 'text': node.id};
if(node.id)
{
/* If the tag doesn't have an "id" attribute, add it now; otherwise,
only add it if the attribute is 'code'.
That is, `div(id="foo")` always takes precedence over `div#foo`
*/
if(!attrs.id || attrs.id.text == "")
attrs.id = {'escape': false, 'text': node.id};
else if(attrs.id.code)
attrs.id.code = "(" + attrs.id.code + ") || " + JSON.stringify(node.id);
}
//class attribute
if(node.classes.length > 0)
{
if(attrs['class'] == undefined)
/* If the tag doesn't have a "class" attribute, add it now; otherwise,
if the "class" attribute is text, just append to it now; otherwise,
append the classes at runtime
*/
if(!attrs['class'])
attrs['class'] = {'escape': false, 'text': node.classes.join(" ")};

@@ -299,2 +300,14 @@ else if(attrs['class'].text)

{
//interpolate text attributes
if(attrs[i].text)
{
var stringified = JSON.stringify(attrs[i].text),
interpolated = bladeutil.interpolate(stringified, ns);
//check to see if this text attribute needs to be interpolated
if(interpolated != stringified)
{
delete attrs[i].text;
attrs[i].code = interpolated;
}
}
//take care of text attributes here

@@ -310,8 +323,8 @@ if(attrs[i].text)

}
else if(i == "class" && attrs[i].append)
varAttrs += "," + i + ":{a:" + JSON.stringify(attrs[i].append) +
",v:" + attrs[i].code + (attrs[i].escape ? ", e:1" : "") + "}";
//take care of code attributes here
else
varAttrs += "," + JSON.stringify(i) + ":{v:" + attrs[i].code +
(attrs[i].escape ? ", e:1" : "") + "}";
(attrs[i].escape ? ",e:1" : "") +
(i == "class" && attrs[i].append ?
",a:" + JSON.stringify(attrs[i].append): "") + "}";
}

@@ -387,4 +400,7 @@ if(varAttrs.length > 0)

}
var output = this.options.filters[node.name](node.filtered_text,
{'minify': this.options.minify, 'compress': this.options.minify});
var output = this.options.filters[node.name](node.filtered_text, {
'minify': this.options.minify,
'compress': this.options.minify,
'filename': this.options.filename
});
//Ensure we prepend a newline if the last node was a text node.

@@ -510,16 +526,27 @@ if(this.prependNewline)

var attrs = node.children[0].attributes;
modifyAttribute('id', 'this.id', false);
modifyAttribute('class', 'this.classes', true);
function modifyAttribute(attrName, mergeWith, append) {
modifyAttribute('id');
modifyAttribute('class');
function modifyAttribute(attrName) {
//If the attribute does not exist, simply add it as code; no need to escape
if(attrs[attrName] == null)
attrs[attrName] = {'escape': false, 'code': mergeWith};
attrs[attrName] = {'escape': false, 'code': "this." + (attrName == "class" ? "classes" : attrName) };
//Merge passed-in classes with the class attribute
else if(attrName == "class")
{
if(attrs[attrName].code)
attrs[attrName].code = "(this.classes || []).concat(" + attrs[attrName].code + ")";
else
{
attrs[attrName].code = "(this.classes || []).concat(" +
bladeutil.interpolate(JSON.stringify(attrs[attrName].text) ) + ")";
delete attrs[attrName].text;
}
}
//Replace the attribute with the passed-in attribute value
else if(attrs[attrName].code)
attrs[attrName].code = "(" + mergeWith + (append ? '.push(' +
attrs[attrName].code + '),' + mergeWith
: '||' + attrs[attrName].code) + ")";
attrs[attrName].code = "this." + attrName + '||' + attrs[attrName].code;
else
{
attrs[attrName].code = "(" + mergeWith + (append ? '.push(' +
JSON.stringify(attrs[attrName].text) + '),' + mergeWith
: '||' + JSON.stringify(attrs[attrName].text)) + ")";
attrs[attrName].code = "this." + attrName + '||' +
bladeutil.interpolate(JSON.stringify(attrs[attrName].text) );
delete attrs[attrName].text;

@@ -565,3 +592,3 @@ }

case 'constant':
this._pushOff(ns + ".r.constant(function() {");
this._pushOff(ns + ".r.constant(" + node.line + ",function() {");
for(var i = 0; i < node.children.length; i++)

@@ -571,2 +598,10 @@ this._compileNode(node.children[i]);

break;
case 'preserve':
if(!node.preserved)
node.preserved = "[]";
this._pushOff(ns + ".r.preserve(" + node.line + ",(" + node.preserved + ")||[],function() {");
for(var i = 0; i < node.children.length; i++)
this._compileNode(node.children[i]);
this._pushOff("}," + ns + ");");
break;
case 'foreach':

@@ -573,0 +608,0 @@ this._pushOff(ns + ".r.foreach(" + ns + "," + node.cursor + ",function(" +

@@ -23,3 +23,3 @@ exports['nl2br'] = function(text) {

return '<script type="text/javascript">\n' +
require('coffee-script').compile(str) +
require('coffee-script').compile(text) +
'\n</script>';

@@ -26,0 +26,0 @@ };

/** Blade Run-time helper functions
(c) Copyright 2012. Blake Miner. All rights reserved.
(c) Copyright 2012-2013. Blake Miner. All rights reserved.
https://github.com/bminer/node-blade

@@ -15,22 +15,20 @@ http://www.blakeminer.com/

htmlNoOp = function(arg1, html) {return html;},
funcNoOp = function(arg1, func) {return func();},
funcNoOp = function(func) {return func();},
funcNoOp2 = function(arg1, func) {return func();},
liveUpdate = {
"attachEvents": htmlNoOp,
"setDataContext": htmlNoOp,
"isolate": function(func) {return func();},
"isolate": funcNoOp,
"render": funcNoOp,
"list": function(cursor, itemFunc, elseFunc) {
var itemList = cursor || [];
//cursor could have an observe method, in which case...
if(cursor && "observe" in cursor)
{
//Let's go ahead and observe it...
itemList = [];
cursor.observe({
"added": function(item) {
//added must be called once per element before the
//`observe` call completes
itemList.push(item);
}
}).stop(); //and then stop observing it.
}
var itemList = [];
//cursor must have an observe method
//Let's go ahead and observe it...
cursor.observe({
"added": function(item) {
//added must be called once per element before the
//`observe` call completes
itemList.push(item);
}
}).stop(); //and then stop observing it.
if(!itemList.length) //If itemList.length is null, zero, etc.

@@ -44,4 +42,5 @@ return elseFunc();

},
"labelBranch": funcNoOp,
"createLandmark": funcNoOp
"labelBranch": funcNoOp2,
"createLandmark": funcNoOp2,
"finalize": htmlNoOp //should do nothing and return nothing meaningful
};

@@ -142,5 +141,28 @@ /* blade.Runtime.mount is the URL where the Blade middleware is mounted (or where

render callback function.
- events - a space-delimited string of event types (i.e. "click change")
- elementID - the "id" attribute of the element to which an event handler is to
be bound
- eventHandler - the event handler
- buf - the Blade template buffer
- commentExists - false if and only if this is the first call to runtime.bind
for this element
*/
runtime.bind = function(events, elementID, eventHandler, buf, commentExists) {
eventHandlers[elementID] = {"events": events, "handler": eventHandler};
/* Place event map into `eventHandlers` global.
Examples of event maps:
// Fires when any element is clicked
"click": function (event) { ... }
//Fires when an element with class "accept" is clicked, or when a key is pressed
"keydown, click .accept": function (event) { ... }
//Fires when an element with class "accept" is either clicked or changed
"click .accept, change .accept": function (event) { ... }
See http://docs.meteor.com/#eventmaps for more information
*/
var eventMapKey = "";
var eventTypes = events.split(" ");
for(var i = 0; i < eventTypes.length; i++)
eventMapKey = "," + eventTypes[i] + " #" + elementID;
eventHandlers[eventMapKey.substr(1)] = eventHandler;
var comment = "i[" + JSON.stringify(events) + "]=" + eventHandler.toString();

@@ -236,10 +258,13 @@ //If other event handlers were already declared for this element,

}
//Set a timer to return an Error after a timeout expires.
var timer = setTimeout(function() {
//Function to be called if the template could not be loaded
function errorFunction(reason) {
var cb = blade._cb[filename].cb; //array of callbacks
delete blade._cb[filename];
st.parentNode.removeChild(st);
callCallbacks(cb, new Error("Timeout Error: Blade Template [" + filename +
"] could not be loaded.") );
}, runtime.options.loadTimeout);
callCallbacks(cb, new Error("Blade Template [" + filename +
"] could not be loaded: " + (reason ? reason : "Request timed out") ) );
}
//Set a timer to return an Error after a timeout expires.
var timer = setTimeout(errorFunction, runtime.options.loadTimeout);
//Setup a callback to be called if the template is loaded successfully
var tmp = blade._cb[filename] = function(dependenciesReldir, dependencies, unknownDependencies) {

@@ -267,2 +292,11 @@ clearTimeout(timer);

s.parentNode.insertBefore(st, s);
//Also setup onload, onreadystatechange, and onerror callbacks to detect errors earlier than the timeout
st.onload = st.onreadystatechange = st.onerror = function() {
var x = this.readyState;
if((!x || x == "loaded" || x == "complete") && blade._cb[filename])
{
clearTimeout(timer);
errorFunction("Request failed");
}
};
}

@@ -285,9 +319,18 @@ return false;

/* This function is a hack to get the resolved URL, so that caching works
okay with relative URLs */
okay with relative URLs.
This function does not work properly if `filename` contains too many "../"
For example, passing "alpha/beta/../../filename.blade" is acceptable; whereas,
"alpha/beta/../../../filename.blade" is unacceptable input.
*/
runtime.resolve = function(filename) {
if(runtime.client) {
//Use the browser's ability to resolve relative URLs
var x = document.createElement('div');
x.innerHTML = '<a href="' + runtime.escape("./" + filename) + '"></a>';
x = x.firstChild.href;
x = x.substr(window.location.href.length).replace(/\/\//g, '/');
/* suppose `window.location.href` is "http://www.example.com/foo/bar/document.html"
and `filename` is "alpha/./beta/../charlie.blade", then
`x` will be something like "http://www.example.com/foo/bar//alpha/charlie.blade" */
var prefix = window.location.href;
x = x.substr(prefix.substr(0, prefix.lastIndexOf("/") ).length).replace(/\/[\/]+/g, '/');
if(x.charAt(0) == '/') x = x.substr(1);

@@ -307,3 +350,5 @@ return x;

pSource = info.source,
pLocals = info.locals;
pLocals = info.locals,
pUnsafeBlockAction = info.unsafeBlockAction,
pPreserve = info.preserve;
info.inc = true;

@@ -333,2 +378,4 @@ //If exposing locals, the included view gets its own set of locals

info.locals = pLocals;
info.unsafeBlockAction = pUnsafeBlockAction;
info.preserve = pPreserve;
}, info);

@@ -421,22 +468,65 @@ });

/* Define a constant block */
runtime.constant = function(func, buf) {
buf.push(liveUpdate.createLandmark({"constant": true}, function(landmark) {
/* Note: This following line is the same as:
var len = buf.length;
func();
return runtime.capture(buf, len);
*/
return runtime.capture(buf, buf.length, func() );
runtime.constant = function(label, func, buf) {
if(buf.unsafeBlockAction)
throw new Error("You cannot preserve elements here because this template either defines a block or performs a block modification.");
buf.preserve = true;
buf.push(liveUpdate.labelBranch(buf.filename + ":" + label, function () {
return liveUpdate.createLandmark({"constant": true}, function(landmark) {
/* Note: This following line is the same as:
var len = buf.length;
func();
return runtime.capture(buf, len);
*/
return runtime.capture(buf, buf.length, func() );
});
}) );
};
/* Foreach/else block */
runtime.foreach = function(buf, cursor, listFunc, elseFunc) {
buf.push(liveUpdate.list(cursor, function(item) {
return runtime.capture(buf, buf.length, listFunc.call(item, item) );
}, function() {
return runtime.capture(buf, buf.length, elseFunc() );
/* Define a preserve block */
runtime.preserve = function(label, preserved, func, buf) {
if(buf.unsafeBlockAction)
throw new Error("You cannot preserve elements here because this template either defines a block or performs a block modification.");
buf.preserve = true;
buf.push(liveUpdate.labelBranch(buf.filename + ":" + label, function () {
return liveUpdate.createLandmark({"preserve": preserved}, function(landmark) {
/* Note: This following line is the same as:
var len = buf.length;
func();
return runtime.capture(buf, len);
*/
return runtime.capture(buf, buf.length, func() );
});
}) );
};
/* Foreach/else block */
runtime.foreach = function(buf, cursor, itemFunc, elseFunc) {
//Define wrapper functions for itemFunc and elseFunc
function itemFuncWrapper(item) {
var label = (item._id || (typeof item === 'string' ? item : null) || liveUpdate.UNIQUE_LABEL);
return liveUpdate.labelBranch(label, function() {
return runtime.capture(buf, buf.length, itemFunc.call(item, item) );
});
}
function elseFuncWrapper() {
return liveUpdate.labelBranch("else", function() {
return elseFunc ? runtime.capture(buf, buf.length, elseFunc() ) : "";
});
}
//Call liveUpdate.list for Cursor Objects
if(cursor && "observe" in cursor)
buf.push(liveUpdate.list(cursor, itemFuncWrapper, elseFuncWrapper) );
else
{
//Allow non-Cursor Objects or Arrays to work, as well
var html = "", empty = 1;
for(var i in cursor)
{
empty = 0;
html += itemFuncWrapper(cursor[i]);
}
buf.push(empty ? elseFuncWrapper() : html);
}
};
/* Copies error reporting information from a block's buffer to the main

@@ -456,2 +546,5 @@ buffer */

runtime.blockDef = function(blockName, buf, childFunc) {
if(buf.preserve)
throw new Error("You cannot define a block in this template because element preservation is being used.");
buf.unsafeBlockAction = true;
var block = buf.blocks[blockName] = {

@@ -492,2 +585,4 @@ 'parent': buf.block || null, //set parent block

runtime.blockRender = function(type, blockName, buf) {
if(buf.preserve)
throw new Error("You cannot render a block in this template because element preservation is being used.");
var block = buf.blocks[blockName];

@@ -499,2 +594,3 @@ if(block == null)

"' is a regular, non-parameterized block, which cannot be rendered.");
buf.unsafeBlockAction = true;
//Extract arguments

@@ -532,5 +628,8 @@ var args = [block.buf];

runtime.blockMod = function(type, blockName, buf, childFunc) {
if(buf.preserve)
throw new Error("You cannot modify a block in this template because element preservation is being used.");
var block = buf.blocks[blockName];
if(block == null)
throw new Error("Block '" + blockName + "' is undefined.");
buf.unsafeBlockAction = true;
if(type == "r") //replace

@@ -548,3 +647,8 @@ {

{
try {childFunc(block.buf);}
try {
//Copy buf.rel and buf.base to block.buf
block.buf.rel = buf.rel;
block.buf.base = buf.base;
childFunc(block.buf);
}
catch(e) {blockError(buf, block.buf); throw e;}

@@ -664,2 +768,3 @@ }

}
where keys are space-delimited event types and values are event handler functions
*/

@@ -666,0 +771,0 @@ //now r refers to the properties populated in the event Object map

@@ -25,2 +25,30 @@ /** Blade utility functions

return str.replace(/'/g, "\\'");
}
};
/* Use uglify-js to obfuscate and optimize JavaScript code */
var uglifyjs = null;
try {
uglifyjs = require("uglify-js");
} catch(e) {}
exports.uglify = function(str, minify) {
if(uglifyjs)
{
var ast = uglifyjs.parse(str);
if(minify)
{
//compress
ast.figure_out_scope();
var comp = uglifyjs.Compressor();
ast = ast.transform(comp);
//mangle
ast.figure_out_scope();
ast.compute_char_frequency();
ast.mangle_names();
}
//output
var stream = uglifyjs.OutputStream({"beautify": !minify});
ast.print(stream);
str = stream.toString();
}
return str;
};

@@ -5,8 +5,15 @@ var path = require('path');

try {
blade = require('blade');
blade = require('blade')
}
catch(e) {
//XXX super lame! we actually have to give paths relative to
// app/inner/app.js, since that's who's evaling us.
blade = require('../../packages/blade/node_modules/blade');
try {
//XXX super lame! we actually have to give paths relative to
// app/lib/packages.js, since that's who's evaling us.
// The next line is for the core Meteor-installed package
blade = require('../../packages/blade/node_modules/blade');
}
catch(e) {
//XXX super lame! The next line is for the Meteorite-installed package
blade = require(process.cwd() + "/.meteor/meteorite/packages/blade/node_modules/blade");
}
}

@@ -38,3 +45,3 @@ //-- end of horrible hack

if(err) throw err;
if(templateName == "head")
if(templateName == "head" || templateName == "body")
tmpl({}, function(err, html) {

@@ -44,3 +51,3 @@ //This should happen synchronously due to compile options set above

bundle.add_resource({
type: 'head',
type: templateName, //either "head" or "body"
data: html,

@@ -50,17 +57,7 @@ where: where

});
else if(templateName == "body")
tmpl({}, function(err, html) {
//This should happen synchronously due to compile options set above
if(err) throw err;
bundle.add_resource({
type: 'body',
data: html,
where: where
});
});
bundle.add_resource({
type: 'js',
path: "/views/" + templateName + ".js", //This can be changed to whatever
data: new Buffer("blade.cachedViews[" +
//just put the template itself in blade.cachedViews
data: new Buffer("blade._cachedViews[" +
//just put the template itself in blade._cachedViews
JSON.stringify(templateName + ".blade") + "]=" + tmpl.toString() + ";" +

@@ -79,5 +76,9 @@ //define a template with the proper name

there is no async. All code is ran synchronously. */
"var ret = ''; blade.cachedViews[" + JSON.stringify(templateName + ".blade") +
"](data, function(err,html) {" +
"if(err) throw err; ret = html;" +
"var ret = ''; blade._cachedViews[" + JSON.stringify(templateName + ".blade") +
"](data, function(err,html,info) {" +
"if(err) throw err;" +
//Remove event handler attributes
'html = html.replace(/on[a-z]+\\=\\"return blade\\.Runtime\\.trigger\\(this\\,arguments\\)\\;\\"/g, "");' +
//now bind any inline events and return
"ret = blade.LiveUpdate.attachEvents(info.eventHandlers, html);" +
"});\n" +

@@ -84,0 +85,0 @@ //so... by here, we can just return `ret`, and everything works okay

@@ -1,14 +0,37 @@

blade.Runtime.loadTemplate = function(baseDir, filename, compileOptions, cb) {
//Append .blade for filenames without an extension
if(filename.split("/").pop().indexOf(".") < 0)
filename += ".blade";
//Either pull from the cache or return an error
filename = blade.Runtime.resolve(filename);
if(blade.cachedViews[filename])
{
cb(null, blade.cachedViews[filename]);
return true;
(function() {
//Helper function
function resolveFilename(filename) {
//Append .blade for filenames without an extension
if(filename.split("/").pop().indexOf(".") < 0)
filename += ".blade";
return blade.Runtime.resolve(filename);
}
cb(new Error("Template '" + filename + "' could not be loaded.") );
return false;
};
//Overwrite blade.Runtime.loadTemplate and include functions
blade.Runtime.loadTemplate = function(baseDir, filename, compileOptions, cb) {
filename = resolveFilename(filename);
//Either pull from the cache or return an error
if(blade._cachedViews[filename])
{
cb(null, blade._cachedViews[filename]);
return true;
}
cb(new Error("Template '" + filename + "' could not be loaded.") );
return false;
};
var oldInclude = blade.Runtime.include;
blade.Runtime.include = function(relFilename, info) {
var name = resolveFilename(info.rel + "/" + relFilename);
//Remove .blade file extension
if(name.substr(-6) == ".blade")
name = name.substr(0, name.length - 6);
//Add helpers to info.locals
var tmpl = Template[name] || {};
var tmplData = tmpl._tmpl_data || {};
_.extend(info.locals, Meteor._partials[name], tmplData.helpers || {});
//Now call original "include" function
return oldInclude.apply(this, arguments);
};
//Use Spark as the live update engine
blade.LiveUpdate = Spark;
})();

@@ -18,3 +18,3 @@ {

],
"version": "3.0.0-alpha1",
"version": "3.0.0alpha13",
"homepage": "https://github.com/bminer/node-blade",

@@ -33,6 +33,6 @@ "repository": {

"pegjs": ">=0.7",
"uglify-js": ">=1.2"
"uglify-js": "~2"
},
"optionalDependencies": {
"uglify-js": ">=1.2"
"uglify-js": "~2"
},

@@ -46,7 +46,7 @@ "engines": {

"prepublish": "./lib/parser/build.sh",
"postinstall": "node ./postinstall.js"
"postinstall": "node ./postinstall.js"
},
"contributors": [
"Michel Löhr (https://github.com/mlohr)"
"Michel Löhr (https://github.com/mlohr)"
]
}
}
/** Blade Live UI plugin
(c) Copyright 2012. Blake Miner. All rights reserved.
https://github.com/bminer/node-blade
http://www.blakeminer.com/
(c) Copyright 2012-2013. Blake Miner. All rights reserved.
https://github.com/bminer/node-blade
http://www.blakeminer.com/
See the full license here:
https://raw.github.com/bminer/node-blade/master/LICENSE.txt
See the full license here:
https://raw.github.com/bminer/node-blade/master/LICENSE.txt
Hard Dependencies:
- node-blade runtime
Soft Dependencies (this plugin should still work without these):
- Spark (https://github.com/meteor/meteor/wiki/Spark)
- The easiest way to obtain Spark is to clone the Meteor Github repo
(git://github.com/meteor/meteor.git) and run `admin/spark-standalone.sh`
- Underscore.js is also a requirement for Spark at this time
Works well with:
- jQuery
Adds the following to the `blade` global variable:

@@ -13,6 +25,5 @@ - Model

Adds the following to `blade.Runtime`:
- render(viewName, locals, cb)
- renderTo(element, viewName, locals [, cb])
- renderTo(element, viewName, locals [,landmarkOptions] [, cb])
Adds the following functions to jQuery.fn:
- render(viewName, locals [, cb])
- render(viewName, locals [,landmarkOptions] [, cb])

@@ -28,71 +39,66 @@ Browser Support:

using a conditional comment like this:
```html
<!--[if IE 8]>
<script type="text/javascript" src="/blade/plugins/definePropertyIE8.js"></script>
<![endif]-->
For element preservation support, please add jQuery 1.7+ to your project.
```
*/
(function() {
if(!window.blade) return; //Nothing to expose, so just quit
var Context = function () {
// Each context has a unique number. You can use this to avoid
// storing multiple copies of the same context in the
// invalidation list.
this.id = Context.next_id++;
this._callbacks = []; //each of these are called when invalidated
this._invalidated = false;
};
blade.Context = Context; //expose this Object
//Global static variables
Context.next_id = 0;
Context.current = null;
Context.pending_invalidate = []; //list of Contexts that have been invalidated but not flushed
//Calls all Context _callbacks on each Context listed in Context.pending_invalidate
Context.flush = function() {
while (Context.pending_invalidate.length > 0) {
var pending = Context.pending_invalidate;
Context.pending_invalidate = [];
for(var i = 0; i < pending.length; i++) {
var ctx = pending[i];
for(var j = 0; j < ctx._callbacks.length; j++)
ctx._callbacks[j](ctx);
delete ctx._callbacks; //maybe help the GC
//This plugin *can* work without Spark...
var Context = blade.Context = (window.Meteor && Meteor.deps) ? Meteor.deps.Context || {} : {};
//Use Spark as the live update engine
if(window.Spark)
{
//--- Basically an excerpt from https://github.com/meteor/meteor/blob/master/packages/spark/utils.js ---
//--- Minor modification is to exclude id's starting with "blade_"
Spark._labelFromIdOrName = function(n) {
var label = null;
if (n.nodeType === 1 /*ELEMENT_NODE*/) {
if (n.id && n.id.substr(0, 6) != "blade_") {
label = '#' + n.id;
} else if (n.getAttribute("name")) {
label = n.getAttribute("name");
// Radio button special case: radio buttons
// in a group all have the same name. Their value
// determines their identity.
// Checkboxes with the same name and different
// values are also sometimes used in apps, so
// we treat them similarly.
if (n.nodeName === 'INPUT' &&
(n.type === 'radio' || n.type === 'checkbox') &&
n.value)
label = label + ':' + n.value;
// include parent names and IDs up to enclosing ID
// in the label
while (n.parentNode &&
n.parentNode.nodeType === 1 /*ELEMENT_NODE*/) {
n = n.parentNode;
if (n.id) {
label = '#' + n.id + "/" + label;
break;
} else if (n.getAttribute('name')) {
label = n.getAttribute('name') + "/" + label;
}
}
}
}
}
return label;
};
//--- End
//--- Excerpt from https://github.com/meteor/meteor/blob/master/packages/preserve-inputs/preserve-inputs.js ---
var inputTags = 'input textarea button select option'.split(' ');
var selector = _.map(inputTags, function (t) {
return t.replace(/^.*$/, '$&[id], $&[name]');
}).join(', ');
Spark._globalPreserves[selector] = Spark._labelFromIdOrName;
//--- End
//Copy stuff from Spark to blade.LiveUpdate
blade.LiveUpdate = Spark;
}
//Run a function in this Context
Context.prototype.run = function (f) {
var previous = Context.current;
Context.current = this;
try { var ret = f(); }
finally { Context.current = previous; }
return ret;
};
//Just mark the Context as invalidated; do not call any invalidation functions
//just yet; instead, schedule them to be executed soon.
Context.prototype.invalidate = function () {
if (!this._invalidated) {
this._invalidated = true;
// If this is first invalidation, schedule a flush.
// We may be inside a flush already, in which case this
// is unnecessary but harmless.
if (Context.pending_invalidate.length == 0)
setTimeout(Context.flush, 1);
Context.pending_invalidate.push(this);
}
};
//Calls f immediately if this context was already
//invalidated. The callback receives one argument, the Context.
Context.prototype.on_invalidate = function (f) {
if (this._invalidated)
f(this);
else
this._callbacks.push(f);
};
function Model(data) {

@@ -157,3 +163,3 @@ /*A proxy object that can be written to

self._keyDeps[key][context.id] = context;
context.on_invalidate(function() {
context.onInvalidate(function () {
//Check to see if self._keyDeps[key] exists first,

@@ -257,134 +263,74 @@ //as this property might have been deleted

/* render(viewName, locals, cb)
Asynchronously loads (if necessary) and renders the specified template
using the specified locals in a new Context. If the Context is invalidated,
the template will be re-rendered and the callback will be called again.
- viewName - the name of the view to be loaded and rendered
/* Renders the specified view using the specified locals and injects the generated
DOM into the specified element. In addition, any event handlers created by
the view are bound.
Finally, the element in focus is "preserved" and if the element either has an
'id' attribute or has a 'name' attribute and a parent who has an 'id' attribute.
Views are rendered within the context specific to the `element`, as expected.
That is, running renderTo against the same element will destroy all registered
Contexts and their callbacks.
- element - the DOM element into which the generated HTML code will be injected
- viewName - the view template to be loaded and rendered
- locals - the locals to be passed to the view. If a `Model` object is
passed to this method, the Model's `observable` Object will be passed
to the view.
- cb - a callback of the form cb(err, html) where `html` is an string of
HTML produced by the view template
- [landmarkOptions] - the options passed to the created Landmark
(see https://github.com/meteor/meteor/wiki/Spark)
- [cb] - a callback of the form cb(err) where `err` is the Error object thrown
when the template was loaded (or null if no error occurred). This callback is
called exactly once, when the template is loaded.
It should also be noted that changing the contents of `el` or removing `el` from
the DOM may confuse Spark and cause errors. To remove `el` from the DOM or to
delete its child nodes, for example, it is best to call `Spark.finalize(el)` first.
*/
blade.Runtime.render = function(viewName, locals, cb) {
//Load and render the template
blade.Runtime.renderTo = function(el, viewName, locals, landmarkOptions, cb) {
//Reorganize args
if(typeof landmarkOptions == "function")
cb = landmarkOptions, landmarkOptions = {};
//Load blade template
blade.Runtime.loadTemplate(viewName, function(err, tmpl) {
if(err) return cb(err);
(function renderTemplate() {
function renderIt() {
tmpl(locals ? locals.observable || locals : {}, cb);
}
var context = new Context();
context.on_invalidate(renderTemplate); //recurse
context.run(renderIt);
})();
});
};
/* renderTo(element, viewName, locals [, cb])
Same as render(), except the output of the view is immediately injected
into the specified element. In addition, any event handlers created by
the view are bound. Finally, the element in focus is "preserved" if jQuery
is available and if the element either has an 'id' attribute or has a 'name'
attribute and a parent who has an 'id' attribute.
Also, from within the callback, `this` refers to the `element`.
*/
blade.Runtime.renderTo = function(el, viewName, locals, cb) {
blade.Runtime.render(viewName, locals, function(err, html, info) {
if(err) {if(cb) cb.call(el, err); return;}
try
//Call optional callback or throw error, if needed
if(cb)
cb(err);
if(err)
{
//Start preserving the element in focus, if necessary
var focus = document.activeElement,
$ = jQuery,
preserve = jQuery && //jQuery is required
//if <body> is in focus, ignore preservation
! $(focus).is("body") &&
//the element must have an 'id' or a 'name'
(focus.id || focus.name) &&
//Make sure that this node is a descendant of `el`
$(focus).parents().index(el) >= 0;
if(preserve)
{
//Setup the new element query now because the 'id' attribute will be deleted soon
var newElementIDQuery = focus.id ? "#" + focus.id : null,
newElementNameQuery = focus.name ? (
$(focus).parent().closest("[id]").length > 0 ?
"#" + $(focus).parent().closest("[id]").attr("id") + " " : ""
) + "[name=" + focus.name + "]" : null,
tmpValue = focus.value;
//Save the selection, if needed
if($(focus).is("input[type=text],input[type=password],textarea"))
var selectionStart = focus.selectionStart,
selectionEnd = focus.selectionEnd;
//Remove event handlers and attributes; in Chrome, 'blur' and possibly 'change'
//events are fired when an in-focus element is removed from the DOM
$(focus).off();
for(var i = focus.attributes.length - 1; i >= 0; i--)
focus.removeAttributeNode(focus.attributes.item(i) );
focus.onchange = focus.onblur = null;
//Now it's safe to call blur and remove this element from the DOM
focus.blur();
}
//Insert newly rendered content (jQuery is not required here)
if(el.html)
el.html(html);
else
el.innerHTML = html;
//Preserve element value, focus, cursor position, etc.
if(preserve)
{
//Find new element in newly rendered content
var newElement = $(newElementIDQuery);
if(newElement.length != 1)
newElement = $(newElementNameQuery);
//If found, do element preservation stuff...
if(newElement.length == 1)
{
var oldValue = $(newElement).val(); //Save the value that's currently in the model
newElement = newElement[0];
newElement.focus(); //Give the new element focus
if(document.activeElement === newElement)
{
//Set value to the temporary value and setup blur event handler to trigger `change`, if needed
$(newElement).val(tmpValue).blur(function(e) {
$(this).unbind(e);
if(this.value !== oldValue)
$(this).trigger('change');
if(!cb) throw err;
return;
}
//Destroy the LiveRanges in this element, if any
var LiveUpdate = blade.LiveUpdate;
LiveUpdate.finalize(el);
var dom = LiveUpdate.render(function() {
return LiveUpdate.labelBranch(viewName + "@" + el.id, function () {
return LiveUpdate.createLandmark(landmarkOptions, function (landmark) {
return LiveUpdate.isolate(function () {
var ret;
tmpl(locals ? locals.observable || locals : {}, function(err, html, info) {
//Remove event handler attributes
html = html.replace(/on[a-z]+\=\"return blade\.Runtime\.trigger\(this\,arguments\)\;\"/g, "");
//Return
ret = LiveUpdate.attachEvents(info.eventHandlers, html);
});
//Set focus again and set cursor & text selection
newElement.focus();
if($(newElement).is("input[type=text],input[type=password],textarea"))
{
newElement.selectionStart = selectionStart;
newElement.selectionEnd = selectionEnd;
}
}
}
}
//Register event handlers
for(var i in info.eventHandlers)
{
var events = info.eventHandlers[i].events.split(" "),
elem = document.getElementById(i);
for(var j = 0; j < events.length; j++)
if(elem === newElement && events[j] == "change")
(function(elem, handler) {
elem['on' + events[j]] = function() {
setTimeout(function() {
elem['on' + events[j]] = handler; //put everything back
}, 1);
//intercept event, if needed
if(this.value !== oldValue)
//call original handler
return handler.apply(this, arguments);
};
})(elem, info.eventHandlers[i].handler);
else
elem['on' + events[j]] = info.eventHandlers[i].handler;
//Delete comment before element
elem.parentNode.removeChild(elem.previousSibling);
}
if(cb) cb.call(el, null, html, info);
return ret;
});
});
});
});
if(window.jQuery)
//Use jQuery's empty() function to call `jQuery.cleanData` and prevent memory leaks
jQuery(el).empty();
else
{
while(el.firstChild)
el.removeChild(el.firstChild);
}
catch(e) {if(cb) cb.call(el, e);}
if(typeof dom == "string")
el.innerHTML = dom;
else
el.appendChild(dom);
});

@@ -394,6 +340,8 @@ };

if(window.jQuery)
jQuery.fn.render = function(viewName, locals, cb) {
blade.Runtime.renderTo(this, viewName, locals, cb);
jQuery.fn.render = function(viewName, locals, landmarkOptions, cb) {
this.each(function() {
blade.Runtime.renderTo(this, viewName, locals, landmarkOptions, cb);
});
};
})();

@@ -17,5 +17,2 @@ Blade - HTML Template Compiler

**UPDATE:** Meteor 0.4.0 support is coming soon! Thanks for your patience! If
anyone wants to help implement support for Meteor 0.4.0, please contact me.
<img src="http://www.empireonline.com/images/features/100greatestcharacters/photos/47.jpg"

@@ -114,2 +111,5 @@ alt="Blade" width="150" height="169"/>

**UPDATE:** Meteor 0.5 support is now available, and many more features are coming soon!
Thanks for your patience!
I'd say that Blade is **stable**. There are very few (if any)

@@ -134,3 +134,3 @@ [known issues](https://github.com/bminer/node-blade/issues), and I think that Blade

Minified runtime is about 5-6 KB, uncompressed.
Minified runtime is about 6-7 KB, uncompressed.

@@ -911,5 +911,32 @@ Syntax

### Isolates
Isolates are only useful when using a live page update engine. Creating an isolate
ensures that if data dependencies relating only to that isolate are updated, then only
the part of the template within isolate will be re-rendered. All other parts of the
Blade template will *not* be re-rendered.
Example (using Meteor):
```
- console.log("Rendering header...")
h1 This is a header
isolate
p The current user is: #{Session.get("user")}
```
In the example above, "Rendering header..." will be printed to the console when the
whole template is rendered, but if the reactive variable is updated using
`Session.set("user", ...)`, only the isolate will be re-rendered. In this case,
nothing will print to the console.
Note: As with Blade functions, any [blocks](#blocks) defined within an isolate will be
deleted and unaccessible outside the isolate block.
See [Reactivity isolation](http://docs.meteor.com/#isolate) on the Meteor documentation
for more details.
### Chunks
#### Chunks are deprecated as of Blade 3.0
#### Chunks are *deprecated* as of Blade 3.0. You should use [isolates](#isolates) instead.

@@ -1272,4 +1299,9 @@ Chunks are simply functions that return HTML. They behave a bit differently than

#### An Atmosphere smart package will be available soon!
#### An [Atmosphere smart package](https://atmosphere.meteor.com/package/blade) is also available.
To install Blade using Atmosphere, simply [install Meteorite]
(https://atmosphere.meteor.com/wtf/app), navigate to your Meteor project directory,
and type `mrt add blade`. Then, don't forget to run your project using `mrt` instead
of `meteor`.
**More documentation and examples for Meteor + Blade can be found [on this wiki page]

@@ -1276,0 +1308,0 @@ (https://github.com/bminer/node-blade/wiki/Using-Blade-with-Meteor).**

@@ -6,6 +6,6 @@ {

"homepage": "https://github.com/bminer/node-blade",
"version": "3.0.0-alpha1",
"version": "3.0.0alpha13",
"git": "https://github.com/bminer/node-blade.git",
"packages": {
}
}
}

@@ -35,3 +35,6 @@ console.log("Stupid tests...\n---------------");

var fs = require('fs');
var files = fs.readdirSync(__dirname + "/templates");
var filesTemp = fs.readdirSync(__dirname + "/templates"), files = [];
for(var i in filesTemp)
if(filesTemp[i].substr(-6) == ".blade")
files.push(filesTemp[i]);
var blade = require('../');

@@ -38,0 +41,0 @@ var numLines = 0;

@@ -67,5 +67,5 @@ var blade = require('../lib/blade'),

console.log("-----------------------------------------------");
console.log("Done - " + (done-failed) + " of " + total + " tests passed");
console.log("Done - " + (total-failed) + " of " + total + " tests passed");
console.timeEnd("Test");
}
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc