CurateBot Devlog 9: Drawing an day-of-week/hour scheduler using v-for loops in Vue 
Tags: devjournal, typescript, vue, javascript
Published: 2021-01-08 10:21:00

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](https://github.com/meseta/curatebot/tree/9ca7d74c7d2a403b60b852cf61da64c1f30cac09) and the majority of the changes are in the new [`Schedule.vue` file](https://github.com/meseta/curatebot/blob/9ca7d74c7d2a403b60b852cf61da64c1f30cac09/web/src/views/Schedule.vue)

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

```vue
<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>

```

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.

```ts
  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;
    })
  }
```

# 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 <v-chip> component with a conditional when the hour index matches the current hour returned by JavaScript's `new Date().getUTCHours();`

```vue
<v-chip v-if="hourIdx == current" small color="primary">{{ hourIdx-1 }}:00 now</v-chip>
```

The result:
![Full screenshot of Schedule](https://dev-to-uploads.s3.amazonaws.com/i/hryk2jstijm6ad3mj5yh.png)

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