Part of an IBM mainframe system circa 1950. Plug and play had a whole different meaning back then.

Improving Legacy Web Applications with Vue and Vuex

Blunt Jackson
Nov 20, 2018 · 8 min read

Previously, I demonstrated how easy it is to drop Vue.js into an existing application in order to quickly add dynamic in-page functionality.

But the true beauty of the Vue framework comes with the addition of Vuex, an extension that elegantly encapsulates both data and in-page server access.

For this demo we are going to use the handy “jsonplaceholder api” to illustrate how Vue and Vuex can either add sweet new functionality to an existing site, or refactor crufty old interfaces into nifty new web apps, but I’m going to ask you to use a little imagination as well.

So let’s imagine your web server delivers your existing web application the following html file:

<DOCTYPE html>
<html>
<head>
<title>Vue Demo #2: The Blog Post App</title>
<meta charset='utf-8' />
<style type="text/css">
h1 {
font-size: 2em;
font-family: sans-serif;
font-weight: bold;
}
h3 {
font-size: 1em;
font-weight: bold;
}
p {
font-size: 1em;
font-family: serif;
}
</style>
</head>
<body>
<!-- Blog Post, id = 94 -->
<h1>qui qui voluptates illo iste minima</h1>
<h3>By: Clementina DuBuque</h3> <!-- User Id, 10 -->
<p>aspernatur expedita soluta quo ab ut similique expedita dolores amet sed temporibus distinctio magnam saepe deleniti omnis facilis nam ipsum natus sint similique omnis</p>
</body>
</html>

This would be a pretty terrible blog, and not only because the latin is incorrect. But you can use your imagination to expand this to a tiny little piece of your vast web empire. Pretend it doesn’t look like this:

So, using Vue and Vuex, we are going to do is make this blog post editable in place. I’m going to skip the “is this the right author” question, and leave security to the experts. We will just imagine that the user is the author for the purposes of this exercise.

First, we want the data out of the html and into a client-side data model. On our server, we change our template to write the data to a javascript object instead:

<script>
var model = {
post: {
id: 94,
title: "qui qui voluptates illo iste minima",
body: "aspernatur expedita soluta quo ab ut similique expedita dolores amet sed temporibus distinctio magnam saepe deleniti omnis facilis nam ipsum natus sint similique omnis"
},
user: {
id: 10,
displayname: "Clementina DuBuque"
}
}
</script>

Now, we are going to add vue.js and vuex:

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuex"></script>

And we are going to replace the html of yesteryear with Vuex, a Vue component, and our Vue application. (Seems more complicated, I know, but you just gotta hang on to your hat for this.)

<body>
<div id="vapp">
<display-post></display-post>
</div>
<script>
const store = new Vuex.Store({
state: model
})
Vue.component('display-post', {
template: `<div><h1>{{ post.title }}</h1>
<h4>By: {{ post.displayname }}</h4>
<p>{{ body }}</p></div>`,
computed: {
title() { return store.state.post.title },
displayname() { return store.state.post.displayname },
body() { return store.state.post.body }
}
})const vapp = new Vue({
el: "#vapp",
store
})
</script>

Don’t worry, things aren’t quite as ugly as they might seem. I’m doing some things very explicitly here to show you how the data flows.

(1) We replace the important part of our app (remember the vast web empire surrounding this little piece!) with a div to mount the app, and then a Vue component. (Did you read the prior article? You should!)

(2) We initialize the Vuex.store with a single hash that maps the state to our model.

(3) We create the component that is going to display our blog post.

(4) We create the Vue app. If you recall the prior article you will see something new in the Vue app: the reference to the Vuex store. That’s all you need to do.

If you are following along at home you should be able to see that… voila nothing has changed in our output!

But… there is one ugly little thing… getting the data from state to component via that computed section. That can’t be the best way to do it?

No, it sure aint.

Vuex to the rescue. Before declaring our Vuex store, add this:

var mapState = Vuex.mapState

And now use it in our component, replacing that ugly object definition with this:

computed: mapState([‘post’, 'user'])

That’s a handy little helper!

Modifying Data

But now, let’s say we want to be able to edit our blog post. Let’s add a second component, a form:

(1) In our html:

<display-post></display-post>
<hr>
<edit-post></edit-post>

That looks like a new component!

(2) Add the edit-post component:

Vue.component('edit-post', {
template: `<form>
<label>Title:</label>
<input type="text" v-model="post.title" /><br>
<label>Description:</label>
<textarea rows="10" cols="50" v-model="post.body"></textarea><br>
<input type="submit">
</form>
`,
computed: mapState(['post'])
})

We are not quite finished with this component, but here are a couple of things to pay attention to:

(A) We are only mapping the ‘post’ object out of our Vuex store. We are not editing the user here, so we don’t need that info for this component.

(B) We are using the v-model directive which tells Vue how to configure and populate the dom with the appropriate data. Unless you have unusual needs, this is a nice little magic wand.

(3) And, just because I can’t help myself, I’m going to add the tiniest little bit of style to this form, back up in our internal css sheet:

label {
width: 90px;
display: inline-block;
vertical-align: top;
}

If you have followed along, you should get this in your display:

If you’re still with me, try editing something in there. Title or description.

Woah! The display component instantly reflects the change.

This is the magic of Vue.

By mapping our Vuex state into our component, and using the v-model directive, our component instantly updates our client side data model.

But… what about the server?

Persisting State

You might notice, the submit button does not yet work. When a user hits the submit button, they expect something to happen!

Our Vuex object holds the client-side model of authoritative data, and it also holds the key to persisting that data. The mechanism of this is dispatch to declared actions. Here is a revision to the Vuex store:

const store = new Vuex.Store({
state: model,
actions: {
persistPost( { state } ) {
axios.put('https://jsonplaceholder.typicode.com/posts/' + state.post.id, state.post).then(function(rsp) {
/* Happy Dance */
}), function(err) {
/* Error Handling */
}
}
}
})

persistPost is the method that our store offers to handle dispatch statements by that name. You will notice that the content of this method adds axios as a new dependency. Fun fact: I once wrote a full json-api-accessing-library only to realize axios already existed and did the job even better. C’est la vie.

(By the way, using jsonplaceholder, you will get a 200 code on the server response, but the actual server data will not be changed. I was sorry the json placeholder people didn’t offer this functionality until I considered what kind of content the internet might update it with...)

Back to the important stuff: how do we call that actions function?

Let’s update the component:

Vue.component('edit-post', {
template: `<form @submit.prevent="savePost">
<label>Title:</label>
<input type="text" v-model="post.title" /><br>
<label>Description:</label>
<textarea rows="10" cols="50" v-model="post.body"></textarea><br>
<input type="submit">
</form>
`,
computed: mapState(['post']),
methods: {
savePost() {
this.$store.dispatch('persistPost')
this.$root.hideEdit()
}
}
})

Our form now prevents the default behavior of submit and replaces it by calling the savePost() method on the component, which in turn dispatches to the persistPost method on the store.

You will notice I also call hideEdit() on the root, which is the Vue app itself.

This is just a fun little wrinkle on the whole app, which is included below. In this last rev, I also add a button to allow editing, and then when editing is complete, to evaporate the edit form.

Now, go ahead and cut and paste the following into your text editor, and load it straight into a browser. Pop your developer tools to watch the network connections and… connect the dots!

<DOCTYPE html>
<html>
<head>
<title>Vue Demo #2: The Blog Post App</title>
<meta charset='utf-8' />
<style type="text/css">
h1 {
font-size: 2em;
font-family: sans-serif;
font-weight: bold;
}
h3 {
font-size: 1em;
font-weight: bold;
}
p {
font-size: 1em;
font-family: serif;
}
label {
width: 90px;
display: inline-block;
vertical-align: top;
}
</style>
<script>
var model = {
post: {
id: 94,
title: "qui qui voluptates illo iste minima",
body: "aspernatur expedita soluta quo ab ut similique expedita dolores amet sed temporibus distinctio magnam saepe deleniti omnis facilis nam ipsum natus sint similique omnis" },
user: {
id: 10,
displayname: "Clementina DuBuque"
}
}
</script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuex"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<div id="vapp">
<display-post></display-post>
<hr>
<edit-post v-show="display === 'edit'"></edit-post>
</div>
<script>
var mapState = Vuex.mapState
const store = new Vuex.Store({
state: model,
actions: {
persistPost( { state } ) {
console.log(state.post)
axios.put('https://jsonplaceholder.typicode.com/posts/' + state.post.id, state.post).then(function(rsp) {
/* Happy Dance */
}), function(err) {
/* Error Handling */
}
}
}
})
Vue.component('edit-post', {
template: `<form @submit.prevent="savePost">
<label>Title:</label>
<input type="text" v-model="post.title" /><br>
<label>Description:</label>
<textarea rows="10" cols="50" v-model="post.body"></textarea><br>
<input type="submit">
</form>
`,
computed: mapState(['post']),
methods: {
savePost() {
this.$store.dispatch('persistPost')
this.$root.hideEdit()
}
}
})
Vue.component('display-post', {
template: `<div><h1>{{ post.title }}</h1>
<h4>By: {{ user.displayname }}</h4>
<p>{{ post.body }}</p>
<button v-on:click="$root.showEdit()">edit</button></div>`,
computed: mapState(['post','user'])
})
const vapp = new Vue({
el: "#vapp",
store,
data: {
display: "noEdit"
},
methods: {
showEdit() {
this.display = "edit"
},
hideEdit() {
this.display = "noEdit"
}
}
})
</script>
</body>
</html>

Integrating Into Existing Apps

Here’s the thing. You can leave all your legacy functionality functioning as it is, but with very simple additions to your template you can immediately start converting your web application or individual web pages, div-by-div, into a fully dynamic, reactive, system.

By adding Vuex, I find the Vue ecosystem offers a perfectly clean separation of concerns:

  1. Vuex holds the authoritative data-model, and all persistence actions.
  2. The Vue components hold all the display and event logic for each dynamic section of the application.
  3. The Vue application itself holds the high level flow control for the reactive components of the site.

Of course, a complex application is always going to run into complex particulars, and the way that the Vue ecosystem propagates data and events between components is not always obvious. Nonetheless, most challenges are a matter of grasping the manner in which Vue manages scope. I am sure that will be a beautiful deep dive for a future article.

This is what happens when you don’t have good separation of concrens… or maybe it’s just the antique laptop.

Blunt Jackson

Written by

Building web applications since 1992. Crikey, that’s a long time.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade