# Trello-Clone 筆記
###### tags: `side project`
一開始是跟著VueMastery一個教學做的project,上完後覺得裡面用的HTML內建drag and drop API太複雜,不好用且難維護,就試著抽換成vuedraggable,順便學了Tailwind.css,意外發現還蠻好用的。
另外也整理vue-fontawesome的使用方法。
[github link](https://github.com/purrup/Trello-Clone)
## 在Vue專案中使用vuedraggable
### Doc: [vuedraggable](https://www.npmjs.com/package/vuedraggable)
vuedraggable 是一款以Sortable.js為基礎建立的Vue套件,輕鬆讓你實現拖拉功能並同步變動data,可以到他的[playground](https://david-desmaisons.github.io/draggable-example/)去玩看看
### install
`npm i vuedraggable`
### use
直接在component中 import
```javascript
import draggable from 'vuedraggable'
...
export default {
components: {
draggable,
},
...
```
在template中使用:
```htmlmixed
<draggable v-model="myArray">
<div v-for="element in myArray" :key="element.id">{{element.name}}</div>
</draggable>
```
加入transition效果:
```htmlmixed
<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**
```htmlmixed
<draggable v-model='myList'>
```
```javascript
computed: {
myList: {
get() {
return this.$store.state.myList
},
set(value) {
this.$store.commit('updateList', value)
}
}
}
```
在兩個(或更多)lists之間拖拉:
[goal demo](http://g.recordit.co/wnTyR6gbZN.gif)
只要在lists的draggable上,設定相同的group name,vuedraggable就會知道這些lists是同一組的,可以相互拖拉。
```htmlmixed
<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](https://github.com/SortableJS/Sortable#options)。
例如我在project有使用的options:
```javascript
dragOptions () {
return {
animation: 200,
emptyInsertThreshold: 100,
scrollSensitivity: 1000
}
}
```
* animation: 單位ms,在移動component時的速度
* emptyInsertThreshold: 這個花了我不少時間才找到,一開始是發現column如果是沒有任何task(也就是empty array),無法將tasks拉進去,google了幾次,才找到empty list這個關鍵字,進而找到[這個issue](https://github.com/SortableJS/Vue.Draggable/issues/673#issuecomment-512793950),最後才發現原來就在Sortable.js的文件中。這個option是指,滑鼠需要拉到靠近empty list 多近才會把component放進該empty list中,單位是px。
* scrollSensitivity: 這是Sortable.js中default plugin之一,可以偵測滑鼠距離edge多近才會開始scroll,我希望scroll敏感一點,因此設定了很大的數字
---
### unsoleved issue(2020/09)
在手動測試的時候,發現有時在兩個list之間會有閃爍的情形,goole後發現有人發過[issue](https://github.com/SortableJS/Vue.Draggable/issues/825),作者表示似乎是Sortable.js的問題,但尚未解決,我也暫時想不到解法,先放著待優化了。
---
### dragging lists結束後改變list的index:
problem: 原先只commit state裡面的board然後存到localstorage裡,就算dragging改變board的list順序,只要透過commit
去覆蓋先前的board即可。與後端串接後,先前的做法無法改變db中lists的順序。
solution: 根據sortable的[文件](https://github.com/SortableJS/Sortable#options),用onSort, 可以在list有任何change發生時去觸發綁定的function。
```javascript=
<draggable
v-model="lists"
v-bind="dragOptions"
@sort="updateListsOrder // 加入這個@sort
">
```
接著在methods
```javascript=
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一樣,比較能正確計算寬度。
```htmlmixed=
<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。
```htmlmixed=
<input
v-model="boardTitle"
:style="autoWidth"
...
>
```
```javascript=
data () {
return {
...
inputWidth: 0
}
},
computed: {
autoWidth () {
return {
'width': `${this.inputWidth}px`
}
}
}
```
* 頁面建立時,將boardTitle傳入span,function resizeWidth()去將span的寬度設為inputWidth的值。
```javascript=
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](https://imgur.com/0TCSF7C)
ref:
[hide span](https://stackoverflow.com/questions/8100770/auto-scaling-inputtype-text-to-width-of-value)
[offsetWidth](https://shubo.io/element-size-scrolling/)
[offsetWidth(MDN)](https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement/offsetWidth)
[vue lifecycle](https://vuejs.org/v2/guide/instance.html#Lifecycle-Diagram)
---
### 如何讓list title寬度隨內容增加且能自動換行
基本上全部都按照文章的教學去寫,原理和上面的input很像,建立一個invisible textarea,再根據他的scrollHeight去調整真正textarea的高度。
[ref](https://www.scottstadt.com/2019/06/03/vue-autosize-textarea.html)
---
### 如何編輯textarea時關掉拖曳功能
problem: 編輯list title時,若按住滑鼠選取textarea內的文字,
會觸發dragging
solution:
1. 使用handle
```htmlembedded=
<draggable
handle=".handle"
...
></draggable>
```
接著在你希望觸發拖曳的div加入handle的class,如此一來便只能透過該div去拖曳。
但我希望的是input or textarea focus時可以排除,用handle會比較麻煩
2. Sortable option中的filter以及preventOnFilter
```javascript=
dragOptions () {
return {
filter: 'textarea, input',
preventOnFilter: false
}
}
```
[Sortable.js的文件](https://github.com/SortableJS/Sortable)寫到:
preventOnFilter = Event.preventDefault()
根據[MDN](https://developer.mozilla.org/zh-TW/docs/Web/API/Event/preventDefault), Event.preventDefault():如果事件可以被取消,就取消事件(即取消事件的預設行為)。 但不會影響事件的傳遞,事件仍會繼續傳遞。
如此一來,當focus在textarea or input時,選取文字的click就不會觸發預設的dragging。
[issue in Sortable](https://github.com/SortableJS/Vue.Draggable/issues/405#issuecomment-458920236)
---
### 在Vue專案中使用Tailwind
Tailwind似乎推出一陣子,最近才知道有這個css framework,剛好這個教學專案有用到,我就去邊查文件邊用,結果發現教案用的是0.x.x版本,現在已經到1.x.x,還好官方有教學如何update,花了一番工夫才完成,但通常應該不會需要那麼大幅度的update,因此這邊我只記錄如何install and setting。
### [Doc](https://tailwindcss.com/docs/installation)
### install
```
# Using npm
npm install tailwindcss
```
Create a new file inside ***src/assets/css/tailwind.css*** with the following content:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```
import this file inside your main.js file:
```javascript
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`
```javascript
// 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。
```javascript
module.exports = {
plugins: [
// ...
require('tailwindcss'),
require('autoprefixer'),
// ...
]
}
```
到這裡為止,應該就可以使用tailwind的utilities了
```htmlmixed
<div id="app" class="min-h-screen w-screen bg-gray-200 flex items-center justify-center">
</div>
```
---
### 如何在Vue專案中使用font awesome
[Doc](https://www.npmjs.com/package/@fortawesome/vue-fontawesome)
以下為在專案中建立一個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
```javascript
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.
```javascript
import { fabars } from '@fortawesome/free-solid-svg-icons'
```
4. 命名為AppIcon,並全域註冊這個component
### 在template中使用
兩種方法:
bind an array:
```htmlmixed
<AppIcon :icon="['far', 'trash-alt']">
</AppIcon>
```
property:
```htmlmixed
<AppIcon icon="trash-alt">
</AppIcon>
```
在fontawesome網站上,html如下
```htmlmixed
<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,以下寫法無法生效
```css=
.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之後
```css=
.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](https://github.com/tailwindlabs/tailwindcss/issues/593)
---
### 若列表中無卡片,新增卡片進列表中時,列表無法顯示卡片
problem: 如題,v-for的ListCard在card的被建立時,沒有呈現在列表中,使用Vue devtool觀察,store中確實有增加卡片,在BoardList中也能夠取得,v-for用到的`cards`也是放在computed中,一有變動應該會讓v-for去render new element。
```javascript=
<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。
```javascript=
CREATE_LIST (state, { data }) {
data.cards = []
state.lists.push(data)
},
```
---