Categories
Vue

Add a Timeline to Your Vue.js App

A timeline is great for displaying chronological items. It looks good and it’s easy to read. We can easily add one to a Vue.js with the timeline-vuejs package, located at https://github.com/pablosirera/timeline-vuejs. It displays a vertical timeline with the year and the content of your choice.

In this article, we will build a journal entry app that lets users enter journal entries in and save them. They can also delete them as they wish. The saved entries will be displayed in a vertical timeline with the year on the left and the content on the right. To start, we will run the Vue CLI by running:

npx @vue/cli create journal-app

Vuex, and Babel.

Next we install some packages. We will use Axios for making HTTP requests, BootstrapVue for styling, Timeline-Vuejs for displaying the user’s diary in a timeline, VueFilterDateFormat for formatting dates in templates, and Vee-Validate for form validation. To install them, we run:

npm i axios bootstrap-vue timeline-vuejs vee-validate vue-filter-date-format

Now we can being building our app. Create a file called JournalForm.vue in the components folder and add:

<template>
  <ValidationObserver ref="observer" v-slot="{ invalid }">
    <b-form @submit.prevent="onSubmit" novalidate>
      <b-form-group label="Date">
        <ValidationProvider name="date" rules="required|date" v-slot="{ errors }">
          <b-form-input
            type="text"
            :state="errors.length == 0"
            v-model="form.date"
            required
            placeholder="Date"
            name="date"
          ></b-form-input>
          <b-form-invalid-feedback :state="errors.length == 0">{{errors.join('. ')}}</b-form-invalid-feedback>
        </ValidationProvider>
      </b-form-group>

      <b-form-group label="Diary">
        <ValidationProvider name="diary" rules="required" v-slot="{ errors }">
          <b-form-textarea
            :state="errors.length == 0"
            v-model="form.diary"
            required
            placeholder="Diary"
            name="diary"
            rows="3"
          ></b-form-textarea>
          <b-form-invalid-feedback :state="errors.length == 0">{{errors.join('. ')}}</b-form-invalid-feedback>
        </ValidationProvider>
      </b-form-group>

      <b-button type="submit" variant="primary" style="margin-right: 10px">Submit</b-button>
      <b-button type="reset" variant="danger" @click="cancel()">Cancel</b-button>
    </b-form>
  </ValidationObserver>
</template>

<script>
import { requestsMixin } from "@/mixins/requestsMixin";

export default {
  name: "JournalForm",
  mixins: [requestsMixin],
  props: {
    edit: Boolean,
    journal: Object
  },
  data() {
    return {
      form: {}
    };
  },
  methods: {
    async onSubmit() {
      const isValid = await this.$refs.observer.validate();
      if (!isValid) {
        return;
      }
      const offDate = new Date(this.form.date);
      const correctedDate = new Date(
        offDate.getTime() + Math.abs(offDate.getTimezoneOffset() * 60000)
      );

      const params = {
        ...this.form,
        date: correctedDate
      };

      if (this.edit) {
        await this.editJournal(params);
      } else {
        await this.addJournal(params);
      }
      const { data } = await this.getJournals();
      this.$store.commit("setJournals", data);
      this.$emit("saved");
    },
    cancel() {
      this.$emit("cancelled");
    }
  },
  watch: {
    journal: {
      handler(val) {
        this.form = JSON.parse(JSON.stringify(val || {}));
      },
      deep: true,
      immediate: true
    }
  }
};
</script>

The file is the form for letting users entering their journal entries.

In the onSubmit function, we validate our form with Vee-Validate by calling this.$refs.observer.validate(); to make sure diary is entered and that it’s entered before saving it, and the date field is entered in the YYYY-MM-DD format. We use Vee-Validate to validate the form fields. The ValidationObserver component is for validating the whole form, while the ValidationProvider component is for validating the form fields that it wraps around. The validation rule is specified by the rule prop of the date and diary fields. The state prop is for setting the validation state which shows the green when errors has length 0 and red otherwise. The error messages are shown in the b-form-invalid-feedback component. This page only has the countries drop down.

We correct the dates before submitting since dates in YYYY-MM-DD format has to be corrected by adding an offset for the time zone to save the date in UTC. Once the data is saved, we get the latest data and then put the data in the store. Then we emit the saved event to the HomePage so that we can close the modal.

Next we create a mixins folder in the src folder and create a file called requestsMixin.js file. In there, we add:

const APIURL = "[http://localhost:3000](http://localhost:3000)";
const axios = require("axios");

export const requestsMixin = {
  methods: {
    getJournals() {
      return axios.get(`${APIURL}/journals`);
    },

    addJournal(data) {
      return axios.post(`${APIURL}/journals`, data);
    },

    editJournal(data) {
      return axios.put(`${APIURL}/journals/${data.id}`, data);
    },

    deleteJournal(id) {
      return axios.delete(`${APIURL}/journals/${id}`);
    }
  }
};

These are the functions to get and save our journal data to back end.

Next in the views folder, we replace the code in the Home.vue file with:

<template>
  <div class="page">
    <h1 class="text-center">Journal</h1>

    <div class="text-center">
      <b-button @click="openAddModal()" variant="primary">Add Journal Entry</b-button>
      <b-button @click="openDeleteModal()" variant="primary">Delete Journal Entries</b-button>
    </div>

    <br />

    <Timeline
      :timeline-items="journals"
      message-when-no-items="No Entries Found"
      :unique-year="true"
      order="desc"
    />

    <b-modal id="add-modal" title="Add Journal" hide-footer>
      <JournalForm [@saved](http://twitter.com/saved "Twitter profile for @saved")="closeModal()" [@cancelled](http://twitter.com/cancelled "Twitter profile for @cancelled")="closeModal()" :edit="false" />
    </b-modal>

    <b-modal id="delete-modal" title="Delete Journal Entries" hide-footer>
      <b-card v-for="(j, i) of journals" :key="i">
        <b-card-text>
          <h2>{{j.from | dateFormat('YYYY-MM-DD')}}</h2>
          <p>{{j.description}}</p>
        </b-card-text>
        <b-button @click="deleteOneJournal(j.id)" variant="primary">Delete</b-button>
      </b-card>
    </b-modal>
  </div>
</template>

<script>
// @ is an alias to /src
import JournalForm from "@/components/JournalForm.vue";
import Timeline from "timeline-vuejs";
import { requestsMixin } from "@/mixins/requestsMixin";

export default {
  name: "home",
  components: {
    JournalForm,
    Timeline
  },

  mixins: [requestsMixin],
  computed: {
    journals() {
      return this.$store.state.journals
        .sort((a, b) => +new Date(b.date) - +new Date(a.date))
        .map(j => ({
          id: j.id,
          from: new Date(j.date),
          title: j.title,
          description: j.diary
        }));
    }
  },
  beforeMount() {
    this.getAllJournals();
  },
  data() {
    return {
      selectedJournal: {}
    };
  },
  methods: {
    openAddModal() {
      this.$bvModal.show("add-modal");
    },
    openDeleteModal() {
      this.$bvModal.show("delete-modal");
    },
    closeModal() {
      this.$bvModal.hide("add-modal");
      this.$bvModal.hide("delete-modal");
    },
    async deleteOneJournal(id) {
      await this.deleteJournal(id);
      this.getAllJournals();
    },
    async getAllJournals() {
      const { data } = await this.getJournals();
      this.$store.commit("setJournals", data);
      this.closeModal();
    }
  }
};
</script>

<style lang="scss" scoped>
.timeline {
  margin: 0 auto;
}
</style>

This is the home page our app. We have a button for adding and deleting journal entries at the top of the page. When we click the buttons we open the modals to open a form for entering diary entries and display a list of diaries where you can click Delete button to delete them individually respectively. Below the buttons, there’s the timeline for displaying the journal entries. We pass in the journals array into the timeline-items .

Below that we have the modals for displaying the JournalForm and a list of cards stacked vertically to display the existing items, where they can be read and deleted individually. we handle the saved event from the JournalForm to close the modal after items are saved.

In the delete-modal , we list the items and have a button to delete each entry. The from field is formatted to YYYY-MM-DD format by the VueFilterDateFormat package to display the dates properly.

In the scripts section, we have the journals property in the computed object to get the latest journal entries. We map the fields to the ones required by the Timeline-Vuejs package so that we can display the timeline properly. We sort them by reverse chronological order to view the latest items at the top and older items below it. Also, we have the beforeMount hook to get all the password entries during page load with the getJournals function we wrote in our mixin. To delete a journal entry, we call deleteOneJournal our mixin to make the request to the back end via the deleteJournal function.

Next in App.vue , we replace the existing code with:

<template>
  <div id="app">
    <b-navbar toggleable="lg" type="dark" variant="info">
      <b-navbar-brand to="/">Journal App</b-navbar-brand>

      <b-navbar-toggle target="nav-collapse"></b-navbar-toggle>

      <b-collapse id="nav-collapse" is-nav>
        <b-navbar-nav>
          <b-nav-item to="/" :active="path  == '/'">Home</b-nav-item>
        </b-navbar-nav>
      </b-collapse>
    </b-navbar>
    <router-view />
  </div>
</template>

<script>
export default {
  data() {
    return {
      path: this.$route && this.$route.path
    };
  },
  watch: {
    $route(route) {
      this.path = route.path;
    }
  }
};
</script>

<style lang="scss">
.page {
  padding: 20px;
}

button,
.btn.btn-primary {
  margin-right: 10px !important;
}

.button-toolbar {
  margin-bottom: 10px;
}
</style>

to add a Bootstrap navigation bar to the top of our pages, and a router-view to display the routes we define. This style section isn’t scoped so the styles will apply globally. In the .page selector, we add some padding to our pages. We also add margins to our app’s buttons here.

Next in main.js , we replace the existing code with:

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import BootstrapVue from "bootstrap-vue";
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-vue/dist/bootstrap-vue.css";
import "../node_modules/timeline-vuejs/dist/timeline-vuejs.css";
import { ValidationProvider, extend, ValidationObserver } from "vee-validate";
import { required } from "vee-validate/dist/rules";
import VueFilterDateFormat from "vue-filter-date-format";

extend("required", required);
extend("date", {
  validate: value =>
    /([12]d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]d|3[01]))/.test(value),
  message: "Date must be in YYYY-MM-DD format"
});
Vue.component("ValidationProvider", ValidationProvider);
Vue.component("ValidationObserver", ValidationObserver);
Vue.use(VueFilterDateFormat);
Vue.use(BootstrapVue);
Vue.config.productionTip = false;

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount("#app");

We added all the libraries we need here, including BootstrapVue JavaScript and CSS, Vee-Validate components along with the validation rules. We created the date rule which verifies that the inputs that used the rule will be in YYYY-MM-DD format. Also, we added the VueFilterDateFormat package to format the dates in the Date column of our table in Home.vue .

In router.js we replace the existing code with:

import Vue from "vue";
import Router from "vue-router";
import Home from "./views/Home.vue";

Vue.use(Router);

export default new Router({
  mode: "history",
  base: process.env.BASE_URL,
  routes: [
    {
      path: "/",
      name: "home",
      component: Home
    }
  ]
});

to include the home page in our routes so users can see the page.

And in store.js , we replace the existing code with:

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    journals: []
  },
  mutations: {
    setJournals(state, payload) {
      state.journals = payload;
    }
  },
  actions: {}
});

to add our journals state to the store so we can observer it in the computed block of JournalForm, and HomePage components. We have the setSavings function to update the savings state and we use it in the components by call this.$store.commit(“setJournals”, data); to set the journal entries data for the JournalForm and HomePage .

Finally, in index.html , we replace the existing code with:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
    <title>Journal App</title>
  </head>
  <body>
    <noscript>
      <strong
        >We're sorry but vue-timeline-tutorial-app doesn't work properly without
        JavaScript enabled. Please enable it to continue.</strong
      >
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

to change the title.

After all the hard work, we can start our app by running npm run serve.

To start the back end, we first install the json-server package by running npm i json-server. Then, go to our project folder and run:

json-server --watch db.json

In db.json, change the text to:

{
  "journals": [],
}

So we have the journals endpoints defined in the requests.js available.

Categories
Quasar

Developing Vue Apps with the Quasar Library — Tooltip Transitions and Position

Quasar is a popular Vue UI library for developing good looking Vue apps.

In this article, we’ll take a look at how to create Vue apps with the Quasar UI library.

Tooltip Transitions

We can add tooltip transitions with the transition-show and transition-hide props:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout view="lHh Lpr lFf" container style="height: 100vh;">
        <div class="q-pa-md">
          <q-btn color="primary" label="Flip">
            <q-tooltip transition-show="flip-right" transition-hide="flip-left">
              tooltip
            </q-tooltip>
          </q-btn>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {}
      });
    </script>
  </body>
</html>

transition-show is applied when we show the tooltip.

And transition-hide is applied when we hide it.

Other values include scale and rotate .

Tooltip Target

We can set the target prop of the q-tooltip component to let us set the tooltip to open when we hover over the element with the given selector.

For instance, we can write:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout view="lHh Lpr lFf" container style="height: 100vh;">
        <div class="q-pa-md">
          <q-img
            src="https://cdn.quasar.dev/img/material.png"
            id="target-img-1"
            style="height: 100px;"
          >
            <div
              class="absolute-bottom-right"
              style="border-top-left-radius: 5px;"
            >
              #target-img-1
            </div>
          </q-img>

          <q-tooltip
            :target="targetEl"
            anchor="center middle"
            self="center middle"
            content-class="bg-black"
          >
            tooltip
          </q-tooltip>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          targetEl: "#target-img-1"
        }
      });
    </script>
  </body>
</html>

We set target to '#target-img-1' , so when we hover over the image, the tooltip will show.

This gives us more flexibility with placing the tooltip.

Tooltip Position

We can change the position of the tooltip with the anchor and self props:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout view="lHh Lpr lFf" container style="height: 100vh;">
        <div class="q-pa-md">
          <q-btn color="primary" label="hover me">
            <q-tooltip anchor="bottom right" self="top middle">
              tooltip
            </q-tooltip>
          </q-btn>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          targetEl: "#target-img-1"
        }
      });
    </script>
  </body>
</html>

anchor changes the position of the tooltip relative to its target.

self changes its own position relative to its target.

Each of these props can be a combination of top , center , or bottom and left , middle or right .

Conclusion

We can add a tooltip with various positions and transitions with Quasar’s q-tooltip component into our Vue app.

Categories
Quasar

Developing Vue Apps with the Quasar Library — Tooltip

Quasar is a popular Vue UI library for developing good looking Vue apps.

In this article, we’ll take a look at how to create Vue apps with the Quasar UI library.

Tooltip

We can add a tooltip into our Vue app with Quasar’s q-tooltip component.

For instance, we can write:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout view="lHh Lpr lFf" container style="height: 100vh;">
        <div class="q-pa-md">
          <q-btn label="Hover me" color="primary">
            <q-tooltip>
              Some text as content of Tooltip
            </q-tooltip>
          </q-btn>

          <div
            class="inline bg-amber rounded-borders cursor-pointer"
            style="max-width: 300px;"
          >
            <div
              class="fit flex flex-center text-center non-selectable q-pa-md"
            >
              hover me
            </div>

            <q-tooltip>
              tooltip
            </q-tooltip>
          </div>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {}
      });
    </script>
  </body>
</html>

We can put the q-tooltip component inside the component or element that we want to show when we hover over them.

Also, we can control the tooltip open or closed state with the v-model directive:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout view="lHh Lpr lFf" container style="height: 100vh;">
        <div class="q-pa-md">
          <div class="q-gutter-sm">
            <q-btn color="primary" @click="showing = true" label="Show"></q-btn>
            <q-btn
              color="primary"
              @click="showing = false"
              label="Hide"
            ></q-btn>
          </div>

          <div
            style="width: 200px; height: 70px;"
            class="bg-purple text-white rounded-borders row flex-center q-mt-md"
          >
            Hover here or click buttons
            <q-tooltip v-model="showing">Tooltip text</q-tooltip>
          </div>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          showing: false
        }
      });
    </script>
  </body>
</html>

We set the showing state when we click on the Show or Hide buttons.

We can set the offset prop to position the tooltip our way:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout view="lHh Lpr lFf" container style="height: 100vh;">
        <div class="q-pa-md">
          <q-btn color="primary">
            Hover
            <q-tooltip content-class="bg-indigo" :offset="[10, 10]">
              tooltip
            </q-tooltip>
          </q-btn>

          <q-btn color="primary">
            Over
            <q-tooltip content-class="bg-red" :offset="[10, 10]">
              tooltip
            </q-tooltip>
          </q-btn>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {}
      });
    </script>
  </body>
</html>

Tooltip Delay

We can set the tooltip delay with the delay prop:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout view="lHh Lpr lFf" container style="height: 100vh;">
        <div class="q-pa-md">
          <div
            style="width: 200px; height: 70px;"
            class="bg-secondary text-white rounded-borders non-selectable row flex-center"
          >
            One second delay
            <q-tooltip :delay="1000" :offset="[0, 10]">tooltip</q-tooltip>
          </div>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {}
      });
    </script>
  </body>
</html>

The delay is in milliseconds.

Conclusion

We can add a tooltip into our Vue app with Quasar’a q-tooltip component.

Categories
Quasar

Developing Vue Apps with the Quasar Library — Timeline Options

Quasar is a popular Vue UI library for developing good looking Vue apps.

In this article, we’ll take a look at how to create Vue apps with the Quasar UI library.

Dark Timeline

We can add the dark prop to display the timeline in a dark background.

For instance, we can write:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout view="lHh Lpr lFf" container style="height: 100vh;">
        <div class="q-pa-md bg-grey-10 text-white">
          <q-timeline color="secondary" dark>
            <q-timeline-entry
              heading
              body="Timeline heading"
            ></q-timeline-entry>

            <q-timeline-entry
              title="Event Title"
              subtitle="February 22, 2000"
              avatar="https://cdn.quasar.dev/img/avatar3.jpg"
              :body="body"
            >
            </q-timeline-entry>

            <q-timeline-entry
              title="Event Title"
              subtitle="February 21, 2000"
              icon="delete"
              :body="body"
            >
            </q-timeline-entry>

            <q-timeline-entry heading body="November, 2017"></q-timeline-entry>

            <q-timeline-entry
              title="Event Title"
              subtitle="February 22, 2000"
              :body="body"
            >
            </q-timeline-entry>
          </q-timeline>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          body: "Lorem ipsum dolor sit amet"
        }
      });
    </script>
  </body>
</html>

We add the bg-grey-10 background to make the background dark.

And text-white makes the text white.

Timeline Layout and Side

We can add the timeline layout and side with the layout and side props:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout view="lHh Lpr lFf" container style="height: 100vh;">
        <div class="q-pa-md">
          <q-timeline color="secondary" layout="dense" side="left">
            <q-timeline-entry
              heading
              body="Timeline heading"
            ></q-timeline-entry>

            <q-timeline-entry
              title="Event Title"
              subtitle="February 22, 2000"
              avatar="https://cdn.quasar.dev/img/avatar3.jpg"
              :body="body"
            >
            </q-timeline-entry>

            <q-timeline-entry
              title="Event Title"
              subtitle="February 21, 2000"
              icon="delete"
              :body="body"
            >
            </q-timeline-entry>

            <q-timeline-entry heading body="November, 2017"></q-timeline-entry>

            <q-timeline-entry
              title="Event Title"
              subtitle="February 22, 2000"
              :body="body"
            >
            </q-timeline-entry>
          </q-timeline>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          body: "Lorem ipsum dolor sit amet"
        }
      });
    </script>
  </body>
</html>

layout can be set to dense , comfortable or loose .

And side can be left or right .

Responsive Timeline

We can add a responsive timeline by setting the layout prop dynamically:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout view="lHh Lpr lFf" container style="height: 100vh;">
        <div class="q-pa-md">
          <q-timeline color="secondary" :layout="layout">
            <q-timeline-entry
              heading
              body="Timeline heading"
            ></q-timeline-entry>

            <q-timeline-entry
              title="Event Title"
              subtitle="February 22, 2000"
              avatar="https://cdn.quasar.dev/img/avatar3.jpg"
              :body="body"
            >
            </q-timeline-entry>

            <q-timeline-entry
              title="Event Title"
              subtitle="February 21, 2000"
              icon="delete"
              :body="body"
            >
            </q-timeline-entry>

            <q-timeline-entry heading body="November, 2017"></q-timeline-entry>

            <q-timeline-entry
              title="Event Title"
              subtitle="February 22, 2000"
              :body="body"
            >
            </q-timeline-entry>
          </q-timeline>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          body: "Lorem ipsum dolor sit amet"
        },
        computed: {
          layout() {
            return this.$q.screen.lt.sm
              ? "dense"
              : this.$q.screen.lt.md
              ? "comfortable"
              : "loose";
          }
        }
      });
    </script>
  </body>
</html>

We add the layout computed property, which returns the value of layout by checking the screen size and returning the corresponding value.

Conclusion

We can add timelines with various options with Quasar’s timeline component.

Categories
Quasar

Developing Vue Apps with the Quasar Library — Swipeable Tabs and Timeline

Quasar is a popular Vue UI library for developing good looking Vue apps.

In this article, we’ll take a look at how to create Vue apps with the Quasar UI library.

Swipeable Tabs

We can make tabs swipeable with the animated , swipeable and infinite props together:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout view="lHh Lpr lFf" container style="height: 100vh;">
        <div class="q-pa-md">
          <q-tab-panels v-model="panel" animated swipeable infinite>
            <q-tab-panel name="mails">
              <div class="text-h6">Mails</div>
              Lorem ipsum
            </q-tab-panel>

            <q-tab-panel name="alarms">
              <div class="text-h6">Alarms</div>
              Lorem ipsum
            </q-tab-panel>

            <q-tab-panel name="movies">
              <div class="text-h6">Movies</div>
              Lorem ipsum
            </q-tab-panel>
          </q-tab-panels>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          panel: "mails"
        }
      });
    </script>
  </body>
</html>

Timeline

Quasar comes with a timeline component.

To add it, we write:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout view="lHh Lpr lFf" container style="height: 100vh;">
        <div class="q-pa-md">
          <q-timeline color="secondary">
            <q-timeline-entry heading>
              Timeline heading
            </q-timeline-entry>

            <q-timeline-entry title="Event Title" subtitle="February 22, 2000">
              <div>
                Lorem ipsum dolor sit amet
              </div>
            </q-timeline-entry>

            <q-timeline-entry
              title="Event Title"
              subtitle="February 21, 2000"
              icon="delete"
            >
              <div>
                Lorem ipsum dolor sit amet
              </div>
            </q-timeline-entry>

            <q-timeline-entry heading>
              November, 2017
            </q-timeline-entry>

            <q-timeline-entry
              title="Event Title"
              subtitle="February 22, 2000"
              avatar="https://cdn.quasar.dev/img/avatar2.jpg"
            >
              <div>
                Lorem ipsum dolor sit amet
              </div>
            </q-timeline-entry>

            <q-timeline-entry title="Event Title" subtitle="February 22, 2000">
              <div>
                Lorem ipsum dolor sit amet
              </div>
            </q-timeline-entry>

            <q-timeline-entry
              title="Event Title"
              subtitle="February 22, 2000"
              color="orange"
              icon="done_all"
            >
              <div>
                Lorem ipsum dolor sit amet
              </div>
            </q-timeline-entry>

            <q-timeline-entry title="Event Title" subtitle="February 22, 2000">
              <div>
                Lorem ipsum dolor sit amet
              </div>
            </q-timeline-entry>

            <q-timeline-entry title="Event Title" subtitle="February 22, 2000">
              <div>
                Lorem ipsum dolor sit amet
              </div>
            </q-timeline-entry>
          </q-timeline>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          panel: "mails"
        }
      });
    </script>
  </body>
</html>

We add the q-timeline component to add the timeline.

And we add the entries inside the timeline with the q-timeline-entry component.

The heading prop renders the entry as a heading.

We can add a q-timeline-entry without populating the default slot:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout view="lHh Lpr lFf" container style="height: 100vh;">
        <div class="q-pa-md">
          <q-timeline color="secondary">
            <q-timeline-entry
              heading
              body="Timeline heading"
            ></q-timeline-entry>

            <q-timeline-entry
              title="Event Title"
              subtitle="February 22, 2000"
              avatar="https://cdn.quasar.dev/img/avatar3.jpg"
              :body="body"
            >
            </q-timeline-entry>

            <q-timeline-entry
              title="Event Title"
              subtitle="February 21, 2000"
              icon="delete"
              :body="body"
            >
            </q-timeline-entry>

            <q-timeline-entry heading body="November, 2017"></q-timeline-entry>

            <q-timeline-entry
              title="Event Title"
              subtitle="February 22, 2000"
              :body="body"
            >
            </q-timeline-entry>
          </q-timeline>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          body: "Lorem ipsum dolor sit amet"
        }
      });
    </script>
  </body>
</html>

We put the content in the body prop.

Conclusion

We can add swipeable tabs and add a timeline with Quasar.