Задача
В этом примере приведена разработка пользовательского виджета управления дополнительными услугами, используя API личного кабинета.
Серверная часть
В этот раз нам снова будет достаточно объявить пустой класс виджета, т.к. серверная часть управления доп. услугами будет использоваться из API.
module Offline class AdditionalServsWidget < HupoWidget::Base end end
Конфигурация и переводы
Виджет не предполагает никаких настроек, однако нужно объявить хотя бы одну спецификацию, чтобы можно было создать экземпляр виджета. Назовем спецификацию main и передадим ей ничего не значащую настройку:
offline: additional_servs: main: dummy: 1
Файл переводов будет выглядеть так:
ru: offline: additional_servs: main: title: Дополнительные услуги connect: Подключить disconnect: Отключить
Клиентская часть
Вывод дополнительных услуг на страницу
Общий подход совпадает с предыдущим примером. Основное отличие в том, что класс страницы использует метод objects базового класса представления HupoApp.View. Метод возвращает объект, содержащий переданные с сервера сущности. Каждая сущность является моделью или коллекцией (набором моделей). Аналогичный результат возвращает глобальный вызов Application.objects – его можно набрать в консоли браузера (F12), чтобы посмотреть все доступные данные. В ключе additionalServs содержится коллекция моделей дополнительных услуг, которую мы передаем в функцию рендеринга представления виджета.
#= 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
Функция рендеринга в представлении виджета создает представление одной дополнительной услуги для каждой из переданных услуг и выводит их списком.
#= 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.
%h1= @t('title') %ul.thumbnails.additional-servs-list
Представление дополнительной услуги рендерит шаблон услуги. Во многих случаях можно обойтись без отдельного представления для элемента списка, однако в нашем случае у каждой услуги есть кнопки подключения и отключения, события которых нужно будет обрабатывать. Это будет удобнее при наличии отдельного представления.
#= 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()
Шаблон услуги выглядит следующим образом:
.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:
.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.
В результате на главной странице появится список доп. услуг.
Управление дополнительными услугами
Осталось реализовать поведение кнопок "Подключить" и "Отключить".
Добавим в класс представления доп. услуги следующий объект:
events: 'click .connect': 'connect' 'click .disconnect': 'disconnect'
Теперь при нажатии на кнопку "Подключить" вызовется метод connect. В качестве аргумента будет передан объект события. Аналогично для кнопки "Отключить" вызовется метод disconnect.
Осталось определить эти методы.
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.