DEV Community

Ilia Mikhailov
Ilia Mikhailov

Posted on • Edited on • Originally published at codechips.me

The use for use in Svelte

Recently I stumbled upon this beautiful login form made with Tailwind CSS. It has some Javascript code beside CSS to achieve the desired animation.

Alt Text

The code looks like this.

<style>
    .input {
        transition: border 0.2s ease-in-out;
        min-width: 280px
    }

    .input:focus+.label,
    .input:active+.label,
    .input.filled+.label {
        font-size: .75rem;
        transition: all 0.2s ease-out;
        top: -0.1rem;
        color: #667eea;
    }

    .label {
        transition: all 0.2s ease-out;
        top: 0.4rem;
        left: 0;
    }
</style>

<script>
    var toggleInputContainer = function (input) {
        if (input.value != "") {
            input.classList.add('filled');
        } else {
            input.classList.remove('filled');
        }
    }

    var labels = document.querySelectorAll('.label');
    for (var i = 0; i < labels.length; i++) {
        labels[i].addEventListener('click', function () {
            this.previousElementSibling.focus();
        });
    }

    window.addEventListener("load", function () {
        var inputs = document.getElementsByClassName("input");
        for (var i = 0; i < inputs.length; i++) {
            console.log('looped');
            inputs[i].addEventListener('keyup', function () {
                toggleInputContainer(this);
            });
            toggleInputContainer(inputs[i]);
        }
    });
</script>

<div class="shadow-xl p-10 bg-white max-w-xl rounded">
    <h1 class="text-4xl font-black mb-4">Login</h1>
    <div class="mb-4 relative">
        <input class="removed-for-readability" id="email" type="text">
        <label for="email" class="removed-for-readability">Email Address</label>
    </div>
    <div class="mb-4 relative">
        <input class="removed-for-readability" id="password" type="password">
        <label for="password" class="removed-for-readability">Password</label>
    </div>
    <button class="removed-for-readability">Submit</button>
</div>


Enter fullscreen mode Exit fullscreen mode

I didn't like the fact that you had to reach out to pure DOM functions to achieve this functionality. Turns out that Svelte's use directive is a perfect fit for the job and also a good example of showing one of the things you can use it for. Let's refactor the code a bit.

<style>
  .input {
    transition: border 0.2s ease-in-out;
  }

  .input:focus + .label,
  .input:active + .label,
  .input.filled + .label {
    font-size: 0.75rem;
    transition: all 0.2s ease-out;
    top: -0.1rem;
    color: #667eea;
  }

  .label {
    transition: all 0.2s ease-out;
    top: 0.4rem;
    left: 0;
  }
</style>

<script>
  const labelToggle = node => {
    const handleKey = event => {
      if (event.target.value) {
        event.target.classList.add('filled');
      } else {
        event.target.classList.remove('filled');
      }
    };
    node.addEventListener('keyup', handleKey);

    return {
      destroy() {
        node.removeEventListener('keyup', handleKey);
      }
    };
  };

  const labelClick = node => {
    const click = event => {
      event.target.previousElementSibling.focus();
    };
    node.addEventListener('click', click);

    return {
      destroy() {
        node.removeEventListener('click', click);
      }
    };
  };
</script>

<div class="max-w-lg p-10 bg-white rounded shadow-md">
  <h1 class="mb-4 text-3xl font-black">Login</h1>
  <form>
    <div class="relative mb-4">
      <input use:labelToggle class="removed-for-readability" id="email" type="text" />
      <label use:labelClick for="email" class="removed-for-readability">Email</label>
    </div>
    <div class="relative mb-4">
      <input use:labelToggle class="removed-for-readability" />
      <label use:labelClick for="password" class="removed-for-readability">Password</label>
    </div>
    <div class="text-center">
      <button class="removed-for-readability">Continue</button>
    </div>
  </form>
</div>


Enter fullscreen mode Exit fullscreen mode

Do you notice that our text inputs now have use:labelToggle directives and our labels have use:labelClick? Basically, we have defined the two "use" handlers, or actions as they are called in Svelte, in the script section of the file and then attached them to the appropriate html nodes. But how does it work?

The use directive explained aka Svelte action

The actions are custom code that will be run when the element is mounted on the DOM and will pass the element to that action as a raw DOM node. If the function returns an object with destroy function on it, Svelte will run that function when the element is unmounted from the DOM. Very simple, but also incredibly powerful in case you want to do something outside of Svelte and use the full power of DOM.

Below is an annotated example of the toggle handler attached to our text inputs.


// Svelte passes in raw html DOM element when element is mounted on the DOM
const labelToggle = node => {
    // Define a custom event handler for the text input element
    const handleKey = event => {
      // if element's value is not empty add class "filled"
      if (event.target.value) {
        event.target.classList.add('filled');
      } else {
        event.target.classList.remove('filled');
      }
    };
    // bind custom event handler to element's keyup event
    node.addEventListener('keyup', handleKey);

    // when element is unmounted from the DOM remove the event listener
    return {
      destroy() {
        node.removeEventListener('keyup', handleKey);
      }
    };
  };
Enter fullscreen mode Exit fullscreen mode

You can also pass in parameters to actions and run custom code if parameters change, but I wanted to keep the example simple here. Read the docs if you want to learn more.

Svelte's actions have many use cases,like drag-and-drop, tooltips, etc. Only your imagination is the limit.

Top comments (2)

Collapse
 
geoffrich profile image
Geoff Rich

I don't think this is a good use-case (heh) for use. It would be less code to add an event listener instead:

<script>
    function toggleInputContainer(e) {
        const input = e.target;
        if (input.value != "") {
            input.classList.add('filled');
        } else {
            input.classList.remove('filled');
        }
    }
</script>

<div class="shadow-xl p-10 bg-white max-w-xl rounded">
    <h1 class="text-4xl font-black mb-4">Login</h1>
    <div class="mb-4 relative">
        <input on:keyup={toggleInputContainer} class="input etc" id="email" type="text" >
        <label for="email" class="label etc">Email Address</label>
    </div>
    <div class="mb-4 relative">
        <input on:keyup={toggleInputContainer} class="input etc" id="password" type="password">
        <label for="password" class="label etc">Password</label>
    </div>
    <button class="removed">Submit</button>
</div>
Enter fullscreen mode Exit fullscreen mode

This way, Svelte takes care of the event listener lifecycle for you. Scenarios where you're only adding a single event listener don't need actions. If you're listening to a lot of events, then it could make sense to move the handlers into an action and dispatch a single custom event that has what you need, like in the Svelte tutorial.

Collapse
 
codechips profile image
Ilia Mikhailov

Agree. Example was mostly for demonstration purpose and is overkill, even though it's a nice one! Thanks for teaching me and others :)