Merge branch 'master' into gh-465

pull/468/head
Rich Harris 8 years ago committed by GitHub
commit 79b5ce090e

@ -45,7 +45,7 @@ export default function visitEventHandler ( generator, block, state, node, attri
// get a name for the event handler that is globally unique // get a name for the event handler that is globally unique
// if hoisted, locally unique otherwise // if hoisted, locally unique otherwise
const handlerName = shouldHoist ? const handlerName = shouldHoist ?
generator.alias( `${name}_handler` ) : generator.getUniqueName( `${name}_handler` ) :
block.getUniqueName( `${name}_handler` ); block.getUniqueName( `${name}_handler` );
// create the handler body // create the handler body

@ -1,5 +1,6 @@
import flattenReference from '../../../../../utils/flattenReference.js'; import flattenReference from '../../../../../utils/flattenReference.js';
import deindent from '../../../../../utils/deindent.js'; import deindent from '../../../../../utils/deindent.js';
import CodeBuilder from '../../../../../utils/CodeBuilder.js';
const associatedEvents = { const associatedEvents = {
innerWidth: 'resize', innerWidth: 'resize',
@ -13,6 +14,7 @@ const associatedEvents = {
export default function visitWindow ( generator, block, node ) { export default function visitWindow ( generator, block, node ) {
const events = {}; const events = {};
const bindings = {};
node.attributes.forEach( attribute => { node.attributes.forEach( attribute => {
if ( attribute.type === 'EventHandler' ) { if ( attribute.type === 'EventHandler' ) {
@ -40,17 +42,22 @@ export default function visitWindow ( generator, block, node ) {
} }
if ( attribute.type === 'Binding' ) { if ( attribute.type === 'Binding' ) {
if ( attribute.value.type !== 'Identifier' ) {
const { parts, keypath } = flattenReference( attribute.value );
throw new Error( `Bindings on <:Window/> must be to top-level properties, e.g. '${parts.pop()}' rather than '${keypath}'` );
}
bindings[ attribute.name ] = attribute.value.name;
// bind:online is a special case, we need to listen for two separate events
if ( attribute.name === 'online' ) return;
const associatedEvent = associatedEvents[ attribute.name ]; const associatedEvent = associatedEvents[ attribute.name ];
if ( !associatedEvent ) { if ( !associatedEvent ) {
throw new Error( `Cannot bind to ${attribute.name} on <:Window>` ); throw new Error( `Cannot bind to ${attribute.name} on <:Window>` );
} }
if ( attribute.value.type !== 'Identifier' ) {
const { parts, keypath } = flattenReference( attribute.value );
throw new Error( `Bindings on <:Window/> must be to top-level properties, e.g. '${parts.pop()}' rather than '${keypath}'` );
}
if ( !events[ associatedEvent ] ) events[ associatedEvent ] = []; if ( !events[ associatedEvent ] ) events[ associatedEvent ] = [];
events[ associatedEvent ].push( `${attribute.value.name}: this.${attribute.name}` ); events[ associatedEvent ].push( `${attribute.value.name}: this.${attribute.name}` );
@ -61,16 +68,31 @@ export default function visitWindow ( generator, block, node ) {
} }
}); });
const lock = block.getUniqueName( `window_updating` );
Object.keys( events ).forEach( event => { Object.keys( events ).forEach( event => {
const handlerName = block.getUniqueName( `onwindow${event}` ); const handlerName = block.getUniqueName( `onwindow${event}` );
const props = events[ event ].join( ',\n' ); const props = events[ event ].join( ',\n' );
const handlerBody = new CodeBuilder();
if ( event === 'scroll' ) { // TODO other bidirectional bindings...
block.builders.create.addLine( `var ${lock} = false;` );
handlerBody.addLine( `${lock} = true;` );
}
handlerBody.addBlock( deindent`
${block.component}.set({
${props}
});
` );
if ( event === 'scroll' ) {
handlerBody.addLine( `${lock} = false;` );
}
block.builders.create.addBlock( deindent` block.builders.create.addBlock( deindent`
var ${handlerName} = function ( event ) { function ${handlerName} ( event ) {
${block.component}.set({ ${handlerBody}
${props}
});
}; };
window.addEventListener( '${event}', ${handlerName} ); window.addEventListener( '${event}', ${handlerName} );
` ); ` );
@ -79,4 +101,52 @@ export default function visitWindow ( generator, block, node ) {
window.removeEventListener( '${event}', ${handlerName} ); window.removeEventListener( '${event}', ${handlerName} );
` ); ` );
}); });
// special case... might need to abstract this out if we add more special cases
if ( bindings.scrollX && bindings.scrollY ) {
const observerCallback = block.getUniqueName( `scrollobserver` );
block.builders.create.addBlock( deindent`
function ${observerCallback} () {
if ( ${lock} ) return;
var x = ${bindings.scrollX ? `component.get( '${bindings.scrollX}' )` : `window.scrollX`};
var y = ${bindings.scrollY ? `component.get( '${bindings.scrollY}' )` : `window.scrollY`};
window.scrollTo( x, y );
};
` );
if ( bindings.scrollX ) block.builders.create.addLine( `component.observe( '${bindings.scrollX}', ${observerCallback} );` );
if ( bindings.scrollY ) block.builders.create.addLine( `component.observe( '${bindings.scrollY}', ${observerCallback} );` );
} else if ( bindings.scrollX || bindings.scrollY ) {
const isX = !!bindings.scrollX;
block.builders.create.addBlock( deindent`
component.observe( '${bindings.scrollX || bindings.scrollY}', function ( ${isX ? 'x' : 'y'} ) {
if ( ${lock} ) return;
window.scrollTo( ${isX ? 'x, window.scrollY' : 'window.scrollX, y' } );
});
` );
}
// another special case. (I'm starting to think these are all special cases.)
if ( bindings.online ) {
const handlerName = block.getUniqueName( `onlinestatuschanged` );
block.builders.create.addBlock( deindent`
function ${handlerName} ( event ) {
component.set({ ${bindings.online}: navigator.onLine });
};
window.addEventListener( 'online', ${handlerName} );
window.addEventListener( 'offline', ${handlerName} );
` );
// add initial value
generator.builders.metaBindings.addLine(
`this._state.${bindings.online} = navigator.onLine;`
);
block.builders.destroy.addBlock( deindent`
window.removeEventListener( 'online', ${handlerName} );
window.removeEventListener( 'offline', ${handlerName} );
` );
}
} }

@ -0,0 +1,36 @@
export default {
data: {
foo: [ 1 ],
bar: [ 2 ],
clicked: 'neither'
},
html: `
<button>foo</button>
<button>bar</button>
<p>clicked: neither</p>
`,
test ( assert, component, target, window ) {
const buttons = target.querySelectorAll( 'button' );
const event = new window.MouseEvent( 'click' );
buttons[0].dispatchEvent( event );
assert.equal( component.get( 'clicked' ), 'foo' );
assert.htmlEqual( target.innerHTML, `
<button>foo</button>
<button>bar</button>
<p>clicked: foo</p>
` );
buttons[1].dispatchEvent( event );
assert.equal( component.get( 'clicked' ), 'bar' );
assert.htmlEqual( target.innerHTML, `
<button>foo</button>
<button>bar</button>
<p>clicked: bar</p>
` );
component.destroy();
}
};

@ -0,0 +1,9 @@
{{#each foo as f}}
<button on:click='set({ clicked: "foo" })'>foo</button>
{{/each}}
{{#each bar as b}}
<button on:click='set({ clicked: "bar" })'>bar</button>
{{/each}}
<p>clicked: {{clicked}}</p>

@ -0,0 +1,12 @@
export default {
skip: true, // JSDOM
test ( assert, component, target, window ) {
assert.equal( window.scrollY, 0 );
component.set({ scrollY: 100 });
assert.equal( window.scrollY, 100 );
component.destroy();
}
};

@ -0,0 +1,3 @@
<:Window bind:scrollY/>
<div style='width: 100%; height: 9999px;'></div>
Loading…
Cancel
Save