Language: EN

como-usar-vuetify-con-esp8266

How to use Vuetify with ESP8266 or ESP32

New entry about ESP8266 and ESP32 where we are going to see how to apply Material Design aesthetics to our VueJS applications served to the client thanks to Vuetify.

We will refer to ESP8266, but the same code is compatible for ESP32, adjusting the name of the libraries. At the end you have the code for both ESP8266 and ESP32.

Two entries ago we saw how to create a Web interface for ESP8266 using Vanilla Javascript. In the previous entry we saw how to use VueJS in ESP8266, as a framework to replace the logic of our App with a declarative formulation.

Unfortunately in the process, our page became as ugly as it was before. And we agreed that, in this post, we would focus on how to achieve the same Material Design aesthetics in our application in VueJS.

This is where Vuetify comes into play, a framework that we already saw in its day that provides a lot of components to make applications in VueJS with a nice, modern interface that won’t make you want to tear your eyes out when you see it.

Like the previous one, this introductory entry to the framework is going to be very simple. We are only going to focus on presenting it and making a small “hello world” App, and verify that it works correctly.

Our main program remains simple, and identical to the previous entry

#include <ESP8266WiFi.h>
#include <ESPAsyncWebServer.h>
#include <FS.h>

#include "config.h"  // Replace with your network data
#include "Server.hpp"
#include "ESP8266_Utils.hpp"

void setup(void)
{
  Serial.begin(115200);
  SPIFFS.begin();
  
  ConnectWiFi_STA();
  
  InitServer();
}

void loop(void)
{
}

Just like the server declaration in the “Server.hpp” file,

AsyncWebServer server(80);

void InitServer()
{
  server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html");

  server.onNotFound([](AsyncWebServerRequest *request) {
    request->send(400, "text/plain", "Not found");
  });

  server.begin();
    Serial.println("HTTP server started");
}

What is going to change is the client side,

Our ‘index.html’ file is longer than the previous one. But it is the “price” of having better aesthetics… more lines of code.

<!DOCTYPE html>
<html>
<head>
  <title>ESP8266 Vuetify</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <!-- From CDN -->
  <!--<link href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' rel="stylesheet">-->
  <!--<link href="https://cdn.jsdelivr.net/npm/vuetify@1.5.16/dist/vuetify.min.css" rel="stylesheet">-->

  <link href="./vendor/google-fonts.css" rel="stylesheet">
  <link href="./vendor/vuetify.min.css" rel="stylesheet">
</head>
<body>
 <div id="app">
  <v-app id="inspire">
    <v-container style="max-width: 500px">
      <v-text-field
        v-model="task"
        label="What are you working on?"
        solo
        @keydown.enter="create"
      >
        <v-fade-transition slot="append">
          <v-icon
            v-if="task"
            @click="create"
          >
            add_circle
          </v-icon>
        </v-fade-transition>
      </v-text-field>
  
      <h2 class="display-1 success--text pl-3">
        Tasks:&nbsp;
        <v-fade-transition leave-absolute>
          <span :key="`tasks-${tasks.length}`">
            {{ tasks.length }}
          </span>
        </v-fade-transition>
      </h2>
  
      <v-divider class="mt-3"></v-divider>
  
      <v-layout
        my-1
        align-center
      >
        <strong class="mx-3 info--text text--darken-3">
          Remaining: {{ remainingTasks }}
        </strong>
  
        <v-divider vertical></v-divider>
  
        <strong class="mx-3 black--text">
          Completed: {{ completedTasks }}
        </strong>
  
        <v-spacer></v-spacer>
  
        <v-progress-circular
          :value="progress"
          class="mr-2"
        ></v-progress-circular>
      </v-layout>
  
      <v-divider class="mb-3"></v-divider>
  
      <v-card v-if="tasks.length > 0">
        <v-slide-y-transition
          class="py-0"
          group
          tag="v-list"
        >
          <template v-for="(task, i) in tasks">
            <v-divider
              v-if="i !== 0"
              :key="`${i}-divider`"
            ></v-divider>
  
            <v-list-tile :key="`${i}-${task.text}`">
              <v-list-tile-action>
                <v-checkbox
                  v-model="task.done"
                  color="info darken-3"
                >
                  <div
                    slot="label"
                    :class="task.done && 'grey--text' || 'text--primary'"
                    class="ml-3"
                    v-text="task.text"
                  ></div>
                </v-checkbox>
              </v-list-tile-action>
  
              <v-spacer></v-spacer>
  
              <v-scroll-x-transition>
                <v-icon
                  v-if="task.done"
                  color="success"
                >
                  check
                </v-icon>
              </v-scroll-x-transition>
            </v-list-tile>
          </template>
        </v-slide-y-transition>
      </v-card>
    </v-container>
  </v-app>
 </div>

  <!-- From CDN -->
  <!--<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js"></script>-->
  <!--<script src="https://cdn.jsdelivr.net/npm/vuetify@1.5.16/dist/vuetify.min.js"></script>-->

  <script type="text/javascript" src="./vendor/vue.min.js"></script>
  <script type="text/javascript" src="./vendor/vuetify.min.js"></script>

  <script src="./js/app.js"></script>
</body>
</html>

On the other hand, the Vue application definition has also changed to adapt to the requirements of Vuetify, becoming

new Vue({
    el: '#app',
    data: () => ({
      tasks: [
        {
          done: false,
          text: 'Foobar'
        },
        {
          done: false,
          text: 'Fizzbuzz'
        }
      ],
      task: null
    }),
  
    computed: {
      completedTasks () {
        return this.tasks.filter(task => task.done).length
      },
      progress () {
        return this.completedTasks / this.tasks.length * 100
      },
      remainingTasks () {
        return this.tasks.length - this.completedTasks
      }
    },
  
    methods: {
      create () {
        this.tasks.push({
          done: false,
          text: this.task
        })
  
        this.task = null
      }
    }
  })

Result

We upload everything to the ESP8266 and load the page in the browser. Well! This is already something else! We have a user interface with Material Design aesthetics again, nice and beautiful, while maintaining the advantages that VueJS provides us.

esp8266-vuetify-resultado

We are now close to being able to completely remake our example web interface for ESP8266 in VueJs. But first, in the next entry, we will take a “mini break” to see another tool we will need, the AXIOS library to make AJAX requests. See you shortly!

Download the code

All the code from this post is available for download on Github.

github-full

Version for ESP8266: https://github.com/luisllamasbinaburo/ESP8266-Examples

Version for ESP32: https://github.com/luisllamasbinaburo/ESP32-Examples