spice up your Golang app with a GUI — part 4: Single Page Application with Vue.js

video @ https://youtu.be/CwdYG8rTOdY

This tutorial would be broken into several stories; each of them exploring a separate concept so that you can read only the interesting pieces instead of everything. This is the 4th story in the series talking about the creation of a Single Page Application with Vue.js. Links of the stories:

source code @ here:

https://github.com/quoeamaster/golang_blogs/tree/master/2020-03-gui-task-app

a brief history of web applications and Javascript

Traditional web applications are typically used for displaying data retrieved from server side. Based on this major responsibility, the webapp is less feature packed and maybe just a few fancy Javascript effects and that’s it. Instead, lots of work are done through the server side code. Examples are the ASP / ASP.net, JSP, PHP and so on.

Later on, a new technology known as ajax has evolved. Thanks to this technology, Javascript code can now make request to the server side WITHOUT issuing a page reload (in the traditional webapps mentioned above, a page reload is required per “action” and actions could be asking for the latest book categories or display products on the 5th page etc). Starting from then, some of the presentation code starts to shift back to Javascript (client side) and server side code can focus more on business logic BUT still maintain certain degrees of the presentation code logic.

For recent years, browser technologies have gone through a lot of improvements and getting much much more powerful than before. Along with the browser’s revolution, so did Javascript! In the old days, Javascript was just a language for fun (usually for adding fancy visual effects on the webapp) and nowadays Javascript could be a serve-side / backend language too (check node.js) plus ECMAScript standards 5 and 6 have made Javascript more and more complete; similarly a lot of frameworks add capabilities to the Javascript language through transpilation — for example, typescript added Object Oriented Programming capabilities to pure Javascript syntax (finally you can use Interface, Class, Inheritance etc).

SPA — Single Page Applications

Lately, a new type of webapp has evolved, known as SPA — Single Page Applications. Some characteristics as follows:

So SPA is kind of like building an application through components; and Javascript glues the presentation logic (components) with the server-side data. The advantage of such approach is that maintainability is much easier, instead of maintaining the whole web page as a unit (rather big and bulky); now the unit of maintenance is based on a much smaller component. Take an example, if the presentation logic of a panel needs to be updated, simply update that component and the rest of the web page / app should not be affected. Of course, we do assume that the interface between the component and the server side code remain intact at this point.

Concept is rather simple, but it would be handy if a framework is available to provide some basic integration functions. One of the most well known framework facilitating SPA is react.js — powered by Facebook. React.js is a powerful framework and some concepts as follows:

However the downside of react.js is the learning curve; it wasn’t easy to grasp all the concepts when developing a webapp with react.js; especially when we need to use redux on controlling the application states…

In order to make the SPA development a bit more pleasant; I have chosen Vue.js instead. Vue is like an easier version of react.js, yet still very powerful and more developer friendly. Though community support wasn’t as much as react.js…

revamp on the index.html

Okay, after all those history lesson earlier; let’s get back to our webapp development. Before we can use Vue.js, we need to install the framework code. Basically 4 approaches are available:

For more information, do checkout the official documentation

The approach I would pick is the simplest one — cdn include. So all we need is an internet connection and all good :)

PS. for serious development, npm approach should be considered as more tools are available to optimise our Javascript code (e.g. webpack)

Let’s add back some cdn includes, also adding bootstrapp css includes on the HTML header section.

<!-- vue -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.0"></script>

<!-- vuex -->
<script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.js"></script>
<script src="https://unpkg.com/vuex"></script>
<!-- JQuery -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<!-- Bootstrap tooltips -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.4/umd/popper.min.js"></script>
<!-- Bootstrap core JavaScript -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/js/bootstrap.min.js"></script>
<!-- MDB core JavaScript -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mdbootstrap/4.8.11/js/mdb.min.js"></script>
<!-- Font Awesome -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css">
<!-- Bootstrap core CSS -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
<!-- Material Design Bootstrap -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/mdbootstrap/4.8.11/css/mdb.min.css" rel="stylesheet">

Yeap quite a lot of imports and we will go through them later once we need these javascript frameworks / libraries.

on our index.html; add back a button to verify if the above imports are valid:

<html>
<head>
... js library and css imports ...
</head>

<body onload="onStart()">
<button class="btn btn-primary">bootstrap buttons</button>
</body>
</html>

re-build the app and run the application should yield the following:

go run generateAssets.go
mv generateAssets.go generateAssets.go.disable
go build -o taskApp
mv generateAssets.go.disable generateAssets.go
./taskApp

The build process includes a few more steps now.

decorated button

Great! If you see the above decorated button, we should be on the right track. Next we would need to bootstrap the Vue app on the main panel of the page.

PS. When we talk about panel, we are usually referring to an html “div” section.

Before adding the “app” component, we need to design where to put the component files. A possible design on the hierarchy as below:

root
|_ main.go
|_ www
|_ index.html
|_ asset
|_ favicon.png
|_ css
|_ app.css
|_ component
|_ app.js

within the “www” folder, we created a “component” folder holding all UI code. Since we would embed all resources under the “www” folder, our UI components would be embed into the executable as well.

The application — app.js

new Vue({
el: '#app',
data: function () {
return {
msg: 'vue app created',
};
}
});

The above is a simple Vue app’s structure, every Vue app needs to have a “home” which is declared in the “el” parameter — we can see that the home should be any HTML component matching the css selector “#app”; meaning the target component has the value “app” for the “id” attribute.

Next we have a “data” section — data refers to the local states of this component. In our app component, a “msg” state is created to validate if our component has been loaded correctly.

PS. Since states are LOCAL; they could only be shared to child component(s) as read-only values. We would talk about how to modify parent states in a later stage.

Our index.html would need some change as well:

...
<body onload="onStart()">
<!-- app entry point -->
<div id="app">
{{msg}}
<button class="btn btn-primary">bootstrap buttons</button>
</div>

<!-- load back all vue related components; app.js MUST be the last one -->
<script src="./component/app.js"></script>

</body>
...

Note that we added a new “div” with id=app ← this is where our app component could mount. Within the component, we have our good old button, plus a line {{msg}}. In order to display a local state directly, use {{state_name}} syntax.

Lastly, remember to import our app.js file.

PS. IMPORTANT! If we are using the cdn import approach; all our components (*.js) MUST be imported at the very end. The reason is when the components start to bootstrap; the HTML content (i.e. div with id=app) is not yet available! Hence would fail to mount.

now re-build and run the app again would yield:

app.js running

The Splash-Screen component

Our daily task app has a splash screen displaying the loading progress before showing the webapp’s layout. Splash screen is a useful component for our webapp to preload resources including images, javascript libraries, css files etc.

To component-ize, we would create a new file — splash.js:

This time we use Vue.component() to create the splash-screen component; similarly within the method, we would provide the details for the component to work:

<div class="splash-container" v-bind:class="getContainerClass()">
<div class="splash-inner-container">
<div>
<div class="splash-caption"
style="margin-top: 180px; margin-bottom: 20px;">
<img src="../asset/favicon.png" width="50px; margin-right: 12px;">
<span>Loading...</span>
</div>

<div class="splash-progress-container">
<div id='splash-progress-bar' class="splash-progress-bar"></div>
</div>
</div>
</div>
</div>

The above template covers a lot of important concepts. Let’s take a closer look line by line.

v-bind:class

The first line declares the primary HTML tag of this component which is a “div”, everything looks normal until v-bind:class=”getContainerClass()”. v-bind:class — means that we are going to add dynamic values to the “class” attribute of this “div” tag. The dynamic logic depends on function getContainerClass():

getContainerClass: function () {
let _c = {};
if (this.show) {
_c['core-display-block'] = true;
_c['core-display-none'] = false;
} else {
_c['core-display-block'] = false;
_c['core-display-none'] = true;
}
return _c;
}

if local state “show” is true → would return a css class “core-display-block” AND remove a css class “core-display-none” (if available earlier); vice versa for “show” is false. The important thing is this method MUST return an Object or the object in stringified format (i.e. convert the object to a string format). By returning different css classes, the corresponding UI’s appearance would change accordingly and hence possible to add visual effects or even creating a swap page effect (e.g. the main page contains 3 tabs, when a particular tab is clicked, the page’s content would change — all these could be done through css manipulations as well)

class=“splash-progress-bar”

Near the end of the component, we found that there is a div with class=“splash-progress-bar”. This progress-bar is exactly where we create the animation, the animation code is located in the “mounted” callback. Remember mounted means the component / app has been mounted to the corresponding location within the HTML, so we can treat it as a MUST called life cycle method and it is triggered just when component UI is ready to be rendered.

mounted: function() {
let instance = this;
let ePBar = document.querySelector('#splash-progress-bar');
let w = 1;
let hInterval = setInterval(function () {
if (w >= 100) {
clearInterval(hInterval);
instance.show = false;
} else {
ePBar.style.width = w + '%';
}
w++;
}, 50);
}

re-build the app and run it would yield:

Yep, that’s it~ Very simple isn’t it! All the magic is done through simple css manipulations. One more point to stress here is even though within our progress bar loading intervals, we didn’t really load any resources; however if we do need to pre-load some heavy loading images or text or establish server-side connections — this splash screen buffer is a good place to do so.

Take an example: our app might need to load main.png, establish an Elasticsearch connection plus retrieve personal profile from Elasticsearch index; hence our splash screen progress might be the following:

That means, we still use the setInterval() method; however within each interval, we would instead check the availability of the above resources and determine when and how much to set on the progress bar’s width.

Moving on…

We have gone through a bunch of things again:

In the next story of the tutorial, we would cover a Form-Input component to collect information from the user and make use of the input data to create our Note component. Happy coding and stay tuned

:)

a java / golang / flutter developer, a big data scientist, a father :)

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store