Это вторая часть многостраничного руководства, в котором мы создаем 90-дневный планировщик действий с использованием Laravel и Vue.js.

Исходный код части 2 можно найти здесь.

Что мы будем освещать?

Продолжая с того места, на котором мы остановились в части 1, в этом посте мы:

  1. добавить эффекты перехода при пошаговом переходе; и
  2. позволяют пользователю добавлять или удалять дополнительные поля показателей и действий.

Давайте запустим наше приложение, запустив yarn watch или npm run watch, если вы используете npm из консоли.

Теперь, прежде чем мы перейдем к добавлению эффектов перехода, я хотел бы, чтобы мы быстро рассмотрели две небольшие вещи.

  1. Название приложения.

Если вы помните, внутри index.blade.php мы устанавливаем заголовок на имя приложения. Вроде так <title>{{ config('app.name') }}</title>. Теперь по умолчанию это «Laravel». Чтобы изменить это, нам нужно открыть файл среды /.env и вверху изменить APP_NAME=Laravel на APP_NAME="90 Day Planner". Если вы забудете кавычки, будет выдана внутренняя ошибка сервера 500.

Также можно изменить значение по умолчанию на тот случай, если кто-то забудет обновить APP_NAME. Давайте откроем ./config/app.php и изменим значение по умолчанию с «Laravel» на «90 Day Planner».

'name' => env('APP_NAME', '90 Day Planner'),

2. Добавьте кнопку «Домой» в многошаговую форму.

Прямо сейчас у пользователя нет возможности вернуться на домашнюю страницу, не нажав несколько раз стрелку назад в браузере. Думаю, мы можем согласиться с тем, что это не лучший пользовательский опыт (UX). Чтобы исправить это, давайте откройте ./resources/assets/js/pages/Create.vue и добавьте новую ссылку на маршрутизатор над <router-view> следующим образом:

<template>
  <div>
    <step-bar :steps="steps"></step-bar>

    <div class="row align-center">
      <div class="column large-8">
        <router-link class="mb-5 block" 
                     :to="{ name: 'home' }"
        >
         <i class="fa fa-home"></i> Home
        </router-link>
        <router-view></router-view>
      </div>
    </div>
  </div>
</template>

Заметили, что мы представили новый вспомогательный класс .block. Все это приводит к блокировке отображения CSS. Мы используем это, потому что добавление нижнего поля без отображения, установленного на блок, не имеет никакого эффекта. Мы можем добавить этот новый вспомогательный класс в нижнюю часть нашего файла ./resources/assets/sass/_helpers.scss:

.block { display: block };

Добавление эффекта перехода

Чтобы добавить наш эффект перехода, мы воспользуемся Motion UI и утилитой JavaScript, которую Foundation предоставляет для этого.

Что такое Motion UI?

«Motion UI - это библиотека Sass для создания переходов и анимации CSS…»

- "Источник"

Шаг 1 - Импорт Motion UI Sass

Сначала мы откроем ./resources/assets/sass/app.scss, импортируем интерфейс движения, а затем включим все его переходы:

// Fonts
@import url('https://fonts.googleapis.com/css?family=Rubik');

// Settings
@import 'settings';

// Import Foundation and Font Awesome
@import "node_modules/foundation-sites/scss/foundation";
@import "node_modules/font-awesome/scss/font-awesome";

// Import Motion UI
@import "node_modules/motion-ui/src/motion-ui";

// Include everything from foundation and
// use flex-grid for our grid system.
@include foundation-everything($flex: true);

// Include all Motion UI transitions.
@include motion-ui-transitions;

// Components
@import "components/callout";

// Helpers
@import "helpers";

Шаг 2 - Инициализируйте фундамент

Чтобы использовать утилиту Foundations JavaScript для пользовательского интерфейса движения, нам сначала нужно инициализировать Foundation.

Есть несколько мест, где мы могли бы инициализировать Foundation. Можно просто создать новый тег <script> после того, как мы включим app.js в файл index.blade.php, например:

<!-- Compressed JavaScript -->
<script src="{{ mix('js/app.js') }}"></script>
<script>
  $(document).foundation();
</script>

Альтернативный подход - инициализировать Foundation внутри app.js сразу после создания нашего экземпляра Vue, например:

const app = new Vue({
  router,
  store,
  created() {
    $(document).foundation();
  },
  el: '#app'
});

Оба подхода допустимы и должны работать нормально. Однако я думаю, что лучшим местом для инициализации Foundation будет наш файл bootstrap.js сразу после того, как он нам понадобится. Итак, давайте откроем ./resources/assets/js/bootstrap.js и после того, как нам потребуется Foundation, мы продолжим его инициализацию.

try {
  window.$ = window.jQuery = require('jquery');
  require('foundation-sites');
  $(document).foundation();
} catch (e) {}

Шаг 3 - Добавьте эффекты перехода к каждому шагу

Для наших эффектов перехода мы собираемся сделать так, чтобы выноска каждого шага div сдвигалась слева и сдвигалась вправо при переходе к следующему шагу.

Давайте откроем ./resources/assets/js/pages/Goal.vue в тегах <script>, позволяющих добавить наши эффекты перехода.

<script>
  export default {
    mounted() {
      Foundation.Motion.animateIn(
        this.$el, 
        'slide-in-left fast'
      );
    },

    methods: {
      next() {
        Foundation.Motion.animateOut(
          this.$el, 
          'slide-out-right fast', 
          () => {
            this.$store.dispatch('goToNextStep')
          }
        );
      }
    }
  }
</script>

Что здесь происходит?

  • Foundation предоставляет два служебных метода Foundation.Motion.animateIn() для переходов, которые входят в окно, и Foundation.Motion.animateOut() для переходов, которые существуют в окне. Оба метода принимают одинаковое количество параметров в одном и том же порядке. Первый параметр - это элемент, к которому мы хотим добавить переход, второй - встроенный переход, который мы хотим применить, и в-третьих, необязательный обратный вызов, который запускается только после завершения перехода.
  • $el - это свойство экземпляра нашего компонента, которое ссылается на корневой элемент DOM. В данном случае это наша выноска div.

Примечание. Мы также можем использовать jQuery для ссылки на наше уточнение div. Это можно сделать, присвоив нашему выноске div идентификатор / класс, а затем сославшись на этот идентификатор / класс. Например, мы можем указать выноску div и идентификатор «цели»:
<div id="goal" class="secondary callout shadow" ref="main">

затем внутри метода перехода мы ссылаемся на идентификатор, используя jQuery $('#goal'), например:
Foundation.Motion.animateIn($('#goal'), 'slide-in-left fast');

  • После того, как компонент смонтирован, шаблон отображается, и у нас есть доступ ко всем элементам DOM. На этом этапе мы говорим Foundation, что хотим, чтобы наша выноска div быстро скользила слева.
  • Затем мы изменили наш next() метод, чтобы сказать, когда нажимается следующая кнопка, быстро сдвиньте выноску div вправо. После завершения перехода мы переходим к следующему шагу.

Вот полная страница Goal.vue после только что внесенных нами изменений:

Давайте продолжим и добавим переходы в./resources/assets/js/pages/Metrics.vue.

Наконец, для ./resources/assets/js/pages/Actions.vue мы просто добавим входящий переход.

Примечание. Также можно использовать атрибут ref Vues. В случаях, когда вы хотите сослаться на элемент, который не является корневым элементом.

Возможность добавлять / удалять метрики и действия.

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

Шаг 1 - Создайте возможность добавлять / удалять метрики.

Для начала давайте откроем файл ./resources/assets/js/pages/Metrics.vue. Внутри наших <script> тегов мы будем определять массив показателей и добавим два новых метода add() и remove().

<script>
  export default {
    data() {
      return {
        metrics: [{ name: '' }]
      }
    },
    ...

    methods: {
      ...
      add() {
        this.metrics.push({ name: '' });
      },

      remove(index) {
        if (this.metrics.length > 1) {
          this.metrics.splice(index, 1)
        }
      }
    }
  }
</script>

Что здесь происходит?

  • metrics: [{ name: '' }]: мы определяем массив показателей внутри нашего объекта данных. В этом массиве у нас есть единственный объект метрики со свойством name, которое по умолчанию равно пустой строке.
  • add(): при срабатывании триггера мы помещаем новый объект метрики в массив метрик.
  • remove(): мы всегда хотим иметь хотя бы одно поле ввода метрики. Таким образом, мы удаляем метрику только в том случае, если в нашем массиве метрик их несколько. Чтобы фактически удалить метрику из массива, мы используем функцию splice(). Первый параметр, который принимает splice(), - это начальная точка, в которой он удаляет элемент из массива. В нашем случае мы используем индекс метрики. Второй параметр - это количество элементов, которые мы хотим удалить. Поскольку мы хотим удалить только саму метрику, мы устанавливаем ее равной единице.

Примечание. Есть некоторые предостережения, когда Vue обнаруживает изменения в массивах. Это описано в документации здесь.

Внутри тегов <template> давайте заменим форму следующей:

<form>
  <div class="input-group" v-for="(metric, index) in metrics">
    <span class="input-group-label">
      <i class="fa fa-line-chart"></i>
    </span>
    <input class="input-group-field"
           type="text"
           v-model="metric.name"
           @keydown.enter.prevent="add"
           placeholder="Metric"
           v-focus
    >
    <div class="input-group-button" v-show="metrics.length > 1">
      <button type="button" 
              class="button alert" 
              @click="remove(index)"
      >
        <i class="fa fa-minus"></i>
      </button>
    </div>
  </div>
  <button class="button expanded tiny" 
        type="button" 
        @click="add"
        title="Add another metric"
  >
    <i class="fa fa-plus"></i>
  </button> 
  <hr>

  <button class="button expanded" 
          type="submit" 
          @click.prevent="next"
  >
    Next
  </button>
</form>

Что здесь происходит?

  • v-for="(metric, index) in metrics": мы создаем новый input-group для каждой метрики и ссылаемся на ее индекс.
  • v-model="metric.name": мы используем двустороннюю привязку данных. Это обновит свойство name объекта метрики, когда пользователь вводит имя метрики.
  • v-focus: это настраиваемая директива. Мы его еще не создали, так что давайте сделаем это сейчас. Сейчас мы добавим это в наш файл resources / assets / js / app.js после того, как нам потребуется Vue.
window.Vue = require('vue');

// Register a global custom directive called v-focus
Vue.directive('focus', {
  inserted: (el) => el.focus()
})
  • в основном это говорит о том, что как только элемент, содержащий директиву v-focus, вставлен в DOM, сосредоточьтесь на нем.
  • Мы заменили кнопку «Добавить» на кнопку «Удалить», при нажатии которой удаляется соответствующая метрика.
  • v-show="metrics.length > 1": показывать кнопку удаления только тогда, когда в массиве более одной метрики.
  • Мы также добавили новую кнопку «Добавить» после нашего input-group. При нажатии отображается новое поле ввода.

Шаг 2 - Создайте возможность «Добавить / удалить действия».

С точки зрения функциональности добавление и удаление действий будет работать точно так же, как и для метрик. Однако мы внесем некоторые заметные изменения в форму на нашей странице действий. Сначала давайте откроем ./resources/assets/js/pages/Actions.vue и в наших <tempalte> тегах мы обновим form следующим образом:

<form @submit.prevent="save">
  <div class="row" v-for="(action, index) in actions">
    
    <div class="medium-6 small-12 columns">
      <div class="input-group">
        <span class="input-group-label">
          <i class="fa fa-list"></i>
        </span>
        <input class="input-group-field" 
               type="text" 
               placeholder="Action..." 
               v-model="action.name" 
               v-focus
        >
      </div>
    </div>

    <div class="medium-6 small-12 columns">
      <div class="input-group">
        <span class="input-group-label">
          <i class="fa fa-user"></i>
        </span>
        <input class="input-group-field"
               type="text"
               placeholder="Who will perform the action?"
               v-model="action.person_responsible"
        >
        <div class="input-group-button">
          <button type="button"
                  class="button alert"
                  title="Remove action"
                  v-show="actions.length > 1"
                  @click="remove(index)"
          >
            <i class="fa fa-minus"></i>
          </button>
        </div>
      </div>
    </div>
  </div>

  <button type="button"
          class="button expanded tiny warning"
          title="Add another action"
          @click="add"
  >
    <i class="fa fa-plus"></i>
  </button>
  <hr>

  <button class="button expanded" type="submit">Done</button>
</form>

Мы не вводили здесь никаких новых концепций, поэтому давайте быстро рассмотрим визуальные изменения, которые мы внесли в нашу форму.

Какие изменения были внесены?

  • Теперь у нас есть row, который содержит два columns, каждый из которых содержит поле ввода. Для маленьких экранов эти поля отображаются одно за другим small-12. Для средних дисплеев и выше мы показываем их бок о бок medium-6.
  • Было введено новое текстовое поле, позволяющее пользователю указать, кто отвечает за выполнение действия.
  • Затем мы удалили кнопку «Добавить» из input-group и заменили ее кнопкой «Удалить».
  • Наконец, как и в нашей форме показателей, мы добавили новую кнопку «Добавить», но на этот раз после row.

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

<script>
  export default {
    data() {
      return {
        actions: [{ name: '', person_responsible: '' }]
      }
    },

    mounted() {
      Foundation.Motion.animateIn(this.$el, 'slide-in-left fast');
    },

    methods: {
      save() {
        alert('Saving...');
      },

      add() {
        this.actions.push({ name: '', person_responsible: ''});
      },

      remove(index) {
        if (this.actions.length > 1) {
          this.actions.splice(index, 1)
        }
      }
    }
  }
</script>

После того, как все скомпилировано, у нас должна получиться такая форма:

Вот полный файл Action.vue с внесенными нами изменениями:

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

На данный момент у нас действительно есть некоторые общие функции между формами, и, в зависимости от того, как идет работа, может быть стоит извлечь их в миксин.

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

До следующего раза, удачного кодирования и спасибо за чтение!