Categories
Quasar

Developing Vue Apps with the Quasar Library — Tab 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.

Tab Ripple Effect

We can a ripple effect on the tabs.

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-tabs v-model="tab">  
            <q-tab  
              :ripple="{ color: 'orange' }"  
              name="mails"  
              icon="mail"  
              label="Mails"  
            ></q-tab>  
            <q-tab  
              :ripple="{ color: 'orange' }"  
              name="alarms"  
              icon="alarm"  
              label="Alarms"  
            ></q-tab>  
            <q-tab  
              :ripple="{ color: 'orange' }"  
              name="movies"  
              icon="movie"  
              label="Movies"  
            ></q-tab>  
          </q-tabs>  
        </div>  
      </q-layout>  
    </div>  
    <script>  
      new Vue({  
        el: "#q-app",  
        data: {  
          tab: undefined  
        }  
      });  
    </script>  
  </body>  
</html>

We set the ripple prop to an object with the color property.

Indicator Color

We can change the color when the tab is active with the active-color prop.

And we can change the color of the bottom indicator when the indicator-color 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">  
          <q-tabs  
            class="bg-primary text-white shadow-2"  
            v-model="tab"  
            indicator-color="transparent"  
            active-color="white"  
          >  
            <q-tab name="mails" icon="mail" label="Mails"></q-tab>  
            <q-tab name="alarms" icon="alarm" label="Alarms"></q-tab>  
            <q-tab name="movies" icon="movie" label="Movies"></q-tab>  
          </q-tabs>  
        </div>  
      </q-layout>  
    </div>  
    <script>  
      new Vue({  
        el: "#q-app",  
        data: {  
          tab: undefined  
        }  
      });  
    </script>  
  </body>  
</html>

Tab Notifications

We can add badges to tabs to show notifications.

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-tabs v-model="tab" class="bg-primary text-white shadow-2">  
            <q-tab name="mails" icon="mail" label="Mails">  
              <q-badge color="red" floating>2</q-badge>  
            </q-tab>  
            <q-tab name="alarms" icon="alarm" label="Alarms">  
              <q-badge color="red" floating>10+</q-badge>  
            </q-tab>  
            <q-tab alert name="movies" icon="movie" label="Movies" />  
          </q-tabs>  
        </div>  
      </q-layout>  
    </div>  
    <script>  
      new Vue({  
        el: "#q-app",  
        data: {  
          tab: undefined  
        }  
      });  
    </script>  
  </body>  
</html>

We add the q-badge into the default slots of the q-tab components.

Tab Alignment

We can set the alignment of the tabs with the align 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">  
          <q-tabs  
            v-model="tab"  
            dense  
            align="left"  
            class="bg-primary text-white shadow-2"  
            :breakpoint="0"  
          >  
            <q-tab name="mails" icon="mail"></q-tab>  
            <q-tab name="alarms" icon="alarm"></q-tab>  
          </q-tabs>  
        </div>  
      </q-layout>  
    </div>  
    <script>  
      new Vue({  
        el: "#q-app",  
        data: {  
          tab: undefined  
        }  
      });  
    </script>  
  </body>  
</html>

Now all the tabs are displayed on the left side of the screen.

We can also set it to center , right and justify .

Conclusion

We can add ripple effects, set tab notifications, and set tab alignment with Quasar’s tab component.

Categories
Quasar

Developing Vue Apps with the Quasar Library — Tabs

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.

Tabs

We can add tabs into our Vue app with Quasar’s q-tab 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;"  
        class="shadow-2 rounded-borders"  
      >  
        <div class="q-pa-md">  
          <q-tabs  
            v-model="tab"  
            inline-label  
            class="bg-purple text-white shadow-2"  
          >  
            <q-tab name="mails" icon="mail" label="Mails"></q-tab>  
            <q-tab name="alarms" icon="alarm" label="Alarms"></q-tab>  
            <q-tab name="movies" icon="movie" label="Movies"></q-tab>  
          </q-tabs>  
        </div>  
      </q-layout>  
    </div>  
    <script>  
      new Vue({  
        el: "#q-app",  
        data: {  
          tab: undefined  
        }  
      });  
    </script>  
  </body>  
</html>

We add the q-tabs component as a wrapper.

And we add the q-tab component to add the tabs.

icon has the icon name. And label has the label text.

The name value is set as the value of the tab reactive property when we click on a tab.

The inline-label prop lets us make the label text display to the right of the icon.

Tab Arrows

Arrows will display when the tabs overflow the container.

We can set it to display outside the tab content with the outside-arrow 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;"  
        class="shadow-2 rounded-borders"  
      >  
        <div class="q-pa-md">  
          <q-tabs  
            v-model="tab"  
            inline-label  
            outside-arrows  
            mobile-arrows  
            class="bg-purple text-white shadow-2"  
          >  
            <q-tab name="mails" icon="mail" label="Mails"></q-tab>  
            <q-tab name="alarms" icon="alarm" label="Alarms"></q-tab>  
            <q-tab name="movies" icon="movie" label="Movies"></q-tab>  
          </q-tabs>  
        </div>  
      </q-layout>  
    </div>  
    <script>  
      new Vue({  
        el: "#q-app",  
        data: {  
          tab: undefined  
        }  
      });  
    </script>  
  </body>  
</html>

Vertical Tabs

We can add the verttical prop to set the tabs to display vertically:

<!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;"  
        class="shadow-2 rounded-borders"  
      >  
        <div class="q-pa-md">  
          <q-tabs v-model="tab" vertical class="bg-purple text-white shadow-2">  
            <q-tab name="mails" icon="mail" label="Mails"></q-tab>  
            <q-tab name="alarms" icon="alarm" label="Alarms"></q-tab>  
            <q-tab name="movies" icon="movie" label="Movies"></q-tab>  
          </q-tabs>  
        </div>  
      </q-layout>  
    </div>  
    <script>  
      new Vue({  
        el: "#q-app",  
        data: {  
          tab: undefined  
        }  
      });  
    </script>  
  </body>  
</html>

Tab Color

We can set the color of the tab content individually with their own classes:

<!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-tabs v-model="tab">  
            <q-tab  
              class="text-purple"  
              name="mails"  
              icon="mail"  
              label="Mails"  
            ></q-tab>  
            <q-tab  
              class="text-orange"  
              name="alarms"  
              icon="alarm"  
              label="Alarms"  
            ></q-tab>  
            <q-tab  
              class="text-teal"  
              name="movies"  
              icon="movie"  
              label="Movies"  
            ></q-tab>  
          </q-tabs>  
        </div>  
      </q-layout>  
    </div>  
    <script>  
      new Vue({  
        el: "#q-app",  
        data: {  
          tab: undefined  
        }  
      });  
    </script>  
  </body>  
</html>

We add our own class to each q-tab .

Conclusion

We can add tabs into our Vue app with Quasar’s q-tabs and q-tab components.

Categories
Quasar

Developing Vue Apps with the Quasar Library — Grid Display and Export Table Content

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.

Conditionally Display Table as Grid

The Quasar q-table‘s grid prop takes a boolean that lets us show the table as a grid conditionally.

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;"
        class="shadow-2 rounded-borders"
      >
        <div class="q-pa-md">
          <q-table
            title="Treats"
            :data="data"
            :columns="columns"
            row-key="name"
            :grid="$q.screen.xs"
          >
          </q-table>
        </div>
      </q-layout>
    </div>
    <script>
      const columns = [
        {
          name: "name",
          required: true,
          label: "Dessert",
          align: "left",
          field: (row) => row.name,
          format: (val) => `${val}`,
          sortable: true
        },
        {
          name: "calories",
          align: "center",
          label: "Calories",
          field: "calories",
          sortable: true
        },
        { name: "fat", label: "Fat (g)", field: "fat", sortable: true },
        {
          name: "calcium",
          label: "Calcium (%)",
          field: "calcium",
          sortable: true,
          sort: (a, b) => parseInt(a, 10) - parseInt(b, 10)
        }
      ];
      const data = [
        {
          name: "Frozen Yogurt",
          calories: 159,
          fat: 6.0,
          calcium: "14%"
        },
        {
          name: "Ice cream sandwich",
          calories: 237,
          fat: 9.0,
          calcium: "8%"
        },
        {
          name: "Eclair",
          calories: 262,
          fat: 16.0,
          calcium: "6%"
        },
        {
          name: "Honeycomb",
          calories: 408,
          fat: 3.2,
          calcium: "0%"
        },
        {
          name: "Donut",
          calories: 452,
          fat: 25.0,
          calcium: "2%"
        },
        {
          name: "KitKat",
          calories: 518,
          fat: 26.0,
          calcium: "12%"
        }
      ];
      new Vue({
        el: "#q-app",
        data: {
          columns,
          data
        }
      });
    </script>
  </body>
</html>

We set it to $q.screen.xs .

So when the screen hits the xs breakpoint, the content is displayed as a grid.

Export Table Data

Quasar comes with the Quasar.export method to let us export data from a table.

We can use it by writing the following:

<!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;"
        class="shadow-2 rounded-borders"
      >
        <div class="q-pa-md">
          <q-table
            title="Treats"
            :data="data"
            :columns="columns"
            row-key="name"
            :grid="$q.screen.xs"
          >
            <template v-slot:top-right>
              <q-btn
                color="primary"
                label="Export to csv"
                no-caps
                @click="exportTable"
              >
              </q-btn>
            </template>
          </q-table>
        </div>
      </q-layout>
    </div>
    <script>
      const columns = [
        {
          name: "name",
          required: true,
          label: "Dessert",
          align: "left",
          field: (row) => row.name,
          format: (val) => `${val}`,
          sortable: true
        },
        {
          name: "calories",
          align: "center",
          label: "Calories",
          field: "calories",
          sortable: true
        },
        { name: "fat", label: "Fat (g)", field: "fat", sortable: true },
        {
          name: "calcium",
          label: "Calcium (%)",
          field: "calcium",
          sortable: true,
          sort: (a, b) => parseInt(a, 10) - parseInt(b, 10)
        }
      ];
      const data = [
        {
          name: "Frozen Yogurt",
          calories: 159,
          fat: 6.0,
          calcium: "14%"
        },
        {
          name: "Ice cream sandwich",
          calories: 237,
          fat: 9.0,
          calcium: "8%"
        },
        {
          name: "Eclair",
          calories: 262,
          fat: 16.0,
          calcium: "6%"
        },
        {
          name: "Honeycomb",
          calories: 408,
          fat: 3.2,
          calcium: "0%"
        },
        {
          name: "Donut",
          calories: 452,
          fat: 25.0,
          calcium: "2%"
        },
        {
          name: "KitKat",
          calories: 518,
          fat: 26.0,
          calcium: "12%"
        }
      ];

      const wrapCsvValue = (val, formatFn) => {
        let formatted = formatFn !== void 0 ? formatFn(val) : val;
        formatted =
          formatted === void 0 || formatted === null ? "" : String(formatted);
        formatted = formatted.split('"').join('""');
        return `"${formatted}"`;
      };

      new Vue({
        el: "#q-app",
        data: {
          columns,
          data
        },
        methods: {
          exportTable() {
            const content = [this.columns.map((col) => wrapCsvValue(col.label))]
              .concat(
                this.data.map((row) =>
                  this.columns
                    .map((col) =>
                      wrapCsvValue(
                        typeof col.field === "function"
                          ? col.field(row)
                          : row[col.field === undefined ? col.name : col.field],
                        col.format
                      )
                    )
                    .join(",")
                )
              )
              .join("rn");

            const status = Quasar.exportFile(
              "table-export.csv",
              content,
              "text/csv"
            );
          }
        }
      });
    </script>
  </body>
</html>

We convert each value to a CSV value with the wrapCsvValue function.

It wraps any value with spaces in quotes.

Then in the exportTable method, we call the wrapCsvValue to format the values.

Then we join each row object property together with commas.

And after that, we join the rows together.

Then we call Quasar.exportFile with the file name, content to save, and the MIME type of the file.

Conclusion

We can display our table conditionally as a grid.

Also, we can export the table content in the format we want with the exportFile method.

Categories
Quasar

Developing Vue Apps with the Quasar Library — Table No Data Display and Custom Sorting

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.

No Data Display

We can display something when no data is displayed in our table.

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;"
        class="shadow-2 rounded-borders"
      >
        <div class="q-pa-md">
          <q-table
            title="Treats"
            :data="data"
            :columns="columns"
            :filter="filter"
            no-data-label="I didn't find anything for you"
            no-results-label="The filter didn't uncover any results"
            row-key="name"
          >
            <template v-slot:top-right>
              <q-input
                borderless
                dense
                debounce="300"
                v-model="filter"
                placeholder="Search"
              >
                <q-icon slot="append" name="search"></q-icon>
              </q-input>
            </template>

            <template v-slot:no-data="{ icon, message, filter }">
              <div>No data</div>
            </template>
          </q-table>
        </div>
      </q-layout>
    </div>
    <script>
      const columns = [
        {
          name: "name",
          required: true,
          label: "Dessert",
          align: "left",
          field: (row) => row.name,
          format: (val) => `${val}`,
          sortable: true
        },
        {
          name: "calories",
          align: "center",
          label: "Calories",
          field: "calories",
          sortable: true
        },
        { name: "fat", label: "Fat (g)", field: "fat", sortable: true },
        {
          name: "calcium",
          label: "Calcium (%)",
          field: "calcium",
          sortable: true,
          sort: (a, b) => parseInt(a, 10) - parseInt(b, 10)
        }
      ];
      const data = [
        {
          name: "Frozen Yogurt",
          calories: 159,
          fat: 6.0,
          calcium: "14%"
        },
        {
          name: "Ice cream sandwich",
          calories: 237,
          fat: 9.0,
          calcium: "8%"
        },
        {
          name: "Eclair",
          calories: 262,
          fat: 16.0,
          calcium: "6%"
        },
        {
          name: "Honeycomb",
          calories: 408,
          fat: 3.2,
          calcium: "0%"
        },
        {
          name: "Donut",
          calories: 452,
          fat: 25.0,
          calcium: "2%"
        },
        {
          name: "KitKat",
          calories: 518,
          fat: 26.0,
          calcium: "12%"
        }
      ];
      new Vue({
        el: "#q-app",
        data: {
          columns,
          data,
          filter: ""
        }
      });
    </script>
  </body>
</html>

to add that.

We populate the no-data slot with the content we want to show when there’s no data displayed in the table.

Hide Bottom Bar

We can hide the bottom bar by adding the hide-bottom 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;"
        class="shadow-2 rounded-borders"
      >
        <div class="q-pa-md">
          <q-table
            title="Treats"
            :data="data"
            :columns="columns"
            row-key="name"
            hide-bottom
          >
          </q-table>
        </div>
      </q-layout>
    </div>
    <script>
      const columns = [
        {
          name: "name",
          required: true,
          label: "Dessert",
          align: "left",
          field: (row) => row.name,
          format: (val) => `${val}`,
          sortable: true
        },
        {
          name: "calories",
          align: "center",
          label: "Calories",
          field: "calories",
          sortable: true
        },
        { name: "fat", label: "Fat (g)", field: "fat", sortable: true },
        {
          name: "calcium",
          label: "Calcium (%)",
          field: "calcium",
          sortable: true,
          sort: (a, b) => parseInt(a, 10) - parseInt(b, 10)
        }
      ];
      const data = [
        {
          name: "Frozen Yogurt",
          calories: 159,
          fat: 6.0,
          calcium: "14%"
        },
        {
          name: "Ice cream sandwich",
          calories: 237,
          fat: 9.0,
          calcium: "8%"
        },
        {
          name: "Eclair",
          calories: 262,
          fat: 16.0,
          calcium: "6%"
        },
        {
          name: "Honeycomb",
          calories: 408,
          fat: 3.2,
          calcium: "0%"
        },
        {
          name: "Donut",
          calories: 452,
          fat: 25.0,
          calcium: "2%"
        },
        {
          name: "KitKat",
          calories: 518,
          fat: 26.0,
          calcium: "12%"
        }
      ];
      new Vue({
        el: "#q-app",
        data: {
          columns,
          data,
          filter: ""
        }
      });
    </script>
  </body>
</html>

Custom Sorting

We can add custom sorting by setting the sort-method prop to our own sorting method:

<!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;"
        class="shadow-2 rounded-borders"
      >
        <div class="q-pa-md">
          <q-table
            title="Treats"
            :data="data"
            :columns="columns"
            row-key="name"
            :sort-method="customSort"
            binary-state-sort
          >
          </q-table>
        </div>
      </q-layout>
    </div>
    <script>
      const columns = [
        {
          name: "name",
          required: true,
          label: "Dessert",
          align: "left",
          field: (row) => row.name,
          format: (val) => `${val}`,
          sortable: true
        },
        {
          name: "calories",
          align: "center",
          label: "Calories",
          field: "calories",
          sortable: true
        },
        { name: "fat", label: "Fat (g)", field: "fat", sortable: true },
        {
          name: "calcium",
          label: "Calcium (%)",
          field: "calcium",
          sortable: true,
          sort: (a, b) => parseInt(a, 10) - parseInt(b, 10)
        }
      ];
      const data = [
        {
          name: "Frozen Yogurt",
          calories: 159,
          fat: 6.0,
          calcium: "14%"
        },
        {
          name: "Ice cream sandwich",
          calories: 237,
          fat: 9.0,
          calcium: "8%"
        },
        {
          name: "Eclair",
          calories: 262,
          fat: 16.0,
          calcium: "6%"
        },
        {
          name: "Honeycomb",
          calories: 408,
          fat: 3.2,
          calcium: "0%"
        },
        {
          name: "Donut",
          calories: 452,
          fat: 25.0,
          calcium: "2%"
        },
        {
          name: "KitKat",
          calories: 518,
          fat: 26.0,
          calcium: "12%"
        }
      ];
      new Vue({
        el: "#q-app",
        data: {
          columns,
          data
        },
        methods: {
          customSort(rows, sortBy, descending) {
            const data = [...rows];

            if (sortBy) {
              data.sort((a, b) => {
                const x = descending ? b : a;
                const y = descending ? a : b;

                if (sortBy === "name") {
                  return x[sortBy] > y[sortBy]
                    ? 1
                    : x[sortBy] < y[sortBy]
                    ? -1
                    : 0;
                } else {
                  return +x[sortBy] - +y[sortBy];
                }
              });
            }

            return data;
          }
        }
      });
    </script>
  </body>
</html>

The customSort method takes the rows parameter which has all the table data.

sortBy has the field that we sort by.

And descending is a boolean that indicates whether we sort in descending order or not.

Conclusion

We can add custom sorting and no data display into our Quasar table.

Categories
JavaScript Basics

Async Iteration with JavaScript

Asynchronous code is a very important part of JavaScript. We need to write lots of asynchronous code since JavaScript is a single-threaded language. If we run everything line by line, then code that takes longer to run will hold up the program, causing it to freeze.

To write asynchronous code easily, JavaScript has promises which are chainable and can be run in sequence without nesting.

ES2017 introduced the async and await syntax for chaining promises, which makes everything easier. However, there was still no way to run promises sequentially by iterating them in sequence. This means that running lots of promises in a sequence is still a problem that has no solution yet.

Fortunately, in ES2018, we finally have the for-await-of loop that we can use with async functions that were introduced in ES2017. It works with any iterable objects like the synchronous for...of loop. This means that we can iterate through objects like Maps, Sets, NodeLists, and the arguments object with it.

Also, it can iterate through any object with the Symbol.iterator method which returns a generator to let us do the iteration.

for-await-of Loop

We can use the for-await-of loop as follows:

let promises = [];
for (let i = 1; i <= 10; i++) {
  promises.push(new Promise((resolve) => {
    setTimeout(() => resolve(i), 1000);
  }));
}

(async () => {
  for await (let p of promises) {
    const val = await p;
    console.log(val);
  }
})();

In the example above, we added 10 promises by defining them in the for loop and then pushing them to the promises array. Then we run the promises one at a time by defining an async function and then use the for-await-of loop to loop through each promise and run them one at a time.

The await in the for-await-of loop will get us the resolved value from the promise and the console.log will log the resolved value.

Like any other async functions, it can only return promises.

The syntax of the for-await-of loop is the same as any other for...of loop. The let p is the variable declaration and promises is the promises array that we’re iterating through.

The code above works like Promise.all , the code is run in parallel, so we see 1 to 10 after 1 second.

Async Iterables

Alternatively, we can define our own asynchronous iterable object if we define an object with the Symbol.asyncIterator method. It’s a special method that returns a generator lets us iterate through it with the for-await-of loop.

For example, we can write:

let asyncIterable = {
  [Symbol.asyncIterator]() {
    return {
      i: 1,
      next() {
        if (this.i <= 10) {
          return new Promise((resolve) => {
            setTimeout(() => resolve({
              value: this.i++,
              done: false
            }), 1000);
          });
        }

        return Promise.resolve({
          done: true
        });
      }
    };
  }
};

(async () => {
  for await (let p of asyncIterable) {
    const val = await p;
    console.log(val);
  }
})();

The code above will return a generator which returns a new promise as the for-await-of loop runs.

In the Symbol.asyncIterator method, we have a next function, which is required if we want to make an asynchronous iterable object. Also, we have the i property to keep track of what we iterated through. ‘

In the promise that we return, we have the setTimeout with a callback function that resolves to an object with the value we want to resolve, and the property done . The done property is false if the iterator should keep on returning new values for the for-await-of loop and true otherwise.

Async Generator

We can make the code shorter with the yield keyword to make a generator function to return a generator. Generators are another kind of iterable object which can be used with the for-await-of loop.

For example, we can write:

async function* asyncGenerator() {
  for (let i = 1; i <= 10; i++) {
    yield i;
  }
}

(async () => {
  for await (let p of asyncGenerator()) {
    const val = await p;
    console.log(val);
  }
})();

The keyword function* denotes that our function only returns a generator. async means that the generator runs asynchronously. Inside the asyncGenerator function, we have the yield keyword. The yield keyword gets us the next item from the generator as we loop through the generator with the for-await-of loop.

The yield keyword only works with top-level code. It doesn’t work inside callback functions.

If we run the code above, we should get 1 to 10 outputted.

If we write something like:

async function* asyncGenerator() {
  for (let i = 1; i <= 10; i++) {
    setTimeout(() => {
      yield i;
    }, 1000);
  }
}

We’ll get a SyntaxError .

An object can be made iterable with if we define a Symbol.asyncIterator method which is set to a generator function.

For example, we can write:

(async () => {
  const iterableObj = {
    [Symbol.asyncIterator]: async function*() {
      for (let i = 1; i <= 10; i++) {
        yield i;
      }
    }
  }

  for await (let val of iterableObj) {
    console.log(val);
  }
})();

Then we get 1 to 10 logged in the console.log output.

It works like the asyncIterable object we defined above, except that the code is much shorter. Also, we can’t use the yield keyword on callbacks so we’ve to write it like what we have above in that case.

Finally, we can write the following to loop through promises directly:

(async () => {

  for await (let i of Array.from({
    length: 10
  }, ((v, i) => i))) {
    const val = await new Promise((resolve) => {
      setTimeout(() => resolve(i), 1000)
    })
    console.log(val);
  }
})();

The await keyword actually waits for each promise to resolve until running the next one, so we get each number showing up after 1 second when we run the code.

Asynchronous iteration solves the promise of running many promises sequentially. This feature is available in ES2018 or later with the introduction of the for-await-of loop and asynchronous iterators and generators. With them, we solved the issue of iterating through asynchronous code.