Skip to content

Commit

Permalink
Merge branch 'master' into gh-312
Browse files Browse the repository at this point in the history
  • Loading branch information
Rich-Harris committed Mar 17, 2017
2 parents 5360bbf + ecf1413 commit 0f9ef05
Show file tree
Hide file tree
Showing 17 changed files with 324 additions and 205 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import createBinding from './binding/index.js';
import addComponentBinding from './addComponentBinding.js';
import deindent from '../../../../utils/deindent.js';

export default function addComponentAttributes ( generator, node, local ) {
Expand Down Expand Up @@ -112,7 +112,7 @@ export default function addComponentAttributes ( generator, node, local ) {
}

else if ( attribute.type === 'Binding' ) {
createBinding( generator, node, attribute, generator.current, local );
addComponentBinding( generator, node, attribute, generator.current, local );
}

else if ( attribute.type === 'Ref' ) {
Expand Down
60 changes: 60 additions & 0 deletions src/generators/dom/visitors/attributes/addComponentBinding.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import deindent from '../../../../utils/deindent.js';
import flattenReference from '../../../../utils/flattenReference.js';
import getSetter from './binding/getSetter.js';

export default function createBinding ( generator, node, attribute, current, local ) {
const { name } = flattenReference( attribute.value );
const { snippet, contexts, dependencies } = generator.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 );
});

const contextual = name in current.contexts;

let obj;
let prop;

if ( contextual ) {
obj = current.listNames[ name ];
prop = current.indexNames[ name ];
} else if ( attribute.value.type === 'MemberExpression' ) {
prop = `'[✂${attribute.value.property.start}-${attribute.value.property.end}✂]}'`;
obj = `root.[✂${attribute.value.object.start}-${attribute.value.object.end}✂]}`;
} else {
obj = 'root';
prop = `'${name}'`;
}

local.bindings.push({
name: attribute.name,
value: snippet,
obj,
prop
});

const setter = getSetter({ current, name, context: '_context', attribute, dependencies, snippet, value: 'value' });

generator.hasComplexBindings = true;

local.init.addBlock( deindent`
var ${local.name}_updating = false;
component._bindings.push( function () {
if ( ${local.name}._torndown ) return;
${local.name}.observe( '${attribute.name}', function ( value ) {
${local.name}_updating = true;
${setter}
${local.name}_updating = false;
});
});
` );

local.update.addBlock( deindent`
if ( !${local.name}_updating && ${dependencies.map( dependency => `'${dependency}' in changed` ).join( '||' )} ) {
${local.name}._set({ ${attribute.name}: ${snippet} });
}
` );
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import attributeLookup from './lookup.js';
import createBinding from './binding/index.js';
import addElementBinding from './addElementBinding';
import deindent from '../../../../utils/deindent.js';
import flattenReference from '../../../../utils/flattenReference.js';

Expand Down Expand Up @@ -204,7 +204,7 @@ export default function addElementAttributes ( generator, node, local ) {
}

else if ( attribute.type === 'Binding' ) {
createBinding( generator, node, attribute, generator.current, local );
addElementBinding( generator, node, attribute, generator.current, local );
}

else if ( attribute.type === 'Ref' ) {
Expand Down
106 changes: 106 additions & 0 deletions src/generators/dom/visitors/attributes/addElementBinding.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import deindent from '../../../../utils/deindent.js';
import flattenReference from '../../../../utils/flattenReference.js';
import getSetter from './binding/getSetter.js';

export default function createBinding ( generator, node, attribute, current, local ) {
const { name } = flattenReference( attribute.value );
const { snippet, contexts, dependencies } = generator.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 );
});

const handler = current.getUniqueName( `${local.name}ChangeHandler` );

const isMultipleSelect = node.name === 'select' && node.attributes.find( attr => attr.name.toLowerCase() === 'multiple' ); // TODO ensure that this is a static attribute
const value = getBindingValue( local, node, attribute, isMultipleSelect );
const eventName = getBindingEventName( node );

let setter = getSetter({ current, name, context: '__svelte', attribute, dependencies, snippet, value });

// special case
if ( node.name === 'select' && !isMultipleSelect ) {
setter = `var selectedOption = ${local.name}.selectedOptions[0] || ${local.name}.options[0];\n` + setter;
}

let updateElement;

if ( node.name === 'select' ) {
const value = generator.current.getUniqueName( 'value' );
const i = generator.current.getUniqueName( 'i' );
const option = generator.current.getUniqueName( 'option' );

const ifStatement = isMultipleSelect ?
deindent`
${option}.selected = ~${value}.indexOf( ${option}.__value );` :
deindent`
if ( ${option}.__value === ${value} ) {
${option}.selected = true;
break;
}`;

updateElement = deindent`
var ${value} = ${snippet};
for ( var ${i} = 0; ${i} < ${local.name}.options.length; ${i} += 1 ) {
var ${option} = ${local.name}.options[${i}];
${ifStatement}
}
`;
} else {
updateElement = `${local.name}.${attribute.name} = ${snippet};`;
}

local.init.addBlock( deindent`
var ${local.name}_updating = false;
function ${handler} () {
${local.name}_updating = true;
${setter}
${local.name}_updating = false;
}
${generator.helper( 'addEventListener' )}( ${local.name}, '${eventName}', ${handler} );
` );

node.initialUpdate = updateElement;

local.update.addLine( deindent`
if ( !${local.name}_updating ) {
${updateElement}
}
` );

generator.current.builders.teardown.addLine( deindent`
${generator.helper( 'removeEventListener' )}( ${local.name}, '${eventName}', ${handler} );
` );
}

function getBindingEventName ( node ) {
if ( node.name === 'input' ) {
const typeAttribute = node.attributes.find( attr => attr.type === 'Attribute' && attr.name === 'type' );
const type = typeAttribute ? typeAttribute.value[0].data : 'text'; // TODO in validation, should throw if type attribute is not static

return type === 'checkbox' || type === 'radio' ? 'change' : 'input';
}

if ( node.name === 'textarea' ) {
return 'input';
}

return 'change';
}

function getBindingValue ( local, node, attribute, isMultipleSelect ) {
if ( isMultipleSelect ) {
return `[].map.call( ${local.name}.selectedOptions, function ( option ) { return option.__value; })`;
}

if ( node.name === 'select' ) {
return 'selectedOption && selectedOption.__value';
}

return `${local.name}.${attribute.name}`;
}
34 changes: 34 additions & 0 deletions src/generators/dom/visitors/attributes/binding/getSetter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import deindent from '../../../../../utils/deindent.js';

export default function getSetter ({ current, name, context, attribute, dependencies, snippet, value }) {
if ( name in current.contexts ) {
const prop = dependencies[0];
const tail = attribute.value.type === 'MemberExpression' ? getTailSnippet( attribute.value ) : '';

return deindent`
var list = this.${context}.${current.listNames[ name ]};
var index = this.${context}.${current.indexNames[ name ]};
list[index]${tail} = ${value};
component._set({ ${prop}: component.get( '${prop}' ) });
`;
}

if ( attribute.value.type === 'MemberExpression' ) {
return deindent`
var ${name} = component.get( '${name}' );
${snippet} = ${value};
component._set({ ${name}: ${name} });
`;
}

return `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}✂]`;
}
Loading

0 comments on commit 0f9ef05

Please sign in to comment.