Most of the attention that JavaScript gets is all about creating large, monolithic Single Page Applications (SPAs). But the reality is that a great percentage of websites still use much simpler jQuery and vanilla JavaScript. Without going all-in on moving everything to a SPA, can you gain some of the benefits of using a framework to simplify your code and make it more reliable and testable? Sure, you can.
In many cases, moving to a SPA framework means a complete re-thinking of your application. It's a change in how you approach building applications. I whole-heartedly recommend that you think about it this way if you're building new applications, as it can really change the way you approach Web development, but…
In many cases, it's beneficial to ramp up to these technologies. Tearing down your jQuery empire and adding something like Angular, Vue, or React is a big leap. That's one of the reasons I love how Vue works.
In this article, my goal is to give you, the jQuery user, a taste of the different approach that Vue takes and how this can improve your code, your markup, and your ability to build apps quickly. Yeah, quickly.
What's Wrong with jQuery?
Nothing's wrong with jQuery. Really, nothing. It's been pivotal to creating most of the great websites you know. jQuery was responsible for making cross-browser/cross-OS websites work. It's great.
But in many ways, it's getting long in the tooth. Let's take a quick example from Jake Rocheleau's blog post here: https://speckyboy.com/building-simple-reddit-api-webapp-using-jquery/. He created a small example that uses jQuery to show how to call the GitHub API. You can see it in Figure 1:
The code is simple but shows a lot of the benefits and drawbacks of jQuery. Let's break it down. You can see the JavaScript in Listing 1. Most of the code is inside one large event handler:
$('#ghsubmitbtn').on('click', function(e) {
e.preventDefault();
...
Listing 1: jQuery Version of the App
$(function() {
$('#ghsubmitbtn').on('click', function(e) {
e.preventDefault();
$('#ghapidata').html(`<div id="loader">
<img src="https://i.imgur.com/UqLN6nl.gif" alt="loading...">
</div>`);
var username = $('#ghusername').val();
var requri = 'https://api.github.com/users/' + username;
var repouri = 'https://api.github.com/users/' +
username + '/repos';
$.getJSON(requri)
.done((json) => {
if (json.message == "Not Found" || username == '') {
$('#ghapidata').html("<h2>No User Info Found</h2>");
} else {
// else we have a user and we display their info
var fullname = json.name;
var username = json.login;
var aviurl = json.avatar_url;
var profileurl = json.html_url;
var followersnum = json.followers;
var followingnum = json.following;
var reposnum = json.public_repos;
if (fullname == undefined) {
fullname = username;
}
var outhtml = `<h2>${fullname}
<span class="smallname"> (@${username}) </span> </h2>
<div class="ghcontent">
<div class="float-left img-thumbnail m-1">
<a href="${profileurl}" target="_blank">
<img src="${aviurl}" width="80" height="80"alt="${username}">
</a>
</div>
<p>
Followers: ${followersnum} -
Following: ${followingnum}
<br>
Repos: ${reposnum}</p>
</div>
<div class="repolist clearfix">`;
var repositories;
$.getJSON(repouri)
.done((json) => {
repositories = json;
outputPageContent();
});
function outputPageContent() {
if (repositories.length == 0) {
outhtml += '<p>No repos!</p></div>';
} else {
outhtml += `<h4>Repos List:</h4> <div>`;
$.each(repositories, function(index) {
outhtml += `<div class='d-inline'>
<a href="${repositories[index].html_url}"
class="btn btn-sm btn-info m-1" target="_blank">
${repositories[index].name}</a></div>`;
});
outhtml += '</div></div>';
}
$('#ghapidata').html(outhtml);
} // end outputPageContent()
} // end else statement
}); // end requestJSON Ajax call
}); // end click event handler
});
Although this is pretty easy to remedy, it's a common practice because it's easy to think of an event handler as the main place for code in jQuery.
Next up is changing the UI in jQuery:
$('#ghapidata').html(`
<div id="loader">
<img src="https://i.imgur.com/UqLN6nl.gif"alt="loading...">
</div>`);
It starts up immediately with code that builds up markup in code. This mixes the metaphors of UI and logic into a single file. Getting the markup correct with in-line text is notoriously fragile because there is no real syntax checking.
Next, the code using jQuery to read a value from an input:
var username = $('#ghusername').val();
Although this is straightforward, it requires you to use an ID on every input that you need to get data out of. It also means a query over the entire DOM which can be slow, even at the fast speeds of jQuery. But I suspect if you're reading this article, you already know about the issues with jQuery. How can you make it better with Vue?
Using Vue in Place of jQuery
I think the best way to describe the way that Vue works is to convert this simple jQuery app. You can see in Listing 1 the complete JavaScript of the project and Listing 2 contains the complete Markup. Let's rebuild this piece by piece so you can see how Vue can be used to do this easier and with less code.
Figure 2: Markup of Original jQuery App
<body>
<div id = "w" class = "container">
<h1> Simple Github API Webapp </h1>
<p> Enter a single Github username below and
click the button to display profile info
via JSON. </p>
<div class="row">
<div class="col-6">
<input type="text"
class="form-control"
id="ghusername"
placeholder="Github username..."
autofocus="autofocus">
</div>
<div class="col-6">
<button class="btn btn-success"
id="ghsubmitbtn">Pull User Data
</button>
</div>
<div id="ghapidata"
class="clearfix">
</div>
</div>
</div>
</body>
Creating the Vue Object
One of the things I really like about Vue is that instead of requiring you buy into a big ecosystem, you can just drop a JavaScript file and start working with it. To get started, you'll just use a link to a development version of the library:
<!-- development version, includes helpful console warnings -->
<script src="//cdn.jsdelivr.net/npm/vue/dist/vue.js">
</script>
In fact, in this example, I left the jQuery in the project as I'll get back to using it a little later. Yeah, really.
The basis of how Vue works is a Vue object. I just created a new JavaScript file and started out with just an instance of the Vue object:
var app = new Vue({
el: "#w"
});
This object takes a JavaScript object to specify options. The first part that's important is the el
property. That represents a selector for the parent element in the HTML that you're telling Vue to take over for. This is a key difference between jQuery and Vue. The magic of jQuery is to be able to search through the DOM and find the elements you're interested in. Vue takes responsibility for a section of the DOM (or the entire DOM). So, in this case, you want set up the code to take over the element called w
:
<body>
<div id="w" class="container">
<h1>Simple Github API Webapp</h1>
<p>
Enter a single Github username below and
click the button to display profile info
via JSON.
</p>
...
The first thing you'll want to do is to be able to read the input for the user name. In jQuery, you accomplish this by:
var username=$('#ghusername').val();
But in Vue, the approach is different. You want to expose a piece of data from the Vue object, and that be changed as the user interacts with it. For example, let's add the data section to the Vue object:
var app = new Vue({
el: "#w",
data: {
userName: ""
},
});
The data
property is a list of the data that you want to share with the markup. In this case, you'll start with just a single property to hold the name of the user. In the markup, you'll use an attribute called v-model to have the input field tied to the userName:
<input type="text"
class="form-control"
placeholder="Github username..."
autofocus="autofocus"
v-model="userName">
The v-model is used to create a two-way binding to the userName. This means that if code changes the value, it will show up in the UI; and if the user changes the value, the property value is changed too. Let's see how you'd work with this new value.
Handling the Click Event
The trick to handling the click event is to use another attribute: v-on. This attribute allows you to register for events on any DOM object. In this case, you want to handle the click event so you'll use v-on:click as the full name (click is the DOM event name).
<button class="btn btn-success"
v-on:click="onSubmit()">
Pull User Data
</button>
Inside the value for v-on, you can simply define what code to execute. In this example, you can just call a method on the Vue object. So now you can add the onSubmit method on the Vue object like so:
var app = new Vue({
el: "#w",
data: {
userName: ""
},
methods: {
onSubmit: function () {
}
}
});
The magic starts to happen when you can just start writing code in the onSubmit without having to interrogate the DOM. You can do that by just calling the this property inside the function. Vue takes the methods and data elements and attaches them to the this property. For example, you can just access the userName inside the onSubmit method:
onSubmit: function () {
if (this.userName) {
console.log(`User: ${this.userName}`);
}
}
The key differentiator for Vue and jQuery is that instead of trying to modify the DOM, you'll just keep your business logic in JavaScript and let the bindings in the HTML show the changes that happen to the data. Although you can replace jQuery with Vue, you'll need to change your mindset about how to solve Web development problems. Let's implement the calls to GitHub to see how you can close the loop using this new mindset.
The key differentiator for Vue and jQuery is that instead of trying to modify the DOM, you'll just keep your business logic in JavaScript and let the bindings in the HTML show the changes that happen to the data.
Executing the API
This simple page takes the name of the GitHub user and executes a network request. The jQuery version uses jQuery to execute the network requests. Vue purposely doesn't attempt to do the networking for you. You're free to use any networking library you want. It could be jQuery (because you likely already know it), but there are also alternatives like axios
and even a Vue-specific one called vue-resource. But because you're converting code, let's just keep the jQuery networking code.
The existing code uses jQuery's getJSON
to get the responses from the API. Instead of constructing HTML like the old version does (see Figure 1), you're going to just create a new data property called user that will hold the object from the server:
data: {
userName: "",
user: null
},
Now that you have the data, you can simplify it by just setting the value to the user property:
$.getJSON(requri)
.done((json) => {
...
this.user = json; // just bind to the data
})
...
This lets you take the all of the jQuery code that constructs the HTML and just move it to markup:
<!-- User Info -->
<div>
<h2>{{ user.name }}</h2>
The double curly braces syntax here (often called mustache syntax) allows you to call a one-way binding from the data element. In this case, you'll just take the user's name and show it in the h2
element. If you run the code looking like this, it will complain because when you first run the page (before you execute the API), it won't be able to find the name property of the user. To get around this, you can use another attribute that Vue supplies called v-if:
<!-- User Info -->
<div v-if="user">
<h2>{{ user.name }}</h2>
The v-if attribute here tells Vue to not show anything in this div if the user is false (which in JavaScript means null, empty, or other case for false). This defers showing the entire div until you set the user. The v-if attribute gives you the power to control which parts of the UI to show until it's needed. You could show the user section, but it would be empty until you have a user.
You want to show more than just the user name, and you'd like to show a link over to the GitHub page too (as seen in Figure 1). To do this, you can add that markup after the user.name
:
<!-- User Info -->
<div v-if="user">
<h2>{{ user.name }}
<span class="smallname">
(@<a v-bind:href="user.html_url"
target="_blank">{{ user.login }}</a>)
</span>
</h2>
This introduces another attribute called v-bind. This attribute is used to bind to attributes. In this case, you're using it to bind to the href
of the anchor tag. Note that because you're using the v-bind attribute, you don't need the curly braces. Vue interprets what's inside the quotes as an expression against the Vue
object.
In this way, you're using Vue to specify what to show inside of the HTML, but it's still valid HTML. This way, your HTML tools will just work.
Collection Binding
In jQuery, you're used to creating collections of markup in order to create lists and grids. For example, from the jQuery version:
$.each(repositories, function(index) {
outhtml += `<div class='d-inline'>
<a href="${repositories[index].html_url}"
class="btn btn-sm btn-info m-1" target="_blank">
${repositories[index].name}
</a>
</div>`;
});
By going through a collection of the repositories, the jQuery code constructs a set of divs that represent each of the repositories to show the user. The strategy with Vue is the opposite of this. In Vue, you simply add the repo to the existing user object (because you need two calls to the GitHub API to get both the repos and the user object) like so:
$.getJSON(repouri)
.done(repos => {
Vue.set(this.user, "repos", repos)
})
Before you make the collection work, let's talk about what's happening here. The call to Vue.set is something new. It's actually uncommonly used, but you need it here. Before it makes sense, I need to talk about how Vue updates the UI when you change the code.
In Vue's data object, you have properties that you want to expose to the UI:
data: {
userName: "",
user: null
},
Vue takes this list of properties and replaces them at runtime with a set of getters and setters (e.g., properties). It does this so that when you set a value, it can react to that change (you know, that reactivity term that you've probably heard bandied about). It does this under the hood. So that when you change a value, it queues up that change so that next time it updates the markup, it knows that your change needs to be shown.
Great, but what is this Vue.set, then? Sometimes you write code that breaks the idea of reactivity and I'm doing that in this example. By adding a new property to user (e.g., repos), Vue didn't know about it initially. So, you can let Vue know about it by using the Vue.set
method. This takes the reactive object to set a property to, the name of the property on the reactive property, and the value to assign it. This way, you're hinting to Vue that it needs to update the value when you change it. Not necessarily in most cases, but it's good to know when you do need it.
With all that in place, you can then use a new Vue attribute called v-for to show the collection in the markup:
<a v-for="repo in user.repos"
v-bind:key="repo.html_url"
v-bind:href="repo.html_url"
class="btn btn-sm btn-info m-1">
{{ repo.name }}
</a>
The v-for attribute instructs the element to create an anchor tag for every repository in the user.repo
property and to create a local variable called repo
to represent the individual repository.
When using v-for, you also need to specify a unique key for each record (which helps Vue remove items from the UI when you change the collection in the code). You do that by binding to a key
attribute with v-bind. In this case, you know that the URL is unique, so just use it as a key. You could use a primary key or other unique key. It doesn't matter as long as it's unique.
The rest of the code is just simple binding like you've seen before (setting the href and the contents of the anchor). You can see the complete finished code in Listings 3 and 4.
Listing 3: The complete Vue implementation
var app = new Vue({
el: "#w",
data: {
userName: "",
user: null
},
methods: {
onSubmit: function() {
if (this.userName) { // user has put a user name in URIs
var requri = `https://api.github.com/users/${this.userName}`;
var repouri = `https://api.github.com/users/${this.userName}/repos`;
$.getJSON(requri)
.done((json) => {
// Ensure we have a name
if (!json.name) json.name = json.login;
this.user = json; // just bind to the data
$.getJSON(repouri)
.done(repos => Vue.set(this.user, "repos", repos));
})
};
}
}
});
Listing 4: The Markup with the Vue implementation
<div id="w" class = "container">
<h1> Simple Github API Webapp </h1>
<p> Enter a single Github username below and
click the button to display profile info
via JSON. </p>
<div class="row">
<div class="col-6">
<input type="text"
class="form-control"
id="ghusername"
placeholder="Github username..."
autofocus="autofocus"
v-model="userName">
</div>
<div class="col-6">
<button class = "btn btn-success" v-on:click="onSubmit()">
Pull User Data
</button>
</div>
<div class="clearfix">
<!--User Info-->
<div v-if="user">
<h2 > {
{user.name}
}
<span class = "smallname"> (
@<a v-bind:href="user.html_url" target="_blank"> {
{user.login}
} </a>)
</span>
</h2>
<div class = "ghcontent">
<div>
<a v-bind:href="user.login" target="_blank">
<img v-bind:src="user.avatar_url"
width="80"
height="80"
class="img-thumbnail float-left m-1"
v-bind: alt="user.login">
</a>
</div>
<p>
Followers: {
{user.followers}
} -
Following: {
{user.following}
}
<br>
Repos: {
{user.public_repos}
}
</p>
</div>
<div class="clearfix">
<p>
<strong> Repos List: </strong></p>
<p v-if="!user.repos || user.repos.length == 0">
No Repos
</p>
<div>
<a v-for="repo in user.repos"
v-bind:key="repo.html_url"
v-bind:href="repo.html_url"
class="btn btn-sm btn-info m-1"
target = "_blank" > {
{repo.name}
}
</a>
</div>
</div>
</div>
</div>
</div>
</div>
Where Are We?
As a jQuery user, you may be used to being able to create simple applications by just dropping jQuery and writing some quick code. I would argue that Vue is a better choice these days. It allows you to quickly get your job done and keep the code from becoming a spaghetti mess of callbacks and inline HTML. But it does require a learning curve that can be a little steep at first. The switch of mindset from querying and manipulating the DOM to having the DOM react to changes in your code is a key change. But once you get over that hump, I think you'll see the benefit of using a framework like Vue.