diff --git a/compiler/ast/CustomTag.js b/compiler/ast/CustomTag.js index 216be2164..ab29a6d17 100644 --- a/compiler/ast/CustomTag.js +++ b/compiler/ast/CustomTag.js @@ -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; diff --git a/package.json b/package.json index 55e81f1b3..9c426f3a6 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/runtime/helpers.js b/runtime/helpers.js index c8efc2536..bd37e0ed0 100644 --- a/runtime/helpers.js +++ b/runtime/helpers.js @@ -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 marko_loadTemplate(path)) */ -exports.l = require('./loader'); -exports.i = require('./include'); \ No newline at end of file +exports.l = require('./loader'); \ No newline at end of file diff --git a/runtime/include.js b/runtime/include.js deleted file mode 100644 index bf0f9ad2c..000000000 --- a/runtime/include.js +++ /dev/null @@ -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); - } - } -}; \ No newline at end of file diff --git a/taglibs/core/include-tag-transformer.js b/taglibs/core/include-tag-transformer.js new file mode 100644 index 000000000..d98fb3433 --- /dev/null +++ b/taglibs/core/include-tag-transformer.js @@ -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); +}; \ No newline at end of file diff --git a/taglibs/core/include-tag.js b/taglibs/core/include-tag.js index 9679d6d5b..e43618cc0 100644 --- a/taglibs/core/include-tag.js +++ b/taglibs/core/include-tag.js @@ -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: - ]); - } - } 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); } } }; \ No newline at end of file diff --git a/taglibs/core/marko.json b/taglibs/core/marko.json index 7af186513..af9806360 100644 --- a/taglibs/core/marko.json +++ b/taglibs/core/marko.json @@ -79,7 +79,8 @@ "code-generator": "./import-tag" }, "": { - "code-generator": "./include-tag", + "renderer": "./include-tag", + "transformer": "./include-tag-transformer", "autocomplete": [ { "displayText": "include(