DEV Community

Bugfender
Bugfender

Posted on • Originally published at bugfender.com on

Create Your First Svelte App

It’s official (well, according to the gospel that is Stack Overflow): Svelte is the world’s favorite web development framework. According to a snapshot of over 80,000 devs carried out in May, this relatively new user interface tool is now more popular than established behemoths like React.js, Django and Vue.js. Programmers love its open-source convenience, its code-efficiency and its super-smart compilation, which generates an optimised JavaScript code based on your manual submissions. The fact that it’s free is a lovely little bonus, too.

We haven’t yet written an integration post about Svelte, and it seemed about time. So in this article you’re going to learn how to build a simple yet powerful app using the Svelte framework, and we’ll show you how to integrate Bugfender with the Svelte app to log and track the application events.

TL;DR

In this article we will cover the following:

  • What is Svelte and how do I get started?
  • How do I build a complete application using Svelte?
  • How do I integrate Bugfender APIs with the application?

Just a quick note: if at any point in time you want to access the code, you can find it on my GitHub: https://github.com/atapas/hobbyland.

You can also find the demo of the app here: https://hobbyland.vercel.app/

What are we building today?

We will build an application called HobbyLand. The app will allow users to list their hobbies and give each one a score based on their proficiency. The lower the score, the more attention they need to give to it. If a hobby has a high score, they’re already smashing it!

At the end of the article, the HobbyLand app will look like this:

Then, we will integrate Bugfender using the SDK to log events like creating, editing and deleting a hobby to the Bugfender console.

What are the benefits of using Svelte?

When it comes to building web applications, Svelte has broken the mold. Unlike React and Vue, Svelte shifts lots of the framework-level heavy lifting to the build phase of the application. So your application doesn’t need to perform many of its core computations at run time, and you get a seriously high-performance app!

The current generation of web frameworks uses the concept of Virtual DOM to build high-performance applications. Svelte goes one stage further by writing code that surgically updates the DOM on your application’s state change. It is far more powerful (and has a far bigger impact on performance) than diffing the DOM and reconciling on every state change.

With Svelte, you:

  • Write less code.
  • Don’t have the Virtual DOM.
  • Can write pure Reactive applications.
  • Don’t need any extra state management libraries as dependencies.

If you want to see more reasons why Svelte rocks, please check out this blog post.

Install and set up the HobbyLand project

We will use degit, a project scaffolding tool, to create the application’s skeleton code using Svelte. To get started, all you need to do is open a command prompt and use the following command:

npx degit sveltejs/template hobbyland-bugfender
Enter fullscreen mode Exit fullscreen mode

This will create a basic Svelte application code structure under the directory ‘hobbyland-bugfender’. Please browse to this directory and use the following command to install the required dependencies.

npm install # or yarn install
Enter fullscreen mode Exit fullscreen mode

It may take a while to install the dependencies. Once you’re done, use the following command to run the skeleton app locally:

npm run dev # or yarn dev
Enter fullscreen mode Exit fullscreen mode

Now you should see the message in your command prompt confirming the application is ready, and it is running locally on [<https://localhost:5000>](<https://localhost:5000>) by default.

Now try to access the app on that URL. You will see the following message from your Svelte app:

Understanding a Svelte project structure

Before we start writing the Svelte project structure and components, we need to understand them. To gain the knowledge we need, we will create components and write our code under the src directory, and create a Svelte component using the .svelte extension.

In the skeleton project we have created, you should have an existing component called App.svelte and an entry point to the app called main.js.

A Svelte component contains everything required for it to work. Inside the component, you must have the markups (HTML), look-and-feel (CSS), and your logic (JavaScript). Please open the App.svelte file. You should see all three sections as:

<script>
    export let name;
</script>

<main>
    <h1>Hello {name}!</h1>
    <p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>
</main>

<style>
    main {
        text-align: center;
        padding: 1em;
        max-width: 240px;
        margin: 0 auto;
    }

    h1 {
        color: #ff3e00;
        text-transform: uppercase;
        font-size: 4em;
        font-weight: 100;
    }

    @media (min-width: 640px) {
        main {
            max-width: none;
        }
    }
</style>
Enter fullscreen mode Exit fullscreen mode

The JavaScript code will go inside the <script></script> tags, while the CSS section will go inside the <style></style> tags. The remaining part of the file may contain the HTML structure of the component.

Now, let’s take a closer look at the <script> part of the App.svelte file:

<script>
    export let name;
</script>

Enter fullscreen mode Exit fullscreen mode

Here you see a variable name with the let keyword, but you’ll also notice the export keyword in front of it. The App component is declaring a property (props as we call it) publicly to set its value from the outside. This means that any other components using the App can set a value to the name property.

Now open the main.js file. Here you can see we are using the App component and passing the name props with a value, world.

new App({
    target: document.body,
    props: {
        name: 'world'
    }
});

Enter fullscreen mode Exit fullscreen mode

This enables components to pass data between each other other inside a Svelte application. Now, armed with this understanding, let’s start building the HobbyLand app.

App design and requirements

At the most basic level, the HobbyLand app will have the following components:

  • A header.
  • A hobby form to assist the creation and editing of each activity.
  • A component to list all the hobbies.
  • Individual hobby entries displaying the hobby id, name, description and rating.

As requirements:

  • We want to save the hobbies into the browser called localstorage, and we will use a library called feather icons to import and use a few icons in our app.
  • We will create a reusable component for each of the Hobby cards.
  • We will create a reusable component for the button.
  • The App.svelte will include both the hobby form and hobby listing. We can still split them into separate components, but for the sake of simplicity we will go ahead with this design for now.

The HobbyLand App structure using components

Please install feather icons as a dependency:

npm install svelte-feather-icons #or yarn add svelte-feather-icons

Enter fullscreen mode Exit fullscreen mode

Also, each of the hobbies requires a unique identifier. We can use the shortid library to generate a random, unique id for each of the hobbies. Let’s install that as well:

npm install shortid #or yarn add shortid

Enter fullscreen mode Exit fullscreen mode

Create a Reusable Button Component

Create a file called Button.svelte under the src folder with the following content:

<script>
    export let negative;
    export let isDisabled;
</script>
<style>
    button{
          cursor: pointer;
          border-radius: 0.25rem;
          text-transform: uppercase;
    }
    .default {
        background-color: #0064c8e0;
        color: #FFF;
        border: 1px solid #0064c8e0 ;
    }
    .negative {
        background-color: #6c7074e0;
        color: #FFF;
        border: 1px solid #6c7074e0 ;
    }
    .disabled {
        background-color: #c1bcbce4;
        color: #000000;
        border: 1px solid #c1bcbce4 ;
        cursor: default;
    }

  </style>

  <button class="{negative ? 'negative' : 'default'} {isDisabled ? 'disabled' : ''}" on:click disabled={isDisabled}>
      <slot/>
  </button>

Enter fullscreen mode Exit fullscreen mode

We are creating a button component with two properties, negative and isDisabled. We have declared them inside the <script></script> tags with the export keyword. When the button is used to negate an action like cancel or close, we should pass the value of the negative props as true. Similarly, to disable the button (no click and other handlers allowed), we should pass the value true for the isDisabled property.

Now, we have defined the look of the button inside the <style></style>tags. But finally there is the HTML issue of defining the button tag. The <slot/> tag tells the Svelte engine that this button component can accept children (like other HTML tags or another svelte component).

Create a reusable hobby card

Next, we want to create the reusable hobby cards to use in the list which sits inside the App.svelte component. A hobby card contains the following information:

  • Edit and delete icons for each hobby.
  • A hobby name.
  • A hobby description.
  • Several thumbs-up icons for rating.
  • Background color to indicate progress towards a particular hobby.

The compilation below shows a list of five hobby cards, with various values.

List of Hobby Cards

We start creating a new file entitled Hobby.svelte under the src directory with the following content:

<script>
    import { EditIcon, XCircleIcon, ThumbsUpIcon } from 'svelte-feather-icons';
    export let hobby = {
        id: '', 
        name: '', 
        description: '',
        weight: '',
        look: {
            background: '', 
            color: ''
        }
    }
    export let deleteHobby;
    export let editMode;
</script>

<div class="hobby" style="background-color: {hobby.look.background}; color: {hobby.look.color};">
    <div class="actions">
        <span on:click={editMode}><EditIcon size="24" /></span>
        <span on:click={deleteHobby}><XCircleIcon size="24" /></span>
    </div>
    <span hidden>{hobby.id}</span>
    <h3><b>{hobby.name}</b></h3>
    <p>{hobby.description}</p>
    <div>
        {#each new Array(hobby.weight) as index}
            <span class="weight"><ThumbsUpIcon size="24" /></span>
        {/each}
    </div>
</div>

<style>
    h3 {
      margin: 0;
      padding: 0;
            font-size: 2em;
            font-weight: 100;
      }
    .hobby {
        padding: 1rem;
        margin: 1rem;
        border: 1px solid #ececec;
        border-radius: .5rem;
        width: 20rem;
        display: flex;
        flex-direction: column;
        justify-content: center;
    }
    .actions {
        justify-content: flex-end;
        display: flex;
    }
    .actions span {
        padding: 0 0 0 .5rem;
        cursor: pointer;
    }
    .weight {
        margin-right: 0.2rem;
    }
</style>

Enter fullscreen mode Exit fullscreen mode

In this component:

  • We first import a few icons for the edit, delete and thumbs-up functions.
  • Then we define the hobby model structure with name, description, weight (rating), look (background color), and an id. The model is a traditional JavaScript object, with empty values. Notice the export keyword at the front of the object. This means we will populate the object’s value from elsewhere; in our case, it will be from the App.svelte component.
  • Next we define two more props, deleteHobby and editMode. These are two functions that we will call from the App component to delete a hobby and set the card in the edit mode.
  • After that, we define the HTML structure of the hobby card.
  • Finally, the style section defines the look of the component.

Create a storage utility

We want to persist the hobby data into the browser’s local storage. So first, let’s create a utility file to write to the local storage and read from it. To do this, you need to create a file named storage.js under the src directory with the following content:

const saveToLS = (key, value) => {

    if (typeof(Storage) !== "undefined") {
        localStorage.setItem(key, JSON.stringify(value));
    }
}

const readFromLS = (key) => {

    if (typeof(Storage) !== "undefined") {
        return localStorage.getItem(key);
    }
}

export { saveToLS, readFromLS };

Enter fullscreen mode Exit fullscreen mode

Now, let’s bring it all together in the app component

Now we will use all these components and utilities inside the App component to create the final app. To kick things off, let’s start modifying the App.svelte file.

Open the file and import the following code under the script section:

<script>
    import { onMount } from 'svelte';
    import Hobby from './Hobby.svelte';
    import Button from './Button.svelte';
    import { saveToLS, readFromLS } from './storage';
    import { generate } from 'shortid';
</script>

Enter fullscreen mode Exit fullscreen mode

Here we have imported all the components and utilities created so far. We have also imported the generate() method from the shortid to create unique ids. Please notice the we have added the onMount import from the svelte library so we can do some action when the component loads.

Next, let’s define the variables, the functions required for the component to work. First, you need to create these variables under the script section, below the imports.

<script>
// --- imports as before ---
let id;
let name = '';
let weight = 3;
let description = '';
let hobbies = JSON.parse(readFromLS('hobbies')) || [];
let isEditMode = false;

let elmToFocus;
onMount(function() {
        elmToFocus.focus();
});
</script>

Enter fullscreen mode Exit fullscreen mode

Here we declare the private variables like the id, name and description of the hobbies. We identify the stored hobbies in the local storage and assign them to a variable; meanwhile, we set the element to focus when the component loads. The element to focus on is the textbox that creates a hobby. We will take a look at that in a while.

Next, let’s create useful methods to toggle between the options to create, edit, delete, and retrieve hobbies. Please create these functions inside the script tag:

<script>
// --- imports as before ---
// --- private variables as before ---

// Define the look of a Hobby based on the weight/rating
const getHobbyLook = (weight) => {
        let background = '#ff6b6b';
        let color = '#FFFFFF';
        if (weight > 3 && weight <= 6) {
            background = '#efef04';
            color = '#000';
        } else if (weight > 6) {
            background = '#0eb30e';
            color = '#FFFFFF';
        }
        return {background: background, color: color};
}

// Method to add a hobby    
const addHobby = () => {
        const look = getHobbyLook(weight);

        if (isEditMode) {
            editHobby(id, name, weight, description, look);

        } else {
            const hobby = {
                id: generate(),
                name : name,
                weight : weight,
                description: description,
                look: look
            };
            hobbies = hobbies.concat(hobby);
            resetAndSave(hobbies);
        }
}

// Method to delete a hobby 
const deleteHobby = id => {
        console.log('hobby to delete', id);
        //find hobby by name
        let index = hobbies.findIndex(hobby => hobby.id === id);
        //remove hobby
        hobbies.splice(index, 1);
        hobbies = [...hobbies];
        console.log('hobbies after delete', JSON.stringify(hobbies));
        resetAndSave(hobbies);
};

// Method to edit a hobby
const editHobby = (id, newName, newWeight, newDescription, newLook) => {
        console.log('hobby to edit', name);
        //find hobby by name
        let index = hobbies.findIndex(hobby => hobby.id === id);
        //edit hobby
        hobbies[index].name = newName;
        hobbies[index].weight = newWeight;
        hobbies[index].description = newDescription;
        hobbies[index].look = newLook;
        hobbies = [...hobbies];
        console.log('hobbies after edit', hobbies);
        resetAndSave(hobbies);
};

// Set the edit mode
const editMode = (hobbyId) => {
        console.log('hobby to edit', name);
        //find hobby by name
        let hobby = hobbies.find(hobby => hobby.id === hobbyId);
        id = hobby.id;
        name = hobby.name;
        weight = hobby.weight;
        description = hobby.description;
        isEditMode = true;
}

// Method to reset the hobby form
const reset = () => {
        id = '';
        name = '';
        weight = 3;
        description = '';
        isEditMode = false;
}

// Method to reset and save
const resetAndSave = hobbies => {
        reset();
        saveToLS('hobbies', hobbies);
}
</script>

Enter fullscreen mode Exit fullscreen mode

Up next, let’s define the HTML structure. To do this, use the following structure after the script tag:

<main>
    <h1>Welcome to the Hobby Land</h1>
    <div class="container">
        <div class="hobby-form">
            <div> 
                <label for="name"></label>
                <input type="text" id="name" bind:value={name} placeholder="What's your Hobby?" bind:this={elmToFocus} />
            </div>
            <div>
                <label for="description"></label>
                <textarea rows="3" cols="35" id="description" bind:value ={description} placeholder="Tell us a bit more about it"/>
            </div>
            <div class="weight-div">
                <label for="weight">How serious are you about it?(1 - Least to 10 - Most)</label>
                <input type="range" min="1" max="10" id="weight" bind:value={weight} />
                <p style="background-color: {getHobbyLook(weight).background}; color: {getHobbyLook(weight).color};">{weight}</p>
            </div>
            {#if isEditMode}
                <Button on:click={reset} negative={true}>Cancel</Button>
                <Button on:click={addHobby}>Edit Hobby</Button>
            {:else}
                <Button on:click={addHobby} isDisabled={name.trim().length === 0}>Add Hobby</Button>
            {/if}
        </div>
        <div>
            <h2>Hobbies</h2>
            <h3>Track Your Hobbies to Get Better</h3>
            <div class="hobby-list">
                {#if hobbies.length === 0}
                    <p class="no-hobby">
                        No Hobbies? Oh dear, please add one to track. 
                    </p>
                {:else}
                    {#each hobbies as hobby}
                        <Hobby
                            hobby={hobby}
                            deleteHobby={() => deleteHobby(hobby.id)} 
                            editMode = {() => editMode(hobby.id)} />
                    {/each}
                {/if}
            </div>
        </div>
    </div>
    <footer>
        <p>Made with ❤️ by <a href="<https://twitter.com/tapasadhikary>">Tapas Adhikary</a>.</p>
    </footer>
</main>

Enter fullscreen mode Exit fullscreen mode

A few things need to happen here.

  • First, we create a heading.
  • Then we create a hobby form with the text field to provide hobby name, a text area for the description, and a slider to provide the weight of each hobby on a scale of 1 to 10.
  • There are two buttons. One is to reset the form any time. The other button is to add/edit a hobby based on the mode setting.
  • Then, we have a list of hobbies created using the Hobby card component.

If you run the application, you will find it fully working, but it still lacks the desired look and feel.

The app is in working condition but lacking a better look

Let’s fix the problem. Please copy and paste the following styles inside the <style></style> tags by replacing the existing styles:

main {
        text-align: center;
        padding: 1em;
        margin: 0;
    }
    h1 {
        color: #ff3e00;
        text-transform: uppercase;
        font-size: 4em;
        font-weight: 100;
        margin: 0;
    }
    p {
        margin: 0.5rem;
    }
    textarea { 
        width: 100%;
    }
    .container {
        display: flex;
        justify-content: space-around;
        margin: 1rem auto auto auto;
    }
    @media screen and (max-width: 720px) {
        .container {
            flex-direction: column;
        }
    }
    .hobby-form {
        padding: 1rem;
        background-color: #ebebeb;
        max-height: 381px;
        min-width: 320px;
    }
    .hobby-list {
        display: flex;
        flex-direction: row;
        flex-wrap: wrap;
        align-content: center;
        justify-content: center;
        align-items: center;
    }
    .no-hobby {
        padding: 1em;
        border: 1px solid;
        border-radius: 4px;
        background-color: #ebebeb;
    }
    .weight-div {
        display: flex;
        flex-direction: column;
        align-content: center;
        justify-content: center;
        align-items: center;
    }
    .weight-div p {
        width: 2rem;
        height: 2rem;
        border: 1px solid #000;
        border-radius: 35px;
        padding: 1rem;
        font-size: 1.5rem;
    }
    input[type="range"] {
        padding: 0;
        margin-top: 0.4em;
        width: 100%;
    }
    input[type="text"] {
        width: 100%;
    }
    @media (min-width: 640px) {
        main {
            max-width: none;
        }
    }

Enter fullscreen mode Exit fullscreen mode

Save your changes and refresh the app. Bingo!!! You got it,

Now try creating, editing and deleting a few more hobbies. You should have it working completely.

Integrate Bugfender to log activities

Bugfender is a remote logging service that allows users to collect logs for everything in the application. It is straightforward to integrate Bugfender with a JavaScript-based framework like Svelte and get started. There are SDKs available to integrate with mobile apps as well.

To kick things off, create an account with Bugfender. Once logged in, you can create a Bugfender application for web apps using the Web SDK option. You can follow this step-by-step guide to create a Bugfender application, and you will find an API key ready for you. Please keep it safe.

Once you have created your app, the Bugfender dashboard will enable you to keep track of logs, issues, feedback, and crashes. This is how my dashboard looks:

Install the Bugfender SDK

Open a command prompt and the root of the project folder and use this command to install the Bugfender SDK:

npm i @bugfender/sdk #Or yarn add @bugfender/sdk

Enter fullscreen mode Exit fullscreen mode

Add Bugfender Logs

Open the App.svelte file and import the Bugfender SDK along with other imports required by your app inside the script tag.

<script>
    import { Bugfender } from '@bugfender/sdk';

  // --- other existing imports ---
</script>

Enter fullscreen mode Exit fullscreen mode

Then initialize the BugFender service by calling the init method. We pass the API key to this method. To do this, place the following code after the imports.

Bugfender.init({
    appKey: '<REPLACE_YOUR_API_KEY_HERE>'
});

Enter fullscreen mode Exit fullscreen mode

Now you can start adding logs of different levels (info, warn, error, etc) in your application code. Here is an example in which we add a normal info log using the addHobby method:

Bugfender.log(`Added a hobby with id ${hobby.id}, and name ${hobby.name}`);

Enter fullscreen mode Exit fullscreen mode

Similarly, you can add deleteHobby and editHobby methods as well. Now, as you start adding, editing, or deleting Hobbies from your app, it will log the relevant details to the Bugfender app. You can view them from the Bugfender dashboard.

Here are a few examples:

Add hobby log

Edit hobby log

Importantly, we can also capture other browser event logs:

Browser Event Logs

If there are warnings or errors, that gets logged automatically!

Warning Logs

Bugfender – Advanced API Usages

The Bugfender SDK provides APIs for many advanced usages, like:

  • sendIssue – To send an issue with the title and details to the Bugfender log server.
Bugfender.sendIssue('Issue title', 'Description of the issue');

Enter fullscreen mode Exit fullscreen mode
  • sendLog – To send logs with tags so that you can filter the log accordingly.
Bugfender.sendLog({ tag: 'tag1', text: 'this is my log' });

Enter fullscreen mode Exit fullscreen mode
  • sendUserFeedback – You can send user feedback using this API and see all the user feedback under the Feedback section of the Bugfender Dashboard.
Bugfender.sendUserFeedback('Love the App!', 'You are doing a great job with it.')

Enter fullscreen mode Exit fullscreen mode
  • As an additional option, we can also use the getUserFeedback() method to create a modal user interface for collecting feedback.
Bugfender.getUserFeedback().then((result) => {
    if (result.isSent) {
// User sent the feedback// `result.feedbackURL` contains the Bugfender feedback URL} else {
// User closed the modal without sending the feedback}
});

Enter fullscreen mode Exit fullscreen mode

Where to go from here

That’s all for now. Thanks for reading! I hope you found the article insightful. Please start exploring Svelte if you are new to it. As a next step, you can connect this HobbyLand app to a database and add more hobbies!

We’d also like to invite you to explore Bugfender and find out more about its other use cases.

Here are a few links you may find helpful:

GitHub – atapas/hobbyland: Hobby Land is a learn and do project built using tye super cool svelte. You can add and track your hobbies, edit them, and also remove them when not needed.

Bugfender is a tool that helps you find errors in your production apps. We strongly believe in sharing knowledge, and that’s why we create articles like this one. If you liked it, help us continue creating content by sharing this article or signing up on the Bugfender site.

Latest comments (0)