Once the critical features were done, then in the third round of refactoring, I started to tackle the rest of the features by designing the remaining specialised components such as TaskListRestore , TaskListArchive , TaskListEdit , TaskBoardEdit , and TaskListActions .

After I got the first draft of the user interface done, then I revisited the components and started finding common behaviour to extract and build generic components so that I can re-use the logic.

In this round of refactoring, I extracted common behaviour and separated it to a generic component that can be reused anywhere in the app, such as Show Popup feature, which is used by TaskListRestore , TaskListArchive , TaskListEdit , TaskBoardEdit , TaskListActions . This generic component became DetailsPopup and DetailsDropdown component.

Now that we have looked at component design approach, let’s dive deeper and learn about individual application components and their purpose in the application.

Application components and their purpose

Breaking down the overall user interface into smaller components do not need to be a complicated process, however it requires a bit of extra efforts in code refactoring but it promotes modular structure for long-term maintainability of the code.

It’s better to define or at least think about the purpose or responsibility of a component as you break down the application user interface. Because quite often the responsibility of a component will drive its behaviour and if the behaviour is too complex then it is a good indication for further refactoring.

From my experience, most of the time, the responsibility of a component satisfies a particular feature of the application.

Let’s briefly look at application components and their responsibility.

App

App component is topmost level component which mounts on DOM when Vue.js is initialised. We’ll use Vue-Router library to build this multi-page application using <router-view> tag to display pages.

App.vue

App component is responsible to host top-level components such as AppHeader , RouterView , AppLoadingIndicator components. In addition, we’ll also wrap router-view with transition component — provided by Vue.js — to show smooth page transitions.

AppHeader

AppHeader component is responsible to show application navigation bar with related actions based on the page.

AppHeader.vue

For example, when the user is on Dashboard page then AppHeader will show TaskBoardEdit component to allow the user to create new or edit existing board.

Alternatively, when the user is on TaskBoard page, while viewing an individual board, then TaskListEdit and TaskListRestore components will be shown.

They allow the user to create new list or edit existing one and restore any archived lists. TaskListArchive will be visible if the user wants to archive any individual list.

AppLoadingIndicator

This component is responsible to show SVG loading animation, which indicates that the app is busy fetching information at the moment.

AppLoadingIndicator.vue

As shown in the above snippet, this component has no local state, rather it uses isLoading Vuex getter to show and hide loading indicator animation.

Note: Sam Herbert has created excellent SVG animations and made it available to the world, which I’m using as a loading indicator.

Dashboard

This component is responsible for:

show active and archived boards,

initiate editing board information and

let user archive, restore and navigate to the individual board.

TaskBoard

This component is responsible to show individual board contents using TaskList component and allow the user to reorder the lists using drag and drop functionality. In addition, this component also set the current board as active using setActiveBoard Vuex action.

TaskBoard.vue

We are iterating through the lists and displaying them in column format using TaskList component. To implement drag and drop reordering of the lists, we are wrapping TaskList component with draggable component.

TaskList

The responsibility of TaskList component is to show draggable and editable items using TaskListItem component and also allow the user to edit and archive list using TaskListActions component.

TaskList.vue

The draggable TaskListItem component is wrapped with draggable component. And it’ll be rendered for each item in the list. While TaskListItem outside of draggable component, is used to add a new TaskListItem .

TaskListItem

The TaskListItem component is responsible to,

display individual item details

add new item

update existing item and

delete existing item

This component can be in display or edit mode at any given time and isEditing component state variable is used to toggle the component between display and edit modes.

Initially, for the display mode, the component uses isNewItem and displayText computed properties to display appropriate text and when user click on an item, then isEditing variable is set to true and the component shows the form.

It’s important to note that, Add and Update are very similar operations as far as component is concerned and it’s not components job to decide whether to add or update the data.

Therefore, the component will only validate the data when the save button is clicked and then simply call saveTaskListItem Vuex action with the data. The action use SAVE_TASKLIST_ITEM Vuex mutation and that will decide whether to update an existing item or add a new item in the list.

TaskListActions

The responsibility of TaskListActions component is to show Edit and Archive actions when user click on “…” in the list header.

TaskListActions.vue

The Edit action allows user to edit list details and archive action allows the user to archive the list .

TaskListArchive

The responsibility of TaskListArchive component is to show a popup to confirm list archive action.

TaskListArchive.vue

If the user clicks on “Yes, please” button then archiveTaskList Vuex action is called to archive the list.

TaskListEdit

The responsibility of this component is to show a popup with the form to update list name.

TaskListEdit.vue

When the user save the form, then handleTaskListSave method validates the form and if validation is successful then saveTaskList Vuex action is called to save the list details.

Note that, the TaskListEdit component is also being used to create a new task list.

TaskListRestore

The responsibility of this component is to show a popup with archived task lists. The component iterates over archivedLists array and shows task lists with a Restore button.

TaskListRestore.vue

When the user clicks on restore button then restoreTaskList Vuex action is called to restore the list.

TaskBoardEdit

The responsibility of this component is to show a popup with the form to update the board name and description details.

When user save the form, then handleSaveBoard method validates the form and if validation is successful then saveTaskBoard Vuex action is called to save the board details.

Note that, the same component is being used to handle the creation of a new task board.

In this section, we have looked at application components and their responsibility, now let’s take a close look at how drag-n-drop functionality works as it’s the key feature of this app to function.

Implementing Drag and Drop

HTML5 provides native events to implement drag and drop functionality, however, when you need to implement the same events with multiple components then your user interface components get complicated real fast.

Therefore, to cut down the boilerplate code from the app, I have used VueDraggable component from sortable.js library. That enabled me to quickly implement drag and drop on the task lists and task list items with few lines of code.

Let’s look at the simple example of how to use VueDraggable component.

Drag and drop items in single list

In the demo, we have a single list object with an items array which looks like the snippet below

list: {

id: "1",

name: "todo",

items: [

{

id: "1",

text: "Build the feature #1"

},

{

id: "2",

text:"Test the feature #1"

},

...

...

]

}

Now to implement drag and drop on individual items in the list, we have created TaskList and TaskListItem components.

TaskList component receives list object through props from the parent component and then iterate over items computed property to display individual TaskListItem components.

// task-list-template

<div class="col-3 list-column list-width">

<div class="heading">

<h4 class="heading-text text-center">

{{ list.name }}

</h4>

</div>

<draggable

v-model="items"

v-bind="dragOptions">

<TaskListItem

class="task-item"

v-for="(item,index) in items"

:key="index"

:item="item">

</TaskListItem>

</draggable>

</div>

In the snippet above, a key thing to note is that we are wrapping the TaskListItem with draggable component and using items computed property with v-model directive to let draggable component manage the index of individual item in the provided data.

In addition, we are binding various options of the component with dragOptions computed property using v-bind directive.

computed: {

dragOptions() {

return {

animation: "200",

ghostClass: "ghost",

group: "task-list-items"

}

}

}

dragOptions computed property will return a JavaScript object with the animation , ghostClass and group properties.

In dragOptions object,

animation will control the duration of animated drag and drop effect

will control the duration of animated drag and drop effect ghostClass will display a placeholder on the anticipated drop position and,

will display a placeholder on the anticipated drop position and, group property is quite important as it is the key to enable drag and drop and collectively identify and group the draggable elements in the user interface.

Now, let’s understand how the index of an individual item is updated.

The code snippet below shows items computed property which is written in verbose format with get and set functions.

// TaskList component

computed: {

items: {

get(){

return this.list.items

},

set(value){

this.list.items=value

}

}

}

Since draggable component uses items computed property with v-model directive, therefore with every drag and drop, it will call set function with updated data. Now to persist, we are simply updating items property of list object with the received data.

Tip: if you are using Vuex for the data then, in set function you can use Vuex action / mutation to update the state in Vuex store. In fact, we are using Vuex with draggable component in the app.

So far we have seen how to use draggable component to implement drag and drop items in single list.

Now let’s take it up a notch and implement,

drag and drop the entire list

drag and drop items across the lists

Take a look at the demo below.

We are showing multiple lists instead of a single list, allowing entire lists to be rearranged and items can be moved across the lists too.

Task Board Implementation

To show multiple lists, we have updated the data to have board as top level object which has lists array property to represent multiple lists and each list has items array property to represent individual list items.

board: {

id:"1",

name:"Project Tracker",

description: "A board to track a project",

lists: [

{

id: "1",

name: "todo",

items: [ ... ]

},

{

id: "2",

name: "doing",

items: [ ... ]

},

{

id: "3",

name: "done",

items: [ ... ]

},

{

id: "4",

name: "deploy",

items: [ ... ]

}

]

}

To show the single board, we have built a new component called TaskBoard . TaskBoard component is used to show multiple lists and manage the reordering of lists. While individual items in the list are managed by TaskList component as seen in previous demo.

To implement reordering of lists, in TaskBoard component we are now wrapping TaskList component with draggable component and using lists computed property with v-model directive. That will manage the index of individual lists in the provided data.

<draggable

v-model="lists"

v-bind="dragOptions"

class="row flex-nowrap my-3"

>

<TaskList

v-for="(listItem, index) in lists"

:key="index"

:list="listItem">

</TaskList>

</draggable>

In addition to that, we are binding multiple properties of draggable component by using dragOptions computed property with v-bind directive.

The dragOptions computed property will return a plain JavaScript object that contains animation , ghostClass , handle and group properties.

computed: {

dragOptions() {

return {

animation: "200",

ghostClass: "ghost",

handle: ".heading",

group: "task-board-lists"

}

}

}

Similar to the previously discussed dragOptions object,

animation will control the duration of animated drag and drop effect

will control the duration of animated drag and drop effect ghostClass will display a placeholder on the anticipated drop position

will display a placeholder on the anticipated drop position handle will identify the drag handle element, and

will identify the drag handle element, and group will collectively identify and group the draggable elements in the user interface.

Note that, the name of the group of list elements, task-board-lists , is different than the group of items task-list-items . This is intentional to assist draggable component to correctly distinguish between two separate groups of draggable elements.

draggable component is smart enough to understand that we have multiple lists and items belonging to different groups and it only allow drag and drop between the elements of the same group.

By explicitly naming different drag and drop groups allow us to move an item from one list to another without any drama and allowed lists to be reordered.

Now let’s see how reordered lists are persisted. Similar to the previous demo, we are using lists computed property in verbose format as well.

computed: {

lists: {

get() {

return this.board.lists

},

set(value) {

this.board.lists = value

}

}

...

}

When the TaskList is reordered, then draggable component will call set function of lists computed property with updated array to reflect the new positions of lists. To persist the change, we are simply overwriting the lists array in the board object with received data.

As demonstrated in this section, the concept of drag and drop is super easy to implement at multiple levels (reorder the entire list and reorder / move items across the lists) using draggable component.

You can even take this concept further and have fun in building a more complex drag and drop interfaces like,

Admin dashboard with component widgets (e.g Salesforce admin dashboard)

Customer facing website/page builder (e.g Website builder, WordPress Theme Builder)

In the next section, we will dig deeper and understand the concept of slots to design reusable components used in the app.

Building Reusable Components

There are few different framework features and design patterns that you can use to create reusable components in Vue.js such as Mixins, Slots, Scoped Slots, functional components and Renderless components etc.

While developing this app, I came across a few components that have common behaviour, such as, contents need to show up in a popup, but the content has different markup and implements a different set of functionalities. Therefore, I decided to use slots to design a generic popup component.

With slots, child component usually has one or more <slot></slot> tags in the markup, which are simply a placeholder for the content. When parent component uses the child component, then this slot will provide a space for the content and can even implement interactivity within as well.

To understand slots in layman terms, take an example of a notebook with blank-lined pages. The lined-pages can be considered as slots because they will be filled-out by the person who buys this notebook.

Now, if the notebook is bought by a school kid, then it will become a school notebook and probably contains classroom notes. Similarly, if it is bought by an office worker, then probably it will contain meeting minutes.

So we can see the pattern here that the same notebook can be used by different users for different purposes. However, notebook only provides blank-lined pages, and it’s totally up to the user of the notebook as to what content goes into it.

Let us look at an example of slots with the demo. I have built a DetailsPopup component which is using html5 <details> and <summary> tags to construct a reusable popup component.

Using Vue.js Slots

In the summary tag, we are using a slot named handle which will display the markup to open the popup and inside the div tag we are using a slot named content to display the popup content.

DetailsPopup component has additional methods open and close , that will simply add and remove open attribute to show and hide popup. In addition to the methods, the component also emits popup-toggled custom event with the current state of the popup to indicate whether it is currently open or closed.

We have TaskListRestore , TaskListArchive , TaskListEdit , TaskBoardEdit components using the DetailsPopup component in the application.

Let’s take an example of TaskListEdit component.

TaskListEdit.vue — Specialised wrapper component using DetailsPopup component

In the code snippet above, TaskListEdit component is using DetailsPopup as child component and providing the markup for slot named handle and content and also implementing the functionality to validate and save the form.

Since TaskListRestore , TaskListArchive , TaskListEdit , TaskBoardEdit are placed in the AppHeader component, which imposes another problem related to component communication as the components don’t follow parent→child component composition model. We can’t simply pass data to them using props since they are placed in different component altogether.

So in the next section, we will talk about component communications in the context of composition.

Component Communication

In the basic component composition such as parent→child relationship, the recommended way to pass the data from the parent component to the child component is via props , and the child can communicate back to the parent by emitting an event using $emit .

The situation gets complicated when the components do not fit into traditional parent→child composition. Then in that case, you can use EventBus pattern to communicate between components that can be placed anywhere in the user interface and still listen and respond to the custom events.

In the Event Bus pattern, we use a Vue instance to emit, listen and respond to the custom events across the app. It is a generic but a powerful pattern and can be used by Vuex actions as well to emit or listen to the custom events.

Event Bus can be set up with just two lines of code.

Setup Event Bus — utils/bus.js

Then Bus can be used to emit and listen to the custom events as shown below.

Usage of Event Bus Pattern

Let’s take an example of TaskList , TaskListActions and TaskListEdit components.

TaskList.vue

TaskListActions component is used as a child component in TaskList component and we are passing board and list information as props to TaskListActions component.

TaskListActions.vue

In TaskListActions component above, showListEditPopup method emits tasklist-editing event and list data as a payload, while showArchiveListPopup method emits tasklist-archiving event on the event bus with list and board data as a payload.

TaskListEdit.vue

On the receiving end, in TaskListEdit component, we are setting up an event listener in mounted() lifecycle hook along with event handler.

Bus.$on("tasklist-editing", this.handleTaskListEditing)

We are using $on method of Bus object to start listening to the events and providing handleTaskListEditing function to handle the event. The responsibility of handleTaskListEditing method is to update listForm variable with the received data and show the popup for editing.

In practice, an event bus pattern goes hand-in-hand with Vuex and most of the time receiving component uses Vuex actions to update the state with new information.

So let’s look at the data and the state management using Vuex.

Data and state management

In this app, we have structured our board's data in hierarchical format. That means, we have an array of boards where each individual board can have multiple lists and each lists can have one or more items in them.

Here is the sample data for an individual board in the app.

Data format of an individual board

Above snippet show the format of an individual board. Each board has id , name , description , archived , lists fields in it. archived is a Boolean field to indicate whether the board is archived or not and lists field is an array of list objects.

Each list contains id , name , headerColor , archived and items fields. Where archived field indicates whether the list is archived or not and items field is an array of item objects which contain id and text fields.

Now that we understood the structure of the data, let’s see how Vuex is used in the app.

State Management

There are two types of states in a Vue application, local component state and global application state. Local component state is useful for defining the behaviour of an individual component, while global application state is useful when you have multiple components sharing a certain piece of the data.

For global application state, we are using Vuex library which is developed and maintained by Vue.js team.

Vuex is used as centralised state management solution and it provides controlled update of the application state through actions and mutations. When I said controlled update, that means you can’t just modify the global state, any updates to the state must go through mutations.

The following is the quick primer on Vuex in context of the application.

Vuex Store

Vuex store is a container that encapsulates application state , getters , mutations and actions and make the application state reactive. When the application state variable is updated, then any user interface components using the state variable will be updated with new information automatically.

Vuex store — store/index.js

Vuex State

Vuex state is a centralised and shared data used by this app. It is a plain old javascript object (POJO) with variables defined in key-value pair format.

Vuex state — store/state.js

In above code, isLoading state variable is used by AppLoadingIndicator component to show a loading indicator animation when Vuex action fetches the data and load it in the state.

activeBoard variable holds the reference to the board which is currently open by the user. And boards variable is an array which contains the information about individual boards.

Vuex Getters

Vuex getters are functions that are used to access the state variables, however you can also use them to return transformed data if required.

Vuex Getters — store/getters.js

For example, in the above code snippet, archivedBoards filters the boards state variable and returns only the archived boards. Whereas, the archivedLists getter checks if the activeBoard is set and then only it returns the archived lists, otherwise returns an empty array.

Vuex Mutations

Vuex mutations are the only functions which can update the state variables with new data. Mutation functions receive state object as the first parameter, followed by any payload parameter.

// store/mutations.js

// Set Initial Boards Data SET_INITIAL_DATA(state, payload) {

state.boards = payload

}

The code snippet above declares a SET_INITIAL_DATA mutation which receives state object and payload as parameters. Through the state object, we can access boards variable and update it with the payload.

Vuex mutations can be accessed directly in Vue components by using mapMutations helper from Vuex, but as your application grows and you need to take care of certain tasks such as,

Call external API to get or update data

Show toast notification

Send email notifications

Route user to a specific route

With the above implementations, your component code will get unnecessarily complicated.

A better way to organise your code is via Vuex actions , as they provide an abstraction layer around Vuex mutations and since they are asynchronous in nature, they are more suitable to invoke any business logic prior or after calling Vuex mutations .

You can read more about Vuex mutations in the official Vuex documentation.

Snippet below shows all Vuex mutations used by this application.

Vuex Mutations — store/mutations.js

Vuex Actions

Vuex actions uses mutations to manipulate the data in the state. The key difference between mutations and actions is that, mutations are called synchronously by Vuex, but actions are asynchronous in nature. Therefore, you can use business logic in actions and then call appropriate mutation to update the state.

You can read more about Vuex actions in the official Vuex documentation.

To fetch and load data, I have stored sample data in a github repository and use axios to fetch the data when the application boots up for the first time.

Let’s take an example of fetchData Vuex action, which is used to fetch and load sample data used by the app. Since all Vuex action receives a context object as first parameter, we are simply destructuring the object and pulling out commit method to call our mutations.

Vuex Action — fetchData

In above code snippet, I’m calling SET_LOADING_STATE mutation to update isLoading state variable, that will make AppLoadingIndicator component to show loading indicator animation while axios get the sample data from the URL.

Upon receiving the data, I’m calling SET_INITIAL_DATA mutation to update boards state variable with received data and then calling SET_LOADING_STATE mutation to update isLoading state variable to false, that will make AppLoadingIndicator component to hide loading indicator animation.

The snippet below shows all Vuex actions used in the app.

Vuex Actions — store/actions.js

Local component state helps you to implement certain behaviour in your component, while Vuex provides global application state which can be used by multiple components and since it is reactive, therefore any updates to certain piece of state data automatically triggers re-rendering of components which are dependent on the state data.

Vuex provides structured and controlled state update mechanism to the application through actions and mutations, which makes debugging your application easier.

After implementing Vuex, one of the certain benefit you may see is that local component state is reduced to minimal or non-existent and that’s because components starts to rely more upon shared state through Vuex getters rather than local component state.

What’s next?

In this article, we discussed the top-down approach to build the user interface incrementally using generic and specialised Vue components and understood how to apply the approach in the context of the application.

We also discussed slots as one of the approach to build reusable components and learned how to implement Drag-and-Drop along with application of Event Bus pattern to communicate between components when Parent→Child composition of components doesn’t serve the purpose.

Finally, we discussed data-structure of the app and understood how to use Vuex to manage the global application state using store , state , getters , mutations and actions .

We’ll discuss Form Validation, Vue.js Plugins, Bundle Optimisation and Application Deployment in the next part of this article.

Update:

Task Management Application using Vue.js — Part 2

https://levelup.gitconnected.com/task-management-application-using-vue-js-part-2-d785a96acda6