2018-10-31に更新

サイボウズLiveを作る-第6回-イベント作成

あと一つ大きなメイン機能であるイベント機能が残っていたのでそちらを作成した。

色々見てみた結果、とりあえず全部FullCalendarに置き換えればいいだろうと言う結論に至った。

FullCalendar - JavaScript Event Calendar

期間や範囲切り替えもあるし、これだけで一通りまかなえる気がする。

イベント予定メニューマスタ

本家にはない(?)が、予定メニューにも色を付けられるようにした。

<template>
  <div class="row">
    <div class="col-12">
      <div class="sample">
        <span class="event-color-sample" v-bind:style="{backgroundColor: currentBgColor.hex, color: currentTextColor.hex}">サンプル</span>
      </div>
    </div>
    <div class="col-12 col-sm-4">
      <div>背景色</div>
      <swatches-picker v-model="currentBgColor"></swatches-picker>
    </div>
    <div class="col-12 col-sm-4">
      <div>文字色</div>
      <swatches-picker v-model="currentTextColor"></swatches-picker>
    </div>
    <input type="hidden" :name="bg_color_name" :value="currentBgColor.hex">
    <input type="hidden" :name="text_color_name" :value="currentTextColor.hex">
  </div>
</template>

<style scoped>
div.row {
  margin-bottom: 20px;
}
</style>

<script>
import { Swatches } from 'vue-color'

export default {
  props: ['bg_color_name', 'text_color_name', 'bg_color', 'text_color'],

  components: {
    'swatches-picker': Swatches,
  },

  data () {
    return {
      currentBgColor: {hex: this.bg_color === undefined ? '#3F51B5' : this.bg_color},
      currentTextColor: {hex: this.text_color === undefined ? '#FFFFFF' : this.text_color},
    }
  },

  methods: {
  }
}
</script>

一覧もvuedraggableを使ってドラッグ&ドロップで簡単に並び替えできるようにした。

面倒かと思ったが、元々のテンプレートをとりあえずコピーから始められるのでそれほどでもなかった。

<template>
  <table class="table">
    <thead>
      <tr>
        <th></th>
        <th>予定メニュー名</th>

        <th></th>
      </tr>
    </thead>
    <draggable v-model="scheduleCategories" :element="'tbody'" :options="{handle: '.handle'}" @end="onEnd">
      <tr v-for="scheduleCategory in scheduleCategories" :key="scheduleCategory.id">
        <td class="handle"><i class="material-icons">drag_handle</i></td>
        <td>
          <span
            v-text="scheduleCategory.name"
            class="event-color-sample"
            v-bind:style="{backgroundColor: scheduleCategory.bg_color, color: scheduleCategory.text_color}"
          ></span>
        </td>

        <td class="text-right">
          <span><a :href="`/${group_id}/schedule-categories/${scheduleCategory.id}/edit`" class="btn btn-default btn-xs">編集</a></span>
          <span>
            <a
              href="#"
              data-confirm="削除してよろしいですか?"
              :data-csrf="csrf"
              data-method="delete"
              :data-to="`/${group_id}/schedule-categories/${scheduleCategory.id}`"
              rel="nofollow"
              class="btn btn-danger btn-xs"
            >削除<div class="ripple-container"></div></a>
          </span>
        </td>
      </tr>
    </draggable>
  </table>
</template>

<style scoped>
.handle {
  cursor: crosshair;
}
</style>

<script>
import draggable from 'vuedraggable'
import axios from 'axios'

export default {
  props: ['group_id', 'schedule_categories'],

  components: {draggable},

  data () {
    return {
      scheduleCategories: this.schedule_categories,
      csrf: document.querySelector('meta[name=csrf]').getAttribute('content'),
    }
  },

  methods: {
    onEnd() {
      axios.put(`/${this.group_id}/schedule-categories/update-order`, {
          ids: this.scheduleCategories.map(c => c.id),
        });
    }
  }
}
</script>

保存側。

  def update_schedule_categories_order(group_id, ids) do
    Enum.with_index(ids)
    |> Enum.each(fn{id, index} ->
      schedule_category = get_schedule_category!(id, group_id)
      update_schedule_category(schedule_category, %{"display_order" => index + 1})
    end)
  end

カレンダー

こちらも面倒かと思ったが、データの取得はコールバックに作ればいいだけだったので非常に楽だった。

<template>
  <div id="calendar">
  </div>
</template>

<style scoped>
</style>

<script>
import axios from 'axios'
import moment from 'moment'

export default {
  props: ['group_id', 'month'],

  data () {
    return {
      currentEvents: [],
    }
  },

  mounted() {
    if (this.month !== undefined) {

    }
    $('#calendar').fullCalendar({
      locale: 'ja',
      header: {
        right:  'month,agendaWeek,agendaDay today prev,next'
      },
      views: {
        month: {
          titleFormat: 'YYYY年 MMMM',
        },
      },
      buttonText: {
        today: '今日',
        month: '月',
        week: '週',
        day: '日',
      },
      firstDay: 1,
      timeFormat: 'HH:mm',
      defaultDate: this.getDefaultDate(),
      events: this.loadEvents.bind(this),
      dayClick: this.dayClick.bind(this),
      eventClick: this.eventClick.bind(this),
    });
  },

  methods: {
    dayClick(date, jsEvent, view) {
      const dateText = date.format('YYYY-MM-DD');
      if (view.name == 'month') {
        location.href = `/${this.group_id}/schedules/new?date=${dateText}`; 
      } else {
        const timeText = date.format('HH:mm:ss');
        location.href = `/${this.group_id}/schedules/new?date=${dateText}&time=${timeText}`; 
      }
    },

    eventClick(calEvent, jsEvent, view) {
      location.href = `/${this.group_id}/schedule-posts/${calEvent.schedule.id}`;
    },

    loadEvents(start, end, timezone, callback) {
      const startDate = start.format('YYYY-MM-DD');
      const endDate = end.format('YYYY-MM-DD');
      axios.get(`/${this.group_id}/schedules/events/${startDate}/${endDate}`)
        .then(response => {
          this.$emit('GET_AJAX_COMPLETE');
          callback(response.data);
        });
    },

    getDefaultDate() {
      if (this.month !== undefined) {
        return moment(this.month + '-01');
      } else {
        return moment();
      }
    }
  }
}
</script>

取得側。
こういう重そうな範囲検索は、大規模サービスだとどう実装してるのか気になる。
日毎にデータを分けてるのかな。(日を子データにしてるとか)大変そう。

  def list_schedules_for_range(group_id, start_date, end_date) do
    query = from s in Schedule,
      where: s.group_id == ^group_id
        and (
          (s.start_date < ^start_date and ^end_date < s.end_date)
          or (^start_date <= s.start_date and s.start_date <= ^end_date)
          or (^start_date <= s.end_date and s.end_date <= ^end_date)
        )
        and is_nil(s.deleted_at)
    Repo.all(query)
    |> Repo.preload(:schedule_category)
  end

不足点

リピートの予定とか放置してる。

あと、設備とかどこで使ってるんだろうと思ったら、アクセスの方法によって登録できたりするっぽい。
完全に抜けてる。

わかりづらいなここは。急にグループ関係なしの画面になるし意味が分からない。

テスト放置なので整備しようかと思う。
そっちの方が面白くて書くこと多かったりするかもしれない。
ソース書いてるのVueばっかりだし。

Copying live


だら@Crieit開発者

Crieitの開発者です。 主にLAMPで開発しているWebエンジニアです(在宅)。大体10年程。 記事でわかりにくいところがあればDMで質問していただくか、案件発注してください。 業務依頼、同業種の方からのコンタクトなどお気軽にご連絡ください。 業務経験有:PHP, MySQL, Laravel5, CakePHP3, JavaScript, RoR 趣味:Elixir, Phoenix, Node, Nuxt, Express, Vue等色々

Crieitはαバージョンで開発中です。進捗は公式Twitterアカウントをフォローして確認してください。 興味がある方は是非記事の投稿もお願いします! どんな軽い内容でも嬉しいです。
なぜCrieitを作ろうと思ったか
関連記事

コメント