Версия: 5.x
Расширение функционала через дополнительный модуль.

Расширение шаблонов через хуки.

Все шаблоны мобильного приложения для интернет магазина располагаются в папке /modules/mobilesiteapp/view/pages/. В TPL-шаблонах, располагается код шаблонов Angular компонентов мобильного приложения. В шаблонах допустимо использовать подключение JS, CSS файлов с помощью стандартных конструкций {addcss}, {addjs}.

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

Например в шаблоне используется хук:

1 {hook name="mobilesiteapp-blocksbanners:slideshow" title="{t}Мобильное приложение - баннеры:блок, слайдер{/t}"}
2  <ion-slides #banners *ngIf="list && list.length" class="slideShowWrapper" [pager]="slideOptions['pager']" [loop]="slideOptions['loop']">
3  {literal}
4  <ion-slide *ngFor="let item of list">
5  <img src="{{item.getUrl()}}"/>
6  </ion-slide>
7  {/literal}
8  </ion-slides>
9 {/hook}

Это значит, что мы у себя в модуле должны сделать следующую структуру.

  • modulename //название модуля
    • view //Корневая папка для шаблонов отображения
      • hooks //Папка с шаблонами-обработчиками хуков
        • mobilesiteapp-blocksbanners //идентификатор имени шаблона хука
          • slideshow.pre.tpl //Данный шаблон добавит свой HTML до зоны хука
          • slideshow.tpl //Данный шаблон добавит свой HTML вместо стандартного содержимого хука
          • slideshow.post.tpl //Данный шаблон добавит свой HTML после зоны хука

Расширение JavaScript кода через JS хуки.

Приложение полностью построено на TypeScript, который при компиляции переводится в JavaScript(версии ES 2015). Поэтому для расширения функциональности, потребуется писать код на JavaScript, чтобы расширять уже скомпилированные в JS классы.

Каждый модуль или страница в приложении может дополняться методами, которые формируются на серверной стороне. Любой сторонний модуль может привнести собственные функции(методы) в тот или иной класс компонента мобильного приложения. В совокупности с возможностью расширения шаблонов это позволяет добавлять какие-либо интерактивные функции в мобильное приложение. Причём при расширении внутри добавляемого нами метода доступны все переменные расширяемого класса в приложении.

Покажем на примере модуля "Бонусная и дисконтная программа. Комплексное решение" из нашего магазина дополнений(маркетплэйс). Например, нам нужно расширить класс, который отображает страницу корзины. Она находится в классе CartPageBlocksCartPage в файле /appsource/src/pages/cartpage/blocks/cartpage.ts для того, чтобы иметь возможность обработать функционал расширенный из хука в шаблоне козины.

mobile_bonuses_use.png
Функционал бонусов в мобильном приложении

Когда приложение стартует, то на сервер с установленной копией ReadyScript отправляется запрос на получение методов и переменных для расширения функционала. Данные возвращаются в специально подготовленном json.

1 {
2  "response": {
3  "CartPageBlocksCartPage": {
4  "changeUseBonuses": "(function()\r\n{\r\n //Добавим параметры\r\n if (!this.config){\r\n this.config = {};\r\n }\r\n if (!this.config['custom']){\r\n this.config['custom'] = {};\r\n }\r\n //Добавим флаг, что мы используем бонусы\r\n this.config['custom']['use_cart_bonuses'] = (this.cartdata.use_cart_bonuses) ? 1 : 0;\r\n this.httpRequestService.addRequestToBuffer(this.cartService.url, this.config, this);\r\n this.httpRequestService.sendRequestsPartially();\r\n})"
5  }
6  }
7 }

Как видно из json у нас есть секция "CartPageBlocksCartPage", она отвечает за расришение именно класса CartPageBlocksCartPage. Также чуть ниже присутствует секция changeUseBonuses в которой присваивается функция. Это значит, что класс CartPageBlocksCartPage будет расширен и будет добавлена функция changeUseBonuses, которую можно использовать. Модуль бонусной программы использует хук шаблона mobilesiteapp-blockscartpage:products, который использует этот метод.

  • bonuses //название модуля
    • view //Корневая папка для шаблонов отображения
      • hooks //Папка с шаблонами-обработчиками хуков
        • mobilesiteapp-blockscartpage //идентификатор имени шаблона хука
          • products.post.tpl //Данный шаблон добавит свой HTML после зоны хука

Содержимое хука:

1 {addcss file="%bonuses%/bonuses_mobile.css"}
2 <ion-grid id="cartBonusesWrapper" class="cartBonusesWrapper" margin-top *ngIf="authService.isAuth() && (cartdata.user_bonuses>0 || cartdata.total_bonuses>0)">
3  <ion-row class="row">
4  <ion-col class="cartitem bonusesCartItem col" no-padding no-margin col-12>
5  <div class="bonusesDescription">
6  <div text-left *ngIf="!cartdata.use_cart_bonuses && cartdata.user_bonuses>0"><b>Ваши бонусы - {literal}{{cartdata.user_bonuses}}{/literal}</b></div>
7  <div text-left *ngIf="cartdata.total_bonuses>0"><b>Бонусы за заказ - {literal}{{cartdata.total_bonuses}}{/literal}</b></div>
8  </div>
9  <ion-item margin-top *ngIf="cartdata.user_bonuses>0">
10  <ion-label text-wrap>{t}Перевести бонусы в скидку{/t}</ion-label>
11  <ion-checkbox (ionChange)="changeUseBonuses()" [(ngModel)]="cartdata.use_cart_bonuses"></ion-checkbox>
12  </ion-item>
13  </ion-col>
14  </ion-row>
15 </ion-grid>

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

  • bonuses //название модуля
    • view //Корневая папка для шаблонов отображения
      • jshooks //Общая папка с js-обработчиками хуков
        • mobilesiteapp //Папка с js-обработчиками хуков для мобильного приложения интернет магазин
          • CartPageBlocksCartPage.changeUseBonuses.js //Данный js файл расширит нужный нам класс CartPageBlocksCartPage

Как видите у нас есть 2 обязательные папки:

  • jshooks - Общая папка с js-обработчиками хуков
  • mobilesiteapp - Папка с js-обработчиками хуков для мобильного приложения интернет магазин

А далее у нас создан файл с именем CartPageBlocksCartPage.changeUseBonuses.js по формату ИмяКлассаДляРасширения.НазваниеМетодаИлиПеременной.js.

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

1 (function(){
2  alert("Функция сработала");
3 })

Если же это текст, то:

1 Это текст переменной

Если число:

1 3.1415926535

На одну дополнительную секцию нужен один файл.

Посмотрим содержимое нужного нам метода.

1 (function()
2 {
3  //Добавим параметры секции custom
4  if (!this.config){
5  this.config = {};
6  }
7  if (!this.config['custom']){
8  this.config['custom'] = {};
9  }
10  //Добавим флаг, что мы используем бонусы
11  this.config['custom']['use_cart_bonuses'] = (this.cartdata.use_cart_bonuses) ? 1 : 0;
12 
13  //Добавляем содержимое запроса
14  this.httpRequestService.addRequestToBuffer(this.cartService.url, this.config, this);
15  //Отправляем запрос на сервер.
16  this.httpRequestService.sendRequestsPartially();
17 })

В данном добавляемом методе мы формируем параметры в секции custom в переменной config, которая присутствует в классе CartPageBlocksCartPage, которые мы потом передаём параметрами для запроса инфорации для корзины через метод this.httpRequestService.sendRequestsPartially(). Этот метод присутствует внутри приложения. Как видно, нам доступен весь функционал который присутствует в рамках расширяемого нами класса.

Расширение методов API при взаимодействии с приложением.

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

Для своих целей мы можем использовать хуки:

  • api.МЕТОД.before
  • api.МЕТОД.success

Например, для бонусной программы импользуется хук api.cart.getcartdata.success и api.cart.getcartdata.before.

/**
* Подвешиваемся на событие перед получением корзины для мобильного приложения
*
* @param array $data - массив данных
* @return array
*/
public static function apiCartGetCartDataBefore($data)
{
$custom = \RS\Http\Request::commonInstance()->request('custom', TYPE_ARRAY, array()); //Дополнительная секция
if (!empty($custom) && isset($custom['use_cart_bonuses'])){
$use_cart_bonuses = $custom['use_cart_bonuses']; //Флаг того, что нужно активировать бонусы
//Добавим сессию если нужно.
if ($use_cart_bonuses){
$_SESSION['use_cart_bonuses'] = 1;
}elseif ($use_cart_bonuses==="0"){
unset($_SESSION['use_cart_bonuses']);
}
}
$data['result']['response']['cartdata']['use_cart_bonuses'] = (isset($_SESSION['use_cart_bonuses']) && $_SESSION['use_cart_bonuses']);
return $data;
}
/**
* Подвешиваемся на получение корзины для мобильного приложения, чтобы добавить в ответ дополнительные поля для бонусов
*
* @param array $data - массив данных
* @return array
*/
public static function apiCartGetCartDataSuccess($data)
{
$data['result']['response']['cartdata'] = self::addBonusesInfoToCart($data['result']['response']['cartdata']);
return $data;
}

В этом методе мы добавляем в сессию флаг об использовании бунусов перед отработкой метода API. Далее при отработке метода формируется объект корзины в котором учитывается этот флаг сессии. А вот apiCartGetCartDataSuccess, добавляет секцию с к обработанной информации. В результате получается примерно такой ответ:

1 {
2  "response": {
3  "cartdata": {
4  "use_cart_bonuses": true,
5  "total": "14 300 р.",
6  "total_base": "14 300 р.",
7  "total_discount": "600 р.",
8  "items": [
9  {
10  "id": "xh3nvstn6n",
11  "cost": "14 300 р.",
12  "base_cost": "14 900 р.",
13  "single_cost": "14 900 р.",
14  "single_weight": 200,
15  "discount": "600 р.", //Скидка после применения бонусов
16  "sub_products": [],
17  "title": "HTC A6380 Gratia Green",
18  "image": {
19  "id": "2745",
20  "title": null,
21  "original_url": "http://192.168.1.199/storage/photo/original/f/pc6ysxj25r8xz28.jpg",
22  "big_url": "http://192.168.1.199/storage/photo/resized/xy_1000x1000/f/pc6ysxj25r8xz28_6cab8bde.jpg",
23  "middle_url": "http://192.168.1.199/storage/photo/resized/xy_600x600/f/pc6ysxj25r8xz28_94cff275.jpg",
24  "small_url": "http://192.168.1.199/storage/photo/resized/xy_300x300/f/pc6ysxj25r8xz28_fd1614b7.jpg",
25  "micro_url": "http://192.168.1.199/storage/photo/resized/xy_100x100/f/pc6ysxj25r8xz28_1c067e49.jpg",
26  "nano_url": "http://192.168.1.199/storage/photo/resized/xy_50x50/f/pc6ysxj25r8xz28_11ecc26f.jpg"
27  },
28  "entity_id": "657",
29  "amount": "1",
30  "amount_error": "",
31  "offer": "0",
32  "model": "SATA HDD, DDR3",
33  "multioffers": [
34  {
35  "title": "Тип",
36  "prop_id": "11",
37  "value": "SATA HDD"
38  },
39  {
40  "title": "Тип памяти",
41  "prop_id": "8",
42  "value": "DDR3"
43  }
44  ],
45  "multioffers_string": "Тип: SATA HDD, Тип памяти: DDR3",
46  "unit": null
47  }
48  ],
49  "items_count": 1,
50  "total_weight": 200,
51  "checkcount": 1,
52  "currency": "р.",
53  "errors": [],
54  "has_error": false,
55  "order_bonuses_for_discount": "600",
56  "order_discount": 600,
57  "order_discount_extra": "(Использовано бонусов - 600)",
58  "order_discount_unformatted": 600,
59  "order_discount_can_use_coupon": 0,
60  "total_discount_unformatted": 600,
61  "total_without_order_discount": "14 900 р.",
62  "total_without_order_discount_unformatted": 14900,
63  "taxes": [],
64  "total_without_delivery": "14 300 р.",
65  "total_without_delivery_unformatted": 14300,
66  "total_without_payment_commission": "14 300 р.",
67  "total_without_payment_commission_unformatted": 14300,
68  "total_unformatted": 14300,
69  "total_bonuses": 330, //Бонусы за оформленный заказ
70  "coupons": [],
71  "user_bonuses": "600" //Бонусы пользователя
72  }
73  }
74 }