keyboard_tabSkip to content
listVue Firebase Masterclass

Let's code แต่ละส่วนกัน

สารบัญ

เริ่มต้น Structure project

เราจะมาทำ Todo list (อันว่าเป็น Hello world ของโลก Frontend ในยุคนี้กัน) โดยเราจะวาง Structure กันตามนี้

├── src
    ├── App.vue --> root component
    ├── router
       └── index.js --> ตำแหน่งรวม router (ตำแหน่งเดิม)
    ├── stores
       └── todo.js --> store สำหรับเก็บ todo list ไว้
    └── views
       ├── EditView.vue --> หน้าสำหรับแก้ไข EditView.vue
       └── HomeView.vue --> หน้าสำหรับหน้าหลัก
    └── main.js

เริ่มทำ todo store

ที่ stores/todo.js

  • เพิ่ม state สำหรับเก็บ todo, selectedTodo และ status ทั้งหมดของ Todo เอาไว้
  • เพิ่ม function
    • loadTodos() สำหรับดึง Todo ทั้งหมดจาก API
    • loadTodo (id) สำหรับดึง todo เฉพาะ id ออกมา
    • addTodo (todoText) สำหรับเพิ่ม todo เข้าไป
    • editTodo (todoData, id) สำหรับแก้ไข todo โดยระบุตาม id
    • removeTodo (id) สำหรับลบ Todo
javascript
import { defineStore } from 'pinia'
import axios from 'axios'

const BASE_URL = 'https://64f192630e1e60602d23f251.mockapi.io'

export const useTodoStore = defineStore('todo', {
  state: () => ({ // กำหนดข้อมูล state ที่จะเก็บใน store นี้
    list: [],
    selectedTodo: {},
    statuses: ['Pending', 'Doing', 'Done']
  }),
  actions: {
    async loadTodos () {
      try {
        const response = await axios.get(`${BASE_URL}/todos`)
        this.list = response.data
      } catch (error) {
        console.log('error', error)
      }
    },
    async loadTodo (id) {
      try {
        const response = await axios.get(`${BASE_URL}/todos/${id}`)
        this.selectedTodo = response.data
      } catch (error) {
        console.log('error', error)
      }
    },
    async addTodo (todoText) {
      const bodyData = {
        name: todoText,
        status: 'Pending'
      }
      try {
        const response = await axios.post(`${BASE_URL}/todos`, bodyData)
        console.log(response.data)
        // this.list = response.data
      } catch (error) {
        console.log('error', error)
      }
    },
    async editTodo (todoData, id) {
      try {
        const bodyData = {
          name: todoData.name,
          status: todoData.status
        }
        const response = await axios.put(`${BASE_URL}/todos/${id}`, bodyData)
        console.log(response.data)
      } catch (error) {
        console.log('error', error)
      }
    },
    async removeTodo (id) {
      try {
        const response = await axios.delete(`${BASE_URL}/todos/${id}`)
        console.log(response.data)
      } catch (error) {
        console.log('error', error)
      }
    }
  }
})

ทำ Router และ View เริ่มต้น

ที่ router/index.js

  • เพิ่ม 2 path มา คือ
    • / สำหรับ HomeView
    • /todo/:id/edit สำหรับ EditView
javascript
import { createRouter, createWebHistory } from 'vue-router'

import HomeView from '../views/HomeView.vue'
import EditView from '../views/EditView.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },
    {
      path: '/todo/:id/edit',
      name: 'edit-view',
      component: EditView
    }
  ]
})

export default router

ทำการ Mock View ก่อน

  • ที่ views/HomeView.vue
html
<template>
  Todo list Home View
</template>
  • ที่ views/EditView.vue
html
<template>
  Todo list Edit View
</template>

ประกอบ HomeView.vue และ EditView.vue

ไฟล์ HomeView.vue

  • เพิ่มตัวรับ Store เข้ามาแสดงผลที่ template
  • เพิ่ม Todo list โดยการแสดง name, status และ Action (Edit, Delete) ให้สามารถไปยังหน้า Edit และลบได้
  • เพิ่ม จังหวะโหลด API todo list ตอนเปิดหน้าขึ้นมา
  • เพิ่ม Todo list จากหน้า Home ได้ (เป็น input ด้านบน)
  • ปรับ status todo list ได้จากหน้า Home
  • เพิ่ม Edit, Delete method เข้าไปและเพิ่ม Loading ให้รู้ว่ากำลังดำเนินการอยู่ (แบบง่ายๆ)
html
<script setup>
import { onMounted, ref } from 'vue'
import { RouterLink } from 'vue-router'
import { useTodoStore } from '../stores/todo'

const todoStore = useTodoStore()
const todoText = ref('')
const isLoading = ref(false)

onMounted(async () => {
  await todoStore.loadTodos()
  // console.log(todoStore.list) มันเป็น proxy = เราก็แก้ผ่านตัวนี้ก็ได้
})

const addTodo = async (todoText) => {
  isLoading.value = true
  try {
    await todoStore.addTodo(todoText)
    await todoStore.loadTodos()
  } catch (error) {
    console.log('error', error)
  }
  isLoading.value = false
}

const updateStatus = async (todoId, todoStatus) => {
  isLoading.value = true
  try {
    await todoStore.editTodo({
      status: todoStatus
    }, todoId)
  } catch (error) {
    console.log('error', error)
  }
  isLoading.value = false
}

const removeTodo = async (id) => {
  isLoading.value = true
  try {
    await todoStore.removeTodo(id)
    await todoStore.loadTodos()
  } catch (error) {
    console.log('error', error)
  }
  isLoading.value = false
}
</script>

<template>
  <div>
    <input type="text" v-model="todoText">
    <button @click="addTodo(todoText)">Add</button>
  </div>
  <div>
    <div v-if="isLoading">
      <h1>Loading...</h1>
    </div>
    <div>
      <div class="flex" v-for="todo in todoStore.list" :key="todo.id">
        <div>Item {{ todo.name }}</div>
        <div>
          Status
          <select v-model="todo.status" @change="updateStatus(todo.id, todo.status)">
            <option
              v-for="status in todoStore.statuses"
              :key="status"
              :value="status"
              >
              {{ status }}
            </option>
          </select>
        </div>
        <div>
          <RouterLink :to="{ name: 'edit-view', params: { id: todo.id } }">
            <button>Edit</button>
          </RouterLink>
          <button @click="removeTodo(todo.id)">Delete</button>
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped>
.flex {
  display: flex;
}
.flex > div {
  margin-right: 20px;
}
</style>

ไฟล์ EditView.vue

  • ดึงข้อมูล id จาก param มา และยิงไปยัง Todo list เพื่อนำข้อมูลมาสำหรับแก้ไข (โดยใช้จาก Store)
  • แก้ไข ชื่อและ status ได้ และสามารถยิงไปยัง Todo list API สำหรับแก้ไข todo list ได้
  • มีตัว alert บอกมาตอน update เสร็จแล้ว
html
<script setup>
import { onMounted, ref, reactive } from 'vue'
import { useTodoStore } from '../stores/todo'
import { useRoute, RouterLink } from 'vue-router'

const todoStore = useTodoStore()
const route = useRoute()

const todoId = ref(-1)
const todoData = reactive({
  name: '',
  status: ''
})

const editTodo = async (todoData, todoId) => {
  await todoStore.editTodo(todoData, todoId)
  alert('update completed')
}
const isLoaded = ref(false)

onMounted(async () => {
  try {
    todoId.value = parseInt(route.params.id)
    await todoStore.loadTodo(todoId.value)
    todoData.name = todoStore.selectedTodo.name
    todoData.status = todoStore.selectedTodo.status
    isLoaded.value = true
  } catch (error) {
    console.log('error', error)
  }
})

</script>

<template>
  <div>
    Edit View

    <RouterLink :to="{ name: 'home' }">
      Back
    </RouterLink>

    <div v-if="isLoaded">
      <div>Name</div>
      <input type="text" v-model="todoData.name">
      <div>Status</div>
      <select v-model="todoData.status">
        <option
          v-for="status in todoStore.statuses"
          :key="status"
          :value="status"
          >
          {{ status }}
        </option>
      </select>
      <button @click="editTodo(todoData, todoId)">Update</button>
    </div>
  </div>
</template>

ผลลัพธ์ทั้งหมด workshop-01

และนี่ก็คือตัวอย่างการต่อ API เข้ากับ pinia เราจะเก็บ project นี้ไว้เพื่อใช้สำหรับ แต่งใน Session ต่อไป

โดยเราจะนำ Todo list นี้มาแต่งเป็นหน้าตาประมาณนี้ออกมาใน Session ต่อไปกัน

workshop-01