Fixes #36 Deprecated invokeBody in favor of renderBody

This commit is contained in:
Patrick Steele-Idem 2015-02-23 16:35:30 -07:00
parent 6a2f603ea9
commit 79aa92254f
13 changed files with 186 additions and 33 deletions

View File

@ -1383,13 +1383,13 @@ exports.render = function(input, out) {
}
```
If, and only if, a tag has nested content, then a special `invokeBody` method will be added to the `input` object. If a renderer wants to render the nested body content then it must call the `invokeBody` method. For example:
If, and only if, a tag has nested content, then a special `renderBody` method will be added to the `input` object. If a renderer wants to render the nested body content then it must call the `renderBody` method. For example:
```javascript
exports.render = function(input, out) {
out.write('BEFORE BODY');
if (input.invokeBody) {
input.invokeBody();
if (input.renderBody) {
input.renderBody(out);
}
out.write('AFTER BODY');
}
@ -1531,12 +1531,12 @@ Marko supports this by leveraging JavaScript closures in the compiled output. A
"tags": {
"ui-tabs": {
"renderer": "./tabs-tag",
"var": "tabs"
"body-function": "getTabs(__tabsHelper)"
},
"ui-tab": {
"renderer": "./tab-tag",
"import-var": {
"tabs": "tabs"
"tabs": "__tabsHelper"
},
"attributes": {
"title": "string"
@ -1557,15 +1557,21 @@ var templatePath = require.resolve('./template.marko');
var template = require('marko').load(templatePath);
exports.render = function(input, out) {
var nestedTabs = [];
var nestedTabs;
if (input.getTabs) {
nestedTabs = [];
// Invoke the body function to discover nested <ui-tab> tags
input.invokeBody({ // Invoke the body with the scoped "tabs" variable
input.getTabs({ // Invoke the body with the scoped "tabs" variable
addTab: function(tab) {
tab.id = tab.id || ("tab" + tabs.length);
nestedTabs.push(tab);
}
});
} else {
nestedTabs = input.tabs || [];
}
// Now render the markup for the tabs:
template.render({
@ -1596,7 +1602,7 @@ _components/tabs/template.marko:_
</ul>
<div class="tab-content">
<div id="${tab.id}" class="tab-pane" for="tab in data.tabs">
<invoke function="tab.invokeBody()"/>
<invoke function="tab.renderBody(out)"/>
</div>
</div>
</div>

View File

@ -134,7 +134,7 @@ CodeWriter.prototype = {
thisObj = arguments[2];
}
this.incIndent(delta);
func.call(thisObj);
func.call(thisObj, this);
this.decIndent(delta);
} else if (typeof arguments[0] === 'string') {
this.code(this._indent + arguments[0]);
@ -264,8 +264,8 @@ TemplateBuilder.prototype = {
var newWriter = new CodeWriter(this.concatWrites, oldWriter.indentStr());
try {
this.writer = newWriter;
func.call(thisObj);
return newWriter.getOutput();
var value = func.call(thisObj);
return value == null ? newWriter.getOutput() : value;
} finally {
this.writer = oldWriter;
}

View File

@ -88,6 +88,7 @@ Taglib.Tag = makeClass({
this.nestedVariables = {};
this.importedVariables = {};
this.patternAttributes = [];
this.bodyFunction = null;
},
inheritFrom: function (superTag) {
var subTag = this;
@ -182,6 +183,12 @@ Taglib.Tag = makeClass({
var key = transformer.path;
transformer.taglibId = this.taglibId;
this.transformers[key] = transformer;
},
setBodyFunction: function(name, params) {
this.bodyFunction = {
name: name,
params: params
};
}
});

View File

@ -7,7 +7,6 @@ try {
}
var ok = require('assert').ok;
var nodePath = require('path');
var Taglib = require('./Taglib');
@ -18,6 +17,9 @@ var tagDefFromCode = require('./tag-def-from-code');
var resolve = require('../util/resolve'); // NOTE: different implementation for browser
var propertyHandlers = require('property-handlers');
var safeVarName = /^[A-Za-z_$][A-Za-z0-9_]*$/;
var bodyFunctionRegExp = /^([A-Za-z_$][A-Za-z0-9_]*)(?:\(([^)]*)\))?$/;
function exists(path) {
try {
require.resolve(path);
@ -192,6 +194,29 @@ function buildTag(tagObject, path, taglib, dirname) {
name: value
});
},
bodyFunction: function(value) {
var parts = bodyFunctionRegExp.exec(value);
if (!parts) {
throw new Error('Invalid value of "' + value + '" for "body-function". Expected value to be of the following form: <function-name>([param1, param2, ...])');
}
var functionName = parts[1];
var params = parts[2];
if (params) {
params = params.trim().split(/\s*,\s*/);
for (var i=0; i<params.length; i++) {
if (params[i].length === 0) {
throw new Error('Invalid parameters for body-function with value of "' + value + '"');
} else if (!safeVarName.test(params[i])) {
throw new Error('Invalid parameter name of "' + params[i] + '" for body-function with value of "' + value + '"');
}
}
} else {
params = [];
}
tag.setBodyFunction(functionName, params);
},
vars: function(value) {
if (value) {
value.forEach(function(v, i) {

View File

@ -25,8 +25,8 @@
"char-props": "~0.1.5",
"events": "^1.0.2",
"htmlparser2": "^3.7.2",
"marko-async": "^1.2.12",
"marko-layout": "^1.1.0",
"marko-async": "^2.0.0",
"marko-layout": "^2.0.0",
"minimatch": "^0.2.14",
"property-handlers": "^1.0.0",
"raptor-args": "^1.0.0",

View File

@ -7,6 +7,8 @@ var attrs = require('raptor-util/attrs');
var forEach = require('raptor-util/forEach');
var markoRegExp = /\.marko(.xml|.html)?$/;
var req = require;
var arrayFromArguments = require('raptor-util/arrayFromArguments');
var logger = require('raptor-logging').logger(module);
function notEmpty(o) {
if (o == null) {
@ -20,6 +22,8 @@ function notEmpty(o) {
return true;
}
var WARNED_INVOKE_BODY = 0;
module.exports = {
s: function(str) {
return (str == null) ? '' : str;
@ -132,13 +136,27 @@ module.exports = {
/**
* Invoke a tag handler render function
*/
t: function (out, renderFunc, input, body) {
t: function (out, renderFunc, input, body, hasOutParam) {
if (!input) {
input = {};
}
if (body) {
input.invokeBody = body;
input.renderBody = body;
input.invokeBody = function() {
if (!WARNED_INVOKE_BODY) {
WARNED_INVOKE_BODY = 1;
logger.warn('invokeBody(...) deprecated. Use renderBody(out) instead.', new Error().stack);
}
if (!hasOutParam) {
var args = arrayFromArguments(arguments);
args.unshift(out);
body.apply(this, args);
} else {
body.apply(this, arguments);
}
};
}
renderFunc(input, out);

View File

@ -34,7 +34,15 @@ function getPropsStr(props, template) {
template.indent(function () {
forEachEntry(props, function (name, value) {
if (typeof value === 'function') {
value = value();
value = template.captureCode(function() {
return value(template);
});
if (!value) {
throw new Error('Invalid value for property "' + name + '"');
}
value = template.makeExpression(value);
}
if (template.isExpression(value)) {
@ -51,7 +59,6 @@ function getPropsStr(props, template) {
});
});
if (propsArray.length) {
return '{\n' + propsArray.join(',\n') + '\n' + template.indentStr() + '}';
} else {
@ -102,16 +109,24 @@ TagHandlerNode.prototype = {
},
doGenerateCode: function (template) {
template.addStaticVar('__renderer', '__helpers.r');
var _this = this;
var rendererPath = template.getRequirePath(this.tag.renderer); // Resolve a path to the renderer relative to the directory of the template
var handlerVar = addHandlerVar(template, rendererPath);
var tagHelperVar = template.addStaticVar('__tag', '__helpers.t');
var bodyFunction = this.tag.bodyFunction;
this.tag.forEachImportedVariable(function (importedVariable) {
this.setProperty(importedVariable.targetProperty, template.makeExpression(importedVariable.expression));
}, this);
var _this = this;
if (bodyFunction) {
this.setProperty(bodyFunction.name, function(template) {
template.code('function(' + bodyFunction.params + ') {\n').indent(function () {
_this.generateCodeForChildren(template);
}).indent().code('}');
});
}
var variableNames = [];
_this.tag.forEachVariable(function (nestedVar) {
var varName;
@ -165,14 +180,32 @@ TagHandlerNode.prototype = {
template.code(getPropsStr(_this.getProperties(), template));
}
if (_this.hasChildren()) {
if (_this.hasChildren() && !_this.tag.bodyFunction) {
var bodyParams = [];
var hasOutParam = false;
variableNames.forEach(function (varName) {
if (varName === 'out') {
hasOutParam = true;
}
bodyParams.push(varName);
});
template.code(',\n').line('function(' + bodyParams.join(',') + ') {').indent(function () {
var params;
if (hasOutParam) {
params = bodyParams.join(',');
} else {
params = 'out' + (bodyParams.length ? ',' + bodyParams.join(',') : '');
}
template.code(',\n').line('function(' + params + ') {').indent(function () {
_this.generateCodeForChildren(template);
}).indent().code('}');
if (hasOutParam) {
template.code(',\n').code(template.indentStr() + '1');
}
}
});
});

View File

@ -60,7 +60,7 @@ function testRender(path, data, done, options) {
}
describe('marko/marko' , function() {
describe('marko/render' , function() {
beforeEach(function(done) {
// for (var k in require.cache) {

View File

@ -13,3 +13,17 @@
Tab 3 content
</test-tab>
</test-tabs>
<test-tabs-new>
<test-tab-new title="Tab 1">
Tab 1 content
</test-tab-new>
<test-tab-new title="Tab 2">
Tab 2 content
</test-tab-new>
<test-tab-new title="Tab 3" if="showConditionalTab">
Tab 3 content
</test-tab-new>
</test-tabs-new>

View File

@ -1 +1 @@
<div class="tabs"><ul class="nav nav-tabs"><li class="active"><a href="#tab0" data-toggle="tab">Tab 1</a></li><li><a href="#tab1" data-toggle="tab">Tab 2</a></li></ul><div class="tab-content"><div id="tab0" class="tab-pane active">Tab 1 content</div><div id="tab1" class="tab-pane">Tab 2 content</div></div></div>
<div class="tabs"><ul class="nav nav-tabs"><li class="active"><a href="#tab0" data-toggle="tab">Tab 1</a></li><li><a href="#tab1" data-toggle="tab">Tab 2</a></li></ul><div class="tab-content"><div id="tab0" class="tab-pane active">Tab 1 content</div><div id="tab1" class="tab-pane">Tab 2 content</div></div></div><div class="tabs"><ul class="nav nav-tabs"><li class="active"><a href="#tab0" data-toggle="tab">Tab 1</a></li><li><a href="#tab1" data-toggle="tab">Tab 2</a></li></ul><div class="tab-content"><div id="tab0" class="tab-pane active">Tab 1 content</div><div id="tab1" class="tab-pane">Tab 2 content</div></div></div>

View File

@ -26,6 +26,19 @@
"title": "string"
}
},
"test-tabs-new": {
"renderer": "./tabs-new-tag.js",
"body-function": "buildTabs(__tabsHelper)"
},
"test-tab-new": {
"renderer": "./tab-new-tag.js",
"import-var": {
"tabs": "__tabsHelper"
},
"attributes": {
"title": "string"
}
},
"test-dynamic-attributes": {
"renderer": "./dynamic-attributes-tag.js",
"attributes": {

View File

@ -0,0 +1,4 @@
exports.render = function(input, out) {
var tabs = input.tabs;
tabs.addTab(input);
};

View File

@ -0,0 +1,33 @@
var marko = require('../../');
exports.render = function(input, out) {
var tabs = [],
activeFound = false;
if (input.buildTabs) {
input.buildTabs({
addTab: function(tab) {
if (tab.active) {
tab.activeFound = true;
}
tab.id = "tab" + tabs.length;
tabs.push(tab);
}
});
}
if (!activeFound && tabs.length) {
tabs[0].active = true;
}
tabs.forEach(function(tab) {
tab.liClass = tab.active ? "active" : "";
tab.divClass = tab.active ? "tab-pane active" : "tab-pane";
});
marko.render(require.resolve('./tabs.marko'), {
tabs: tabs
}, out);
};