Page tree
Skip to end of metadata
Go to start of metadata

Задача

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

Серверная часть

В этот раз нам снова будет достаточно объявить пустой класс виджета, т.к. серверная часть управления доп. услугами будет использоваться из API.

engine/app/widgets/offline/additional_servs_widget.rb
module Offline
  class AdditionalServsWidget < HupoWidget::Base
  end
end

Конфигурация и переводы

Виджет не предполагает никаких настроек, однако нужно объявить хотя бы одну спецификацию, чтобы можно было создать экземпляр виджета. Назовем спецификацию main и передадим ей ничего не значащую настройку:

widgets/additional_servs.yml
offline:
  additional_servs:
    main:
      dummy: 1

Файл переводов будет выглядеть так:

locales/ru.widgets.yml
ru:
  offline:
    additional_servs:
      main:
        title: Дополнительные услуги
        connect: Подключить
        disconnect: Отключить

Клиентская часть

Вывод дополнительных услуг на страницу

Общий подход совпадает с предыдущим примером. Основное отличие в том, что класс страницы использует метод objects базового класса представления HupoApp.View. Метод возвращает объект, содержащий переданные с сервера сущности. Каждая сущность является моделью или коллекцией (набором моделей). Аналогичный результат возвращает глобальный вызов Application.objects – его можно набрать в консоли браузера (F12), чтобы посмотреть все доступные данные. В ключе additionalServs содержится коллекция моделей дополнительных услуг, которую мы передаем в функцию рендеринга представления виджета.

assets/javascripts/views/index.js.coffee
#= require base/view
class HupoApp.Views.Index extends HupoApp.View
  initialize: =>
    super
    @additionalServsWidget = new HupoApp.Views.Offline.Widgets.AdditionalServs('main')

  render: =>
    @$el.empty()
    @renderAdditionalServs()
    @$el.show()

  renderAdditionalServs: =>
    @$el.append @additionalServsWidget.render
      additional_servs: Application.objects.additionalServs

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

assets/javascripts/views/offline/widgets/additional_servs.js.coffee
#= require views/offline/widgets
class HupoApp.Views.Offline.Widgets.AdditionalServs extends HupoApp.Views.Widgets
  widgetCode: 'offline.additional_servs'
  className: 'additional-servs-widget'

  render: (options) =>
    super(options)
    @$el.html JST['widgets/offline/additional_servs'](t: @translator)
    @$additionalServsList = @$el.find('.additional-servs-list')
    @renderAdditionalServs(options.additional_servs)
    @$el.show()

  renderAdditionalServs: (additionalServs) =>
    if additionalServs and additionalServs.length > 0
      additionalServs.each (additionalServ) =>
        additionalServView = new HupoApp.Views.Offline.Widgets.AdditionalServs.AdditionalServ
          model: additionalServ
          translator: @translator
        @$additionalServsList.append additionalServView.render()

Шаблон виджета содержит заголовок и список, заполняемый из функции renderAdditionalServs.

assets/templates/widgets/offline/additional_servs.jst.hamlc
%h1= @t('title')
%ul.thumbnails.additional-servs-list

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

assets/javascripts/views/offline/widgets/additional_servs/additional_serv.js.coffee
#= require views/widgets/offline/additional_servs
class HupoApp.Views.Offline.Widgets.AdditionalServs.AdditionalServ extends HupoApp.View
  tagName: 'li'
  className: 'additional-serv span3'

  initialize: (options) =>
    super
    @model = options.model
    @translator = @options.translator

  render: =>
    @$el.html JST['widgets/offline/additional_servs/additional_serv']
      t: @translator
      additional_serv: @model
    if @model.get('connected')
      @$el.addClass('connected')
    @$el.show()

Шаблон услуги выглядит следующим образом:

assets/templates/widgets/offline/additional_servs/additional_serv.jst.hamlc
.thumbnail
  %h3= @additional_serv.get('vc_name')
  %p.description!= @additional_serv.get('vc_user_rem')
  %p.price!= I18n.l('currency', @additional_serv.get('n_good_sum'), @additional_serv.get('vc_currency_code'))
  .controls
    - if @additional_serv.get('connected')
      %button.btn.disconnect= @t('disconnect')
    - else
      %button.btn.btn-primary.connect= @t('connect')

Для верстки используются возможности CSS-фреймворка Bootstrap. Однако, может потребоваться задать собственные стили. Это можно сделать в файле custom.sass:

assets/stylesheets/custom.sass
.additional-servs-widget
  padding: 10px
  h1
    margin-bottom: 10px

  .additional-servs-list > .additional-serv
    padding: 10px

    &.connected > .thumbnail
      background-color: #f7fff7

    .price
      text-align: right
      font-size: 24px
      font-weight: bold

    .controls
      margin-top: 10px
      text-align: right

Файл custom.sass работает аналогично custom.js.coffee – при запуске приложения описание стилей на языке SASS компилируется в CSS. Если файл становится большим, рекомендуется разнести описания на несколько файлов, а в custom.sass оставить только их подключения с помощью директивы require.

В результате на главной странице появится список доп. услуг.

Управление дополнительными услугами

Осталось реализовать поведение кнопок "Подключить" и "Отключить".

Добавим в класс представления доп. услуги следующий объект:

assets/javascripts/views/widgets/offline/additional_servs/additional_serv.js.coffee
events:
  'click .connect': 'connect'
  'click .disconnect': 'disconnect'

Теперь при нажатии на кнопку "Подключить" вызовется метод connect. В качестве аргумента будет передан объект события. Аналогично для кнопки "Отключить" вызовется метод disconnect.

Осталось определить эти методы.

assets/javascripts/views/widgets/offline/additional_servs/additional_serv.js.coffee
  connect: (evt) => @toggle(evt, true)
  disconnect: (evt) => @toggle(evt, false)
  toggle: (evt, enable) =>
    # Переводим кнопку в неактивное состояние
    $(evt.currentTarget).attr('disabled', 'disabled').html(@translator('loading'))

    # Получаем список подключенных доп. услуг по подписке
    activeServs = @objects().additionalServs.where
      n_par_subj_good_id: @model.get('n_par_subj_good_id'),
      connected: true

    # В зависимости от параметра
    if enable
      # Добавляем доп. услугу
      activeServs.push(@model)
    else
      # Или исключаем ее из списка
      activeServs = _.without(activeServs, @model)

    # Получаем идентификаторы из моделей, оставляем только уникальные
    activeServs = _.uniq(_.map(activeServs, (s) => s.id))

    # Делаем AJAX-запрос к API
    $.ajax
      url: "/accounts/#{@model.get('n_account_id')}/servs/#{@model.get('n_par_subj_good_id')}"
      type: 'put'
      data:
        # Передаём идентификатор подписки
        n_subscription_id: @model.get('n_par_subj_good_id')
        serv:
          # И список подключенных услуг
          additional_servs: _.map(activeServs, (n_good_id) => n_good_id: n_good_id)

      # В случае успешного завершения запроса
      success: (response) =>
        # Синхронизируем состояние объектов с серером
        Application.updateCommonObjects(response.data)
        # Устанавливаем доп. услуге флаг подключения/отключения
        # Этот вызов вызовет событие change для @model
        @model.set('connected', enable)
        # Меняем css-класс
        @$el.toggleClass('connected', enable)

Таким образом при нажатии на кнопку, производится AJAX-запрос к API личного кабинета, затем данные на клиенте обновляются и представление дополнительной услуги перерисовывается в новом состоянии.

Исходные коды примера: additional_servs_widget.zip.

  • No labels