Let's say I want to create a multi-color picker with svelte, maybe to let the user choose a foreground color and a background color. My data model that looks like this:
{
foreground: {
r: 100,g:100,b:100
},
background: {
r: 200,g:200,b:200
}
};
So my app.js is
import AppUI from './App.html';
import { Store } from 'svelte/store.js';
const defaultData = {
foreground: {
r: 100,g:100,b:100
},
background: {
r: 200,g:200,b:200
}
};
const store = new Store(defaultData);
window.store = store; // useful for debugging!
store.onchange(() => console.log('something changed'));
var app = new AppUI({
target: document.querySelector( '#main' ),
store
});
export default app;
Then I can build an RGBSelector
component to reuse:
<input type="range" min=0 max=255 step=1 bind:value=data.r/>{{data.r}}
<input type="range" min=0 max=255 step=1 bind:value=data.g/>{{data.g}}
<input type="range" min=0 max=255 step=1 bind:value=data.b/>{{data.b}}
And my App.html
is pretty simple:
foreground:
<RGBSelector bind:data=$foreground/>
background:
<RGBSelector bind:data=$background/>
<script>
import RGBSelector from './RGBSelector.html';
export default {
components: {
RGBSelector
}
};
</script>
This seems to work, mostly. The two-way binding in the range inputs is working (the labels update), and the store is even being updated (verified by inspecting store._state
in the console). So I believe the bind
keywords in the RGBSelector
are passing the change up to where they're declared in the App
, which in turn is bind
ing them to the store.
Trouble is, the store.onchange
handler is not firing. Can anyone see what I'm doing wrong?
Full example: https://glitch.com/edit/#!/nonstop-hourglass
This is a bug in Svelte, not your app! It turns out component bindings don't play nicely with store
— the bind:data=$foreground
is just updating $foreground
in your <App>
component rather than updating foreground
in your store.
Tracking the issue here: https://github.com/sveltejs/svelte/issues/1100
There isn't a great workaround sadly — until we get this fixed you would need to do something like this:
foreground: <RGBSelector bind:data=foreground/>
background: <RGBSelector bind:data=background/>
text: <Textinput bind:value=text/>
<script>
import RGBSelector from './RGBSelector.html';
import Textinput from './Textinput.html';
export default {
components: {
RGBSelector, Textinput
},
oncreate() {
this.observe('foreground', foreground => {
this.store.set({ foreground });
});
this.observe('background', background => {
this.store.set({ background });
});
this.observe('text', text => {
this.store.set({ text });
});
}
};
</script>
And in your JS file, this:
var app = new App({
target: document.body,
data: defaultData,
store
});
If changes can go both ways, you'd also need to observe the store properties, taking care to prevent infinite update loops:
// inside `oncreate` — would also need to do this
// for `background` and `text`
let foregroundUpdating = false;
this.observe('foreground', foreground => {
if (foregroundUpdating) return;
foregroundUpdating = true;
this.store.set({ foreground });
foregroundUpdating = false;
});
this.store.observe('foreground', foreground => {
if (foregroundUpdating) return;
foregroundUpdating = true;
this.set({ foreground });
foregroundUpdating = false;
});
Recreating the functionality of bindings like this is obviously a bit cumbersome, so we'll try and get this bug fixed soon.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With