Get away from jQuery to Svelte, as it were

Hello everyone.

This is a continuation of the article " Get away from jQuery to Svelte, no pain ."

Below I will talk about the difficulties that I encountered, there were not many of them, and only one was so fundamental where I could not cope without the support of the community .

Introduction


I planned to rewrite the front-end in pieces, itā€™s not something that would not work out at all, it didnā€™t work out completely - I had to rewrite in large pieces.

First, because the jQuery approach is imperative, the Svelte approach is declarative.

Secondly, because using jQuery, our scope is always global, we can access all elements of a web page from any line of code, we access them by ID or CSS selector, while Svelte recommends using components and inside the component we see only the component itself, we have no external or internal elements, and we are not able to access them directly.

With Svelte, we get real OOP: we cannot make changes ourselves, we can only tell the component about the need for changes. How these changes will be made, knows only the code inside the component.

And that's great :)

Svelte has two mechanisms for communicating with components.

Binding variables (binds, mapping)


We declare a variable and map it to the component attribute:

<script>
    import DetailTaskView from './DetailTaskView.svelte';
    let task = {};
    let isModal = false;
    function renderTask(data) {
        const isSuccess = data.hasOwnProperty(0);
        if (isSuccess) {
            isModal = true;
            task = data[0];
        }
    }
    import {fade} from 'svelte/transition';
</script>
{#if isModal}
    <div transition:fade>
        <DetailTaskView bind:isModal="{isModal}" {...task}/>
    </div>
{/if}

What have we done?


We declared two local variables ā€œtaskā€ and ā€œisModalā€, ā€œtaskā€ is information to be displayed in the component, data is only displayed, will not be changed, ā€œisModalā€ is the flag of component visibility, if the user clicked the cross on the component, then the component should disappear , the cross belongs to the component, so we donā€™t know anything about the click, but we learn that the value of the ā€œisModalā€ variable has changed and thanks to the reactivity, we will work out this new value.

If we need two-way binding, then we write ā€œbind:ā€, a change in the value inside the component will be reported to the ā€œparentā€ component.

We can use an abbreviated form if we only need to tell the component the values, and if the name of the attribute of the component matches the name of the variable, we can write "{task}" or use the destructor "{... task}".

Conveniently.

But if we have another component embedded in one component, and there is also a third one, then of course there is a bollerplate for scrolling values ā€‹ā€‹up and down the nesting hierarchy.

Event bubbling


I can be mistaken with the terminology, do not kick much.

The parent component can handle the events of the child component, but only those events that the child component reports.

<!-- App.svelte   -->
<script>
    import SearchForm from './SearchForm.svelte';
    async function applySample(event) {
        const sample = event.detail.sample;
        if(sample){
            search(sample);
        }
        if(!sample){
            renderPage();
        }
    }
</script>
<div class="container">
    <h1>  </h1>
    <SearchForm on:search={applySample}/>
</div>

<!-- SearchForm.svelte    -->
<script>
	import { createEventDispatcher } from 'svelte';
	const dispatch = createEventDispatcher();
	let sample = '';
    function search(event) {
        event.preventDefault();
        dispatch('search', {
            sample: sample
        });
    }
</script>
<form class="form-horizontal" id="search"
on:submit={search}>
    <div class="form-group">
        <label class="control-label col-sm-2" for="sample">
        
        </label>
        <div class="col-sm-10">
            <input id="sample" class="form-control" type="search"
             placeholder="  "
             autocomplete="on" bind:value={sample}
             />
        </div>
    </div>
</form>

What's going on here ?


In the parent component, the "applySample" function is executed by the "on: search" event of the "SearchForm" child component, this function receives the parameters (event.detail) from the event object and processes them.

What happens in a component?


The ā€œvalueā€ attribute of the input element is mapped to the ā€œsampleā€ variable, by the ā€œon: submitā€ event (of the ā€œformā€ element) the ā€œsearchā€ function is executed, which raises the 'search' event and writes the {sample: sample object to the "detail" property } - that is, the value of the search string.

Thus, the value of the search string is passed to the parent component and it is he who decides what to do with this value.

The component is only responsible for displaying the input form and transmitting the entered value, the component does not implement the search and displaying the results, so we share responsibility.

The beauty!

The transition from imperative to declarative


Here, unfortunately, it doesnā€™t work out to show the difference just as clearly. In words, it sounds like this: if, when using jQuery, I created html markup and then inserted it into the right place, then with Svelte I generate an array with the attributes of the components and then in the loop I add the components with pre-calculated attributes:

<!-- Paging.svelte        -->
<script>
    import {createEventDispatcher} from 'svelte';
    import OrdinalPlace from './OrdinalPlace.svelte';
    let pagingPlaces;
    //         pagingPlaces
    function addPlace(
        paging = [], index = Number.NEGATIVE_INFINITY,
        text = "", css = "") {
        paging.push({index:index,text:text,css:css});

        return paging;
    }
    const dispatch = createEventDispatcher();
    function browsePage(event) {
        const pageIndex = event.detail.index;
        dispatch('move', {
            index: pageIndex
        });
    }
</script>

{#if pagingPlaces.length}
    <table class = "table table-hover-cells table-bordered">
        <tbody>
            <tr>
            {#each pagingPlaces as place (place.index)}
                <OrdinalPlace on:move="{browsePage}"
                {...place}>
                </OrdinalPlace>
            {/each}
            </tr>
        </tbody>
    </table>
{/if}

<!-- OrdinalPlace.svelte     ""      -->
<script>
    export let index = -1;
    export let text = "";
    export let css = "";

    let number = index +1;
    function skip() {
        return !(text === "");
    }

    let letSkip = skip();
    let noSkip = !letSkip;

    import { createEventDispatcher } from "svelte";
    const dispatch = createEventDispatcher();
    function moveTo() {
        if(noSkip){
            dispatch("move", {index:index});
        }
    }
</script>
<td class="{css}" on:click="{moveTo}">
    {#if letSkip}
        {text}
    {/if}
    {#if noSkip}
        {number}
    {/if}
</td>

How it works ?


When creating the Paging component, we form an array of ā€œelementsā€ to go to certain pages - ā€œpagingPlacesā€, then cycle through all the elements and insert a component to display one paging position - ā€œOrdinalPlaceā€.

Again, a declarative approach, we do not form each position ourselves, we tell the component that we need to display a position with such attributes.

Here we see a confused case of the emergence of an event. To go to the search results page, the user clicks on the ā€œOrdinalPlaceā€ component, this component cannot load the page, therefore it creates a ā€œmoveā€ event with the page index parameter and this event picks up the parent component - ā€œPagingā€, which also cannot load the page therefore it raises the 'move' event, and the next parent component already picks it up and somehow processes it.

Svelte and the component approach push us to share responsibility and follow SOLID.

Biggest ambush


The example above shows a solution to a fundamental problem that I would not be able to handle without a hint. Svelte caches all components and you need to help him track changes in these components.

Here is the code in question:

            {#each pagingPlaces as place (place.index)}
                <OrdinalPlace on:move="{browsePage}"
                {...place}>
                </OrdinalPlace>
            {/each}

To display the list of pages in the paging, we ran through the array and Svelte assigned a component an array index to each component, now Svelte makes a decision to redraw the component based on this index, if you do not specify the index while iterating through the array elements, then do not understand what , I tried to understand for a day, then I asked for help from the hall and in the hall I did not immediately find a person who was well acquainted with this rake, but they helped me, thanks to the guys.

When working with arrays, keep this in mind: any pass through the array must use an index, again:

            {#each pagingPlaces as place (place.index)}

"PagingPlaces as place (place.index)" - be sure to use.

Of course, if you have previously worked with React / Vue, then you are probably already familiar with this feature.

Visual effects


My application used modal windows. jQuery for this sets the markup requirements, without it, the jQuery.modal () method will not work.

Svelte makes this easier:

{#if isModal}
    <div transition:fade>
        <DetailTaskView bind:isModal="{isModal}" {...task}/>
    </div>
{/if}

Specifically, "transition: fade" is responsible for the disappearance / appearance of elements on the page.
No one dictates to us what markup we should have.

It's good.

In addition to this animation, Svelte has a couple more: fly and tweened , examples from the links in the tutorial.

Other


Variable names


The trouble with the naming of variables / parameters / attributes, you have to use one word to call both the property of the object and the variable that you write there, the handset rule when you need to talk about the code on the phone, so that you donā€™t get confused at that end and understand everything, such repeated names violate.

Ajax


This does not apply to Svelte, but concerns the rejection of using jQuery, jQuery.ajax can be replaced with fetch (), I made such a replacement, you can see it in the repository.

Conclusion


Moving from using jQuery to using Svelte will require rewriting the logic for creating markup, but it is not as difficult and as long as it may seem, especially if your code doesnā€™t sin with this.

Svelte simplifies your markup and shortens JS code, using Svelte makes your code more reusable and resistant to random errors.

Use Svelte, it will be good for you and your customers!

References


Official site of Svelte
Repository with the transition from using jQuery to using Svelte
Channel of the Russian-speaking community Svelte in Telegram

Thank you for reading.

PS: I donā€™t know why Habr excludes the colon from the link to the community channel, the correct link line is: tg: // resolve? Domain = sveltejs

All Articles