VueJS 101 for CFML Devs

Webinar – Distributed tracing and on-prem observability
Log4j vulnerability Important information for ColdFusion, Lucee, and Java users 

VueJS 101 for CFML Devs

I’ve been stumbling across a fair few developers recently who like the idea of using VueJS, but are not sure how to get started. VueJS can be used to build whole applications, but sometimes the requirement is to just build something within an existing CFML page to make the UI more dynamic and user-friendly. This set of examples is aimed at those of you who want to dip your toes into VueJS and might have an existing CFML application that you want to enhance.

We’re mainly going to focus on the question I get the most, which is “how to do I get my data from CFML to VueJS?”.

All the code is available on Github, but we’ll go through some of the key concepts each example brings. The setup is simple, one index file, and one “remote” cfm/cfc file which we’ll call via VueJS.

VueJS 101 example 1 – Simple Message

Our aim here is simple – pressing the button should do a request to our CFML page, and return a message.

Let’s look at the index.cfm first.

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

The first thing to note is we’re just going to use regular old script tags to import Vue. We’ll also import Axios, which is a library we’ll use to make our AJAX requests.

<div id="app">
{{ message }}
<br />
<button @click="getData">Get Message</button>
</div>

Then, we’ve got a div with an id of “app”: this is our Vue instance will operate within. Our JS markup uses `{{}}` to denote the value of a variable, and our button has an `@click` handler which calls the corresponding method in the methods block.

<script> var app = new Vue({ el: '#app', data: { message: 'Hello Vue!' }, methods: { getData: function() { axios.get('remote.cfm') .then(function(response) { app.message = response.data.message; }) .catch(function(error) { console.log(error); }); } } }); </script>

Our main script block initializes the Vue instance and uses `el` to bind it to the div.

The data object is where you can define variable defaults; we’ll use a simple message variable to store the message we get back from our request.

Our methods block contains a single method, `getData()`, which is called when the button is clicked. It uses the Axios library to make a request to our CFML page and then sets the `message` variable to the response. If anything goes wrong, then the catch block will output the error to the console.

Our remote.cfm couldn’t be simpler;

header name="Content-Type" value="application/json"; data = { "message" = "Hi from CFML! The time is #now()#" }; writeOutput(serializeJSON(data));

We set the return type as JSON, create a new struct with a single field called “message”, and then return that via serializeJSON();

We can monitor the response in chrome dev tools.

VueJS 101 for CFML Devs, FusionReactor

Obviously, this is a very simple example: most of the time you’ll have a backend framework that will handle the incoming requests and allow you to pass back JSON easily.

VueJS 101 example 2 – Basic Array

The second example is equally simple, but this time we’ll output an array.

data: {
messages: []
},

The key difference is that in our data block, our messages js variable now defaults to an empty array.

<ul v-if="messages.length">
<li v-for="msg in messages">
{{ msg }}
</li>
</ul>

In our HTML output, the unordered list will only display when we have something via the “v-if” directive. Lastly, we’ll use a “v-for” directive to loop over the array and output the messages.

VueJS 101 for CFML Devs, FusionReactor

VueJS 101 example 3 – Simple Query

Next, we can try a CFML query. The Vue side is basically the same as the previous example, except we’re now renamed the array to `users`, and are outputting id, name and role within the loop.

<ul v-if="users.length">
<li v-for="user in users">
{{ user.id }} {{ user.name }} {{ user.role }}
</li>
</ul>

Our remote.cfm will now return a query result as an array.

header name="Content-Type" value="application/json";
data = queryNew(
"id,name,role",
"integer,varchar,varchar",
[
{"id"=1, name="Joe", role="admin"},
{"id"=2, name="Jane", role="user"},
{"id"=3, name="John", role="user"}
]
);
writeOutput(serializeJSON(data, "struct"));

If you return a query object via serializeJSON(), it will default to a different format, separating the columns and data; personally, I prefer the array of structs format, so in Lucee we can pass in the “struct” return type and it will do it all for us.

VueJS 101 for CFML Devs, FusionReactor

Example 4 – To-Do List

This last example is a bit more fleshed out – and we’ve included Bootstrap for some simple styling. It’s the ubiquitous “To-Do List” example.

VueJS 101 for CFML Devs, FusionReactor

Let’s look at the “backend” first; A simple CFC with some remote methods. We’re using an array in the session scope to store our to-do items.

private function setup(){
if(!structKeyExists(session, "items"))
session.items = [];
if (cgi.content_type EQ "application/json")
request.data = deserializeJSON(ToString(getHTTPRequestData().content));
}

Before each request, we check there’s an array in the session scope called “items”, and if not we’ll create it.

private function respond(){
header name="Content-Type" value="application/json";
return structKeyExists(session, "items") && isArray(session.items) ? session.items : [];
}

We’ll also set the request’s response type to be JSON.

remote function get() {
return this.respond();
}
remote function add() {
this.setup();
session.items.push({
"name": request.data.item,
"created": DateTimeFormat(now())
});
return this.respond();
}
remote function remove() {
this.setup();
ArrayDeleteAt(session.items, url.position);
return this.respond();
}
remote function clear(){
this.setup();
session.items = [];
return this.respond();
}

The `get()` function will simply return our array; `add()` will push an item to the array, and `remove()` will remove an item. `clear()` will remove all items.

There’s a bit more code in the index.cfm, so let’s go through some of the more notable changes.

The Axios configuration has changed slightly. There’s an instance of Axios which we’ve set to the `api` constant, set a timeout default of 5 seconds for any remote request, and used an interceptor to handle any errors.

const api = axios.create({
timeout: 5000
});
api.interceptors.response.use((response) => response, (error) => {
app.errors.push(error.message);
});

There’s now an error array in the Vue data object, which we’ll use to display any errors. The interceptor simply pushes any errors to this array, which are then looped over, alongside a simple dismiss button which resets the error array back to zero.

mounted: function() {
this.getItems();
},

The `mounted` function is triggered when the Vue component is initially loaded on the page – so within that, we’ll call our `getItems()` method to get the items from our remote `get` function in the CFC.

addItem: function() {
api.post('remote.cfc?method=add', {
item: this.newItem
})
.then(function(response) {
app.items = response.data;
app.newItem = null;
})
},

Inputting a new item uses one of the key concepts in Vue, that of the `v-model`, which binds a variable to the input value automatically. The `addItem()` method simply POSTs the new item to our remote `add` function. Since all the remote functions return the array back, we can just update the local array with the response.

One of the nice things about Vue is how it can easily enable and disable (or hide) elements. When Vue checks the validity of a variable, it will assume it’s invalid if it’s null, undefined of zero length. So you can see that the Add Button can be easily disabled when the input is empty.

<button class="btn btn-primary" :disabled="!newItem" @click="addItem">Add Item</button>

To remove an item, we’ll send the position of the clicked item in the javascript array (plus one, of course, remembering that JS arrays start at zero) to the remote `remove` function.

removeItem: function(position) {
api.delete('remote.cfc?method=remove&position=' + position)
.then(function(response) {
app.items = response.data;
})
}

Hopefully, this might have whetted your appetite a bit, and given you a few pointers to how you might start using VueJS in your existing CFML application.