В этом руководстве я расскажу вам, как написать простое приложение для создания списков задач и управления ими. Мы будем использовать VueJS (v2.6.10) и Framework7 (v4.2.0), поэтому рекомендуется их базовое знание.

Для начала клонируйте ветку core моего репозитория с https://github.com/nataliasulkama/todo-tutorial в новую папку:

mkdir todo
cd todo
git clone -b core --single-branch https://github.com/nataliasulkama/todo-tutorial

Затем заходим во вновь созданную папку проекта, устанавливаем зависимости и запускаем сервер разработки:

cd todo-tutorial
npm install
npm start

Мы работаем с приложением с двумя вкладками и боковой панелью. Мы реализуем:

  1. Домашняя страница, на которой пользователь может отмечать и снимать отметки с задач, а также удалять выбранный список.
  2. Отдельная страница для создания списков дел
  3. Боковая панель на главной странице для переключения между созданными списками.

Откройте папку проекта в редакторе кода - все файлы, в которые мы будем вносить изменения, находятся в папке components (app.vue, home.vue, createlist.vue).

Примечание. Иногда при внесении изменений в app.vue Framework7 повторно инициализируется, и приложение перестает работать. Вы получите ошибку в консоли. Если это произойдет, просто обновите страницу.

Создание списка

Давайте сначала поработаем над созданием новых списков. Взгляните на createlist.vue. Для успешного чтения входных данных формы нам понадобятся три вещи:

  1. Список с идентификатором (мой - «list-form»)
  2. Список входов с атрибутами «имя» (в данном случае «имя списка» и «элемент списка»)
  3. Кнопка вне списка, запускающая метод получения входных данных.

Все они уже реализованы в виде:

<f7-block>
 <!-- List needs an id -->
  <f7-list no-hairlines-md id=”list-form”>
    <!-- List inputs need names -->
    <f7-list-input v-on:input=”validateForm” name=”listname” type=”text” placeholder=”List name” clear-button required validate> 
    </f7-list-input>
    <f7-list-input name=”listitem” type=”text” placeholder=”What needs to be done?”>
    </f7-list-input>
  </f7-list>
<!-- Button to get inputs is outside the list -->
<f7-row class=”button-row”>
    <f7-col>
      <f7-button style=”max-width: 150px;” fill @click=”createList” :disabled=”disabled == 1 ? true : false”>
        Create list
      </f7-button>
    </f7-col>
    <f7-col>
      <!-- Button to add multiple tasks to the list -->
      <f7-button style=”max-width: 120px;” raised @click=”addTask”>
        + Add task
      </f7-button>
    </f7-col>
  </f7-row>
</f7-block>

Во-первых, мы должны написать метод, который вызывается при нажатии кнопки «Создать список». Этот метод будет читать введенные пользователем данные и заполнять ими свойства данных createdList и listItems. Кроме того, мы хотим использовать свойство listid для добавления идентификатора в каждый созданный список. Все это уже создано в скрипте:

<script>
  export default {
    data() {
      return {
        listName: ‘’,
        listItems: [],
        listItem: {},
        createdList: {},
        lists: [],
        listid: 0,
        disabled: 1
      }
    },
   ...
  }
</script>

Наш метод будет выглядеть так:

ВАЖНО: компоненты VueJS Framework7 не поддерживают v-модель. Поэтому вместо этого нам нужно использовать пользовательскую библиотеку DOM Framework7, Dom7.

Здесь мы читаем входные данные формы с помощью this. $ F7.form и вызываем функцию convertToData для преобразования входных данных в читаемый формат. Затем мы сохраняем входные данные, сбрасываем форму, очищая поля (используя Dom7 для целевых входов, имеющих значение), и сбрасываем свойства данных. Мы поместим созданный список в массив списков, чтобы мы могли хранить их все.

Этот метод привязан к событию @click на кнопке:

<f7-button style=”max-width: 150px;” fill @click=”createList” :disabled=”disabled == 1 ? true : false”>
  Create list
</f7-button>

Свойство : disabled отключает кнопку условно в зависимости от того, равно ли значение свойства данных disabled 1 или 0. Это определяется в предварительно созданном validateForm метод.

...
methods: {
  validateForm: function() {
    let formData = this.$f7.form.convertToData(‘#list-form’);
    this.listName = formData.listname;
    if (formData.listname == ‘’) {
      this.disabled = 1;
    } else if (this.listItems.length == 0) {
      this.disabled = 1;
    } else {
      this.disabled = 0;
    }
  },
...

Кнопка будет отключена, если поле ввода имени списка пустое или если свойство listItems пусто (задачи не добавляются). Итак, теперь нам нужно создать метод addTask, который будет запускаться, когда мы нажимаем кнопку «Добавить задачу».

<f7-button style=”max-width: 120px;” raised @click=”addTask”>
  + Add task
</f7-button>

Итак, теперь, если вы напишете что-то во входных данных задачи и нажмете кнопку «добавить задачу», ваш ввод будет перенесен в массив listItems, и вы сможете создавать столько задач, сколько захотите.

Предварительный просмотр списка

Теперь, когда наш список работает, нам нужно действительно посмотреть, как он выглядит. Мы реализуем предварительный просмотр списка под формой и кнопками:

<!-- Render the list name -->
<h2 id=”list-name-preview”> {{ listName }} </h2>
<f7-list inset no-hairlines-md id=”tasks-preview”>
  <!-- Dynamically rendering the list items using v-for -->
  <f7-list-item id=”list-item-preview” v-for=”(listItem, index) in listItems”>
    {{ index+1 }}. {{ listItem.item }}
   
    <!-- Icon span with a @click to remove tasks -->
    <span @click=”removeTodo(index)”>
      <f7-icon f7=”close” size=”25px” color=”red”></f7-icon>
    </span>
  </f7-list-item>
</f7-list>

Мы используем v-for для динамической печати задач по мере их добавления. Предварительно созданный метод validateForm сохраняет введенное имя списка в свойстве listName, поэтому теперь предварительный просмотр обновляется при вводе.

validateForm: function() {
  let formData = this.$f7.form.convertToData(‘#list-form’);
  this.listName = formData.listname;
  ...
}

Чтобы удалить задачи в предварительном просмотре, у нас есть элемент span с методом removeTodo, который срабатывает при нажатии. Метод прост:

removeTodo(selectedItem) {
  this.listItems.splice(selectedItem, 1);
  // If the listItems array is empty, prevent list creation
  if (this.listItems.length <= 0) {
    this.disabled = 1;
  }
}

Поскольку мы динамически распечатываем элементы списка с помощью v-for, нам больше ничего делать не нужно.

Отправка списка в app.vue

Затем нам нужен способ автоматически отображать список на главной странице по мере его создания. На домашней странице мы хотим отмечать и снимать отметки с задач и удалять список.

Во-первых, нам нужно отправить созданный список в app.vue. Добавьте в конец метода createList файла createlist.vue:

...
createList: function() {
  let formData = this.$f7.form.convertToData(‘#list-form’);
  if (formData.listname != ‘’ && formData.listname != undefined && this.listItems.length != 0) {
    ...
    // Add the following:
    this.$emit(‘created’, this.createdList);
    this.$f7.tab.show(tab1, true);
  } else {
    return false;
  }
},
...

Это вызовет настраиваемое событие created и передаст свойство данных createdList родительскому элементу, которым в данном случае является app.vue. Затем он изменит вид на главную вкладку.

В app.vue нам нужно создать метод для чтения переданных данных. Мы должны привязать событие created к элементу create-list:

<f7-view main class=”safe-areas” url=”/”>
  <f7-page :page-content=”false”>
    ...
    <f7-tabs class=”tabs” swipeable>
      ...
      <f7-tab id=”tab2" class=”page-content”>
         <create-list @created=”getList”></create-list>
      </f7-tab>
    </f7-tabs>
  </f7-page>
</f7-view>

Это означает, что мы прослушиваем событие created, которое генерируется компонентом createlist.vue, и когда родительский элемент (app.vue) обнаруживает это событие, он запускает метод getList, который выглядит так: это:

methods: {
  // Here, the "list" parameter is the createdList data property that we passed from createlist.vue
  getList: function(list) {
    // Pass the parameter to a data property
    this.todoList = {
      id: list.id,
      name: list.name,
      items: list.items,
      checked: []
    };
    // Add the created list to an array of lists, so we can display it on the side panel
    this.lists.push(this.todoList);
  },
...
}

Помните: редактирование app.vue приведет к повторной инициализации Framework7, и он перестанет работать. Если это произойдет, обновите страницу.

После создания этого метода и добавления свойства данных todoList, если мы console.log this.todoList или this.lists, мы увидим, что объект был успешно отправлен, и мы теперь можно передать их обоих в home.vue в качестве свойств для использования позже.

<home-page :todoList=”todoList” :lists=”lists”></home-page>

Боковая панель

В app.vue отобразим созданные списки на боковой панели. В верхней части шаблона в элементе f7-panel создадим элемент списка:

Здесь мы отображаем количество имеющихся у нас списков, а затем динамически генерируем представление списка всех существующих списков. Мы также хотим быстро увидеть, сколько задач в списке и сколько из них отмечено как выполненное. При нажатии на элементы списка запускается метод selectList, который выглядит следующим образом:

methods: {
  getList: function(list) {
    ...
  },
  selectList: function(selection) {
    this.todoList = selection;
    this.$f7.panel.close();
  },
...
}

На данный момент ничего видимого не произойдет, поскольку метод сохраняет только выбранный список в свойстве данных todoList и закрывает боковую панель. Теперь мы перейдем к просмотру домашней страницы, чтобы реализовать основное представление и функции списка.

5. Отображение списка в home.vue, проверка задач

Поскольку мы уже передали свойства todoList и lists в app.vue, например:

<home-page :todoList=”todoList” :lists=”lists”></home-page>

Все, что нам нужно сделать в home.vue сейчас, это получить их:

<script>
  export default {
    data() {
      return {
        listSelected: false
      }
    },
    props: [‘todoList’, ‘lists’],
    ...
</script>

Теперь мы можем сразу использовать их для рендеринга нашего списка:

Здесь мы печатаем содержимое домашней страницы условно. Если списков нет, отобразится сообщение, а если существует один или несколько, они будут напечатаны с разделением отмеченных и непроверенных задач. Поскольку мы используем реквизиты на домашней странице, мы должны создавать из них вычисляемые свойства. Во Vue никогда не следует изменять свойства напрямую. Вместо этого мы можем изменить вычисленные свойства.

<script>
  export default {
    data() {
      ...
    },
    props: [‘todoList’, ‘lists’],
    computed: {
      activeList: function() {
        return this.todoList;
      },
      listArray: function() {
        return this.lists;
      },
      uncheckedTasks: function() {
        return this.activeList.items.filter(function(item) {
          return !item.done;
        })
      },
      checkedTasks: function() {
        return this.activeList.items.filter(function(item) {
          return item.done;
      })
    }
  },
...
}
</script>

Теперь мы можем начать писать методы для изменения нашего списка, не касаясь напрямую свойств. Однако сначала давайте установим наблюдателя в свойство todoList. Это означает, что мы можем вызывать функцию всякий раз, когда изменяется значение свойства.

computed: {
...
},
watch: {
  todoList: function(newList, oldList) {
    if (Object.keys(newList).length != 0 && newList.constructor === Object) {
      this.listSelected = true;
    }
  }
},
...

В этой функции мы проверяем, является ли новое значение свойства пустым, поэтому, когда список выбран, this.listSelected изменится на true, и список будет отображается на вкладке домашней страницы.

Теперь нам потребуются методы проверки задач и удаления отображаемого списка:

...
methods: {
  checkTask: function(task) {
    task.done = !task.done;
    this.$emit(‘checked’, this.checkedTasks);
  },
  deleteList: function(list) {
    // Find the index of the list to delete (so we do not delete lists with the same name, for example)
    let listToDelete = this.listArray.indexOf(list);
    this.listArray.splice(listToDelete, 1);
    // Go back to default view ("no list is selected")
    this.listSelected = false;
  }
}

При проверке задач мы генерируем настраиваемое событие checked, поэтому теперь в app.vue давайте его послушаем:

<f7-tabs class=”tabs” swipeable>
  <f7-tab id=”tab1" class=”page-content” tab-active>
    <home-page :todoList=”todoList” :lists=”lists” @checked=”checkedTasks”></home-page>
  </f7-tab>
  ...
</f7-tabs>

И последнее, что нам нужно сделать, это добавить метод checkedTasks:

methods: {
  getList: function(list) {
    ...
  },
  selectList: function(selection) {
    ...
  },
  checkedTasks: function(list) {
    this.todoList.checked = list;
  }
}

И с этим - мы закончили. Готовый код вы можете найти в моем репозитории: https://github.com/nataliasulkama/todo-tutorial.

Спасибо за чтение! 🌸