5.10 Практические задания


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

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

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

Задачи отсортированы по возрастанию сложности. Решив их все, я уверен, что вы окончательно устраните все пробелы и будете готовы написать с нуля своё собственное приложение. Итак, поехали!




На странице списка приёмов добавьте в фильтр следующие поля: "Статус", "Принимающий" и "Жалобы".




На странице списка приёмов добавьте в фильтр кнопку "Сброс", наделив ее какой-нибудь svg-иконкой по аналогии с кнопкой "Поиск". По нажатии на "Сброс" поля фильтра должны быть очищены, а список перезагружен с пустыми параметрами фильтрации.


Подсказка: для того, чтобы сбросить фильтр, нам понадобится уже реализованное действие cleanFilter().




На странице списка приёмов сделайте скрывающийся фильтр, используя компонент <Collapse> библиотеки reactstrap.

Для распахивания/скрытия фильтра использовать svg-иконку в правом верхнем углу:




По умолчанию фильтр должен быть скрыт. Фильтр вынести в отдельный компонент-контейнер <AppointmentFilter>, поместив его в папку /Appointments. Компонент фильтра должен иметь доступ к определённой части состояния и действиям redux.


Подсказка: когда мы переносим фильтр в отдельный компонент-контейнер, последний будет иметь доступ к состоянию и действиям списка. Сам компонент будет выглядеть примерно так:


Код
    
  // маппинг состояния фильтра в свойства компонента-контейнера
  function mapStateToProps (state) {
     return {
         fields: state.appointment.list.dataSource.filter
     }
  }
  
  // подключение генераторов действий к компоненту-контейнеру
  function mapDispatchToProps(dispatch) {
     return {
         actions: {
             ...bindActionCreators(appointmentListActions, dispatch)
         }
     }
  }
  
  class AppointmentFilter extends Component {
    onChangeField = (name, value) => {
     this.changeFilterField(name, value, false)
   }
   
   onChangeDateField = (name, value) => {
     this.changeFilterField(name, value && value.getTime(), false)
   }
   
   onSearch = () => {
     this.change()
   }
   
   onClean = () => {
     this.clean()
   }
   
   // изменение определённых нескольких полей фильтра
   change (changes, shouldReload) {
     this.props
             .actions
             .changeFilter(changes, shouldReload)
   }
   
   // изменение определённого поля фильтра
   changeField (name, value, shouldReload) {
         this.props
             .actions
             .changeFilterField(name, value, shouldReload)
   }
   
   // сброс фильра
   clean () {
     this.props
             .actions
             .changeFilter()
   }
   
   // разметка
   render() {}
  }
  
  // объявляем контейнер
  export default connect(mapStateToProps, mapDispatchToProps)(AppointmentFilter)
  

Компонент <Appointments> нужно очистить от лишних методов, тем самым упростив его код.




На странице списка приёмов добавьте сортировку в следующие столбцы таблицы: "Дата", "Клиент", "Статус" и "Принимающий". Сортировка должна происходить на стороне сервера(фейкового), а на клиент приходят готовые отсортированные данные.


Подсказка. Чтобы организовать сортировку понадобится сделать несколько вещей:

  1. Включить сортировку на необходимых столбцах используя .

  2. По событию onSort список следует перезагрузить, передав на сервер поле сортировки.

  3. Redux состояние списка будет содержать новое свойство sorting:

    Код
        
      export default Record({
         error: null,
         isFetching: false,
         shouldReload: false,
         dataSource: Record({
             data: [],
             filter: Record({
                 startDate: null,
                 endDate: null,
                 clientName: '',
                 onlyMe: false
             })(),
             // сортировка
             sorting: Record({
                 field: 'firstName',
                 order: 'asc'
             })()
         })()
      })
      

  4. Добавится новое действие sort():

    Код
        
      // сортировать список
      export function sort (field, order, shouldReload) {
         return {
             type: CHANGE_ASSESSMENT_LIST_SORTING,
             payload: { field, order, shouldReload }
         }
      }
      

  5. Метод load() компонента <Appointments> будет передавать дополнительный параметр sort, равный значению sort='fieldName,order' (например sort='firstName,desc'). Сервер парсит это строковое значение и возвращает отсортированный список.




На странице списка приёмов увеличить количество тестовых данных и реализовать пагинацию.


Подсказка. Чтобы увеличить количество тестовых данных следует отрефакторить код файла MockData.js. Сейчас там просто статический список appointments. Чтобы придать этому списку произвольный размер, нужно сделать его динамическим. Это значит, что нужно создать метод createAppointments(N), который будет создавать N элементов списка при старте приложения. Чтобы данные списка были разнообразными, нужно построить несколько дополнительных статических списков: список имен firstNames, список фамилий lastNames, список диагнозов и так далее. Затем при конструировании элемента динамического списка appointments, мы для каждого элемента случайным образом выбираем имя, фамилию, диагноз и пр. из статических списков.

Чтобы реализовать пагинацию, в Redux состояние списка приёмов следует добавить новое свойство pagination:


Код
    
  export default Record({
     error: null,
     isFetching: false,
     shouldReload: false,
     dataSource: Record({
         data: [],
         filter: Record({
             startDate: null,
             endDate: null,
             clientName: '',
             onlyMe: false
         })(),
         // пагинация
         pagination: Record({
             page: 1, // текущий номер страницы
             size: 15, // размер страницы
             totalCount: 0 // всего элементов
         })()
         // ...
     })()
  })
  

Под страницей понимается количество/часть элементов списка, показываемых пользователю в данный момент. Соответственно нам нужны такие параметры как номер/индекс и размер части, а также общее количество элементов (для корректной работа пагинатора таблицы).

Пример подключения пагинатора в компоненте таблицы можно . В компоненте <Table> рекомендую добавить метод onRefresh:


Код
    
  export default class Table extends Component {
  
   // ...
   
   onRefresh = (type, { page }) => {
    this.props.onRefresh(page)
   }
   
   // ...
   
   render() {
     // ...
   
     return (
       <div className={cn('TableContainer', containerClass)}>
         <BootstrapTable
            // ...
            onTableChange={this.onRefresh}
            // ...
         />
       </div>
     )
   }
  }
  

При срабатывании onRefresh компонента <Table> компонент <Appointments> будет загружать необходимую часть (страницу) данных списка для значения параметра page. Сервер вычисляет нужную часть элементов списка(страницу) и возвращает её.




Реализуйте демонстрацию ошибки при неудаче получения данных с сервера. Продемонстрировать ошибку следует в виде модального диалога с кнопкой ОК, по нажатию на которой диалог пропадает.


Подсказка. в библиотеке reactstrap есть модальное окно. Вам понадобится сделать свой универсальный базовый компонент-обёртку <Dialog> со всеми необходимыми для вас свойствами (props). Компонент <Dialog>, помимо прочих, будет содержать такие специфические свойства как: icon (иконка диалога: ошибка, успех, информация), title (название диалога) и buttons (кнопки диалога). Содержимое диалога будет передаваться через свойство children. Использование <Dialog> будет выглядеть приблизительно так:


Код
    
  <Dialog
      text={text}
      title={title}
      icon={Danger}
      isOpen={isOpen}
      // buttons - это массив элементов вида:
      // [{ text: ‘OK’, color: ‘success’, outline: true, className: ‘ErrorDialog-OkBtn’ }, … любые кнопки]
      buttons={buttons}
      onClosed={onClosed}
      className={cn('ErrorDialog', className)}
   />
  

Для демонстрации ошибки удобно создать ещё один компонент-обёртку вокруг <Dialog>. Назовём его <ErrorViewer>. Он будет отрисовывать с компонент диалога с предварительно заданными свойствами props (title, icon, text и buttons), которые при необходимости можно переопределить.

Саму ошибку мы получаем из соответствующей части состояния.




Реализуйте аутентификацию. Требования:

  • При вводе URL приложения пользователь должен быть перенаправлен на форму аутентификации.

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


Подсказка. Работа с формой аналогична работе с фильтром, который мы реализовали для списка приёмов. К тому же имеется готовый макет. Важным моментом является добавление нескольких тестовых пользователей в фейковую базу MockData.js. Создайте в этом файле метод login(), который будет сверять введённые данные с этими пользователями. Если есть полное совпадение, аутентификация признаётся успешной, а метод возвращает данные найденного пользователя для его последующего сохранения в состояние приложения. Эти данные будут доступны на протяжении всего сеанса. Простейший вариант реализации метода login():


Код
    
  export function login ({ username, password }) {
      const user = // найти пользователя
      
      if (user) {
          return getSuccessResponse(user, { success: true })
      }
      
      return getFailureResponse(
          'bad.credentials',
          'Пользователя с такими данными не найдено'
      )
  }
  

Что касается состояния, то для работы с аутентификацией удобно создать поддиректорию /redux/auth, а для формы аутентификации - поддиректорию /redux/login:




Для выполнения AJAX запросов, связанных аутентификацией (логин, логаут) создайте сервис AuthService.




Подключите валидацию на форму аутентификации. Валидация должна срабатывать, когда пользователь пытается отправить не заполненную форму.


Подсказка. Для валидации неплохо подойдёт плагин validate.js. Он обладает обширными возможностями и простотой использования. Валидацию лучше организовать через действие (в файле loginFormActions.js). Также подготовьте специальный класс-валидатор формы LoginFormValidatоr с методом validate(), который можно вызывать в действии. Код действия и валидатора может выглядеть так:







В состояние, для каждого основного поля формы, следует добавить два валидационных:




В редюсере, в случае неудачной валидации, мы проинициализируем эти поля, а затем отобразим их значения на форме.




Реализовать просмотрщик и редактор деталей приёма в виде диалоговых окон, используя макеты. Требования:

  • В просмотрщике должна быть показана более подробная информация по приёму, чем в списке (придумайте дополнительные данные).

    • Информация должна быть представлена в виде трёх столбцов.

    • Просмотрщик должен иметь название "Просмотр приёма", а также содержать кнопку "Закрыть", по нажатию на которой он закрывается.

  • В редакторе должна отображаться форма для создания/редактирования приёма, на которой должна присутствовать валидация.

  • Если редактор открыт на редактирование приёма, его название должно быть "Редактировать приём", а если на создание - "Создать приём".

  • Редактор должен иметь кнопки "Закрыть" и "Сохранить".

    • По нажатию на кнопку "Закрыть", редактор должен закрываться.

    • По нажатию на кнопку "Сохранить", сначала происходит валидация. Если валидация прошла успешно - данные отправляются на сервер.

    • Пока ожидается ответ сервера, на диалоге должен показываться "модальный" лоудер, который затеняет все поля формы, не давая возможности её редактировать.

    • Если данные сохранены успешно - лоудер пропадает, редактор закрывается, а список обновляется.

  • Для вызова просмотрщика и редактора в таблицу следует добавить дополнительный безымянный столбец.

    • Каждая ячейка этого столбца должна содержать две кнопки для просмотра (с иконкой глаза) и редактирования (с иконкой карандаша) конкретного приёма.

  • Над таблицей следует добавить кнопку "Создать" для вызова редактора в режиме создания нового приёма.

Макеты:







Подсказка. Cледует создать одноимённую компонент-обёртку вокруг компонента модального окна reactstrap. Такая обёртка позволит сократить код при использовании и расширить возможности.

Просмотрщик и редактор можно представить компонентами <AppointmentViewer> и <AppointmentEditor>, которые будут являться обёртками модального окна.




Реализовать раздел "События" по аналогии с разделом "Приёмы". Требования:

  • Схлопывающийся фильтр

  • Список с возможностями сортировки и пагинации

  • Просмотрщик и редактор событий




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

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