Skip to content

Commit 79b5ce0

Browse files
authored
Merge branch 'master' into gh-465
2 parents 91a58a0 + eaf5b6b commit 79b5ce0

File tree

6 files changed

+141
-11
lines changed

6 files changed

+141
-11
lines changed

src/generators/dom/visitors/Element/EventHandler.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export default function visitEventHandler ( generator, block, state, node, attri
4545
// get a name for the event handler that is globally unique
4646
// if hoisted, locally unique otherwise
4747
const handlerName = shouldHoist ?
48-
generator.alias( `${name}_handler` ) :
48+
generator.getUniqueName( `${name}_handler` ) :
4949
block.getUniqueName( `${name}_handler` );
5050

5151
// create the handler body

src/generators/dom/visitors/Element/meta/Window.js

+80-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import flattenReference from '../../../../../utils/flattenReference.js';
22
import deindent from '../../../../../utils/deindent.js';
3+
import CodeBuilder from '../../../../../utils/CodeBuilder.js';
34

45
const associatedEvents = {
56
innerWidth: 'resize',
@@ -13,6 +14,7 @@ const associatedEvents = {
1314

1415
export default function visitWindow ( generator, block, node ) {
1516
const events = {};
17+
const bindings = {};
1618

1719
node.attributes.forEach( attribute => {
1820
if ( attribute.type === 'EventHandler' ) {
@@ -40,17 +42,22 @@ export default function visitWindow ( generator, block, node ) {
4042
}
4143

4244
if ( attribute.type === 'Binding' ) {
45+
if ( attribute.value.type !== 'Identifier' ) {
46+
const { parts, keypath } = flattenReference( attribute.value );
47+
throw new Error( `Bindings on <:Window/> must be to top-level properties, e.g. '${parts.pop()}' rather than '${keypath}'` );
48+
}
49+
50+
bindings[ attribute.name ] = attribute.value.name;
51+
52+
// bind:online is a special case, we need to listen for two separate events
53+
if ( attribute.name === 'online' ) return;
54+
4355
const associatedEvent = associatedEvents[ attribute.name ];
4456

4557
if ( !associatedEvent ) {
4658
throw new Error( `Cannot bind to ${attribute.name} on <:Window>` );
4759
}
4860

49-
if ( attribute.value.type !== 'Identifier' ) {
50-
const { parts, keypath } = flattenReference( attribute.value );
51-
throw new Error( `Bindings on <:Window/> must be to top-level properties, e.g. '${parts.pop()}' rather than '${keypath}'` );
52-
}
53-
5461
if ( !events[ associatedEvent ] ) events[ associatedEvent ] = [];
5562
events[ associatedEvent ].push( `${attribute.value.name}: this.${attribute.name}` );
5663

@@ -61,16 +68,31 @@ export default function visitWindow ( generator, block, node ) {
6168
}
6269
});
6370

71+
const lock = block.getUniqueName( `window_updating` );
72+
6473
Object.keys( events ).forEach( event => {
6574
const handlerName = block.getUniqueName( `onwindow${event}` );
66-
6775
const props = events[ event ].join( ',\n' );
6876

77+
const handlerBody = new CodeBuilder();
78+
if ( event === 'scroll' ) { // TODO other bidirectional bindings...
79+
block.builders.create.addLine( `var ${lock} = false;` );
80+
handlerBody.addLine( `${lock} = true;` );
81+
}
82+
83+
handlerBody.addBlock( deindent`
84+
${block.component}.set({
85+
${props}
86+
});
87+
` );
88+
89+
if ( event === 'scroll' ) {
90+
handlerBody.addLine( `${lock} = false;` );
91+
}
92+
6993
block.builders.create.addBlock( deindent`
70-
var ${handlerName} = function ( event ) {
71-
${block.component}.set({
72-
${props}
73-
});
94+
function ${handlerName} ( event ) {
95+
${handlerBody}
7496
};
7597
window.addEventListener( '${event}', ${handlerName} );
7698
` );
@@ -79,4 +101,52 @@ export default function visitWindow ( generator, block, node ) {
79101
window.removeEventListener( '${event}', ${handlerName} );
80102
` );
81103
});
104+
105+
// special case... might need to abstract this out if we add more special cases
106+
if ( bindings.scrollX && bindings.scrollY ) {
107+
const observerCallback = block.getUniqueName( `scrollobserver` );
108+
109+
block.builders.create.addBlock( deindent`
110+
function ${observerCallback} () {
111+
if ( ${lock} ) return;
112+
var x = ${bindings.scrollX ? `component.get( '${bindings.scrollX}' )` : `window.scrollX`};
113+
var y = ${bindings.scrollY ? `component.get( '${bindings.scrollY}' )` : `window.scrollY`};
114+
window.scrollTo( x, y );
115+
};
116+
` );
117+
118+
if ( bindings.scrollX ) block.builders.create.addLine( `component.observe( '${bindings.scrollX}', ${observerCallback} );` );
119+
if ( bindings.scrollY ) block.builders.create.addLine( `component.observe( '${bindings.scrollY}', ${observerCallback} );` );
120+
} else if ( bindings.scrollX || bindings.scrollY ) {
121+
const isX = !!bindings.scrollX;
122+
123+
block.builders.create.addBlock( deindent`
124+
component.observe( '${bindings.scrollX || bindings.scrollY}', function ( ${isX ? 'x' : 'y'} ) {
125+
if ( ${lock} ) return;
126+
window.scrollTo( ${isX ? 'x, window.scrollY' : 'window.scrollX, y' } );
127+
});
128+
` );
129+
}
130+
131+
// another special case. (I'm starting to think these are all special cases.)
132+
if ( bindings.online ) {
133+
const handlerName = block.getUniqueName( `onlinestatuschanged` );
134+
block.builders.create.addBlock( deindent`
135+
function ${handlerName} ( event ) {
136+
component.set({ ${bindings.online}: navigator.onLine });
137+
};
138+
window.addEventListener( 'online', ${handlerName} );
139+
window.addEventListener( 'offline', ${handlerName} );
140+
` );
141+
142+
// add initial value
143+
generator.builders.metaBindings.addLine(
144+
`this._state.${bindings.online} = navigator.onLine;`
145+
);
146+
147+
block.builders.destroy.addBlock( deindent`
148+
window.removeEventListener( 'online', ${handlerName} );
149+
window.removeEventListener( 'offline', ${handlerName} );
150+
` );
151+
}
82152
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
export default {
2+
data: {
3+
foo: [ 1 ],
4+
bar: [ 2 ],
5+
clicked: 'neither'
6+
},
7+
8+
html: `
9+
<button>foo</button>
10+
<button>bar</button>
11+
<p>clicked: neither</p>
12+
`,
13+
14+
test ( assert, component, target, window ) {
15+
const buttons = target.querySelectorAll( 'button' );
16+
const event = new window.MouseEvent( 'click' );
17+
18+
buttons[0].dispatchEvent( event );
19+
assert.equal( component.get( 'clicked' ), 'foo' );
20+
assert.htmlEqual( target.innerHTML, `
21+
<button>foo</button>
22+
<button>bar</button>
23+
<p>clicked: foo</p>
24+
` );
25+
26+
buttons[1].dispatchEvent( event );
27+
assert.equal( component.get( 'clicked' ), 'bar' );
28+
assert.htmlEqual( target.innerHTML, `
29+
<button>foo</button>
30+
<button>bar</button>
31+
<p>clicked: bar</p>
32+
` );
33+
34+
component.destroy();
35+
}
36+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{{#each foo as f}}
2+
<button on:click='set({ clicked: "foo" })'>foo</button>
3+
{{/each}}
4+
5+
{{#each bar as b}}
6+
<button on:click='set({ clicked: "bar" })'>bar</button>
7+
{{/each}}
8+
9+
<p>clicked: {{clicked}}</p>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export default {
2+
skip: true, // JSDOM
3+
4+
test ( assert, component, target, window ) {
5+
assert.equal( window.scrollY, 0 );
6+
7+
component.set({ scrollY: 100 });
8+
assert.equal( window.scrollY, 100 );
9+
10+
component.destroy();
11+
}
12+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<:Window bind:scrollY/>
2+
3+
<div style='width: 100%; height: 9999px;'></div>

0 commit comments

Comments
 (0)