Fixes #452 - Deprecate the layout taglib

This commit is contained in:
Patrick Steele-Idem 2016-11-30 20:46:31 -07:00
parent 9e372967c3
commit abbe88dfca
51 changed files with 351 additions and 227 deletions

View File

@ -5,6 +5,7 @@ var removeDashes = require('../util/removeDashes');
var safeVarName = require('../util/safeVarName');
var ok = require('assert').ok;
var tagLoader;
var Node = require('./Node');
var CUSTOM_TAG_KEY = Symbol('CustomTag');
@ -156,6 +157,51 @@ function processDirectlyNestedTags(node, codegen) {
});
}
function merge(props1, props2, context) {
if (!props2) {
return props1;
}
if (!(props2 instanceof Node)) {
if (Object.keys(props2).length === 0) {
return props1;
}
}
if (props1 instanceof Node) {
let mergeVar = context.helper('merge');
if (!(props2 instanceof Node)) {
props2 = context.builder.literal(props2);
}
return context.builder.functionCall(mergeVar, [
props2, // Input props from the attributes take precedence
props1
]);
} else {
if (props2 instanceof Node) {
let mergeVar = context.helper('merge');
return context.builder.functionCall(mergeVar, [
props2, // Input props from the attributes take precedence
props1
]);
} else {
if (props1._arg) {
let mergeVar = context.helper('merge');
props1._arg = context.builder.functionCall(mergeVar, [
context.builder.literal(props2), // Input props from the attributes take precedence
props1._arg
]);
return props1;
} else {
return Object.assign(props1, props2);
}
}
}
}
class CustomTag extends HtmlElement {
constructor(el, tagDef) {
super(el);
@ -168,6 +214,8 @@ class CustomTag extends HtmlElement {
this._condition = null;
this._foundNestedTagsByName = {};
this._hasDynamicNestedTags = false;
this._additionalProps = null;
this._rendererPath = null;
}
buildInputProps(codegen) {
@ -342,6 +390,26 @@ class CustomTag extends HtmlElement {
byNameArray.push(nestedTag);
}
addProps(additionalProps) {
if (!this._additionalProps) {
this._additionalProps = {};
}
Object.assign(this._additionalProps, additionalProps);
}
addProp(name, value) {
if (!this._additionalProps) {
this._additionalProps = {};
}
this._additionalProps[name] = value;
}
setRendererPath(path) {
ok(typeof path === 'string', '"path" should be a string');
this._rendererPath = path;
}
getNestedTagVar(context) {
if (!this._nestedTagVar) {
var tagDef = this.tagDef;
@ -364,25 +432,22 @@ class CustomTag extends HtmlElement {
var tagDef = this.resolveTagDef(codegen);
if (!tagDef) {
// The tag def was not able to be resolved and an error should have already
// been added to the context
return null;
}
var parentCustomTag;
// console.log('BEGIN:', JSON.stringify(this, null, 2));
// console.log(module.id, 'generateCode BEGIN:', this.tagName, new Error().stack);
context.pushData(CUSTOM_TAG_KEY, this);
processDirectlyNestedTags(this, codegen);
var body = codegen.generateCode(this.body);
context.popData(CUSTOM_TAG_KEY);
// console.log(module.id, 'generateCode END:', this.tagName);
var isNestedTag = tagDef.isNestedTag === true;
if (isNestedTag) {
parentCustomTag = context.getData(CUSTOM_TAG_KEY);
// console.log('parentCustomTag:', parentCustomTag.tagName, 'child:', tagDef.name);
if (!parentCustomTag) {
if (tagDef.parentTagName) {
codegen.addError(`Invalid usage of the <${this.tagName}> nested tag. Tag not nested within a <${tagDef.parentTagName}> tag.`);
@ -435,12 +500,6 @@ class CustomTag extends HtmlElement {
var inputProps = this.buildInputProps(codegen);
// if (isNestedTag) {
// isRepeated = tagDef.isRepeated === true;
// targetProperty = tagDef.targetProperty;
// parentTagVar = parentCustomTag.getNestedTagVar(context);
// }
var renderBodyFunction;
if (body && body.length) {
@ -481,29 +540,31 @@ class CustomTag extends HtmlElement {
bodyOnlyIf = null;
}
inputProps = builder.literal(inputProps);
var argExpression;
var argument = this.argument;
if (this.argument) {
argExpression = builder.parseExpression(this.argument);
}
if (argument) {
argument = builder.parseExpression(argument);
var additionalProps = this._additionalProps;
if (Object.keys(inputProps.value).length === 0) {
inputProps = argument;
} else {
var mergeVar = context.helper('merge');
inputProps = builder.functionCall(mergeVar, [
inputProps, // Input props from the attributes take precedence
argument
]);
}
if (additionalProps) {
inputProps = merge(additionalProps, inputProps, context);
}
if (argExpression) {
inputProps = merge(argExpression, inputProps, context);
}
if (!(inputProps instanceof Node)) {
inputProps = builder.literal(inputProps);
}
if (hasDynamicNestedTags) {
inputProps = builder.functionCall(context.helper('mergeNestedTagsHelper'), [ inputProps ]);
}
var rendererPath = tagDef.renderer;
var rendererPath = this._rendererPath || tagDef.renderer;
var rendererRequirePath;
var requireRendererFunctionCall;

View File

@ -30,7 +30,7 @@
"test-express": "mocha --ui bdd --reporter spec ./test/express-test",
"test-widgets": "npm run test-widgets-browser -s && npm run test-widgets-browser-deprecated -s && npm run jshint --silent",
"test-widgets-browser": "node test/browser-tests-runner/cli.js test/widgets-browser-tests.js --automated && npm run test-widgets-browser-pages",
"test-widgets-browser-deprecated": "node test/browser-tests-runner/cli.js test/widgets-browser-deprecated-tests.js --automated && npm run test-widgets-browser-pages",
"test-widgets-browser-deprecated": "node test/browser-tests-runner/cli.js test/deprecated-widgets-browser-tests.js --automated && npm run test-widgets-browser-pages",
"test-widgets-browser-pages": "node test/browser-tests-runner/cli.js --pages --automated",
"test-widgets-browser-dev": "browser-refresh test/browser-tests-runner/cli.js test/widgets-browser-tests.js --server",
"test-widgets-page": "browser-refresh test/browser-tests-runner/cli.js test/widgets-browser-tests.js --server --page",

View File

@ -7,7 +7,7 @@ function classListHelper(arg, classNames) {
if (arg) {
if (typeof arg === 'string') {
if (arg) {
classNames.push(arg);
classNames.push(arg);
}
} else if (typeof (len = arg.length) === 'number') {
for (var i=0; i<len; i++) {
@ -250,5 +250,4 @@ exports.cl = function classListHelper() {
/**
* Loads a template (__helpers.l --> marko_loadTemplate(path))
*/
exports.l = require('./loader');
exports.i = require('./include');
exports.l = require('./loader');

View File

@ -1,17 +0,0 @@
module.exports = function include(target, out, data) {
if (target) {
if (typeof target === 'function') {
target(out, data);
} else if (typeof target === 'object') {
if (target.renderBody) {
target.renderBody(out, data);
} else if (target.renderer) {
target.renderer(data, out);
} else if (target.render) {
target.render(data, out);
}
} else {
throw new Error('Invalid include target: ' + target);
}
}
};

View File

@ -0,0 +1,34 @@
'use strict';
module.exports = function codeGenerator(el, context) {
let builder = context.builder;
let target;
let arg;
if (el.argument) {
let args = el.argument && builder.parseJavaScriptArgs(el.argument);
el.argument = null;
target = args[0];
arg = args[1];
} else {
return;
}
if (target.type === 'Literal') {
target = context.importTemplate(target.value);
}
var includeProps = {
_target: target
};
if (arg) {
includeProps._arg = arg;
}
el.data.includeTarget = target;
el.addProps(includeProps);
};

View File

@ -1,87 +1,20 @@
'use strict';
var removeHyphens = require('../../compiler/util/removeDashes');
module.exports = function codeGenerator(el, codegen) {
let builder = codegen.builder;
let target;
let data;
if (el.argument) {
let args = el.argument && builder.parseJavaScriptArgs(el.argument);
target = args[0];
data = args[1];
}
var isTemplate = false;
module.exports = function include(input, out) {
var target = input._target;
var arg = input._arg || input;
if (target) {
if (target.type === 'Literal') {
target = codegen.context.importTemplate(target.value);
isTemplate = true;
}
}
let finalData = {};
let attrs = el.getAttributes();
attrs.forEach((attr) => {
var propName = attr.name;
if (propName.indexOf('-') !== -1) {
propName = removeHyphens(propName); // Convert the property name to camel case
}
finalData[propName] = attr.value;
});
if (el.body && el.body.length) {
finalData.renderBody = builder.renderBodyFunction(el.body);
}
if (data) {
if (Object.keys(finalData).length === 0) {
finalData = data;
} else {
let mergeVar = codegen.context.helper('merge');
finalData = builder.functionCall(mergeVar, [
builder.literal(finalData), // Input props from the attributes take precedence
data // The template data object is passed as the second argument: <include("./foo.marko", { ... })/>
]);
}
} else {
if (Object.keys(finalData).length === 0) {
finalData = null;
} else {
finalData = builder.literal(finalData);
}
}
if (isTemplate) {
let renderMethod = builder.memberExpression(target, builder.identifier('render'));
if (!finalData) {
finalData = builder.literal({});
}
let renderArgs = [ finalData, builder.identifierOut() ];
let renderFunctionCall = builder.functionCall(renderMethod, renderArgs);
return renderFunctionCall;
} else {
if (this.generateCodeForDynamicInclude) {
return this.generateCodeForDynamicInclude({
target: target,
data: finalData
}, codegen);
} else {
if (!target) {
target = builder.memberExpression(builder.identifier('data'), builder.identifier('renderBody'));
if (typeof target === 'function') {
target(out, arg);
} else if (typeof target === 'object') {
if (target.renderBody) {
target.renderBody(out, arg);
} else if (target.renderer) {
target.renderer(arg, out);
} else if (target.render) {
target.render(arg, out);
}
let includeVar = codegen.context.helper('include');
let includeArgs = [ target, builder.identifierOut() ];
if (finalData) {
includeArgs.push(finalData);
}
return builder.functionCall(includeVar, includeArgs);
} else {
throw new Error('Invalid include target: ' + target);
}
}
};

View File

@ -79,7 +79,8 @@
"code-generator": "./import-tag"
},
"<include>": {
"code-generator": "./include-tag",
"renderer": "./include-tag",
"transformer": "./include-tag-transformer",
"autocomplete": [
{
"displayText": "include(<template>)",

View File

@ -6,6 +6,9 @@ module.exports = function transform(el, context) {
context.addError(el, 'Invalid <layout-use> tag. Expected: <layout-use(template[, data]) ...>');
return;
}
console.warn('The <layout-use> tag is deprecated. Please use <include> instead. See: https://github.com/marko-js/marko/issues/452 (' + (el.pos ? context.getPosInfo(el.pos) : context.filename) + ')');
var builder = context.builder;
var args = builder.parseJavaScriptArgs(argument);

View File

@ -0,0 +1 @@
<div>BODY CONTENT</div> ---- <h1>DEFAULT TITLE</h1><div>BODY CONTENT</div><h1>FOOTER CONTENT</h1>

View File

@ -0,0 +1,19 @@
<h1 if(data.showHeader !== false)>
<if(data.header)>
<include(data.header)/>
</if>
<else>
DEFAULT TITLE
</else>
</h1>
<div>
<include(data.body)/>
</div>
<h1 if(data.showFooter !== false)>
<if(data.footer)>
<include(data.footer)/>
</if>
<else>
DEFAULT FOOTER
</else>
</h1>

View File

@ -0,0 +1,11 @@
-------
<include("./layout-default.marko", {showHeader: false}) show-footer=false>
<@body>BODY CONTENT</@body>
<@footer>FOOTER CONTENT</@footer>
</include>
----
<include("./layout-default.marko", {showHeader: false}) show-header=true>
<@body>BODY CONTENT</@body>
<@footer>FOOTER CONTENT</@footer>
</include>
-------

View File

@ -0,0 +1,3 @@
exports.templateData = {
layoutDynamic: require('./layout-default.marko')
};

View File

@ -0,0 +1 @@
<div>BODY CONTENT</div>FOOTER CONTENT ---- <h1>DEFAULT TITLE</h1><div>BODY CONTENT2</div>FOOTER CONTENT2 ---- <h1>My Title</h1><div>BODY CONTENT2</div>FOOTER CONTENT2

View File

@ -0,0 +1,13 @@
<h1 if(data.showHeader !== false)>
<if(data.header)>
<include(data.header)/>
</if>
<else>
DEFAULT TITLE
</else>
</h1>
<div>
<include(data.body)/>
</div>
<include(data.footer)/>
<include(data.empty)/>

View File

@ -0,0 +1,17 @@
---------
<include("./layout-default.marko", {showHeader: false})>
<@body>BODY CONTENT</@body>
<@footer>FOOTER CONTENT</@footer>
</include>
----
<include("./layout-default.marko", {showHeader: true})>
<@body>BODY CONTENT2</@body>
<@footer>FOOTER CONTENT2</@footer>
</include>
----
<include("./layout-default.marko", {showHeader: true})>
<@header>My Title</@header>
<@body>BODY CONTENT2</@body>
<@footer>FOOTER CONTENT2</@footer>
</include>
---------

View File

@ -0,0 +1,3 @@
exports.templateData = {
layoutDynamic: require('./layout-default.marko')
};

View File

@ -0,0 +1 @@
<div>BODY CONTENT</div>FOOTER CONTENT<h1>HEADER CONTENT</h1><div>BODY CONTENT</div>FOOTER CONTENT<h1>VALUE HEADER</h1><div>BODY CONTENT</div>FOOTER CONTENT<h1>DEFAULT TITLE</h1><div>BODY CONTENT</div>FOOTER CONTENT

View File

@ -0,0 +1,13 @@
<h1 if(data.showHeader !== false)>
<if(data.header)>
<include(data.header)/>
</if>
<else>
DEFAULT TITLE
</else>
</h1>
<div>
<include(data.body)/>
</div>
<include(data.footer)/>
<include(data.empty)/>

View File

@ -0,0 +1,18 @@
<include("./layout-default.marko") show-header=false>
<@body>BODY CONTENT</@body>
<@footer>FOOTER CONTENT</@footer>
</include>
<include("./layout-default.marko") show-header=true>
<@header>HEADER CONTENT</@header>
<@body>BODY CONTENT</@body>
<@footer>FOOTER CONTENT</@footer>
</include>
<include("./layout-default.marko") show-header=true>
<@header>VALUE HEADER</@header>
<@body>BODY CONTENT</@body>
<@footer>FOOTER CONTENT</@footer>
</include>
<include(data.layoutDynamic) show-header=true>
<@body>BODY CONTENT</@body>
<@footer>FOOTER CONTENT</@footer>
</include>

View File

@ -0,0 +1,3 @@
exports.templateData = {
layoutDynamic: require('./layout-default.marko')
};

View File

@ -0,0 +1 @@
<div>Frank:10</div>

View File

@ -0,0 +1 @@
<include(data.renderBody, {name: 'Frank'}) age=10/>

View File

@ -0,0 +1,7 @@
exports.templateData = {
renderBody(out, data) {
out.beginElement('div');
out.text(data.name + ':' + data.age);
out.endElement();
}
};

View File

@ -1,5 +0,0 @@
<div>
<h1>
Hello World!
</h1>
</div>

View File

@ -0,0 +1,22 @@
'use strict';
require('./util/patch-module');
var chai = require('chai');
chai.config.includeStack = true;
var path = require('path');
var autotest = require('./autotest');
var runRenderTest = require('./util/runRenderTest');
require('../node-require').install();
describe('render', function() {
var autoTestDir = path.join(__dirname, 'autotests/render-deprecated');
autotest.scanDir(
autoTestDir,
function run(dir, helpers, done) {
runRenderTest(dir, helpers, done, {
output: 'html'
});
});
});

View File

@ -1,18 +1,3 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
function getContainingWidgetNode() {
if (this.containingWidgetNode !== undefined) {
@ -22,7 +7,7 @@ function getContainingWidgetNode() {
var curNode = this.el;
while (true) {
if (curNode.tagName === 'w-widget') {
if (curNode.tagName === '_widget') {
this.containingWidgetNode = curNode;
return this.containingWidgetNode;
} else if (curNode.isFlagSet('hasWidgetExtend')) {

View File

@ -1,5 +1,7 @@
'use strict';
var includeTagForWidgets = require.resolve('../include-tag');
module.exports = function(includeNode) {
var builder = this.builder;
var context = this.context;
@ -11,15 +13,13 @@ module.exports = function(includeNode) {
var parentNode = includeNode.parentNode;
var parentTransformHelper;
parentNode._normalizeChildTextNodes(context);
if (parentNode.childCount === 1) {
parentTransformHelper = this.getTransformHelper(parentNode);
let parentTransformHelper = this.getTransformHelper(parentNode);
if (includeNode.argument) {
var widgetIdInfo = parentTransformHelper.assignWidgetId(true /* repeated */);
let widgetIdInfo = parentTransformHelper.assignWidgetId(true /* repeated */);
if (!widgetIdInfo.idVarNode) {
let idVarNode = widgetIdInfo.createIdVarNode();
parentNode.onBeforeGenerateCode((event) => {
@ -30,35 +30,44 @@ module.exports = function(includeNode) {
parentTransformHelper.assignWidgetId(false /* not repeated */);
widgetTagNode.setAttributeValue('body', parentTransformHelper.getNestedIdExpression());
}
if (!includeNode.data.includeTarget) {
includeNode.addProp('_target', builder.memberExpression(builder.identifier('data'), builder.identifier('widgetBody')));
}
includeNode.setRendererPath(includeTagForWidgets);
includeNode.onBeforeGenerateCode(function() {
includeNode.addProp('_widgetId', parentTransformHelper.getIdExpression());
includeNode.addProp('_widget', builder.identifier('widget'));
});
}
includeNode.generateCodeForDynamicInclude = (options, codegen) => {
var target = options.target;
var data = options.data;
if (!target) {
target = builder.memberExpression(builder.identifier('data'), builder.identifier('widgetBody'));
}
if (!data) {
data = builder.literal(null);
}
let includeVar = context.importModule('marko_widget_include', this.getMarkoWidgetsRequirePath('marko/widgets/taglib/helpers/include'));
let includeArgs = [
target,
builder.identifierOut(),
data
];
if (parentTransformHelper) {
includeArgs = includeArgs.concat([
parentTransformHelper.getIdExpression(),
builder.identifier('widget')
]);
}
return builder.functionCall(includeVar, includeArgs);
};
// includeNode.generateCodeForDynamicInclude = (options, codegen) => {
// var target = options.target;
// var data = options.data;
//
// if (!data) {
// data = builder.literal(null);
// }
//
// let includeVar = context.importModule('marko_widget_include', this.getMarkoWidgetsRequirePath('marko/widgets/taglib/helpers/include'));
//
// let includeArgs = [
// target,
// builder.identifierOut(),
// data
// ];
//
// if (parentTransformHelper) {
// includeArgs = includeArgs.concat([
// parentTransformHelper.getIdExpression(),
//
// ]);
// }
//
// return builder.functionCall(includeVar, includeArgs);
// };
};

View File

@ -165,7 +165,7 @@ module.exports = function handleWidgetBind() {
widgetAttrs.id = id;
}
let widgetNode = context.createNodeForEl('w-widget', widgetAttrs);
let widgetNode = context.createNodeForEl('_widget', widgetAttrs);
el.wrapWith(widgetNode);
el.setAttributeValue('id', builder.memberExpression(builder.identifier('widget'), builder.identifier('id')));

View File

@ -1,23 +0,0 @@
var isBrowser = typeof window !== 'undefined';
var normalInclude = require('../../../runtime/include');
var markoWidgets = require('../../');
module.exports = function include(target, out, data, id, widget) {
if (typeof target === 'string') {
out.text(target);
} else if (target) {
normalInclude(target, out, data || widget);
} else if (isBrowser) {
if (id) {
// There is no body content so let's see if we should reuse
// the existing body content in the DOM
var existingEl = document.getElementById(id);
if (existingEl) {
var widgetsContext = markoWidgets.getWidgetsContext(out);
widgetsContext.addPreservedDOMNode(existingEl, true /* body only */);
}
} else {
throw new Error('Invalid include');
}
}
};

View File

@ -0,0 +1,23 @@
var isBrowser = typeof window !== 'undefined';
var normalInclude = require('../../taglibs/core/include-tag');
var markoWidgets = require('../');
module.exports = function include(input, out) {
var target = input._target;
if (typeof target === 'string') {
out.text(target);
} else if (target) {
normalInclude(input, out);
} else if (isBrowser) {
var widgetId = input._widgetId;
// Thereis no body content so let's see if we should reuse
// the existing body content in the DOM
var existingEl = document.getElementById(widgetId);
if (existingEl) {
var widgetsContext = markoWidgets.getWidgetsContext(out);
widgetsContext.addPreservedDOMNode(existingEl, true /* body only */);
}
}
};

View File

@ -195,7 +195,7 @@
},
"transformer": "./widgets-transformer.js"
},
"<w-widget>": {
"<_widget>": {
"renderer": "./widget-tag.js",
"@type": "object",
"@config": "object",

View File

@ -1,24 +1,8 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
var markoWidgets = require('../');
var extend = require('raptor-util/extend');
var widgetArgsId = require('../widget-args-id');
var includeHelper = require('./helpers/include');
var includeTag = require('./include-tag');
var DUMMY_WIDGET_DEF = {
elId: function () {
@ -50,7 +34,10 @@ function preserveWidgetEl(existingWidget, out, widgetsContext, widgetBody) {
if (widgetBody && existingWidget.bodyEl) {
hasUnpreservedBody = true;
includeHelper(widgetBody, out, null, existingWidget.bodyEl.id, existingWidget.bodyEl.id);
includeTag({
_target: widgetBody,
_widgetId: existingWidget.bodyEl.id
}, out);
}
out.endElement();