Photo by on Oskar Yildiz Unsplash is a framework for building web applications. It has a reactivity system that allows you to model and manage your application state such that when data changes, it’s reflected in the UI, without you having to query the DOM. If you’ve built apps in vanilla JavaScript or with jQuery, you know you need to query DOM elements and update them in order to display some data or show some other application state. Vue.js For a large application this becomes difficult to manage. Last week I spent a few hours to learn some basic things, and adopted it in a side-project built in vanilla JS. I want to share with you some of my learnings and compare side by side the differences in code. The is a shopping list which uses . project progressive web application Hoodie If you want to follow along, you can download the in Vanilla JS while I show you how I added in Vue (follow this if you want to read about how I built the app in Vanilla JS, Hoodie, and Service Worker). source code link Adding items The application allows users to add shopping items to their shopping list. This is done in in the public folder. Lines 92 to 124 contains the markup for this: index.html <div> <div class="mdl-grid center-items"> <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label"> <input class="mdl-textfield__input" type="text" id="new-item-name"> <label class="mdl-textfield__label" for="new-item-name">Item Name</label> </div> <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label"> <input class="mdl-textfield__input" type="number" id="new-item-cost"> <label class="mdl-textfield__label" for="new-item-cost">Item Cost</label> </div> <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label"> <input class="mdl-textfield__input" type="number" id="new-item-quantity"> <label class="mdl-textfield__label" for="new-item-quantity">Quantity</label> </div> </div> <div class="mdl-grid center-items"> <button id="add-item" class="mdl-button mdl-js-button mdl-button--raised mdl-button--colored"> Add Item </button> </div></div> The code to handle data collection and saving the data is in the file . The function on line 28 collects the value from the input elements and saves the item. It is then bound to the click event of the button. Here’s the code: public/js/src/index.js saveNewItem() add-item function saveNewitem() { let name = document.getElementById("new-item-name").value; let cost = document.getElementById("new-item-cost").value; let quantity = document.getElementById("new-item-quantity").value; let subTotal = cost * quantity; if (name && cost && quantity) { hoodie.store.withIdPrefix("item").add({ name: name, cost: cost, quantity: quantity, subTotal: subTotal }); document.getElementById("new-item-name").value = ""; document.getElementById("new-item-cost").value = ""; document.getElementById("new-item-quantity").value = ""; } else { let snackbarContainer = document.querySelector("#toast"); snackbarContainer.MaterialSnackbar.showSnackbar({ message: "All fields are required" }); }} document.getElementById("add-item").addEventListener("click", saveNewitem); Switching to Vue In adopting Vue the first thing is to add a reference to Vue on your page. I added this to as follows: index.html <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> I also added a element with an id of to surround every page elements inside the body tag. This is needed because when we will initialise a Vue instance, we need to tell it what section of our app we want it to control. And by doing this I’m telling it to manage everything inside that block. I modified the markup to use some Vue directives. Vue Directives are special attributes with the prefix. Below is the updated markup div app v- <form v-on:submit.prevent="onSubmit"> <div class="mdl-grid center-items"> <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label"> <input class="mdl-textfield__input" type="text" id="new-item-name" v-model="name"> <label class="mdl-textfield__label" for="new-item-name">Item Name</label> </div> <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label"> <input class="mdl-textfield__input" type="number" id="new-item-cost" v-model.number="cost"> <label class="mdl-textfield__label" for="new-item-cost">Item Cost</label> </div> <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label"> <input class="mdl-textfield__input" type="number" id="new-item-quantity" v-model.number="quantity"> <label class="mdl-textfield__label" for="new-item-quantity">Quantity</label> </div> </div> <div class="mdl-grid center-items"> <button id="add-item" class="mdl-button mdl-js-button mdl-button--raised mdl-button--colored"> Add Item </button> </div></form> The directive if used to listen to DOM events. In code above it’s used in the form element to listen to the submit event. It also uses the modifier which tells the directive to call on the triggered event. We’ve used directives on the input elements. It is used to create two-way data bindings on form input. It will automatically pick the correct way to update the element based on the input type. We’ve used the modifier for the cost and quantity input elements. What it does is to automatically typecast the value from the input element to a number. This is because even if the type set is the value will always return string. So these modifiers I’ve used here helps short-circuit some extra checks checks we would have had to do. v-on .prevent v-on event.preventDefault() v-model .number type=number I created a new file to contain code equivalent of what is in but using Vue. Below is the code in this file, which creates a Vue instance with needed properties to handle the form event and collect data. index-vue.js index.js const vm = new Vue({ el: "#app", data: { name: "", cost: "", quantity: "" }, methods: { onSubmit: function(event) { if (this.name && this.cost && this.quantity) { hoodie.store.withIdPrefix("item").add({ name: this.name, cost: this.cost, quantity: this.quantity, subTotal: this.cost * this.quantity }); this.name = ""; this.cost = ""; this.quantity = ""; } else { const snackbarContainer = document.querySelector("#toast"); snackbarContainer.MaterialSnackbar.showSnackbar({ message: "All fields are required" }); } } }}); In the code block above, I created a Vue instance passing it an object that tells Vue how to setup the application. The property tells it the id of the DOM element that Vue will pick and define its territory. It’s within this territory that it picks Vue directives (and other things related to Vue), and when it is initialised, it sets up bindings and event handlers for the app. el The property contains the application state. All the properties in the containing object here will be added to Vue’s reactivity system when the Vue instance is initialised. It is this reactivity system that causes the UI to update when one of the values bound to the DOM changes. For example, the property is bound to the name input element using the directive. That directive sets up a two-way binding between the and the input element such that when a character is added or removed in the input field, it updates the property which will cause the value of the input to reflect the current value of . Same way other elements bound to the will also change as a user types in value. data name v-model="name" name name name name The property contains functions. The code above defines an function that is bound to the form’s submit event. methods onSubmit() Displaying Saved Items The functions saves an item to Hoodie. I want to display the items added in a table in the UI. The Vanilla JS app had the following markup for it: onSubmit <div class="mdl-grid center-items"> <table id="item-table" class="mdl-data-table mdl-js-data-table mdl-shadow--2dp"> <thead> <tr> <th class="mdl-data-table__cell--non-numeric">Item Name</th> <th class="mdl-data-table__cell--non-numeric">Cost</th> <th class="mdl-data-table__cell--non-numeric">Quantity</th> <th class="mdl-data-table__cell">Sub-total</th> <th class="mdl-data-table__cell--non-numeric"> <button class="mdl-button mdl-js-button mdl-button--icon"> <i class="material-icons">delete</i> </button> </th> </tr> </thead> <tbody> </tbody> </table></div><div class="mdl-grid center-items"> <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label"> <input class="mdl-textfield__input" type="number" id="total-cost" readonly value="0"> <label class="mdl-textfield__label" for="cost">Total Item Cost</label> </div></div> <script id="item-row" type="text/template"> <tr id='{{row-id}}'> <td class="mdl-data-table__cell--non-numeric">{{name}}</td> <td class="mdl-data-table__cell--non-numeric">{{cost}}</td> <td class="mdl-data-table__cell--non-numeric">{{quantity}}</td> <td class="mdl-data-table__cell">{{subTotal}}</td> <td class="mdl-data-table__cell--non-numeric"> <button class="mdl-button mdl-js-button mdl-button--icon mdl-button--colored" onclick="pageEvents.deleteItem('{{item-id}}')"> <i class="material-icons">remove</i> </button> </td> </tr></script> In the markup above I used micro-template because the table will contain dynamic data and I want a way to replace some placeholder with real data and attach it to the DOM. Below is the code that displays the items in the UI as it gets added: function addItemToPage(item) { if (document.getElementById(item._id)) return; let template = document.querySelector("#item-row").innerHTML; template = template.replace("{{name}}", item.name); template = template.replace("{{cost}}", item.cost); template = template.replace("{{quantity}}", item.quantity); template = template.replace("{{subTotal}}", item.subTotal); template = template.replace("{{row-id}}", item._id); template = template.replace("{{item-id}}", item._id); document.getElementById("item-table").tBodies[0].innerHTML += template; let totalCost = Number.parseFloat( document.getElementById("total-cost").value ); document.getElementById("total-cost").value = totalCost + item.subTotal;} hoodie.store.withIdPrefix("item").on("add", addItemToPage); In the code block above, it gets the script template from the DOM, replaces the placeholders with actual data, then appends it to the DOM. The total cost is also calculated and displayed in the UI. The Vue Alternative Transitioning to Vue I removed the script template from the page and updated the table element to use Vue’s directive which will loop through data property that contains the items. Below is the markup v-for <div class="mdl-grid center-items"> <table id="item-table" class="mdl-data-table mdl-js-data-table mdl-shadow--2dp"> <thead> <tr> <th class="mdl-data-table__cell--non-numeric">Item Name</th> <th class="mdl-data-table__cell--non-numeric">Cost</th> <th class="mdl-data-table__cell--non-numeric">Quantity</th> <th class="mdl-data-table__cell">Sub-total</th> <th class="mdl-data-table__cell--non-numeric"> <button class="mdl-button mdl-js-button mdl-button--icon"> <i class="material-icons">delete</i> </button> </th> </tr> </thead> <tbody> <tr v-for="item in items" :key="item._id"> <td class="mdl-data-table__cell--non-numeric">{{ item.name}}</td> <td class="mdl-data-table__cell--non-numeric">{{ item.cost}}</td> <td class="mdl-data-table__cell--non-numeric">{{ item.quantity}}</td> <td class="mdl-data-table__cell">{{ item.subTotal}}</td> <td class="mdl-data-table__cell--non-numeric"> <button @click="deleteRow(item._id)" class="mdl-button mdl-js-button mdl-button--icon mdl-button--colored"> <i class="material-icons">remove</i> </button> </td> </tr> </tbody> </table></div> <div class="mdl-grid center-items"> <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label"> <!-- <input class="mdl-textfield__input" type="number" id="total-cost" readonly value="0"> <label class="mdl-textfield__label" for="cost">Total Item Cost</label> --> <h4>Total Cost: {{ total }}</h4> </div> </div> There isn’t a big change in the markup. I copied the content from the previous micro-template and used Vue directives and text interpolation. I’m using the directive to render the list of items which will be gotten from a data property called . The respective columns renders the data using Vue’s text interpolation . This is similar to the placeholder we used with the micro-template. Total is displayed on the page using text interpolation. v-for items {{ item.name }} Updating the JavaScript code in will give us the following: index-vue.js const vm = new Vue({ el: "#app", data: { name: "", cost: "", quantity: "", items: [] }, computed: { // a computed getter total: function() { // `this` points to the vm instance return this.items.reduce( (accumulator, currentValue) => accumulator + currentValue.subTotal, 0 ); } }, methods: { ..... }}); hoodie.store.withIdPrefix("item").on("add", item => vm.items.push(item)); The Vue adaptation is much shorter and simpler. What I did in the code above was add a data property , which is what gets used in the directive seen earlier. When an item gets added Hoodie calls the function which runs to update the state and with Vue’s reactive system the UI is automatically updated. To calculate the total there’s no need to track items in the DOM. I used a computed property which runs a reduce function on . Now with Vue’s reactive system the UI gets updated whenever any of these values change. The good thing here is that I don’t have to worry about DOM manipulation in my code. So in fewer lines of code we achieved what required more code when using vanilla JS (I reckon it would be a similar thing with jQuery). items v-for vm.items.push(item) items Save items as a list After adding items, I want to save them for later reference and be able to add another list of shopping items. I have a button which will gather the items, save them as a group of items with hoodie, and allow the user add new set of items. Save List The Vanilla JS version had the button bound to a click event. Below is the markup and code that made it work //index.html<div class="mdl-grid center-items"> <button class="mdl-button mdl-js-button mdl-button--raised mdl-button--colored" onclick="pageEvents.saveList()"> Save List </button></div> //index.jsfunction saveList() { let cost = 0; hoodie.store .withIdPrefix("item") .findAll() .then(function(items) { for (var item of items) { cost += item.subTotal; } //store the list hoodie.store.withIdPrefix("list").add({ cost: cost, items: items }); //delete the items hoodie.store .withIdPrefix("item") .remove(items) .then(function() { //clear the table document.getElementById("item-table").tBodies[0].innerHTML = ""; //notify the user var snackbarContainer = document.querySelector("#toast"); snackbarContainer.MaterialSnackbar.showSnackbar({ message: "List saved succesfully" }); }) .catch(function(error) { //notify the user var snackbarContainer = document.querySelector("#toast"); snackbarContainer.MaterialSnackbar.showSnackbar({ message: error.message }); }); });} window.pageEvents = { deleteItem: deleteItem, saveList: saveList ....}; Vue Alternative Switching to Vue didn’t require much of a difference. I still had to bind to a click event and added the event handler method to the methods property in the Vue options object during initialisation. Below is the markup for it: <div class="mdl-grid center-items"> <button @click="saveList" class="mdl-button mdl-js-button mdl-button--raised mdl-button--colored"> Save List </button></div> The is a shorthand for which is used to listen to a DOM event. The same function from the Vanilla JS version is added to the methods property of the Vue object. @click="saveList" v-on:click=saveList" saveList Navigation Bar Now that the items can be saved as a list, I want to see a history with the total cost of each list in a period of time. It will be in another page and will look like what’s shown in the image below This page has its markup in and code to control the page in . This page share some code in common with which is the navigation bar at the top. The navigation bar contains the links links to different pages, the and links which when clicked brings up login or register dialog forms, and the button. public/history.html public/js/src/history.js index.html Login Register Signout In the version of the app that’s using Vanilla JS I duplicated the same HTML markup in both pages. Below is the markup for the Navigation bar: <header class="mdl-layout__header"> <div class="mdl-layout__header-row"> <!-- Title --> <span class="mdl-layout-title">Shopping List</span> <!-- Add spacer, to align navigation to the right --> <div class="mdl-layout-spacer"></div> <!-- Navigation. We hide it in small screens. --> <nav class="mdl-navigation mdl-layout--large-screen-only"> <a class="mdl-navigation__link" href="index.html">Home</a> <a class="mdl-navigation__link" href="history.html">History</a> <a onclick="pageEvents.showLogin()" style="cursor: pointer" class="mdl-navigation__link login">Login</a> <a onclick="pageEvents.showRegister()" style="cursor: pointer" class="mdl-navigation__link register">Register</a> <a onclick="pageEvents.signout()" style="cursor: pointer" class="mdl-navigation__link logout">Logout</a> </nav> </div></header><div class="mdl-layout__drawer"> <span class="mdl-layout-title">Shopping List</span> <nav class="mdl-navigation"> <a class="mdl-navigation__link" href="index.html">Home</a> <a class="mdl-navigation__link" href="history.html">History</a> <a onclick="pageEvents.showLogin()" style="cursor: pointer" class="mdl-navigation__link login">Login</a> <a onclick="pageEvents.showRegister()" style="cursor: pointer" class="mdl-navigation__link register">Register</a> <a onclick="pageEvents.signout()" style="cursor: pointer" class="mdl-navigation__link logout">Logout</a> </nav></div> From the markup you can see that when the links for Login, Register, and Logout are clicked, they call their respective methods. Those page event handlers are defined in index.js import * as shared from "shared.js"; .... shared.updateDOMLoginStatus();window.pageEvents = { showLogin: shared.showLoginDialog, showRegister: shared.showRegisterDialog, signout: shared.signOut}; The actual functions that gets called are defined in `shared.js`. Below are the functions in `shared.js` responsible for the navigation bar: //register dialog elementlet loginDialog = document.querySelector("#login-dialog");dialogPolyfill.registerDialog(loginDialog);let registerDialog = document.querySelector("#register-dialog");dialogPolyfill.registerDialog(registerDialog); let showLoginDialog = function() { loginDialog.showModal();}; let showRegisterDialog = function() { registerDialog.showModal();}; let showAnonymous = function() { document.getElementsByClassName("login")[0].style.display = "inline"; document.getElementsByClassName("login")[1].style.display = "inline"; document.getElementsByClassName("register")[0].style.display = "inline"; document.getElementsByClassName("register")[1].style.display = "inline"; document.getElementsByClassName("logout")[0].style.display = "none"; document.getElementsByClassName("logout")[1].style.display = "none";}; let showLoggedIn = function() { document.getElementsByClassName("login")[0].style.display = "none"; document.getElementsByClassName("login")[1].style.display = "none"; document.getElementsByClassName("register")[0].style.display = "none"; document.getElementsByClassName("register")[1].style.display = "none"; document.getElementsByClassName("logout")[0].style.display = "inline"; document.getElementsByClassName("logout")[1].style.display = "inline";}; let updateDOMLoginStatus = () => { hoodie.account.get("session").then(function(session) { if (!session) { // user is singed out showAnonymous(); } else if (session.invalid) { // user is signed in, but session is no longer authenticated showAnonymous(); } else { // user is signed in showLoggedIn(); } });}; let signOut = function() { hoodie.account .signOut() .then(function() { showAnonymous(); let snackbarContainer = document.querySelector("#toast"); snackbarContainer.MaterialSnackbar.showSnackbar({ message: "You logged out" }); location.href = location.origin; }) .catch(function() { let snackbarContainer = document.querySelector("#toast"); snackbarContainer.MaterialSnackbar.showSnackbar({ message: "Could not logout" }); });}; export { signOut, showRegisterDialog, showLoginDialog, updateDOMLoginStatus}; This code exports functions which was used in . The and functions displays a Modal for login and register respectively. The functions logs the user out and calls which hides the link and shows only the and links. The function checks if the user is authenticated and displays the appropriate links. This function is called when the page loads. index.js showLoginDialog() showRegisterDialog() signout() showAnonymous() Logout Register Login updateDOMLoginStatus Achieving a shared navigation bar required duplicating markup and querying DOM elements and applying CSS to show and hide links in the navigation bar. Let’s look at the Vue alternative. Vue Alternative Many web applications have portions that are the same across pages, for example navigation headers. These should be abstracted into some sort of container or component. Vue provides what is called a component, which can be used to solve the issue of the navigation bar in this example. Vue components are self-contained and re-usable. Moving to Vue components I created a new file . Inside it I defined a Vue component for the navigation bar as follows: shared-vue.js Vue.component("navigation", { props: ["isLoggedIn", "toggleLoggedIn"], template: `<div> <header class="mdl-layout__header"> <div class="mdl-layout__header-row"> <!-- Title --> <span class="mdl-layout-title">Shopping List</span> <!-- Add spacer, to align navigation to the right --> <div class="mdl-layout-spacer"></div> <!-- Navigation. We hide it in small screens. --> <nav class="mdl-navigation mdl-layout--large-screen-only"> <a class="mdl-navigation__link" href="index.html">Home</a> <a class="mdl-navigation__link" href="history.html">History</a> <a v-show="!isLoggedIn" @click="showLogin" style="cursor: pointer" class="mdl-navigation__link login">Login</a> <a v-show="!isLoggedIn" @click="showRegister" style="cursor: pointer" class="mdl-navigation__link register">Register</a> <a v-show="isLoggedIn" @click="logout" style="cursor: pointer" class="mdl-navigation__link logout">Logout</a> </nav> </div> </header> <div class="mdl-layout__drawer"> <span class="mdl-layout-title">Shopping List</span> <nav class="mdl-navigation"> <a class="mdl-navigation__link" href="index.html">Home</a> <a class="mdl-navigation__link" href="history.html">History</a> <a v-show="!isLoggedIn" @click="showLogin" style="cursor: pointer" class="mdl-navigation__link login">Login</a> <a v-show="!isLoggedIn" @click="showRegister" style="cursor: pointer" class="mdl-navigation__link register">Register</a> <a v-show="isLoggedIn" @click="logout" style="cursor: pointer" class="mdl-navigation__link logout">Logout</a> </nav> </div> </div>`, methods: { showLogin: function() { const loginDialog = document.querySelector("#login-dialog"); dialogPolyfill.registerDialog(loginDialog); loginDialog.showModal(); }, showRegister: function() { const registerDialog = document.querySelector("#register-dialog"); dialogPolyfill.registerDialog(registerDialog); registerDialog.showModal(); }, logout: function() { hoodie.account .signOut() .then(() => { this.toggleLoggedIn(); }) .catch(error => { alert("Could not logout"); }); } }}); In the code above we registered a Vue component named with an options object similar to what we used when creating a Vue instance. The first property is the Props are a way to pass data to a component. A component can define its own data, but in cases where a piece of application state needs to be used in different components, props are used. The props holds a boolean value showing if a user is authenticated or not. navigation props . isLoggedIn The second property contains the markup that will be shown in the page. The markup is almost exactly like the vanilla JS alternative in the previous section, except that we’ve used two Vue directives, and . The attribute is used for conditional rendering. Here I’m telling it to show the link when is true, or show and links when it’s false. Vue also provides and for conditional rendering and you can read more about them . The attribute is a shorthand for directive. I’ve set , , and as event handlers for click events of the respective links. template v-show @click v-show Logout isLoggedIn Login Register v-if v-else here @click v-on:click showLogin showRegister logout These functions are defined in the methods property. The function after successful signout, calls which is the props passed to this component. This will execute the function passed props, and is expected to change the value of props which is this component can’t modify. When it changes, Vue’s reactivity system will update the DOM accordingly. logout this.toggleLoggedIn() isLoggedIn This component is added to like as a custom element. I’ll remove the navigation bar markup from lines 59 to 84 and replace it with the following index.html <navigation v-bind:is-logged-in="isLoggedIn" v-bind:toggle-logged-in="toggleLoggedIn"></navigation> In the JavaScript code we declared props and , but when passing props these values use their kebab-cased equivalents. This is because HTML attributes are case-insensitive. I’ve used the directive to pass values for these props dynamically. Without this directive, it’ll be passed as a static value and the component will receive the string instead of a boolean value. We can as well use the shorthand for and it can be re-written as isLoggedIn toggleLoggedIn v-bind isLoggedIn : v-bind <navigation :is-logged-in="isLoggedIn" :toggle-logged-in="toggleLoggedIn"></navigation> The value is an application state and is a method declared in the Vue instance in as follows isLoggedIn toggleLoggedIn index-vue.js const vm = new Vue({ el: "#app", data: { name: "", cost: "", quantity: "", items: [], isLoggedIn: false }, computed: { .....//collapsed code }, methods: { toggleLoggedIn: function() { this.isLoggedIn = !this.isLoggedIn; }, ......//collapsed code }}); .....//collapsed code hoodie.account.get("session").then(function(session) { if (!session) { // user is singed out vm.isLoggedIn = false; } else if (session.invalid) { vm.isLoggedIn = false; } else { // user is signed in vm.isLoggedIn = true; }}); With the Vue alternative I’ve eliminated duplicate markup and if in the future I need to make any change for it, I will do it from one location, and this is done using Vue component. I eliminated having to traverse/query the DOM to select which elements to show or hide based on the authentication state. Login Dialog The and links shows a modal which allows a user to enter username and password to get authenticated. The markup for both are duplicated across pages just like the Navigation bar. This can be seen on lines 171 to 244 in and lines 100 to 158 in . Login Register index.html history.html <dialog id="login-dialog" class="mdl-dialog"> <h4 class="mdl-dialog__title">Login</h4> <div class="mdl-dialog__content"> <div class="mdl-grid center-items"> <!-- Simple Textfield --> <div class="mdl-textfield mdl-js-textfield"> <input class="mdl-textfield__input" type="text" id="login-username"> <label class="mdl-textfield__label" for="login-username">Username</label> </div> </div> <div class="mdl-grid center-items"> <!-- Simple Textfield --> <div class="mdl-textfield mdl-js-textfield"> <input class="mdl-textfield__input" type="password" id="login-password"> <label class="mdl-textfield__label" for="login-password">Password</label> </div> </div> <div class="mdl-grid center-items"> <!-- Simple Textfield --> <div class="mdl-textfield mdl-js-textfield"> <span id="login-error"></span> </div> </div> </div> <div class="mdl-dialog__actions"> <button onclick="pageEvents.closeLogin()" type="button" class="mdl-button close">Cancel</button> <button onclick="pageEvents.login()" type="button" class="mdl-button">Login</button> </div></dialog> <dialog id="register-dialog" class="mdl-dialog"> <h4 class="mdl-dialog__title">Login</h4> <div class="mdl-dialog__content"> <div class="mdl-grid center-items"> <!-- Simple Textfield --> <div class="mdl-textfield mdl-js-textfield"> <input class="mdl-textfield__input" type="text" id="register-username"> <label class="mdl-textfield__label" for="register-username">Username</label> </div> </div> <div class="mdl-grid center-items"> <!-- Simple Textfield --> <div class="mdl-textfield mdl-js-textfield"> <input class="mdl-textfield__input" type="password" id="register-password"> <label class="mdl-textfield__label" for="register-password">Password</label> </div> </div> <div class="mdl-grid center-items"> <!-- Simple Textfield --> <div class="mdl-textfield mdl-js-textfield"> <span id="register-error"></span> </div> </div> </div> <div class="mdl-dialog__actions"> <button onclick="pageEvents.closeRegister()" type="button" class="mdl-button close">Cancel</button> <button onclick="pageEvents.register()" type="button" class="mdl-button">Register</button> </div></dialog> The code to handle both login and register is defined in and used in shared.js index.js //shared.js //register dialog elementlet loginDialog = document.querySelector("#login-dialog");dialogPolyfill.registerDialog(loginDialog);let registerDialog = document.querySelector("#register-dialog");dialogPolyfill.registerDialog(registerDialog); let closeLoginDialog = function() { loginDialog.close();}; let closeRegisterDialog = function() { registerDialog.close();}; let showAnonymous = function() { ...}; let showLoggedIn = function() { ....}; let signOut = function() { ....}; let updateDOMLoginStatus = () => { ....}; let login = function() { let username = document.querySelector("#login-username").value; let password = document.querySelector("#login-password").value; hoodie.account .signIn({ username: username, password: password }) .then(function() { showLoggedIn(); closeLoginDialog(); let snackbarContainer = document.querySelector("#toast"); snackbarContainer.MaterialSnackbar.showSnackbar({ message: "You logged in" }); }) .catch(function(error) { console.log(error); document.querySelector("#login-error").innerHTML = error.message; });}; let register = function() { let username = document.querySelector("#register-username").value; let password = document.querySelector("#register-password").value; let options = { username: username, password: password }; hoodie.account .signUp(options) .then(function(account) { return hoodie.account.signIn(options); }) .then(account => { showLoggedIn(); closeRegisterDialog(); return account; }) .catch(function(error) { console.log(error); document.querySelector("#register-error").innerHTML = error.message; });}; export { register, login, closeRegisterDialog, closeLoginDialog, ...}; index.js //index.js window.pageEvents = { closeLogin: shared.closeLoginDialog, showLogin: shared.showLoginDialog, closeRegister: shared.closeRegisterDialog, showRegister: shared.showRegisterDialog, login: shared.login, register: shared.register, signout: shared.signOut}; Vue Alternative When switching to Vue I used separate component for both login and register component. Below is the component registration for login dialog Vue.component("login-dialog", { data: function() { return { username: "", password: "" }; }, props: ["toggleLoggedIn"], template: `<dialog id="login-dialog" class="mdl-dialog"> <h4 class="mdl-dialog__title">Login</h4> <div class="mdl-dialog__content"> <div class="mdl-grid center-items"> <!-- Simple Textfield --> <div class="mdl-textfield mdl-js-textfield"> <input v-model="username" class="mdl-textfield__input" type="text" id="login-username"> <label class="mdl-textfield__label" for="login-username">Username</label> </div> </div> <div class="mdl-grid center-items"> <!-- Simple Textfield --> <div class="mdl-textfield mdl-js-textfield"> <input v-model="password" class="mdl-textfield__input" type="password" id="login-password"> <label class="mdl-textfield__label" for="login-password">Password</label> </div> </div> <div class="mdl-grid center-items"> <!-- Simple Textfield --> <div class="mdl-textfield mdl-js-textfield"> <span id="login-error"></span> </div> </div> </div> <div class="mdl-dialog__actions"> <button @click="closeLogin" type="button" class="mdl-button close">Cancel</button> <button @click="login" type="button" class="mdl-button">Login</button> </div> </dialog>`, methods: { closeLogin: function() { const loginDialog = document.querySelector("#login-dialog"); dialogPolyfill.registerDialog(loginDialog); loginDialog.close(); }, login: function(event) { hoodie.account .signIn({ username: this.username, password: this.password }) .then(() => { this.toggleLoggedIn(); this.closeLogin(); }) .catch(error => { console.log(error); document.querySelector("#login-error").innerHTML = "Error loggin in"; }); } }}); It is registered with data, props, template and methods as properties of the options object passed to . Then on the pages I replace the markup with Vue’s custom element Vue.component() //index.html<login-dialog v-bind:toggle-logged-in="toggleLoggedIn"></login-dialog> Similar steps apply to the register dialog which I’ve skipped. I also skipped showing some parts of the app in order to avoid showing duplicate Vue syntax. Follow this if you want to learn how I built the whole app step by step. It also explained concepts such as and link Service Workers Push API. Conclusion So far I’ve shown you some of the changes I made to my application while moving from Vanilla JS to Vue.js. It’s not complicated to start using Vue if you’ve just learned how to build web apps with HTML, CSS and JavaScript (or jQuery). You don’t need to know ES6 or understand any build step to get started. I achieved less code duplication and better code organisation while using Vue. I only covered the basic things you need to understand to start using Vue, but of course there’s more to Vue. In a future post I’ll cover more concepts as I keep digging into it and hopefully share how to build a fairly complex single page applications with it. You can find the complete code for the Vanilla JS and Vue.js applications on GitHub using the links below. Shopping List — Vanilla JS Shopping List — Vue.js Also shared on Dev.to Peter Mbanugo is interested in offline-first. His current project is Hamoni Sync , a real-time application state synchronisation service. Reach him anytime at p.mbanugo@yahoo.com or at @p_mbanugo on Twitter.