Categories
Advanced

Svelte vs jQuery Bootstrap Progress Bar

I wanted to check what would be easier: implementing a progress bar with jQuery or with Svelte. So I implemented both. I intended to create a progress bar that goes through some list of items so it would also have control buttons, one for start, one for pausing and another for resetting. 

The control buttons need to be user friendly, so when the progress bar is loading the start button and reset button need to be disabled, when paused the pause button should be disabled and when done the reset button should be the only one to be enabled.

jQuery

Check Demo….

The HTML looks like this:

<head>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
    <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
    <script src="progress.js"></script>
</head>
 
<div>
    <div class="progress" style="margin: 20px;">
        <div id="progress-bar" class="progress-bar progress-bar-striped" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%">0%</div>
    </div>
    <div style="text-align: center;">
        <button id="start-button" type="button" class="btn btn-primary">Start</button>
        <button id="pause-button" disabled="true" type="button" class="btn btn-secondary">Pause</button>
        <button id="reset-button" disabled="true" type="button" class="btn btn-danger">Reset</button>
    </div>
</div>

As you can see I’m importing jQuery and bootstrap.

Attempt 01:

Initially, I tried using jQuery with no custom events, I’m just hooking into button click events. The code looks like this:

$(document).ready(() => {
    var bar = $("#progress-bar");
    var currIndex = 0;
    const maxIndex = 5;
    var stop = false;

    var resetButton = $("#reset-button").on("click", () => 
    {
        setBar(0);
        currIndex = 0;
        setDisabledState(resetButton, true);
        setDisabledState(startButton,false);
    });

    var pauseButton = $("#pause-button").on("click", () => 
    {
        stop = true;
        setDisabledState(pauseButton, true);
    });

    var startButton = $("#start-button").on("click", () => 
    {
        setDisabledState(pauseButton, false);
        setDisabledState(startButton, true);
        setDisabledState(resetButton, true);
        bar.toggleClass("progress-bar-animated");
        nextItem();

    });

    function incrementIndex(){
        currIndex++;
        if(currIndex == maxIndex){
            stop = true;
            setDisabledState(startButton, true);
            setDisabledState(pauseButton, true);
        }
    }

    function setBar(val){
        bar.width(val + "%");
        bar.text(val + "%");
    }

    function setDisabledState(elem, disabled){
        elem.prop('disabled', disabled);
    }

    function nextItem(){
        setTimeout(() => {
            incrementIndex();
            setBar(currIndex/maxIndex*100);
            if(stop){
                bar.toggleClass("progress-bar-animated");
                stop = false;
                setDisabledState(resetButton, false);
                if(currIndex<maxIndex) setDisabledState(startButton,false);
                return;
            }
            nextItem();
        }, 1000)
    }

As you can see I’m struggling to maintain the logic I wanted, the code is both complicated to write and read.

Attempt 02- with custom events: 

With custom events, the code is a little longer but much more readable and conceptually easier to write. I basically have the progress bar listen to four events: loading, pause, reset, and done. Each of the buttons trigger these events, and the ‘done’ event is triggered when we’re completed with all the items. Final code looks like this:

$(document).ready(() => {
    var bar = $("#progress-bar");
    var currIndex = 0;
    const maxIndex = 5;
    var stop = false;
 
    bar.on("loading", () => {
        setDisabledState(pauseButton, false);
        setDisabledState(startButton, true);
        setDisabledState(resetButton, true);
        bar.toggleClass("progress-bar-animated");
    });
 
    bar.on("pause", () => {
        stop = true;
        setDisabledState(pauseButton, true);
    });
 
    bar.on("reset", () => {
        setBar(0);
        currIndex = 0;
        setDisabledState(resetButton, true);
        setDisabledState(startButton,false);
    });
 
    bar.on("done", () => {
        stop = true;
        setDisabledState(startButton, true);
        setDisabledState(pauseButton, true);
    });
 
    bar.on("stop", () => {
        bar.toggleClass("progress-bar-animated");
        stop = false;
        setDisabledState(resetButton, false);
        if(currIndex<maxIndex) setDisabledState(startButton,false);
    });
 
    var resetButton = $("#reset-button").on("click", () => 
    {
        bar.trigger("reset");
    });
 
    var pauseButton = $("#pause-button").on("click", () => 
    {
        bar.trigger("pause");
    });
 
    var startButton = $("#start-button").on("click", () => 
    {
        bar.trigger("loading");
        nextItem();
    });
 
    function incrementIndex(){
        currIndex++;
        if(currIndex == maxIndex){
            bar.trigger("done");
            
        }
    }
 
    function setBar(val){
        bar.width(val + "%");
        bar.text(val + "%");
    }
 
    function setDisabledState(elem, disabled){
        elem.prop('disabled', disabled);
    }
 
    function nextItem(){
        setTimeout(() => {
            incrementIndex();
            setBar(currIndex/maxIndex*100);
            if(stop){
                bar.trigger("stop");
                return;
            }
            nextItem();
        }, 1000)
    }
 
});

To sum up, without custom events jQuery is not easy to construct, but custom events make it a little better. Next: Svelte.

Svelte


I’m rather new to Svelte but I managed to create my progress bar with three components. The top component is the app itself which contains all of the logic connecting the control buttons to the progress bar. Upon starting the app I will go through some prefixed number of items (in this case 5), and will set a timeout between each to simulate some asynchronous action.

The other two components are the control buttons and the progress bar. 

In order to get it right I had to import the bootstrap SCSS into my app:

<svelte:head>
    @import "bootstrap/scss/functions";
 
    @import "bootstrap/scss/variables";
 
    @import "bootstrap/scss/mixins";
 
    @import 'bootstrap/scss/_progress.scss'; 
 
   @import 'bootstrap/scss/_buttons.scss';
 
</svelte:head>

Afterwards, I implemented the ControlButtons and ProgressBar components.

First, the ProgressBar will look like this: (TODO: fix it so it’s also striped)

<script>
    import Progress from 'sveltestrap/src/Progress.svelte';
 
    export let progressPercent = 0;
    export let loading = false;
</script>
 
<Progress value={progressPercent} animated={loading} striped=true>{progressPercent + "%"}</Progress>

You can see it has two input variables, one to show its loading, and another for the actual progress percentage.

Next, the control buttons:

<script>
    import { createEventDispatcher } from 'svelte';
    import { Button, Row } from "sveltestrap";
 
    export let disablePause = true;
    export let disableStart = false;
    export let disableReset = true;
    const dispatch = createEventDispatcher();
 
    function start(){
        dispatch('start');
    }
 
    function pause(){
        dispatch('pause');
    }
 
    function reset(){
        dispatch('reset');
    }
 
</script>
<div style="text-align: center">
    <Button color="primary" on:click={start} disabled={disableStart}>Start</Button>
    <Button color="secondary" on:click={pause} disabled={disablePause}>Pause</Button>
    <Button color="danger" on:click={reset} disabled={disableReset}>Reset</Button>
</div>

You can see that there are control elements to disable the buttons and there are output elements for binding with the clicks.

Finally, I implemented the logic in the app component:

<svelte:head>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
</svelte:head>
<script>
    import ProggressBar from './ProgressBar.svelte';
    import ControlButtons from './ControlButtons.svelte';
    let currIndex = 0;
    let loading = false;
    let paused = false;
    const maxIndex = 5;
 
    $: done = maxIndex == currIndex;
 
    function nextItem(){
        setTimeout(() => {
            currIndex++;
            if(currIndex == maxIndex || paused){
                paused = false;
                loading = false;
                return;
            }
            nextItem();
        }, 1000)
    }
 
    function start(){
       loading = true;
       nextItem();
    }
 
    function pause(){
        paused = true;
    }
 
    function reset(){
        currIndex = 0;
    } 
</script>
 
<div style="margin: 20px;">
    <ProggressBar progressPercent={currIndex/maxIndex*100} loading={loading}></ProggressBar>
</div>
<ControlButtons on:start={start} on:pause={pause} on:reset={reset} 
disableStart={loading || done} disablePause={!loading} disableReset={currIndex==0 || loading}></ControlButtons>

As you can see, the main logic here deals with disabling and enabling the buttons whenever necessary. But svelte does make it simpler. So given that it took me only about an hour’s work, I believe Svelte is the way to go and definitely deserves a thumbs up.

Leave a Reply

Your email address will not be published. Required fields are marked *