Skip to content

Vue.js studies

A progressive framework to be used in existing js app if needed. It is reactive using the data binding mode.

Update

02/2023 migrate to version 3

Getting Started

To isolate the development environment the DockerfileForNode give us all we need to run vuejs app.

docker build -f DockerfileForNode -t jbcodeforce/nodejs .

jbcodeforce/nodejs is node v19.6 with yarn and vue@cli. So better to use this environment with the command:

docker run -ti -v $(pwd):/app -p 8080:8080 -p 5173:5173 jbcodeforce/nodejs bash 

Use ./startDevEnv.sh script.

Vue CLI is currently in maintenance mode and no longer the default tooling used to build Vue applications. Vuetify projects are now generated using vite.

Create basic project

npm init vue@latest
cd <projectname>
npm install
npm run dev

Enter the project name and answer no to all other questions (in fact when using pinia for store data between component, select it and also may be Lint for code quality control). To get the web pages visible from the host, modify the package.json on the dev statement with vite --host .

As an alternate we can use vue cli (which seems to be in maintenance mode, and vite may replace it):

vue create hello-world

And finally as we want to use Material and Vuetify, we could also use the vuetify CLI:

yarn create vuetify
  • Start server: npm run dev or yarn dev
  • Browser to http://localhost:5173, at this time any code change will be visible in the browser.

See tutorial

Basic concepts

  • First the index.html page includes the vuejs scripts and a <div id="app"> to hook the vue app.
<body>
  <div id="app"></div>
  <script type="module" src="/src/main.js"></script>
</body>
  • The main.js defines the Vue app instance, links it to the single page app,
    renders the application and mounts the components to the #app of the index.html.
import { createApp } from 'vue'
import App from './App.vue'

import './assets/main.css'

createApp(App).mount('#app')
  • The App.vue defines the root component, page template, css and the components to use. When using router to navigate between page it includes the router-view element.
  • Components are used in html template as element.
  • CSS styles that will be applied to this component and any child component of this component
  • Data is interpolated via {{}} in html.
<template>
 <div class="hello">
   <h1>{{ msg }}</h1>

and can be object or property and declared in the component. Below props are the input parameters the user of the component can use to inject data, like the msg.

export default {
 name: 'HelloWorld',
 props: {
   msg: String
 }
}
  • Data object can be injected via data binding in a reactive way: when properties of this data object change, Vue reacts to and digests the changes.
// in template
 <HelloWorld :msg="message"/>
// ... in script section
data(){
   return {
     message: 'Hello World!'
   }
 }
  • VueJS requires data to be a function so that each instance of the component can maintain an independent copy of the returned data object.
  • v-model is used to manipulate component data.

Organize code

  • Keep images in assets
  • components folder includes reusable components like header, footer...
  • main.js defines the Vue app and load router and other components
  • App.vue includes the application template with Header, Footer... and the result of the URL routing (see later section).
<template>
  <div id="app">
    <Header/>
    <router-view/>
  </div>
</template>
  • Define a header with logout, and links to other content external to the app
  • Define content of the Home.vue with a left menu to comeback to the Home page.
  • Add a stores folder to keep pinia store declaration. See next section.

Sharing data between components

  • The first technique for passing data is with props. They allow us to transfer data from a parent to a child component.

  • 2nd one is to emit Events from a child component to its parent component. In the child component: the username is the data and changeUsername is the event type.

<form action="submit" @submit.prevent="$emit('changeUsername', username)">
      <input 
                type="text" 
                v-model="username" 
                placeholder="Enter your name" />
      ...

And in the parent calling the child passing the callback function to use to process the event. username is an attribute inside the parent now.

<ChildComponent
      @changeUsername="
        (payload) => { username = payload;
        }
      "
    />

Pinia

  • Finally, Stores introduce an advanced method of passing data across multiple components by eliminating the problem of prop drilling. Popular state management tools for Vue.js include Pinia or Vuex (that is not recommended anymore).

Initialize pinia in the main.js file

import { createPinia } from 'pinia'
//....
const app = createApp(App)

app.use(createPinia())

Then use the store in a view or component:


See a complete tutorial in this article: Vue 3 and state management with Pinia and code in vue-pinia folder.

Inventory app notes

See code in inventory-html folder

  • To make the image dynamic, use the data binding, where the place in the template is linked to the data source: <img v-bind:src="image">
  • Use v-if, v-else-if, v-else to control element display
  • If your app needs an element to frequently toggle on and off the page, you’ll want to use the v-show directive. An element with this directive on it will always be present in our DOM, but it will only be visible on the page if its condition is met. It will conditionally add or remove the CSS property display: none to the element
  • The v-for directive allows us to iterate over an array to display data. We can loop over an array of objects and use dot notation to display values from the objects. When using v-for it is recommended to give each rendered element its own unique key.
  • The v-on directive is used to allow elements to listen for events

  • The shorthand for v-on is @

  • You can specify the type of event to listen for: click, mouseover, any other DOM event
  • The v-on directive can trigger a method, Triggered methods can take in arguments
  • this refers to the current Vue instance’s data as well as other methods declared inside the instance

  • Data can be bound to an element’s style attribute, an element’s class. We can use expressions inside an element’s class binding to evaluate whether a class should appear or not.

  • Computed properties calculate a value rather than store a value.
  • Components are blocks of code, grouped together within a custom element. They make applications more manageable by breaking up the whole into reusable parts that have their own structure and behavior.

  • Data on a component must be a function

  • Props are used to pass data from parent to child. We can specify requirements for the props a component is receiving. Props are fed into a component through a custom attribute. Props can be dynamically bound to the parent’s data

  • Vue dev tools provide helpful insight about your components

App router

vue-router helps to link components to route.

  • Define a router object in the main.js. This is from the last router version, and using vite server.
import {createRouter, createWebHistory } from 'vue-router'
import Home from '@/components/Home.vue'
import Login from '@/components/Login.vue'

const router = createRouter({
  history: createWebHistory(),
  routes: [
  {
    path: '/',
    name: 'home',
    component: Home
  }]
})

createApp(App)
  .use(router)
  .mount('#app')
  • The router instance can be accessed in any component via this.$router
  • Change the main App.vue by adding a content and a router-view object, remove the component declaration in the script section
<v-content>
  <v-container>
    <router-view />
  </v-container>
</v-content> 
  • We can test with the different paths defined in the routes.
  • URL can have parameter that can be accessed in the component:
const routes = [
  // dynamic segments start with a colon
  { path: '/users/:id', component: User },
]

and access it via: this.$route.params

  • To add control over the URL access, like access to a view only of the user is authenticated, we need to use Guard.

Some interesting components

Rich text editor

Vue2 Editor and used in person-manager app:

<v-col cols="12">
  <vue-editor v-model="item.text"></vue-editor>
</v-col>

...
import { VueEditor } from "vue2-editor"
export default {
  components: {
    CardList,
    VueEditor
  },

Vue app with quarkus app

Here are the steps to integrate Vue app in a Quarkus app and develop both in parallel:

<!-- in properies-->
<version.frontend-maven-plugin>1.10.0</version.frontend-maven-plugin>
<version.resources-plugin>3.2.0</version.resources-plugin>
<!-- in plugins -->
<plugin>
      <groupId>com.github.eirslett</groupId>
      <artifactId>frontend-maven-plugin</artifactId>
      <version>${version.frontend-maven-plugin}</version>
      <configuration>
        <workingDirectory>webapp</workingDirectory>
      </configuration>
    </plugin>
  • Create the vue app under the webapp folder: vue create webapp
  • Remove src/main/resources/META-INF/resources/index.html from quarkus app
  • Using the maven resource plugin, add to the pom the copy of the built webapp to the directory used by Quarkus
<plugin>
  <artifactId>maven-resources-plugin</artifactId>
  <version>${version.resources-plugin}</version>
  <executions>
    <execution>
      <id>copy-resources</id>
      <phase>process-resources</phase>
      <goals>
        <goal>copy-resources</goal>
      </goals>
      <configuration>
        <outputDirectory>${basedir}/target/classes/META-INF/resources/</outputDirectory>
        <resources>
          <resource>
            <directory>webapp/dist</directory>
            <filtering>false</filtering>
          </resource>
        </resources>
      </configuration>
    </execution>
  </executions>
</plugin>
  • Could add a maven profile to update the UI dependencies, but could also be done by Vue and yarn build.
  • Add a build UI maven profile,
<profile>
      <id>Build the UI</id>
      <activation>
        <property>
          <name>ui.dev</name>
        </property>
      </activation>
      <build>
        <plugins>
          <plugin>
            <groupId>com.github.eirslett</groupId>
            <artifactId>frontend-maven-plugin</artifactId>
            <executions>
              <execution>
                <id>yarn run build</id>
                <goals>
                  <goal>yarn</goal>
                </goals>
                <configuration>
                  <arguments>run build --output-hashing=all</arguments>
                </configuration>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </profile>

To build the webapp for production. This could be run with:

mvn package quarkus:dev -Dui.deps -Dui.dev

# OR in the vue folder do
yarn build 
# in root folder
mvn package -DskipTests
  • When doing development run the vue serve and quarkus:dev into two different terminals but add a proxy configuration (file vue.config.js) for vue to match /api route to backend, and uses another port than 8080:

js module.exports = { devServer: { proxy: { '^/api': { target: 'http://localhost:8080', changeOrigin: true }, }, port: 4545 } }

  • Add Quarkus resource with API definition
    @GET
    @Path("hello")
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "hello a toi l etranger";
    }
  • Install axios as a library to access remote server: yarn install axios
  • Add calls to api using axios in the component which needs the data.
import axios from 'axios
export default {
  name: 'HelloWorld',
  data() { return {
    msg: ''
  }
  },
  mounted() {
    axios.get('/api/v1/persons/hello').then(resp => (this.msg= resp.data ))
  }
}

Vue service to call backend api

To call backend API, we need to add service and use the Promise based HTTP client: axios with some Vue samples, here

Load reference data

import axios from "axios";
export default {
  data: () => ({
      stores: [],
  }),
  methods: {
      initialize () {
        axios.get("/api/v1/stores").then((resp) => (this.stores = resp.data));
      }
    }
}

Vue app deployment

See product doc on deployment.

If the front end is a pure static app, it can be served by a http server. We need to properly use Cross Origin Resource Sharing.

Get a docker file with build stage to use nodejs and npmto build the front end page under dist folder, and a runtime stage that use nginx to expose the app. Add a nginx configuration.

FROM node:latest as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY ./ .
RUN npm run build

FROM nginx as production-stage
RUN mkdir /app
COPY --from=build-stage /app/dist /app
COPY nginx.conf /etc/nginx/nginx.conf

See example in BetterToDoFrontEnd project.

For getting access to back end service, use environment variables and in the axios code use something like (see product doc):

const client = axios.create({
    baseURL: process.env.VUE_APP_BASE_URL
});

See quarkus section above for deployment with BFF.

Serving VueApp with Python

The static folder includes the outcome of the vuejs build.

@app.route("/")
def vueApp():
    return send_from_directory('./static','index.html')

More Reading