Trello-Clone 筆記

tags: side project

一開始是跟著VueMastery一個教學做的project,上完後覺得裡面用的HTML內建drag and drop API太複雜,不好用且難維護,就試著抽換成vuedraggable,順便學了Tailwind.css,意外發現還蠻好用的。
另外也整理vue-fontawesome的使用方法。

github link

在Vue專案中使用vuedraggable

Doc: vuedraggable

vuedraggable 是一款以Sortable.js為基礎建立的Vue套件,輕鬆讓你實現拖拉功能並同步變動data,可以到他的playground去玩看看

install

npm i vuedraggable

use

直接在component中 import

import draggable from 'vuedraggable'
...
export default {
    components: {
        draggable,
    },
...

在template中使用:

<draggable v-model="myArray">
   <div v-for="element in myArray" :key="element.id">{{element.name}}</div>
</draggable>

加入transition效果:

<draggable v-model="myArray">
    <transition-group>
        <div v-for="element in myArray" :key="element.id">{{element.name}}</div>
    </transition-group>
</draggable>

將transition-group包住你要拖拉的component就可以了

draggable之後一定要直接寫入你要拖拉的component或是transition-group

Vuex:

  • 使用v-model去綁定data,再用get, set去取出store的data以及update data。
  • v-model必須傳入Array
<draggable v-model='myList'>
computed: {
    myList: {
        get() {
            return this.$store.state.myList
        },
        set(value) {
            this.$store.commit('updateList', value)
        }
    }
}

在兩個(或更多)lists之間拖拉:

goal demo

只要在lists的draggable上,設定相同的group name,vuedraggable就會知道這些lists是同一組的,可以相互拖拉。

<draggable v-model="myArray1" group="people">
    <transition-group>
        <div v-for="element1 in myArray1" :key="element1.id">{{element2.name}}</div>
    </transition-group>
</draggable>

<draggable v-model="myArray2" group="people">
    <transition-group>
        <div v-for="element2 in myArray2" :key="element2.id">{{element2.name}}</div>
    </transition-group>

set options:

在draggable tag 上v-bind option,然後在data中設定客製的options,在Sortable.js的文件中可以找到所有options

例如我在project有使用的options:

dragOptions () {
  return {
    animation: 200,
    emptyInsertThreshold: 100,
    scrollSensitivity: 1000
  }
}
  • animation: 單位ms,在移動component時的速度
  • emptyInsertThreshold: 這個花了我不少時間才找到,一開始是發現column如果是沒有任何task(也就是empty array),無法將tasks拉進去,google了幾次,才找到empty list這個關鍵字,進而找到這個issue,最後才發現原來就在Sortable.js的文件中。這個option是指,滑鼠需要拉到靠近empty list 多近才會把component放進該empty list中,單位是px。
  • scrollSensitivity: 這是Sortable.js中default plugin之一,可以偵測滑鼠距離edge多近才會開始scroll,我希望scroll敏感一點,因此設定了很大的數字

unsoleved issue(2020/09)

在手動測試的時候,發現有時在兩個list之間會有閃爍的情形,goole後發現有人發過issue,作者表示似乎是Sortable.js的問題,但尚未解決,我也暫時想不到解法,先放著待優化了。


dragging lists結束後改變list的index:

problem: 原先只commit state裡面的board然後存到localstorage裡,就算dragging改變board的list順序,只要透過commit
去覆蓋先前的board即可。與後端串接後,先前的做法無法改變db中lists的順序。
solution: 根據sortable的文件,用onSort, 可以在list有任何change發生時去觸發綁定的function。

<draggable v-model="lists" v-bind="dragOptions" @sort="updateListsOrder // 加入這個@sort ">

接著在methods

async updateListsOrder () { const lists = this.lists.map((list, index) => { list.order = index return list }) await lists.forEach(list => { this.updateList({ id: list._id, data: list }) // call action: updateList in store }) }

如何製作“依content auto resize的input”

problem: 希望board title的input 可以像trello的board title一樣隨著內容寬度變化。

solution:

爬了許多文後發現,共通點都是在input旁建立一個sibling element,讓這個element 隱形,但它的textContent跟input同步更新,input再去參照element的寬度,並依據element寬度改變input 寬度。

  • 建立一個span,因為只想紀錄文字內容的寬度,因此用span不用input。這裡的font-bold text-2xl都設定的跟input一樣,比較能正確計算寬度。
<span class="h-0 px-4 whitespace-pre overflow-hidden absolute font-bold text-2xl" ref="hideSpan" >{{ boardTitle }}</span> <input v-model="boardTitle" ... >
  • 在input綁定一個style "autoWidth",然後在data中先建立變數inputWidth: '0',autoWidth的值就是inputWidth,之後就依據span的寬度去改變inputWidth。
<input v-model="boardTitle" :style="autoWidth" ... >
data () { return { ... inputWidth: 0 } }, computed: { autoWidth () { return { 'width': `${this.inputWidth}px` } } }
  • 頁面建立時,將boardTitle傳入span,function resizeWidth()去將span的寬度設為inputWidth的值。
mounted () { this.resizeWidth() }, method: { resizeWidth () { this.inputWidth = this.$refs.hideSpan.offsetWidth } }

透過offsetWidth可以取得span的寬度。而用offsetWidth的原因:

offsetWidth / offsetHeight : 是「元素本身」的寬度/高度,並完整了包含了邊界、捲軸及padding。

clientWidth / clientHeight : 則是元素所包含的「子元素」的寬度/高度,其中包含了padding,但不包含邊界及捲軸。

我希望取得的寬度可以包含一些空間,否則input看起來會緊貼著文字,還要自己另外加一些空間,因此使用offsetWidth。不考慮scrollWidth是因為這裡不需要overflow。
offsetWidth有包含padding,所以在span裡也有設定px-4。如此一來取得的寬度就不用另外加上magic number去調整。

Result

ref:
hide span
offsetWidth
offsetWidth(MDN)
vue lifecycle


如何讓list title寬度隨內容增加且能自動換行

基本上全部都按照文章的教學去寫,原理和上面的input很像,建立一個invisible textarea,再根據他的scrollHeight去調整真正textarea的高度。

ref


如何編輯textarea時關掉拖曳功能

problem: 編輯list title時,若按住滑鼠選取textarea內的文字,
會觸發dragging

solution:

  1. 使用handle
<draggable handle=".handle" ... ></draggable>

接著在你希望觸發拖曳的div加入handle的class,如此一來便只能透過該div去拖曳。
但我希望的是input or textarea focus時可以排除,用handle會比較麻煩

  1. Sortable option中的filter以及preventOnFilter
dragOptions () { return { filter: 'textarea, input', preventOnFilter: false } }

Sortable.js的文件寫到:
preventOnFilter = Event.preventDefault()

根據MDN, Event.preventDefault():如果事件可以被取消,就取消事件(即取消事件的預設行為)。 但不會影響事件的傳遞,事件仍會繼續傳遞。

如此一來,當focus在textarea or input時,選取文字的click就不會觸發預設的dragging。

issue in Sortable


在Vue專案中使用Tailwind

Tailwind似乎推出一陣子,最近才知道有這個css framework,剛好這個教學專案有用到,我就去邊查文件邊用,結果發現教案用的是0.x.x版本,現在已經到1.x.x,還好官方有教學如何update,花了一番工夫才完成,但通常應該不會需要那麼大幅度的update,因此這邊我只記錄如何install and setting。

Doc

install

# Using npm

npm install tailwindcss

Create a new file inside src/assets/css/tailwind.css with the following content:

@tailwind base;

@tailwind components;

@tailwind utilities;

import this file inside your main.js file:

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import '@/assets/css/tailwind.css'

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

optional:Run npx tailwind init to create a tailwind configuration file:

npx tailwind init

// tailwind.config.js
module.exports = {
  purge: [
    './src/**/*.html',
    './src/**/*.vue'
  ],
  theme: {
    extend: {}
  },
  variants: {},
  plugins: []
}

在這裡可以設定custom css settings
purge是設定讓PurgeCSS管理的檔案

Using Tailwind with PostCSS:

根目錄建立postcss.config.js,由 PostCSS 啟動 Tailwind 與 AutoPrefixer。

module.exports = {
  plugins: [
    // ...
    require('tailwindcss'),
    require('autoprefixer'),
    // ...
  ]
}

到這裡為止,應該就可以使用tailwind的utilities了

<div id="app" class="min-h-screen w-screen bg-gray-200 flex items-center justify-center">
</div>

如何在Vue專案中使用font awesome

Doc

以下為在專案中建立一個reusable font awesome icon compoenet的步驟

install

free font awesome user:

$ npm i --save @fortawesome/fontawesome-svg-core
$ npm i --save @fortawesome/free-solid-svg-icons
$ npm i --save @fortawesome/free-brands-svg-icons
$ npm i --save @fortawesome/free-regular-svg-icons
$ npm i --save @fortawesome/vue-fontawesome@2

安裝這些之後,fas, fab, far開頭的icon都能使用,當然也可以視你的需求去install

Usage

在這專案中我另外建立的一個plugins資料夾,新增fontawesome.js,再從App.vue import進去。官方是推薦直接在App.vue中import和設定。

./plugins/fontawesome.js

import Vue from 'vue'

import { library } from '@fortawesome/fontawesome-svg-core'
import { fas } from '@fortawesome/free-solid-svg-icons'
import { far } from '@fortawesome/free-regular-svg-icons'
import { fab } from '@fortawesome/free-brands-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'

library.add(fas, far, fab)

Vue.component('AppIcon', FontAwesomeIcon)

這裡做幾件事:

  1. 引入所有fontawesome
  2. 在library中新增所有icon的種類(solid, regular, brand),也可以只import需要的icon: e.g.
import { fabars } from '@fortawesome/free-solid-svg-icons'
  1. 命名為AppIcon,並全域註冊這個component

在template中使用

兩種方法:

bind an array:

<AppIcon :icon="['far', 'trash-alt']">
</AppIcon>

property:

<AppIcon icon="trash-alt">
</AppIcon>

在fontawesome網站上,html如下

<i class="far fa-trash-alt"></i>

可以發現trash-alt的前綴fa-是不需要寫的

如此一來,就可以使用fontawesome網站上所有的icons,也可以把它當作 vue component一樣操作。


ref:
https://www.binarcode.com/blog/building-animated-draggable-interfaces-with-vuejs-and-tailwind/
https://fpjs.fun/tailwind/general/vue/


使用@apply時如何使用transition css

problem: 在Home中的 board card,想實現hover改變background,以下寫法無法生效

.card { @apply rounded-sm relative mr-4 mb-6 transition duration-200 ease-in; width: 23%; min-width: 190px; height: 96px; &:hover { background-color: #005B8F; } }

結果:hover時background color會改變但transition效果沒有出現

solution:

card用@apply, hover也要使用@apply
先在tailwind.config.js設定custom color #005B8F之後

.card { @apply rounded-sm relative mr-4 mb-6 transition duration-200 ease-in; width: 23%; min-width: 190px; height: 96px; &:hover { @apply bg-boardCard-hover; } }

ref


若列表中無卡片,新增卡片進列表中時,列表無法顯示卡片

problem: 如題,v-for的ListCard在card的被建立時,沒有呈現在列表中,使用Vue devtool觀察,store中確實有增加卡片,在BoardList中也能夠取得,v-for用到的cards也是放在computed中,一有變動應該會讓v-for去render new element。

<transition-group tag="div"> <ListCard v-for="card of cards" :key="card._id" :card="card" /> </transition-group>

solution: 只要v-for element的key值有變化,就會render新的element,但前提是cards不能為undefined,否則v-for就無法得知element的key值有變動。因此在create list 的mutation中加入cards的空array。

CREATE_LIST (state, { data }) { data.cards = [] state.lists.push(data) },