Categories
Quasar

Developing Vue Apps with the Quasar Library — Touch Pan Directive

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.

Touch Pan Directive

We can listen to touch pan events with the v-touch directive.

For example, 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">
      <div class="q-pa-md">
        <q-card
          v-touch-pan.prevent.mouse="handlePan"
          class="custom-area cursor-pointer bg-primary text-white shadow-2 relative-position row flex-center"
        >
          <div v-if="info" class="custom-info">
            <pre>{{ info }}</pre>
          </div>
          <div v-else class="text-center">
            <q-icon name="arrow_upward"></q-icon>
            <div class="row items-center">
              <q-icon name="arrow_back"></q-icon>
              <div>Pan in any direction</div>
              <q-icon name="arrow_forward"></q-icon>
            </div>
            <q-icon name="arrow_downward"></q-icon>
          </div>

          <div v-show="panning" class="touch-signal">
            <q-icon name="touch_app"></q-icon>
          </div>
        </q-card>
      </div>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          info: null,
          panning: false
        },
        methods: {
          handlePan({ evt, ...info }) {
            this.info = info;
            if (info.isFirst) {
              this.panning = true;
            } else if (info.isFinal) {
              this.panning = false;
            }
          }
        }
      });
    </script>
  </body>
</html>

We add the v-touch-pan directive to the element we want to watch the touch and pan events for.

Then we get the event info from the first parameter of the event listener.

We get the distance, duration, offset, position, and more with the parameter.

isFirst indicates whether it’s the start of mouse drag or touch pan.

And isFinal indicates whether it’s the end of the mouse drag or touch pan.

We can restrict the direction of panning that’s detected for horizontal directions only with the horizontal modifier:

<!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">
      <div class="q-pa-md">
        <q-card
          v-touch-pan.horizontal.prevent.mouse="handlePan"
          class="custom-area cursor-pointer bg-primary text-white shadow-2 relative-position row flex-center"
        >
          <div v-if="info" class="custom-info">
            <pre>{{ info }}</pre>
          </div>
          <div v-else class="text-center">
            <div class="row items-center">
              <q-icon name="arrow_back"></q-icon>
              <div>Pan left or right</div>
              <q-icon name="arrow_forward"></q-icon>
            </div>
          </div>

          <div v-show="panning" class="touch-signal">
            <q-icon name="touch_app"></q-icon>
          </div>
        </q-card>
      </div>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          info: null,
          panning: false
        },
        methods: {
          handlePan({ evt, ...info }) {
            this.info = info;
            if (info.isFirst) {
              this.panning = true;
            } else if (info.isFinal) {
              this.panning = false;
            }
          }
        }
      });
    </script>
  </body>
</html>

Also, we can change the modifier to vertical to watch up and downward panning only:

<!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">
      <div class="q-pa-md">
        <q-card
          v-touch-pan.vertical.prevent.mouse="handlePan"
          class="custom-area cursor-pointer bg-primary text-white shadow-2 relative-position row flex-center"
        >
          <div v-if="info" class="custom-info">
            <pre>{{ info }}</pre>
          </div>
          <div v-else class="text-center">
            <div class="row items-center">
              <div>Pan up or down</div>
            </div>
          </div>

          <div v-show="panning" class="touch-signal">
            <q-icon name="touch_app"></q-icon>
          </div>
        </q-card>
      </div>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          info: null,
          panning: false
        },
        methods: {
          handlePan({ evt, ...info }) {
            this.info = info;
            if (info.isFirst) {
              this.panning = true;
            } else if (info.isFinal) {
              this.panning = false;
            }
          }
        }
      });
    </script>
  </body>
</html>

Conclusion

We can watch touch pan events with the v-touch-pan directive.

Categories
Quasar

Developing Vue Apps with the Quasar Library — Touch and Click Events

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.

Touch Hold Directive

We can add the v-touch-hold directive to run code when we click and hold the mouse button on an element.

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">  
      <div class="q-pa-md">  
        <q-card  
          v-touch-hold.mouse="handleHold"  
          class="custom-area cursor-pointer bg-primary text-white shadow-2 relative-position row flex-center"  
        >  
          <div v-if="info" class="custom-info">  
            <pre>{{ info }}</pre>  
          </div>  
          <div v-else class="text-center">  
            Click me  
          </div>  
        </q-card>  
      </div>  
    </div>  
    <script>  
      new Vue({  
        el: "#q-app",  
        data: {  
          info: null  
        },  
        methods: {  
          handleHold({ evt, ...info }) {  
            this.info = info;  
          }  
        }  
      });  
    </script>  
  </body>  
</html>

We get the data from the click event listener, which includes whether a tap or click is done.

Also, we get the position of the mouse click or tap and the duration.

The listener method runs 600ms after a click or tap is done by default.

We can change it with the argument:

<!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">  
      <div class="q-pa-md">  
        <q-card  
          v-touch-hold:2000.mouse="handleHold"  
          class="custom-area cursor-pointer bg-primary text-white shadow-2 relative-position row flex-center"  
        >  
          <div v-if="info" class="custom-info">  
            <pre>{{ info }}</pre>  
          </div>  
          <div v-else class="text-center">  
            Click me  
          </div>  
        </q-card>  
      </div>  
    </div>  
    <script>  
      new Vue({  
        el: "#q-app",  
        data: {  
          info: null  
        },  
        methods: {  
          handleHold({ evt, ...info }) {  
            this.info = info;  
          }  
        }  
      });  
    </script>  
  </body>  
</html>

We changed the listen to run 2000ms after a click or tap.

Also, we can change the sensitivity with some arguments:

<!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">  
      <div class="q-pa-md">  
        <q-card  
          v-touch-hold:2000:12:15.mouse="handleHold"  
          class="custom-area cursor-pointer bg-primary text-white shadow-2 relative-position row flex-center"  
        >  
          <div v-if="info" class="custom-info">  
            <pre>{{ info }}</pre>  
          </div>  
          <div v-else class="text-center">  
            Click me  
          </div>  
        </q-card>  
      </div>  
    </div>  
    <script>  
      new Vue({  
        el: "#q-app",  
        data: {  
          info: null  
        },  
        methods: {  
          handleHold({ evt, ...info }) {  
            this.info = info;  
          }  
        }  
      });  
    </script>  
  </body>  
</html>

We change the sensitivity to 12px for touch events and 15px for clicks.

Conclusion

We can add listeners for various mouse and touch events with the directives provided by Quasar.

Categories
Quasar

Developing Vue Apps with the Quasar Library — Effect Directives

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.

Ripple Effect

We can use the v-ripple directive to add ripple effects to elements or components that don’t have the effect built-in.

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">
      <div class="q-pa-md row justify-center">
        <div v-ripple class="relative-position container flex flex-center">
          Click me
        </div>
      </div>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {}
      });
    </script>
  </body>
</html>

We just add it to the element directly to add the effect.

We can change the color with an argument:

<!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">
      <div class="q-pa-md row justify-center">
        <div
          v-ripple:purple
          class="relative-position container flex flex-center"
        >
          Click me
        </div>
      </div>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {}
      });
    </script>
  </body>
</html>

Also, we can change the position by writing:

<!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">
    <style>
      .container {
        border-radius: 50%;
        cursor: pointer;
        width: 150px;
        height: 150px;
      }
    </style>
    <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">
      <div class="q-pa-md row justify-center">
        <div
          v-ripple.center
          class="relative-position container bg-grey-3 text-black inline flex flex-center"
        >
          Click me
        </div>
      </div>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {}
      });
    </script>
  </body>
</html>

Watch Scrolling

We can watch scrolling of an element with the v-scroll directive.

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">
      <div class="q-pa-md">
        <div v-scroll="scrolled">
          <p v-for="n of 1000" :key="n">{{n}}</p>
        </div>
      </div>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {},
        methods: {
          scrolled(position) {
            console.log(position);
          }
        }
      });
    </script>
  </body>
</html>

We pass in the scrolled event listener as the value of v-scroll .

Then we can get the position in pixels with the position parameter.

Trigger Scroll Effect Once

We can add the v-scroll-fire directive to trigger an effect once when the element is in view.

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">
    <style>
      .animate-bounce {
        animation: q-bounce 2s infinite;
      }

      @keyframes q-bounce {
        0%,
        20%,
        50%,
        80%,
        100% {
          transform: translateY(0);
        }
        40% {
          transform: translateY(-30px);
        }
        60% {
          transform: translateY(-15px);
        }
      }
    </style>
    <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">
      <div class="q-pa-md">
        <div>
          <p v-for="n of 1000" :key="n">{{n}}</p>
          <img
            v-scroll-fire="bounceImage"
            src="https://cdn.quasar.dev/logo/svg/quasar-logo.svg"
          />
        </div>
      </div>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {},
        methods: {
          bounceImage(el) {
            el.classList.add("animate-bounce");
            setTimeout(() => {
              if (document.body.contains(el)) {
                el.classList.remove("animate-bounce");
              }
            }, 2000);
          }
        }
      });
    </script>
  </body>
</html>

The bounceImage method is set as the value of the directive.

And it run once when the img element is in view to add the animate-bounce class and remove it after 2 seconds.

Conclusion

We can add various effects with the directives that are provided by Quasar.

Categories
Quasar

Developing Vue Apps with the Quasar Library — Intersection Observer

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.

Intersection Directive

We can watch for element visibility with Quasar’s wrapper on the Intersection Observer API.

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">
    <style>
      .state {
        background: #ccc;
        font-size: 20px;
        color: gray;
        padding: 10px;
        opacity: 0.8;
      }

      .observed {
        width: 100%;
        font-size: 20px;
        color: #ccc;
        background: #282a37;
        padding: 10px;
      }

      .area {
        height: 300px;
      }

      .filler {
        height: 500px;
      }
    </style>
    <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">
      <div class="relative-position">
        <div class="area q-pa-lg scroll">
          <div class="filler"></div>

          <div
            v-intersection="onIntersection"
            class="observed text-center rounded-borders"
          >
            Observed Element
          </div>

          <div class="filler"></div>
        </div>

        <div
          class="state rounded-borders text-center absolute-top q-mt-md q-ml-md q-mr-lg text-white"
          :class="visibleClass"
        >
          {{ visible === true ? 'Visible' : 'Hidden' }}
        </div>
      </div>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          visible: false
        },
        computed: {
          visibleClass() {
            return `bg-${this.visible ? "positive" : "negative"}`;
          }
        },
        methods: {
          onIntersection(entry) {
            this.visible = entry.isIntersecting;
          }
        }
      });
    </script>
  </body>
</html>

We add the v-intersection directive to run the onIntersection method went the intersection status changes.

We get the intersection status with the entry.isIntersecting property.

The handler will run when the div with the directive applied intersections the edge of the scroll container.

We add the visibleClass computed property to return the class to apply when the visible reactive property changes.

We can make the onIntersection method trigger only once with the once modifier.

Also, we can watch for the intersection percentage by referencing the entry.isIntersection property:

<!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">
    <style>
      .state {
        background: #ccc;
        font-size: 20px;
        color: gray;
        padding: 10px;
        opacity: 0.8;
      }

      .observed {
        width: 100%;
        font-size: 20px;
        color: #ccc;
        background: #282a37;
        padding: 10px;
      }

      .area {
        height: 300px;
      }

      .filler {
        height: 500px;
      }
    </style>
    <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">
      <div class="relative-position">
        <div class="area q-pa-lg scroll">
          <div class="filler"></div>

<div
            v-intersection="onIntersection"
            class="observed text-center rounded-borders"
          >
            Observed Element
          </div>

          <div class="filler"></div>
        </div>

        <div
          class="state rounded-borders text-center absolute-top q-mt-md q-ml-md q-mr-lg text-white"
          :class="visibleClass"
        >
          Percent: {{ percent }}%
        </div>
      </div>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          visible: false,
          percent: 0
        },
        computed: {
          visibleClass() {
            return `bg-${this.visible ? "positive" : "negative"}`;
          }
        },
        methods: {
          onIntersection(entry) {
            this.visible = entry.isIntersecting;
            const percent = (entry.intersectionRatio * 100).toFixed(0);
            if (this.percent !== percent) {
              this.percent = percent;
            }
          }
        }
      });
    </script>
  </body>
</html>

Also, we can use it to render items that are displayed on the screen:

<!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">
    <style>
      .item {
        height: 200px;
        width: 200px;
      }
    </style>
    <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">
      <div class="q-pa-md">
        <div class="row justify-center q-gutter-sm">
          <div
            v-for="index in inView.length"
            :key="index"
            :data-id="index - 1"
            class="item q-pa-sm flex flex-center relative-position"
            v-intersection="onIntersection"
          >
            <transition name="q-transition--scale">
              <q-card v-if="inView[index - 1]">
                <img src="https://cdn.quasar.dev/img/mountains.jpg" />

                <q-card-section>
                  <div class="text-h6">Card #{{ index }}</div>
                  <div class="text-subtitle2">by John Doe</div>
                </q-card-section>
              </q-card>
            </transition>
          </div>
        </div>
      </div>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          inView: Array(50)
            .fill()
            .map(() => false)
        },
        methods: {
          onIntersection(entry) {
            const index = +entry.target.dataset.id;
            setTimeout(() => {
              this.inView.splice(index, 1, entry.isIntersecting);
            }, 50);
          }
        }
      });
    </script>
  </body>
</html>

All we have to do is call splice to set the items that are displayed on the screen.

Conclusion

We can watch for intersections with Quasar’s wrapper for the Intersection Observer API.

Categories
Quasar

Developing Vue Apps with the Quasar Library — Close Popup and Go Back

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.

v-close-popup

The v-close-popup directive that comes with Quasar lets us close a popup when we click on an item that has the directive applied.

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-btn label="Menu" color="primary">
        <q-menu>
          <q-list dense style="min-width: 100px;">
            <q-item clickable v-close-popup>
              <q-item-section>Open...</q-item-section>
            </q-item>
            <q-item clickable v-close-popup>
              <q-item-section>New</q-item-section>
            </q-item>
            <q-separator></q-separator>

            <q-item clickable v-close-popup>
              <q-item-section>Quit</q-item-section>
            </q-item>
          </q-list>
        </q-menu>
      </q-btn>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {}
      });
    </script>
  </body>
</html>

We just add it to the item that we want to have applied to make them close the popup when we click on them.

The directive can also be applied to a dialog box.

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-btn label="Open Dialog" color="primary" @click="dialog = true"></q-btn>

      <q-dialog v-model="dialog">
        <q-card>
          <q-card-section>
            <div class="text-h6">Dialog</div>
          </q-card-section>
          <q-card-section class="row items-center q-gutter-sm">
            <q-btn label="Open dialog" color="primary" @click="dialog2 = true">
            </q-btn>
            <q-btn v-close-popup label="Close" color="primary"></q-btn>

            <q-dialog v-model="dialog2">
              <q-card>
                <q-card-section>
                  <div class="text-h6">Second dialog</div>
                </q-card-section>
                <q-card-section class="row items-center q-gutter-sm">
                  <q-btn
                    v-close-popup="2"
                    label="Close both dialogs"
                    color="accent"
                  >
                  </q-btn>
                  <q-btn v-close-popup label="Close this dialog" color="accent">
                  </q-btn>
                </q-card-section>
              </q-card>
            </q-dialog>
          </q-card-section>
        </q-card>
      </q-dialog>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          dialog: false,
          dialog2: false
        }
      });
    </script>
  </body>
</html>

We add the v-close-popup directive to the Close both dialogs button to make it close all popups.

Go Back

We can add the v-go-back directive to handle back button actions.

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-btn v-go-back=" '/' " color="primary" label="Logout"> </q-btn>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {}
      });
    </script>
  </body>
</html>

to add the v-go-back directive to go to the / path when we click it.

Conclusion

Quasar comes with directives to let us close popups when we click on something.

It also has the v-go-back directive to let us navigate when clicking an element.