Vue 와 Django(DRF) 를 이용하여 Todo 리스트 만들기 - 10 (프론트 Update)
Project/Django & Vue.js

Vue 와 Django(DRF) 를 이용하여 Todo 리스트 만들기 - 10 (프론트 Update)

뉴비뉴 2020. 8. 26.

안녕하세요.

 

저번 포스팅에서는 작성한 TodoList를 삭제할 수 있는 Delete 기능을 구현하였습니다.

https://newbiecs.tistory.com/313

 

Vue 와 Django(DRF) 를 이용하여 Todo 리스트 만들기 - 9 (프론트 Delete)

안녕하세요. 저번 포스팅에서는 Create 하고 리스트를 갱신해주는 것을 만들어보았습니다. 오늘은 Delete 기능과 Vuetify DatePicker 에 선택한 날짜를 텍스트로 보여주는 것을 구현해주도록 하겠습니다

newbiecs.tistory.com

오늘은 여러 TodoList 중에 원하는 TodoList 의 Update 버튼 클릭 시 수정할 수 있는 Form 이 나오고

업데이트를 할 수 있고, 다른 TodoList 의 Update 버튼을 클릭하면 처음에 눌렀던 Update Form 은 없어지는 것을 구현해보겠습니다.

 

그 전에 현재 vuetify 의 date picker 가 제대로 안보이는 상황이기 때문에 백그라운드 색상을 추가해주도록 하겠습니다.

<v-date-picker style="background-color: gray" v-model="data.due_date" @input="showPicker = false"></v-date-picker>

기존에 타이틀을 생성하면 테마가 동작하지 않아 빈 칸으로 보이던 부분을 gray 색으로 채워주었습니다.

 

동작 예시

 

구현

1. App.vue

동작예시와 같은 기능을 구현하기 위해서는 is_hidden 이라는 플래그가 필요합니다.

하지만 백엔드 서버에서는 is_hidden 이라는 필드를 제공해주지 않기 때문에 생성해주도록 하겠습니다.

// HTML 에 v-on:patched:getTodoList 가 추가되었음
<todo-content v-bind:propsdata="todoList" v-on:saved="getTodoList" v-on:deleted="getTodoList"></todo-content>


import axios from "axios";
import TodoHeader from "./components/TodoHeader.vue";
import TodoContent from "./components/TodoContent.vue";
import TodoFooter from "./components/TodoFooter.vue";
let url = "http://localhost:8000/api/todo/"; // drf server addr
export default {
  data: () => {
    return {
      todoList: []
    };
  },
  components: {
    "todo-header": TodoHeader,
    "todo-content": TodoContent,
    "todo-footer": TodoFooter
  },
  mounted() {
    // DOM 객체 생성 후 drf server 에서 데이터를 가져와 todoList 저장
    axios({
      method: "GET",
      url: url
    })
      .then(response => {
        for (var index in response.data) {
          response.data[index].is_hidden = false;  // 백에서 받은 데이터에 is_hidden=false 추가
        }
        this.todoList = response.data;
        console.log("Success", response);
      })
      .catch(error => {
        console.log("Failed to get todoList", error.response);
      });
  },
  methods: {
    getTodoList: function() {
      axios({
        method: "GET",
        url: url
      })
        .then(response => {
          for (var index in response.data) {
            response.data[index].is_hidden = false;  // 백에서 받은 데이터에 is_hidden=false 추가
          }
          this.todoList = response.data;
          console.log("Success", response);
        })
        .catch(error => {
          console.log("Failed to get todoList", error.response);
        });
    },
    updateTodoList: function() {}
  }
};

TodoList 를 propsdata 로 자식컴포넌트로 전달 할 때 is_hidden 이 추가되어 전달이 됩니다.

 

2. TodoContent

이전 포스팅의 코드와 크게 달라진 점은 === 표시 이후를 살펴보시면 되고,

업데이트 버튼을 눌렀을 때와 is_hidden 의 동작에 대한 추가만 되었습니다.

<template>
  <div>
    <v-container fluid>
      <v-layout column>
        <v-flex>
          <h3 class="subject">what is your plan?</h3>
        </v-flex>
        <v-flex column>
          <v-row>
            <v-col cols="4" md="3">
              <v-text-field v-model="data.title" :counter="32" label="Title" required></v-text-field>
            </v-col>
            <v-col cols="4" md="3">
              <v-text-field v-model="data.author" :counter="64" label="Author" required></v-text-field>
            </v-col>
            <v-col cols="4" md="6">
              <v-text-field v-model="data.description" :counter="255" label="Content" required></v-text-field>
            </v-col>
          </v-row>
          <div data-app>
            <v-menu
              v-model="showPicker"
              :close-on-content-click="false"
              transition="scale-transition"
              offset-y
              max-width="500px"
              min-width="500px"
            >
              <template v-slot:activator="{ on }">
                <v-text-field
                  v-model="data.due_date"
                  label="Due Date"
                  hint="YYYY-MM-DD"
                  persistent-hint
                  readonly
                  v-on="on"
                ></v-text-field>
                <v-date-picker
                  style="background-color: gray"
                  v-model="data.due_date"
                  @input="showPicker = false"
                ></v-date-picker>
              </template>
            </v-menu>
          </div>
          <!-- <v-date-picker v-model="data.due_date" locale="ko" no-title></v-date-picker> -->
          <v-btn @click="sendForm" color="#4CAF50">create</v-btn>
          <v-btn @click="clearForm" color="#F44336">clear</v-btn>
        </v-flex>

================================변경 된 로직
        <v-flex class="todoList" column>
          <v-card fluid>
            <v-list-item v-for="(data, index) in propsdata" v-bind:key="index">
              <v-list-item-content v-show="!data.is_hidden">  <!-- Not 연산을 수행하기 때문에 !data.is_hidden = true 로 화면에 보이게 됩니다 -->
                <v-list-item-title>{{ data.title }}</v-list-item-title>
                <v-list-item-title>{{ data.author }}</v-list-item-title>
                <v-list-item-title>{{ data.due_date }}</v-list-item-title>
                <v-list-item-subtitle>{{ data.description }}</v-list-item-subtitle>
              </v-list-item-content>
              <v-form v-show="data.is_hidden"> <!-- is_hidden=true 가 되면 입력할 수 있는 form 이 보여집니다. -->
                <v-container>
                  <v-row>
                    <v-col cols="12" md="6">
                      <v-text-field v-model="data.title" :counter="64" label="Title" required></v-text-field>
                    </v-col>
                    <v-col cols="12" md="3">
                      <v-text-field v-model="data.author" :counter="32" label="Author" disabled></v-text-field>
                    </v-col>
                    <v-col cols="12" md="3">
                      <v-text-field
                        v-model="data.due_date"
                        label="Due Date"
                        required
                      ></v-text-field>
                    </v-col>
                    <v-col cols="12" md="10">
                      <v-text-field
                        v-model="data.description"
                        :counter="500"
                        label="Description"
                        required
                      ></v-text-field>
                    </v-col>
                    <v-col cols="12" md="2">
                    <v-checkbox
                      v-model="data.completed"
                      color="#2962FF"
                      label="Completed"
                      required
                    ></v-checkbox>
                    </v-col>
                    <v-col cols="12" md="4">
                      <!-- Save 버튼은 Update 버튼을 클릭하면 볼 수 있고, Save 버튼 클릭 시 is_hidden=false 를 전달합니다. -->
                      <v-btn class="ma-2" @click="data.is_hidden = !data.is_hidden ; updateTodo(data)" v-show="data.is_hidden" color="#4CAF50">Save</v-btn>
                      <!-- Delete 버튼은 Update 버튼을 클릭하면 볼 수 있고, Delete 버튼 클릭 시 선택 된 객체를 삭제합니다. -->
                      <v-btn class="ma-2" @click="deleteTodo(data.id)" color="#F44336">Delete</v-btn>
                    </v-col>
                  </v-row>
                </v-container>
              </v-form>
              <!-- Update 버튼 클릭 시 onlyTodoListCard 예제에 보이는 하나의 객체만 띄울 수 있는 메소드에 데이터를 전달합니다. -->
              <v-btn class="ma-2" @click="data.is_hidden = !data.is_hidden ; onlyTodoListCard(data, propsdata)" v-show="!data.is_hidden" color="#F9A825">Update</v-btn>
              <!-- Delete 버튼 클릭 시 선택한 객체가 삭제 됩니다. -->
              <v-btn class="ma-2" @click="deleteTodo(data.id)" v-show="!data.is_hidden" color="#F44336">Delete</v-btn>
            </v-list-item>
          </v-card>
        </v-flex>
      </v-layout>
    </v-container>
  </div>
</template>

 

다음으로 Vue.js 코드를 살펴보도록 하겠습니다.

 

import axios from "axios";
let url = "http://localhost:8000/api/todo/";
export default {
  data: () => {
    return {
      showPicker: false,
      data: {
        id: "",
        title: "",
        author: "",
        description: "",
        due_date: new Date().toISOString().substr(0, 10)
      }
    };
  },
  props: ["propsdata"],
  methods: {
    sendForm: function() {
      axios({
        method: "POST",
        url: url,
        data: this.data
      })
        .then(response => {
          console.log("Success", response);
          this.$emit("saved");
        })
        .catch(error => {
          console.log("Failed to create todoList", error.response);
        });
    },
    clearForm: function() {
        (this.data.title = ""),
        (this.data.description = ""),
        (this.data.author = ""),
        (this.data.due_date = "");
    },
    deleteTodo: function(id) {
      axios({
        method: "DELETE",
        url: url + id
      })
        .then(response => {
          this.$emit("deleted");
          console.log("Success", response);
        })
        .catch(error => {
          console.log("Failed to delete todoList", error.response);
        });
    },
    updateTodo: function(data) {  // 사용자가 입력한 데이터를 받아 PATCH 로 업데이트
      axios({
        method: "PATCH",
        url: url + data.id + "/",
        data: data
      })
        .then(response => {
          this.$emit("patched");  // 업데이트 후에는 상위 컴포넌트에 이벤트를 보내 getTodoList 실행
          console.log("Success", response);
        })
        .catch(error => {
          console.log("Failed to patched todoList", error.response);
        });
    },
    onlyTodoListCard: function(data, propsdata) {   // 한 개의 투두리스트만 입력 form이 보이게끔
      for (var index in propsdata) {
        (data.id != propsdata[index].id) ? propsdata[index].is_hidden = false : ''
      }
    }
  }
};

 

Update

 

백엔드 서버에 구현되어있는 REST API 에 PATCH 메소드로 사용자가 수정한 데이터를 넘겨줍니다.

백엔드 서버에는 사용자가 입력한 정보가 업데이트 되었지만, 페이지를 새로고침을 해야만 변경 된 데이터가 보이기 때문에

this.$emit("patched") 로 이벤트를 발생시켜 상위컴포넌트에서 새로운 TodoList 를 받아오도록 해주었습니다.

 

onlyTodoListCard

 

TodoList 목록들에 수정할 수 있게 Update 버튼들이 위치해있습니다.

Hello 와 Dude 라는 투두리스트가 있다고 가정하겠습니다.

Hello 를 클릭하여 정보를 수정하고, Dude 를 클릭하여 정보를 수정할 때 기존에 열려있던 Hello 는 닫히도록 해주는 함수입니다.

 

Update 검증

 

최근에 자격증 시험을 치뤄서 공부한다고 포스팅이 많이 늦어졌네요.

다음 포스팅에서는 디자인 적인 요소와 Header, Footer 의 구색을 맞춰보도록 하겠습니다.

 

감사합니다!

 

댓글

💲 추천 글