Skip to content
Browse files

Push to 3.0.0-alpha1

runtime: Moved 'blade.runtime' to 'blade.Runtime' (but kept blade.runtime for backward compatibility)
runtime: Moved 'blade.timeout' to 'blade.Runtime.options.loadTimeout'
runtime: Moved 'blade.mount' to 'blade.Runtime.options.mount'
runtime: Moved 'blade.cb' to 'blade._cb'
runtime: Moved 'blade.cachedViews' to 'blade._cachedViews'
Added isolate block (still undocumented)
Added constant block (still undocumented)
Added foreach block
Compiler now quotes attribute properties properly (fixes #94)
blade.LiveUpdate is a [Spark-compatible](https://github.com/meteor/meteor/tree/master/packages/spark) interface containing all Spark annotation types, as specified by the Spark API.
Blade chunks are now deprecated
Removed weird spacing from LICENSE; license is MIT license
Fixed a bug in runtime.capture, where blocks defined within a function were deleted completely, not inserted/resolved
Fixed a bug in the test suite when performing diff
Fixed bugs in Meteor runtime: needs to append .blade to filenames, as appropriate; needs to return boolean (might fix #95)
Package.json spaces converted to tabs
  • Loading branch information...
1 parent eec128f commit 013c621188d414abc5953c0c11fad7545206a131 @bminer committed Oct 24, 2012
View
31 LICENSE.txt
@@ -1,19 +1,20 @@
+MIT License - node-blade (https://github.com/bminer/node-blade/)
Copyright (c) 2011-2012 Blake Miner (http://www.blakeminer.com)
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in
- all copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- THE SOFTWARE.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
View
84 README.md
@@ -437,6 +437,47 @@ should avoid using these names in your view templates whenever possible:
- `__` (that's two underscores)
- Any of the compiler options (i.e. `debug`, `minify`, etc.)
+### Foreach block
+
+The exact syntax of a foreach block is the word "foreach", followed by the variable
+name of the JavaScript Array or [Cursor Object](http://docs.meteor.com/#observe),
+optionally followed by "as" and an item alias. Finally, it is possible to follow the
+foreach block by an "else" block, which is only rendered if there were no items in the
+collection.
+
+As a side note, a Cursor Object, as described above, is an Object with an `observe()`
+method, as described by [`cursor.observe(callbacks)`](http://docs.meteor.com/#observe)
+
+For example:
+
+```
+ul
+ foreach users as user
+ li #{user.firstName} #{user.lastName} (#{user.age})
+ else
+ li No users were found
+```
+
+Assuming that `users` is an Array, the above would produce the same as:
+
+```
+ul
+ - for(var i = 0; i < users.length; i++)
+ - var user = users[i];
+ li #{user.firstName} #{user.lastName} (#{user.age})
+ - if(users.length == 0)
+ li No users were found
+```
+
+The foreach block is preferred over the example above not only because of readability
+and brevity, but because it also provides Blade with the ability to better integrate
+with live page updating engines (like [Meteor](http://www.meteor.com/),
+[Spark](https://github.com/meteor/meteor/wiki/Spark),
+[Live UI](https://github.com/bminer/node-blade/wiki/Live-UI-Blade-Plugin), etc.).
+That is, if the live page update engine supports tracking reactive collections, the most
+efficient DOM operations may occur to update the view's results in-place, without
+re-rendering the entire Blade template.
+
### Doctypes
Don't forget a doctype! Actually, you can, whatever...
@@ -659,7 +700,7 @@ details](#fileIncludeDetails) for a more detailed explanation.
If you do not specifiy a file extension, `.blade` will be appended to your string
internally.
-You may also place an `include` inside of a `function`, `block`, or `chunk`.
+You may also place an `include` inside of a `function` or `block`.
Finally, you can specify which local variables should be passed to the included view
template by using the `exposing` keyword. By default, Blade will pass the parent's
@@ -866,6 +907,8 @@ render homepage.blade, you get:
### Chunks
+#### Chunks are deprecated as of Blade 3.0
+
Chunks are simply functions that return HTML. They behave a bit differently than
conventional Blade functions.
@@ -875,12 +918,13 @@ variable, as described above. Chunks, on the other hand, always return HTML, and
cannot be called using `call` statements. The only way to render a chunk is to call it
via your code (see example below).
-One reason you might define a chunk is to pass it to
+~~One reason you might define a chunk is to pass it to
[Meteor's](http://meteor.com/)
[`Meteor.ui.chunk` function](http://docs.meteor.com/#meteor_ui_chunk); however,
chunks can be used for other purposes, as well.
You can also use chunks to work with [`Meteor.ui.listChunk`]
-(http://docs.meteor.com/#meteor_ui_listchunk).
+(http://docs.meteor.com/#meteor_ui_listchunk).~~
+As of Meteor 0.4.0, the Meteor functions above no longer exist.
Example:
@@ -1078,9 +1122,9 @@ is a breeze.
Once you have the middleware setup, you can now serve your compiled Blade views
to the client. Simply include the /blade/blade.js file in your `<script>`
-tags, and then call `blade.runtime.loadTemplate`.
+tags, and then call `blade.Runtime.loadTemplate`.
-### blade.runtime.loadTemplate(filename, cb)
+### blade.Runtime.loadTemplate(filename, cb)
- `filename` - the filename of the view you wish to retrieve, relative to the
`sourcePath` you setup in the Blade middleware.
@@ -1096,14 +1140,22 @@ Yes, included files work, too. Like magic.
Example client-side JavaScript:
```javascript
-blade.runtime.loadTemplate("homepage.blade", function(err, tmpl) {
+blade.Runtime.loadTemplate("homepage.blade", function(err, tmpl) {
tmpl({'users': ['John', 'Joe']}, function(err, html) {
console.log(html); //YAY! We have rendered HTML
});
});
```
-As a side note, you can override the `blade.runtime.loadTemplate` function with
+Additionally, you can set `blade.Runtime.options` to control how the templates are
+loaded:
+
+- `blade.Runtime.options.mount` - the URL path where you can request compiled views
+ (defaults to "/views/")
+- `blade.Runtime.options.loadTimeout` - the maximum number of milliseconds to wait
+ before `loadTemplate` throws an error (defaults to 15 seconds).
+
+As a side note, you can override the `blade.Runtime.loadTemplate` function with
your own implementation.
Simple Example
@@ -1129,7 +1181,7 @@ html
... compiles to this JavaScript function ...
```javascript
-function tmpl(locals,cb,__){var __=__||[];__.r=__.r||blade.runtime,__.blocks=__.blocks||{},__.func=__.func||{},__.locals=locals||{};with(__.locals){__.push("<!DOCTYPE html>","<html",">","<head",">","<title",">",__.r.escape("Blade"),"</title>","</head>","<body",">","<div",' id="nav"',">","<ul",">");for(var i in nav)__.push("<li",">","<a"),__.r.attrs({href:{val:nav[i],escape:!0}},__,this),__.push(">",__.r.escape(i),"</a>","</li>");__.push("</ul>","</div>","<div",' id="content"',' class="center"',">","<h1",">",__.r.escape("Blade is cool"),"</h1>","</div>","</body>","</html>"),__.inc||__.r.done(__)}cb(null,__.join(""),__)}
+function tmpl(locals,cb,__){__=__||[],__.r=__.r||blade.Runtime,__.func||(__.func={},__.blocks={},__.chunk={}),__.locals=locals||{};with(__.locals){__.push("<!DOCTYPE html>","<html",">","<head",">","<title",">","Blade","</title>","</head>","<body",">","<div",' id="nav"',">","<ul",">");for(var i in nav)__.push("<li",">","<a"),__.r.attrs({href:{v:nav[i],e:1}},__),__.push(">",__.r.escape(i),"</a>","</li>");__.push("</ul>","</div>","<div",' id="content"',' class="center"',">","<h1",">","Blade is cool","</h1>","</div>","</body>","</html>")}__.inc||__.r.done(__),cb(null,__.join(""),__)}
```
... now you call the function like this...
@@ -1175,7 +1227,11 @@ Plugins
**Live UI**
-Blade provides a Live UI plugin that allows Blade to support live binding. Live binding
+#### As of Blade 3.0, the Live UI plugin has been moved to another repository.
+
+#### More information about the Live UI plugin is coming soon...
+
+~~Blade provides a Live UI plugin that allows Blade to support live binding. Live binding
provides automatic two-way synchronization between your models and views on a given web
page. That is, when data in your Model is updated, the rendered Blade views on the
client's browser are automatically updated with the new content, and similarly, when a
@@ -1187,12 +1243,12 @@ can be found on the [Live UI Plugin wiki page]
(https://github.com/bminer/node-blade/wiki/Live-UI-Blade-Plugin).**
Eventually, the Live UI plugin might live in a separate repository and work for any
-templating language.
+templating language. More information coming soon...
**definePropertyIE8**
This plugin is a prerequisite for the Live UI plugin if you plan on using Live UI in
-Internet Explorer 8.
+Internet Explorer 8.~~
Meteor Support
--------------
@@ -1209,6 +1265,8 @@ You need to replace the above command with the correct paths, as appropriate.
Then, execute `meteor add blade` in your Meteor project directory.
+#### An Atmosphere smart package will be available soon!
+
**More documentation and examples for Meteor + Blade can be found [on this wiki page]
(https://github.com/bminer/node-blade/wiki/Using-Blade-with-Meteor).**
@@ -1260,15 +1318,15 @@ of an issue.
Event handlers in Blade work by injecting the event handler function as an HTML comment
directly before the bound element. Then, the appropriate event attribute (i.e.
-onclick, onchange, etc.) on the element is set to call `blade.runtime.trigger`. The
+onclick, onchange, etc.) on the element is set to call `blade.Runtime.trigger`. The
`trigger` function basically grabs the HTML comment, passes the contents through eval(),
and binds the event handler directly to the element. This means that the event handlers
work on templates rendered on the browser or on the server. Everything gets wired up the
first time that the event occurs on the browser.
The Blade runtime also keeps track of any event handlers bound to a specific element
by assigning each element an 'id' attribute, if necessary. When the view has
-finished rendering, the Blade runtime will pass a bunch of information (chunks, blocks,
+finished rendering, the Blade runtime will pass a bunch of information (blocks,
functions, or event handlers that were defined, etc.) to the 3rd (undocumented) argument
of the render callback function. If you are rendering Blade templates on the browser,
you can access the list of event handlers and bind the defined event handler directly
View
4 lib/blade.js
@@ -146,8 +146,8 @@ function middleware(sourcePath, options) {
compileFile(fullPath, options.compileOptions, function(err, tmpl) {
if(err) return next(err);
res.type('application/javascript');
- res.send("blade.cachedViews[" + JSON.stringify(filename) + "]=" + tmpl.toString() +
- ";if(blade.cb[" + JSON.stringify(filename) + "])blade.cb[" +
+ res.send("blade._cachedViews[" + JSON.stringify(filename) + "]=" + tmpl.toString() +
+ ";if(blade._cb[" + JSON.stringify(filename) + "])blade._cb[" +
JSON.stringify(filename) + "](" + JSON.stringify(tmpl.reldir) + "," +
JSON.stringify(tmpl.dependencies) + "," + tmpl.unknownDependencies + ");");
});
View
45 lib/compiler.js
@@ -89,7 +89,7 @@ Compiler.prototype.compile = function(cb) {
//Convert to JS function
this.buf = "";
this._pushOff(ns + ' = ' + ns + ' || [];' + //Define ns as an array
- ns + '.r = ' + ns + '.r || blade.runtime;' + //Define ns.r to point to the runtime
+ ns + '.r = ' + ns + '.r || blade.Runtime;' + //Define ns.r to point to the runtime
'if(!' + ns + '.func) ' + ns + '.func = {},' + //Define ns.func to hold all functions
ns + '.blocks = {},' + //Define ns.blocks to hold all blocks
ns + '.chunk = {};' + //Define ns.chunk to hold all functions chunks
@@ -224,7 +224,7 @@ Compiler.prototype._compileNode = function(node) {
var ns = this.options.templateNamespace;
if(this.options.minify !== true && node.line != null && (this.lastNode == null ||
this.lastNode.type != "code" || this.lastNode.children.length == 0 ||
- node.type != "code") )
+ node.type != "code") && node.type != "foreach_else")
{
this._pushOff(ns + ".line=" + node.line + "," + ns + ".col=" + node.col + ";");
if(node.type != "code")
@@ -283,7 +283,7 @@ Compiler.prototype._compileNode = function(node) {
for(var j = 0; j < events.length; j++)
attrs["on" + events[j].toLowerCase() ] = {
"escape": false,
- "text": "return blade.runtime.trigger(this,arguments);"
+ "text": "return blade.Runtime.trigger(this,arguments);"
};
}
//start tag
@@ -306,7 +306,7 @@ Compiler.prototype._compileNode = function(node) {
varAttrs += "," + i + ":{a:" + JSON.stringify(attrs[i].append) +
",v:" + attrs[i].code + (attrs[i].escape ? ", e:1" : "") + "}";
else
- varAttrs += "," + i + ":{v:" + attrs[i].code +
+ varAttrs += "," + JSON.stringify(i) + ":{v:" + attrs[i].code +
(attrs[i].escape ? ", e:1" : "") + "}";
}
if(varAttrs.length > 0)
@@ -537,12 +537,47 @@ Compiler.prototype._compileNode = function(node) {
"}," + ns + node.arguments + (node.output ? ") );" : ");") );
break;
case 'chunk':
+ console.warn("Blade chunks are now deprecated. Please fix " +
+ (this.options.filename ? this.options.filename + ":" : "line ") +
+ node.line + ":" + node.col);
var paramStr = node.parameters == null ? "" : node.parameters.join(",");
this._pushOff(ns + ".r.chunk(" + JSON.stringify(node.name) + ",function(" +
paramStr + ") {");
for(var i = 0; i < node.children.length; i++)
this._compileNode(node.children[i]);
- this._pushOff("return this;}, " + ns + ");");
+ this._pushOff("}," + ns + ");");
+ break;
+ case 'isolate':
+ this._pushOff(ns + ".r.isolate(function() {");
+ for(var i = 0; i < node.children.length; i++)
+ this._compileNode(node.children[i]);
+ this._pushOff("}," + ns + ");");
+ break;
+ case 'constant':
+ this._pushOff(ns + ".r.constant(function() {");
+ for(var i = 0; i < node.children.length; i++)
+ this._compileNode(node.children[i]);
+ this._pushOff("}," + ns + ");");
+ break;
+ case 'foreach':
+ this._pushOff(ns + ".r.foreach(" + ns + "," + node.cursor + ",function(" +
+ (node.itemAlias ? node.itemAlias : "") + ") {");
+ for(var i = 0; i < node.children.length; i++)
+ this._compileNode(node.children[i]);
+ this._pushOff("});");
+ break;
+ case 'foreach_else':
+ if(!this.lastNode || this.lastNode.type != "foreach")
+ {
+ var e = new Error("No matching foreach list block. You cannot put a foreach else block here!");
+ e.line = node.line, e.column = node.col;
+ throw e;
+ }
+ //Remove trailing ");" and add elseFunc argument to the Runtime.foreach(...) call.
+ this.buf = this.buf.substr(0, this.buf.length - 2) + ",function() {";
+ for(var i = 0; i < node.children.length; i++)
+ this._compileNode(node.children[i]);
+ this._pushOff("});");
break;
case 'blank_line':
//Ignore these lines
View
39 lib/parser/blade-grammer.pegjs
@@ -123,6 +123,14 @@ parent_node_types =
/
chunk
/
+ isolate
+ /
+ constant
+ /
+ foreach
+ /
+ foreach_else
+ /
tag
/
comment
@@ -271,11 +279,7 @@ block_modifier "block modifier (i.e. append, prepend, or replace)" =
{return {'type': keyword, 'name': name, 'parameters': params == "" ? null : params};}
block_modifier_keyword =
- "append"
- /
- "prepend"
- /
- "replace"
+ "append" / "prepend" / "replace"
/************** Functions **************/
function_definition "function definition" =
@@ -344,7 +348,7 @@ parameters "parameter list" =
")"
{return [first_param].concat(next_params);}
-/************** Chunks and Includes **************/
+/************** Chunks, isolates, constant areas, and foreach **************/
chunk "chunk" =
"chunk" name:(whitespace+ name:identifier {return name;})?
whitespace* params:parameters?
@@ -353,6 +357,29 @@ chunk "chunk" =
'parameters': params == "" ? null : params};
}
+isolate "isolate" =
+ "isolate"
+ {return {'type': 'isolate'};}
+
+constant "constant block" =
+ "constant"
+ {return {'type': 'constant'};}
+
+foreach "foreach list block" =
+ "foreach" whitespace+ cursor:identifier itemAlias:(
+ whitespace+ "as" whitespace+ itemAlias:identifier {return itemAlias;})?
+ {
+ var ret = {'type': 'foreach', 'cursor': cursor};
+ if(itemAlias != "")
+ ret.itemAlias = itemAlias;
+ return ret;
+ }
+
+foreach_else "foreach else block" =
+ "else"
+ {return {'type': 'foreach_else'};}
+
+/************** File includes **************/
include "include" =
"include" whitespace+ filename:quoted_string exposing:exposed_locals?
{
View
232 lib/parser/index.js
@@ -64,6 +64,10 @@ module.exports = (function(){
"matched_parentheses": parse_matched_parentheses,
"parameters": parse_parameters,
"chunk": parse_chunk,
+ "isolate": parse_isolate,
+ "constant": parse_constant,
+ "foreach": parse_foreach,
+ "foreach_else": parse_foreach_else,
"include": parse_include,
"exposed_locals": parse_exposed_locals,
"comment": parse_comment,
@@ -608,11 +612,23 @@ module.exports = (function(){
if (result0 === null) {
result0 = parse_chunk();
if (result0 === null) {
- result0 = parse_tag();
+ result0 = parse_isolate();
if (result0 === null) {
- result0 = parse_comment();
+ result0 = parse_constant();
if (result0 === null) {
- result0 = parse_code();
+ result0 = parse_foreach();
+ if (result0 === null) {
+ result0 = parse_foreach_else();
+ if (result0 === null) {
+ result0 = parse_tag();
+ if (result0 === null) {
+ result0 = parse_comment();
+ if (result0 === null) {
+ result0 = parse_code();
+ }
+ }
+ }
+ }
}
}
}
@@ -3011,6 +3027,216 @@ module.exports = (function(){
return result0;
}
+ function parse_isolate() {
+ var result0;
+ var pos0;
+
+ reportFailures++;
+ pos0 = clone(pos);
+ if (input.substr(pos.offset, 7) === "isolate") {
+ result0 = "isolate";
+ advance(pos, 7);
+ } else {
+ result0 = null;
+ if (reportFailures === 0) {
+ matchFailed("\"isolate\"");
+ }
+ }
+ if (result0 !== null) {
+ result0 = (function(offset, line, column) {return {'type': 'isolate'};})(pos0.offset, pos0.line, pos0.column);
+ }
+ if (result0 === null) {
+ pos = clone(pos0);
+ }
+ reportFailures--;
+ if (reportFailures === 0 && result0 === null) {
+ matchFailed("isolate");
+ }
+ return result0;
+ }
+
+ function parse_constant() {
+ var result0;
+ var pos0;
+
+ reportFailures++;
+ pos0 = clone(pos);
+ if (input.substr(pos.offset, 8) === "constant") {
+ result0 = "constant";
+ advance(pos, 8);
+ } else {
+ result0 = null;
+ if (reportFailures === 0) {
+ matchFailed("\"constant\"");
+ }
+ }
+ if (result0 !== null) {
+ result0 = (function(offset, line, column) {return {'type': 'constant'};})(pos0.offset, pos0.line, pos0.column);
+ }
+ if (result0 === null) {
+ pos = clone(pos0);
+ }
+ reportFailures--;
+ if (reportFailures === 0 && result0 === null) {
+ matchFailed("constant block");
+ }
+ return result0;
+ }
+
+ function parse_foreach() {
+ var result0, result1, result2, result3, result4, result5, result6;
+ var pos0, pos1, pos2, pos3;
+
+ reportFailures++;
+ pos0 = clone(pos);
+ pos1 = clone(pos);
+ if (input.substr(pos.offset, 7) === "foreach") {
+ result0 = "foreach";
+ advance(pos, 7);
+ } else {
+ result0 = null;
+ if (reportFailures === 0) {
+ matchFailed("\"foreach\"");
+ }
+ }
+ if (result0 !== null) {
+ result2 = parse_whitespace();
+ if (result2 !== null) {
+ result1 = [];
+ while (result2 !== null) {
+ result1.push(result2);
+ result2 = parse_whitespace();
+ }
+ } else {
+ result1 = null;
+ }
+ if (result1 !== null) {
+ result2 = parse_identifier();
+ if (result2 !== null) {
+ pos2 = clone(pos);
+ pos3 = clone(pos);
+ result4 = parse_whitespace();
+ if (result4 !== null) {
+ result3 = [];
+ while (result4 !== null) {
+ result3.push(result4);
+ result4 = parse_whitespace();
+ }
+ } else {
+ result3 = null;
+ }
+ if (result3 !== null) {
+ if (input.substr(pos.offset, 2) === "as") {
+ result4 = "as";
+ advance(pos, 2);
+ } else {
+ result4 = null;
+ if (reportFailures === 0) {
+ matchFailed("\"as\"");
+ }
+ }
+ if (result4 !== null) {
+ result6 = parse_whitespace();
+ if (result6 !== null) {
+ result5 = [];
+ while (result6 !== null) {
+ result5.push(result6);
+ result6 = parse_whitespace();
+ }
+ } else {
+ result5 = null;
+ }
+ if (result5 !== null) {
+ result6 = parse_identifier();
+ if (result6 !== null) {
+ result3 = [result3, result4, result5, result6];
+ } else {
+ result3 = null;
+ pos = clone(pos3);
+ }
+ } else {
+ result3 = null;
+ pos = clone(pos3);
+ }
+ } else {
+ result3 = null;
+ pos = clone(pos3);
+ }
+ } else {
+ result3 = null;
+ pos = clone(pos3);
+ }
+ if (result3 !== null) {
+ result3 = (function(offset, line, column, itemAlias) {return itemAlias;})(pos2.offset, pos2.line, pos2.column, result3[3]);
+ }
+ if (result3 === null) {
+ pos = clone(pos2);
+ }
+ result3 = result3 !== null ? result3 : "";
+ if (result3 !== null) {
+ result0 = [result0, result1, result2, result3];
+ } else {
+ result0 = null;
+ pos = clone(pos1);
+ }
+ } else {
+ result0 = null;
+ pos = clone(pos1);
+ }
+ } else {
+ result0 = null;
+ pos = clone(pos1);
+ }
+ } else {
+ result0 = null;
+ pos = clone(pos1);
+ }
+ if (result0 !== null) {
+ result0 = (function(offset, line, column, cursor, itemAlias) {
+ var ret = {'type': 'foreach', 'cursor': cursor};
+ if(itemAlias != "")
+ ret.itemAlias = itemAlias;
+ return ret;
+ })(pos0.offset, pos0.line, pos0.column, result0[2], result0[3]);
+ }
+ if (result0 === null) {
+ pos = clone(pos0);
+ }
+ reportFailures--;
+ if (reportFailures === 0 && result0 === null) {
+ matchFailed("foreach list block");
+ }
+ return result0;
+ }
+
+ function parse_foreach_else() {
+ var result0;
+ var pos0;
+
+ reportFailures++;
+ pos0 = clone(pos);
+ if (input.substr(pos.offset, 4) === "else") {
+ result0 = "else";
+ advance(pos, 4);
+ } else {
+ result0 = null;
+ if (reportFailures === 0) {
+ matchFailed("\"else\"");
+ }
+ }
+ if (result0 !== null) {
+ result0 = (function(offset, line, column) {return {'type': 'foreach_else'};})(pos0.offset, pos0.line, pos0.column);
+ }
+ if (result0 === null) {
+ pos = clone(pos0);
+ }
+ reportFailures--;
+ if (reportFailures === 0 && result0 === null) {
+ matchFailed("foreach else block");
+ }
+ return result0;
+ }
+
function parse_include() {
var result0, result1, result2, result3;
var pos0, pos1;
View
124 lib/runtime.js
@@ -9,20 +9,58 @@
(function(triggerFunction) {
var runtime = typeof exports == "object" ? exports : {},
cachedViews = {},
- eventHandlers = {};
+ eventHandlers = {},
+ /* Add blade.LiveUpdate no-op functions */
+ htmlNoOp = function(arg1, html) {return html;},
+ funcNoOp = function(arg1, func) {return func();},
+ liveUpdate = {
+ "attachEvents": htmlNoOp,
+ "setDataContext": htmlNoOp,
+ "isolate": function(func) {return func();},
+ "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.
+ }
+ if(!itemList.length) //If itemList.length is null, zero, etc.
+ return elseFunc();
+ //Otherwise, call itemFunc for each item in itemList array
+ var html = "";
+ for(var i = 0; i < itemList.length; i++)
+ html += itemFunc(itemList[i]);
+ return html;
+ },
+ "labelBranch": funcNoOp,
+ "createLandmark": funcNoOp
+ };
+ /* blade.Runtime.mount is the URL where the Blade middleware is mounted (or where
+ compiled templates can be downloaded)
+ */
+ runtime.options = {
+ 'mount': '/views/', 'loadTimeout': 15000
+ };
/* Expose Blade runtime via window.blade, if we are running on the browser
- blade.runtime is the Blade runtime
- blade.cachedViews is an Object of cached views, indexed by filename
- blade.cb contains a callback function to be called when a view is
+ blade.Runtime is the Blade runtime
+ blade.runtime was kept for backward compatibility (but is now deprecated)
+ blade._cachedViews is an Object of cached views, indexed by filename
+ blade._cb contains a callback function to be called when a view is
loaded, indexed by filename. The callback function also has a 'cb'
property that contains an array of callbacks to be called once all
of the view's dependencies have been loaded.
- blade.mount is the URL where the Blade middleware is mounted (or where
- compiled templates can be downloaded)
*/
if(runtime.client = typeof window != "undefined")
- window.blade = {'runtime': runtime, 'cachedViews': cachedViews,
- 'cb': {}, 'mount': '/views/', 'timeout': 15000};
+ window.blade = {'Runtime': runtime, 'LiveUpdate': liveUpdate,
+ '_cachedViews': cachedViews, '_cb': {}, 'runtime': runtime};
/* Convert special characters to HTML entities.
This function performs replacements similar to PHP's ubiquitous
@@ -159,15 +197,15 @@
}
var blade = window.blade;
//If the file is already loading...
- if(blade.cb[filename])
- blade.cb[filename].cb.push(cb); //push to the array of callbacks
+ if(blade._cb[filename])
+ blade._cb[filename].cb.push(cb); //push to the array of callbacks
else
{
//Otherwise, start loading it by creating a script tag
var st = document.createElement('script');
st.type = 'text/javascript'; //use text/javascript because of IE
st.async = true;
- st.src = blade.mount + filename;
+ st.src = runtime.options.mount + filename;
//Add compile options to the query string of the URL, if given
//(this functionality is disabled for now since the middleware ignores it anyway)
/*if(compileOptions)
@@ -194,15 +232,15 @@
}
//Set a timer to return an Error after a timeout expires.
var timer = setTimeout(function() {
- var cb = blade.cb[filename].cb; //array of callbacks
- delete blade.cb[filename];
+ 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.") );
- }, blade.timeout);
- var tmp = blade.cb[filename] = function(dependenciesReldir, dependencies, unknownDependencies) {
+ }, runtime.options.loadTimeout);
+ var tmp = blade._cb[filename] = function(dependenciesReldir, dependencies, unknownDependencies) {
clearTimeout(timer);
- delete blade.cb[filename];
+ delete blade._cb[filename];
st.parentNode.removeChild(st);
//Load all dependencies, too
if(dependencies.length > 0)
@@ -325,8 +363,22 @@
runtime.capture = function(buf, start) {
//Delete all blocks defined within the function
for(var i in buf.blocks)
- if(buf.blocks[i].pos >= start)
+ {
+ var x = buf.blocks[i];
+ if(x.pos >= start && (!buf.block || x.parent == buf.block) )
+ {
+ //Insert the buffer contents where it belongs
+ if(x.parent == null)
+ buf[x.pos] = x.buf.join("");
+ else
+ {
+ x.parent.buf[x.pos] = x.buf.join("");
+ x.parent.numChildren--;
+ }
+ //Delete the block
delete buf.blocks[i];
+ }
+ }
/* Now remove the content generated by the function from the buffer
and return it as a string */
return buf.splice(start, buf.length - start).join("");
@@ -336,10 +388,48 @@
runtime.chunk = function(name, func, info) {
info.chunk[name] = function() {
//This function needs to accept params and return HTML
+ /* Note: This following line is the same as:
+ var len = info.length;
+ func.apply(this, arguments);
+ return runtime.capture(info, len);
+ */
return runtime.capture(info, info.length, func.apply(this, arguments) );
};
};
+ /* Define an isolate block */
+ runtime.isolate = function(func, buf) {
+ buf.push(liveUpdate.isolate(function() {
+ /* 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() );
+ }) );
+ };
+
+ /* 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() );
+ }) );
+ };
+
+ /* 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() );
+ }) );
+ };
+
/* Copies error reporting information from a block's buffer to the main
buffer */
function blockError(buf, blockBuf, copyFilename) {
View
16 meteor/runtime-meteor.js
@@ -1,8 +1,14 @@
-blade.runtime.loadTemplate = function(baseDir, filename, compileOptions, cb) {
+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);
+ filename = blade.Runtime.resolve(filename);
if(blade.cachedViews[filename])
- return cb(null, blade.cachedViews[filename]);
- else
- return cb(new Error("Template '" + filename + "' could not be loaded.") );
+ {
+ cb(null, blade.cachedViews[filename]);
+ return true;
+ }
+ cb(new Error("Template '" + filename + "' could not be loaded.") );
+ return false;
};
View
89 package.json
@@ -1,48 +1,49 @@
{
- "author": "Blake Miner <miner.blake@gmail.com> (http://www.blakeminer.com/)",
- "name": "blade",
- "description": "Blade - HTML Template Compiler, inspired by Jade & Haml",
- "keywords": [
- "html",
- "compile",
- "compiler",
- "render",
- "view",
- "template",
- "engine",
- "jade",
- "haml",
- "live binding",
- "meteor"
- ],
- "version": "2.6.4",
- "homepage": "https://github.com/bminer/node-blade",
- "repository": {
+ "name": "blade",
+ "author": "Blake Miner <miner.blake@gmail.com> (http://www.blakeminer.com/)",
+ "description": "Blade - HTML Template Compiler, inspired by Jade & Haml",
+ "keywords": [
+ "html",
+ "compile",
+ "compiler",
+ "render",
+ "view",
+ "template",
+ "engine",
+ "jade",
+ "haml",
+ "live binding",
+ "meteor"
+ ],
+ "version": "3.0.0-alpha1",
+ "homepage": "https://github.com/bminer/node-blade",
+ "repository": {
"type": "git",
- "url": "https://github.com/bminer/node-blade.git"
- },
- "main": "lib/blade.js",
- "bin": { "blade": "./bin/blade" },
- "dependencies": {
- "commander": ">=0.6"
- },
- "devDependencies": {
- "pegjs": ">=0.7",
- "uglify-js": ">=1.2"
- },
- "optionalDependencies": {
- "uglify-js": ">=1.2"
- },
- "engines": {
- "node": ">=0.6"
- },
- "scripts": {
- "pretest": "./lib/parser/build.sh",
- "test": "node ./test/test.js",
- "prepublish": "./lib/parser/build.sh",
+ "url": "https://github.com/bminer/node-blade.git"
+ },
+ "license": "MIT",
+ "main": "lib/blade.js",
+ "bin": { "blade": "./bin/blade" },
+ "dependencies": {
+ "commander": ">=0.6"
+ },
+ "devDependencies": {
+ "pegjs": ">=0.7",
+ "uglify-js": ">=1.2"
+ },
+ "optionalDependencies": {
+ "uglify-js": ">=1.2"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "scripts": {
+ "pretest": "./lib/parser/build.sh",
+ "test": "node ./test/test.js",
+ "prepublish": "./lib/parser/build.sh",
"postinstall": "node ./postinstall.js"
- },
- "contributors": [
+ },
+ "contributors": [
"Michel Löhr (https://github.com/mlohr)"
- ]
-}
+ ]
+}
View
12 plugins/liveui.js
@@ -9,7 +9,7 @@
Adds the following to the `blade` global variable:
- Model
- Context
- Adds the following to `blade.runtime`:
+ Adds the following to `blade.Runtime`:
- render(viewName, locals, cb)
- renderTo(element, viewName, locals [, cb])
Adds the following functions to jQuery.fn:
@@ -262,9 +262,9 @@
- cb - a callback of the form cb(err, html) where `html` is an string of
HTML produced by the view template
*/
- blade.runtime.render = function(viewName, locals, cb) {
+ blade.Runtime.render = function(viewName, locals, cb) {
//Load and render the template
- blade.runtime.loadTemplate(viewName, function(err, tmpl) {
+ blade.Runtime.loadTemplate(viewName, function(err, tmpl) {
if(err) return cb(err);
(function renderTemplate() {
function renderIt() {
@@ -284,8 +284,8 @@
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) {
+ 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
{
@@ -388,7 +388,7 @@
if(window.jQuery)
jQuery.fn.render = function(viewName, locals, cb) {
- blade.runtime.renderTo(this, viewName, locals, cb);
+ blade.Runtime.renderTo(this, viewName, locals, cb);
};
})();
View
6 test/output/event_handlers.html
@@ -1,9 +1,9 @@
<!--i["change"]=function (e){//test1
-}--><input id="foo" class="bar foobar" onchange="return blade.runtime.trigger(this,arguments);"/><!--i["change click"]=function (e){alert("Test2");
+}--><input id="foo" class="bar foobar" onchange="return blade.Runtime.trigger(this,arguments);"/><!--i["change click"]=function (e){alert("Test2");
//test2
alert("Still test 2");
/* More of test 2 */
-}--><input class="foo bar" onchange="return blade.runtime.trigger(this,arguments);" onclick="return blade.runtime.trigger(this,arguments);" id="blade_0"/><!--i["click"]=function (e){//test3 - click
+}--><input class="foo bar" onchange="return blade.Runtime.trigger(this,arguments);" onclick="return blade.Runtime.trigger(this,arguments);" id="blade_0"/><!--i["click"]=function (e){//test3 - click
};i["change"]=function (e){//test4 - change
};i["keyup"]=function (e){//test5 - keyup
-}--><input onclick="return blade.runtime.trigger(this,arguments);" onchange="return blade.runtime.trigger(this,arguments);" onkeyup="return blade.runtime.trigger(this,arguments);" id="blade_1"/>
+}--><input onclick="return blade.Runtime.trigger(this,arguments);" onchange="return blade.Runtime.trigger(this,arguments);" onkeyup="return blade.Runtime.trigger(this,arguments);" id="blade_1"/>
View
1 test/output/foreach.html
@@ -0,0 +1 @@
+<p>Welcome, Joe</p><p>Welcome, Bob</p><p>Welcome, Billy</p><p>Welcome again, Joe</p><p>But `this` == `user`, as well? true</p><p>Welcome again, Bob</p><p>But `this` == `user`, as well? true</p><p>Welcome again, Billy</p><p>But `this` == `user`, as well? true</p><p>Empty is definitely empty</p>
View
1 test/output/isolate_constant.html
@@ -0,0 +1 @@
+<h1>Begin</h1><h1>Testing isolate</h1><h3>Testing 1, 2, 3</h3><h1>Testing constant</h1><h1>Testing nesting</h1><p>Foobar!</p><h3>Idiot!</h3><p>The end!</p>
View
13 test/templates/foreach.blade
@@ -0,0 +1,13 @@
+- var users = ["Joe", "Bob", "Billy"]
+foreach users
+ p Welcome, #{this}
+foreach users as user
+ p Welcome again, #{user}
+ p But `this` == `user`, as well? #{this == user}
+else
+ p Users should not be empty?!?!
+- var empty = []
+foreach empty as foo
+ p This should not happen
+else
+ p Empty is definitely empty
View
3 test/templates/functions2.blade
@@ -8,5 +8,6 @@ function test(foo)
function dialog(msg)
.dialog
- =msg
+ block temp
+ =msg
call dialog("Blade is awesome")#foobar.foo.bar
View
13 test/templates/isolate_constant.blade
@@ -0,0 +1,13 @@
+h1 Begin
+isolate
+ h1 Testing isolate
+ h3 Testing 1, 2, 3
+constant
+ h1 Testing constant
+isolate
+ constant
+ isolate
+ h1 Testing nesting
+ p Foobar!
+ h3 Idiot!
+p The end!
View
2 test/test.js
@@ -32,7 +32,7 @@ for(var i in files)
compare.stdout.on('data', function(chunk) {
diff += chunk;
});
- compare.on('exit', function(code) {
+ compare.on('close', function(code) {
if(diff != "")
{
failed++;

0 comments on commit 013c621

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