# 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) }, ``` ---