2018-10-23に更新

サイボウズLiveを作る-第4回-Todoをざっと

掲示板をざっと作成後、次は次に簡単そうなToDoを作成することにした。
とりあえずざっと下記を作成した。

ToDoの新規登録、編集、コメント追加

特に目新しいこともなく、コメントなどはほとんど掲示板と同じ。
黙々とシンプルに作成したので、特筆することはなかった。

担当者選択UI

本家だとselectのマルチセレクトで複数の担当者を選択できるように実装されている。
昔は良く使われていた気がする。ちゃちゃっとjavascriptで作成できる。

ただ、今の時代はそういったUIはnpmでインストールするだけ。
丁度良さそうなものを見つけたので導入した。

GitHub - SortableJS/Vue.Draggable: Vue component allowing drag-and-drop sorting in sync with View-Model. Based on Sortable.js

READMEを見ると分かるように、ドラッグで並び替えも、左右のボックスで入れ替えもできる。
Vueのコンポーネントを作成してhiddenタグを自動的に更新するだけで実装できる。

コンポーネントの実装も非常にシンプル。

<template>
  <div class="row">
    <div class="col-6 col-sm-3">
      <div class="card">
        <draggable v-model="selectedUsers" :element="'ul'" :options="{group:'users'}" @start="drag=true" @end="drag=true" class="list-group list-group-flush">
          <li class="list-group-item" v-for="(user, index) in selectedUsers" :key="user.id">
            <img :src="user.avatar">
            {{user.name}}
            <input type="hidden" :name="`todo_task[todo_tasks_users][${index}][user_id]`" :value="user.id">
            <input type="hidden" :name="`todo_task[todo_tasks_users][${index}][display_order]`" :value="index + 1">
          </li>
        </draggable>
      </div>
    </div>
    <div class="col-6 col-sm-3">
      <div class="card text-secondary">
        <draggable v-model="allUsers" :element="'ul'" :options="{group:'users'}" @start="drag=true" @end="drag=true" class="list-group list-group-flush">
          <li class="list-group-item" v-for="user in allUsers" :key="user.id">
            <img :src="user.avatar">
            {{user.name}}
          </li>
        </draggable>
      </div>
    </div>
    <input v-if="selectedUsers.length == 0" type="hidden" name="todo_task[todo_tasks_users]">
  </div>
</template>

<style scoped>
li {
  cursor: pointer;
}

img {
  width: 24px;
}
</style>

<script>
import draggable from 'vuedraggable'

export default {
  components: {draggable},
  props: ['name', 'value', 'users', 'selected'],

  data () {
    const users = JSON.parse(this.users);
    const selected = JSON.parse(this.selected);
    return {
      allUsers: users.filter(user => selected.indexOf(user.id) === -1),
      selectedUsers: users.filter(user => selected.indexOf(user.id) !== -1),
    }
  },

  methods: {
  }
}
</script>

呼び出しも簡単。

    <todo-user-select users="<%= Poison.encode!(@users) %>" selected="[<%= if Map.get(@conn.assigns, :todo_task) do
      Enum.join(Cybozulive.Todo.TodoTask.user_ids(@todo_task), ",")
    end %>]"></todo-user-select>

締め切り日時選択

これもよくあるのはinputタグをクリックすると日付選択UIが現れるもの。
ただ、inputタグを使って直接入力させる必要性も感じなかったので完全にDatepickerとTimepickerで選択させるようにした。

Datepicker。左下のリンクをクリックで表示される。ゴミ箱クリックでnullとなる。

Timepicker。

実装も特筆することはなくシンプル。Timepickerもほとんど同じ。

<template>
  <span>
    <a ref="toggle" href="#" @click.prevent="toggle()">
      {{showDate()}}
    </a>
    <a href="#" @click.prevent="setNull()"><i class="material-icons">delete_forever</i></a>
    <input type="hidden" :name="name" :value="showValue()">
  </span>
</template>

<style scoped>
.material-icons {
  font-size: 20px;
}
</style>

<script>
import mdDateTimePicker from 'md-date-time-picker'
import moment from 'moment'

const dialog = new mdDateTimePicker({
  type: 'date'
})

export default {
  props: ['name', 'value'],

  data () {
    const currentValue = this.value === '' ? null : moment(this.value);
    if (currentValue !== null) {
      dialog.time = currentValue;
    }
    return {
      currentValue,
    }
  },

  mounted() {
    dialog.trigger = this.$refs.toggle;
    this.$refs.toggle.addEventListener('onOk', () => {
      this.currentValue = dialog.time;
    })
  },

  methods: {
    toggle() {
      dialog.toggle();
    },

    showDate() {
      if (this.currentValue === null) {
        return '(未設定)';
      }
      return this.currentValue.format('YYYY-MM-DD');
    },

    showValue() {
      if (this.currentValue === null) {
        return '';
      }
      return this.currentValue.format('YYYY-MM-DD');
    },

    setNull() {
      this.currentValue = null;
    }
  }
}
</script>

マークダウン

<datepicker name="todo_task[limited_date]" value="<%= Ecto.Changeset.get_field(@changeset, :limited_date) %>"></datepicker>
<timepicker name="todo_task[limited_time]" value="<%= Cybozulive.Todo.TodoTask.limited_time(@changeset.data) %>"></timepicker>

次はスケジュールを作成、としたいところだが、グループウェアなのにユーザーが自分しかいないのが意味不明なので、
とりあえず招待とかを作ろうかと思う。
本当はメールアドレスのような個人情報は登録したくはないのだが…非ユーザーを招待するならそれしかなさそう。
(なんかあるのかな)

Copying live

ツイッターでシェア
みんなに共有、忘れないようにメモ

view_list [連載] サイボウズLiveを作る
第2回 サイボウズLiveを作る-第2回-グループ登録まで
第3回 サイボウズLiveを作る-第3回-トピック登録まで
第4回 サイボウズLiveを作る-第4回-Todoをざっと
第5回 サイボウズLiveを作る-第5回-グループへ参加
第6回 サイボウズLiveを作る-第6回-イベント作成

だら@Crieit開発者

Crieitの開発者です。 Webエンジニアです(在宅)。大体10年ちょい。 記事でわかりにくいところがあればDMで質問していただくか、案件発注してください。 業務依頼、同業種の方からのコンタクトなどお気軽にご連絡ください。 業務経験有:PHP, MySQL, Laravel, React, Flutter, Vue.js, Node, RoR 趣味:Elixir, Phoenix, Nuxt, Express, GCP, AWS等色々 PHPフレームワークちいたんの作者

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント