vgapp 0.7.9 → 0.8.1
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 +9 -0
- package/app/langs/en/buttons.json +16 -0
- package/app/langs/en/messages.json +32 -0
- package/app/langs/en/titles.json +6 -0
- package/app/langs/ru/buttons.json +16 -0
- package/app/langs/ru/messages.json +32 -0
- package/app/langs/ru/titles.json +6 -0
- package/app/modules/base-module.js +12 -1
- package/app/modules/module-fn.js +20 -9
- package/app/modules/vgalert/js/vgalert.js +12 -6
- package/app/modules/vgalert/readme.md +1 -1
- package/app/modules/vgcollapse/readme.md +1 -1
- package/app/modules/vgdropdown/js/vgdropdown.js +140 -38
- package/app/modules/vgdropdown/readme.md +225 -0
- package/app/modules/vgfiles/js/base.js +499 -0
- package/app/modules/vgfiles/js/droppable.js +159 -0
- package/app/modules/vgfiles/js/loader.js +389 -0
- package/app/modules/vgfiles/js/render.js +83 -0
- package/app/modules/vgfiles/js/sortable.js +155 -0
- package/app/modules/vgfiles/js/vgfiles.js +796 -280
- package/app/modules/vgfiles/readme.md +193 -0
- package/app/modules/vgfiles/scss/_animations.scss +18 -0
- package/app/modules/vgfiles/scss/_mixins.scss +73 -0
- package/app/modules/vgfiles/scss/_variables.scss +103 -26
- package/app/modules/vgfiles/scss/vgfiles.scss +573 -60
- package/app/modules/vgformsender/js/vgformsender.js +5 -1
- package/app/modules/vgformsender/readme.md +1 -1
- package/app/modules/vglawcookie/js/vglawcookie.js +96 -62
- package/app/modules/vglawcookie/readme.md +102 -0
- package/app/modules/vgloadmore/js/vgloadmore.js +212 -112
- package/app/modules/vgloadmore/readme.md +145 -0
- package/app/modules/vgsidebar/js/vgsidebar.js +6 -4
- package/app/utils/js/components/ajax.js +172 -122
- package/app/utils/js/components/animation.js +124 -39
- package/app/utils/js/components/backdrop.js +54 -31
- package/app/utils/js/components/lang.js +69 -88
- package/app/utils/js/components/params.js +34 -31
- package/app/utils/js/components/scrollbar.js +118 -67
- package/app/utils/js/components/templater.js +14 -4
- package/app/utils/js/dom/cookie.js +107 -64
- package/app/utils/js/dom/data.js +68 -20
- package/app/utils/js/dom/event.js +272 -239
- package/app/utils/js/dom/manipulator.js +135 -62
- package/app/utils/js/dom/selectors.js +134 -59
- package/app/utils/js/functions.js +183 -349
- package/build/vgapp.css +1 -1
- package/build/vgapp.css.map +1 -1
- package/package.json +1 -1
- package/app/utils/js/components/overflow.js +0 -28
|
@@ -1,37 +1,42 @@
|
|
|
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";
|
|
6
|
-
|
|
7
|
-
const NAME = 'loadmore';
|
|
8
|
-
const NAME_KEY = 'vg.loadmore';
|
|
5
|
+
import { Manipulator } from "../../../utils/js/dom/manipulator";
|
|
6
|
+
import {lang_buttons} from "../../../utils/js/components/lang";
|
|
9
7
|
|
|
8
|
+
const NAME = 'loadmore';
|
|
9
|
+
const NAME_KEY = 'vg.loadmore';
|
|
10
10
|
const SELECTOR_DATA_TOGGLE = '[data-vg-toggle="loadmore"]';
|
|
11
|
+
const SELECTOR_DATA_MODULE = '[data-vgloadmore]';
|
|
11
12
|
|
|
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
13
|
const EVENT_KEY_LOADED = `${NAME_KEY}.loaded`;
|
|
17
|
-
|
|
14
|
+
const EVENT_KEY_BEFORE_LOAD = `${NAME_KEY}.before.load`;
|
|
18
15
|
const CLASS_NAME_HIDE = 'vg-collapse';
|
|
19
16
|
const CLASS_NAME_SHOW = 'show';
|
|
17
|
+
const EVENT_KEY_CLICK_DATA_API = `click.${NAME_KEY}.data.api`;
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class VGLoadMore extends BaseModule{
|
|
19
|
+
class VGLoadMore extends BaseModule {
|
|
24
20
|
constructor(element, params) {
|
|
25
21
|
super(element, params);
|
|
26
22
|
|
|
27
23
|
this._params = this._getParams(element, mergeDeepObject({
|
|
24
|
+
lang: document.documentElement.lang || 'ru',
|
|
28
25
|
limit: 0,
|
|
29
26
|
offset: 0,
|
|
30
27
|
output: true,
|
|
31
28
|
autohide: true,
|
|
29
|
+
animate: false,
|
|
30
|
+
append: 'after',
|
|
31
|
+
mode: 'button',
|
|
32
|
+
threshold: 100,
|
|
33
|
+
debug: false,
|
|
34
|
+
detach: false,
|
|
32
35
|
button: {
|
|
33
36
|
text: '',
|
|
34
|
-
send: '
|
|
37
|
+
send: 'Загружаю...',
|
|
38
|
+
show: 'Показываю...',
|
|
39
|
+
loader: false,
|
|
35
40
|
classes: []
|
|
36
41
|
},
|
|
37
42
|
ajax: {
|
|
@@ -41,168 +46,263 @@ class VGLoadMore extends BaseModule{
|
|
|
41
46
|
loader: false,
|
|
42
47
|
once: false,
|
|
43
48
|
output: false,
|
|
44
|
-
|
|
49
|
+
data: {}
|
|
50
|
+
}
|
|
45
51
|
}, params));
|
|
46
52
|
|
|
47
|
-
this.
|
|
53
|
+
this._observer = null;
|
|
54
|
+
this._isScrollMode = this._params.mode === 'scroll';
|
|
55
|
+
this._isToggleElement = element.hasAttribute('data-vg-toggle');
|
|
56
|
+
|
|
57
|
+
this._params.button.send = lang_buttons(this._params.lang, NAME)['send'];
|
|
58
|
+
this._params.button.show = lang_buttons(this._params.lang, NAME)['show'];
|
|
48
59
|
|
|
49
60
|
if (!this._params.button.text) {
|
|
50
|
-
this._params.button.text = this.
|
|
61
|
+
this._params.button.text = this._isToggleElement
|
|
62
|
+
? this._element.innerHTML.trim() || lang_buttons(this._params.lang, NAME)['text-ajax']
|
|
63
|
+
: this._params.ajax.route ? lang_buttons(this._params.lang, NAME)['text-ajax'] : lang_buttons(this._params.lang, NAME)['text-more'];
|
|
51
64
|
}
|
|
52
|
-
}
|
|
53
65
|
|
|
54
|
-
|
|
55
|
-
|
|
66
|
+
if (this._isToggleElement) {
|
|
67
|
+
this._initializeAsButton();
|
|
68
|
+
} else {
|
|
69
|
+
this._initializeContainer();
|
|
70
|
+
}
|
|
56
71
|
}
|
|
57
72
|
|
|
58
|
-
static get
|
|
59
|
-
|
|
60
|
-
}
|
|
73
|
+
static get NAME() { return NAME; }
|
|
74
|
+
static get NAME_KEY() { return NAME_KEY; }
|
|
61
75
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
limit = normalizeData(el.dataset.limit),
|
|
66
|
-
offset = normalizeData(el.dataset.offset),
|
|
67
|
-
output = el.dataset.output || 'true',
|
|
68
|
-
autohide = el.dataset.autohide || 'true',
|
|
69
|
-
params = el.dataset.params,
|
|
70
|
-
buttonParams = normalizeData(el.dataset.button);
|
|
71
|
-
|
|
72
|
-
if (!isObject(buttonParams)) {
|
|
73
|
-
console.error('Дата атрибут data-button должен быть в формате json и передавать объект');
|
|
74
|
-
return;
|
|
76
|
+
_initializeAsButton() {
|
|
77
|
+
if (this._isScrollMode) {
|
|
78
|
+
this._initScrollMode();
|
|
75
79
|
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
_initializeContainer() {
|
|
83
|
+
const { elements: itemClass, limit } = this._params;
|
|
84
|
+
const container = this._element;
|
|
85
|
+
const items = Selectors.findAll(`.${itemClass}`, container);
|
|
76
86
|
|
|
77
|
-
if (limit
|
|
78
|
-
console.error('Параметр offset должен быть меньше или равен параметру limit');
|
|
87
|
+
if (!this._params.ajax.route && (items.length <= limit || this._params.offset >= items.length)) {
|
|
79
88
|
return;
|
|
80
89
|
}
|
|
81
90
|
|
|
82
|
-
|
|
91
|
+
items.forEach((item, i) => {
|
|
92
|
+
item.classList.toggle(CLASS_NAME_SHOW, i < limit);
|
|
93
|
+
item.classList.toggle(CLASS_NAME_HIDE, i >= limit);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (this._params.mode === 'button') {
|
|
97
|
+
this._createAndInsertButton(container);
|
|
98
|
+
} else if (this._isScrollMode) {
|
|
99
|
+
this._initScrollMode();
|
|
100
|
+
}
|
|
83
101
|
|
|
84
|
-
|
|
102
|
+
if (this._params.offset === 0) {
|
|
103
|
+
this._params.offset = limit;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
85
106
|
|
|
86
|
-
|
|
107
|
+
_createAndInsertButton(container) {
|
|
108
|
+
const button = document.createElement('button');
|
|
109
|
+
const buttonText = normalizeData(container.dataset.buttonText) || this._params.button.text;
|
|
87
110
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
111
|
+
const buttonData = {
|
|
112
|
+
limit: this._params.limit,
|
|
113
|
+
offset: this._params.offset,
|
|
114
|
+
output: this._params.output,
|
|
115
|
+
autohide: this._params.autohide,
|
|
116
|
+
animate: this._params.animate,
|
|
117
|
+
append: this._params.append,
|
|
118
|
+
mode: this._params.mode,
|
|
119
|
+
threshold: this._params.threshold,
|
|
120
|
+
debug: this._params.debug,
|
|
121
|
+
detach: this._params.detach,
|
|
122
|
+
elements: this._params.elements,
|
|
123
|
+
'vg-toggle': 'loadmore',
|
|
124
|
+
target: `#${container.id}`
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
Object.assign(buttonData, normalizeData(container.dataset.params));
|
|
128
|
+
|
|
129
|
+
Object.keys(buttonData).forEach(key => {
|
|
130
|
+
Manipulator.set(button, `data-${key}`, buttonData[key]);
|
|
91
131
|
});
|
|
92
132
|
|
|
93
|
-
|
|
133
|
+
button.textContent = buttonText;
|
|
134
|
+
button.classList.add(...this._params.button.classes);
|
|
94
135
|
|
|
95
|
-
|
|
136
|
+
container.parentNode.insertBefore(button, container.nextSibling);
|
|
137
|
+
}
|
|
96
138
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
Manipulator.set(button, 'data-elements', items);
|
|
102
|
-
Manipulator.set(button, 'data-vg-toggle', 'loadmore');
|
|
103
|
-
Manipulator.set(button, 'data-target', '#' + id);
|
|
139
|
+
_initScrollMode() {
|
|
140
|
+
this._setupIntersectionObserver();
|
|
141
|
+
this._observeLastVisibleItem();
|
|
142
|
+
}
|
|
104
143
|
|
|
105
|
-
|
|
144
|
+
_setupIntersectionObserver() {
|
|
145
|
+
if (this._observer) return;
|
|
146
|
+
|
|
147
|
+
this._observer = new IntersectionObserver((entries) => {
|
|
148
|
+
entries.forEach(entry => {
|
|
149
|
+
if (entry.isIntersecting) {
|
|
150
|
+
this._params.debug && console.log('[VGLoadMore] Пересечение — вызов toggle');
|
|
151
|
+
this.toggle();
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}, {
|
|
155
|
+
root: null,
|
|
156
|
+
rootMargin: `0px 0px ${this._params.threshold}px 0px`,
|
|
157
|
+
threshold: 0.1
|
|
158
|
+
});
|
|
159
|
+
}
|
|
106
160
|
|
|
107
|
-
|
|
161
|
+
_observeLastVisibleItem() {
|
|
162
|
+
if (!this._observer) return;
|
|
108
163
|
|
|
109
|
-
|
|
110
|
-
buttonParams.classes.forEach(cl => button.classList.add(cl));
|
|
111
|
-
}
|
|
164
|
+
this._observer.disconnect();
|
|
112
165
|
|
|
113
|
-
|
|
166
|
+
const container = Selectors.find(this._params.target) || this._element.parentNode;
|
|
167
|
+
const items = Selectors.findAll(`.${this._params.elements}`, container);
|
|
168
|
+
const visibleItems = items.filter(item => item.classList.contains(CLASS_NAME_SHOW));
|
|
169
|
+
const lastVisible = visibleItems[visibleItems.length - 1];
|
|
114
170
|
|
|
115
|
-
|
|
171
|
+
if (lastVisible instanceof Element) {
|
|
172
|
+
this._observer.observe(lastVisible);
|
|
173
|
+
}
|
|
116
174
|
}
|
|
117
175
|
|
|
118
176
|
toggle(callback) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
177
|
+
this._params.debug && console.log('[VGLoadMore] toggle()');
|
|
178
|
+
|
|
179
|
+
if (EventHandler.trigger(this._element, EVENT_KEY_BEFORE_LOAD).defaultPrevented) return;
|
|
180
|
+
|
|
181
|
+
const isButton = this._isToggleElement || (['BUTTON', 'A'].includes(this._element.tagName) && !this._isScrollMode);
|
|
182
|
+
if (isButton) {
|
|
183
|
+
this._element.disabled = true;
|
|
184
|
+
this._element.innerHTML = this._params.ajax.route ? this._params.button.send : this._params.button.show;
|
|
123
185
|
}
|
|
186
|
+
|
|
187
|
+
this._params.ajax.route ? this.ajax(callback) : this.staticLoad(callback);
|
|
124
188
|
}
|
|
125
189
|
|
|
126
190
|
ajax(callback) {
|
|
127
|
-
this._params.ajax.
|
|
128
|
-
|
|
129
|
-
|
|
191
|
+
const targetSelector = this._params.ajax.target?.trim();
|
|
192
|
+
let targetEl = null;
|
|
193
|
+
|
|
194
|
+
if (targetSelector) {
|
|
195
|
+
targetEl = Selectors.find(targetSelector);
|
|
196
|
+
} else {
|
|
197
|
+
targetEl = this._element
|
|
130
198
|
}
|
|
131
199
|
|
|
132
|
-
|
|
133
|
-
|
|
200
|
+
const originalText = this._params.button.text;
|
|
201
|
+
|
|
202
|
+
if (!targetEl) {
|
|
203
|
+
console.error('[VGLoadMore] target элемент не найден:', this._params.ajax.target);
|
|
204
|
+
return;
|
|
134
205
|
}
|
|
135
206
|
|
|
136
|
-
this.
|
|
137
|
-
if ('loader' in this._params.ajax && this._params.ajax.loader) {
|
|
138
|
-
let loader = Selectors.find('.vg-loader', target);
|
|
139
|
-
if (loader) loader.remove();
|
|
140
|
-
}
|
|
207
|
+
this._params.ajax.data = { limit: this._params.limit, offset: this._params.offset };
|
|
141
208
|
|
|
142
|
-
|
|
143
|
-
|
|
209
|
+
this._route((status, data, responseTarget) => {
|
|
210
|
+
if (status === 'error' || typeof data?.response !== 'string') return;
|
|
211
|
+
|
|
212
|
+
if (this._params.output) {
|
|
213
|
+
targetEl.insertAdjacentHTML(
|
|
214
|
+
this._params.append === 'after' ? 'beforeend' : 'afterbegin',
|
|
215
|
+
data.response
|
|
216
|
+
);
|
|
144
217
|
}
|
|
145
218
|
|
|
146
|
-
this._params.offset
|
|
147
|
-
this.
|
|
219
|
+
this._params.offset += this._params.limit;
|
|
220
|
+
this._restoreElementState(originalText);
|
|
221
|
+
this._observeLastVisibleItem();
|
|
148
222
|
|
|
149
|
-
|
|
150
|
-
|
|
223
|
+
const noMoreData = !data.response.trim();
|
|
224
|
+
if (this._params.autohide && this._params.detach && noMoreData) {
|
|
225
|
+
this._autohideTrigger();
|
|
151
226
|
}
|
|
152
227
|
|
|
153
|
-
EventHandler.trigger(this._element, EVENT_KEY_LOADED, {stats: status, data
|
|
154
|
-
execute(callback, [this, data,
|
|
228
|
+
EventHandler.trigger(this._element, EVENT_KEY_LOADED, { stats: status, data });
|
|
229
|
+
execute(callback, [this, data, responseTarget, status]);
|
|
230
|
+
}, (error) => {
|
|
231
|
+
this._restoreElementState(originalText);
|
|
232
|
+
console.error('[VGLoadMore] AJAX ошибка', error);
|
|
155
233
|
});
|
|
156
234
|
}
|
|
157
235
|
|
|
158
|
-
|
|
159
|
-
|
|
236
|
+
staticLoad(callback) {
|
|
237
|
+
const container = Selectors.find(this._params.target) || this._element.parentNode;
|
|
238
|
+
const items = Selectors.findAll(`.${this._params.elements}`, container);
|
|
239
|
+
const start = this._params.offset;
|
|
240
|
+
const end = start + this._params.limit;
|
|
241
|
+
const newItems = items.slice(start, end);
|
|
160
242
|
|
|
161
|
-
|
|
162
|
-
items = Selectors.findAll('.' + this._params.elements, container);
|
|
243
|
+
if (newItems.length === 0) return;
|
|
163
244
|
|
|
164
|
-
if (
|
|
165
|
-
|
|
166
|
-
this.
|
|
245
|
+
if (this._isToggleElement || (['BUTTON', 'A'].includes(this._element.tagName) && !this._isScrollMode)) {
|
|
246
|
+
this._element.disabled = true;
|
|
247
|
+
this._element.innerHTML = this._params.button.show;
|
|
167
248
|
}
|
|
168
249
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
250
|
+
newItems.forEach(item => {
|
|
251
|
+
item.classList.replace(CLASS_NAME_HIDE, CLASS_NAME_SHOW);
|
|
252
|
+
if (this._params.animate) {
|
|
253
|
+
item.style.opacity = 0;
|
|
254
|
+
requestAnimationFrame(() => {
|
|
255
|
+
item.style.transition = 'opacity 0.3s ease';
|
|
256
|
+
item.style.opacity = 1;
|
|
257
|
+
});
|
|
174
258
|
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
this._params.offset = end;
|
|
262
|
+
this._observeLastVisibleItem();
|
|
263
|
+
|
|
264
|
+
const remaining = items.slice(end);
|
|
265
|
+
if (this._params.autohide && this._params.detach && remaining.length === 0) {
|
|
266
|
+
this._autohideTrigger();
|
|
175
267
|
}
|
|
176
268
|
|
|
269
|
+
this._restoreElementState(this._params.button.text);
|
|
177
270
|
execute(callback, [this, this._element]);
|
|
178
271
|
}
|
|
179
272
|
|
|
180
|
-
|
|
181
|
-
|
|
273
|
+
_restoreElementState(text) {
|
|
274
|
+
const isButton = this._isToggleElement || this._element.tagName === 'BUTTON';
|
|
275
|
+
if (isButton && !this._isScrollMode) {
|
|
276
|
+
this._element.disabled = false;
|
|
277
|
+
this._element.innerHTML = text;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
_autohideTrigger() {
|
|
282
|
+
if (this._isScrollMode) {
|
|
283
|
+
this._observer?.disconnect();
|
|
284
|
+
this._observer = null;
|
|
285
|
+
} else if (this._params.detach && this._element.parentNode) {
|
|
286
|
+
this._element.remove();
|
|
287
|
+
}
|
|
182
288
|
}
|
|
183
289
|
|
|
184
|
-
|
|
185
|
-
|
|
290
|
+
dispose() {
|
|
291
|
+
this._observer?.disconnect();
|
|
292
|
+
super.dispose();
|
|
186
293
|
}
|
|
187
294
|
}
|
|
188
295
|
|
|
189
|
-
EventHandler.on(document, 'DOMContentLoaded',
|
|
190
|
-
|
|
191
|
-
VGLoadMore.
|
|
192
|
-
|
|
296
|
+
EventHandler.on(document, 'DOMContentLoaded', () => {
|
|
297
|
+
Selectors.findAll(SELECTOR_DATA_MODULE).forEach(el => {
|
|
298
|
+
!el.dataset.initialized && VGLoadMore.getOrCreateInstance(el);
|
|
299
|
+
el.dataset.initialized = 'true';
|
|
300
|
+
});
|
|
193
301
|
});
|
|
194
302
|
|
|
195
|
-
/**
|
|
196
|
-
* Data API implementation
|
|
197
|
-
*/
|
|
198
303
|
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();
|
|
304
|
+
['A', 'AREA'].includes(this.tagName) && event.preventDefault();
|
|
305
|
+
VGLoadMore.getOrCreateInstance(this).toggle();
|
|
205
306
|
});
|
|
206
307
|
|
|
207
|
-
|
|
208
308
|
export default VGLoadMore;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# VGLoadMore — Модуль подгрузки контента
|
|
2
|
+
|
|
3
|
+
Модуль **VGLoadMore** позволяет реализовать подгрузку контента по кнопке или при прокрутке (infinite scroll). Поддерживает как статическую подгрузку элементов из DOM, так и AJAX-запросы на сервер. Удобно использовать для лент, списков, галерей и других контейнеров с большим количеством элементов.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## ✅ Возможности
|
|
8
|
+
|
|
9
|
+
- 🔘 Подгрузка по **кнопке** или **при прокрутке**
|
|
10
|
+
- 📥 **AJAX-загрузка** с сервера
|
|
11
|
+
- 🧩 **Статическая подгрузка** скрытых элементов из DOM
|
|
12
|
+
- 🎯 Автоматическое скрытие кнопки при достижении конца
|
|
13
|
+
- 🖱 Поддержка `data-vg-toggle` и самостоятельная инициализация
|
|
14
|
+
- 🧪 Гибкая настройка через `data-*` атрибуты или JS
|
|
15
|
+
- 🔄 Анимации показа элементов
|
|
16
|
+
- 🛠 Полная отладка через `data-debug="true"`
|
|
17
|
+
- 🌐 Многоязычная поддержка** (настраиваемые кнопки)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 🌐 Многоязычная поддержка
|
|
22
|
+
|
|
23
|
+
Модуль `VGFiles` полностью поддерживает локализацию интерфейса. Все тексты кнопок.
|
|
24
|
+
|
|
25
|
+
### ✅ Автоматическое определение языка
|
|
26
|
+
|
|
27
|
+
Язык определяется автоматически:
|
|
28
|
+
- Из атрибута `lang` тега `<html>`: `<html lang="en">`
|
|
29
|
+
- Или через параметр `lang` при инициализации модуля.
|
|
30
|
+
|
|
31
|
+
Если язык не указан — используется **`ru`** (русский).
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 🔧 Установка
|
|
36
|
+
|
|
37
|
+
Модуль автоматически инициализируется при наличии атрибутов `data-vgloadmore` или `data-vg-toggle="loadmore"`.
|
|
38
|
+
|
|
39
|
+
## 🧩 Режимы работы
|
|
40
|
+
|
|
41
|
+
### 1. Подгрузка по кнопке
|
|
42
|
+
### 2. Подгрузка при прокрутке
|
|
43
|
+
|
|
44
|
+
→ Подгрузка начнётся, когда последний видимый элемент приблизится к нижней границе окна.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## ⚙️ Настройки (data-атрибуты)
|
|
49
|
+
|
|
50
|
+
| Атрибут | Описание | По умолчанию |
|
|
51
|
+
|-------|--------|-------------|
|
|
52
|
+
| `data-limit` | Сколько элементов подгружать за раз | `0` |
|
|
53
|
+
| `data-offset` | Начальный сдвиг | `0` |
|
|
54
|
+
| `data-elements` | Класс подгружаемых элементов | — |
|
|
55
|
+
| `data-mode` | `button` или `scroll` | `button` |
|
|
56
|
+
| `data-threshold` | Отступ в px для scroll-режима | `100` |
|
|
57
|
+
| `data-autohide` | Скрывать кнопку при окончании | `true` |
|
|
58
|
+
| `data-detach` | Удалять кнопку из DOM | `false` |
|
|
59
|
+
| `data-animate` | Плавное появление элементов | `false` |
|
|
60
|
+
| `data-append` | `after` или `before` | `after` |
|
|
61
|
+
| `data-debug` | Включить логи в консоль | `false` |
|
|
62
|
+
| `data-button-text` | Текст кнопки | Авто |
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
### AJAX-настройки
|
|
67
|
+
|
|
68
|
+
| Атрибут | Описание | По умолчанию |
|
|
69
|
+
|-------|--------|-------------|
|
|
70
|
+
| `data-ajax-route` | URL для запроса | — |
|
|
71
|
+
| `data-ajax-target` | Куда вставлять ответ | `parent` |
|
|
72
|
+
| `data-ajax-method` | HTTP-метод | `get` |
|
|
73
|
+
| `data-ajax-output` | Вставлять ли ответ | `false` |
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
### Текст кнопки
|
|
78
|
+
|
|
79
|
+
| Состояние | Текст |
|
|
80
|
+
|---------|------|
|
|
81
|
+
| По умолчанию (AJAX) | `Загрузить еще` |
|
|
82
|
+
| По умолчанию (статика) | `Показать еще` |
|
|
83
|
+
| При загрузке (AJAX) | `Загружаю...` |
|
|
84
|
+
| При загрузке (статика) | `Показываю...` |
|
|
85
|
+
|
|
86
|
+
→ Настраивается через `data-button='{"send": "...", "show": "..."}'`
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 🔌 JavaScript API
|
|
91
|
+
|
|
92
|
+
### Инициализация
|
|
93
|
+
```js
|
|
94
|
+
import VGLoadMore from 'app/modules/vgloadmore/js/vgloadmore';
|
|
95
|
+
const instance = VGLoadMore.getOrCreateInstance(element, { limit: 5, ajax: { route: '/api/items' } });
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Методы
|
|
99
|
+
```js
|
|
100
|
+
instance.toggle(); // Ручной вызов подгрузки
|
|
101
|
+
instance.dispose(); // Уничтожить инстанс
|
|
102
|
+
```
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 📣 События
|
|
106
|
+
|
|
107
|
+
| Событие | Описание |
|
|
108
|
+
|-------|--------|
|
|
109
|
+
| `vg.loadmore.before.load` | Перед подгрузкой |
|
|
110
|
+
| `vg.loadmore.loaded` | После подгрузки |
|
|
111
|
+
|
|
112
|
+
```js
|
|
113
|
+
element.addEventListener('vg.loadmore.loaded', (e) => { console.log('Загружено:', e.detail.data); });
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 💡 Советы
|
|
119
|
+
|
|
120
|
+
- Используйте класс `vg-collapse`, чтобы изначально скрыть элементы.
|
|
121
|
+
- В `scroll`-режиме кнопка не создаётся.
|
|
122
|
+
- Если `data-ajax-route` указан, модуль всегда использует AJAX.
|
|
123
|
+
- При `data-detach="true"` кнопка удаляется из DOM после окончания.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 🐞 Отладка
|
|
128
|
+
|
|
129
|
+
Включите режим отладки:
|
|
130
|
+
```html
|
|
131
|
+
data-debug="true"
|
|
132
|
+
```
|
|
133
|
+
→ В консоли появятся логи инициализации, IntersectionObserver и AJAX.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 📝 Лицензия
|
|
138
|
+
|
|
139
|
+
MIT. Свободно использовать и модифицировать.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
📌 *Разработано в рамках фронтенд-системы VG Modules.*
|
|
144
|
+
> 🚀 Автор: VEGAS STUDIO (vegas-dev.com)
|
|
145
|
+
> 📍 Поддерживается в проектах VEGAS
|
|
@@ -4,7 +4,8 @@ import EventHandler from "../../../utils/js/dom/event";
|
|
|
4
4
|
import {dismissTrigger} from "../../module-fn";
|
|
5
5
|
import Selectors from "../../../utils/js/dom/selectors";
|
|
6
6
|
import Backdrop from "../../../utils/js/components/backdrop";
|
|
7
|
-
import
|
|
7
|
+
import ScrollBarHelper from "../../../utils/js/components/scrollbar";
|
|
8
|
+
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Constants
|
|
@@ -56,6 +57,7 @@ class VGSidebar extends BaseModule {
|
|
|
56
57
|
this._addEventListeners();
|
|
57
58
|
this._dismissElement();
|
|
58
59
|
|
|
60
|
+
this._scrollBar = new ScrollBarHelper();
|
|
59
61
|
this._params.animation.delay = !this._params.animation.enable ? 0 : this._params.animation.delay;
|
|
60
62
|
this._animation(this._element, VGSidebar.NAME_KEY, this._params.animation);
|
|
61
63
|
}
|
|
@@ -90,7 +92,7 @@ class VGSidebar extends BaseModule {
|
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
if (_this._params.overflow) {
|
|
93
|
-
|
|
95
|
+
this._scrollBar.hide();
|
|
94
96
|
}
|
|
95
97
|
|
|
96
98
|
if (this._params.hash) {
|
|
@@ -131,13 +133,13 @@ class VGSidebar extends BaseModule {
|
|
|
131
133
|
if (this._params.backdrop) {
|
|
132
134
|
Backdrop.hide(() => {
|
|
133
135
|
if (this._params.overflow) {
|
|
134
|
-
|
|
136
|
+
this._scrollBar.reset();
|
|
135
137
|
}
|
|
136
138
|
});
|
|
137
139
|
}
|
|
138
140
|
|
|
139
141
|
if (this._params.overflow) {
|
|
140
|
-
|
|
142
|
+
this._scrollBar.reset();
|
|
141
143
|
}
|
|
142
144
|
|
|
143
145
|
if (this._params.hash) {
|