Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow components to have computed member expressions for bindings #627

Merged
merged 3 commits into from
Jun 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 6 additions & 9 deletions src/generators/dom/visitors/Component/Binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
import getObject from '../../../../utils/getObject';

export default function visitBinding(
generator: DomGenerator,
Expand All @@ -14,16 +15,11 @@ export default function visitBinding(
attribute,
local
) {
const { name } = flattenReference(attribute.value);
const { name } = getObject(attribute.value);
const { snippet, contexts, dependencies } = block.contextualise(
attribute.value
);

if (dependencies.length > 1)
throw new Error(
'An unexpected situation arose. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!'
);

contexts.forEach(context => {
if (!~local.allUsedContexts.indexOf(context))
local.allUsedContexts.push(context);
Expand All @@ -38,8 +34,9 @@ export default function visitBinding(
obj = block.listNames.get(name);
prop = block.indexNames.get(name);
} else if (attribute.value.type === 'MemberExpression') {
prop = `'[✂${attribute.value.property.start}-${attribute.value.property
.end}✂]'`;
prop = `[✂${attribute.value.property.start}-${attribute.value.property
.end}✂]`;
if (!attribute.value.computed) prop = `'${prop}'`;
obj = `[✂${attribute.value.object.start}-${attribute.value.object.end}✂]`;
} else {
obj = 'state';
Expand Down Expand Up @@ -85,7 +82,7 @@ export default function visitBinding(
local.update.addBlock(deindent`
if ( !${updating} && ${dependencies
.map(dependency => `'${dependency}' in changed`)
.join('||')} ) {
.join(' || ')} ) {
${updating} = true;
${local.name}._set({ ${attribute.name}: ${snippet} });
${updating} = false;
Expand Down
17 changes: 2 additions & 15 deletions src/generators/dom/visitors/Element/Binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@ import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';

function getObject(node) {
// TODO validation should ensure this is an Identifier or a MemberExpression
while (node.type === 'MemberExpression') node = node.object;
return node;
}
import getObject from '../../../../utils/getObject';

export default function visitBinding(
generator: DomGenerator,
Expand All @@ -21,15 +16,7 @@ export default function visitBinding(
attribute: Node
) {
const { name } = getObject(attribute.value);
const { snippet, contexts } = block.contextualise(attribute.value);
const dependencies = block.contextDependencies.has(name)
? block.contextDependencies.get(name)
: [name];

if (dependencies.length > 1)
throw new Error(
'An unexpected situation arose. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!'
);
const { snippet, contexts, dependencies } = block.contextualise(attribute.value);

contexts.forEach(context => {
if (!~state.allUsedContexts.indexOf(context))
Expand Down
12 changes: 3 additions & 9 deletions src/generators/dom/visitors/shared/binding/getSetter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import deindent from '../../../../../utils/deindent';
import getTailSnippet from '../../../../../utils/getTailSnippet';
import { Node } from '../../../../../interfaces';

export default function getSetter({
block,
Expand Down Expand Up @@ -40,15 +42,7 @@ export default function getSetter({
return `${block.component}._set({ ${name}: ${value} });`;
}

function getTailSnippet(node) {
const end = node.end;
while (node.type === 'MemberExpression') node = node.object;
const start = node.end;

return `[✂${start}-${end}✂]`;
}

function isComputed(node) {
function isComputed(node: Node) {
while (node.type === 'MemberExpression') {
if (node.computed) return true;
node = node.object;
Expand Down
7 changes: 4 additions & 3 deletions src/generators/server-side-rendering/Block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import deindent from '../../utils/deindent';
import flattenReference from '../../utils/flattenReference';
import { SsrGenerator } from './index';
import { Node } from '../../interfaces';
import getObject from '../../utils/getObject';

interface BlockOptions {
// TODO
Expand All @@ -25,13 +26,13 @@ export default class Block {
this.conditions.map(c => `(${c})`)
);

const { keypath } = flattenReference(binding.value);
const { name: prop } = getObject(binding.value);

this.generator.bindings.push(deindent`
if ( ${conditions.join('&&')} ) {
tmp = ${name}.data();
if ( '${keypath}' in tmp ) {
state.${binding.name} = tmp.${keypath};
if ( '${prop}' in tmp ) {
state.${binding.name} = tmp.${prop};
settled = false;
}
}
Expand Down
12 changes: 9 additions & 3 deletions src/generators/server-side-rendering/visitors/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import visit from '../visit';
import { SsrGenerator } from '../index';
import Block from '../Block';
import { Node } from '../../../interfaces';
import getObject from '../../../utils/getObject';
import getTailSnippet from '../../../utils/getTailSnippet';

export default function visitComponent(
generator: SsrGenerator,
Expand Down Expand Up @@ -52,9 +54,13 @@ export default function visitComponent(
})
.concat(
bindings.map(binding => {
const { name, keypath } = flattenReference(binding.value);
const value = block.contexts.has(name) ? keypath : `state.${keypath}`;
return `${binding.name}: ${value}`;
const { name } = getObject(binding.value);
const tail = binding.value.type === 'MemberExpression'
? getTailSnippet(binding.value)
: '';

const keypath = block.contexts.has(name) ? `${name}${tail}` : `state.${name}${tail}`;
return `${binding.name}: ${keypath}`;
})
)
.join(', ');
Expand Down
2 changes: 1 addition & 1 deletion src/utils/deindent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const start = /\n(\t+)/;

export default function deindent(strings: string[], ...values: any[]) {
export default function deindent(strings: TemplateStringsArray, ...values: any[]) {
const indentation = start.exec(strings[0])[1];
const pattern = new RegExp(`^${indentation}`, 'gm');

Expand Down
6 changes: 6 additions & 0 deletions src/utils/getObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Node } from '../interfaces';

export default function getObject(node: Node) {
while (node.type === 'MemberExpression') node = node.object;
return node;
}
9 changes: 9 additions & 0 deletions src/utils/getTailSnippet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Node } from '../interfaces';

export default function getTailSnippet(node: Node) {
const end = node.end;
while (node.type === 'MemberExpression') node = node.object;
const start = node.end;

return `[✂${start}-${end}✂]`;
}
13 changes: 9 additions & 4 deletions test/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import jsdom from 'jsdom';
import assert from 'assert';
import glob from 'glob';
import fs from 'fs';
import path from 'path';
import chalk from 'chalk';

import * as consoleGroup from 'console-group';
Expand Down Expand Up @@ -162,13 +163,17 @@ export function addLineNumbers(code) {
.join('\n');
}

export function showOutput(cwd, shared) {
function capitalize(str) {
return str[0].toUpperCase() + str.slice(1);
}

export function showOutput(cwd, options) {
glob.sync('**/*.html', { cwd }).forEach(file => {
const { code } = svelte.compile(
fs.readFileSync(`${cwd}/${file}`, 'utf-8'),
{
shared
}
Object.assign(options, {
name: capitalize(file.slice(0, -path.extname(file).length))
})
);

console.log( // eslint-disable-line no-console
Expand Down
35 changes: 10 additions & 25 deletions test/runtime/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,41 +57,26 @@ describe("runtime", () => {
throw new Error("Forgot to remove `solo: true` from test");
}

(config.skip ? it.skip : config.solo ? it.only : it)(`${dir} (${shared ? 'shared' : 'inline'} helpers`, () => {
(config.skip ? it.skip : config.solo ? it.only : it)(`${dir} (${shared ? 'shared' : 'inline'} helpers)`, () => {
if (failed.has(dir)) {
// this makes debugging easier, by only printing compiled output once
throw new Error('skipping inline helpers test');
}

const cwd = path.resolve(`test/runtime/samples/${dir}`);
let compiled;

compileOptions = config.compileOptions || {};
compileOptions.shared = shared;
compileOptions.dev = config.dev;

try {
const source = fs.readFileSync(
`test/runtime/samples/${dir}/main.html`,
"utf-8"
);
compiled = svelte.compile(source, compileOptions);
} catch (err) {
if (config.compileError) {
config.compileError(err);
return;
} else {
failed.add(dir);
showOutput(cwd, shared);
throw err;
}
}

const { code } = compiled;

// check that no ES2015+ syntax slipped in
if (!config.allowES2015) {
try {
const source = fs.readFileSync(
`test/runtime/samples/${dir}/main.html`,
"utf-8"
);
const { code } = svelte.compile(source, compileOptions);
const startIndex = code.indexOf("function create_main_fragment"); // may change!
if (startIndex === -1)
throw new Error("missing create_main_fragment");
Expand All @@ -101,7 +86,7 @@ describe("runtime", () => {
acorn.parse(es5, { ecmaVersion: 5 });
} catch (err) {
failed.add(dir);
showOutput(cwd, shared); // eslint-disable-line no-console
showOutput(cwd, { shared }); // eslint-disable-line no-console
throw err;
}
}
Expand Down Expand Up @@ -146,7 +131,7 @@ describe("runtime", () => {
try {
SvelteComponent = require(`./samples/${dir}/main.html`).default;
} catch (err) {
showOutput(cwd, shared); // eslint-disable-line no-console
showOutput(cwd, { shared }); // eslint-disable-line no-console
throw err;
}

Expand Down Expand Up @@ -210,12 +195,12 @@ describe("runtime", () => {
config.error(assert, err);
} else {
failed.add(dir);
showOutput(cwd, shared); // eslint-disable-line no-console
showOutput(cwd, { shared }); // eslint-disable-line no-console
throw err;
}
})
.then(() => {
if (config.show) showOutput(cwd, shared);
if (config.show) showOutput(cwd, { shared });
});
});
}
Expand Down
3 changes: 3 additions & 0 deletions test/runtime/samples/component-binding-computed/Nested.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<label>
{{field}} <input bind:value>
</label>
34 changes: 34 additions & 0 deletions test/runtime/samples/component-binding-computed/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export default {
html: `
<label>firstname <input></label>
<label>lastname <input></label>
`,

test ( assert, component, target, window ) {
const input = new window.Event( 'input' );
const inputs = target.querySelectorAll( 'input' );

inputs[0].value = 'Ada';
inputs[0].dispatchEvent(input);
assert.deepEqual(component.get('values'), {
firstname: 'Ada',
lastname: ''
});

inputs[1].value = 'Lovelace';
inputs[1].dispatchEvent(input);
assert.deepEqual(component.get('values'), {
firstname: 'Ada',
lastname: 'Lovelace'
});

component.set({
values: {
firstname: 'Grace',
lastname: 'Hopper'
}
});
assert.equal(inputs[0].value, 'Grace');
assert.equal(inputs[1].value, 'Hopper');
}
};
22 changes: 22 additions & 0 deletions test/runtime/samples/component-binding-computed/main.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{{#each fields as field}}
<Nested :field bind:value='values[field]'/>
{{/each}}

<script>
import Nested from './Nested.html';

export default {
data: function () {
return {
fields: ['firstname', 'lastname'],
values: {
firstname: '',
lastname: ''
}
};
},
components: {
Nested
}
};
</script>
Loading