Binding
Nessa página aprenderemos um importantíssimo recurso do svelte. O “binding” em campos de formulários. Em qualquer site que visitemos sempre vamos encontrar uma opção de interagir, que é através dos formulários, enviando mensagens, realizando pesquisas e outros mais.
Input de texto
Inputs de texto são muito comuns em páginas de cadastro, login, etc. Mas primeiro, vamos entender como é o fluxo de dados no svelte. Vejamos o que diz o site oficial:
Como regra geral, o fluxo de dados no Svelte é de cima para baixo — um componente pai pode definir props em um componente filho e um componente pode definir atributos em um elemento, mas não o contrário. Documentação
Para entendermos melhor, veja o exemplo abaixo:
<!-- App.svelte -->
<script>
let text = 'Bom dia';
</script>
<input value={text} />
Aqui, o componente “App.svelte” está definindo um valor para o elemento “input”. É assim que funciona.
Mas, esse regra pode ser “quebrada”, ou seja, o “input” pode definir o valor da variável text
do componente.
Fazemos isso usando a diretiva bind
.
<!-- App.svelte -->
<script>
let text = 'Bom dia';
</script>
<input bind:value={text} />
<p>{text}</p>
Veja em ação:

O exemplo acima mostrou como podemos lidar com inputs de texto usando a diretiva bind
. Vamos ver como usamos essa diretiva com outros tipos de campos de formulário.
Antes de mostrar os tipos de input de formulário, é preciso que você entenda uma coisa muito importante na hora de usar o bind
.
Veja o que diz a documentação do svelte.
Se você estiver usando diretivas
bind:
junto com diretivason:
, a ordem em que elas são definidas afetará o valor da variável associada quando o manipulador de eventos for chamado. Documentação
Veja no exemplo abaixo:
<script>
let value = '';
</script>
<input bind:value on:input="{() => console.log(value)}" />

Caso você tenha um problema com atraso de valor, isso pode ser na ordem em que foi definido as diretivas. Coisa simples de resolver.
Input numérico
Vamos escrever um código que calcula a soma baseado no valor digitado pelo usuário.
<script>
let value1 = 5
let sum = 0;
function handleChange(event) {
sum = value1 + event.target.value
}
</script>
<p>A soma de {value1} + <input type="number" on:change={handleChange} /> é {sum}</p>
Observe o resultado:

Viu o que aconteceu? A função handleChange
não somou os valores, mas sim concatenou pois event.target.value
é uma string.
Para resolver esse problema é muito simples, basta converter a string para número.

Mas o svelte resolve isso com a diretiva bind
. Não precisamos nos preocupar com esse conversão.
Transformando o código acima para um código mais “sveltico”, fica assim.
<script>
let value1 = 5;
let value = 0;
$: sum = value + value1
</script>
<!-- Deixei a variável "value" com o mesmo nome da propriedade "value"
para deixar o código mais curto. Poderia ser assim:
<p>A soma de {value1} + <input type="number" bind:value={value} /> é {sum}</p> -->
<p>A soma de {value1} + <input type="number" bind:value /> é {sum}</p>
E tudo funciona perfeitamente.
Input checkbox
Outro campo de formulário muito utilizado. Para lidar com o estado entre “marcado” e “desmarcado”, o svelte lida com a propriedade checked
, ao invés da propriedade value
.
<script>
let iAgree = false
</script>
<label>
<input type="checkbox" bind:checked={iAgree} />
Eu concordo com os termos de uso dessa página.
</label>
<br/>
<div>
...
</div>
{#if iAgree}
<p>Parabéns, agora clique no botão abaixo.</p>
{:else}
<p>Você precisa concordar com os termos de uso da página para continuar</p>
{/if}
<button disabled={!iAgree}>Continuar</button>
<style>
div {
max-height: 100px;
max-width: 300px;
overflow-y: auto;
border: 1px solid;
font-size: 1.2rem;
padding: 1rem;
}
</style>

Se quiser criar um grupo de checkbox relacionados, use a diretiva bind:group
. Inputs do tipo checkbox no mesmo grupo formam um array com os valores selecionados.
<script>
let orders = []
function handleSubmit(e) {
const formData = new FormData(e.target);
let data = []
formData.forEach(value => data = [...data, value])
alert(`As opções escolhidas foram:\n${data.join('\n')}`)
}
</script>
<form on:submit|preventDefault={handleSubmit}>
<label>
<input type="checkbox" bind:group={orders} name="orders" value="Água com gás"/>
Água com gás
</label>
<label>
<input type="checkbox" bind:group={orders} name="orders" value="Porção de peixe com batata frita"/>
Porção de peixe com batata frita
</label>
<label>
<input type="checkbox" bind:group={orders} name="orders" value="Suco natural de limão"/>
Suco natural de limão
</label>
<button type="submit">Enviar</button>
</form>

Textarea
O bind em elementos textarea funciona de forma similar ao input text.
<script>
let value = 'Um input de texto, só que grande...';
</script>
<textarea bind:value rows={5} cols={50} />
Select
Em elementos select, o svelte faz o bind na “propriedade” value ao invés de “selected”. Fique atento a isso.
Devemos fazer <select bind:value={selected}>
ao invés de <select bind:selected={selected}>
Veja abaixo:
<script>
let carBrands = ['Ford', 'Fiat', 'GM'];
let selectedBrand;
let cars = [
{id: 1, brand: 'Ford', model: 'F-150'},
{id: 2, brand: 'Ford', model: 'F-250'},
{id: 3, brand: 'Fiat', model: 'Toro'},
{id: 4, brand: 'GM', model: 'S-10'},
]
let availableCars = []
function handleChange() {
availableCars = cars.filter(car => car.brand === selectedBrand )
}
</script>
<select bind:value={selectedBrand} on:change={handleChange}>
<option hidden>Selecione uma marca</option>
{#each carBrands as brand}
<option value={brand}>{brand}</option>
{/each}
</select>
<h4>Os modelos disponíveis para essa marca são: </h4>
{#each availableCars as {id, model} (id)}
<p>{model}</p>
{/each}

Select múltiplo
Quando utilizamos um select do tipo múltiplo, o value
será um array de valores ao invés de um valor único.
<script>
let ninjas = ['Naruto', 'Uchiha Itachi', 'Uchiha Sasuke', 'Maito Guy', 'Pain'];
let favorites = [];
$: pruralOrSingular = favorites.length > 1 ? 'Suas escolhas foram' : 'Sua escolha foi';
</script>
<h3>Quais seus personagens favoritos? (Selecione 1 ou mais)</h3>
<select bind:value={favorites} multiple>
{#each ninjas as ninja}
<option value={ninja}>{ninja}</option>
{/each}
</select>
{#if favorites.length > 0}
<h3>{pruralOrSingular}</h3>
<ul>
{#each favorites as favorite}
<li>{favorite}</li>
{/each}
</ul>
{/if}

Input radio
Diferentemente do input checkbox, o input radio não armazena um array com os valores selecionados, mas sim um valor exclusivo, ou seja, os inputs que estiverem no mesmo grupo vão definir um valor único. É por isso que quando você seleciona 1, os outros ficam desmarcados.
<script>
let nations = ['Brasil', 'Argentina', 'Outro'];
let whichNation = '';
$: if(whichNation === 'Argentina') {
alert('Você não vai torcer pra Argentina aqui...')
whichNation = 'Brasil'
}
</script>
<h4>Pra qual país vai sua torcida hoje?</h4>
{#each nations as nation}
<label>
<input type="radio" bind:group={whichNation} name="nations" value={nation} />
{nation}
</label>
{/each}
Um detalhe é que a variável whichNation
está com um valor vazio. Podemos definir um valor para ela e isso fará com que o input que tiver o mesmo valor fique marcado por padrão.
Veja o exemplo abaixo.

Input range
Esse tipo de input é muito comum em lojas virtuais onde você pode selecionar uma faixa de preços e exibir apenas os items que estão dentro desses valores.
Fiz um exemplo bem simples mas que pode te ajudar a entender como utilizar o poder do svelte para criar componentes dinâmicos.
<script>
let clothes = [
{id: 1, title: 'Camisa azul', price: 109.90},
{id: 2, title: 'Bermuda jeans', price: 81.90},
{id: 3, title: 'Chinelo rider', price: 74.90},
{id: 4, title: 'Boné nike', price: 119.90},
]
// Filtrei a lista pra criar uma lista de preços e usar as funções matemáticas min e max.
let prices = clothes.map(item => item.price);
let min = Math.min(...prices)
let max = Math.max(...prices)
// Fiz a média dos valores para deixar o slider no meio.
// Pra deixar no início: let value = min;
// Pra deixar no final: let value = max;
let value = (min + max) / 2
// Defini um valor padrão para a lista ao iniciar o componente. Retorna uma cópia de "clothes".
let filteredList = clothes.map(item => item)
function handleChange() {
filteredList = clothes.filter(item => item.price <= value)
}
</script>
<h2>Selecione a faixa de preço: De R$ {min} a {max}</h2>
<label class="range">
<input type="range" {min} {max} bind:value on:change={handleChange} />
Valor: R$ {value}
</label>
<div class="container">
{#each filteredList as {id, title, price} (id)}
<div class="container__item">
<h4>{title}</h4>
<span>R$ {price.toFixed(2)}</span>
</div>
{/each}
</div>
<style>
.range {
display: flex;
gap: 1rem;
}
.container {
display: flex;
gap: .5rem;
}
.container__item {
background-color: #AED6F1;
padding: 1rem;
}
.container__item h4 {
margin: 0;
}
</style>

Nada do que fiz acima foge do que aprendemos até agora. Se ficou com alguma dúvida, volte nas páginas anteriores.
Input file
Para fazer upload de um arquivo o processo é um pouco diferente. Usamos a diretiva bind:files
. Nesse caso, tem que ser exatamente assim, pois essa variável files
vai armazenar o arquivo ou lista de arquivos selecionados.
Se tiver apenas um input do tipo file, faça assim:
<script>
let files;
</script>
<input type="file" bind:files />
Se for mais de um, você pode fazer assim:
<script>
// Use qualquer nome de variável que desejar.
let image1, image2, image3;
</script>
<input type="file" bind:files={image1} />
<input type="file" bind:files={image2} />
<input type="file" bind:files={image3} />
E abaixo eu mostro como fazer upload de uma imagem e exibir na tela. Note que não estou enviando nada para o servidor, apenas mostrando a imagem selecionada pelo usuário.
<script>
let files;
let src = ''
function uploadImage() {
// Temos que criar um leitor para ler os arquivos que vamos selecionar.
// Depois pegamos o resultado e colocamos na variável "src" ta tag img para exibir a imagem.
const fileReader = new FileReader();
fileReader.readAsDataURL(files[0]);
fileReader.onload = (e) => {
src = e.target.result;
}
}
</script>
<label for="avatar">Selecione uma imagem para enviar:</label>
<input
accept="image/png, image/jpeg, image/gif"
bind:files
id="avatar"
name="avatar"
type="file"
on:change={uploadImage}
/>
<br />
<img {src} alt="" />
<style>
img {
width: 400px;
max-width: 100%;
}
</style>
