본문 바로가기
Web과 프로그래밍 언어/JavaScript

[SVELTE]Bindings- Text inputs, Numeric inputs, Checkbox inputs, Group inputs, Textarea inputs, Select multiple, Contenteditable bindings, Each block bindings, Media elements, Dimensions, This, Component bindings, Binding to component instance

by cosmicgy 2023. 3. 8.

 

Bindings

 

1. Text inputs

bind:value 지시어를 사용할 수 있다. name 값을 변경하면 입력 값이 업데이트될 뿐만 아니라 입력 값이 변경되면 name도 업데이트된다.

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

<input bind:value={name}>

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

 

2. Numberic inputs

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

<style>
    label { display: flex }
    input, p { margin: 6px }
</style>

 

3. Checkbos inputs

<script>
    let yes = false;
</script>

<label>
    <input type=checkbox bind:checked={yes}>
    Yes! Send me regular email spam
</label>

{#if yes}
    <p>Thank you. We will bombard your inbox and sell your personal details.</p>
{:else}
    <p>You must opt-in to continue. If you're not paying, you're the product.</p>
{/if}

<button disabled={!yes}>
    Subscribe
</button>

 

4. group inputs

<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} name="scoops" value={1}>
    One scoop
</label>

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

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

<h2>Flavours</h2>

{#each menu as flavour}
    <label>
        <input type=checkbox bind:group={flavours} name="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}

 

5. Textarea inputs

<script>
    import { marked } from 'marked';
    let value = `Some words are *italic*, some are **bold**`;
</script>

{@html marked(value)}

<textarea bind:value></textarea>

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

 

6. select bindings

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

<h2>Insecurity questions</h2>

<form on:submit|preventDefault={handleSubmit}>
    <select bind:value={selected} on:change="{() => answer = ''}">
        {#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>

<style>
    input {
        display: block;
        width: 500px;
        max-width: 100%;
    }
</style>

 

7. select multiple

  • version1 (복수선택 시 컨트롤 키 제어 필요)
<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}

 

  • version2
<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}

 

8. contenteditable bindings

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

<div
    contenteditable="true"
    bind:innerHTML={html}
></div>

<pre>{html}</pre>

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

 

9. each block bindings

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

<h1>Todos</h1>

{#each todos as todo}
    <div class:done={todo.done}>
        <input
            type=checkbox
            bind:checked={todo.done}
        >

        <input
            placeholder="What needs to be done?"
            bind:value={todo.text}
        >
    </div>
{/each}

<p>{remaining} remaining</p>

<button on:click={add}>
    Add new
</button>

<button on:click={clear}>
    Clear completed
</button>

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

 

10. media elements

<script>
    // These values are bound to properties of the video
    let time = 0;
    let duration;
    let paused = true;

    let showControls = true;
    let showControlsTimeout;

    // Used to track time of last mouse down event
    let lastMouseDown;

    function handleMove(e) {
        // Make the controls visible, but fade out after
        // 2.5 seconds of inactivity
        clearTimeout(showControlsTimeout);
        showControlsTimeout = setTimeout(() => showControls = false, 2500);
        showControls = true;

        if (!duration) return; // video not loaded yet
        if (e.type !== 'touchmove' && !(e.buttons & 1)) return; // mouse not down

        const clientX = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX;
        const { left, right } = this.getBoundingClientRect();
        time = duration * (clientX - left) / (right - left);
    }

    // we can't rely on the built-in click event, because it fires
    // after a drag — we have to listen for clicks ourselves
    function handleMousedown(e) {
        lastMouseDown = new Date();
    }

    function handleMouseup(e) {
        if (new Date() - lastMouseDown < 300) {
            if (paused) e.target.play();
            else e.target.pause();
        }
    }

    function format(seconds) {
        if (isNaN(seconds)) return '...';

        const minutes = Math.floor(seconds / 60);
        seconds = Math.floor(seconds % 60);
        if (seconds < 10) seconds = '0' + seconds;

        return `${minutes}:${seconds}`;
    }
</script>

<h1>Caminandes: Llamigos</h1>
<p>From <a href="https://studio.blender.org/films">Blender Studio</a>. CC-BY</p>

<div>
    <video
        poster="https://sveltejs.github.io/assets/caminandes-llamigos.jpg"
        src="https://sveltejs.github.io/assets/caminandes-llamigos.mp4"
        on:mousemove={handleMove}
        on:touchmove|preventDefault={handleMove}
        on:mousedown={handleMousedown}
        on:mouseup={handleMouseup}
        bind:currentTime={time}
        bind:duration
        bind:paused>
        <track kind="captions">
    </video>

    <div class="controls" style="opacity: {duration && showControls ? 1 : 0}">
        <progress value="{(time / duration) || 0}"/>

        <div class="info">
            <span class="time">{format(time)}</span>
            <span>click anywhere to {paused ? 'play' : 'pause'} / drag to seek</span>
            <span class="time">{format(duration)}</span>
        </div>
    </div>
</div>

<style>
    div {
        position: relative;
    }

    .controls {
        position: absolute;
        top: 0;
        width: 100%;
        transition: opacity 1s;
    }

    .info {
        display: flex;
        width: 100%;
        justify-content: space-between;
    }

    span {
        padding: 0.2em 0.5em;
        color: white;
        text-shadow: 0 0 8px black;
        font-size: 1.4em;
        opacity: 0.7;
    }

    .time {
        width: 3em;
    }

    .time:last-child { text-align: right }

    progress {
        display: block;
        width: 100%;
        height: 10px;
        -webkit-appearance: none;
        appearance: none;
    }

    progress::-webkit-progress-bar {
        background-color: rgba(0,0,0,0.2);
    }

    progress::-webkit-progress-value {
        background-color: rgba(255,255,255,0.6);
    }

    video {
        width: 100%;
    }
</style>

 

11. demensions

<script>
    let w;
    let h;
    let size = 42;
    let text = 'edit me';
</script>

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

<style>
    input { display: block; }
    div { display: inline-block; }
    span { word-break: break-all; }
</style>

 

12.  this

<script>
    import { onMount } from 'svelte';

    let canvas;

    onMount(() => {
        const ctx = canvas.getContext('2d');
        let frame = requestAnimationFrame(loop);

        function loop(t) {
            frame = requestAnimationFrame(loop);

            const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

            for (let p = 0; p < imageData.data.length; p += 4) {
                const i = p / 4;
                const x = i % canvas.width;
                const y = i / canvas.width >>> 0;

                const r = 64 + (128 * x / canvas.width) + (64 * Math.sin(t / 1000));
                const g = 64 + (128 * y / canvas.height) + (64 * Math.cos(t / 1000));
                const b = 128;

                imageData.data[p + 0] = r;
                imageData.data[p + 1] = g;
                imageData.data[p + 2] = b;
                imageData.data[p + 3] = 255;
            }

            ctx.putImageData(imageData, 0, 0);
        }

        return () => {
            cancelAnimationFrame(frame);
        };
    });
</script>

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

<style>
    canvas {
        width: 100%;
        height: 100%;
        background-color: #666;
        -webkit-mask: url(/svelte-logo-mask.svg) 50% 50% no-repeat;
        mask: url(/svelte-logo-mask.svg) 50% 50% no-repeat;
    }
</style>

 

13.  component bindings

<!-- App.svelte -->
<script>
    import Keypad from './Keypad.svelte';

    let pin;
    $: view = pin ? pin.replace(/\d(?!$)/g, '•') : 'enter your pin';

    function handleSubmit() {
        alert(`submitted ${pin}`);
    }
</script>

<h1 style="color: {pin ? '#333' : '#ccc'}">{view}</h1>

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

<!-- Keypad.svelte -->
<script>
    import { createEventDispatcher } from 'svelte';

    export let value = '';

    const dispatch = createEventDispatcher();

    const select = num => () => value += num;
    const clear  = () => value = '';
    const submit = () => dispatch('submit');
</script>

<div class="keypad">
    <button on:click={select(1)}>1</button>
    <button on:click={select(2)}>2</button>
    <button on:click={select(3)}>3</button>
    <button on:click={select(4)}>4</button>
    <button on:click={select(5)}>5</button>
    <button on:click={select(6)}>6</button>
    <button on:click={select(7)}>7</button>
    <button on:click={select(8)}>8</button>
    <button on:click={select(9)}>9</button>

    <button disabled={!value} on:click={clear}>clear</button>
    <button on:click={select(0)}>0</button>
    <button disabled={!value} on:click={submit}>submit</button>
</div>

<style>
    .keypad {
        display: grid;
        grid-template-columns: repeat(3, 5em);
        grid-template-rows: repeat(4, 3em);
        grid-gap: 0.5em
    }

    button {
        margin: 0
    }
</style>

 

14. binding to component instances

<!-- App.svelte -->
<script>
    import InputField from './InputField.svelte';

    let field;
</script>

<InputField bind:this={field}/>

<button on:click={() => field.focus()}>Focus field</button>

<!-- InputField.svelte -->
<script>
    let input;

    export function focus() {
        input.focus();
    }
</script>

<input bind:this={input} />