Guia em atualização...

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:

gif

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 diretivas on:, 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)}"  />
gif

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:

gif

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.

gif

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>
gif

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>
gif

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}
gif

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}
gif

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.

gif

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>
gif

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>
gif