DEV Community

loading...
Cover image for CurateBot Devlog 9: Drawing an day-of-week/hour scheduler using v-for loops in Vue

CurateBot Devlog 9: Drawing an day-of-week/hour scheduler using v-for loops in Vue

meseta profile image Yuan Gao ・3 min read

The final pieces of functionality before completing the frontend, is to let the user select what kind of schedule to use for sending tweets. My biggest gripe with using TweetDeck or Twitter's own scheduling, is how many clicks it takes to schedule a tweet. For the kind of tweeting I want this bot to do, I just need it to automatically tweet a few times a day, spread out over the day. In fact, I don't really care exactly when, only that it's roughly at the hours that I want.

So I've decided to give users a 24x7 grid of hours to pick from, if the user checks a grid square, then a tweet will go out at some time in that hour slot.

The code for this is here and the majority of the changes are in the new Schedule.vue file

The Schedule Template

Here, I make extensive use of v-for to render the 24x7 grid of checkboxes in a table:

<template>
  <v-container>
    <Section>
      <template v-slot:title>Schedule</template>
      <p>
      When to post? CurateBot will post sometime within each hour. Times are UTC. Current hour is marked with "now"
      </p>
      <v-simple-table dense>
        <thead>
          <tr>
            <th class="text-center">Time</th>
            <th
              v-for="(dayName, dayIdx) in ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']"
              :key="'day'+dayName"
              class="text-center px-0"
            >
              <v-chip v-if="(dayIdx-1) == currentDay" small color="primary">{{ dayName}}</v-chip>
              <span v-else>{{ dayName }}</span>
            </th>
          </tr>
        </thead>
        <tbody>
          <tr
            v-for="hourIdx in 24"
            :key="'row'+hourIdx"
          >
            <td class="text-center">
              <v-chip v-if="(hourIdx-1) == currentHour" small color="primary">{{ hourIdx-1 }}:00 now</v-chip>
              <span v-else>{{ hourIdx-1 }}:00</span>
            </td>
            <td
              v-for="dayIdx in 7"
              :key="(dayIdx-1)*24+hourIdx-1"
              class="text-center"
            >
              <v-simple-checkbox
                :disabled="loading"
                v-model="schedule[(dayIdx-1)*24+hourIdx-1]"
                color="primary"
                :ripple="false"
              ></v-simple-checkbox>
            </td>
          </tr>
        </tbody>
      </v-simple-table>

      <template v-slot:actions>
        <v-btn
          :disabled="loading"
          color="warning"
          @click="clearAction"
        >
          Clear Schedule
        </v-btn>
        <v-spacer></v-spacer>
        <v-btn
          :disabled="loading"
          color="success"
          @click="saveAction"
        >
          Save Schedule
        </v-btn>
      </template>
    </Section>
  </v-container>
</template>

Enter fullscreen mode Exit fullscreen mode

The data for this is actually a 24*7 1D array (schedule: Array<boolean> = new Array(24*7).fill(false);), which has been rendered out in a 2D grid using the two v-for loops. Each checkbox's v-model points to a different member of this array.

Saving and loading schedule

Because later on we need to be able to figure out which user has a particular slot in their schedule, we convert the 168 (24*7) array of bools, into just an array of enabled slots (an array of the indices). We do the reverse transform when loading.

  saveAction() {
    if (!this.loading) {
      this.loading = true;
      const enabled = [];
      for (const idx in this.schedule) {
        if (this.schedule[idx]) {
          enabled.push(idx);
        }
      }
      return firestore.collection('users').doc(this.uid).update({
        scheduleEnabled: enabled
      })
      .then(() => {
        this.showSuccess("Schedule updated");
        this.loading = false;
      })
      .catch(err => {
        console.error(err);
        this.showError("Something went wrong, could not update schedule");
      })
    }
  }

  mounted() {
    this.loading = true;
    return firestore.collection('users').doc(this.uid).get()
    .then(doc => {
      const scheduleEnabled = doc.get("scheduleEnabled")
      if (scheduleEnabled) {
        this.schedule = new Array(24*7).fill(false);
        for (const idx of scheduleEnabled) {
          this.schedule[idx] = true;
        }
      }

      return firestore.collection('system').doc('stats').get()
    })
    .then(doc => {
      this.lastRun = doc.get('lastRun');
    })
    .catch(err => {
      console.error(err);
      this.showError("Something went wrong, could not load tweets");
    })
    .finally(() => {
      this.loading = false;
    })
  }
Enter fullscreen mode Exit fullscreen mode

Displaying the current time

Since I'm not bothered at this stage to add in timezone support, this grid will run on UTC time, and to help a user select the right times, I will highlight the current time. This is simply done with a component with a conditional when the hour index matches the current hour returned by JavaScript's new Date().getUTCHours();

<v-chip v-if="hourIdx == current" small color="primary">{{ hourIdx-1 }}:00 now</v-chip>
Enter fullscreen mode Exit fullscreen mode

The result:
Full screenshot of Schedule

At this point, we are now done with the frontend (other than some style changes needed - we're still using all of Vuetify's default blue theme, something snazzier would be nice).

I'll be moving onto the backend next

Discussion (0)

pic
Editor guide