From 7b30ab0d5d6137dad3b32a54ec51736b42248d2b Mon Sep 17 00:00:00 2001 From: asakusuma Date: Wed, 4 May 2016 14:35:06 -0700 Subject: [PATCH] [Glimmer2] Implement classBinding --- packages/ember-glimmer/lib/environment.js | 34 ++++++ packages/ember-glimmer/lib/helpers/-class.js | 11 +- .../components/curly-components-test.js | 115 ++++++++++++++++++ 3 files changed, 158 insertions(+), 2 deletions(-) diff --git a/packages/ember-glimmer/lib/environment.js b/packages/ember-glimmer/lib/environment.js index 57508277265..05b0cde43f9 100644 --- a/packages/ember-glimmer/lib/environment.js +++ b/packages/ember-glimmer/lib/environment.js @@ -62,6 +62,39 @@ function wrapClassAttribute(args) { return args; } +function wrapClassBindingAttribute(args) { + let hasClassBinding = args.named.has('classBinding'); + + if (hasClassBinding) { + let { value , type } = args.named.at('classBinding'); + if (type === 'value') { + let [ prop, truthy, falsy ] = value.split(':'); + let spec; + if (prop === '') { + spec = ['helper', ['-class'], [truthy], null]; + } else if (falsy) { + let parts = prop.split('.'); + spec = ['helper', ['-class'], [['get', parts], truthy, falsy], null]; + } else if (truthy) { + let parts = prop.split('.'); + spec = ['helper', ['-class'], [['get', parts], truthy], null]; + } + + if (spec) { + let syntax; + if (args.named.has('class')) { + // If class already exists, merge + let classValue = args.named.at('class').value; + syntax = HelperSyntax.fromSpec(['helper', ['concat'], [classValue, ' ', spec]]); + } else { + syntax = HelperSyntax.fromSpec(spec); + } + args.named.add('class', syntax); + } + } + } +} + export default class Environment extends GlimmerEnvironment { static create(options) { return new Environment(options); @@ -94,6 +127,7 @@ export default class Environment extends GlimmerEnvironment { let definition = this.getComponentDefinition(path); if (definition) { + wrapClassBindingAttribute(args); wrapClassAttribute(args); return new CurlyComponentSyntax({ args, definition, templates }); } diff --git a/packages/ember-glimmer/lib/helpers/-class.js b/packages/ember-glimmer/lib/helpers/-class.js index 8bc0c1c4c1c..076173b1f0d 100644 --- a/packages/ember-glimmer/lib/helpers/-class.js +++ b/packages/ember-glimmer/lib/helpers/-class.js @@ -3,14 +3,21 @@ import { dasherize } from 'ember-runtime/system/string'; function classHelper({ positional }) { let path = positional.at(0); - let propName = positional.at(1); + let truthyPropName = positional.at(1); + let falsyPropName = positional.at(2); let value = path.value(); if (value === true) { - return dasherize(propName.value()); + if (truthyPropName) { + return dasherize(truthyPropName.value()); + } + return null; } if (value === false) { + if (falsyPropName) { + return dasherize(falsyPropName.value()); + } return null; } diff --git a/packages/ember-glimmer/tests/integration/components/curly-components-test.js b/packages/ember-glimmer/tests/integration/components/curly-components-test.js index 51af164b55e..c136777062f 100644 --- a/packages/ember-glimmer/tests/integration/components/curly-components-test.js +++ b/packages/ember-glimmer/tests/integration/components/curly-components-test.js @@ -9,6 +9,9 @@ import { classes, equalTokens, equalsElement } from '../../utils/test-helpers'; import { htmlSafe } from 'ember-htmlbars/utils/string'; import { computed } from 'ember-metal/computed'; +const bindingDeprecationMessage = '`Ember.Binding` is deprecated. Consider' + + ' using an `alias` computed property instead.'; + moduleFor('Components test: curly components', class extends RenderingTest { ['@test it can render a basic component']() { @@ -200,6 +203,118 @@ moduleFor('Components test: curly components', class extends RenderingTest { this.assertComponentElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { 'class': classes('foo ember-view') } }); } + ['@glimmer it should merge classBinding with class']() { + expectDeprecation(bindingDeprecationMessage); + + this.registerComponent('foo-bar', { template: 'hello' }); + + this.render('{{foo-bar classBinding="birdman:respeck" class="myName"}}', { birdman: true }); + + this.assertComponentElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { 'class': classes('respeck myName ember-view') } }); + + this.runTask(() => this.rerender()); + + this.assertComponentElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { 'class': classes('respeck myName ember-view') } }); + } + + ['@glimmer in glimmer it should apply classBinding without condition always']() { + expectDeprecation(bindingDeprecationMessage); + + this.registerComponent('foo-bar', { template: 'hello' }); + + this.render('{{foo-bar classBinding=":foo"}}'); + + this.assertComponentElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { 'class': classes('foo ember-view') } }); + + this.runTask(() => this.rerender()); + + this.assertComponentElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { 'class': classes('foo ember-view') } }); + } + + ['@glimmer it should apply classBinding with only truthy condition']() { + expectDeprecation(bindingDeprecationMessage); + + this.registerComponent('foo-bar', { template: 'hello' }); + + this.render('{{foo-bar classBinding="myName:respeck"}}', { myName: true }); + + this.assertComponentElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { 'class': classes('respeck ember-view') } }); + + this.runTask(() => this.rerender()); + + this.assertComponentElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { 'class': classes('respeck ember-view') } }); + } + + ['@glimmer it should apply classBinding with only falsy condition']() { + expectDeprecation(bindingDeprecationMessage); + + this.registerComponent('foo-bar', { template: 'hello' }); + + this.render('{{foo-bar classBinding="myName::shade"}}', { myName: false }); + + this.assertComponentElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { 'class': classes('shade ember-view') } }); + + this.runTask(() => this.rerender()); + + this.assertComponentElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { 'class': classes('shade ember-view') } }); + } + + ['@glimmer it should apply nothing when classBinding is falsy but only supplies truthy class']() { + expectDeprecation(bindingDeprecationMessage); + + this.registerComponent('foo-bar', { template: 'hello' }); + + this.render('{{foo-bar classBinding="myName:respeck"}}', { myName: false }); + + this.assertComponentElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { 'class': classes('ember-view') } }); + + this.runTask(() => this.rerender()); + + this.assertComponentElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { 'class': classes('ember-view') } }); + } + + ['@glimmer it should apply nothing when classBinding is truthy but only supplies falsy class']() { + expectDeprecation(bindingDeprecationMessage); + + this.registerComponent('foo-bar', { template: 'hello' }); + + this.render('{{foo-bar classBinding="myName::shade"}}', { myName: true }); + + this.assertComponentElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { 'class': classes('ember-view') } }); + + this.runTask(() => this.rerender()); + + this.assertComponentElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { 'class': classes('ember-view') } }); + } + + ['@glimmer it should apply classBinding with falsy condition']() { + expectDeprecation(bindingDeprecationMessage); + + this.registerComponent('foo-bar', { template: 'hello' }); + + this.render('{{foo-bar classBinding="swag:fresh:scrub"}}', { swag: false }); + + this.assertComponentElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { 'class': classes('scrub ember-view') } }); + + this.runTask(() => this.rerender()); + + this.assertComponentElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { 'class': classes('scrub ember-view') } }); + } + + ['@glimmer it should apply classBinding with truthy condition']() { + expectDeprecation(bindingDeprecationMessage); + + this.registerComponent('foo-bar', { template: 'hello' }); + + this.render('{{foo-bar classBinding="swag:fresh:scrub"}}', { swag: true }); + + this.assertComponentElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { 'class': classes('fresh ember-view') } }); + + this.runTask(() => this.rerender()); + + this.assertComponentElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { 'class': classes('fresh ember-view') } }); + } + ['@test should not apply falsy class name']() { this.registerComponent('foo-bar', { template: 'hello' });