๐Ÿ“• Language/Vue

[์›น์•ฑ ์ œ์ž‘์œผ๋กœ ๋ฐฐ์›Œ๋ณด๋Š” Vue.js, ES6, Vuex] - Todo App ํ”„๋กœ์ ํŠธ ์‹ค์Šต

a n u e 2022. 5. 22. 14:58

 

์บกํ‹ดํŒ๊ต๋‹˜์˜ Vue.js ์ค‘๊ธ‰ ๊ฐ•์ขŒ - ์›น์•ฑ ์ œ์ž‘์œผ๋กœ ๋ฐฐ์›Œ๋ณด๋Š” Vue.js, ES6, Vuex ๊ฐ•์˜๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์‹ค์Šตํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.


TodoHeader.vue

<template>
  <header>
    <h1>TODO it!</h1>
  </header>
</template>

<!-- ์ปดํฌ๋„ŒํŠธ ์•ˆ์—์„œ๋งŒ ์œ ํšจํ•œ ์Šคํƒ€์ผ ์†์„ฑ์„ ์ง€์ •ํ•˜๋ ค๋ฉด scoped ์ฒ˜๋ฆฌ -->
<style scoped>
h1 {
  color: #2F3B52;
  font-weight: 900;
  margin: 2.5rem 0 1.5rem;
}
</style>

 


TodoInput.vue

<template>
  <div class="inputBox shadow">
      <input type="text" v-model="newTodoItem" v-on:keyup.enter="addTodo">
      <!-- <button v-on:click="addTodo">add</button> -->
      <span class="addContainer" v-on:click="addTodo">
        <i class="fa-solid fa-plus addBtn"></i>
      </span>
  </div>
</template>

<script>
export default {
  data : function() {
    return {
      //์ดˆ๊ธฐ ์„ ์–ธ
      newTodoItem : ""
    }
  },
  methods : {
    addTodo : function() {
      //inputํƒœ๊ทธ์— ์–ด๋–ค ๊ฐ’์ด ์žˆ์„๋•Œ๋งŒ ๊ฐ’์„ ์ถ”๊ฐ€ํ•ด์คŒ
      if(this.newTodoItem != '') 
      {
                    //emit ์ด๋ฒคํŠธ ์ด๋ฆ„ ์ง€์ •, ๋ณด๋‚ด์ค„ ์ธ์ž 1,2,3,4,...
        this.$emit('addTodoItem', this.newTodoItem);
        this.clearInput();
      }
    },
    clearInput : function() {
      this.newTodoItem = '';
    }
  }
}
</script>

<style scoped>
input:focus {
  outline: none;
}
.inputBox {
  background: white;
  height: 50px;
  line-height: 50px;
  border-radius: 5px;
}
.inputBox input {
  border-style: none;
  font-size: 0.9rem;
}
.addContainer {
  float: right;
  background: linear-gradient(to right, #6478FB, #8763FB);
  display: block;
  width: 3rem;
  border-radius: 0 5px 5px 0;
}
.addBtn {
  color: white;
  vertical-align: middle;
}
</style>

 


TodoList.vue

<template>
  <div>
    <!-- ul>li*3-->
     <ul>
       <!--
         <li v-for="todoItem in todoItems" v-bind:key="todoItem" class="shadow">
       -->
       <li v-for="(todoItem, index) in propsdata" v-bind:key="todoItem.item" class="shadow">
          <i class="checkBtn fas fa-check" v-bind:class="{checkBtnCompleted: todoItem.completed}"
            v-on:click="toggleComplete(todoItem, index)"></i>
          <!-- todoitem์˜ ๋ฐฐ์—ด์— ๋‹ด๊ธด ๊ฐ์ฒด์˜ ์†์„ฑ ์ค‘ completed ์†์„ฑ์ด true๋ฉด ํ•ด๋‹น ํƒœ๊ทธ๊ฐ€ ๊ทธ๋Œ€๋กœ ์ ์šฉ. false๋ฉด {} ์ฒ˜๋ฆฌ๋˜์–ด์ง -->
          <span v-bind:class="{textCompleted: todoItem.completed}">{{ todoItem.item }}</span>
          <span class="removeBtn" v-on:click="removeTodo(todoItem, index)">
            <i class="fas fa-trash-alt"></i>
          </span>
       </li>
     </ul> 
  </div>
</template>

<script>
export default {
  //App.vue์—์„œ ๋‚ด๋ฆฐ propsdata๋ฅผ ๋ฐ›๋Š”๋‹ค.
  props : ['propsdata'],
  methods : {
    removeTodo : function(todoItem, index) {
      this.$emit('removeItem', todoItem, index);
    },
    toggleComplete : function(todoItem, index) {
      this.$emit('toggleItem', todoItem, index);
    }
  }
}
</script>


<style scoped>
ul {
  list-style-type: none;
  padding-left: 0px;
  margin-top: 0;
  text-align: left;
}
li {
  display: flex;
  min-height: 50px;
  height: 50px;
  line-height: 50px;
  margin: 0.5rem 0;
  padding: 0 0.9rem;
  background: white;
  border-radius: 5px;
}
.checkBtn {
  line-height: 45px;
  color: #62acde;
  margin-right: 5px;
}
.checkBtnCompleted {
  color: #b3adad;
}
.textCompleted {
  text-decoration: line-through;
  color: #b3adad;
}
.removeBtn {
  margin-left: auto;
  color: #de4343;
}
</style>

 


TodoFooter.vue

<template>
  <div class="clearAllContainer">
    <span class="clearAllBtn" v-on:click="clearTodo">Clear All</span>
  </div>
</template>

<script>
export default {
  methods : {
    clearTodo : function() {
      this.$emit('clearAll');
    }
  }
}
</script>



<style scoped>
.clearAllContainer {
  width: 8.5rem;
  height: 50px;
  line-height: 50px;
  background-color: white;
  border-radius: 5px;
  margin: 0 auto;
}
.clearAllBtn {
  color: #e20303;
  display: block;
}
</style>

App.vue

<template>
  <div id="app">
    <TodoHeader></TodoHeader>
                    <!-- todoinput์—์„œ ๋ณด๋‚ธ event๋ฅผ ๋ฐ›๊ณ , ๊ทธ๊ฑธ ๋ฐ›์œผ๋ฉด์„œ ์‹คํ–‰ํ•  ๋ฉ”์†Œ๋“œ๋Š” addOneItem-->
    <TodoInput v-on:addTodoItem="addOneItem"></TodoInput>
                    <!--๋‚ด๋ ค๋ณด๋‚ผ ์ปดํฌ๋„ŒํŠธ์— ๋ถ€์—ฌํ•  ์ด๋ฆ„(๋‚ด๊ฐ€ ์ง€์ •) : ํ˜„์žฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ถ™์—ฌ์ง„ ๋ฐ์ดํ„ฐ ์†์„ฑ ์ด๋ฆ„(์—ฌ๊ธฐ์„  ๋ฐฐ์—ด ์ด๋ฆ„)-->
                    <!--์•”๋ฌต์ ์œผ๋กœ ์ธ์ž๊นŒ์ง€ ๊ฐ™์ด ๋„˜์–ด์˜ด-->
    <TodoList v-bind:propsdata="todoItems" 
      v-on:removeItem="removeOneItem" 
      v-on:toggleItem="toggleOneItem">
    </TodoList>
    <TodoFooter v-on:clearAll="clearAllItems"></TodoFooter>
    <my_cmp></my_cmp>
  </div>
</template>

<script>
import Vue from "vue"
import TodoHeader from './components/TodoHeader.vue'
import TodoInput from './components/TodoInput.vue'
import TodoList from './components/TodoList.vue'
import TodoFooter from './components/TodoFooter.vue'

var my_cmp = {
  template : '<div>made by euna</div>'
}

new Vue(
  {
    el:'#app',
    components : {
      'my-cmp': my_cmp
    }
  }

)
export default {
  data : function() {
    return {
      todoItems : []
    }
  },
  methods : {
                        //์•„๋ž˜์—์„œ ๋ณด๋‚ธ ์ธ์ž  this.$emit('addTodoItem', this.newTodoItem);์˜ this.newTodoItem
    addOneItem : function(todoItem) {
      var obj = {complete: false, item: todoItem}; //text๊ฐ’๊ณผ boolean๊ฐ’์„ ์ €์žฅํ•  ๊ฐ์ฒด ์ƒ์„ฑ
      localStorage.setItem(todoItem, JSON.stringify(obj));
      this.todoItems.push(obj);
    },
    removeOneItem : function(todoItem, index) {
      localStorage.removeItem(todoItem.item);
      this.todoItems.splice(index, 1);
    },
    toggleOneItem : function(todoItem, index) {
      this.todoItems[index].completed = !this.todoItems[index].completed;
      localStorage.removeItem(todoItem.item);
      localStorage.setItem(todoItem.item, JSON.stringify(todoItem));
    },
    clearAllItems : function() {
      localStorage.clear();
      this.todoItems = [];
    }
  },
    //created:์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒ์„ฑ๋˜์ž๋งˆ์ž ์‹คํ–‰๋˜๋Š” lifecycle
  created : function() {
    //console.log('created');
    if(localStorage.length > 0) {
      for (var i=0; i<localStorage.length; i++) {
        if(localStorage.key(i) !== 'loglevel:webpack-dev-server')
        {
          //localStorage.getItem(localStorage.key(i)); //todoinput์—์„œ stringifyํ•œ ๊ฐ’์ด๋ฏ€๋กœ string type
          console.log(JSON.parse(localStorage.getItem(localStorage.key(i))));//stringํ™” ํ•œ ๊ฒƒ์„ ๋‹ค์‹œ json parsingํ•˜์—ฌ obj๋กœ ๋Œ๋ฆผ
          this.todoItems.push(JSON.parse(localStorage.getItem(localStorage.key(i))));
          //์œ„์—์„œ ์ƒ์„ฑํ•œ ๋ฐฐ์—ด์— ๋ฐ์ดํ„ฐ๋ฅผ push
          //this.todoItems.push();
        }
      }
    }
  },
  components : {
    'TodoHeader':TodoHeader,
    'TodoInput':TodoInput,
    'TodoList':TodoList,
    'TodoFooter':TodoFooter,
    'my_cmp':my_cmp
  }
}
</script>

<style>
body {
  text-align: center;
  background-color: #F6F6F8;
}
input {
  border-style: groove;
  width: 200px;
}
button {
  border-style: groove;
}
.shadow {
  box-shadow: 5px 10px 10px rgba(0, 0, 0, 0.03)
}
</style>