vgapp 0.8.0 → 0.8.2
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/CHANGELOG.md +16 -1
- package/app/langs/en/buttons.json +14 -0
- package/app/langs/en/messages.json +3 -0
- package/app/langs/en/titles.json +3 -0
- package/app/langs/ru/buttons.json +14 -0
- package/app/langs/ru/messages.json +3 -0
- package/app/langs/ru/titles.json +3 -0
- package/app/modules/vgloadmore/js/vgloadmore.js +363 -112
- package/app/modules/vgloadmore/readme.md +145 -0
- package/app/modules/vgrollup/js/vgrollup.js +328 -160
- package/app/modules/vgrollup/readme.md +196 -0
- package/app/modules/vgselect/js/handlers.js +220 -0
- package/app/modules/vgselect/js/vgselect.js +783 -298
- package/app/modules/vgselect/readme.md +180 -0
- package/app/modules/vgselect/scss/_variables.scss +20 -0
- package/app/modules/vgselect/scss/vgselect.scss +42 -2
- package/app/modules/vgsidebar/js/vgsidebar.js +194 -84
- package/app/modules/vgsidebar/readme.md +157 -0
- package/app/modules/vgspy/js/vgspy.js +236 -132
- package/app/modules/vgspy/readme.md +105 -0
- package/app/modules/vgtabs/js/vgtabs.js +290 -182
- package/app/modules/vgtabs/readme.md +156 -0
- package/app/modules/vgtoast/js/vgtoast.js +260 -156
- package/app/modules/vgtoast/readme.md +145 -0
- package/build/vgapp.css +1 -1
- package/build/vgapp.css.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
|
-
# VEGAS-APP 0.8.
|
|
1
|
+
# VEGAS-APP 0.8.2 (Январь, 4, 2025)
|
|
2
|
+
* Оптимизирован модуль VGRollup, см. файл readme.md
|
|
3
|
+
* Оптимизирован модуль VGSidebar, см. файл readme.md
|
|
4
|
+
* Оптимизирован модуль VGTabs, см. файл readme.md
|
|
5
|
+
* Оптимизирован модуль VGSpy, см. файл readme.md
|
|
6
|
+
* Оптимизирован модуль VGToast, см. файл readme.md
|
|
7
|
+
* Оптимизирован и дополнен модуль VGSelect, см. файл readme.md
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# VEGAS-APP 0.8.1 (Январь, 2, 2025)
|
|
12
|
+
* Оптимизирован и дополнен модуль VGLoadMore, см. файл readme.md
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# VEGAS-APP 0.8.0 (Декабрь, 31, 2025)
|
|
2
17
|
* Полностью переписан модуль VGFiles, см. файл readme.md
|
|
3
18
|
* Рефакторинг модуля VGLawCookie, см. файл readme.md
|
|
4
19
|
|
|
@@ -6,5 +6,19 @@
|
|
|
6
6
|
"files": {
|
|
7
7
|
"agree": "Yeah, I agree",
|
|
8
8
|
"cancel": "Cancel"
|
|
9
|
+
},
|
|
10
|
+
"loadmore": {
|
|
11
|
+
"send": "Uploading...",
|
|
12
|
+
"show": "I'm showing you...",
|
|
13
|
+
"text-ajax": "Upload more",
|
|
14
|
+
"text-more": "Show more"
|
|
15
|
+
},
|
|
16
|
+
"rollup": {
|
|
17
|
+
"less": "Roll up",
|
|
18
|
+
"show": "Show",
|
|
19
|
+
"more": " more "
|
|
20
|
+
},
|
|
21
|
+
"select": {
|
|
22
|
+
"load-more": "Upload more"
|
|
9
23
|
}
|
|
10
24
|
}
|
package/app/langs/en/titles.json
CHANGED
|
@@ -6,5 +6,19 @@
|
|
|
6
6
|
"files": {
|
|
7
7
|
"agree": "Да",
|
|
8
8
|
"cancel": "Нет"
|
|
9
|
+
},
|
|
10
|
+
"loadmore": {
|
|
11
|
+
"send": "Загружаю...",
|
|
12
|
+
"show": "Показываю...",
|
|
13
|
+
"text-ajax": "Загрузить еще",
|
|
14
|
+
"text-more": "Показать еще"
|
|
15
|
+
},
|
|
16
|
+
"rollup": {
|
|
17
|
+
"less": "Свернуть",
|
|
18
|
+
"show": "Показать",
|
|
19
|
+
"more": " еще "
|
|
20
|
+
},
|
|
21
|
+
"select": {
|
|
22
|
+
"load-more": "Загрузить еще"
|
|
9
23
|
}
|
|
10
24
|
}
|
package/app/langs/ru/titles.json
CHANGED
|
@@ -1,37 +1,117 @@
|
|
|
1
1
|
import BaseModule from "../../base-module";
|
|
2
2
|
import EventHandler from "../../../utils/js/dom/event";
|
|
3
|
-
import {execute, isObject, mergeDeepObject, normalizeData} from "../../../utils/js/functions";
|
|
3
|
+
import { execute, isObject, mergeDeepObject, normalizeData } from "../../../utils/js/functions";
|
|
4
4
|
import Selectors from "../../../utils/js/dom/selectors";
|
|
5
|
-
import {Manipulator} from "../../../utils/js/dom/manipulator";
|
|
5
|
+
import { Manipulator } from "../../../utils/js/dom/manipulator";
|
|
6
|
+
import { lang_buttons } from "../../../utils/js/components/lang";
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Object} VGLoadMoreParams
|
|
10
|
+
* @property {string} [lang='ru'] - Язык интерфейса (используется для локализации)
|
|
11
|
+
* @property {number} limit - Количество элементов, загружаемых за один раз
|
|
12
|
+
* @property {number} offset - Начальный сдвиг при загрузке
|
|
13
|
+
* @property {boolean} output - Вставлять ли ответ в DOM
|
|
14
|
+
* @property {boolean} autohide - Автоматически скрывать/удалять кнопку при достижении конца
|
|
15
|
+
* @property {boolean} animate - Анимировать появление новых элементов
|
|
16
|
+
* @property {string} append - Позиция вставки: 'after' (в конец) или 'before' (в начало)
|
|
17
|
+
* @property {string} mode - Режим загрузки: 'button' или 'scroll'
|
|
18
|
+
* @property {number} threshold - Отступ в пикселях для срабатывания при прокрутке
|
|
19
|
+
* @property {boolean} debug - Включить отладочные сообщения в консоли
|
|
20
|
+
* @property {boolean} detach - Удалять кнопку из DOM после использования
|
|
21
|
+
* @property {Object} button - Настройки кнопки
|
|
22
|
+
* @property {string} button.text - Текст кнопки по умолчанию
|
|
23
|
+
* @property {string} button.send - Текст во время AJAX-загрузки
|
|
24
|
+
* @property {string} button.show - Текст во время статической загрузки
|
|
25
|
+
* @property {boolean} button.loader - Показывать лоадер
|
|
26
|
+
* @property {string[]} button.classes - CSS-классы кнопки
|
|
27
|
+
* @property {Object} ajax - Настройки AJAX-запроса
|
|
28
|
+
* @property {string} ajax.route - URL для загрузки данных
|
|
29
|
+
* @property {string} ajax.target - Селектор контейнера для вставки
|
|
30
|
+
* @property {string} ajax.method - HTTP-метод ('get', 'post')
|
|
31
|
+
* @property {boolean} ajax.loader - Использовать лоадер
|
|
32
|
+
* @property {boolean} ajax.once - Загружать только один раз
|
|
33
|
+
* @property {boolean} ajax.output - Выводить ли ответ
|
|
34
|
+
* @property {Object} ajax.data - Дополнительные данные для запроса
|
|
35
|
+
*/
|
|
9
36
|
|
|
37
|
+
/**
|
|
38
|
+
* @class VGLoadMore
|
|
39
|
+
* @extends BaseModule
|
|
40
|
+
* @description
|
|
41
|
+
* Модуль подгрузки контента по кнопке или при прокрутке.
|
|
42
|
+
* Поддерживает как статическую подгрузку скрытых элементов из DOM,
|
|
43
|
+
* так и AJAX-загрузку с сервера. Имеет гибкую настройку через data-атрибуты,
|
|
44
|
+
* поддержку языков, анимаций и отладку.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* <!-- Простая подгрузка по кнопке -->
|
|
48
|
+
* <div data-vgloadmore data-limit="3" data-elements="item">
|
|
49
|
+
* <div class="item vg-collapse">Элемент 1</div>
|
|
50
|
+
* <div class="item vg-collapse">Элемент 2</div>
|
|
51
|
+
* ...
|
|
52
|
+
* </div>
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* <!-- AJAX-подгрузка при прокрутке -->
|
|
56
|
+
* <div data-vgloadmore
|
|
57
|
+
* data-ajax-route="/api/load"
|
|
58
|
+
* data-mode="scroll"
|
|
59
|
+
* data-threshold="200">
|
|
60
|
+
* </div>
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
const NAME = 'loadmore';
|
|
64
|
+
const NAME_KEY = 'vg.' + NAME;
|
|
10
65
|
const SELECTOR_DATA_TOGGLE = '[data-vg-toggle="loadmore"]';
|
|
66
|
+
const SELECTOR_DATA_MODULE = '[data-vgloadmore]';
|
|
11
67
|
|
|
12
|
-
const EVENT_KEY_HIDE = `${NAME_KEY}.hide`;
|
|
13
|
-
const EVENT_KEY_HIDDEN = `${NAME_KEY}.hidden`;
|
|
14
|
-
const EVENT_KEY_SHOW = `${NAME_KEY}.show`;
|
|
15
|
-
const EVENT_KEY_SHOWN = `${NAME_KEY}.shown`;
|
|
16
68
|
const EVENT_KEY_LOADED = `${NAME_KEY}.loaded`;
|
|
17
|
-
|
|
69
|
+
const EVENT_KEY_BEFORE_LOAD = `${NAME_KEY}.before.load`;
|
|
18
70
|
const CLASS_NAME_HIDE = 'vg-collapse';
|
|
19
71
|
const CLASS_NAME_SHOW = 'show';
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
72
|
+
const EVENT_KEY_CLICK_DATA_API = `click.${NAME_KEY}.data.api`;
|
|
73
|
+
|
|
74
|
+
class VGLoadMore extends BaseModule {
|
|
75
|
+
/**
|
|
76
|
+
* Имя модуля
|
|
77
|
+
* @type {string}
|
|
78
|
+
*/
|
|
79
|
+
static get NAME() { return NAME; }
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Ключ события модуля
|
|
83
|
+
* @type {string}
|
|
84
|
+
*/
|
|
85
|
+
static get NAME_KEY() { return NAME_KEY; }
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @private
|
|
89
|
+
*/
|
|
24
90
|
constructor(element, params) {
|
|
25
91
|
super(element, params);
|
|
26
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Параметры модуля
|
|
95
|
+
* @type {VGLoadMoreParams}
|
|
96
|
+
* @private
|
|
97
|
+
*/
|
|
27
98
|
this._params = this._getParams(element, mergeDeepObject({
|
|
99
|
+
lang: document.documentElement.lang || 'ru',
|
|
28
100
|
limit: 0,
|
|
29
101
|
offset: 0,
|
|
30
102
|
output: true,
|
|
31
103
|
autohide: true,
|
|
104
|
+
animate: false,
|
|
105
|
+
append: 'after',
|
|
106
|
+
mode: 'button',
|
|
107
|
+
threshold: 100,
|
|
108
|
+
debug: false,
|
|
109
|
+
detach: false,
|
|
32
110
|
button: {
|
|
33
111
|
text: '',
|
|
34
|
-
send: '
|
|
112
|
+
send: 'Загружаю...',
|
|
113
|
+
show: 'Показываю...',
|
|
114
|
+
loader: false,
|
|
35
115
|
classes: []
|
|
36
116
|
},
|
|
37
117
|
ajax: {
|
|
@@ -41,168 +121,339 @@ class VGLoadMore extends BaseModule{
|
|
|
41
121
|
loader: false,
|
|
42
122
|
once: false,
|
|
43
123
|
output: false,
|
|
44
|
-
|
|
124
|
+
data: {}
|
|
125
|
+
}
|
|
45
126
|
}, params));
|
|
46
127
|
|
|
47
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Обсервер пересечения (для режима scroll)
|
|
130
|
+
* @type {IntersectionObserver|null}
|
|
131
|
+
* @private
|
|
132
|
+
*/
|
|
133
|
+
this._observer = null;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Режим прокрутки включён
|
|
137
|
+
* @type {boolean}
|
|
138
|
+
* @private
|
|
139
|
+
*/
|
|
140
|
+
this._isScrollMode = this._params.mode === 'scroll';
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Элемент является триггером (имеет data-vg-toggle)
|
|
144
|
+
* @type {boolean}
|
|
145
|
+
* @private
|
|
146
|
+
*/
|
|
147
|
+
this._isToggleElement = element.hasAttribute('data-vg-toggle');
|
|
148
|
+
|
|
149
|
+
// Локализация текстов кнопок
|
|
150
|
+
this._params.button.send = lang_buttons(this._params.lang, NAME)['send'];
|
|
151
|
+
this._params.button.show = lang_buttons(this._params.lang, NAME)['show'];
|
|
48
152
|
|
|
49
153
|
if (!this._params.button.text) {
|
|
50
|
-
this._params.button.text = this.
|
|
154
|
+
this._params.button.text = this._isToggleElement
|
|
155
|
+
? this._element.innerHTML.trim() || lang_buttons(this._params.lang, NAME)['text-ajax']
|
|
156
|
+
: this._params.ajax.route ? lang_buttons(this._params.lang, NAME)['text-ajax'] : lang_buttons(this._params.lang, NAME)['text-more'];
|
|
51
157
|
}
|
|
52
|
-
}
|
|
53
158
|
|
|
54
|
-
|
|
55
|
-
|
|
159
|
+
if (this._isToggleElement) {
|
|
160
|
+
this._initializeAsButton();
|
|
161
|
+
} else {
|
|
162
|
+
this._initializeContainer();
|
|
163
|
+
}
|
|
56
164
|
}
|
|
57
165
|
|
|
58
|
-
|
|
59
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Инициализация, если элемент — кнопка-триггер
|
|
168
|
+
* @private
|
|
169
|
+
*/
|
|
170
|
+
_initializeAsButton() {
|
|
171
|
+
if (this._isScrollMode) {
|
|
172
|
+
this._initScrollMode();
|
|
173
|
+
}
|
|
60
174
|
}
|
|
61
175
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
buttonParams = normalizeData(el.dataset.button);
|
|
71
|
-
|
|
72
|
-
if (!isObject(buttonParams)) {
|
|
73
|
-
console.error('Дата атрибут data-button должен быть в формате json и передавать объект');
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
176
|
+
/**
|
|
177
|
+
* Инициализация контейнера с элементами
|
|
178
|
+
* @private
|
|
179
|
+
*/
|
|
180
|
+
_initializeContainer() {
|
|
181
|
+
const { elements: itemClass, limit } = this._params;
|
|
182
|
+
const container = this._element;
|
|
183
|
+
const items = Selectors.findAll(`.${itemClass}`, container);
|
|
76
184
|
|
|
77
|
-
if (limit
|
|
78
|
-
console.error('Параметр offset должен быть меньше или равен параметру limit');
|
|
185
|
+
if (!this._params.ajax.route && (items.length <= limit || this._params.offset >= items.length)) {
|
|
79
186
|
return;
|
|
80
187
|
}
|
|
81
188
|
|
|
82
|
-
|
|
189
|
+
items.forEach((item, i) => {
|
|
190
|
+
item.classList.toggle(CLASS_NAME_SHOW, i < limit);
|
|
191
|
+
item.classList.toggle(CLASS_NAME_HIDE, i >= limit);
|
|
192
|
+
});
|
|
83
193
|
|
|
84
|
-
|
|
194
|
+
if (this._params.mode === 'button') {
|
|
195
|
+
this._createAndInsertButton(container);
|
|
196
|
+
} else if (this._isScrollMode) {
|
|
197
|
+
this._initScrollMode();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (this._params.offset === 0) {
|
|
201
|
+
this._params.offset = limit;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
85
204
|
|
|
86
|
-
|
|
205
|
+
/**
|
|
206
|
+
* Создаёт и вставляет кнопку управления
|
|
207
|
+
* @param {HTMLElement} container - Контейнер, рядом с которым вставляется кнопка
|
|
208
|
+
* @private
|
|
209
|
+
*/
|
|
210
|
+
_createAndInsertButton(container) {
|
|
211
|
+
const button = document.createElement('button');
|
|
212
|
+
const buttonText = normalizeData(container.dataset.buttonText) || this._params.button.text;
|
|
87
213
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
214
|
+
const buttonData = {
|
|
215
|
+
limit: this._params.limit,
|
|
216
|
+
offset: this._params.offset,
|
|
217
|
+
output: this._params.output,
|
|
218
|
+
autohide: this._params.autohide,
|
|
219
|
+
animate: this._params.animate,
|
|
220
|
+
append: this._params.append,
|
|
221
|
+
mode: this._params.mode,
|
|
222
|
+
threshold: this._params.threshold,
|
|
223
|
+
debug: this._params.debug,
|
|
224
|
+
detach: this._params.detach,
|
|
225
|
+
elements: this._params.elements,
|
|
226
|
+
'vg-toggle': 'loadmore',
|
|
227
|
+
target: `#${container.id}`
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
Object.assign(buttonData, normalizeData(container.dataset.params));
|
|
231
|
+
|
|
232
|
+
Object.keys(buttonData).forEach(key => {
|
|
233
|
+
Manipulator.set(button, `data-${key}`, buttonData[key]);
|
|
91
234
|
});
|
|
92
235
|
|
|
93
|
-
|
|
236
|
+
button.textContent = buttonText;
|
|
237
|
+
button.classList.add(...this._params.button.classes);
|
|
94
238
|
|
|
95
|
-
|
|
239
|
+
container.parentNode.insertBefore(button, container.nextSibling);
|
|
240
|
+
}
|
|
96
241
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
242
|
+
/**
|
|
243
|
+
* Инициализирует режим прокрутки
|
|
244
|
+
* @private
|
|
245
|
+
*/
|
|
246
|
+
_initScrollMode() {
|
|
247
|
+
this._setupIntersectionObserver();
|
|
248
|
+
this._observeLastVisibleItem();
|
|
249
|
+
}
|
|
104
250
|
|
|
105
|
-
|
|
251
|
+
/**
|
|
252
|
+
* Настраивает IntersectionObserver для отслеживания видимости последнего элемента
|
|
253
|
+
* @private
|
|
254
|
+
*/
|
|
255
|
+
_setupIntersectionObserver() {
|
|
256
|
+
if (this._observer) return;
|
|
257
|
+
|
|
258
|
+
this._observer = new IntersectionObserver((entries) => {
|
|
259
|
+
entries.forEach(entry => {
|
|
260
|
+
if (entry.isIntersecting) {
|
|
261
|
+
this._params.debug && console.log('[VGLoadMore] Пересечение — вызов toggle');
|
|
262
|
+
this.toggle();
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}, {
|
|
266
|
+
root: null,
|
|
267
|
+
rootMargin: `0px 0px ${this._params.threshold}px 0px`,
|
|
268
|
+
threshold: 0.1
|
|
269
|
+
});
|
|
270
|
+
}
|
|
106
271
|
|
|
107
|
-
|
|
272
|
+
/**
|
|
273
|
+
* Начинает отслеживание последнего видимого элемента
|
|
274
|
+
* @private
|
|
275
|
+
*/
|
|
276
|
+
_observeLastVisibleItem() {
|
|
277
|
+
if (!this._observer) return;
|
|
108
278
|
|
|
109
|
-
|
|
110
|
-
buttonParams.classes.forEach(cl => button.classList.add(cl));
|
|
111
|
-
}
|
|
279
|
+
this._observer.disconnect();
|
|
112
280
|
|
|
113
|
-
|
|
281
|
+
const container = Selectors.find(this._params.target) || this._element.parentNode;
|
|
282
|
+
const items = Selectors.findAll(`.${this._params.elements}`, container);
|
|
283
|
+
const visibleItems = items.filter(item => item.classList.contains(CLASS_NAME_SHOW));
|
|
284
|
+
const lastVisible = visibleItems[visibleItems.length - 1];
|
|
114
285
|
|
|
115
|
-
|
|
286
|
+
if (lastVisible instanceof Element) {
|
|
287
|
+
this._observer.observe(lastVisible);
|
|
288
|
+
}
|
|
116
289
|
}
|
|
117
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Основной метод подгрузки — вызывается по клику или при прокрутке
|
|
293
|
+
* @param {Function} [callback] - Колбэк, вызываемый после загрузки
|
|
294
|
+
* @fires VGLoadMore#vg.loadmore.before.load
|
|
295
|
+
* @fires VGLoadMore#vg.loadmore.loaded
|
|
296
|
+
* @public
|
|
297
|
+
*/
|
|
118
298
|
toggle(callback) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
299
|
+
this._params.debug && console.log('[VGLoadMore] toggle()');
|
|
300
|
+
|
|
301
|
+
if (EventHandler.trigger(this._element, EVENT_KEY_BEFORE_LOAD).defaultPrevented) return;
|
|
302
|
+
|
|
303
|
+
const isButton = this._isToggleElement || (['BUTTON', 'A'].includes(this._element.tagName) && !this._isScrollMode);
|
|
304
|
+
if (isButton) {
|
|
305
|
+
this._element.disabled = true;
|
|
306
|
+
this._element.innerHTML = this._params.ajax.route ? this._params.button.send : this._params.button.show;
|
|
123
307
|
}
|
|
308
|
+
|
|
309
|
+
this._params.ajax.route ? this.ajax(callback) : this.staticLoad(callback);
|
|
124
310
|
}
|
|
125
311
|
|
|
312
|
+
/**
|
|
313
|
+
* Выполняет AJAX-запрос для подгрузки данных
|
|
314
|
+
* @param {Function} [callback] - Колбэк после завершения
|
|
315
|
+
* @private
|
|
316
|
+
*/
|
|
126
317
|
ajax(callback) {
|
|
127
|
-
this._params.ajax.
|
|
128
|
-
|
|
129
|
-
|
|
318
|
+
const targetSelector = this._params.ajax.target?.trim();
|
|
319
|
+
let targetEl = null;
|
|
320
|
+
|
|
321
|
+
if (targetSelector) {
|
|
322
|
+
targetEl = Selectors.find(targetSelector);
|
|
323
|
+
} else {
|
|
324
|
+
targetEl = this._element;
|
|
130
325
|
}
|
|
131
326
|
|
|
132
|
-
if (
|
|
133
|
-
|
|
327
|
+
if (!targetEl) {
|
|
328
|
+
console.error('[VGLoadMore] target элемент не найден:', this._params.ajax.target);
|
|
329
|
+
return;
|
|
134
330
|
}
|
|
135
331
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (loader) loader.remove();
|
|
140
|
-
}
|
|
332
|
+
const originalText = this._params.button.text;
|
|
333
|
+
|
|
334
|
+
this._params.ajax.data = { limit: this._params.limit, offset: this._params.offset };
|
|
141
335
|
|
|
142
|
-
|
|
143
|
-
|
|
336
|
+
this._route((status, data, responseTarget) => {
|
|
337
|
+
if (status === 'error' || typeof data?.response !== 'string') return;
|
|
338
|
+
|
|
339
|
+
if (this._params.output) {
|
|
340
|
+
targetEl.insertAdjacentHTML(
|
|
341
|
+
this._params.append === 'after' ? 'beforeend' : 'afterbegin',
|
|
342
|
+
data.response
|
|
343
|
+
);
|
|
144
344
|
}
|
|
145
345
|
|
|
146
|
-
this._params.offset
|
|
147
|
-
this.
|
|
346
|
+
this._params.offset += this._params.limit;
|
|
347
|
+
this._restoreElementState(originalText);
|
|
348
|
+
this._observeLastVisibleItem();
|
|
148
349
|
|
|
149
|
-
|
|
150
|
-
|
|
350
|
+
const noMoreData = !data.response.trim();
|
|
351
|
+
if (this._params.autohide && this._params.detach && noMoreData) {
|
|
352
|
+
this._autohideTrigger();
|
|
151
353
|
}
|
|
152
354
|
|
|
153
|
-
EventHandler.trigger(this._element, EVENT_KEY_LOADED, {stats: status, data
|
|
154
|
-
execute(callback, [this, data,
|
|
355
|
+
EventHandler.trigger(this._element, EVENT_KEY_LOADED, { stats: status, data });
|
|
356
|
+
execute(callback, [this, data, responseTarget, status]);
|
|
357
|
+
}, (error) => {
|
|
358
|
+
this._restoreElementState(originalText);
|
|
359
|
+
console.error('[VGLoadMore] AJAX ошибка', error);
|
|
155
360
|
});
|
|
156
361
|
}
|
|
157
362
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
363
|
+
/**
|
|
364
|
+
* Подгружает элементы из DOM (статический режим)
|
|
365
|
+
* @param {Function} [callback] - Колбэк после завершения
|
|
366
|
+
* @private
|
|
367
|
+
*/
|
|
368
|
+
staticLoad(callback) {
|
|
369
|
+
const container = Selectors.find(this._params.target) || this._element.parentNode;
|
|
370
|
+
const items = Selectors.findAll(`.${this._params.elements}`, container);
|
|
371
|
+
const start = this._params.offset;
|
|
372
|
+
const end = start + this._params.limit;
|
|
373
|
+
const newItems = items.slice(start, end);
|
|
374
|
+
|
|
375
|
+
if (newItems.length === 0) return;
|
|
376
|
+
|
|
377
|
+
if (this._isToggleElement || (['BUTTON', 'A'].includes(this._element.tagName) && !this._isScrollMode)) {
|
|
378
|
+
this._element.disabled = true;
|
|
379
|
+
this._element.innerHTML = this._params.button.show;
|
|
167
380
|
}
|
|
168
381
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
382
|
+
newItems.forEach(item => {
|
|
383
|
+
item.classList.replace(CLASS_NAME_HIDE, CLASS_NAME_SHOW);
|
|
384
|
+
if (this._params.animate) {
|
|
385
|
+
item.style.opacity = 0;
|
|
386
|
+
requestAnimationFrame(() => {
|
|
387
|
+
item.style.transition = 'opacity 0.3s ease';
|
|
388
|
+
item.style.opacity = 1;
|
|
389
|
+
});
|
|
174
390
|
}
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
this._params.offset = end;
|
|
394
|
+
this._observeLastVisibleItem();
|
|
395
|
+
|
|
396
|
+
const remaining = items.slice(end);
|
|
397
|
+
if (this._params.autohide && this._params.detach && remaining.length === 0) {
|
|
398
|
+
this._autohideTrigger();
|
|
175
399
|
}
|
|
176
400
|
|
|
401
|
+
this._restoreElementState(this._params.button.text);
|
|
177
402
|
execute(callback, [this, this._element]);
|
|
178
403
|
}
|
|
179
404
|
|
|
180
|
-
|
|
181
|
-
|
|
405
|
+
/**
|
|
406
|
+
* Восстанавливает состояние кнопки (включает, устанавливает текст)
|
|
407
|
+
* @param {string} text - Текст кнопки
|
|
408
|
+
* @private
|
|
409
|
+
*/
|
|
410
|
+
_restoreElementState(text) {
|
|
411
|
+
const isButton = this._isToggleElement || this._element.tagName === 'BUTTON';
|
|
412
|
+
if (isButton && !this._isScrollMode) {
|
|
413
|
+
this._element.disabled = false;
|
|
414
|
+
this._element.innerHTML = text;
|
|
415
|
+
}
|
|
182
416
|
}
|
|
183
417
|
|
|
184
|
-
|
|
185
|
-
|
|
418
|
+
/**
|
|
419
|
+
* Автоматически скрывает или удаляет элемент при завершении
|
|
420
|
+
* @private
|
|
421
|
+
*/
|
|
422
|
+
_autohideTrigger() {
|
|
423
|
+
if (this._isScrollMode) {
|
|
424
|
+
this._observer?.disconnect();
|
|
425
|
+
this._observer = null;
|
|
426
|
+
} else if (this._params.detach && this._element.parentNode) {
|
|
427
|
+
this._element.remove();
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Полная деинициализация модуля
|
|
433
|
+
* @override
|
|
434
|
+
*/
|
|
435
|
+
dispose() {
|
|
436
|
+
this._observer?.disconnect();
|
|
437
|
+
super.dispose();
|
|
186
438
|
}
|
|
187
439
|
}
|
|
188
440
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
441
|
+
/**
|
|
442
|
+
* Автоматическая инициализация на элементах с data-vgloadmore
|
|
443
|
+
*/
|
|
444
|
+
EventHandler.on(document, 'DOMContentLoaded', () => {
|
|
445
|
+
Selectors.findAll(SELECTOR_DATA_MODULE).forEach(el => {
|
|
446
|
+
!el.dataset.initialized && VGLoadMore.getOrCreateInstance(el);
|
|
447
|
+
el.dataset.initialized = 'true';
|
|
448
|
+
});
|
|
193
449
|
});
|
|
194
450
|
|
|
195
451
|
/**
|
|
196
|
-
*
|
|
452
|
+
* Обработка кликов по элементам с data-vg-toggle="loadmore"
|
|
197
453
|
*/
|
|
198
454
|
EventHandler.on(document, EVENT_KEY_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (['A', 'AREA'].includes(this.tagName)) event.preventDefault();
|
|
202
|
-
|
|
203
|
-
const instance = VGLoadMore.getOrCreateInstance(target);
|
|
204
|
-
instance.toggle();
|
|
455
|
+
['A', 'AREA'].includes(this.tagName) && event.preventDefault();
|
|
456
|
+
VGLoadMore.getOrCreateInstance(this).toggle();
|
|
205
457
|
});
|
|
206
458
|
|
|
207
|
-
|
|
208
459
|
export default VGLoadMore;
|