一人もくもく会 α verでサービス開始しました。
請求書作成システム α verでサービス開始しました。

サイボウズ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タグを自動的に更新するだけで実装できる。

f:id:dala:20171127220507p:plain

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

<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となる。

f:id:dala:20171127221425p:plain

Timepicker。

f:id:dala:20171127221628p:plain

実装も特筆することはなくシンプル。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