Thursday, February 25, 2021

Vue.js - HTTP Request

When entering an URL (such as google.com) in your browser, there are some steps before you see the webpage:

    1. Browser will contact DNS to translate the URL to the IP address.

    2. Browser will send request to server.

    3. Browser will parse the response from server.

    4. Browser will render the page based on the parsed response (HTML, CSS and JavaScript)

In reality, when launching our website (built by Vue.js), we need a server to host it.

In addition, instead storing data in pc memory (once you refresh the page, we lost data), we need send data via HTTP to backend server to store them in database. Later on, we can fetch it via HTTP again.

In order to focus on our Vue journey, we can utilize 'firebase' to quickly build a backend service. It is free and easy to setup.


REST API



Representational State Transfer

Structure:

    1. Resource (URL)

    2. Representation (text, HTML, or JSON)

    3. State Transfer (GET, PUT, POST, DELETE)



How to send http request to backend?



There are some options you can used in your Vue project.

1. fetch

2. axios


Handling Errors



There are two types of error:

1. Client side error

2. Server side error


Client Side Error



Using fetch(), then you need to add .catch() to get the client side error (browser error)

Ex:
  

        fetch(
            'url',
        )
        .catch((error=> {
// Client Side Error
            console.log(error);
        });

 

Server Side Error



Refer to this:

Using fetch(), server side error will not be caught by .catch().

You need to throw a new Error with the condition '!response.ok' in .then()

Ex:


fetch(
            'url',
)
        .then((response=> {
            if (!response.ok) {
// Server Side Error
// Raise an error to let .catch() to catch it
// Then we can handle the errors in one location
                throw new Error('Something went wrong...');
            }
        })
        .catch((error=> {
// 1. Client Side Error
// 2. Or Server Side Error raised by our JavaScript
            console.log(error);
        });

Wednesday, February 24, 2021

Vue.js - Form

Basic Usage



Refer to this:

v-model will ignore the initial value, checked or selected attributes found on any form elements.

It will always treat the current active instance data as the source of truth.


Input string


Ex:


<input
     id="user-name"
    name="user-name"
     type="text"
    v-model="userName"
/>



Input number


    
Ex:


<input
     id="age"
    name="age"
    type="number"
     v-model="age"
/>


NOTE:

If the input type is number, then v-model will help you to convert it to number automatically.


Dropdown



Refer to this:  

If the initial value of your v-model expression does not match any of the options, the <select> element will render in an "unselected" state.

It is recommended to provide a disabled option with an empty value.

Ex:


<select id="language" name="language" v-model="language">
     <option disabled value="">Please select one</option>
    <option value="en">English</option>
     <option value="fr">French</option>
</select>



Radio



Ex:


<h1>How to commute?</h1>
<div>
     <input
         id="driving"
        name="how-commute"
         type="radio"
         value="driving"
        v-model="howCommute"
     />
    <label for="driving">Driving</label>
</div>
<div>
     <input
         id="transit"
        name="how-commute"
         type="radio"
         value="transit"
        v-model="howCommute"
     />
    <label for="transit">Transit</label>
</div>


NOTE:

We need to add 'value' attribute to the input element.

Then Vue will know what value to put into v-model.


Checkbox



Ex:


<h2>Which instrument do you like?</h2>
<div>
     <input
         id="instrument-piano"
        name="instrument"
         type="checkbox"
         value="piano"
        v-model="instrument"
     />
   <label for="instrument-piano">Piano</label>
</div>
<div>
     <input
         id="instrument-drum"
        name="instrument"
         type="checkbox"
         value="drum"
        v-model="instrument"
     />
    <label for="instrument-drum">Drum</label>
</div>


NOTE:

Like radio box, we need to add 'value' attribute to the input element for checkbox.

Then Vue will know what value to put into v-model.


Single Checkbox



Ex:


<input
    id="agreement"
     name="agreement"
    type="checkbox"
     v-model="agreement"
/>
<label for="agreement">Agree?</label>



Form Validation



You can leave the form validation only when user click submit button.

Or you can use blur event to validate form when the event fire each time.

Ex:


<template>
     <input
         id="user-name"
        name="user-name"
         type="text"
         v-model.trim="userName"
        v-on:blur="validateForm"
    />
     <p v-show="userNameValidity == 'invalid'">
         User Name is not valid
    </p>
</template>

<script>
export default {
     data() {
         return {
             userName: '',
            userNameValidity: 'pending',
         };
    },
     methods: {
         validateForm() {
             if (this.userName) {
                 this.userNameValidity = 'valid';
            } else {
                 this.userNameValidity = 'invalid';
             }
         },
    },
};
</script>



Custom Input Component



Refer to this:

It is normal to build a custom form control as a component to make it reusable.

Vue define a rule to make it possible to use 'v-model' to make it two-way binding with custom form control.

For custom form control:

    Bind the value attribute to the modelValue prop
    On input, emit an update:modelValue event with the new value

For parent component:

    Then parent component can use 'v-model' to bind data to the custom form control.    


Ex:

// DescriptionControl.vue

<template>
    <label for="description">Description</label>
     <input
         id="description"
        name="description"
         type="text"
         v-bind:value="modelValue"
        v-on:input="$emit('update:modelValue'$event.target.value)"
     />
</template>

<script>
export default {
     props: ['modelValue'],
    emits: ['update:modelValue'],
};
</script>


// TheForm.vue

<template>
    <form v-on:submit.prevent="onSubmitBtnClicked">
         <div class="form-control">
             <label for="user-name">Name</label>
             <input
                 id="user-name"
                name="user-name"
                 type="text"
                 v-model.trim="userName"
             />
        </div>
         <div class="form-control">
<!-- Custom Control -->
             <description-control v-model="description"></description-control>
        </div>
         <div>
             <button>Submit Form</button>
        </div>
     </form>
</template>

<script>
import DescriptionControl from './DescriptionControl.vue';

export default {
    components: {
         'description-control': DescriptionControl,
    },
     data() {
         return {
             userName: '',
            description: '',
         };
    },
     methods: {
         onSubmitBtnClicked() {
        },
     },
};
</script>


Tuesday, February 23, 2021

Vue.js - More on Components

Component Registration


Component Registration could be global or local.


Global



Components you can use anywhere in your Vue app, which means you can use those custom html you defined in any template under its app. 

Take below codes as an example, all components are registered in global level (bind to app).

Ex:

// main.js

const app = createApp(App);

app.component('my-header'MyHeader);
app.component('my-body'MyBody);
app.component('my-content'MyContent);
app.component('my-message'MyMessage);

app.mount("#app");


// MyBody.vue

    <template>
        <header>
<!-- Using global component -->
            <my-message message="This is Body"></my-message>
            <my-content></my-content>
        </header>
    </template>


Since MyMessage Component was registered in Global Level, MyBody.vue can use it without any issues.


Local


Another approach is called Local Registration. Components can be used only by the component whom register them directly. Even its child component can not access those registered components.

Ex:
 
// main.js

    const app = createApp(App);

    app.component('my-header'MyHeader);
    app.component('my-body'MyBody);
    app.component('my-message'MyMessage);

    app.mount("#app");



// MyBody.vue

<template>
     <header>
         <my-message message="This is Body"></my-message>
<!-- Using local component -->
         <my-content></my-content>
    </header>
</template>

<script>
import MyContent from "./MyContent.vue";
export default {
// Local Registration
     components: {
         "my-content": MyContent,
    },
};
</script>


From the example above, since 'my-content' is only used by MyBody.vue, therefore, we can move it from global to local level.

And 'my-message' is used by plenty of components, so we keep it as global level.


Component Styling - scoped


It is called 'style encapsulation' to limit the CSS styling to the template under the same file (Single File Component).

It will not be applied to any child, sibling, or other components.

Ex:

// MyMessage.vue

<template>
     <header>
         <h1>{{ message }}</h1>
    </header>
</template>

    <script>
    export default {
        props: ["message"],
    };
    </script>

    <style scoped>
    h1 {
        colorblue;
    }
    </style>


If you want to know how Vue implemented it, you can inspect HTML elements, and you will notice that there is a special attribute added to html element.

<header data-v-345b333b="">
        <h1 data-v-345b333b="">This is Header</h1>
    </header>


Also, the styling will be changed by Vue as below.

 h1[data-v-345b333b] {
         color: blue;
    }



Slots


When your app get bigger, and most components have the same layout and styling, you might want to find a way to reduce the redundant code (both html and CSS) in your app.

Then you might want to abstract this common layout and styling as a wrapper (or container), which can wrap the content from input.

Then Vue provide 'slot' for you.

Ex: (Before using slot)

// MyLeftContent.vue
<template>
     <section>
         <h1>This is content area</h1>
        <p>Hello World</p>

         <h2>This is left section</h2>
    </section>
</template>

<style scoped>
section {
     background-colorblue;
}
</style>


// MyRightContent.vue

<template>
     <section>
         <h1>This is content area</h1>
        <p>Hello World</p>

         <h2>This is right section</h2>
    </section>
</template>

<style scoped>
section {
     background-colorblue;
}
</style>


You can see it is so duplicated both in template and style.

Using 'slot', then we can simplify our code.

Ex: (Using slot)

// ContentWrapper.vue

<template>
     <section>
         <h1>This is content area</h1>
        <p>Hello World</p>
<!-- Placeholder for the passing content -->
         <slot></slot>
    </section>
</template>

<style scoped>
section {
     background-colorblue;
}
</style>


// MyLeftContent.vue

<template>
    <content-wrapper>
<!-- Passing content -->
         <h2>This is left section</h2>
     </content-wrapper>
</template>


// MyRightContent.vue

<template>
     <content-wrapper>
<!-- Passing content -->
         <h2>This is right section</h2>
    </content-wrapper>
</template>



Named Slots



It will be useful if we can pass multiple content into a component.

Giving slots names can help to determine which slot to distribute.
 
Ex:

// ContenWrapper.vue

<template>
     <section>
         <h1>This is wrapper</h1>

        <div>
             <!-- slot: message -->
             <slot name="message"> </slot>
        </div>

         <!-- slot: default -->
         <slot></slot>
    </section>
</template>


// MyLeftContent.vue

<template>
     <content-wrapper>
         <!-- slot: message -->
        <template v-slot:message>
             <h1>{{ message }}</h1>
         </template>

        <!-- slot: default -->
         <h1>This is left section</h1>
     </content-wrapper>
</template>

<script>
export default {
     data() {
         return {
             message: "Hello left section",
        };
     },
};
</script>


NOTE: If you did not provide slot names, then it will be treated as 'default' name.


Slot with default content



To cover a case if some slots did not pass content, Vue provide 'default' content.

Ex:

// ContentWrapper.vue

<template>
     <section>
         <h1>This is wrapper</h1>

        <div>
             <!-- slot: message -->
             <slot name="message">
                 <!-- Default content -->
                 <h1>This is default message</h1>
            </slot>
         </div>

         <!-- slot: default -->
        <slot></slot>
     </section>
</template>


// MyLeftContent.vue

<template>
     <content-wrapper>
<!-- Did not pass content to message slot -->

         <!-- slot: default -->
        <h1>This is left section</h1>
     </content-wrapper>
</template>


Since MyLeftContnet.vue did not provide template with v-slot:message, the default content will be shown.


Using $slots



Instead providing default content, we can use $slots to determine slots are included or not, and then control the UI.

Ex:

// ContentWrapper.vue

<template>
     <section>
         <h1>This is wrapper</h1>
    
            <!-- Using $slot -->
        <div v-if="$slots.message">
             <!-- slot: message -->
             <slot name="message"></slot>
        </div>

        <!-- slot: default -->
         <slot></slot>
     </section>
</template>



Scoped Slots


It is advanced topic, and it is seldom to be used in simple projects.


Dynamic Components



Instead using v-if to show/hide the components, Vue provide a way to dynamically switch it.


Ex:

// App.vue

<template>
    <div>
         <button v-on:click="onBtnClicked('a')">Show Section A</button>
        <button v-on:click="onBtnClicked('b')">Show Section B</button>

<!-- Using dynamic component to switch -->
         <component :is="currentComponent"></component>
    </div>
</template>

<script>
export default {
     data() {
         return {
             currentComponent: "section-a",
        };
     },
    methods: {
         onBtnClicked(tab) {
             if (tab == "a") {
                 this.currentComponent = "section-a";
            } else {
                 this.currentComponent = "section-b";
             }
         },
    },
};
</script>



Keep Alive?


You may encountered an issue that using dynamic component will lost some user input.

The reason is that when switching to another component, Vue will create a new instance for you.

Vue provide an element called 'keep-alive' to solve this issue.



Teleport



Vue allows us to wrap our components (relating template) into Tree format.

However, some nested template will against Semantic HTML.

The common case is that templates need to use full window to display.

Vue provide a special tag called 'teleport' to help you move some template to other position with CSS selector.


Ex:

// App.vue

<template>
     <div>
         <button v-on:click="showModal = true">Open My Modal</button>
        <div v-if="showModal">
<!-- Using teleport to move this template to other position-->
             <teleport to="body">
                 <my-modal>
                     Modal Content
                     <button v-on:click="onCloaseBtnClicked">Close</button>
                </my-modal>
             </teleport>
         </div>
    </div>
</template>

<script>
export default {
     data() {
         return {
             showModal: false,
        };
     },
    methods: {
         onCloaseBtnClicked() {
             this.showModal = false;
        },
     },
};
</script>


// MyModal.vue

<template>
     <div class="container">
         <h1>Modal Header</h1>
        <slot></slot>
         <h1>Modal Footer</h1>
    </div>
</template>

<style scoped>
.container {
     positionfixed;
    background-colorgray;
    colorwhite;
     height100vh;
    width100vw;
}
</style>