como-usar-vuetify-con-esp8266

Cómo usar Vuetify con ESP8266 o ESP32

Nueva entrada sobre el ESP8266 y el ESP32 en la que vamos a ver cómo aplicar estética Material Design a nuestras aplicaciones VueJS servidas al cliente gracias a Vuetify.

Haremos referencia al ESP8266, pero el mismo código es compatible para el ESP32, ajustando el nombre de las librerías. Al final tenéis el código tanto para el ESP8266 como para el ESP32.

Hace dos entradas vimos cómo crear un interface Web para el ESP8266, usando Vanilla Javascript. En la entrada anterior vimos cómo usar VueJS en el ESP8266, como uno framework para sustituir la lógica de nuestra App por una formulación declarativa.

Lamentablemente en el camino, nuestra página volvió a ser fea como ella sola. Y quedamos en que, en esta entrada, nos centraríamos en ver cómo conseguir la misma estética Material Design en nuestra aplicación en VueJS.

Aquí es donde entra en juego Vuetify, un framework que ya vimos en su día, que aporta un montón de componentes para realizar aplicaciones en VueJS con un interface agradable, moderno, y que no te den ganas de arrancarte los ojos cuando lo veas.

Al igual que la anterior, esta entrada de introducción al framework va a ser muy sencilla. Únicamente nos vamos a centrar en presentarlo, y hacer una pequeña App “hola mundo”, y verificar que funciona correctamente.

Nuestro programa principal sigue siendo sencillo, e idéntico a la entrada anterior

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

#include "config.h"  // Sustituir con datos de vuestra red
#include "Server.hpp"
#include "ESP8266_Utils.hpp"

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

void loop(void)
{
}

Así como la declaración del servidor en el fichero “Server.hpp”,

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");
}

Lo que va a cambiar es el lado del cliente,

Nuestro fichero ‘index.html’ es más largo que el anterior. Pero es el “precio” de tener una estética mejor… más líneas de código.

<!DOCTYPE html>
<html>
<head>
  <title>ESP8266 Vuetify</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <!-- Desde 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>

  <!-- Desde 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>

Por su parte, la definición de la aplicación en Vue también ha cambiado, para adaptarse a los requisitos de Vuetify, pasando a ser,

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
      }
    }
  })

Resultado

Subimos todo al ESP8266 y cargamos la página en el navegador. ¡Bueno! ¡Esto ya es otra cosa! Volvemos a tener un interface con estética Material Design, agradable y bonito, a la vez que mantenemos las ventajas que nos aporta VueJS.

esp8266-vuetify-resultado

Ya estamos cerca de poder rehacer por completo nuestro interface web de ejemplo para el ESP8266, en VueJs. Pero antes, en la próxima entrada, haremos una “mini pausa” para ver otra de las herramientas que necesitaremos, la librería AXIOS para realizar peticiones AJAX. ¡Nos vemos enseguida!

Descarga el código

Todo el código de esta entrada está disponible para su descarga en Github.

github-full

Versión para el ESP8266: https://github.com/luisllamasbinaburo/ESP8266-Examples

Versión para el ESP32: https://github.com/luisllamasbinaburo/ESP32-Examples