프로그래밍/Vue.js

[Vue.js] 간단한 todo app 구현

dev109 2018. 2. 22.

목차

    반응형


    [Vue.js] 간단한 todo app 구현




    프로젝트 폴더 구조


    [Vue.js] 간단한 todo app 구현


    기능

    1. 입력폼에 데이터 입력 후 엔터 혹은 add 버튼 클릭 시 데이터 추가

    2. 탭 [todo, finish] 클릭 시 해당 목록 출력

    3. todo 목록에서 완료버튼 클릭 시 finish로 이동

    4. finish 목록에서 reset버튼 클릭 시 todo로 이동





    vue-cli 설치(전역)

    sudo npm install vue-cli



    'todo-app' 폴더 생성 후 webpack-simple 템플릿으로 프로젝트 생성

    mkdir todo-app

    cd todo-app

    vue init webpack-simple



    전부 enter(기본값)

    npm install

    npm run dev






    프로젝트 생성 후 폴더 구조

    [Vue.js] 간단한 todo app 구현









    todo-app/style.css(생성 후 작성)


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    body, ul {
      margin: 0;
      padding: 0;
    }
    ul {
      list-style: none;
    }
    img {
      width: 100%;
    }
    .container {
      margin: 0 15px 0 15px;
    }
    header {
      border-bottom: 1px #ccc solid;
      padding: 10px 0  10px 0;
      text-align: center;
    }
    input[type=text] {
      display: block;
      box-sizing: border-box;
      width: 100%;
      margin: 15px 0 15px 0;
      padding: 10px 15px;
      font-size: 14px;
      line-height: 1.5;
      border: 1px solid #cccccc;
    }
    .content {
      border: 1px solid #ccc;
    }
    ul.tabs {
      display: flex;
    }
    .tabs li {
      display: inline-block;
      width: 50%;
      padding: 15px;
      text-align: center;
      box-sizing: border-box;
      border-bottom: 1px solid #ccc;
      background-color: #eee;
      color: #999;
    }
    .tabs li.active {
      /* background-color: #2ac1bc; */
      background-color: #045FB4;
      color: #fff;
    }
    .list li {
      box-sizing: border-box;
      display: block;
      padding: 15px;
      border-bottom: 1px solid #ccc;
      position: relative;
    }
    .list li:last-child {
      border-bottom: none;
    }
    .list li .number{
      margin-right: 15px;
      color: #ccc;
    }
    .list li .date{
      position: absolute;
      right: 50px;
      top: 15px;
      margin-right: 15px;
      color: #ccc;
    }
    .list li .btn-remove{
      position: absolute;
      right: 0px;
      top: 15px;
      margin-right: 15px;
    }
    form {
      position: relative;
    }
    .btn-reset,
    .btn-remove {
      border-radius: 15%;
      background-color: #ccc;
      color: white;
      border: none;
      padding: 2px 5px;
    }
    .btn-reset {
      position: absolute;
      top: 12px;
      right: 10px;
    }
    .btn-reset::before,
    .btn-remove::before {
      content: 'add'
    }
    #search-result li {
      display: flex;
      margin-bottom: 15px;
    }
    #search-result img {
      width: 30%;
      height: 30%;
    }
    .todoBtn {
      float: right;
      margin: 2px;
    }
     
    cs






    todo-app/index.html 수정


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>todo-app</title>
        <link rel="stylesheet" href="style.css">
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
      </head>
      <body>
        <div id="app"></div>
        <script src="/dist/build.js"></script>
      </body>
    </html>
     
    cs






    src/components/FormComponent.vue


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <template>
      <form v-on:enter.prevent="addTodo">
        <input type="text" v-model="inputValue" />
        <button class="btn-reset" v-on:click.prevent="addTodo"></button>
        <!-- v-on:click => click 이벤트 리슨 (.prevent 속성을 추가하면 이벤트 전파를 중단함) -->
      </form>
    </template>
     
    <script>
    export default {
      props: ['value'], //부모(App.vue)로부터 받아옴
      data() {
        return {
          inputValue: this.value
        }
      },
      methods: {
        addTodo() {
          //$emit => 부모(App.vue)에게 @submit이라는 이름으로 이벤트 전달
          //this.inputValue 인자로 같이 전달
          this.$emit('@submit', this.inputValue)
     
          //데이터 추가 후 form을 비우기 위해 inputValue값을 비워줌
          this.inputValue = ''
        }
      }
    }
    </script>
     
    cs






    src/components/TabComponent.vue


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <template>
      <ul class="tabs">
        <li v-for="tab in tabs" v-bind:class="{active: tab === selectedTab}"
          v-on:click="onClickTabs(tab)">
          <!-- v-bind:class => 선택한 탭에 active class를 지정 -->
          {{tab}}
        </li>
      </ul>
    </template>
     
    <script>
    export default {
      props: ['tabs''selectedTab'], //부모(App.vue)로부터 받아옴 => tabs(todo, finish), selectedTab : 현재 선택한 탭
      methods: {
        onClickTabs(tab) {
          //$emit => 부모(App.vue)에게 @change 이름으로 이벤트 전달
          //tab(선택한 탭) 인자로 같이 전달
          this.$emit('@change', tab)
        }
      }
    }
    </script>
     
    cs






    src/components/ListComponent.vue


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    <template>
      <div>
        <ul class="list">
          <li v-for="(item, index) in data">
            <span class="glyphicon glyphicon-asterisk"></span>
            {{item.todo}}
            <span class="todoBtn label label-primary"
              v-if="selectedTab === 'todo'"
              v-on:click="finishBtnClick(index)">
              <span class="glyphicon glyphicon-ok"></span>
            </span>
            <span class="todoBtn label label-danger"
              v-if="selectedTab === 'finish'"
              v-on:click="resetBtnClick(index)">reset</span>
          </li>
        </ul>
      </div>
     
     
    </template>
     
    <script>
    export default {
      props: ['data''selectedTab'],
      methods: {
        finishBtnClick(item) {
          this.$emit('@finish', item)
        },
        resetBtnClick(item) {
          this.$emit('@reset', item)
        }
      }
    }
    </script>
     
    cs







    src/models/TodoModel.js


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    export default {
      data: [
        {todo: '할 일 0', state: true},
        {todo: '할 일 1', state: true},
        {todo: '할 일 2', state: false},
        {todo: '할 일 3', state: true},
        {todo: '할 일 4', state: false}
      ],
     
      list(tab) {
        return new Promise(res => {
          if(tab === 'todo') res(this.data.filter(item => item.state === true))
          if(tab === 'finish') res(this.data.filter(item => item.state === false))
        })
      },
     
      add(todo = '') {
        todo = todo.trim()
        if (!todo) return
     
        const state = true
        this.data.push({todo, state})
      },
     
      finish(index) {
        this.data.filter(item => item.state === true)[index].state = false
      },
     
      reset(index) {
        this.data.filter(item => item.state === false)[index].state = true
      },
     
      remove(todo) {
        this.data = this.data.filter(item => item.todo !== todo)
      }
    }
     
    cs






    src/App.vue


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    <template>
      <div>
        <header>
          <h2 calss="container">Todo</h2>
        </header>
     
        <div calss="container">
          <add-form v-bind:value="query" v-on:@submit="onInputTodo"></add-form>
          <!-- FormComponent.vue에서 inputValue에 넣어준 value를 v-bind가 App.vue의 query와 바인딩해줌 -->
          <!-- v-on:@submit => FormComponent.vue에서 $emit으로 전달한 @submit이라는 이벤트를 받아와서 onInputTodo 메서드와 연결 -->
     
          <tab v-bind:tabs="tabs" v-bind:selected-tab="selectedTab" v-on:@change="onClickTab"></tab>
     
        </div>
     
        <div>
          <list v-bind:selected-tab="selectedTab" v-bind:data="todoList"
            v-on:@finish="onClickFinish"
            v-on:@reset="onClickReset"></list>
        </div>
     
      </div>
    </template>
     
    <script>
    //FormComponent 불러옴
    import FormComponent from './components/FormComponent.vue'
    //TabComponent 불러옴
    import TabComponent from './components/TabComponent.vue'
    //ListComponent 불러옴
    import ListComponent from './components/ListComponent.vue'
     
    //Model 불러옴
    import TodoModel from './models/TodoModel.js'
     
    export default {
      name'app',
      data () {
        return {
          query: '',
          tabs: ['todo''finish'],
          selectedTab: '',
          todoList: []
        }
      },
      created() { //vue 인스턴스가 생성된 후에 실행됨
        this.selectedTab = this.tabs[0//todo 탭 선택
        this.search() //todo list 출력
      },
      components: { //사용할 컴포넌트 등록
        'add-form': FormComponent,
        'tab': TabComponent,
        'list': ListComponent
      },
      methods: {
        search() { //list 검색
          TodoModel.list(this.selectedTab).then(data => {
            this.todoList = data
          })
        },
        onClickTab(tab) { //tab 선택
          this.selectedTab = tab
          this.search()
        },
        onClickFinish(item) { //todo 완료
          TodoModel.finish(item)
          this.search()
        },
        onClickReset(item) { //완료된 todo 리셋
          TodoModel.reset(item)
          this.search()
        },
        onInputTodo(query) { //todo 입력
          TodoModel.add(query)
          this.selectedTab = this.tabs[0]
          this.search()
        }
      }
    }
    </script>
     
    <style>
    </style>
     
    cs







    반응형

    '프로그래밍 > Vue.js' 카테고리의 다른 글

    Vue.js 기초  (0) 2017.12.26

    댓글