もがき系プログラマの日常

もがき系エンジニアの勉強したこと、日常のこと、気になっている技術、備忘録などを紹介するブログです。

久々にnuxt入門した #08

はじめに

こんばんは。

今回も前回の続きです。

kojirooooocks.hatenablog.com kojirooooocks.hatenablog.com kojirooooocks.hatenablog.com kojirooooocks.hatenablog.com kojirooooocks.hatenablog.com kojirooooocks.hatenablog.com kojirooooocks.hatenablog.com

今回はカードのドラッグ & ドロップです。

今回が完結版です!

本題

Vue.Draggableのインストール

このライブラリを入れるだけですごく簡単に実装できました。

https://github.com/SortableJS/Vue.Draggable

$ npm i vuedraggable

CardList.vue

draggableコンポーネントを使用して、ドラッグ可能にし、emitでdataを更新するようにしています。

オプションなどは 基本的に exampleのものでOKでした!

<template>
  <div class="flex-1 bg-gray-100 m-2">
    <p class="text-xl ml-2">{{ type }}</p>
    <draggable
      v-model="computedCardList"
      v-bind="dragOptions"
      @start="drag = true"
      @end="drag = false"
    >
      <Card
        v-for="(card, k) in cardList"
        :key="k"
        :card-key="k"
        :card-title="card.title"
        @showModal="showModal"
      />
    </draggable>
    <modals-container
      @onModifyCard="onModifyCard"
      @onDeleteCard="onDeleteCard"
    />
    <AddCard @onAddCard="onAddCard" />
  </div>
</template>

<script>
import draggable from 'vuedraggable'
import Card from './Card'
import AddCard from './AddCard'
import CardDetailModal from './CardDetailModal'
export default {
  name: 'CardList',
  components: { AddCard, Card, draggable },
  props: {
    type: {
      type: String,
      require: true,
      default: ''
    },
    cardList: {
      type: Array,
      require: true,
      default: null
    }
  },
  computed: {
    computedCardList: {
      get() {
        return this.cardList
      },
      set(value) {
        this.$emit('doMoveCard', value, this.type)
      }
    },
    dragOptions() {
      return {
        animation: 0,
        group: 'description',
        ghostClass: 'ghost'
      }
    }
  },
  methods: {
    showModal(cardKey) {
      this.$modal.show(
        CardDetailModal,
        {
          cardKey,
          cardData: this.cardList[cardKey]
        },
        {
          height: 550
        }
      )
    },
    onAddCard(title) {
      this.$emit('doAddCard', this.type, title)
    },
    onModifyCard(cardKey, cardTitle, cardBody, cardStatus) {
      this.$emit('doModifyCard', cardStatus, cardKey, cardTitle, cardBody)
      this.$modal.hideAll()
    },
    onDeleteCard(cardKey, cardStatus) {
      this.$emit('doDeleteCard', cardStatus, cardKey)
      this.$modal.hideAll()
    }
  }
}
</script>

<style scoped></style>

index.vue

emitでカードの移動を実装しています。

<script>
  methods: {
    ...
    doMoveCard(cardList, type) {
      this.cardList[type] = cardList
    }
  }
}
</script>

<style></style>

Image from Gyazo

終わりに

一番大変だと思っていたドラッグアンドドロップですが、かなり簡単に実装できました。

APIでデータを永続化などするのが残っていますが、一旦ここで trelloは完成とします!

routerとかvuexとかその辺使ってないので、次作るものはそのへん活用できる何かを作ってみようと思います 。

ではまた。

久々にnuxt入門した #07

はじめに

こんばんは。

今回も前回の続きです。

kojirooooocks.hatenablog.com kojirooooocks.hatenablog.com kojirooooocks.hatenablog.com kojirooooocks.hatenablog.com kojirooooocks.hatenablog.com kojirooooocks.hatenablog.com

今回は、修正にバリデーションを入れるのと、カードの削除機能を追加しました。

本題

CardDetailModal.vue

モーダルコンポーネントValidationObserverValidationProvider を追加して、さらに、 削除ボタンを追加しました。

<template>
  <div class="m-8">
    <h2 class="text-3xl">{{ cardData.title }}の修正</h2>
    <div class="mt-5">
      <ValidationObserver ref="obs" v-slot="{ reset, valid }">
        <ValidationProvider v-slot="{ errors }" rules="required|max:10">
          <div>
            <p class="my-1 text-lg">タイトル</p>
            <input
              v-model="title"
              class="w-full p-3 border-2"
              @focus="$event.target.select()"
            />
            <span v-for="(error, k) in errors" :key="k" class="text-red-500">
              {{ error }}
            </span>
          </div>

          <div class="mt-10">
            <p class="my-1 text-lg">本文</p>
            <textarea
              v-model="body"
              class="w-full p-3 border-2"
              rows="6"
              @focus="$event.target.select()"
            ></textarea>
          </div>

          <div class="mt-5 text-right">
            <button
              class="bg-blue-400 text-white font-bold py-2 px-4 rounded"
              :class="{
                'hover:bg-blue-700': valid,
                'bg-blue-500': valid,
                'cursor-not-allowed': !valid
              }"
              :disabled="!valid"
              @click="
                $emit('onModifyCard', cardKey, title, body, cardData.status)
              "
            >
              更新する
            </button>
            <button
              class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
              @click="$emit('onDeleteCard', cardKey, cardData.status)"
            >
              削除する
            </button>
            <button
              class="bg-white hover:bg-gray-100 text-gray-800 font-semibold py-2 px-4 border border-gray-400 rounded shadow"
              @click="$emit('close')"
            >
              閉じる
            </button>
          </div>
        </ValidationProvider>
      </ValidationObserver>
    </div>
  </div>
</template>

<script>
import { ValidationObserver, ValidationProvider } from 'vee-validate'
export default {
  name: 'CardDetailModal',
  components: {
    ValidationObserver,
    ValidationProvider
  },
  props: {
    cardKey: {
      type: Number,
      require: true,
      default: 0
    },
    cardData: {
      type: Object,
      require: true,
      default: () => {}
    }
  },
  data() {
    return {
      body: this.cardData.body,
      title: this.cardData.title
    }
  }
}
</script>

<style scoped></style>

CardList.vue

modalに削除用のイベントを渡しました。

<template>
...
    <modals-container
      @onModifyCard="onModifyCard"
      @onDeleteCard="onDeleteCard"
    />
...
</template>

methods: {
...
    onDeleteCard(cardKey, cardStatus) {
      this.$emit('doDeleteCard', cardStatus, cardKey)
      this.$modal.hideAll()
    }
...
}

index.vue

削除用イベントをわたしています。

また、実際の削除用メソッドを追加しています。

<template>
...
      <CardList
        type="todo"
        :card-list="cardList.todo"
        @doAddCard="doAddCard"
        @doDeleteCard="doDeleteCard"
        @doModifyCard="doModifyCard"
      />
...
</template>


methods: {
...
    doDeleteCard(type, key) {
      this.cardList[type].splice(key, 1)
    }
...
}

Image from Gyazo

終わりに

今回は簡単ですが、これでおわりです。

だんだんトレロに近づいてきました...!

あとはカードの移動ですが、一番たいへんそうなので、最後の課題にしようと思います...

久々にnuxt入門した #06

はじめに

こんばんは。

今回も前回の続きです。

kojirooooocks.hatenablog.com kojirooooocks.hatenablog.com kojirooooocks.hatenablog.com kojirooooocks.hatenablog.com kojirooooocks.hatenablog.com

今回は、vee-validateを導入して入力フォームのバリデーションを行いました。

logaretm.github.io

本題

まず vue-js-modal をインストールします。

$ npm install vee-validate --save

plugins/vee-validate.js

vee-validateの設定ファイル作成します。

import { localize, extend } from 'vee-validate'
import { required, max } from 'vee-validate/dist/rules'
import ja from 'vee-validate/dist/locale/ja.json'
localize('ja', ja)

extend('required', {
  ...required,
  message: '未入力になっています。'
})

extend('max', {
  ...max,
  message: '{length}文字以内で入力してください。'
})

nuxt-config.js

pluginを読み込みます

...
  plugins: [
    { src: '~/plugins/vue-js-modal.js' },
    { src: '~/plugins/vee-validate.js' }
  ],
...

また、buildの transpile で指定する必要があります。

...
build: {
   transpile: ['vee-validate/dist/rules'],
}
...

AddCard.vue

カードの追加時のバリデーションを実装しました。

<template>
  <div class="px-2 mb-2">
    <p
      v-show="!isPushedAddCardButton"
      class="cursor-pointer"
      @click="isPushedAddCardButton = true"
    >
      + カードを追加
    </p>
    <div v-show="isPushedAddCardButton" class="mt-6">
      <ValidationObserver ref="obs" v-slot="{ reset, valid }">
        <ValidationProvider v-slot="{ errors }" rules="required|max:10">
          <input v-model="addCardTitle" class="w-full p-3 border-2" />
          <ul>
            <li v-for="(error, k) in errors" :key="k" class="text-red-500">
              {{ error }}
            </li>
          </ul>

          <div class="mt-2">
            <button
              class="bg-blue-400 text-white font-bold py-2 px-4 rounded"
              :class="{
                'hover:bg-blue-700 ': valid,
                'bg-blue-500 ': valid,
                'cursor-not-allowed': !valid
              }"
              :disabled="!valid"
              @click="onClickAddButton(reset)"
            >
              追加
            </button>
            <button
              class="bg-white hover:bg-gray-100 text-gray-800 font-semibold py-2 px-4 border border-gray-400 rounded shadow"
              @click="onClickCloseButton(reset)"
            >
              閉じる
            </button>
          </div>
        </ValidationProvider>
      </ValidationObserver>
    </div>
  </div>
</template>

<script>
import { ValidationObserver, ValidationProvider } from 'vee-validate'
export default {
  name: 'AddCard',
  components: {
    ValidationObserver,
    ValidationProvider
  },
  data() {
    return {
      addCardTitle: '',
      isPushedAddCardButton: false
    }
  },
  methods: {
    onClickAddButton(validateReset) {
      validateReset()
      this.$emit('onAddCard', this.addCardTitle)
      this.addCardTitle = ''
      this.isPushedAddCardButton = false
    },
    onClickCloseButton(validateReset) {
      validateReset()
      this.addCardTitle = ''
      this.isPushedAddCardButton = false
    }
  }
}
</script>

<style scoped></style>

Image from Gyazo

終わりに

今回は比較的スムーズに行きました。

ただ、引数にイベント渡したりちょっと変な感じで使ってるかも...

まあ、こちらもできたから..よし!

次は、モーダルで開いた際のフォームのばりーでションと、カードを削除する機能を追加します!

ではまた。

久々にnuxt入門した #05

はじめに

こんばんは。

今回も前回の続きです。

kojirooooocks.hatenablog.com

kojirooooocks.hatenablog.com

kojirooooocks.hatenablog.com

kojirooooocks.hatenablog.com

今回は、ついにモーダル表示やりました。

本題

まず vue-js-modal をインストールします。

www.npmjs.com

plugins/vue-js-modal.js

vue-js-modalの設定ファイル作成します。

import Vue from 'vue'
import VModal from 'vue-js-modal'

Vue.use(VModal, {
  dynamic: true,
  dynamicDefaults: { clickToClose: true },
  injectModalsContainer: true
})

nuxt-config.js

pluginを読み込みます

...
plugins: [{ src: '~/plugins/vue-js-modal.js' }],
...

index.vue

新たに編集時のイベントを渡しています。

<template>
  <div class="bg-blue-100">
    <h1 class="text-2xl">Todo List</h1>

    <div class="flex">
      <CardList
        type="todo"
        :card-list="cardList.todo"
        @doAddCard="doAddCard"
        @doModifyCard="doModifyCard"
      />
      <CardList
        type="doing"
        :card-list="cardList.doing"
        @doAddCard="doAddCard"
        @doModifyCard="doModifyCard"
      />
      <CardList
        type="done"
        :card-list="cardList.done"
        @doAddCard="doAddCard"
        @doModifyCard="doModifyCard"
      />
    </div>
  </div>
</template>

<script>
import CardList from '../components/CardList'
export default {
  components: { CardList },
  data() {
    return {
      cardList: {
        todo: [
          { title: 'aaa', body: 'AAA', status: 'todo' },
          { title: 'bbb', body: 'BBB', status: 'todo' },
          { title: 'ccc', body: 'CCC', status: 'todo' },
          { title: 'ddd', body: 'DDD', status: 'todo' },
          { title: 'eee', body: 'EEE', status: 'todo' },
          { title: 'fff', body: 'FFF', status: 'todo' }
        ],
        doing: [{ title: '○○○○○', body: '◎◎◎◎◎◎◎', status: 'doing' }],
        done: [
          { title: '000', body: '0000000000', status: 'done' },
          { title: '111', body: '1111111111', status: 'done' },
          { title: '222', body: '2222222222', status: 'done' }
        ]
      }
    }
  },
  methods: {
    doAddCard(type, title) {
      this.cardList[type].push({
        title,
        body: ''
      })
    },
    doModifyCard(type, key, title, body) {
      this.cardList[type][key].title = title
      this.cardList[type][key].body = body
    }
  }
}
</script>

<style></style>

CardList.vue

実際にmodalの表示を行っています。

<template>
  <div class="flex-1 bg-gray-100 m-2 cursor-pointer">
    <p class="text-xl ml-2">{{ type }}</p>
    <Card
      v-for="(card, k) in cardList"
      :key="k"
      :card-key="k"
      :card-title="card.title"
      @showModal="showModal"
    />
    <modals-container @onModifyCard="onModifyCard" />
    <AddCard @onAddCard="onAddCard" />
  </div>
</template>

<script>
import Card from './Card'
import AddCard from './AddCard'
import CardDetailModal from './CardDetailModal'
export default {
  name: 'CardList',
  components: { AddCard, Card },
  props: {
    type: {
      type: String,
      require: true,
      default: ''
    },
    cardList: {
      type: Array,
      require: true,
      default: null
    }
  },
  methods: {
    showModal(cardKey) {
      this.$modal.show(
        CardDetailModal,
        {
          cardKey,
          cardData: this.cardList[cardKey]
        },
        {
          height: 500
        }
      )
    },
    onAddCard(title) {
      this.$emit('doAddCard', this.type, title)
    },
    onModifyCard(cardKey, cardTitle, cardBody, cardStatus) {
      this.$emit('doModifyCard', cardStatus, cardKey, cardTitle, cardBody)
      this.$modal.hideAll()
    }
  }
}
</script>

<style scoped></style>

Card.vue

カード一つ一つにイベントを設定します。

<template>
  <div class="z-10 flex-auto bg-white m-2 shadow">
    <p class="w-full p-3" @click="$emit('showModal', cardKey)">
      {{ cardTitle }}
    </p>
  </div>
</template>

<script>
export default {
  props: {
    cardKey: {
      type: Number,
      require: true,
      default: 0
    },
    cardTitle: {
      type: String,
      require: true,
      default: ''
    }
  }
}
</script>

<style scoped></style>

CardDetailModal.vue

modalの中身です。

<template>
  <div class="m-8">
    <h2 class="text-3xl">{{ cardData.title }}の修正</h2>
    <div class="mt-5">
      <div>
        <p class="my-1 text-lg">タイトル</p>
        <input
          v-model="title"
          class="w-full p-3 border-2"
          @focus="$event.target.select()"
        />
      </div>

      <div class=" mt-5">
        <p class="my-1 text-lg">本文</p>
        <textarea
          v-model="body"
          class="w-full p-3 border-2"
          rows="6"
          @focus="$event.target.select()"
        ></textarea>
      </div>

      <div class="mt-5 text-right">
        <button
          class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
          @click="$emit('onModifyCard', cardKey, title, body, cardData.status)"
        >
          追加
        </button>
        <button
          class="bg-white hover:bg-gray-100 text-gray-800 font-semibold py-2 px-4 border border-gray-400 rounded shadow"
          @click="$emit('close')"
        >
          閉じる
        </button>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'CardDetailModal',
  props: {
    cardKey: {
      type: Number,
      require: true,
      default: 0
    },
    cardData: {
      type: Object,
      require: true,
      default: () => {}
    }
  },
  data() {
    return {
      body: this.cardData.body,
      title: this.cardData.title
    }
  }
}
</script>

<style scoped></style>

Image from Gyazo

終わりに

最初の方読み込みが悪いのか、emitを設定できなかったり、しかし再読みこすると設定できたりと思わぬトラブルがありましたが、なんとかなりました。

emitの使い方(というよりコンポーネントの分け方?)が多分下手で、無駄にいっぱい引数取り回しててちょっとダサいです。。。

まぁ、できたので良し!

次はバリデーションをやってみます。

ではまた。

久々にnuxt入門した #04

はじめに

こんばんは。

今回も前回の続きです。

kojirooooocks.hatenablog.com

kojirooooocks.hatenablog.com

kojirooooocks.hatenablog.com

今回は、モーダル表示の前に、カードの追加を実装しました。

本題

index.vue

追加する際のイベントを渡してます。

<template>
  <div class="bg-blue-100">
    <h1 class="text-2xl">Todo List</h1>

    <div class="flex">
      <CardList
        title="todo"
        :card-list="cardList.todo"
        @doAddCard="doAddCard"
      />
      <CardList
        title="doing"
        :card-list="cardList.doing"
        @doAddCard="doAddCard"
      />
      <CardList
        title="done"
        :card-list="cardList.done"
        @doAddCard="doAddCard"
      />
    </div>
  </div>
</template>

<script>
import CardList from '../components/CardList'
export default {
  components: { CardList },
  data() {
    return {
      cardList: {
        todo: [
          { title: 'aaa', body: 'AAA' },
          { title: 'bbb', body: 'BBB' },
          { title: 'ccc', body: 'CCC' },
          { title: 'ddd', body: 'DDD' },
          { title: 'eee', body: 'EEE' },
          { title: 'fff', body: 'FFF' }
        ],
        doing: [{ title: '○○○○○', body: '◎◎◎◎◎◎◎' }],
        done: [
          { title: '000', body: '0000000000' },
          { title: '111', body: '1111111111' },
          { title: '222', body: '2222222222' }
        ]
      }
    }
  },
  methods: {
    doAddCard(type, title) {
      this.cardList[type].push({
        title,
        body: ''
      })
    }
  }
}
</script>

<style></style>

CardList.vue

AddCardコンポーネントを追加しました。

<template>
  <div class="flex-1 bg-gray-100 m-2 cursor-pointer">
    <p class="text-xl">{{ title }}</p>
    <Card
      v-for="(card, k) in cardList"
      :key="k"
      :card-title.sync="card.title"
    />
    <AddCard @onAddCard="onAddCard" />
  </div>
</template>

<script>
import Card from './Card'
import AddCard from './AddCard'
export default {
  name: 'CardList',
  components: { AddCard, Card },
  props: {
    title: {
      type: String,
      require: true,
      default: ''
    },
    cardList: {
      type: Array,
      require: true,
      default: null
    }
  },
  methods: {
    onAddCard(title) {
      const type = this.title
      this.$emit('doAddCard', type, title)
    }
  }
}
</script>

<style scoped></style>

AddCard.vue

トレロっぽくカードの最後に追加ボタンを用意して、ボタンを押すと入力フォーム & 追加、閉じるボタンを表示されるようにしました。

<template>
  <div class="px-2">
    <p @click="isPushedAddCardButton = true">+ カードを追加</p>
    <div v-show="isPushedAddCardButton">
      <input v-model="addCardTitle" class="w-full p-3" />

      <div class="mt-2">
        <button
          class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
          @click="onClickAddButton"
        >
          追加
        </button>
        <button
          class="bg-white hover:bg-gray-100 text-gray-800 font-semibold py-2 px-4 border border-gray-400 rounded shadow"
          @click="onClickCloseButton"
        >
          閉じる
        </button>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'AddCard',
  data() {
    return {
      addCardTitle: '',
      isPushedAddCardButton: false
    }
  },
  methods: {
    onClickAddButton() {
      this.$emit('onAddCard', this.addCardTitle)
      this.addCardTitle = ''
      this.isPushedAddCardButton = false
    },
    onClickCloseButton() {
      this.addCardTitle = ''
      this.isPushedAddCardButton = false
    }
  }
}
</script>

<style scoped></style>

gyazo.com

終わりに

だいたいトレロっぽくなってきました!

次こそは本当に編集モーダル作ります!

久々にnuxt入門した #03

はじめに

こんにちは。

前回の続きです。

kojirooooocks.hatenablog.com

kojirooooocks.hatenablog.com

次は emitあたりを振り返ります。

本題

CardList.vue

<template>
  <div class="flex-1 bg-gray-100 m-2 cursor-pointer">
    <p class="text-xl">{{ title }}</p>
    <Card
      v-for="(card, k) in cardList"
      :key="k"
      :card-title.sync="card.title"
    />
  </div>
</template>

<script>
import Card from './Card'
export default {
  name: 'CardList',
  components: { Card },
  props: {
    title: {
      type: String,
      require: true,
      default: ''
    },
    cardList: {
      type: Array,
      require: true,
      default: null
    }
  }
}
</script>

<style scoped></style>

Card.vue

<template>
  <div class="z-10 flex-auto bg-white m-2 shadow">
    <input v-model="computedCardTitle" class="w-full p-3" />
  </div>
</template>

<script>
export default {
  name: 'Card',
  props: {
    cardTitle: {
      type: String,
      require: true,
      default: ''
    }
  },
  computed: {
    computedCardTitle: {
      get() {
        return this.cardTitle
      },
      set(value) {
        this.$emit('update:cardTitle', value)
      }
    }
  }
}
</script>

<style scoped></style>

コンポーネントまである今回の構成では、 .sync を2回使って連鎖する感じではなく、こちらの記事を参考に、computedを使ってemitを行いました。

gyazo.com

終わりに

大体出来上がってきました。

次はvee-validateでフォームのバリデーションとかをやってみます。

あんまnuxt関係ないよね...w

久々にnuxt入門した #02

はじめに

こんにちは。

前回の記事の続きです。

kojirooooocks.hatenablog.com

次はテンプレートのコンポーネント分けをします。

本題

index.vue

apiでデータを取ってきた的なのりで、dataをつかってみます。 そして、そのデータからカード表示します。

<template>
  <div class="bg-blue-100">
    <h1 class="text-2xl">Todo List</h1>

    <div class="flex">
      <CardList title="todo" :card-list="cardList.todo" />
      <CardList title="doing" :card-list="cardList.doing" />
      <CardList title="done" :card-list="cardList.done" />
    </div>
  </div>
</template>

<script>
import CardList from '../components/CardList'
export default {
  components: { CardList },
  data() {
    return {
      cardList: {
        todo: [
          { id: 1, title: 'aaa', body: 'AAA' },
          { id: 2, title: 'bbb', body: 'BBB' },
          { id: 3, title: 'ccc', body: 'CCC' },
          { id: 4, title: 'ddd', body: 'DDD' },
          { id: 5, title: 'eee', body: 'EEE' },
          { id: 6, title: 'fff', body: 'FFF' }
        ],
        doing: [{ id: 10, title: '○○○○○', body: '◎◎◎◎◎◎◎' }],
        done: [
          { id: 100, title: '000', body: '0000000000' },
          { id: 200, title: '111', body: '1111111111' },
          { id: 300, title: '222', body: '2222222222' }
        ]
      }
    }
  }
}
</script>

<style></style>

CardLit.vue

<template>
  <div class="flex-1 bg-gray-100 m-2 cursor-pointer">
    <p class="text-xl">{{ title }}</p>
    <Card v-for="(card, k) in cardList" :key="k" :card="card" />
  </div>
</template>

<script>
import Card from './Card'
export default {
  name: 'CardList',
  components: { Card },
  props: {
    title: {
      type: String,
      require: true,
      default: ''
    },
    cardList: {
      type: Array,
      require: true,
      default: null
    }
  }
}
</script>

<style scoped></style>

Card.vue

<template>
  <div class="z-10 flex-auto bg-white m-2 p-3 shadow">
    <span>ID:{{ card.id }}&nbsp;{{ card.title }}</span>
  </div>
</template>

<script>
export default {
  name: 'Card',
  props: {
    card: {
      type: Object,
      require: true,
      default: null
    }
  }
}
</script>

<style scoped></style>

f:id:kojirooooocks:20200526094537p:plain

こんな感じでできました。

終わりに

次はイベントとmodal表示とかまでやってみます。