Categories
Svelte

Sharing Code Between Svelte Component Instances with Module Context

Svelte is an up and coming front end framework for developing front end web apps.

It’s simple to use and lets us create results fast.

In this article, we’ll look at how to share variables between component instances with module context.

Module Context

Svelte components can contain code that is shared between multiple component instances.

To do this, we can declare the script block with context='module' . The code inside it will run once, when the module first evaluates, rather than when a component is instantiated.

For instance, we can use it to create multiple audio elements that can only have one audio element play at a time by stopping the ones that aren’t being played as follows:

App.svelte :

<script>
  import AudioPlayer from "./AudioPlayer.svelte";
</script>

<AudioPlayer
  src="https://file-examples.com/wp-content/uploads/2017/11/file_example_MP3_700KB.mp3"
/>

<AudioPlayer
  src="https://file-examples.com/wp-content/uploads/2017/11/file_example_MP3_1MG.mp3"
/>

<AudioPlayer
  src="http://www.hochmuth.com/mp3/Haydn_Cello_Concerto_D-1.mp3"
/>

AudioPlayer.svelts :

<script context="module">
  let current;
</script>

<script>
  export let src;

  let audio;
  let paused = true;

  const stopOthers = () =>{
    if (current && current !== audio) current.pause();
    current = audio;
  }
</script>

<article>
  <audio
    bind:this={audio}
    bind:paused
    on:play={stopOthers}
    controls
    {src}
  ></audio>
</article>

In the code above, we have the AudioPlayer component which has the code that’s shared between all instances in:

<script context="module">
  let current;
</script>

This lets us call pause on any instance of the audio element that isn’t the one that’s being played as we did in the stopOthers function. stopOthers is run when we click play on an audio element.

Then we included the AudioPlayer instances in the App.svelte component.

Therefore, when we click play on one of the audio elements, we pause the other ones.

Exports

Anything that’s exported from a context='module' script block becomes an export of the module itself. For instance, we can create a button to stop all the audio elements in AudioPlayer that is currently playing as follows:

App.svelte :

<script>
  import AudioPlayer, { stopAll } from "./AudioPlayer.svelte";
</script>

<button on:click={stopAll}>Stop All</button>

<AudioPlayer
  src="https://file-examples.com/wp-content/uploads/2017/11/file_example_MP3_700KB.mp3"
/>

<AudioPlayer
  src="https://file-examples.com/wp-content/uploads/2017/11/file_example_MP3_1MG.mp3"
/>

<AudioPlayer
  src="http://www.hochmuth.com/mp3/Haydn_Cello_Concerto_D-1.mp3"
/>

AudioPlayer.svelte :

<script context="module">
  let elements = new Set();

  export const stopAll = () => {
    elements.forEach(element => {
      element.pause();
    });
  };
</script>

<script>
  import { onMount } from 'svelte';
  export let src;
  let audio;

  onMount(() => {
    elements.add(audio);
    return () => elements.delete(audio);
  });
</script>

<article>
  <audio
    bind:this={audio}
    controls
    {src}
  ></audio>
</article>

In the code above, we add all the audio element instances to the elements set which we created in the contex='module' block as each AudioPlayer component is mounted.

Then we export the stopAll function, which has all the audio element instances from elements , and we call pause on all of them by looping through them and calling pause .

In App.svelte , we import the stopAll function from AudioPlayer and then call it when we click the Stop All button.

Then when we click play on one or more audio elements, they’ll all be paused when we click the Stop All button.

Note that we can’t have a default export because the component is the default export.

Conclusion

We can use the module context script block to add script blocks that are shared between all instances of a component.

If we export functions inside module context script blocks, we can import the function and then call it.

Categories
Svelte

More Complex Bindings with Svelte

Svelte is an up and coming front end framework for developing front end web apps.

It’s simple to use and lets us create results fast.

In this article, we’ll look at how to use the bind directive outside of input elements.

Media Elements

We can use the bind directive with audio or video elements.

For instance, we can bind to various attributes of audio or video elements as follows:

<script>
let time = 0;
let duration = 0;
let paused = true;
</script>

<video
  bind:currentTime={time}
  bind:duration
  bind:paused
  controls
  src='https://sample-videos.com/video123/mp4/240/big_buck_bunny_240p_30mb.mp4'
>
</video>
<p>{time}</p>
<p>{duration}</p>
<p>{paused}</p>

In the code above, we used the bind directive to bind to currentTime , duration , and paused properties of the DOM video element.

They’re all updated as the video is being played or paused if applicable.

6 of the bindings for the audio and video elements are read only, they’re:

  • duration — length of the video in seconds
  • buffered — array of {start, end} objects
  • seekable — array of {start, end} objects
  • played — array of {start, end} objects
  • seeking — boolean
  • ended — boolean

2 way binds are available for:

  • currentTime — current point in the video in seconds
  • playbackRate — how fast to play the video
  • paused
  • volume — value between 0 and 1

Video also has the videoWidth and videoHeight bindings.

Dimensions

We can bind to the dimensions of an HTML element.

For instance, we can write the following to adjust the font size of a piece of text as follows:

<script>
  let size = 42;
  let text = "hello";
</script>

<input type=range bind:value={size}>

<div >
  <span style="font-size: {size}px">{text}</span>
</div>

There’re also read-only bindings for width and height of elements, which we can use as follows:

<script>
 let w;
 let h;
</script>

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

<div bind:clientWidth={w} bind:clientHeight={h}>
  {w} x {h}
</div>

bind:clientWidth and bind:clientHeight binds the width and height to w and h respectively.

This Binding

We can use the this bind to set the DOM element object to a variable.

For instance, we can use the this binding to access the canvas element as follows:

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

  let canvas;

  onMount(() => {
    const ctx = canvas.getContext("2d");
    ctx.beginPath();
    ctx.moveTo(20, 20);
    ctx.lineTo(20, 100);
    ctx.lineTo(100, 100);
    ctx.stroke();
  });
</script>

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

In the code above, we put our canvas manipulation code in the onMount callback so that we only run the code when the canvas is loaded.

We have bind:this={canvas} to bind to the canvas element so that we can access it in the onMount callback.

In the end, we get an L-shaped line drawn on the screen.

Component Bindings

We can bind to component props with bind .

For example, we can write the following code to get the value from the props as follows:

App.svelte :

<script>
  import Input from "./Input.svelte";
  let val;
</script>

<Input bind:value={val} />
<p>{val}</p>

Input.svelte :

<script>
export let value;
</script>

<input bind:value={value} />

In the code above, we have value bound to the input element. Then since we passed val in as the value of the value prop and used the bind directive, val in App will be updated as value in Input is updated.

So when we type into the input box, the value will also be shown in the p element below.

Conclusion

We can bind to media elements when we use bind with some property names.

Using the this binding, we can get the DOM element in the script tag and call DOM methods on it.

Component props can also be used with bind , then we get 2-way binding between parent and child via the props.

Categories
Svelte

Special Elements in Svelte

Svelte is an up and coming front end framework for developing front end web apps.

It’s simple to use and lets us create results fast.

In this article, we’ll look at special Svelte elements and how we can use them.

svelte:component

We can use the svelte:component element to load components dynamically. It takes a this prop which accepts a component object to load.

For instance, we can use it as follows:

<script>
  import Foo from "./Foo.svelte";
  import Bar from "./Bar.svelte";

let component = Foo;
  const toggle = () => {
    component = component === Foo ? Bar : Foo;
  };
</script>

<button on:click={toggle}>Toggle</button>

<svelte:component this={component}/>

In the code above, we have the toggle to set component to the component we want to load by toggling between Foo and Bar as the value of component .

Then we pass component into the this prop to load the component.

svelte:window

We can use the svelte:window component to add listeners to the window object.

For instance, we can use it as follows:

<script>
  let key;
  const handleKeydown = e => {
    key = e.keyCode;
  };
</script>

<p>{key}</p>

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

The code above attaches the handleKeydown to the window object. handleKeydown gets the keyCode from the event object e and then sets the value to key .

key is then displayed in a p element. Then when we press on keys then we see the key code of the key that’s pressed.

We can also add event modifiers like preventDefault to svelte:window like any other DOM element.

Also, we can bind to certain properties of window such as the following:

  • innerWidth
  • innerHeight
  • outerWidth
  • outerHeight
  • scrollX
  • scrollY
  • online — an alias for window.navigator.onLine

All properties except scrollX and scrollY are read-only. For instance, we can write the following to display those values on the screen:

<script>
  let innerWidth;
  let innerHeight;
  let outerWidth;
  let outerHeight;
  let scrollX;
  let scrollY;
  let online;
</script>

<p>{innerWidth}</p>
<p>{innerHeight}</p>
<p>{outerWidth}</p>
<p>{outerHeight}</p>
<p>{scrollX}</p>
<p>{scrollY}</p>
<p>{online}</p>

<svelte:window
  bind:innerWidth
  bind:innerHeight
  bind:outerWidth
  bind:outerHeight
  bind:online
  bind:scrollX
  bind:scrollY
/>

The values are automatically updated since we used bind to bind the values to the variables.

svelte:body

svelte:body element allows us to listen for events in that’s fired on document.body .

For instance, we can use it to listen to mouse movements on the body element as follows:

<script>
  let clientX;
  let clientY;
  const handleMousemove = e => {
    clientX = e.clientX;
    clientY = e.clientY;
  };
</script>

<p>{clientX}</p>
<p>{clientY}</p>
<svelte:body on:mousemove={handleMousemove} />

The code above watches for mouse movements in the body adding the on:mousemove directive to svelte:body and setting the handleMousemove handler function as the value.

Then we get the mouse position by getting the clientX and clientY from the event object e and setting them to variables.

Finally, we display those values by interpolating those variables in the p elements.

Therefore, when we move the mouse inside the body, then we see the mouse position coordinates displayed.

svelte:head

The svelte:head elements let us insert elements inside the head of our document. For instance, we can use it to reference an external CSS file by writing:

<svelte:head>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
   crossorigin="anonymous">
</svelte:head>

<h1 class="text-primary">foo</h1>

In the code above, we just put the link tag for Bootstrap CSS in between the svelte:head tags. Then we can see the word foo with the color blue.

In server-side rendering mode, svelte:head ‘s contents are returned separated from the rest of the HTML.

svelte:options

svelte:options lets us specify compiler options. We can change the following options by passing them as props:

  • immutable={true} — Make the compiler do simple referential checks to determine if values have changed
  • immutable={false} — The default choice. Svelte will be more conservative about whether or not mutable objects have changed
  • accessors={true} — Adds getters and setters for the component’s props
  • accessors={false} — The default choice.
  • namespace="..." — The namespace where this component will be used
  • tag="..." — The name to use when compiling a component as a custom element.

For example, we can write the following to set the immutable option to true :

<svelte:options immutable={true} />

<h1>foo</h1>

Conclusion

Svelte’s special components let us access various parts of the DOM like window and document.body without hassle.

svelte:component lets us dynamically load components.

Also, we can reference the svelte:options component to set various app-wide options.

Categories
Svelte

Handling Inputs with Svelte

Svelte is an up and coming front end framework for developing front end web apps.

It’s simple to use and lets us create results fast.

In this article, we’ll look at how to handle text inputs in Svelte components.

Data Bindings

We can bind input values to variables in our components with the bind:value directive.

To use it we can write the following:

App.svelte :

<script>
  let name = "";
</script>

<input bind:value={name}>

<p>{name}</p>

In the code above, we used the bind:value directive to bind the input’s value to the name variable in our component.

Therefore, when we type into the input, the value will be shown in the p element below the input.

In the DOM, everything is a string. We have to convert the inputted values to the value that we want before using it.

bind:value takes care of that.

For example, if we write:

<script>
  let a = 1;
</script>

<label>
  <input type=number bind:value={a} min=0 max=10>
</label>
<br>
<label>
  <input type=range bind:value={a} min=0 max=10>
</label>

Then we’ll see both inputs change if we type in a value to the number input or change the slider value.

Checkbox Inputs

Svelte can handle checkbox inputs. So we can get the checked values from checkboxes easily.

For example, we can write the following code:

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

<input type=checkbox bind:checked={yes}>
<p>{yes}</p>

and we’ll see the value in the p tag change as we check and uncheck the checkbox since we have the bind:checked attribute to bind the checked value to the yes variable.

Group Inputs

We can use the bind:group directive to bind the values of the group to one value.

For instance, this is useful for radio buttons since a group of radio buttons shares one value. Only one can be selected at a time.

To make a radio button group with bind:group , we can write the following:

<script>
 let fruits = ["apple", "grape", "orange"];
 let selectedFruit = "";
</script>

{#each fruits as fruit}
<label>
  <input type=radio bind:group={selectedFruit} value={fruit} >
  {fruit}
  <br>
</label>
{/each}
<p>{selectedFruit}</p>

In the code above, we have bind:group to bind to the selectedFruit , which has the selected value of the group.

We set the value attribute of each radio button to fruit so that when we click it, Svelte will set the value of selectedFruit to the value that we set to value .

Therefore, when we click a radio button, we’ll see the selected value of the radio button group in the p element.

Also, only one radio button is selected at one time.

Textarea Inputs

Text area inputs work like text inputs. For instance, we can write:

<script>
 let text = "";
</script>

<textarea bind:value={text}></textarea>
<p>{text}</p>

bind:value bind the value entered in the textarea to the value text .

Then we get text displayed in the p tag.

Select Bindings

We can bind value from select elements by writing the following code:

<script>
  let selected = "";
  let names = [
    { id: 1, name: "Jane" },
    { id: 2, name: "John" },
    { id: 3, name: "Mary" }
  ];
</script>

<select bind:value={selected} >
{#each names as name}
  <option value={name}>
    {name.name}
  </option>
{/each}
</select>
<p>{selected.name}</p>

In the code above, we passed in the whole name object into the value prop.

Then when we select an option, we’ll see selected set to the selected choice and we can display the name property to display the name.

Multiple Selection

We can select multiple items from select elements with the multiple attribute set.

For instance, we can write the following code:

<script>
   let selected = "";
   let fruits = ["apple", "orange", "grape"];
</script>

<select bind:value={selected} multiple >
{#each fruits as fruit}
  <option value={fruit}>
    {fruit}
  </option>
{/each}
</select>
<p>{selected}</p>

All we had to do is pass selected to the bind:value directive, then Svelte will automatically get all the selected values.

Therefore, when we choose one or more values from the list, we’ll see them displayed in the p element.

Contenteditable Bindings

Any element with contenteditable set to true can bind to variables in our code.

For instance, we can write:

<script>
   let html = "";
</script>

<style>
      [contenteditable] {
        height: 200px;
        width: 90vw;
      }
</style>

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

In the code above, we set our div’s contenteditable attribute to true . Then we use bind:innerHTML to bind to the html variable.

Since we have contenteditable set to true , we can type into it, and have the inputted value displayed in the p tag since bind does the data binding.

Each Bindings

Binding to elements in the each block will mutate the values of the entry that’s being modified.

For instance, if we have:

<script>
  let todos = [
    { done: false, text: "eat" },
    { done: false, text: "drink" },
    { done: false, text: "sleep" }
  ];

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

Then when we change the value of the input or the checkbox, they’ll be reflected in the template.

For instance, remaining will be updated as we check and uncheck the checkboxes.

Conclusion

We can bind our inputted values from various controls to variables’ values with the bind directive.

It works with any native form element in addition to anything that has the contenteditable attribute set to true .

Categories
Svelte

Handling Component and DOM Events in Svelte Apps

Svelte is an up and coming front end framework for developing front end web apps.

It’s simple to use and lets us create results fast.

In this article, we’ll look at how to handle DOM events in Svelte components.

Events

We can use the on: directive to handle events. For instance, we can write the following to get the mouse position as we move our mouse around the screen:

App.svelte :

<script>
  let pos = { x: 0, y: 0 };

  const handleMousemove = event => {
    pos.x = event.clientX;
    pos.y = event.clientY;
  };
</script>

<style>
  div {
    width: 100vw;
    height: 100vh;
  }
</style>

<div on:mousemove={handleMousemove}>
  The mouse position is {pos.x} x {pos.y}
</div>

In the code above, we have on:mousemove={handleMousemove} to attach the handleMousemove handler to our div.

The handleMousemove function has the event parameter, which is the MouseEvent object.

Therefore, we can access the mouse position with the clientX and clientY properties for the x and y position of the mouse respectively.

In the end, we display them on the screen.

Event handlers can also be declared inline as follows:

App.svelte :

<script>
  let pos = { x: 0, y: 0 };
</script>

<style>
  div {
    width: 100vw;
    height: 100vh;
  }
</style>

<div on:mousemove={event => {
    pos.x = event.clientX;
    pos.y = event.clientY;
  }}>
  The mouse position is {pos.x} x {pos.y}
</div>

Or we can put them between quotes as follows:

App.svelte :

<script>
  let pos = { x: 0, y: 0 };
</script>

<style>
  div {
    width: 100vw;
    height: 100vh;
  }
</style>

<div on:mousemove="{event => {
    pos.x = event.clientX;
    pos.y = event.clientY;
  }}">
  The mouse position is {pos.x} x {pos.y}
</div>

The quotes provide syntax highlighting in some environments, but they’re optional.

Inline event handler doesn’t impact the performance of the app since Svelte will optimize it by not attaching and detaching on the fly.

Modifiers

DOM event directives can have modifiers attached to them to change their behavior.

For instance, we can add the once modifier to the on:click directive to only listen to mouse click of an element once:

App.svelte :

<script>
  const handleClick = () => {
    alert("alert");
  };
</script>

<button on:click|once={handleClick}>
  Click Me Once
</button>

The once modifier in the code above only runs the handleClick handler once, so if we click the button again, it’ll run nothing.

Other modifiers include:

  • preventDefault — calls event.preventDefault before running the handler
  • stopPropagation — calls event.stopPropagation to prevent the event from reaching the next element
  • passive — improves scrolling performance on touch or wheel events
  • capture — fires handlers during the capture phase instead of the bubbling phase
  • self — only trigger handler if event.target is the element itself.

They can also be chained together like on:click|once|self .

Photo by Marvin Meyer on Unsplash

Component Events

Components can also dispatch events. For instance, we can write the following code to dispatch an event from a child to a parent:

App.svelte :

<script>
  import Button from "./Button.svelte";
  const handleGreet = event => {
    alert(event.detail.text);
  };
</script>

<Button on:greet={handleGreet} />

Button.svelte :

<script>
  import { createEventDispatcher } from "svelte";

  const dispatch = createEventDispatcher();

  const greet = () => {
    dispatch("greet", {
      text: "Hello Mary"
    });
  };
</script>

<button on:click={greet}>
  Send Greeting
</button>

In Button, we create the dispatch constant with the createEventDispatcher function. dispatch is a function, which we call with the event name and the payload to send with the event emission as the arguments.

Then in App , we listen to the greet event emitted from Button by attaching the handleGreet event listener to the on:greet directive.

When handleGreet runs after the greet event is emitted, then the event object has the data sent with the event emission and we retrieved it with event.detail.text .

Therefore, when we click the Send Greeting button, we see ‘Hello Mary’ displayed in an alert box.

Event Forwarding

Component events don’t bubble like DOM events. To forward them to another component, we can add a forward function, which calls dispatch .

Then we can listen to the event in the component where the event is forwarded to.

For instance, we can write:

Button.svelte :

<script>
  import { createEventDispatcher } from "svelte";

  const dispatch = createEventDispatcher();

  const greet = () => {
    dispatch("greet", {
      text: "Hello Mary"
    });
  };
</script>

<button on:click={greet}>
  Send Greeting
</button>

Parent.svelte :

<script>
  import Button from "./Button.svelte";
  import { createEventDispatcher } from "svelte";

  const dispatch = createEventDispatcher();

  const forward = () => {
    dispatch("greet", event.detail);
  };
</script>

<Button on:greet />

App.svelte :

<script>
  import Parent from "./Parent.svelte";
  const handleGreet = event => {
    alert(event.detail.text);
  };
</script>

<Parent on:greet={handleGreet} />

In the code above, Button.svelte emits the greet event, then the Parent.svelte component forwards the event, then App.svelte listens to the greet event.

In Parent.svelte ,

<Button on:greet />

is the same as:

<Button on:greet={forward} />

DOM Event Forwarding

We can forward DOM events like component events. For instance, we can write:

Button.svelte :

<button on:click>
  Send Greeting
</button>

Parent.svelte :

<script>
  import Button from "./Button.svelte";
</script>

<Button on:click />

App.svelte :

<script>
  import Parent from "./Parent.svelte";
  const handleClick = event => {
    alert("Hello");
  };
</script>

<Parent on:click={handleClick} />

The code above forwards the click event from Buttonto Parent and Parent to App .

Therefore, we’ll see a ‘Hello’ alert displayed on the screen when we click the Send Greeting button.

Conclusion

Svelte components and DOM elements can have event handlers attached to them.

We listen to events using the on: directives with modifiers.

Events can be forwarded with the on: directive without any value set to it.