iwgt 2.4.10 → 2.4.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/data/references/commonjs-complete-guide.json +1082 -0
- package/dist/data/references/commonjs-reference.json +1 -1
- package/dist/data/references/javascript-guide.json +436 -0
- package/dist/data/references/liquid-filters.json +1 -1
- package/dist/data/references/liquid-variables.json +1 -1
- package/dist/data/widget-wrapper-and-context.md +647 -0
- package/dist/generators/index.js +5 -0
- package/dist/server-http.js +1 -1
- package/dist/server.js +57 -2
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.js +136 -5
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "2.0",
|
|
3
|
-
"generatedAt": "2025-10-
|
|
3
|
+
"generatedAt": "2025-10-22T12:01:25.425Z",
|
|
4
4
|
"description": "Справочник по библиотеке common.v2.js для InSales - набор готовых скриптов для разработки шаблонов",
|
|
5
5
|
"installation": {
|
|
6
6
|
"method": "liquid_tag",
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Руководство по написанию JavaScript для виджетов InSales",
|
|
3
|
+
"description": "Полное руководство по написанию JavaScript для виджетов платформы InSales",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"sections": {
|
|
6
|
+
"principles": {
|
|
7
|
+
"title": "Основные принципы",
|
|
8
|
+
"description": "Фундаментальные правила написания JavaScript для виджетов",
|
|
9
|
+
"items": [
|
|
10
|
+
{
|
|
11
|
+
"id": "global_environment",
|
|
12
|
+
"title": "Глобальная среда",
|
|
13
|
+
"description": "НЕ используйте импорты/экспорты - все доступно глобально",
|
|
14
|
+
"wrong_example": "import $ from 'jquery';\nimport { Cart } from 'commonjs';\nexport default function() { }",
|
|
15
|
+
"correct_example": "// Все уже доступно глобально:\n// $, jQuery, Cart, Products, EventBus, Shop, etc.",
|
|
16
|
+
"note": "Все API доступны глобально без импортов"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "widget_isolation",
|
|
20
|
+
"title": "Изоляция виджетов",
|
|
21
|
+
"description": "ВСЕГДА используйте $widget.each() для изоляции",
|
|
22
|
+
"wrong_example": "var $button = $('.my-widget__button');\n$button.on('click', function() { /* ... */ });",
|
|
23
|
+
"correct_example": "$widget.each(function(index, widget) {\n var $thisWidget = $(widget);\n var $button = $thisWidget.find('.my-widget__button');\n \n $button.on('click', function() {\n // Код работает только для этого экземпляра\n });\n});",
|
|
24
|
+
"note": "Изоляция предотвращает конфликты между экземплярами виджетов"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "declarative_approach",
|
|
28
|
+
"title": "Декларативный подход через data-атрибуты",
|
|
29
|
+
"description": "Предпочитайте data-атрибуты вместо JavaScript",
|
|
30
|
+
"wrong_example": "$button.on('click', function() {\n var productId = $(this).data('id');\n $.ajax({\n url: '/cart_items.json',\n method: 'POST',\n data: { variant_id: productId }\n });\n});",
|
|
31
|
+
"correct_example": "<!-- В Liquid: форма товара с data-атрибутами -->\n<form data-product-id=\"{{ product.id }}\" action=\"{{ cart_url }}\" method=\"post\">\n <input type=\"hidden\" name=\"variant_id\" value=\"{{ product.variants.first.id }}\">\n <input type=\"hidden\" name=\"quantity\" value=\"1\">\n \n <div data-add-cart-counter>\n <button type=\"button\" data-add-cart-counter-btn>В корзину</button>\n <div class=\"controls\">\n <button data-add-cart-counter-plus type=\"button\">\n <span data-add-cart-counter-count></span> +1\n </button>\n </div>\n </div>\n</form>\n<!-- Работает БЕЗ JavaScript! -->",
|
|
32
|
+
"note": "CommonJS работает через data-атрибуты - большинство функций не требуют JavaScript!"
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"global_apis": {
|
|
37
|
+
"title": "Глобальные API",
|
|
38
|
+
"description": "Доступные глобальные API для работы с виджетами",
|
|
39
|
+
"modules": [
|
|
40
|
+
{
|
|
41
|
+
"name": "Cart",
|
|
42
|
+
"description": "API для работы с корзиной",
|
|
43
|
+
"methods": [
|
|
44
|
+
{
|
|
45
|
+
"name": "add",
|
|
46
|
+
"description": "Добавить товар в корзину",
|
|
47
|
+
"syntax": "Cart.add({ variant_id: 123, quantity: 1 })",
|
|
48
|
+
"example": "Cart.add({ \n variant_id: 123, \n quantity: 1 \n});"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"name": "remove",
|
|
52
|
+
"description": "Удалить товар из корзины",
|
|
53
|
+
"syntax": "Cart.remove(itemId)",
|
|
54
|
+
"example": "Cart.remove(itemId);"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "update",
|
|
58
|
+
"description": "Изменить количество товара",
|
|
59
|
+
"syntax": "Cart.update({ line_item_id: itemId, quantity: 2 })",
|
|
60
|
+
"example": "Cart.update({ \n line_item_id: itemId, \n quantity: 2 \n});"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "getCart",
|
|
64
|
+
"description": "Получить данные корзины",
|
|
65
|
+
"syntax": "Cart.getCart().then(function(cart) { ... })",
|
|
66
|
+
"example": "Cart.getCart().then(function(cart) {\n console.log(cart.items);\n});"
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"name": "Products",
|
|
72
|
+
"description": "API для работы с товарами",
|
|
73
|
+
"methods": [
|
|
74
|
+
{
|
|
75
|
+
"name": "getProduct",
|
|
76
|
+
"description": "Получить товар по ID",
|
|
77
|
+
"syntax": "Products.getProduct(productId).then(function(product) { ... })",
|
|
78
|
+
"example": "Products.getProduct(productId).then(function(product) {\n console.log(product.title);\n});"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"name": "getProducts",
|
|
82
|
+
"description": "Получить несколько товаров",
|
|
83
|
+
"syntax": "Products.getProducts([id1, id2, id3]).then(function(products) { ... })",
|
|
84
|
+
"example": "Products.getProducts([id1, id2, id3]).then(function(products) {\n console.log(products);\n});"
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "EventBus",
|
|
90
|
+
"description": "Подписка на события",
|
|
91
|
+
"methods": [
|
|
92
|
+
{
|
|
93
|
+
"name": "subscribe",
|
|
94
|
+
"description": "Подписаться на событие",
|
|
95
|
+
"syntax": "EventBus.subscribe('event_name', function(data) { ... })",
|
|
96
|
+
"example": "EventBus.subscribe('add_items:insales:cart', function(data) {\n console.log('Добавлен товар:', data.items);\n});"
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
"events": [
|
|
100
|
+
{
|
|
101
|
+
"name": "add_items:insales:cart",
|
|
102
|
+
"description": "Товар добавлен в корзину",
|
|
103
|
+
"example": "EventBus.subscribe('add_items:insales:cart', function(data) {\n console.log('Добавлен товар:', data.items);\n});"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"name": "remove_items:insales:cart",
|
|
107
|
+
"description": "Товар удален из корзины",
|
|
108
|
+
"example": "EventBus.subscribe('remove_items:insales:cart', function(data) {\n console.log('Удален товар:', data);\n});"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "update_items:insales:cart",
|
|
112
|
+
"description": "Корзина обновлена",
|
|
113
|
+
"example": "EventBus.subscribe('update_items:insales:cart', function(data) {\n console.log('Корзина обновлена:', data);\n});"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"name": "add_items:insales:favorites",
|
|
117
|
+
"description": "Товар добавлен в избранное",
|
|
118
|
+
"example": "EventBus.subscribe('add_items:insales:favorites', function(data) {\n console.log('В избранное:', data);\n});"
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"name": "Shop",
|
|
124
|
+
"description": "Вспомогательные методы магазина",
|
|
125
|
+
"methods": [
|
|
126
|
+
{
|
|
127
|
+
"name": "money.format",
|
|
128
|
+
"description": "Форматирование цены",
|
|
129
|
+
"syntax": "Shop.money.format(price)",
|
|
130
|
+
"example": "var formattedPrice = Shop.money.format(1234.50); // '1 234,50 руб.'"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"name": "config.getProductId",
|
|
134
|
+
"description": "Получить ID товара на текущей странице",
|
|
135
|
+
"syntax": "Shop.config.getProductId()",
|
|
136
|
+
"example": "var productId = Shop.config.getProductId(); // number или null"
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"name": "config.get",
|
|
140
|
+
"description": "Получить конфигурацию магазина",
|
|
141
|
+
"syntax": "Shop.config.get()",
|
|
142
|
+
"example": "var config = Shop.config.get();"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"name": "units.getName",
|
|
146
|
+
"description": "Получить название единицы измерения",
|
|
147
|
+
"syntax": "Shop.units.getName(unitCode)",
|
|
148
|
+
"example": "var unitName = Shop.units.getName('kgm'); // 'кг'"
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"name": "ajaxAPI",
|
|
154
|
+
"description": "Прямые запросы к API",
|
|
155
|
+
"methods": [
|
|
156
|
+
{
|
|
157
|
+
"name": "collection",
|
|
158
|
+
"description": "Получить коллекцию товаров",
|
|
159
|
+
"syntax": "ajaxAPI.collection({ collection_id: 123, page: 1, per_page: 12 })",
|
|
160
|
+
"example": "ajaxAPI.collection({\n collection_id: 123,\n page: 1,\n per_page: 12\n}).done(function(data) {\n console.log(data.products);\n});"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"name": "search",
|
|
164
|
+
"description": "Поиск товаров",
|
|
165
|
+
"syntax": "ajaxAPI.search({ q: 'запрос', per_page: 10 })",
|
|
166
|
+
"example": "ajaxAPI.search({\n q: 'запрос',\n per_page: 10\n}).done(function(data) {\n console.log(data.products);\n});"
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"name": "Template",
|
|
172
|
+
"description": "Рендеринг шаблонов",
|
|
173
|
+
"methods": [
|
|
174
|
+
{
|
|
175
|
+
"name": "product",
|
|
176
|
+
"description": "Рендер товара из шаблона",
|
|
177
|
+
"syntax": "Template.product(productData, 'template_name').then(function(html) { ... })",
|
|
178
|
+
"example": "Template.product(productData, 'product-card').then(function(html) {\n $('.products-grid').append(html);\n});"
|
|
179
|
+
}
|
|
180
|
+
]
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"name": "AjaxSearch",
|
|
184
|
+
"description": "Поиск с автодополнением",
|
|
185
|
+
"methods": [
|
|
186
|
+
{
|
|
187
|
+
"name": "constructor",
|
|
188
|
+
"description": "Инициализация поиска",
|
|
189
|
+
"syntax": "new AjaxSearch({ $input: $('#search-input'), $results: $('#search-results'), template: 'search-result' })",
|
|
190
|
+
"example": "var search = new AjaxSearch({\n $input: $('#search-input'),\n $results: $('#search-results'),\n template: 'search-result'\n});"
|
|
191
|
+
}
|
|
192
|
+
]
|
|
193
|
+
}
|
|
194
|
+
]
|
|
195
|
+
},
|
|
196
|
+
"data_attributes": {
|
|
197
|
+
"title": "Data-атрибуты CommonJS",
|
|
198
|
+
"description": "Декларативные атрибуты для работы без JavaScript",
|
|
199
|
+
"attributes": [
|
|
200
|
+
{
|
|
201
|
+
"name": "data-product-id",
|
|
202
|
+
"description": "ID товара (обязательный для формы товара)",
|
|
203
|
+
"usage": "data-product-id=\"{{ product.id }}\"",
|
|
204
|
+
"example": "<form data-product-id=\"{{ product.id }}\" action=\"{{ cart_url }}\" method=\"post\">"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"name": "data-add-cart-counter",
|
|
208
|
+
"description": "Кнопка добавления с счетчиком",
|
|
209
|
+
"usage": "data-add-cart-counter",
|
|
210
|
+
"example": "<div data-add-cart-counter>\n <button type=\"button\" data-add-cart-counter-btn>В корзину</button>\n</div>"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"name": "data-add-cart-counter-btn",
|
|
214
|
+
"description": "Кнопка \"Добавить в корзину\"",
|
|
215
|
+
"usage": "data-add-cart-counter-btn",
|
|
216
|
+
"example": "<button type=\"button\" data-add-cart-counter-btn>В корзину</button>"
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"name": "data-add-cart-counter-plus",
|
|
220
|
+
"description": "Кнопка \"+1\"",
|
|
221
|
+
"usage": "data-add-cart-counter-plus",
|
|
222
|
+
"example": "<button data-add-cart-counter-plus type=\"button\">+1</button>"
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"name": "data-add-cart-counter-count",
|
|
226
|
+
"description": "Отображение количества",
|
|
227
|
+
"usage": "data-add-cart-counter-count",
|
|
228
|
+
"example": "<span data-add-cart-counter-count></span>"
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"name": "data-cart-positions-count",
|
|
232
|
+
"description": "Счетчик позиций в корзине",
|
|
233
|
+
"usage": "data-cart-positions-count",
|
|
234
|
+
"example": "<span data-cart-positions-count></span>"
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"name": "data-ui-favorites-trigger",
|
|
238
|
+
"description": "Кнопка добавления в избранное",
|
|
239
|
+
"usage": "data-ui-favorites-trigger",
|
|
240
|
+
"example": "<button data-ui-favorites-trigger>В избранное</button>"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"name": "data-ui-favorites-counter",
|
|
244
|
+
"description": "Счетчик избранного",
|
|
245
|
+
"usage": "data-ui-favorites-counter",
|
|
246
|
+
"example": "<span data-ui-favorites-counter></span>"
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
"name": "data-compare-trigger",
|
|
250
|
+
"description": "Кнопка добавления в сравнение",
|
|
251
|
+
"usage": "data-compare-trigger",
|
|
252
|
+
"example": "<button data-compare-trigger>Сравнить</button>"
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"name": "data-quick-checkout",
|
|
256
|
+
"description": "Кнопка быстрого заказа",
|
|
257
|
+
"usage": "data-quick-checkout=\"[data-product-id='{{ product.id }}']\"",
|
|
258
|
+
"example": "<button data-quick-checkout=\"[data-product-id='{{ product.id }}']\">Купить в 1 клик</button>"
|
|
259
|
+
}
|
|
260
|
+
]
|
|
261
|
+
},
|
|
262
|
+
"patterns": {
|
|
263
|
+
"title": "Паттерны и примеры",
|
|
264
|
+
"description": "Готовые паттерны для типичных задач",
|
|
265
|
+
"patterns": [
|
|
266
|
+
{
|
|
267
|
+
"id": "widget_isolation",
|
|
268
|
+
"title": "Изоляция виджета",
|
|
269
|
+
"description": "Правильная изоляция кода виджета",
|
|
270
|
+
"code": "$widget.each(function(index, widget) {\n var $thisWidget = $(widget);\n \n // Все переменные и функции внутри замыкания\n var isOpen = false;\n \n function toggle() {\n isOpen = !isOpen;\n $thisWidget.toggleClass('is-open', isOpen);\n }\n \n // События относительно $thisWidget\n var $button = $thisWidget.find('.my-widget__toggle');\n $button.on('click', toggle);\n});"
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
"id": "declarative_cart",
|
|
274
|
+
"title": "Декларативная корзина",
|
|
275
|
+
"description": "Использование data-атрибутов для работы с корзиной",
|
|
276
|
+
"liquid_code": "<!-- В Liquid: форма товара с data-атрибутами -->\n<form \n data-product-id=\"{{ product.id }}\"\n data-product-updated-at=\"{{ product.updated_at }}\"\n action=\"{{ cart_url }}\"\n method=\"post\"\n class=\"product-form\"\n>\n <input type=\"hidden\" name=\"variant_id\" value=\"{{ product.variants.first.id }}\">\n <input type=\"hidden\" name=\"quantity\" value=\"1\">\n \n <!-- Кнопка с data-add-cart-counter - работает автоматически! -->\n <div class=\"add-cart-counter\" data-add-cart-counter>\n <button type=\"button\" class=\"button\" data-add-cart-counter-btn>\n <span class=\"button__icon icon-cart\"></span>\n <span class=\"button__text\">В корзину</span>\n </button>\n <div class=\"add-cart-counter__controls\">\n <button data-add-cart-counter-plus class=\"button\" type=\"button\">\n <span data-add-cart-counter-count></span>\n +1\n </button>\n </div>\n </div>\n</form>",
|
|
277
|
+
"js_code": "// JavaScript только для реакции на события:\n$widget.each(function(index, widget) {\n var $thisWidget = $(widget);\n \n // Показать уведомление после добавления\n EventBus.subscribe('add_items:insales:cart', function(data) {\n $thisWidget.find('.notification').addClass('is-visible');\n \n setTimeout(function() {\n $thisWidget.find('.notification').removeClass('is-visible');\n }, 3000);\n });\n});"
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"id": "counters",
|
|
281
|
+
"title": "Счетчики корзины и избранного",
|
|
282
|
+
"description": "Автоматические счетчики через data-атрибуты",
|
|
283
|
+
"code": "<!-- В HTML используйте data-атрибуты CommonJS: -->\n<span data-cart-positions-count></span>\n<span data-ui-favorites-counter></span>\n\n<!-- Обновляются АВТОМАТИЧЕСКИ при изменениях! -->\n<!-- JavaScript НЕ требуется! -->"
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
"id": "mobile_menu",
|
|
287
|
+
"title": "Мобильное меню",
|
|
288
|
+
"description": "Адаптивное меню для мобильных устройств",
|
|
289
|
+
"code": "$widget.each(function(index, widget) {\n var $thisWidget = $(widget);\n \n var $menuBtn = $thisWidget.find('.header__menu-btn');\n var $menu = $thisWidget.find('.header__menu');\n var $overlay = $thisWidget.find('.header__overlay');\n \n function openMenu() {\n $menu.addClass('is-active');\n $overlay.addClass('is-active');\n }\n \n function closeMenu() {\n $menu.removeClass('is-active');\n $overlay.removeClass('is-active');\n }\n \n $menuBtn.on('click', function() {\n if ($menu.hasClass('is-active')) {\n closeMenu();\n } else {\n openMenu();\n }\n });\n \n $overlay.on('click', closeMenu);\n});"
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
"id": "scroll_animation",
|
|
293
|
+
"title": "Анимация при скролле",
|
|
294
|
+
"description": "Анимация элементов при прокрутке страницы",
|
|
295
|
+
"code": "$widget.each(function(index, widget) {\n var $thisWidget = $(widget);\n var $items = $thisWidget.find('.my-widget__item');\n \n function checkVisibility() {\n var viewportBottom = $(window).scrollTop() + $(window).height();\n \n $items.each(function() {\n var $item = $(this);\n if ($item.hasClass('is-visible')) return;\n \n var elementTop = $item.offset().top;\n if (elementTop < viewportBottom - 50) {\n $item.addClass('is-visible');\n }\n });\n }\n \n $(window).on('scroll', checkVisibility);\n checkVisibility(); // Проверить при загрузке\n});"
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
"id": "products_work",
|
|
299
|
+
"title": "Работа с товарами",
|
|
300
|
+
"description": "Загрузка и отображение товаров",
|
|
301
|
+
"code": "$widget.each(function(index, widget) {\n var $thisWidget = $(widget);\n \n // Загрузить рекомендованные товары\n var productIds = [123, 456, 789];\n \n Products.getProducts(productIds).then(function(products) {\n products.forEach(function(product) {\n // Рендерим товар через Template\n Template.product(product, 'product-card').then(function(html) {\n $thisWidget.find('.products-grid').append(html);\n });\n });\n });\n});"
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
"id": "cart_events",
|
|
305
|
+
"title": "Подписка на события корзины",
|
|
306
|
+
"description": "Реакция на изменения в корзине",
|
|
307
|
+
"code": "$widget.each(function(index, widget) {\n var $thisWidget = $(widget);\n var $notification = $thisWidget.find('.notification');\n \n // Товар добавлен\n EventBus.subscribe('add_items:insales:cart', function(data) {\n var item = data.items[0];\n $notification\n .text('Товар \"' + item.title + '\" добавлен в корзину')\n .addClass('is-visible');\n \n setTimeout(function() {\n $notification.removeClass('is-visible');\n }, 3000);\n });\n \n // Товар удален\n EventBus.subscribe('remove_items:insales:cart', function(data) {\n console.log('Товар удален из корзины');\n });\n});"
|
|
308
|
+
}
|
|
309
|
+
]
|
|
310
|
+
},
|
|
311
|
+
"common_errors": {
|
|
312
|
+
"title": "Частые ошибки",
|
|
313
|
+
"description": "Типичные ошибки и способы их избежать",
|
|
314
|
+
"errors": [
|
|
315
|
+
{
|
|
316
|
+
"id": "no_widget_isolation",
|
|
317
|
+
"title": "Нет изоляции виджета",
|
|
318
|
+
"description": "Код работает для всех виджетов на странице",
|
|
319
|
+
"wrong_code": "var $button = $('.my-widget__button');\n$button.on('click', function() {\n // Сработает для ВСЕХ виджетов на странице!\n});",
|
|
320
|
+
"correct_code": "$widget.each(function(index, widget) {\n var $thisWidget = $(widget);\n var $button = $thisWidget.find('.my-widget__button');\n $button.on('click', function() {\n // Сработает только для этого виджета\n });\n});"
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"id": "global_selectors",
|
|
324
|
+
"title": "Глобальные селекторы",
|
|
325
|
+
"description": "Поиск элементов по всему документу",
|
|
326
|
+
"wrong_code": "$widget.each(function(index, widget) {\n var $button = $('.my-widget__button'); // ❌ Ищет везде!\n});",
|
|
327
|
+
"correct_code": "$widget.each(function(index, widget) {\n var $thisWidget = $(widget);\n var $button = $thisWidget.find('.my-widget__button'); // ✅\n});"
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
"id": "reinventing_wheel",
|
|
331
|
+
"title": "Переизобретение велосипеда",
|
|
332
|
+
"description": "Самописная корзина вместо data-атрибутов",
|
|
333
|
+
"wrong_code": "$button.on('click', function() {\n var productId = $(this).data('id');\n $.ajax({\n url: '/cart_items.json',\n method: 'POST',\n data: { variant_id: productId }\n }).done(function() {\n // Обновить счетчик...\n // Показать уведомление...\n });\n});",
|
|
334
|
+
"correct_code": "<!-- В Liquid: используем CommonJS data-атрибуты -->\n<form data-product-id=\"{{ product.id }}\" action=\"{{ cart_url }}\" method=\"post\">\n <input type=\"hidden\" name=\"variant_id\" value=\"{{ product.variants.first.id }}\">\n <input type=\"hidden\" name=\"quantity\" value=\"1\">\n \n <div data-add-cart-counter>\n <button type=\"button\" data-add-cart-counter-btn>В корзину</button>\n <div class=\"controls\">\n <button data-add-cart-counter-plus type=\"button\">\n <span data-add-cart-counter-count></span> +1\n </button>\n </div>\n </div>\n</form>\n\n<!-- В JS только реакция на событие: -->\nEventBus.subscribe('add_items:insales:cart', function(data) {\n // Показать уведомление\n console.log('Товар добавлен:', data.items);\n});"
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
"id": "using_imports",
|
|
338
|
+
"title": "Использование импортов",
|
|
339
|
+
"description": "Попытка импортировать глобальные модули",
|
|
340
|
+
"wrong_code": "import $ from 'jquery';\nimport { Cart } from 'commonjs';",
|
|
341
|
+
"correct_code": "// Все уже глобально доступно!\n$widget.each(function() {\n Cart.add({ variant_id: 123 });\n});"
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
"id": "return_outside_function",
|
|
345
|
+
"title": "Использование return вне функции",
|
|
346
|
+
"description": "return в глобальной области видимости",
|
|
347
|
+
"wrong_code": "if ($widget.length === 0) return; // SyntaxError: Illegal return statement",
|
|
348
|
+
"correct_code": "if ($widget.length === 0) {\n console.warn('Виджет не найден');\n} else {\n $widget.each(function(index, widget) {\n // Код виджета...\n });\n}\n\n// АЛЬТЕРНАТИВА - обертка в IIFE\n(function() {\n if ($widget.length === 0) return; // ✅ Теперь можно\n \n $widget.each(function(index, widget) {\n // Код виджета...\n });\n})();"
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
"id": "forgot_mobile",
|
|
352
|
+
"title": "Забыли про мобильные",
|
|
353
|
+
"description": "Не учитывают мобильные устройства",
|
|
354
|
+
"wrong_code": "$menu.hover(function() {\n $submenu.show();\n});",
|
|
355
|
+
"correct_code": "$widget.each(function(index, widget) {\n var $thisWidget = $(widget);\n \n if ($(window).width() > 768) {\n // Десктопное поведение\n $menu.hover(function() {\n $submenu.show();\n });\n } else {\n // Мобильное поведение\n $menu.on('click', function() {\n $submenu.toggleClass('is-active');\n });\n }\n});"
|
|
356
|
+
}
|
|
357
|
+
]
|
|
358
|
+
},
|
|
359
|
+
"debugging": {
|
|
360
|
+
"title": "Отладка",
|
|
361
|
+
"description": "Способы отладки JavaScript в виджетах",
|
|
362
|
+
"techniques": [
|
|
363
|
+
{
|
|
364
|
+
"id": "console_log",
|
|
365
|
+
"title": "Console.log для отладки",
|
|
366
|
+
"description": "Использование console.log для отслеживания выполнения",
|
|
367
|
+
"code": "$widget.each(function(index, widget) {\n console.log('Виджет инициализирован:', index);\n \n var $thisWidget = $(widget);\n var $items = $thisWidget.find('.my-widget__item');\n \n console.log('Найдено элементов:', $items.length);\n \n EventBus.subscribe('add_items:insales:cart', function(data) {\n console.log('Товар добавлен:', data);\n });\n});"
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
"id": "check_elements",
|
|
371
|
+
"title": "Проверка наличия элементов",
|
|
372
|
+
"description": "Проверка существования элементов перед их использованием",
|
|
373
|
+
"code": "$widget.each(function(index, widget) {\n var $thisWidget = $(widget);\n var $button = $thisWidget.find('.my-widget__button');\n \n if ($button.length === 0) {\n console.warn('Кнопка не найдена!');\n return; // ✅ ПРАВИЛЬНО - внутри функции\n }\n \n $button.on('click', function() {\n console.log('Клик по кнопке');\n });\n});"
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
"id": "check_widgets",
|
|
377
|
+
"title": "Проверка наличия виджетов на странице",
|
|
378
|
+
"description": "Проверка существования виджетов перед инициализацией",
|
|
379
|
+
"code": "// ❌ НЕПРАВИЛЬНО - return вне функции\nif ($widget.length === 0) return; // SyntaxError!\n\n// ✅ ПРАВИЛЬНО - проверка с ранним выходом\nif ($widget.length === 0) {\n console.warn('Виджет не найден на странице');\n} else {\n $widget.each(function(index, widget) {\n var $thisWidget = $(widget);\n // Код виджета...\n });\n}\n\n// ✅ АЛЬТЕРНАТИВА - обертка в функцию\n(function() {\n if ($widget.length === 0) return; // ✅ Теперь можно использовать return\n \n $widget.each(function(index, widget) {\n var $thisWidget = $(widget);\n // Код виджета...\n });\n})();"
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
"id": "check_events",
|
|
383
|
+
"title": "Проверка событий CommonJS",
|
|
384
|
+
"description": "Отладка событий CommonJS",
|
|
385
|
+
"code": "// Подпишитесь на все события для отладки\nEventBus.subscribe('add_items:insales:cart', function(data) {\n console.log('Cart add:', data);\n});\n\nEventBus.subscribe('update_items:insales:cart', function(data) {\n console.log('Cart update:', data);\n});\n\nEventBus.subscribe('remove_items:insales:cart', function(data) {\n console.log('Cart remove:', data);\n});"
|
|
386
|
+
}
|
|
387
|
+
]
|
|
388
|
+
},
|
|
389
|
+
"linting": {
|
|
390
|
+
"title": "Линтер и проверка",
|
|
391
|
+
"description": "Автоматическая проверка кода",
|
|
392
|
+
"commands": [
|
|
393
|
+
{
|
|
394
|
+
"name": "lint:js:check",
|
|
395
|
+
"description": "Проверить JavaScript",
|
|
396
|
+
"command": "npm run lint:js:check"
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
"name": "lint:js",
|
|
400
|
+
"description": "Автофикс JavaScript",
|
|
401
|
+
"command": "npm run lint:js"
|
|
402
|
+
}
|
|
403
|
+
],
|
|
404
|
+
"rules": [
|
|
405
|
+
"Использование const/let вместо var",
|
|
406
|
+
"Глобальные API доступны (Cart, Products, EventBus, и т.д.)",
|
|
407
|
+
"jQuery ($) доступен глобально",
|
|
408
|
+
"$widget доступен глобально",
|
|
409
|
+
"Максимум 3 уровня вложенности",
|
|
410
|
+
"Сложность функций до 10"
|
|
411
|
+
]
|
|
412
|
+
},
|
|
413
|
+
"checklist": {
|
|
414
|
+
"title": "Чеклист",
|
|
415
|
+
"description": "Проверка перед завершением разработки",
|
|
416
|
+
"items": [
|
|
417
|
+
"Используется $widget.each() для изоляции",
|
|
418
|
+
"Все селекторы относительно $thisWidget.find()",
|
|
419
|
+
"Нет импортов/экспортов",
|
|
420
|
+
"Используются data-атрибуты CommonJS где возможно",
|
|
421
|
+
"Подписка на события через EventBus",
|
|
422
|
+
"Счетчики корзины/избранного работают автоматически",
|
|
423
|
+
"Учтена мобильная версия",
|
|
424
|
+
"Линтер не выдает ошибок",
|
|
425
|
+
"Нет глобальных переменных вне $widget.each()",
|
|
426
|
+
"Код работает для нескольких экземпляров виджета",
|
|
427
|
+
"Нет return вне функций (используйте if/else или IIFE)"
|
|
428
|
+
]
|
|
429
|
+
},
|
|
430
|
+
"golden_rule": {
|
|
431
|
+
"title": "Золотое правило",
|
|
432
|
+
"description": "Пишите МЕНЬШЕ JavaScript! Используйте data-атрибуты CommonJS и подписывайтесь на события. Большинство функций работают БЕЗ кода!",
|
|
433
|
+
"example": "<!-- Форма товара работает через data-атрибуты! -->\n<form data-product-id=\"{{ product.id }}\" action=\"{{ cart_url }}\" method=\"post\">\n <input type=\"hidden\" name=\"variant_id\" value=\"{{ product.variants.first.id }}\">\n <input type=\"hidden\" name=\"quantity\" value=\"1\">\n \n <!-- Кнопка с автоматическим счетчиком -->\n <div data-add-cart-counter>\n <button type=\"button\" data-add-cart-counter-btn>В корзину</button>\n <div class=\"controls\">\n <button data-add-cart-counter-plus type=\"button\">\n <span data-add-cart-counter-count></span> +1\n </button>\n </div>\n </div>\n</form>\n\n<!-- Счетчик корзины обновляется автоматически! -->\n<span data-cart-positions-count></span>\n\n<!-- Счетчик избранного обновляется автоматически! -->\n<span data-ui-favorites-counter></span>\n\n<!-- Кнопка быстрого заказа -->\n<button data-quick-checkout=\"[data-product-id='{{ product.id }}']\">\n Купить в 1 клик\n</button>"
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|