Создание списка задач с Vue.js

March 2017

Разработка: Создание списка задач с Vue.js

Се­год­ня мы со­зда­дим при­ло­же­ние — спи­сок за­дач с Vue.​js, а та­к­же рас­смот­рим и дру­гие удоб­ные ин­стру­мен­ты для со­зда­ния со­вре­мен­ных веб-при­ло­же­ний.

Убе­ди­тесь, что у вас уста­нов­ле­на Vue CLI! Ес­ли нет, то уста­но­ви­те её ко­ман­дой

$ npm install --global vue-cli

С Vue CLI идут несколь­ко шаб­ло­нов го­то­вых при­ло­же­ний:

webpack - Полнофункциональная установка Webpack + Vue-loader с hot reload, linting, testing & CSS extraction.

webpack-simple - Упрощённая установка Webpack + Vue-loader.

browserify - Полнофункциональная установка Browserify + vueify с hot-reload, linting & unit testing.

browserify-simple - Упрощённая установка Browserify + vueify.

simple - Простенькая установка Vue всего в одном файле HTML

Со­зда­дим при­ло­же­ние:

$ vue init webpack todo-app

Вас спро­сят на­зва­ние для про­ек­та, опи­са­ние, ав­то­ра и сбор­ку Vue. Мы бу­дем ра­бо­тать не уста­нав­ли­вая Vue-router.

Уста­но­вим за­ви­си­мо­сти:

$ cd my-project
$ npm install

Для за­пус­ка при­ло­же­ния вы­пол­ни­те ко­ман­ду:

$ npm run dev

От­кро­ет­ся бра­у­зер на стра­ни­це localhost:8080

Разработка: Создание списка задач с Vue.js

Для сти­ли­за­ции при­ло­же­ния бу­дем ис­поль­зо­вать Semantic.

Струк­ту­ра ком­по­нен­та

Каж­дое при­ло­же­ние Vue име­ет ком­по­нент верх­не­го уров­ня, у нас та­ким ком­по­нен­том бу­дет TodoList.

У нас уже есть го­то­вый ком­по­нент Hello (сге­не­ри­ро­ван­ный Vue CLI) в пап­ке src/App.​vue, оста­лось со­здать недо­ста­ю­щие ком­по­нен­ты ниж­не­го уров­ня.
Со­зда­дим про­стень­кий ком­по­нент TodoList.vue:

<template>
  <div>
    <ul>
        <li> Todo A </li>
        <li> Todo B </li>
        <li> Todo C </li>
    </ul>
  </div>
</template>

<script type = "text/javascript" >

export default {
};
</script>
<style>
</style>

Это обыч­ная за­го­тов­ка, ко­то­рую мы за­пол­ним позд­нее.

Им­пор­ти­ру­ем наш ком­по­нент в глав­ный эк­зем­пляр Vue, что­бы мож­но бы­ло с ним ра­бо­тать. От­крой­те файл src/App.​vue и от­ре­дак­ти­руй­те его:

<script>
import TodoList from './components/TodoList';

export default {
  components: {
    // добавим ссылку на компонент TodoList
    TodoList,
  },
};
</script>

Для об­ра­бот­ки ком­по­нен­та необ­хо­ди­мо его вклю­чить в HTML:

<template>
  <div>
    // Обработка компонента TodoList
    // он будет здесь:
    <todo-list></todo-list>
  </div>
</template>

Те­перь на­ше при­ло­же­ние вы­гля­дит так:

Разработка: Создание списка задач с Vue.js

Мы долж­ны пе­ре­да­вать дан­ные глав­но­му ком­по­нен­ту для вы­во­да спис­ка за­дач. За­да­чи бу­дут иметь три свой­ства: На­зва­ние, Про­ект и Вы­пол­не­но.

До­ба­вим дан­ных в наш ком­по­нент:

export default {
  name: 'app',
  components: {
    TodoList,
  },
  // data function avails data to the template
  data() {
    return {
      todos: [{
        title: 'Todo A',
        project: 'Project A',
        done: false,
      }, {
        title: 'Todo B',
        project: 'Project B',
        done: true,
      }, {
        title: 'Todo C',
        project: 'Project C',
        done: false,
      }, {
        title: 'Todo D',
        project: 'Project D',
        done: false,
      }],
    };
  },
};

Нам нуж­но пе­ре­дать дан­ные из глав­но­го ком­по­нен­та в TodoList и для это­го мы бу­дем ис­поль­зо­вать ди­рек­ти­ву v-bind. Она при­ни­ма­ет ар­гу­мент (он от­де­ля­ет­ся двое­то­чи­ем от име­ни ди­рек­ти­вы), в на­шем слу­чае это бу­дет todos; это по­ка­зы­ва­ет ди­рек­ти­ве v-bind свя­зать эле­мент todos с тем, что идёт в нём.

<todo-list v-bind:todos="todos"></todo-list>

Те­перь нуж­но до­ра­бо­тать ком­по­нент TodoList для до­сту­па к этим дан­ным. До­ба­вим свой­ство к клас­су ком­по­нен­та:

export default {
    props: ['todos'],
}
Об­ра­бот­ка дан­ных

Прой­дём­ся в цик­ле по спис­ку за­дач в шаб­лоне TodoList и вы­ве­дем ко­ли­че­ство за­вер­шён­ных и неза­вер­шён­ных за­дач:

<template>
  <div>
    // JavaScript expressions in Vue are enclosed in double curly brackets.
    <p>Completed Tasks: {{todos.filter(todo => {return todo.done === true}).length}}</p>
    <p>Pending Tasks: {{todos.filter(todo => {return todo.done === false}).length}}</p>
    <div class='ui centered card' v-for="todo in todos">
      <div class='content'>
        <div class='header'>
          {{ todo.title }}
        </div>
        <div class='meta'>
          {{ todo.project }}
        </div>
        <div class='extra content'>
          <span class='right floated edit icon'>
            <i class='edit icon'></i>
          </span>
        </div>
      </div>
      <div class='ui bottom attached green basic button' v-show="todo.done">
        Completed
      </div>
      <div class='ui bottom attached red basic button' v-show="!todo.done">
        Complete
      </div>
  </div>
</template>

<script type = "text/javascript" >

export default {
  props: ['todos'],
};
</script>
Ре­дак­ти­ро­ва­ние за­дач

Раз­де­лим шаб­лон за­да­чи для улуч­ше­ния чи­та­бель­но­сти ко­да. Со­здай­те но­вый ком­по­нент Todo.vue в пап­ке src/components и пе­ре­не­си­те в него шаб­лон за­да­чи:

<template>
  <div class='ui centered card'>
    <div class='content'>
        <div class='header'>
            {{ todo.title }}
        </div>
        <div class='meta'>
            {{ todo.project }}
        </div>
        <div class='extra content'>
            <span class='right floated edit icon'>
            <i class='edit icon'></i>
          </span>
        </div>
    </div>
    <div class='ui bottom attached green basic button' v-show="todo.done">
        Completed
    </div>
    <div class='ui bottom attached red basic button' v-show="!todo.done">
        Complete
    </div>
</div>
</template>

<script type="text/javascript">
  export default {
    props: ['todo'],
  };
</script>

Пе­ре­ра­бо­та­ем ком­по­нент TodoList для ра­бо­ты с ком­по­нен­том Todo:

<template>
  <div>
    <p>Completed Tasks: {{todos.filter(todo => {return todo.done === true}).length}}</p>
    <p>Pending Tasks: {{todos.filter(todo => {return todo.done === false}).length}}</p>
   // we are now passing the data to the todo component to render the todo list
    <todo  v-for="todo in todos" :todo.sync="todo"></todo>
  </div>
</template>

<script type = "text/javascript" >

import Todo from './Todo';

export default {
  props: ['todos'],
  components: {
    Todo,
  },
};
</script>

До­ба­вим свой­ство isEditing в класс ком­по­нен­та Todo, это свой­ство по­ка­жет ре­дак­ти­ру­ет­ся ли в дан­ный мо­мент за­да­ча или нет. По­ве­сим об­ра­бот­чик со­бы­тия на эле­мент Edit и бу­дем по­ка­зы­вать фор­му ре­дак­ти­ро­ва­ния, при этом из­ме­ним зна­че­ние свой­ства isEditing в true. Оста­лось до­ба­вить фор­му и уста­но­вить на­чаль­ное зна­че­ние свой­ства. Наш шаб­лон те­перь вы­гля­дит так:

<template>
  <div class='ui centered card'>
    // Todo shown when we are not in editing mode.
    <div class="content" v-show="!isEditing">
      <div class='header'>
          {{ todo.title }}
      </div>
      <div class='meta'>
          {{ todo.project }}
      </div>
      <div class='extra content'>
          <span class='right floated edit icon' v-on:click="showForm">
          <i class='edit icon'></i>
        </span>
      </div>
    </div>
    // form is visible when we are in editing mode
    <div class="content" v-show="isEditing">
      <div class='ui form'>
        <div class='field'>
          <label>Title</label>
          <input type='text' v-model="todo.title" >
        </div>
        <div class='field'>
          <label>Project</label>
          <input type='text' v-model="todo.project" >
        </div>
        <div class='ui two button attached buttons'>
          <button class='ui basic blue button' v-on:click="hideForm">
            Close X
          </button>
        </div>
      </div>
    </div>
    <div class='ui bottom attached green basic button' v-show="!isEditing &&todo.done" disabled>
        Completed
    </div>
    <div class='ui bottom attached red basic button' v-show="!isEditing && !todo.done">
        Pending
    </div>
  </div>
</template>
</template>

У нас есть ме­тод showForm, от­кры­ва­ю­щий фор­му прав­ки, те­перь до­ба­вим ме­тод hideForm для её за­кры­тия при на­жа­тии на кноп­ку от­ме­ны:

<script>
export default {
  props: ['todo'],
  data() {
    return {
      isEditing: false,
    };
  },
  methods: {
    showForm() {
      this.isEditing = true;
    },
    hideForm() {
      this.isEditing = false;
    },
  },
};
</script>

Дан­ные в фор­ме уже при­вя­за­ны к спис­ку за­дач и их ре­дак­ти­ро­ва­ние ав­то­ма­ти­че­ски со­хра­ня­ет об­нов­лён­ные дан­ные. По на­жа­тию кноп­ки Close уви­дим об­нов­лён­ную за­да­чу:

Разработка: Создание списка задач с Vue.js

Уда­ле­ние за­да­чи

До­ба­вим икон­ку уда­ле­ния за­да­чи:

<template>
    <span class='right floated edit icon' v-on:click="showForm">
      <i class='edit icon'></i>
    </span>
    /* add the trash icon in below the edit icon in the template */
    <span class='right floated trash icon' v-on:click="deleteTodo(todo)">
      <i class='trash icon'></i>
    </span>
</template>

До­ба­вим ме­тод в класс ком­по­нен­та, вы­зы­ва­е­мый по со­бы­тию delete-todo, и пе­ре­да­ю­щий ком­по­нен­ту те­ку­щую за­да­чу к уда­ле­нию.

<span class='right floated trash icon' v-on:click="deleteTodo(todo)">
// Todo component
methods: {
    deleteTodo(todo) {
      this.$emit('delete-todo', todo);
    },
  },

Те­перь нуж­но до­ба­вить об­ра­бот­чик со­бы­тия уда­ле­ния в ро­ди­тель­ский ком­по­нент (TodoList):

// TodoList component
methods: {
    deleteTodo(todo) {
      const todoIndex = this.todos.indexOf(todo);
      this.todos.splice(todoIndex, 1);
    },
  },

Пе­ре­да­ём ме­тод deleteTodo в ком­по­нент Todo:

// шаблон TodoList
<todo  v-on:remove-todo="deleteTodo" v-for="todo in todos" :todo.sync="todo"></todo>
До­бав­ле­ние но­вой за­да­чи

Для со­зда­ния но­вой за­да­чи сде­ла­ем но­вый ком­по­нент CreateTodo в пап­ке src/components. Он вы­ве­дет кноп­ку со зна­ком плюс, она при на­жа­тии от­кро­ет фор­му до­бав­ле­ния за­да­чи:

<template>
  <div class='ui basic content center aligned segment'>
    <button class='ui basic button icon' v-on:click="openForm" v-show="!isCreating">
      <i class='plus icon'></i>
    </button>
    <div class='ui centered card' v-show="isCreating">
      <div class='content'>
        <div class='ui form'>
          <div class='field'>
            <label>Title</label>
            <input v-model="titleText" type='text' ref='title' defaultValue="">
          </div>
          <div class='field'>
            <label>Project</label>
            <input type='text' ref='project' defaultValue="">
          </div>
          <div class='ui two button attached buttons'>
            <button class='ui basic blue button' v-on:click="sendForm()">
              Create
            </button>
            <button class='ui basic red button' v-on:click="closeForm">
              Cancel
            </button>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      titleText: '',
      projectText: '',
      isCreating: false,
    };
  },
  methods: {
    openForm() {
      this.isCreating = true;
    },
    closeForm() {
      this.isCreating = false;
    },
    sendForm() {
      if (this.titleText.length > 0 && this.projectText.length > 0) {
        const title = this.titleText;
        const project = this.projectText;
        this.$emit('create-todo', {
          title,
          project,
          done: false,
        });
        this.newTodoText = '';
      }
      this.isCreating = false;
    },
  },
};
</script>

До­ба­вим но­вый ком­по­нент в глав­ный ком­по­нент:

// главный компонент в App.vue
components: {
    TodoList,
    CreateTodo,
  },

А та­к­же до­ба­вим ме­тод для со­зда­ния но­вой за­да­чи:

// в App.vue
methods: {
    addTodo(title) {
      this.todos.push({
        title,
        done: false,
      });
    },
  },

Вы­зов ком­по­нен­та CreateTodo из шаб­ло­на App.​vue:

<create-form v-on:add-todo="addTodo">

Разработка: Создание списка задач с Vue.js

Вы­пол­не­ние за­да­чи

Оста­лось до­ба­вить ме­тод для уста­нов­ки от­мет­ки о вы­пол­не­нии за­да­чи.

// компонент Todo
methods: {
      completeTodo(todo) {
        this.$emit('complete-todo', todo);
      },
}

Об­ра­бот­чик со­бы­тия бу­дет до­бав­лен в TodoList:

methods: {
    completeTodo(todo) {
      const todoIndex = this.todos.indexOf(todo);
      this.todos[todoIndex].done = true;
    },
  },

И до­ба­вим вы­зов ме­то­да в шаб­лоне:

<todo v-on:delete-todo="deleteTodo" v-on:complete-todo="completeTodo" v-for="todo in todos" :todo.sync="todo"></todo>

Го­то­во! Ни­же по ссыл­кам мож­но уви­деть пол­ный код при­ло­же­ния или по­щу­пать де­мон­стра­ци­он­ную вер­сию.

Пол­ный код при­ме­ра. Де­мо-вер­сия.

По ма­те­ри­а­лам «Build a To-Do App with Vue.​js 2» by Jeremy Kithome

comments powered by Disqus