2020-04-24

Svelte Tutorial 8: Stores (English & Indonesia)

English

Stores


Not all application state belongs inside your application's component hierarchy. Sometimes, you'll have values that need to be accessed by multiple unrelated components, or by a regular JavaScript module.

Writable Stores


In Svelte, we do this with stores. A store is simply an object with a subscribe method that allows interested parties to be notified whenever the store value changes. Below is an example where, count is a store, and we're setting count_value in the count.subscribe callback.

let count_value;

const unsubscribe = count.subscribe(value => {
  count_value = value;
});

This is the definition of count. It's a writable store, which means it has set and update methods in addition to subscribe.

import { writable } from 'svelte/store';
export const count = writable(0);

count can be updated or set like this:

function increment() { count.update(n => n + 1); }
function reset() { count.set(0); }

Full example can be seen here.


Auto-Subscriptions


The app in the previous example works, but there's a subtle bug — the unsubscribe function never gets called. If the component was instantiated and destroyed many times, this would result in a memory leak.

One way to fix it would be to use the onDestroy lifecycle hook:

<script>
  import { onDestroy } from 'svelte';
  import { count } from './stores.js';
  import Incrementer from './Incrementer.svelte';
  import Decrementer from './Decrementer.svelte';
  import Resetter from './Resetter.svelte';

  let count_value;
  const unsubscribe = count.subscribe(value => {
    count_value = value;
  });

  onDestroy(unsubscribe);
</script>

<h1>The count is {count_value}</h1>

It starts to get a bit boilerplatey though, especially if your component subscribes to multiple stores. Instead, Svelte has a trick up its sleeve — with autosubscribe means you can reference a store value by prefixing the store name with $ (you don't need to manually do onDestroy too, it's automatic):

<script>
  import { count } from './stores.js';
  import Incrementer from './Incrementer.svelte';
  import Decrementer from './Decrementer.svelte';
  import Resetter from './Resetter.svelte';
</script>

<h1>The count is {$count}</h1>

You can see the difference in first and second example, much simpler by using $. Auto-subscription only works with store variables that are declared (or imported) at the top-level scope of a component.

You're not limited to using $count inside the markup, either — you can use it anywhere in the <script> as well, such as in event handlers or reactive declarations.

Any name beginning with $ is assumed to refer to a store value. It's effectively a reserved character — Svelte will prevent you from declaring your own variables with a $ prefix.

Readable Stores


Not all stores should be writable by whoever has a reference to them. For example, you might have a store representing the mouse position or the user's geolocation, and it doesn't make sense to be able to set those values from 'outside'. For those cases, we have readable stores.

Create a stores.js file. The first argument to readable is an initial value, which can be null or undefined if you don't have one yet. The second argument is a start function that takes a set callback and returns a  stop function. The start function is called when the store gets its first subscriber; stop is called when the last subscriber unsubscribes.

// this is stores.js
import { readable } from 'svelte/store';

export const time = readable(new Date(), function start(set) { 
  // called at subscribe
  const interval = setInterval(() => {
    set(new Date());
  }, 1000);

  return function stop() { // called when unsubscribe
    clearInterval(interval);
  };
});

Then we can use time in App.svelte:

<script>
import { time } from './stores.js';

const formatter = new Intl.DateTimeFormat('en', {
hour12: true,
hour: 'numeric',
minute: '2-digit',
second: '2-digit'
});
</script>

<h1>The time is {formatter.format($time)}</h1>


Derived Stores


You can create a store whose value is based on the value of one or more other stores with derived. Building on our previous example, we can create a store that derives the time the page has been open:

export const elapsed = derived(
  time,
  $time => Math.round(($time - start) / 1000)
);

It's possible to derive a store from multiple inputs, and to explicitly set a value instead of returning it (which is useful for deriving values asynchronously). Consult the API reference for more information. Full example can be seen here.

Custom Stores


As long as an object correctly implements the subscribe method, it's a store. Beyond that, anything goes. It's very easy, therefore, to create custom stores with domain-specific logic.

For example, the count store from our earlier example could include incrementdecrement and reset methods and avoid exposing set and update:

function createCount() {
  const { subscribe, set, update } = writable(0);
  
  return {
    subscribe,
    increment: () => update(n => n + 1),
    decrement: () => update(n => n - 1),
    reset: () => set(0)
  };
}

And here is how we're gonna use it in main app:

<script> import { count } from './stores.js'; </script>

<h1>The count is {$count}</h1>
<button on:click={count.increment}>+</button>
<button on:click={count.decrement}>-</button>
<button on:click={count.reset}>reset</button>

Stores Binding


If a store is writable — i.e. it has a set method — you can bind to its value, just as you can bind to local component state. In this example we have a writable store name and a derived store greeting.

import { writable, derived } from 'svelte/store';

export const name = writable('world');
export const greeting = derived(
  name,
  $name => `Hello ${$name}!`
);

Changing the input value will now update name and all its dependents. We can also assign directly to store values inside a component. Add a <button> element:

<script> import { name, greeting } from './stores.js'; </script>

<h1>{$greeting}</h1>
<input bind:value={$name}>
<button on:click="{() => $name+='!'}">
  Add exclamation mark!
</button>

The $name+='!' assignment is equivalent to name.set($name+'!').




Indonesia

Stores


Tidak semua nilai dalam aplikasi berada di dalam hirarki komponen. Kadang, kita akan mempunyai suatu nilai yang perlu diakses oleh beberapa komponen yang tidak berhubungan, atau bisa juga oleh modul JavaScript biasa.

Writable Stores


Di svelte, kita bisa menggunakan store untuk keperluan ini. Store adalah suatu object dengan method subscribe yang memungkinkan bagian yang membutuhkan untuk mendapat notifikasi saat nilai dari store itu berubah. Di bawah ini adalah contoh store, count dan kita akan mengatur nilai dari count_value pada callback count.subscribe.

let count_value;

const unsubscribe = count.subscribe(value => {
  count_value = value;
});

Di bawah ini adalah definisi dari count. Yang merupakan writable store, berarti count memiliki method set dan update selain subscribe.

import { writable } from 'svelte/store';
export const count = writable(0);

ini contoh cara update atau set count:

function increment() { count.update(n => n + 1); }
function reset() { count.set(0); }

Contoh lengkap bisa dilihat di sini.

Auto-Subscriptions


Aplikasi di contoh sebelumnya akan berjalan, tapi ada sedikit kekurangan — fungsi unsubscribe tidak pernah dijalankan. Jika komponen itu akan di instantiate dan di destroy berulang kali, akan ada memori yang bocor.

Salah satu cara untuk memperbaikinya adalah dengan menggunakan pancingan lifecycle onDestroy :

<script>
  import { onDestroy } from 'svelte';
  import { count } from './stores.js';
  import Incrementer from './Incrementer.svelte';
  import Decrementer from './Decrementer.svelte';
  import Resetter from './Resetter.svelte';

  let count_value;
  const unsubscribe = count.subscribe(value => {
    count_value = value;
  });

  onDestroy(unsubscribe);
</script>

<h1>The count is {count_value}</h1>

Tapi programnya mulai terlihat sedikit ribet, terutama jika komponen itu melakukan subscribe ke banyak store. Maka, svelte sudah menyediakan satu kemudahan — dengan autosubscribe, yang berarti kita bisa langsung melakukan referensi ke suatu store dengan memberikan awalan nama store dengan $ (maka kita tidak perlu lagi melakukan unsubscribe di onDestroy):

<script>
  import { count } from './stores.js';
  import Incrementer from './Incrementer.svelte';
  import Decrementer from './Decrementer.svelte';
  import Resetter from './Resetter.svelte';
</script>

<h1>The count is {$count}</h1>

Kita bisa liat perbedaan dari contoh pertama dan kedua, akan jauh lebih mudah dengan menggunakan $. Auto subscription hanya bekerja dengan variabel store yang dideklarasikan (atau di import) pada bagian atas dari scope suatu komponen.

Kita juga tidak terbatas untuk menggunakan $count hanya di dalam markup, kita bisa gunakan di dalam  <script> juga, seperti di event handle atau deklarasi reactive.

Semua nama yang berawalan dengan $ akan dianggap mengacu pada nilai store. Maka karakter ini adalah karakter reserved  — Svelte akan mencegah kita deklarasi variabel dengan awalan  $.

Readable Stores


Tidak semua store perlu bisa writable. Sebagai contoh , kita bisa mempunyai store yang menyimpan posisi mouse, atau geolokasi, maka akan tidak masuk akal jika nilai store itu mau dirubah-rubah. Untuk kasus seperti itu ada readable store, store yang hanya readonly.

Pertama buat file stores.js . Argumen pertama untuk readable adalah inisialisasi awal, bisa berisi null atau undefined jika memang belum ada. Argumen kedua adalah fungsi start yang akan menerima callback set dan me return fungsi  stop. Fungsi start akan dijalankan ketika store mendapat subsriber pertamanya, dan stop akan dijalankan saat subscriber terakhir unsubscribe.

// this is stores.js
import { readable } from 'svelte/store';

export const time = readable(new Date(), function start(set) { 
  // called at subscribe
  const interval = setInterval(() => {
    set(new Date());
  }, 1000);

  return function stop() { // called when unsubscribe
    clearInterval(interval);
  };
});

Sesudah itu kita bisa menggunakan komponen time di dalam App.svelte:

<script>
import { time } from './stores.js';

const formatter = new Intl.DateTimeFormat('en', {
hour12: true,
hour: 'numeric',
minute: '2-digit',
second: '2-digit'
});
</script>

<h1>The time is {formatter.format($time)}</h1>

Derived Stores


Kita bisa membuat store yang nilainya berdasarkan dari nilai satu atau lebih store dengan derived. Melanjutkan dari contoh sebelumnya, kita bisa membuat store yang akan menghitung waktu berapa lama halaman aplikasi sudah dijalankan:

export const elapsed = derived(
  time,
  $time => Math.round(($time - start) / 1000)
);

Kita bisa men derive store dari berbagai input, dan secara eksplisit memberikan nilai daripada me return nya (biasanya digunakan untuk derive sesuatu secara asinkron). Cek  API reference untuk informasi lebih lanjut. Contoh lengkap bisa dilihat di sini.

Custom Stores


Selama suatu object menjalankan method subscribe dengan benar, maka dia adalah store. Maka dari itu kita bisa dengan mudah membuat store custom dengan logika yang spesifik untuk domain tertentu.

Sebagai contoh store count dari contoh sebelumnya bisa menambakan method incrementdecrement dan reset sehingga tidak perlu mengekspos set dan update pada saat penggunaan komponen:

function createCount() {
  const { subscribe, set, update } = writable(0);
  
  return {
    subscribe,
    increment: () => update(n => n + 1),
    decrement: () => update(n => n - 1),
    reset: () => set(0)
  };
}

Kemudian bisa digunakan di aplikasi utama seperti ini:

<script> import { count } from './stores.js'; </script>

<h1>The count is {$count}</h1>
<button on:click={count.increment}>+</button>
<button on:click={count.decrement}>-</button>
<button on:click={count.reset}>reset</button>

Stores Binding


Jika suatu store writable — mempunyai method set — kita bisa melakukan bind pada nilainya, seperti kita bisa melakukan bind pada state komponen lokal. Pada contoh berikut kita memiliki store writable  name dan derived store greeting.

import { writable, derived } from 'svelte/store';

export const name = writable('world');
export const greeting = derived(
  name,
  $name => `Hello ${$name}!`
);

Maka mengubah nilai input sekarang akan meng update name dan semua dependensinya. Kita juga bisa melakukan assign nilai secara langsung pada store di dalam komponen. Tambahkan elemen <button> :

<script> import { name, greeting } from './stores.js'; </script>

<h1>{$greeting}</h1>
<input bind:value={$name}>
<button on:click="{() => $name+='!'}">
  Add exclamation mark!
</button>

Proses $name+='!' akan menghasilkan sesuatu yang sama dengan name.set($name+'!').

2020-04-23

Svelte Tutorial 7: Lifecycle (English & Indonesia)

English


Lifecycle


Every component has a lifecycle that starts when it is created, and ends when it is destroyed. There are a handful of functions that allow you to run code at key moments during that lifecycle.

onMount


The one you'll use most frequently is onMount, which runs after the component is first rendered to the DOM. We briefly encountered it  when we needed to interact with a <canvas> element after it had been rendered. We'll add an onMount handler that loads some data over the network:

<script>
  import { onMount } from 'svelte';
  let photos = [];
  onMount(async () => {
    const res = await fetch(`https://jsonplaceholder.typicode.com/photos?_limit=20`);
    photos = await res.json();
  });
</script>

It's recommended to put the fetch in onMount rather than at the top level of the <script> because of server-side rendering (SSR). With the exception of onDestroy, lifecycle functions don't run during SSR, which means we can avoid fetching data that should be loaded lazily once the component has been mounted in the DOM.

Lifecycle functions must be called while the component is initialising so that the callback is bound to the component instance — not (say) in a setTimeout.

If the onMount callback returns a function, that function will be called when the component is destroyed.

onDestroy


To run code when your component is destroyed, use onDestroy. For example, we can add a setInterval function when our component initialises, and clean it up when it's no longer relevant. Doing so prevents memory leaks.

<script>

  import { onDestroy } from 'svelte';
  let seconds = 0;
  const interval = setInterval(() => seconds += 1, 1000);
  onDestroy(() => clearInterval(interval));
</script>

While it's important to call lifecycle functions during the component's initialisation, it doesn't matter where you call them from. So if we wanted, we could abstract the interval logic into a helper function in util.js...

import { onDestroy } from 'svelte';
export function onInterval(callback, milliseconds) {
 const interval = setInterval(callback, milliseconds);
  onDestroy(() => {
  clearInterval(interval);
 });
}

then import it into our component:

<script>
 import { onInterval } from './utils.js';
 let seconds = 0;
 onInterval(() => seconds += 1, 1000);
</script>

beforeUpdate and afterUpdate


The beforeUpdate function schedules work to happen immediately before the DOM has been updated. afterUpdate is its counterpart, used for running code once the DOM is in sync with your data. Together, they're useful for doing things imperatively that are difficult to achieve in a purely state-driven way, like updating the scroll position of an element.

let div;
let autoscroll;
beforeUpdate(() => {
  autoscroll = div && (div.offsetHeight + div.scrollTop) > (div.scrollHeight - 20);
});
afterUpdate(() => {
  if (autoscroll) div.scrollTo(0, div.scrollHeight);
});

Note that beforeUpdate will first run before the component has mounted, so we need to check for the existence of div before reading its properties. See working example here.

Tick


The tick function is unlike other lifecycle functions in that you can call it any time, not just when the component first initialises. It returns a promise that resolves as soon as any pending state changes have been applied to the DOM (or immediately, if there are no pending state changes).

When you invalidate component state in Svelte, it doesn't update the DOM immediately. Instead, it waits until the next microtask to see if there are any other changes that need to be applied, including in other components. Doing so avoids unnecessary work and allows the browser to batch things more effectively.

You can see that behaviour in this example. Select a range of text and hit the tab key. Because the <textarea> value changes, the current selection is cleared and the cursor jumps, annoyingly, to the end. We can fix this by importing tick... ...and running it immediately before we set this.selectionStart and .selectionEnd at the end of handleKeydown:

<script>
  import { tick } from 'svelte';
  let text = `Select some text and hit the tab key to toggle uppercase`;

  async function handleKeydown(event) {
    if (event.which !== 9) return;
    event.preventDefault();

    const { selectionStart, selectionEnd, value } = this;
    const selection = value.slice(selectionStart, selectionEnd);

    const replacement = /[a-z]/.test(selection)
      ? selection.toUpperCase()
      : selection.toLowerCase();

    text = (
      value.slice( 0, 
        selectionStart) + replacement + value.slice(selectionEnd));

    await tick();  // update textarea value first
                   // then put selection in place
    this.selectionStart = selectionStart;
    this.selectionEnd = selectionEnd;
  }
</script>

<style> textarea { width: 100%; height: 200px; } </style>

<textarea value={text} on:keydown={handleKeydown}></textarea>




Indonesia


Lifecycle


Setiap komponen mempunyai lifecycle yang dimulai ketika komponen itu dicreate, dan berakhir ketika komponen didestroy. Ada beberapa fungsi yang berguna untuk memungkinkan kita menjalankan program pada saat tertentu selama lifecycle.

onMount


onMount adalah fungsi yang paling sering digunakan, fungsi ini akan berjalan saat komponen pertama kali dirender ke dalam DOM. Kita sempat menggunakan onMount saat melakukan interaksi dengan elemen  <canvas> sesudah dirender. Kita akan menambahkan handler onMount yang akan melakukan loading data melalui jaringan:

<script>
  import { onMount } from 'svelte';
  let photos = [];
  onMount(async () => {
    const res = await fetch(`https://jsonplaceholder.typicode.com/photos?_limit=20`);
    photos = await res.json();
  });
</script>

Alangkah baiknya jika kita meletakkan fetch di onMount dan bukan di baris atas <script> hal ini dikarenakan proses server-side rendering (SSR). Dengan perkecualian untuk onDestroy, fungsi lifecycle tidak berjalan saat SSR, maka kita bisa menghindari fetch data yang bisa diload belakangan kita komponen sudah termount pada DOM.

Fungsi lifecycle harus dipanggil saat komponen dalam proses pembentukan jadi callback nya akan terikat pada komponen itu  — jangan (misalnya) dalam setTimeout.

Jika callback onMount mereturn suatu fungsi, fungsi tersebut akan dipanggil saat komponen di destroy.

onDestroy


Untuk menjalankan program saat komponen di destroy, gunakan onDestroy. Sebagai contoh, kita bisa menambakan fungsi setInterval ketika komponen dibentuk, dan membersihkan ketika interval sudah tidak lagi dibutuhkan. Hal ini untuk mencegah kehabisan memori.

<script>

  import { onDestroy } from 'svelte';
  let seconds = 0;
  const interval = setInterval(() => seconds += 1, 1000);
  onDestroy(() => clearInterval(interval));
</script>

Meskipun penting untuk memanggil fungsi lifecycle selama masa pembentukan komponen. Tapi tidak masalah kita memanggil dari mana. Jadi jika kita mau, kita bisa melakukan proses logika interval  dalam fungsi helper di dalam util.js...

import { onDestroy } from 'svelte';
export function onInterval(callback, milliseconds) {
 const interval = setInterval(callback, milliseconds);
  onDestroy(() => {
  clearInterval(interval);
 });
}

kemudian diimporkan ke dalam komponen:

<script>
 import { onInterval } from './utils.js';
 let seconds = 0;
 onInterval(() => seconds += 1, 1000);
</script>


beforeUpdate and afterUpdate


Fungsi beforeUpdate menjadwalkan kerja program untuk terjadi segera sebelum DOM nya ter update. afterUpdate adalah kebalikannya, digunakan untuk menjalankan program, setelah DOM sudah tersinkronisasi dengan data dalam program. Bersama-sama, kedua fungsi ini berguna untuk melakukan hal-hal yang susah dilakukan dalam cara yang murni berhubungan dengan state, seperti mengupdate posisi scroll suatu elemen.

let div;
let autoscroll;
beforeUpdate(() => {
  autoscroll = div && (div.offsetHeight + div.scrollTop) > (div.scrollHeight - 20);
});
afterUpdate(() => {
  if (autoscroll) div.scrollTo(0, div.scrollHeight);
});

Jangan lupa bahwa beforeUpdate akan dijalankan duluan sebelum komponen sudah ter mount, jadi kita harus melakukan pengecekan keberadaan div, sebelum mengecek properti nya. Contoh aplikasi yang sudah berjalan bisa dilihat di sini.

Tick


Fungsi tick tidak seperti fungsi lifecycle yang lainnya, dalam hal fungsi ini bisa dipanggil kapan saja, bukan hanya saat komponen pertama terbentuk. Fungsi ini akan mereturn promise yang akan cair saat semua perubahan state yang masih pending sudah diterapkan pada DOM (atau segera, jika tidak ada perubahan state yang pending).

Ketika terjadi invalidasi state komponen, perubahan DOM tidak dilakukan langsung. Melainkan menunggu beberapa perubahan kecil lain untuk melihat apabila ada beberapa perubahan yang bisa dilakukan bersamaan, termasuk perubahan di komponen lain. Hal ini dimaksudkan untuk menghindari pekerjaan tambahan dan juga membantui browser untuk bekerja lebih efektif.

Kita bisa melihat kelakukan itu di contoh ini. Pilih sekumpulan teks dan pencet tombol tab. Karena nilai <textarea> berubah, pilihan teks akan hilang dan kursor akan lompat ke bagian akhir, sangat mengganggu.. Kita bisa memperbaiki ini dengan menggunakan tick... dan langsung menjalankan secara langsung sebelum kita mengatur this.selectionStart dan this.selectionEnd di bagian akhir handleKeydown:

<script>
  import { tick } from 'svelte';
  let text = `Select some text and hit the tab key to toggle uppercase`;

  async function handleKeydown(event) {
    if (event.which !== 9) return;
    event.preventDefault();

    const { selectionStart, selectionEnd, value } = this;
    const selection = value.slice(selectionStart, selectionEnd);

    const replacement = /[a-z]/.test(selection)
      ? selection.toUpperCase()
      : selection.toLowerCase();

    text = (
      value.slice( 0, 
        selectionStart) + replacement + value.slice(selectionEnd));

    await tick();  // tick akan update textarea dulu
                   // baru selection akan diset ulang
    this.selectionStart = selectionStart;
    this.selectionEnd = selectionEnd;
  }
</script>

<style> textarea { width: 100%; height: 200px; } </style>

<textarea value={text} on:keydown={handleKeydown}></textarea>



Svelte Tutorial 6: Bindings (English & Indonesia)

English


Bindings

As a general rule, data flow in Svelte is top down — a parent component can set props on a child component, and a component can set attributes on an element, but not the other way around. Bindings is useful way in svelte to keep all value of variable or attribute that is bound together to stay the same. Here is type of bindings we can use.

Text Input


If we want to bind value of text input we can use something like <input bind:value={name}>. Below is some code example:

<script>
let name = 'world';
</script>

<input bind:value={name}>

<h1>Hello {name}!</h1>

Numeric Input


In the DOM, everything is a string. That's unhelpful when you're dealing with numeric inputs — type=number and type=range — as it means you have to remember to coerce input.value before using it.

With bind:value, Svelte takes care of it for you:

<script>
let a = 1;
let b = 2;
</script>

<label>
<input type=number bind:value={a} min=0 max=10>
<input type=range bind:value={a} min=0 max=10>
</label>

<label>
<input type=number bind:value={b} min=0 max=10>
<input type=range bind:value={b} min=0 max=10>
</label>

<p>{a} + {b} = {a + b}</p>

Checkbox Input


Checkboxes are used for toggling between states. Instead of binding to input.value, we bind to input.checked:

<input type=checkbox bind:checked={yes}>

Group Input


If you have multiple inputs relating to the same value, you can use bind:group along with the value attribute. Radio inputs in the same group are mutually exclusive; checkbox inputs in the same group form an array of selected values.

Add bind:group to each input. Here is some example:

<script>
let scoops = 1;
let flavours = ['Mint choc chip'];

let menu = [
'Cookies and cream',
'Mint choc chip',
'Raspberry ripple'
];

function join(flavours) {
if (flavours.length === 1) return flavours[0];
return `${flavours.slice(0, -1).join(', ')} and ${flavours[flavours.length - 1]}`;
}
</script>

<h2>Size</h2>

<label>
<input type=radio bind:group={scoops} value={1}>
One scoop
</label>

<label>
<input type=radio bind:group={scoops} value={2}>
Two scoops
</label>

<label>
<input type=radio bind:group={scoops} value={3}>
Three scoops
</label>

<h2>Flavours</h2>

{#each menu as flavour}
<label>
<input type=checkbox bind:group={flavours} value={flavour}>
{flavour}
</label>
{/each}

{#if flavours.length === 0}
<p>Please select at least one flavour</p>
{:else if flavours.length > scoops}
<p>Can't order more flavours than scoops!</p>
{:else}
<p>
You ordered {scoops} {scoops === 1 ? 'scoop' : 'scoops'}
of {join(flavours)}
</p>
{/if}

Textarea Input


The <textarea> element behaves similarly to a text input in Svelte — use bind:value=:

<textarea bind:value={value}></textarea>

In cases like these, where the names match, we can also use a shorthand form:

<textarea bind:value></textarea>

This applies to all bindings, not just textareas.

Select Binding


We can also use bind:value= with <select> elements.

<script>
  let questions = [
    { id: 1, text: `Where did you go to school?` },
    { id: 2, text: `What is your mother's name?` },
    { id: 3, text: `What is another personal fact that an attacker could easily find with Google?` }
  ];
  let selected;
  let answer = '';
  function handleSubmit() {
    alert(`answered question ${selected.id} (${selected.text}) with "${answer}"`);
  }
</script>
<style>
  input { display: block; width: 500px; max-width: 100%; }
</style>

<h2>Insecurity questions</h2>

<form on:submit|preventDefault={handleSubmit}>
  <select bind:value={selected} on:change="{() => answer = ''}">
  <!-- ^^^^ select binding here ^^^^ -->
    {#each questions as question}
      <option value={question}>{question.text}</option>
    {/each}
  </select>
  <input bind:value={answer}>
  <button disabled={!answer} type=submit>Submit</button>
</form>

<p>selected question {selected ? selected.id : '[waiting...]'}</p>

Note that the <option> values are objects rather than strings. Svelte doesn't mind.

Because we haven't set an initial value of selected, the binding will set it to the default value (the first in the list) automatically. Be careful though — until the binding is initialised, selected remains undefined, so we can't blindly reference e.g. selected.id in the template.

Select Multiple


A select can have a multiple attribute, in which case it will populate an array rather than selecting a single value.

Returning to our ice cream example in group binding , we can replace the checkboxes with a <select multiple>:

<h2>Flavours</h2>

<select multiple bind:value={flavours}>
{#each menu as flavour}
<option value={flavour}>
{flavour}
</option>
{/each}
</select>

Contenteditable Binding


Elements with a contenteditable="true" attribute support bind:textContent and bind:innerHTML bindings:

<script>
  let html = '<p>Write some text!</p>';
</script>

<div
  contenteditable="true"
  bind:innerHTML={html}  
></div> <!-- ^ innerHTML binding -->

<pre>{html}</pre>
<style>
  [contenteditable] {
    padding: 0.5em;
    border: 1px solid #eee;
    border-radius: 4px;
  }
</style>

Without inner HTML bind in above example, changes in contenteditable wont automatically update text inside <pre> tag.

Each Block Binding


You can even bind to properties inside an each block.

<script>
  let todos = [
    { done: false, text: 'finish Svelte tutorial' },
    { done: false, text: 'build an app' },
    { done: false, text: 'world domination' }
  ];

  function add() {
    todos = todos.concat({ done: false, text: '' });
  }

  function clear() {
    todos = todos.filter(t => !t.done);
  }

$: remaining = todos.filter(t => !t.done).length;
</script>

<style> .done { opacity: 0.4; </style>

<h1>Todos</h1>
{#each todos as todo}
  <div class:done={todo.done}>
    <input
      type=checkbox
      bind:checked={todo.done}  <!-- each binding -->
    >
    <input
      placeholder="What needs to be done?"
      bind:value={todo.text}    <!-- each binding -->
    >
  </div>
{/each}

<p>{remaining} remaining</p>
<button on:click={add}> Add new </button>
<button on:click={clear}> Clear completed </button>

The point is in each-block binding, every member of array is bound with every component in each-block.

Media Element


The <audio> and <video> elements have several properties that you can bind to. This example demonstrates a few of them.

<video
  poster="https://sveltejs.github.io/assets/caminandes-llamigos.jpg"
  src="https://sveltejs.github.io/assets/caminandes-llamigos.mp4"
  on:mousemove={handleMousemove}
  on:mousedown={handleMousedown}
  bind:currentTime={time}
  bind:duration
  bind:paused
></video>

Remember that bind:duration is equivalent to bind:duration={duration}. Now, when you click on the video, it will update timeduration and paused as appropriate. This means we can use them to build custom controls.

The complete set of bindings for <audio> and <video> is as follows — six readonly bindings...
  • duration (readonly) — the total duration of the video, in seconds
  • buffered (readonly) — an array of {start, end} objects
  • seekable (readonly) — ditto
  • played (readonly) — ditto
  • seeking (readonly) — boolean
  • ended (readonly) — boolean
...and four two-way bindings:
  • currentTime — the current point in the video, in seconds
  • playbackRate — how fast to play the video, where 1 is 'normal'
  • paused — this one should be self-explanatory
  • volume — a value between 0 and 1
Videos additionally have readonly videoWidth and videoHeight bindings. For complete interactive example of Media-Element binding you can check it here.

Dimensions


Every block-level element has clientWidthclientHeightoffsetWidth and offsetHeight bindings:

<script>
  let w;
  let h;
  let size = 42;
  let text = 'edit me';
</script>
<style>
  input { display: block; }
  div { display: inline-block; }
  span { word-break: break-all; }
</style>

<input type=range bind:value={size}>
<input bind:value={text}>
<p>size: {w}px x {h}px</p>

<div bind:clientWidth={w} bind:clientHeight={h}>
  <span style="font-size: {size}px">{text}</span>
</div>

In the above example when we slide the slider it's automatically variable size, because we already bind the value with size variable. Then when size of text change, width and height of div element also automatically change, and bind with variable w and h respectively, so the text <p>size: {w}px x {h}px</p> also got updated.


This


The readonly this binding applies to every element (and component) and allows you to obtain a reference to rendered elements. For example, we can get a reference to a <canvas> element:

<canvas
  bind:this={canvas}
  width={32}
  height={32}
></canvas>

Note that the value of canvas will be undefined until the component has mounted, so we put the logic inside the onMount life cycle function. You can check complete interactive example here.

Component Bindings


Just as you can bind to properties of DOM elements, you can bind to component props. For example, we can bind to the value prop of this <Keypad> custom component as though it were a form element:

<Keypad bind:value={pin} on:submit={handleSubmit}/>

Now, when the user interacts with the keypad, the value of pin in the parent component is immediately updated. Check complete example of app to enter pin number and alert the result here.
Don't forget to update keypad component with bind:value={pin} to see the correct result.

Use component bindings sparingly. It can be difficult to track the flow of data around your application if you have too many of them, especially if there is no 'single source of truth'.




Indonesia


Bindings


Aturan umum di Svelte adalah data flow akan terjadi secara top down — komponen parent bisa mengatur properti di komponen child, dan sebuah komponen bisa mengatur atribut dalam suatu elemen, tapi TIDAK sebaliknya. Binding adalah suatu cara yang berguna untuk memastikan semua nilai variabel atau atribut terikat untuk selalu sama. Berikut adalah tipe-tipe binding yang bisa kita gunakan.

Text Input


Jika kita ingin melakukan bind pada isi dari input teks kita bisa menggunakan seperti ini <input bind:value={name}>. Berikut adalah contoh program:

<script>
let name = 'world';
</script>

<input bind:value={name}>

<h1>Hello {name}!</h1>

Numeric Input


Pada DOM, semua dianggap string, hal ini merpersulit jika kita berurusan dengan input numerik — type=number dan type=range — karena ini berarti kita harus mengolah  input.value sebelum bisa menggunakannya.

Dengan bind:value, Svelte yang akan menyelesaikan urusan itu:

<script>
let a = 1;
let b = 2;
</script>

<label>
<input type=number bind:value={a} min=0 max=10>
<input type=range bind:value={a} min=0 max=10>
</label>

<label>
<input type=number bind:value={b} min=0 max=10>
<input type=range bind:value={b} min=0 max=10>
</label>

<p>{a} + {b} = {a + b}</p>

Checkbox Input


Checkbox digunakan untuk melakukan toggle antara true atau false. Untuk melakukan binding pada checkbox kita tidak menggunakan input.value, tetapi kita melakukan bind ke input.checked:

<input type=checkbox bind:checked={yes}>

Group Input


Jika kita memiliki beberapa input yang berhubungan dengan nilai yang sama, kita bisa menggunakan bind:group bersamaan dengan atribut value. Input radio pada grup yang sama tidak bisa dipilih bersamaan (mutually exclusive); sementara input checkbox pada grup yang sama akan membentuk array dari nilai-nilai yang dipilih.

Tambahkan bind:group pada masing-masing input. Berikut adalah contohnya:

<script>
let scoops = 1;
let flavours = ['Mint choc chip'];

let menu = [
'Cookies and cream',
'Mint choc chip',
'Raspberry ripple'
];

function join(flavours) {
if (flavours.length === 1) return flavours[0];
return `${flavours.slice(0, -1).join(', ')} and ${flavours[flavours.length - 1]}`;
}
</script>

<h2>Size</h2>

<label>
<input type=radio bind:group={scoops} value={1}>
One scoop
</label>

<label>
<input type=radio bind:group={scoops} value={2}>
Two scoops
</label>

<label>
<input type=radio bind:group={scoops} value={3}>
Three scoops
</label>

<h2>Flavours</h2>

{#each menu as flavour}
<label>
<input type=checkbox bind:group={flavours} value={flavour}>
{flavour}
</label>
{/each}

{#if flavours.length === 0}
<p>Please select at least one flavour</p>
{:else if flavours.length > scoops}
<p>Can't order more flavours than scoops!</p>
{:else}
<p>
You ordered {scoops} {scoops === 1 ? 'scoop' : 'scoops'}
of {join(flavours)}
</p>
{/if}

Textarea Input


Elemen <textarea> mempunya sifat mirip dengan input teks — gunakan bind:value= seperti berikut:

<textarea bind:value={value}></textarea>

Pada kasus seperti ini dimana nama variabel sama dengan nama atribut kita bisa menuliskan dengan cara singkat:

<textarea bind:value></textarea>

Cara menyingkat ini berlaku pada semua elemen bukan textarea saja.

Select Binding


Kita juga bisa menggunakan bind:value= dengan elemen <select> .

<script>
  let questions = [
    { id: 1, text: `Where did you go to school?` },
    { id: 2, text: `What is your mother's name?` },
    { id: 3, text: `What is another personal fact that an attacker could easily find with Google?` }
  ];
  let selected;
  let answer = '';
  function handleSubmit() {
    alert(`answered question ${selected.id} (${selected.text}) with "${answer}"`);
  }
</script>
<style>
  input { display: block; width: 500px; max-width: 100%; }
</style>

<h2>Insecurity questions</h2>

<form on:submit|preventDefault={handleSubmit}>
  <select bind:value={selected} on:change="{() => answer = ''}">
  <!-- ^^^^ select binding here ^^^^ -->
    {#each questions as question}
      <option value={question}>{question.text}</option>
    {/each}
  </select>
  <input bind:value={answer}>
  <button disabled={!answer} type=submit>Submit</button>
</form>

<p>selected question {selected ? selected.id : '[waiting...]'}</p>

Perhatikan bahwa nilai dari  <option> merupakan object dan bukan string. Svelte bisa melakukan ini secara otomatis.

Karena kita belum melakukan pengaturan nilai awal dari selected, proses binding akan memberikan nilai default (yang pertama yang ada di daftar) secara otomatis. Akan tetapi kita harus berhati-hati — sampai proses binding dijalankan, selected akan tetap bernilai undefined, jadi kita tidak bisa menggunakan contohnya selected.id pada template.

Select Multiple


Sebuah elemen select bisa memiliki atribut multiple, di mana pada kasus ini, nilainya akan tersimpan pada array, dan bukan satu variabel saja.

Kembali pada contoh app ice cream pada binding group, kita bisa menggantik checkbox dengan <select multiple>:

<h2>Flavours</h2>

<select multiple bind:value={flavours}>
{#each menu as flavour}
<option value={flavour}>
{flavour}
</option>
{/each}
</select>

Contenteditable Binding


Elemen dengan atribut contenteditable="true" mendukung binding bind:textContent dan bind:innerHTML :

<script>
  let html = '<p>Write some text!</p>';
</script>

<div
  contenteditable="true"
  bind:innerHTML={html}  
></div> <!-- ^ innerHTML binding -->

<pre>{html}</pre>
<style>
  [contenteditable] {
    padding: 0.5em;
    border: 1px solid #eee;
    border-radius: 4px;
  }
</style>

Pada contoh di atas, tanpa menggunakan bind, perubahan di contenteditable tidak akan meng update teks di dalam tag <pre> secara otomatis.

Each Block Binding


Kita juga bisa melakukan bind pada property di dalam blok each.

<script>
  let todos = [
    { done: false, text: 'finish Svelte tutorial' },
    { done: false, text: 'build an app' },
    { done: false, text: 'world domination' }
  ];

  function add() {
    todos = todos.concat({ done: false, text: '' });
  }

  function clear() {
    todos = todos.filter(t => !t.done);
  }

$: remaining = todos.filter(t => !t.done).length;
</script>

<style> .done { opacity: 0.4; </style>

<h1>Todos</h1>
{#each todos as todo}
  <div class:done={todo.done}>
    <input
      type=checkbox
      bind:checked={todo.done}  <!-- each binding -->
    >
    <input
      placeholder="What needs to be done?"
      bind:value={todo.text}    <!-- each binding -->
    >
  </div>
{/each}

<p>{remaining} remaining</p>
<button on:click={add}> Add new </button>
<button on:click={clear}> Clear completed </button>

Hal utama yang terjadi pada binding di blok each, setiap anggota array akan terikat dengan setiap komponen di dalam blok each.

Media Element


Elemen <audio> dan <video> mempunyai beberapa elemen yang bisa kita bind. Berikut adalah beberapa contoh nya.

<video
  poster="https://sveltejs.github.io/assets/caminandes-llamigos.jpg"
  src="https://sveltejs.github.io/assets/caminandes-llamigos.mp4"
  on:mousemove={handleMousemove}
  on:mousedown={handleMousedown}
  bind:currentTime={time}
  bind:duration
  bind:paused
></video>

Jangan lupa bahwa bind:duration sama dengan bind:duration={duration}. Sekarang, ketika kita mengklik video, kita akan mengupdate timeduration dan paused sesuai yang kita pilih. Kita juga bisa memanfaatkan bind untuk membuat custom control untuk video.

Daftar binding lengkap untuk <audio> dan <video> adalah sebagai berikut — 6 binding readonly...
  • duration (readonly) — durasi total video dalam detik
  • buffered (readonly) — array dari object {start, end}
  • seekable (readonly) — ditto
  • played (readonly) — ditto
  • seeking (readonly) — boolean
  • ended (readonly) — boolean

...dan empat binding 2 arah:
  • currentTime — titik waktu pada video, dalam detik
  • playbackRate — kecepatan menjalankan video,  1 adalah 'normal'
  • paused — sedang terpause atau tidak
  • volume — bernilai antara 0 sampai 1

Video mempunyai tambahan readonly binding videoWidth dan videoHeight. Untuk contoh interaktif Elemen Media yang lebih lengkap bisa dilihat di sini.

Dimensions


Setiap elemen blok memiliki binding clientWidthclientHeightoffsetWidth dan offsetHeight:

<script>
  let w;
  let h;
  let size = 42;
  let text = 'edit me';
</script>
<style>
  input { display: block; }
  div { display: inline-block; }
  span { word-break: break-all; }
</style>

<input type=range bind:value={size}>
<input bind:value={text}>
<p>size: {w}px x {h}px</p>

<div bind:clientWidth={w} bind:clientHeight={h}>
  <span style="font-size: {size}px">{text}</span>
</div>

Pada contoh di atas saat kita menggeser slider maka nilai variabel akan otomatis berubah, karena kita sudah melakukan bind value dengan variabel size. Kemudian ketika ukuran tulisan berubah, lebar dan tinggi dari elemen div juga akan otomatis berubah, dan berlanjut dengan bind ke variabel w and h , maka teks <p>size: {w}px x {h}px</p> juga ikut terupdate.

This


Binding readonly this berlaku pada semua elemen (dan komponen) dan memungkinkan kita untuk mendapatkan referensi untuk elemen yang sedang dirender. Sebagai contoh, kita bisa mendapatkan referensi untuk elemen <canvas>:

<canvas
  bind:this={canvas}
  width={32}
  height={32}
></canvas>

Perhatikan bahwa nilai dari canvas akan tetap undefined sampai komponen sudah ter mount, maka kita meletakkan proses logika di dalam siklus fungsi life cycle onMount . Contoh interaktif lengkap bisa dilihat di sini.

Component Bindings


Sama halnya dengan kita bisa melakukan bind pada properti elemen DOM, kita juga bisa melakukan bind pada properti komponen. Contohnya kita bisa melakukan bind pada properti  value pada komponen custom <Keypad> seperti pada elemen form:

<Keypad bind:value={pin} on:submit={handleSubmit}/>

Sekarang, ketika user berinteraksi dengan keypad, maka nilai dari pin pada komponen parentakan langsung terupdate. Contoh interaktif lengkap bisa dilihat di sini.

Usahakan tidak menggunakan binding komponen terlalu sering, karena bisa mengakibatkan program sulit untuk dipaham jika terlalu banyak, terutama jika tidak ada satu sumber data yang pasti.