Categories
Quasar

Developing Vue Apps with the Quasar Library — Virtual Scrolling

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.

Horizontal Virtual Scrolling

We can add the virtual-scroll-horizontal prop to make the virtual scrolling container horizontal:

<!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-virtual-scroll
        style="max-height: 300px;"
        :items="heavyList"
        separator
        virtual-scroll-horizontal
      >
        <template v-slot="{ item, index }">
          <q-item :key="index" dense>
            <q-item-section>
              <q-item-label>
                #{{ index }} - {{ item.label }}
              </q-item-label>
            </q-item-section>
          </q-item>
        </template>
      </q-virtual-scroll>
    </div>
    <script>
      const maxSize = 10000;
      const heavyList = [];

      for (let i = 0; i < maxSize; i++) {
        heavyList.push({
          label: `option ${i}`
        });
      }

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

Customized Item Template

We can customize the item template to display items the way we want:

<!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-virtual-scroll style="max-height: 300px;" :items="heavyList" separator>
        <template v-slot="{ item, index }">
          <q-banner
            v-if="item.banner === true"
            class="bg-black text-white q-py-xl"
            :key="index"
          >
            #{{ index }} - {{ item.label }}
          </q-banner>

          <q-item v-else :key="index" dense clickable>
            <q-item-section>
              <q-item-label>
                #{{ index }} - {{ item.label }}
              </q-item-label>
            </q-item-section>
          </q-item>
        </template>
      </q-virtual-scroll>
    </div>
    <script>
      const maxSize = 10000;
      const heavyList = [];

      for (let i = 0; i < maxSize; i++) {
        heavyList.push({
          label: `option ${i}`,
          banner: i === 0
        });
      }

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

We just put the item template in the default slot.

Table Style Virtual Scrolling Container

Also, we can display the items in a table style virtual scrolling container:

<!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-virtual-scroll
        type="table"
        style="max-height: 70vh;"
        :virtual-scroll-item-size="48"
        :virtual-scroll-sticky-size-start="48"
        :virtual-scroll-sticky-size-end="32"
        :items="heavyList"
      >
        <template v-slot="{ item: row, index }">
          <tr :key="index">
            <td>#{{ index }}</td>
            <td v-for="col in columns" :key="index + '-' + col">
              {{ row[col] }}
            </td>
          </tr>
        </template>
      </q-virtual-scroll>
    </div>
    <script>
      const data = [
        {
          name: "Frozen Yogurt",
          calories: 159,
          fat: 6.0,
          carbs: 24
        },
        {
          name: "Ice cream sandwich",
          calories: 237,
          fat: 9.0,
          carbs: 37
        },
        {
          name: "Eclair",
          calories: 262,
          fat: 16.0,
          carbs: 23
        },
        {
          name: "Cupcake",
          calories: 305,
          fat: 3.7,
          carbs: 67
        },
        {
          name: "Gingerbread",
          calories: 356,
          fat: 16.0,
          carbs: 49
        },
        {
          name: "Jelly bean",
          calories: 375,
          fat: 0.0,
          carbs: 94
        },
        {
          name: "Lollipop",
          calories: 392,
          fat: 0.2,
          carbs: 98
        },
        {
          name: "Honeycomb",
          calories: 408,
          fat: 3.2,
          carbs: 87
        },
        {
          name: "Donut",
          calories: 452,
          fat: 25.0,
          carbs: 51
        },
        {
          name: "KitKat",
          calories: 518,
          fat: 26.0,
          carbs: 65
        }
      ];

      const columns = ["name", "calories", "fat", "carbs"];

      const heavyList = [];
      for (let i = 0; i <= 1000; i++) {
        heavyList.push(...data);
      }
      Object.freeze(heavyList);
      Object.freeze(columns);

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

We render the columns in the default slot.

And we set the virtual-scroll-item-size prop to change height or width of the item in pixels, depending on if the list is vertical or horizontal respectively

The virtual-scroll-sticky-size-start prop to change the height or width of the sticky part in pixels, depending on if the list is vertical or horizontal respectively.

And the virtual-scroll-sticky-size-end prop to change the height or width of the bottom sticky part in pixels, depending on if the list is vertical or horizontal respectively.

Conclusion

We can add a virtual scrolling container with various styles with Quasar’s q-virtual-scroll component.

Categories
Quasar

Developing Vue Apps with the Quasar Library — Uploader, Video, and Virtual Scrolling

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.

Customized Uploader Header

We can customize the header by populating the header 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-uploader url="http://localhost/upload" label="Custom header" multiple>
        <template v-slot:header="scope">
          <div class="row no-wrap items-center q-pa-sm q-gutter-xs">
            <q-btn
              v-if="scope.queuedFiles.length > 0"
              icon="clear_all"
              @click="scope.removeQueuedFiles"
              round
              dense
              flat
            >
              <q-tooltip>Clear All</q-tooltip>
            </q-btn>
            <q-btn
              v-if="scope.uploadedFiles.length > 0"
              icon="done_all"
              @click="scope.removeUploadedFiles"
              round
              dense
              flat
            >
              <q-tooltip>Remove Uploaded Files</q-tooltip>
            </q-btn>
            <q-spinner
              v-if="scope.isUploading"
              class="q-uploader__spinner"
            ></q-spinner>
            <div class="col">
              <div class="q-uploader__title">Upload your files</div>
              <div class="q-uploader__subtitle">
                {{ scope.uploadSizeLabel }} / {{ scope.uploadProgressLabel }}
              </div>
            </div>
            <q-btn
              v-if="scope.canAddFiles"
              type="a"
              icon="add_box"
              round
              dense
              flat
            >
              <q-uploader-add-trigger></q-uploader-add-trigger>
              <q-tooltip>Pick Files</q-tooltip>
            </q-btn>
            <q-btn
              v-if="scope.canUpload"
              icon="cloud_upload"
              @click="scope.upload"
              round
              dense
              flat
            >
              <q-tooltip>Upload Files</q-tooltip>
            </q-btn>

            <q-btn
              v-if="scope.isUploading"
              icon="clear"
              @click="scope.abort"
              round
              dense
              flat
            >
              <q-tooltip>Abort Upload</q-tooltip>
            </q-btn>
          </div>
        </template>
      </q-uploader>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {},
        methods: {}
      });
    </script>
  </body>
</html>

scope.queuedFiles has an array of files added for upload.

scope.removeQueuedFiles is a method to remove the queued files.

scope.uploadedFiles has an array of the uploaded files.

scope.removeQueuedFiles is a method to remove the uploaded files.

scope.canAddFiles is a boolean that indicates if we can add files.

scope.uploadSizeLabel has the total size of the files uploaded.

scope.uploadProgressLabel has the upload progress.

scope.canUpload lets us know if we can upload.

Video

Quasar comes with a video component to let us embed videos.

For example, we can add a YouTube video 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">
    <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-video src="https://www.youtube.com/embed/KfYrKGPUi94"> </q-video>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {},
        methods: {}
      });
    </script>
  </body>
</html>

We add it by setting src to the embed URL.

We can set the aspect ratio with the ratio 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-video :ratio="16/9" src="https://www.youtube.com/embed/KfYrKGPUi94">
      </q-video>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {},
        methods: {}
      });
    </script>
  </body>
</html>

Virtual Scrolling

Quasar comes with a virtual scrolling container component.

It lets us render data that are only shown on the screen instead of rendering everything, increasing performance.

For example, 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-virtual-scroll style="max-height: 300px;" :items="heavyList" separator>
        <template v-slot="{ item, index }">
          <q-item :key="index" dense>
            <q-item-section>
              <q-item-label>
                #{{ index }} - {{ item.label }}
              </q-item-label>
            </q-item-section>
          </q-item>
        </template>
      </q-virtual-scroll>
    </div>
    <script>
      const maxSize = 10000;
      const heavyList = [];

      for (let i = 0; i < maxSize; i++) {
        heavyList.push({
          label: `option ${i}`
        });
      }

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

to add it.

We add the item into the default slot to render it.

items has the items.

separator adds a separator between items.

Conclusion

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

Also, we can add videos and virtual scrolling into our Vue app with Quasar.

Categories
Quasar

Developing Vue Apps with the Quasar Library — Uploader

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.

Uploader

We can add an upload widget into our Vue app with Quasar’s q-uploader 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-uploader
        url="http://localhost/upload"
        label="Upload files"
        color="purple"
        square
        flat
        bordered
        style="max-width: 300px;"
      >
      </q-uploader>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {}
      });
    </script>
  </body>
</html>

We set the URL to upload to with the url prop.

label is displayed on the widget.

color changes the color of the top var.

bordered, square and flat change the appearance of the widget.

We can enable multiple uploads with the multiple prop.

And the batch prop lets us upload items in parallel.

Restrict File Type

We can restrict the file type allowed with the accept 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-uploader
        url="http://localhost/upload"
        label="Upload files"
        color="purple"
        square
        flat
        bordered
        style="max-width: 300px;"
        accept=".jpg, image/*"
        @rejected="onRejected"
      >
      </q-uploader>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {},
        methods: {
          onRejected(files) {
            console.log(files);
          }
        }
      });
    </script>
  </body>
</html>

We set the accept prop to the MIME types.

And the rejected event listener has the rejected files in an array in the first parameter of the listener function.

We can restrict the file size of the files that are accepted with the filter 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-uploader
        url="http://localhost/upload"
        label="Upload files"
        color="purple"
        square
        flat
        bordered
        style="max-width: 300px;"
        :filter="checkFileSize"
        @rejected="onRejected"
      >
      </q-uploader>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {},
        methods: {
          onRejected(files) {
            console.log(files);
          },
          checkFileSize(files) {
            return files.filter((file) => file.size < 1000);
          }
        }
      });
    </script>
  </body>
</html>

We pass the checkFileSize method to chekc the file size.

The size property is in bytes.

Async Factory Function

We can add an async factory function to create the q-uploader :

<!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-uploader :factory="factoryFn" multiple> </q-uploader>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {},
        methods: {
          factoryFn(files) {
            return Promise.resolve({
              url: "http://localhost/upload"
            });
          }
        }
      });
    </script>
  </body>
</html>

We return a promise in the factoryFn method and pass the fucntion to the factory prop.

This will apply the settings in the resolved object.

Conclusion

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

Categories
Quasar

Developing Vue Apps with the Quasar Library — Accordion Tree, Filter, and Selection

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.

Accordion Tree Mode

We can show the tree view as an accordion with the accordion 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-tree
        :nodes="data"
        node-key="label"
        selected-color="primary"
        :selected.sync="selected"
        default-expand-all
        accordion
      >
      </q-tree>
    </div>
    <script>
      const data = [
        {
          label: "Hotel",
          children: [
            {
              label: "Food",
              icon: "restaurant_menu"
            },
            {
              label: "Room service",
              icon: "room_service"
            },
            {
              label: "Room view",
              icon: "photo"
            }
          ]
        }
      ];

      new Vue({
        el: "#q-app",
        data: {
          data,
          splitterModel: 50,
          selected: "Food"
        }
      });
    </script>
  </body>
</html>

Filter Tree Nodes

We can add a filter to let users search for tree nodes.

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-input ref="filter" filled v-model="filter" label="Filter">
        <template v-slot:append>
          <q-icon
            v-if="filter !== ''"
            name="clear"
            class="cursor-pointer"
            @click="resetFilter"
          >
          </q-icon>
        </template>
      </q-input>

      <q-tree
        :nodes="data"
        node-key="label"
        :filter="filter"
        default-expand-all
      >
      </q-tree>
    </div>
    <script>
      const data = [
        {
          label: "Hotel",
          children: [
            {
              label: "Food",
              icon: "restaurant_menu"
            },
            {
              label: "Room service",
              icon: "room_service"
            },
            {
              label: "Room view",
              icon: "photo"
            }
          ]
        }
      ];

      new Vue({
        el: "#q-app",
        data: {
          data,
          filter: ""
        },
        methods: {
          resetFilter() {
            this.filter = "";
            this.$refs.filter.focus();
          }
        }
      });
    </script>
  </body>
</html>

We add a q-input that’s bound to the filter reactive property.

Then we pass in the filter reactive property as the value of filter prop of the q-tree component.

This will let us search for nodes as we type into the input box.

Selectable Nodes

We can set nodes as selected by setting the reactive property bound to the q-tree as the value.

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>
        <div class="q-gutter-sm">
          <q-btn
            size="sm"
            color="primary"
            @click="selectRoomService"
            label="Select Room service"
          >
          </q-btn>
          <q-btn
            v-if="selected"
            size="sm"
            color="red"
            @click="unselectNode"
            label="Unselect node"
          >
          </q-btn>
        </div>
      </div>

      <q-tree
        :nodes="data"
        default-expand-all
        :selected.sync="selected"
        node-key="label"
      >
      </q-tree>
    </div>
    <script>
      const data = [
        {
          label: "Hotel",
          children: [
            {
              label: "Food",
              icon: "restaurant_menu"
            },
            {
              label: "Room service",
              icon: "room_service"
            },
            {
              label: "Room view",
              icon: "photo"
            }
          ]
        }
      ];

      new Vue({
        el: "#q-app",
        data: {
          data,
          selected: ""
        },
        methods: {
          selectRoomService() {
            if (this.selected !== "Room service") {
              this.selected = "Room service";
            }
          },
          unselectNode() {
            this.selected = null;
          }
        }
      });
    </script>
  </body>
</html>

In the selectRoomService method, we set this.selected to 'Room service' .

And since this.selected is bound to q-tree with v-model , the node will be selected.

And if we set it to null as we did in unselectNode , the node will be unselected.

Conclusion

We can customize Quasar’s tree component and add filter and selection features to it.

Categories
Quasar

Developing Vue Apps with the Quasar Library — Tree View

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.

Tree View

We can add a tree view into our Vue app with Quasar’s q-tree 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-tree :nodes="data" node-key="label"> </q-tree>
        </div>
      </q-layout>
    </div>
    <script>
      const data = [
        {
          label: "Satisfied customers",
          avatar: "https://cdn.quasar.dev/img/avatar.png",
          children: [
            {
              label: "Good food (with icon)",
              icon: "restaurant_menu",
              children: [
                { label: "Good ingredients" },
                { label: "Good recipe" }
              ]
            },
            {
              label: "Good service (disabled node with icon)",
              icon: "room_service",
              disabled: true,
              children: [
                { label: "Prompt attention" },
                { label: "Professional waiter" }
              ]
            },
            {
              label: "Pleasant surroundings (with icon)",
              icon: "photo",
              children: [
                {
                  label: "Happy atmosphere",
                  img: "https://cdn.quasar.dev/img/logo_calendar_128px.png"
                },
                { label: "Good presentation" },
                { label: "Pleasing decor" }
              ]
            }
          ]
        }
      ];

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

We pass the tree’s data into the node prop.

node-key has the unique ID of each tree node.

The disabled property lets us disable interactivity with a tree node.

We can remove the connectors with the no-connectors prop.

We can bind the selected node to a reactive property with the v-model 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">
      <q-splitter v-model="splitterModel" style="height: 400px;">
        <template v-slot:before>
          <div class="q-pa-md">
            <q-tree
              :nodes="data"
              node-key="label"
              selected-color="primary"
              :selected.sync="selected"
              default-expand-all
            >
            </q-tree>
          </div>
        </template>

        <template v-slot:after>
          <q-tab-panels
            v-model="selected"
            animated
            transition-prev="jump-up"
            transition-next="jump-up"
          >
            <q-tab-panel name="Hotel">
              <div class="text-h4 q-mb-md">Welcome</div>
              <p>
                Lorem ipsum dolor sit
              </p>
            </q-tab-panel>

            <q-tab-panel name="Food">
              <div class="text-h4 q-mb-md">Food</div>
              <p>
                Lorem ipsum dolor sit
              </p>
            </q-tab-panel>

            <q-tab-panel name="Room service">
              <div class="text-h4 q-mb-md">Room service</div>
              <p>
                Lorem ipsum dolor sit
              </p>
            </q-tab-panel>

            <q-tab-panel name="Room view">
              <div class="text-h4 q-mb-md">Room view</div>
              <p>
                Lorem ipsum dolor sit
              </p>
            </q-tab-panel>
          </q-tab-panels>
        </template>
      </q-splitter>
    </div>
    <script>
      const data = [
        {
          label: "Hotel",
          children: [
            {
              label: "Food",
              icon: "restaurant_menu"
            },
            {
              label: "Room service",
              icon: "room_service"
            },
            {
              label: "Room view",
              icon: "photo"
            }
          ]
        }
      ];

      new Vue({
        el: "#q-app",
        data: {
          data,
          splitterModel: 50,
          selected: "Food"
        }
      });
    </script>
  </body>
</html>

The splitterModel reactive property is bound to the label property value of the node that’s been clicked on.

Conclusion

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