How to marry this todo list with instant search in Svelte?

那年仲夏 提交于 2020-12-15 04:44:05

问题


EDIT for the tldr version scroll down to where it says I'm extremely close in the code below.

Problem

I have a basic todo list that when completed will have these qualities:

  1. When the page loads the form field should be in focus.
  2. When the user clicks the up/down arrows the items in the list should be selected via a CSS background color change.
  3. When the user types a new item into the form it should appear in the list to the user.
  4. When the user types into the form field, an "instant search" is applied to the list and only items that match the search appear. All other features should still work. When the user scrolls using the up/down arrows it should still work regardless of how many items are displayed on the page. If only two items are displayed the user should be able to use the up down arrows to select those two items and the form field focus.

All items except number 4 seem to work fine. The following code demonstrates items 1-3

https://svelte.dev/repl/214b9c033d1c489e991484d387c267c2?version=3.30.0

<script>
    import { onMount } from 'svelte';
    let clients = ["a Little piggy","b little piggy","c little piggy"]; 
    let indexVal = -1;
    let downArrowPress = 40;
    let upArrowPress = 38;
    let clientInputTextField = "";
    let clientVal = "";
    onMount(function() {
        clientInputTextField.focus();
    });


    function handleKeydown(event) {
        if(event.keyCode === upArrowPress) {
            indexVal-=1;    
            indexVal = indexVal < 0 ?  clients.length : indexVal
            console.log(indexVal);
        }

        if(event.keyCode === downArrowPress) {
            indexVal+=1;
            indexVal = indexVal > clients.length ?  0 : indexVal
            console.log(indexVal);
        }

        if(indexVal > clients.length -1 || indexVal < 0){
            clientInputTextField.focus(); 
        } else {
            clientInputTextField.blur()
        }   
    }
    
    function handleSubmit(e) {
        const value = e.target.input.value;
        console.log(value);
        clients = [...clients, value];
        clientVal = "";
    }
</script>



<svelte:window on:keydown={handleKeydown}/>

<form on:submit|preventDefault={handleSubmit}>
    <input type="text" name="input" bind:this={clientInputTextField}  bind:value={clientVal} autocomplete="off">
    <input type="submit" name="">
</form>

<ul>
    {#each clients as client, i}
        {#if i === indexVal}
            <li style="background-color:orange">{client}</li>
            {:else}
            <li style="">{client}</li>
        {/if}
    {/each}
</ul>




<!-- 

function search(e){
    console.log(e.target.value)
    const searchString = e.target.value.toLowerCase();
    const filteredCharacters = clients.filter((character) => {
        return (
            character.toLowerCase().includes(searchString) ||
            character.toLowerCase().includes(searchString)
        );
    });
    
     console.log(filteredCharacters)
}
 -->

I want to marry the above code with an instant search feature.

function search(e){
    console.log(e.target.value)
    const searchString = e.target.value.toLowerCase();
    const filteredCharacters = clients.filter((character) => {
        return (
            character.toLowerCase().includes(searchString) ||
            character.toLowerCase().includes(searchString)
        );
    });
    
     console.log(filteredCharacters)
}

Everything I've tried so far just makes a mess, does not return the list and unfocuses (onBlur) the input field.

The code below console.log's an array that gives me what I want as the user searches. The next step is to apply it to the DOM. Everything I've tried so far has not worked.

https://svelte.dev/repl/ceca685a3a4c49b4b5ccd780a101fc63?version=3.30.0

<script>
    import { onMount } from 'svelte';
    let clients = ["a Little piggy","b little piggy","c little piggy"]; 
    let indexVal = -1;
    let downArrowPress = 40;
    let upArrowPress = 38;
    let clientInputTextField = "";
    let clientVal = "";
    onMount(function() {
        clientInputTextField.focus();
    });


    function search(e){
    console.log(e.target.value)
    const searchString = e.target.value.toLowerCase();
    const filteredCharacters = clients.filter((character) => {
        return (
            character.toLowerCase().includes(searchString) ||
            character.toLowerCase().includes(searchString)
        );
    });
    
     console.log(filteredCharacters)
    
    }


    function handleKeydown(event) {
        if(event.keyCode === upArrowPress) {
            indexVal-=1;    
            indexVal = indexVal < 0 ?  clients.length : indexVal
            console.log(indexVal);
        }

        if(event.keyCode === downArrowPress) {
            indexVal+=1;
            indexVal = indexVal > clients.length ?  0 : indexVal
            console.log(indexVal);
        }

        if(indexVal > clients.length -1 || indexVal < 0){
            clientInputTextField.focus(); 
        } else {
            clientInputTextField.blur()
        }   
    }
    
    function handleSubmit(e) {
        const value = e.target.input.value;
        console.log(value);
        clients = [...clients, value];
        clientVal = "";
    }
</script>



<svelte:window on:keydown={handleKeydown}/>

<form on:submit|preventDefault={handleSubmit}>
    <input type="text" name="input" bind:this={clientInputTextField}  bind:value={clientVal} on:input={search} autocomplete="off">
    <input type="submit" name="">
</form>

<ul>
    {#each clients as client, i}
        {#if i === indexVal}
            <li style="background-color:orange">{client}</li>
            {:else}
            <li style="">{client}</li>
        {/if}
    {/each}
</ul>




<!-- 


 -->

EDIT

I'm extremely close in the code below.

In this version, the only problem is that when a user submits a new todo the list does not appear in the DOM. Everything else behind the scenes works. To see what I mean, add an item to the input field, submit it and then type the letter a in the form field and then hit the back space button. The list will reappear.

https://svelte.dev/repl/f8fb733401eb4e27b9b8e54c688d355a?version=3.30.0

<script>
    import { onMount } from 'svelte';
    let clients = ["a Little piggy","b little piggy","c little piggy"]; 
    let clientsCopy = [...clients];
    let indexVal = -1;
    let downArrowPress = 40;
    let upArrowPress = 38;
    let clientInputTextField = "";
    let clientVal = "";
    onMount(function() {
        clientInputTextField.focus();
    });


    function search(e){
    console.log(e.target.value)
    const searchString = e.target.value.toLowerCase();
    const filteredCharacters = clients.filter((character) => {
        return (
            character.toLowerCase().includes(searchString) ||
            character.toLowerCase().includes(searchString)
        );
    });
    
     clientsCopy = [...filteredCharacters]
    
    }


    function handleKeydown(event) {
        if(event.keyCode === upArrowPress) {
            indexVal-=1;    
            indexVal = indexVal < 0 ?  clientsCopy.length : indexVal
            console.log(indexVal);
        }

        if(event.keyCode === downArrowPress) {
            indexVal+=1;
            indexVal = indexVal > clientsCopy.length ?  0 : indexVal
            console.log(indexVal);
        }

        if(indexVal > clientsCopy.length -1 || indexVal < 0){
            clientInputTextField.focus(); 
        } else {
            clientInputTextField.blur()
        }   
    }
    
    function handleSubmit(e) {
        const value = e.target.input.value;
        console.log(value);
        clients = [...clients, value];
        clientVal = "";
    }
</script>



<svelte:window on:keydown={handleKeydown}/>

<form on:submit|preventDefault={handleSubmit}>
    <input type="text" name="input" bind:this={clientInputTextField}  bind:value={clientVal} on:input={search} autocomplete="off">
    <input type="submit" name="">
</form>

<ul>
    {#each clientsCopy as client, i}
        {#if i === indexVal}
            <li style="background-color:orange">{client}</li>
            {:else}
            <li style="">{client}</li>
        {/if}
    {/each}
</ul>




<!-- 


 -->

回答1:


I got it. I set a copy of the clients array to a reactive array. Hopefully this helps someone out.

https://svelte.dev/repl/ad1c185bf97a47b98506c3603f510de6?version=3.30.0

<script>
    import { onMount } from 'svelte';
    let clients = ["a Little piggy","b little piggy","c little piggy"]; 
    $: clientsCopy = clients ;
    let indexVal = -1;
    let downArrowPress = 40;
    let upArrowPress = 38;
    let clientInputTextField = "";
    let clientVal = "";
    onMount(function() {
        clientInputTextField.focus();
    });


    function search(e){
    console.log(e.target.value)
    const searchString = e.target.value.toLowerCase();
    const filteredCharacters = clients.filter((character) => {
        return (
            character.toLowerCase().includes(searchString) ||
            character.toLowerCase().includes(searchString)
        );
    });
    
     clientsCopy = [...filteredCharacters]
    
    }


    function handleKeydown(event) {
        if(event.keyCode === upArrowPress) {
            indexVal-=1;    
            indexVal = indexVal < 0 ?  clientsCopy.length : indexVal
            console.log(indexVal);
        }

        if(event.keyCode === downArrowPress) {
            indexVal+=1;
            indexVal = indexVal > clientsCopy.length ?  0 : indexVal
            console.log(indexVal);
        }

        if(indexVal > clientsCopy.length -1 || indexVal < 0){
            clientInputTextField.focus(); 
        } else {
            clientInputTextField.blur()
        }   
    }
    
    function handleSubmit(e) {
        const value = e.target.input.value;
        console.log(value);
        clients = [...clients, value];
        clientVal = "";
    }
</script>



<svelte:window on:keydown={handleKeydown}/>

<form on:submit|preventDefault={handleSubmit}>
    <input type="text" name="input" bind:this={clientInputTextField}  bind:value={clientVal} on:input={search} autocomplete="off">
    <input type="submit" name="">
</form>

<ul>
    {#each clientsCopy as client, i}
        {#if i === indexVal}
            <li style="background-color:orange">{client}</li>
            {:else}
            <li style="">{client}</li>
        {/if}
    {/each}
</ul>




<!-- 


 -->



回答2:


This is a little more succint version of what you have, based on code found in a gist here. It's effectively the same as your solution though (i.e. creating and displaying a separate "filtered" list)!

<script>
    import { onMount } from 'svelte';
    let clients = ["a Little piggy","b little piggy","c little piggy"]; 
    let indexVal = -1;
    let downArrowPress = 40;
    let upArrowPress = 38;
    let clientInputTextField = "";
    let clientVal = "";
    
        const startsWith = (item, query) => item.toLowerCase().indexOf(query.toLowerCase()) != -1;
        $: filteredClients = clientVal && clientVal.length ? clients.filter(item => startsWith(item, clientVal)) : clients;
    
        onMount(function() {
            clientInputTextField.focus();
        });


    function handleKeydown(event) {
    if(event.keyCode === upArrowPress) {
            indexVal-=1;    
            indexVal = indexVal < 0 ?  clients.length : indexVal
            console.log(indexVal);
    }

    if(event.keyCode === downArrowPress) {
            indexVal+=1;
            indexVal = indexVal > clients.length ?  0 : indexVal
            console.log(indexVal);
    }

    if(indexVal > clients.length -1 || indexVal < 0){
            clientInputTextField.focus(); 
    } else {
        clientInputTextField.blur()
    }   
  }
    
    function onChange(e) {
        console.log("what")
        const searchTerm = e.target.value.toLowerCase();
        console.log(searchTerm)
        clients = clients.filter(client => client.toLowerCase().includes(searchTerm))
    }
    
    function handleSubmit(e) {
        const value = e.target.input.value;
        console.log(value);
        clients = [...clients, value];
        clientVal = "";
    }
</script>



<svelte:window on:keydown={handleKeydown}/>

<form on:submit|preventDefault={handleSubmit}>
<!--     <input type="text" name="input" on:change={onChange} bind:this={clientInputTextField}  bind:value={clientVal} autocomplete="off"> -->
    <input type="text" name="input" bind:this={clientInputTextField}  bind:value={clientVal} autocomplete="off">
    <input type="submit" name="">
</form>

<ul>
    {#each filteredClients as client, i}
        {#if i === indexVal}
            <li style="background-color:orange">{client}</li>
            {:else}
            <li style="">{client}</li>
        {/if}
    {/each}
</ul>


来源:https://stackoverflow.com/questions/65030010/how-to-marry-this-todo-list-with-instant-search-in-svelte

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!