Categories
Svelte

Composing Svelte Components with Slots

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 nest Svelte components with slots.

Component Composition

We can add the slot component to a Svelte component to let us nest components inside another component.

For example, we can use the slot component as follows:

App.svelte :

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

<Parent>
  <Foo />
</Parent>

Parent.svelte :

<div>
  <p>Start</p>
  <p>
    <slot></slot>
  </p>
  <p>End</p>
</div>

Foo.svelte :

<button>
  foo
</button>

In the code above, we have the Parent component, which has the slot component, which lets us nest one or more components inside it.

Parent has 3 p elements. The slot is in the 2nd p element.

We created the Foo component with a button. Then when we write:

<Parent>
  <Foo />
</Parent>

in App.svelte , we’ll see the button displayed between ‘start’ and ‘end’.

Fallback Content

We can put fallback content in between the slot tags so that when we didn’t pass in anything in between the tags of the component with the slot component, then we’ll still see something displayed.

For example, we can write the following code to add fallback content and display it:

App.svelte :

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

<Parent />

Parent.svelte :

<div>
  <p>Start</p>
  <p>
    <slot>
      <p>Nothing to see here</p>
    </slot>
  </p>
  <p>End</p>
</div>

In the code above, we have:

<p>Nothing to see here</p>

in between the slot tags. Then when we write <Parent /> as we did in App.svelte , we’ll see:

Start

Nothing to see here

End

displayed on the screen since we used a self-closing version of Parent instead of passing child elements in between the tags.

Multiple Slots with Named Slots

We can add multiple slots by naming the slots. Then we can add content for each slot with the slot attribute with the name of the slot as the value.

For instance, we can write the following code to add multiple slots and populate them with content:

Contact.svelte :

<article>
  <h2>
    <slot name="name">
      <span>No name</span>
    </slot>
  </h2>

  <div class="address">
    <slot name="address">
      <span>No address</span>
    </slot>
  </div>

  <div class="email">
    <slot name="email">
      <span>No email</span>
    </slot>
  </div>
</article>

App.svelte

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

<Contact>
  <span slot="name">
    Jane Smith
  </span>

  <span slot="address">
    123 A St.
  </span>

  <span slot="email">
    abc@abc.com
  </span>
</Contact>

In the code above, we defined multiple slots with the name attribute added to each in Contact.svelte .

Then we can use the Contact component in App.svelte when we pass in components into each slot as identified by their names. Therefore, we should see:

Jane Smith

123 A St.
abc@abc.com

displayed on the screen.

Passing Child Data to Parent Component via Slots

We can use slot props to pass data from the child component to the parent component. To define slot props, we set the prop name with the variable that we want to pass to the parent as the value.

To define slot props and use it, we can write the following code:

Contact.svelte :

<script>
let names = ["John", "Jane", "Mary"];
</script>

<article>
  <slot names={names}></slot>
</article>

The code above has the slot prop names , which we pass the names variable into it.

Then we can use it in App.svelte as follows:

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

<Contact let:names={names}>
  {#each names as name}
    <p>{name}</p>
  {/each}
</Contact>

The let directive defines the slot prop variable, which should have the slot prop as the value.

In the code above, let:names={names} sets the slot props with the names prop from Contact as the value for the names variable in App.svelte .

Then we can access names inside the loop as we did in the each loop.

Therefore, we should get:

John

Jane

Mary

displayed on the screen.

Named slots can also have props. We can write:

Contact.svelte :

<script>
let contactName = "Jane Smith";
let address = "123 A. St";
let email = "abc@abc.com";
</script>

<article>
  <h2>
    <slot name="name" contactName={contactName}>
      <span>No name</span>
    </slot>
  </h2>
  <div class="address">
    <slot name="address" address={address}>
      <span>No address</span>
    </slot>
  </div>
  <div class="email">
    <slot name="email" email={email}>
      <span>No email</span>
    </slot>
  </div>
</article>

App.svelte :

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

<Contact>
  <span slot="name" let:contactName={contactName}>
    {contactName}
  </span>
  <span slot="address" let:address={address}>
    {address}
  </span>
  <span slot="email" let:email={email}>
    {email}
  </span>
</Contact>

In the code above, we defined slot props for each slot, and then pass them into the App.svelte component with the let directive as we did with the single slot example.

Therefore, we should have:

Jane Smith

123 A St.
abc@abc.com

displayed as we did with the earlier example.

Conclusion

We can use slots to nest components within another component. To pass data from the child components to the parent, we can use slot props.

Slot props are defined with the let directive. The value of it should be the variable name to reference the slot prop value. Then we can access the slot prop value in between the tags.

Fallback content can be added between the slot tags and they’re displayed and we reference a component with a self-closing tag.

Categories
JavaScript Svelte

Changing HTML Class in Svelte Components with the Class Directive

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 change class names dynamically with the Svelte class directive.

Class Directive

We can use the class directive with an expression to return the class to display based on the condition.

Also, we can add class names as modifiers and the condition to display it as the value as pass in.

For instance, we can write the following code to toggle a class:

App.svelte :

<script>  
let enableFoo;  
</script>

<style>  
  .foo {  
    color: red;  
  }  
</style>

<button    
  on:click="{() => enableFoo = !enableFoo}"  
>  
  Toggle Class  
</button>  

<p class="{enableFoo ? 'foo': ''}">foo</p>

In the code above, we have class=”{enableFoo ? ‘foo’: ‘’}” to toggle the foo class on when the enableFoo variable is true since we return 'foo' then. Otherwise, we have no class apply to the p element.

The class directive conditionally applies the foo class to the p element since we added the class directive to that element.

The button toggles the enableFoo on and off, which is used to return 'foo' when enableFoo is true . Otherwise, we return an empty string, which means no class.

The foo class sets the color to red. So the text in the p tag will be toggle between red and black.

We can also write the code to do the same thing as follows:

App.svelte :

<script>  
let enableFoo;  
</script>

<style>  
  .foo {  
    color: red;  
  }  
</style>

<button    
  on:click="{() => enableFoo = !enableFoo}"  
>  
  Toggle Class  
</button>  
<p class:foo="{enableFoo}">foo</p>

We changed:

class="{enableFoo ? 'foo': ''}"

to:

class:foo="{enableFoo}

foo is the class name, which the modifier for the class directive.

If our class is the same as our variable name for toggling on a class on and off, then we can shrink the class directive code with the shorthand.

For instance, instead of writing:

<script>  
let foo;  
</script>

<style>  
  .foo {  
    color: red;  
  }  
</style>

<button    
  on:click="{() => foo = !foo}"  
>  
  Toggle Class  
</button>  

<p class:foo="{foo}">foo</p>

We can write:

<script>  
let foo;  
</script>

<style>  
  .foo {  
    color: red;  
  }  
</style>

<button    
  on:click="{() => foo = !foo}"  
>  
  Toggle Class  
</button>  

<p class:foo>foo</p>

They both toggle the foo class. class:foo=”{foo}” is the same as class:foo .

Conclusion

The class directive lets us conditionally apply a class to an element. We can do that either by passing in an expression to conditionally return a class name to apply.

Or we can add the class name as the modifier to the class directive and then pass in the condition to add the class as the value we pass into the directive.

Categories
JavaScript Svelte

Passing Props Between Svelte Components

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 pass data to child Svelte components via props.

Props

We can use the export keyword to create a variable that can be passed into a child within the child component itself.

For example, if App.svelte is the parent and Button.svelte is the child, then we can write the following to pass a value to the text prop of Button:

App.svelte :

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

<main>  
  <Button text='Toggle' />  
</main>

Button.svelte :

<script>  
export let text;  
</script>

<button>  
  {text}  
</button>

In the code above, we wrote:

export let text;

to indicate that Button takes text as a prop.

Then in App.svelte , we write:

<Button text='Toggle' />

to pass in the string 'Toggle' to Button . Since Button references text between the button tags, we see the word Toggle as the text for the button.

We can set default values of props by setting the value of our export expression as follows:

App.svelte :

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

<main>  
  <Button text='Toggle' />  
  <Button />  
</main>

Button.svelte :

<script>  
export let text = "Empty Button";  
</script>

<button>  
  {text}  
</button>

In the code above, we set text to 'Empty Button' in Button.svelte .

Therefore, when we have a Button without a text prop passed in, we’ll get that value as the text of the button.

Spread Props

We can use the spread operator to spread an object’s properties into multiple props.

For instance, if we want to pass in more than one prop, we can write the following:

App.svelte ;

<script>  
  import Info from "./Info.svelte"; 
  const person = {  
    name: "Jane Smith",  
    age: 20,  
    gender: "female"  
  };  
</script>

<main>  
  <Info {...person} />  
</main>

Info.svelte :

<script>  
  export let name;  
  export let age;  
  export let gender;  
</script>

<p>{name} {age} {gender}</p>

In the code above, we have the person object in App.svelte , which spread into the name , age , and gender props via the spread operator.

In Info.svelte , we indicated that name , age , and gender are props. Since they’re passed in from App.svelte , the values will be displayed in the p tag.

So we have Jane Smith 20 female on the screen.

Conversely, if we want to access props that aren’t declared explicitly with export , we can get the values by the $$props variable.

For instance, we can rewrite the example above as:

App.svelte :

<script>  
  import Info from "./Info.svelte"; 
  const person = {  
    name: "Jane Smith",  
    age: 20,  
    gender: "female"  
  };  
</script>

<main>  
  <Info {...person} />  
</main>

Info.svelte :

<p>{$$props.name} {$$props.age} {$$props.gender}</p>

As we can see, we removed all the exports in Info.svelte and just referenced $$props directly in our template markup.

Conclusion

We can pass data from a parent component to a child component by declaring props withexport expressions.

Then we can pass in props from the parent to the child.

If we have lots of props that we want to pass in, we can use the spread operator to spread object properties as props.

Finally, we can use the $$props variable to reference the props that have no export expressions associated with them.

Categories
JavaScript Svelte

Passing Arguments into Svelte Actions

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 pass parameters into actions.

Adding Parameters

Actions can take arguments. This means that the action will be called alongside the elements it belongs to.

For instance, we can create an app where we display a message when we clicked and held a button for a specified amount of time that we can change with a slider.

To make this, we write the following code:

longpress.js

export const longpress = (node, duration) => {  
  let timer; 
  const handleMousedown = () => {  
    timer = setTimeout(() => {  
      node.dispatchEvent(new CustomEvent("longpress"));  
    }, duration);  
  }; 

  const handleMouseup = () => {  
    clearTimeout(timer);  
  }; 

  node.addEventListener("mousedown", handleMousedown);  
  node.addEventListener("mouseup", handleMouseup); return {  
    destroy() {  
      node.removeEventListener("mousedown", handleMousedown);  
      node.removeEventListener("mouseup", handleMouseup);  
    },  
    update(newDuration) {  
      duration = newDuration;  
    }  
  };  
};

App.svelte :

<script>  
  import { longpress } from "./longpress.js";
  let pressed = false;  
  let duration = 2000;  
</script>

<label>  
  <input type=range bind:value={duration} max={2000} step={100}>  
  {duration}ms  
</label>

<button use:longpress={duration}  
  on:longpress="{() => pressed = true}"  
  on:mouseenter="{() => pressed = false}"  
>
  press and hold
</button>

{#if pressed}  
  <p>You pressed for {duration}ms</p>  
{/if}

In the code above, we created a longpress action that takes a duration as an argument.

We have the update method in the object we return to update the duration when it’s passed in.

Then when we click the mouse, the mousedown event is emitted, and then the handleMousedown is called.

We dispatch the custom longpress event after the specified duration via setTimeout .

Then when the mouse button is released, handleMouseup is called, and then clearTimeout is called.

Then in App.svelte , we have the button that we long press to see the message. We have the slider to adjust how long the long-press last until we see the message.

This works because we listened to the longpress event emitted by the button, which is attached to the longpress action with the use:longpress directive.

When we first hover over the button, then mouseenter event is emitted and pressed is set to false .

When the longpress event is emitted from longprss , which is emitted, when we hold the button for long enough, then pressed is set to true .

Then the message is displayed when pressed is true .

If we need to pass in multiple arguments, we pass in one object with multiple properties like:

use:longpress={{duration, foo}}

Conclusion

We can pass in a single argument to action. This lets us adjust our actions in the way that we want.

The update function is required to update the value when the argument is updated.

Categories
JavaScript Svelte

Adding Lifecycle Hooks to Elements with Svelte Actions

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 actions to add lifecycle hooks to elements of our choice.

Actions

Actions are element level lifecycle functions. They’re useful for that needs to listen to element events like tooltips, and interfacing with third-party libraries.

For example, we can use it to make a box that bounces when we drag it as follows:

pannable.js :

export const pannable = node => {  
  let x;  
  let y; const handleMousedown = event => {  
    x = event.clientX;  
    y = event.clientY; node.dispatchEvent(  
      new CustomEvent("panstart", {  
        detail: { x, y }  
      })  
    ); 
    window.addEventListener("mousemove", handleMousemove);  
    window.addEventListener("mouseup", handleMouseup);  
  }; 

  const handleMousemove = event => {  
    const dx = event.clientX - x;  
    const dy = event.clientY - y;  
    x = event.clientX;  
    y = event.clientY; node.dispatchEvent(  
      new CustomEvent("panmove", {  
        detail: { x, y, dx, dy }  
      })  
    );  
  }; 

  const handleMouseup = event => {  
    x = event.clientX;  
    y = event.clientY; node.dispatchEvent(  
      new CustomEvent("panend", {  
        detail: { x, y }  
      })  
    ); 
    window.removeEventListener("mousemove", handleMousemove);  
    window.removeEventListener("mouseup", handleMouseup);  
  }; 

  node.addEventListener("mousedown", handleMousedown); return {  
    destroy() {  
      node.removeEventListener("mousedown", handleMousedown);  
    }  
  };  
};

App.svelte :

<script>  
  import { spring } from "svelte/motion";  
  import { pannable } from "./pannable.js"; 

  const coords = spring(  
    { x: 0, y: 0 },  
    {  
      stiffness: 0.2,  
      damping: 0.4  
    }  
  ); 

  const handlePanStart = () => {  
    coords.stiffness = coords.damping = 1;  
  }; 

  const handlePanMove = event => {  
    coords.update($coords => ({  
      x: $coords.x + event.detail.dx,  
      y: $coords.y + event.detail.dy  
    }));  
  }; 

  const handlePanEnd = event => {  
    coords.stiffness = 0.2;  
    coords.damping = 0.4;  
    coords.set({ x: 0, y: 0 });  
  };  
</script>

<style>  
  .box {  
    position: absolute;  
    width: 100px;  
    height: 100px;  
    border-radius: 4px;  
    background-color: green;  
    cursor: move;  
  }  
</style>

<div class="box"  
  use:pannable  
  on:panstart={handlePanStart}  
  on:panmove={handlePanMove}  
  on:panend={handlePanEnd}  
  style="transform:  
    translate({$coords.x}px,{$coords.y}px)  
    rotate({$coords.x * 0.2}deg)"  
>
</div>

In the code above, we created a div that listens to the pannable ‘s events. We use the pannable action with the use:pannable directive.

The pannable function has all the custom events that whatever it applies to emits.

In the handleMousedown listener of the pannable function, we have the node , which is the DOM node the action is attached to, and we set x and y with the mouse position. Then we call dispatchEvent to emit a custom event, which is panstart to send the x and y coordinates to our parent compone.t

In the handleMousemove function, we handle mouse moves by changing the position of the box, which is node , by using x and y to translate the box. Then we dispatch a custom panmove event to the parent.

When the mouse stops moving, the handleMouseup event handler is called, which sets x and y to the coordinates of the mouse. Then we dispatch the panned event with the x and y coordinates.

When the div box is destroyed, we called node.removeListener to remove the mousedown listener when the box is being destroyed.

In App.svelte , we use the x and y coordinates sent from pannable to update the coordinates of the box.

When also added a bounce effect with the spring transition that’s built into Svelte. coords is the animation effect that’s returned from spring . It has the x and y coordinates for the box, which we’ll update the location of the box as the mouse moves.

When the panstart , panmove , and panned events are triggered, we set the coordinates of the box.

Because mousemove triggers the emission of the panstart event we set the stiffness and the damping of the animation.

mousemove triggers the panmove event to be emitted, which changes the coordinates of the box.

Then when we stop moving the mouse and release the button, the panend event is triggered so that handlePanEnd is called to change the box’s coordinates back to (0, 0).

The style attribute of the div is dynamic and depends on coords , so the box will move as coords changes. We also rotate the box with the angle as a function of coords , so we’ll also see rotation.

Therefore, when we drag the box and then release the mouse button, we’ll see a bounce effect.

Conclusion

In Svelte, actions are functions that emit custom events. We can attach the action to an element and then listen to those custom events to manipulate the elements as we wish.