|
|
import deindent from '../../../utils/deindent.js';
|
|
|
import CodeBuilder from '../../../utils/CodeBuilder.js';
|
|
|
import visit from '../visit.js';
|
|
|
import addComponentAttributes from './attributes/addComponentAttributes.js';
|
|
|
|
|
|
function capDown ( name ) {
|
|
|
return `${name[0].toLowerCase()}${name.slice( 1 )}`;
|
|
|
}
|
|
|
|
|
|
function stringifyProps ( props ) {
|
|
|
if ( !props.length ) return '{}';
|
|
|
|
|
|
const joined = props.join( ', ' );
|
|
|
if ( joined.length > 40 ) {
|
|
|
// make larger data objects readable
|
|
|
return `{\n\t${props.join( ',\n\t' )}\n}`;
|
|
|
}
|
|
|
|
|
|
return `{ ${joined} }`;
|
|
|
}
|
|
|
|
|
|
export default function visitComponent ( generator, fragment, node ) {
|
|
|
const hasChildren = node.children.length > 0;
|
|
|
const name = fragment.getUniqueName( capDown( node.name === ':Self' ? generator.name : node.name ) );
|
|
|
|
|
|
const local = {
|
|
|
name,
|
|
|
namespace: fragment.namespace,
|
|
|
isComponent: true,
|
|
|
|
|
|
allUsedContexts: [],
|
|
|
|
|
|
create: new CodeBuilder(),
|
|
|
update: new CodeBuilder()
|
|
|
};
|
|
|
|
|
|
const isToplevel = fragment.localElementDepth === 0;
|
|
|
|
|
|
generator.hasComponents = true;
|
|
|
|
|
|
addComponentAttributes( generator, fragment, node, local );
|
|
|
|
|
|
if ( local.allUsedContexts.length ) {
|
|
|
const initialProps = local.allUsedContexts.map( contextName => {
|
|
|
if ( contextName === 'root' ) return `root: root`;
|
|
|
|
|
|
const listName = fragment.listNames.get( contextName );
|
|
|
const indexName = fragment.indexNames.get( contextName );
|
|
|
|
|
|
return `${listName}: ${listName},\n${indexName}: ${indexName}`;
|
|
|
}).join( ',\n' );
|
|
|
|
|
|
const updates = local.allUsedContexts.map( contextName => {
|
|
|
if ( contextName === 'root' ) return `${name}._context.root = root;`;
|
|
|
|
|
|
const listName = fragment.listNames.get( contextName );
|
|
|
const indexName = fragment.indexNames.get( contextName );
|
|
|
|
|
|
return `${name}._context.${listName} = ${listName};\n${name}._context.${indexName} = ${indexName};`;
|
|
|
}).join( '\n' );
|
|
|
|
|
|
local.create.addBlock( deindent`
|
|
|
${name}._context = {
|
|
|
${initialProps}
|
|
|
};
|
|
|
` );
|
|
|
|
|
|
local.update.addBlock( updates );
|
|
|
}
|
|
|
|
|
|
const componentInitProperties = [
|
|
|
`target: ${!isToplevel ? fragment.target: 'null'}`,
|
|
|
`_root: ${fragment.component}._root || ${fragment.component}`
|
|
|
];
|
|
|
|
|
|
// Component has children, put them in a separate {{yield}} block
|
|
|
if ( hasChildren ) {
|
|
|
const yieldName = generator.getUniqueName( `render_${name}_yield_fragment` );
|
|
|
const params = fragment.params.join( ', ' );
|
|
|
|
|
|
const childFragment = this.current.child({
|
|
|
type: 'component',
|
|
|
name: generator.getUniqueName( `render_${name}_yield_fragment` ), // TODO should getUniqueName happen inside Fragment? probably
|
|
|
target: 'target',
|
|
|
localElementDepth: 0
|
|
|
});
|
|
|
|
|
|
node.children.forEach( child => {
|
|
|
visit( generator, childFragment, child );
|
|
|
});
|
|
|
|
|
|
const yieldFragment = fragment.getUniqueName( `${name}_yield_fragment` );
|
|
|
|
|
|
fragment.builders.create.addLine(
|
|
|
`var ${yieldFragment} = ${yieldName}( ${params}, ${fragment.component} );`
|
|
|
);
|
|
|
|
|
|
fragment.builders.update.addLine(
|
|
|
`${yieldFragment}.update( changed, ${params} );`
|
|
|
);
|
|
|
|
|
|
componentInitProperties.push( `_yield: ${yieldFragment}`);
|
|
|
}
|
|
|
|
|
|
const statements = [];
|
|
|
|
|
|
if ( local.staticAttributes.length || local.dynamicAttributes.length || local.bindings.length ) {
|
|
|
const initialProps = local.staticAttributes
|
|
|
.concat( local.dynamicAttributes )
|
|
|
.map( attribute => `${attribute.name}: ${attribute.value}` );
|
|
|
|
|
|
const initialPropString = stringifyProps( initialProps );
|
|
|
|
|
|
if ( local.bindings.length ) {
|
|
|
const initialData = fragment.getUniqueName( `${name}_initial_data` );
|
|
|
|
|
|
statements.push( `var ${name}_initial_data = ${initialPropString};` );
|
|
|
|
|
|
local.bindings.forEach( binding => {
|
|
|
statements.push( `if ( ${binding.prop} in ${binding.obj} ) ${initialData}.${binding.name} = ${binding.value};` );
|
|
|
});
|
|
|
|
|
|
componentInitProperties.push( `data: ${initialData}` );
|
|
|
} else if ( initialProps.length ) {
|
|
|
componentInitProperties.push( `data: ${initialPropString}` );
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const expression = node.name === ':Self' ? generator.name : generator.importedComponents.get( node.name ) || `${generator.alias( 'template' )}.components.${node.name}`;
|
|
|
|
|
|
local.create.addBlockAtStart( deindent`
|
|
|
${statements.join( '\n' )}
|
|
|
var ${name} = new ${expression}({
|
|
|
${componentInitProperties.join(',\n')}
|
|
|
});
|
|
|
` );
|
|
|
|
|
|
if ( isToplevel ) {
|
|
|
fragment.builders.mount.addLine( `${name}._fragment.mount( target, anchor );` );
|
|
|
}
|
|
|
|
|
|
if ( local.dynamicAttributes.length ) {
|
|
|
const updates = local.dynamicAttributes.map( attribute => {
|
|
|
if ( attribute.dependencies.length ) {
|
|
|
return deindent`
|
|
|
if ( ${attribute.dependencies.map( dependency => `'${dependency}' in changed` ).join( '||' )} ) ${name}_changes.${attribute.name} = ${attribute.value};
|
|
|
`;
|
|
|
}
|
|
|
|
|
|
// TODO this is an odd situation to encounter – I *think* it should only happen with
|
|
|
// each block indices, in which case it may be possible to optimise this
|
|
|
return `${name}_changes.${attribute.name} = ${attribute.value};`;
|
|
|
});
|
|
|
|
|
|
local.update.addBlock( deindent`
|
|
|
var ${name}_changes = {};
|
|
|
|
|
|
${updates.join( '\n' )}
|
|
|
|
|
|
if ( Object.keys( ${name}_changes ).length ) ${name}.set( ${name}_changes );
|
|
|
` );
|
|
|
}
|
|
|
|
|
|
fragment.builders.destroy.addLine( `${name}.destroy( ${isToplevel ? 'detach' : 'false'} );` );
|
|
|
|
|
|
fragment.builders.create.addBlock( local.create );
|
|
|
if ( !local.update.isEmpty() ) fragment.builders.update.addBlock( local.update );
|
|
|
} |