adt-js-components 2.0.3 → 2.2.0
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/package.json +22 -6
- package/src/AjaxSelect/index.js +86 -9
- package/src/Notifications/index.js +40 -10
- package/src/Select2/index.js +8 -0
- package/src/SubmitForm/index.js +35 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adt-js-components",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "JavaScript components for Nette framework",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
"url": "git+https://github.com/AppsDevTeam/js-components.git"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
+
"nette.ajax.js": "^2.3.0"
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"jquery": "^3.4.1",
|
|
14
18
|
"select2": "^4.0.13",
|
|
15
19
|
"select2-bootstrap-5-theme": "^1.1.1",
|
|
16
20
|
"autonumeric": "^4.6.0",
|
|
@@ -23,14 +27,26 @@
|
|
|
23
27
|
"scrollparent": "^2.0.1",
|
|
24
28
|
"tinymce": "^5.7.0",
|
|
25
29
|
"tinymce-i18n": "^20.12.25",
|
|
26
|
-
"nette.ajax.js": "^2.3.0",
|
|
27
30
|
"leaflet": "^1.9.4",
|
|
28
31
|
"leaflet.markercluster": "^1.5.3",
|
|
29
|
-
"firebase": "^12.10.0"
|
|
30
|
-
"@firebase/messaging": "^0.12.24"
|
|
32
|
+
"firebase": "^12.10.0"
|
|
31
33
|
},
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
+
"peerDependenciesMeta": {
|
|
35
|
+
"select2": { "optional": true },
|
|
36
|
+
"select2-bootstrap-5-theme": { "optional": true },
|
|
37
|
+
"autonumeric": { "optional": true },
|
|
38
|
+
"@here/flexpolyline": { "optional": true },
|
|
39
|
+
"flatpickr": { "optional": true },
|
|
40
|
+
"script-loader": { "optional": true },
|
|
41
|
+
"timepicker": { "optional": true },
|
|
42
|
+
"glightbox": { "optional": true },
|
|
43
|
+
"@googlemaps/js-api-loader": { "optional": true },
|
|
44
|
+
"scrollparent": { "optional": true },
|
|
45
|
+
"tinymce": { "optional": true },
|
|
46
|
+
"tinymce-i18n": { "optional": true },
|
|
47
|
+
"leaflet": { "optional": true },
|
|
48
|
+
"leaflet.markercluster": { "optional": true },
|
|
49
|
+
"firebase": { "optional": true }
|
|
34
50
|
},
|
|
35
51
|
"author": "Apps Dev Team",
|
|
36
52
|
"license": "MIT",
|
package/src/AjaxSelect/index.js
CHANGED
|
@@ -107,26 +107,105 @@ function run(options) {
|
|
|
107
107
|
this.$originalElement.select2('destroy');
|
|
108
108
|
};
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Lazy initialization.
|
|
112
|
+
*
|
|
113
|
+
* Building select2 reads computed styles and layout (e.g. the mousewheel plugin bundled
|
|
114
|
+
* in select2.full measures the line height of every results list). Right after the
|
|
115
|
+
* previous select2 was inserted, each such read forces the browser to lay out the whole
|
|
116
|
+
* page again, so initializing many selects at once gets quadratically slower - a form
|
|
117
|
+
* with 300 rows took tens of seconds.
|
|
118
|
+
*
|
|
119
|
+
* Selects that are visible (or close to the viewport) are therefore initialized right
|
|
120
|
+
* away and the rest only when they are about to scroll into view. Visibility of all newly
|
|
121
|
+
* added selects is read in one pass before any of them is initialized, so the page is laid
|
|
122
|
+
* out once, not once per select. A select that is not initialized yet is initialized on
|
|
123
|
+
* focus as well (e.g. when the user tabs into it).
|
|
124
|
+
*
|
|
125
|
+
* A few selects added at once (a row added by a replicator) are always initialized right
|
|
126
|
+
* away - that is cheap, and the code that added the row may call select2('open') on it,
|
|
127
|
+
* even when the row ended up at the end of a long list out of the viewport.
|
|
128
|
+
*/
|
|
129
|
+
const VISIBILITY_MARGIN = 200;
|
|
130
|
+
const EAGER_LIMIT = 10;
|
|
131
|
+
|
|
132
|
+
function initialize(el) {
|
|
133
|
+
if (el.adtAjaxSelect) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
el.adtAjaxSelect = new AjaxSelect($(el));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const visibilityObserver = 'IntersectionObserver' in window
|
|
140
|
+
? new IntersectionObserver(entries => {
|
|
141
|
+
entries.forEach(entry => {
|
|
142
|
+
if (entry.isIntersecting) {
|
|
143
|
+
visibilityObserver.unobserve(entry.target);
|
|
144
|
+
entry.target.removeEventListener('focus', initializeOnFocus);
|
|
145
|
+
initialize(entry.target);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}, { rootMargin: VISIBILITY_MARGIN + 'px' })
|
|
149
|
+
: null;
|
|
150
|
+
|
|
151
|
+
function initializeOnFocus(e) {
|
|
152
|
+
const el = e.currentTarget;
|
|
153
|
+
el.removeEventListener('focus', initializeOnFocus);
|
|
154
|
+
visibilityObserver.unobserve(el);
|
|
155
|
+
initialize(el);
|
|
156
|
+
$(el).data('select2')?.$selection.trigger('focus');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function isNearViewport(rect) {
|
|
160
|
+
// zero size = not rendered (display: none), it will be initialized once it shows up
|
|
161
|
+
return (rect.width > 0 || rect.height > 0)
|
|
162
|
+
&& rect.bottom >= -VISIBILITY_MARGIN
|
|
163
|
+
&& rect.top <= window.innerHeight + VISIBILITY_MARGIN
|
|
164
|
+
&& rect.right >= -VISIBILITY_MARGIN
|
|
165
|
+
&& rect.left <= window.innerWidth + VISIBILITY_MARGIN;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function applyEventHandlers(elements) {
|
|
169
|
+
elements = elements.filter(el => !el.adtAjaxSelect);
|
|
170
|
+
|
|
171
|
+
if (!visibilityObserver || elements.length <= EAGER_LIMIT) {
|
|
172
|
+
elements.forEach(initialize);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// read all rects first (one layout), only then start building select2
|
|
177
|
+
const rects = elements.map(el => el.getBoundingClientRect());
|
|
178
|
+
|
|
179
|
+
elements.forEach((el, i) => {
|
|
180
|
+
if (isNearViewport(rects[i])) {
|
|
181
|
+
initialize(el);
|
|
182
|
+
} else {
|
|
183
|
+
visibilityObserver.observe(el);
|
|
184
|
+
el.addEventListener('focus', initializeOnFocus);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
112
187
|
}
|
|
113
188
|
|
|
114
189
|
const observer = new MutationObserver(mutations => {
|
|
190
|
+
const elements = [];
|
|
191
|
+
|
|
115
192
|
mutations.forEach(mutation => {
|
|
116
193
|
if (mutation.type === "childList") {
|
|
117
194
|
mutation.addedNodes.forEach(node => {
|
|
118
195
|
if (node.nodeType === 1) {
|
|
119
196
|
if (node.hasAttribute("data-adt-ajax-select")) {
|
|
120
|
-
|
|
197
|
+
elements.push(node);
|
|
121
198
|
}
|
|
122
199
|
|
|
123
|
-
node.querySelectorAll('[data-adt-ajax-select]')
|
|
124
|
-
applyEventHandlers(child);
|
|
125
|
-
});
|
|
200
|
+
elements.push(...node.querySelectorAll('[data-adt-ajax-select]'));
|
|
126
201
|
}
|
|
127
202
|
});
|
|
128
203
|
}
|
|
129
204
|
});
|
|
205
|
+
|
|
206
|
+
if (elements.length) {
|
|
207
|
+
applyEventHandlers(elements);
|
|
208
|
+
}
|
|
130
209
|
});
|
|
131
210
|
|
|
132
211
|
observer.observe(document.body, {
|
|
@@ -134,9 +213,7 @@ function run(options) {
|
|
|
134
213
|
subtree: true,
|
|
135
214
|
});
|
|
136
215
|
|
|
137
|
-
document.querySelectorAll('[data-adt-ajax-select]')
|
|
138
|
-
applyEventHandlers(el);
|
|
139
|
-
});
|
|
216
|
+
applyEventHandlers([...document.querySelectorAll('[data-adt-ajax-select]')]);
|
|
140
217
|
}
|
|
141
218
|
|
|
142
219
|
export default { run }
|
|
@@ -88,18 +88,48 @@ const run = (config) => {
|
|
|
88
88
|
updateButtons();
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
91
|
+
// window.messaging se inicializuje async v Messaging komponentě, která nemusí
|
|
92
|
+
// doběhnout dřív než tento init (typicky po F5). Počkáme na ni, jinak by se
|
|
93
|
+
// getToken nikdy nezavolal a window.adtNotificationsToken by zůstal nenastavený.
|
|
94
|
+
var waitForMessaging = function (timeoutMs) {
|
|
95
|
+
return new Promise(function (resolve) {
|
|
96
|
+
if (window.messaging) {
|
|
97
|
+
resolve(window.messaging);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
var elapsed = 0;
|
|
101
|
+
var step = 100;
|
|
102
|
+
var interval = setInterval(function () {
|
|
103
|
+
if (window.messaging) {
|
|
104
|
+
clearInterval(interval);
|
|
105
|
+
resolve(window.messaging);
|
|
106
|
+
} else if ((elapsed += step) >= timeoutMs) {
|
|
107
|
+
clearInterval(interval);
|
|
108
|
+
resolve(null);
|
|
97
109
|
}
|
|
110
|
+
}, step);
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Initial state: check if notifications are already enabled
|
|
115
|
+
if (Notification.permission === 'granted') {
|
|
116
|
+
waitForMessaging(5000).then(function (messaging) {
|
|
117
|
+
if (!messaging) {
|
|
98
118
|
updateButtons();
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
})
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
getToken(messaging, { vapidKey: config.vapidKey })
|
|
123
|
+
.then(function (currentToken) {
|
|
124
|
+
if (currentToken) {
|
|
125
|
+
window.adtNotificationsToken = currentToken;
|
|
126
|
+
}
|
|
127
|
+
updateButtons();
|
|
128
|
+
})
|
|
129
|
+
.catch(function () {
|
|
130
|
+
updateButtons();
|
|
131
|
+
});
|
|
132
|
+
});
|
|
103
133
|
} else {
|
|
104
134
|
updateButtons();
|
|
105
135
|
}
|
package/src/Select2/index.js
CHANGED
|
@@ -8,6 +8,14 @@ function run(options) {
|
|
|
8
8
|
const selector = options.selector || 'select:not(' + noSelect2Class + ')';
|
|
9
9
|
|
|
10
10
|
function applyEventHandlers(el) {
|
|
11
|
+
// AJAX selects are initialized by the AjaxSelect component with their own options.
|
|
12
|
+
// Initializing them here as well would build select2 twice and AjaxSelect would
|
|
13
|
+
// rebuild it right away. Each initialization reads computed styles, which is the
|
|
14
|
+
// most expensive part of rendering a form with many selects.
|
|
15
|
+
if (el.matches('[data-adt-ajax-select]')) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
11
19
|
$(el)
|
|
12
20
|
.select2($.extend({theme: 'bootstrap-5', language: "en"}, $(el).data('adt-select2') || {}))
|
|
13
21
|
.on('change', function(e) {
|
package/src/SubmitForm/index.js
CHANGED
|
@@ -8,6 +8,30 @@ function getErrorElementId(el) {
|
|
|
8
8
|
return isList(el) ? el.id.split('-').slice(0, -1).join('-') : el.id;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
function toggleInvalid(el, invalid) {
|
|
12
|
+
el.classList.toggle('is-invalid', invalid);
|
|
13
|
+
// because of radio lists and checkbox lists
|
|
14
|
+
if (isList(el)) {
|
|
15
|
+
el.parentNode.classList.toggle('is-invalid', invalid);
|
|
16
|
+
}
|
|
17
|
+
// because of https://github.com/twbs/bootstrap/issues/25110
|
|
18
|
+
if (el.parentNode.classList.contains('input-group')) {
|
|
19
|
+
el.parentNode.classList.toggle('has-validation', invalid);
|
|
20
|
+
el.parentNode.classList.toggle('is-invalid', invalid);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function hasErrors(errorElement) {
|
|
25
|
+
return Array.from(errorElement.childNodes).some(node => node.nodeName !== 'SCRIPT' && node.textContent.trim() !== '');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function markErrorsFor(errorElement) {
|
|
29
|
+
const el = document.getElementById(errorElement.getAttribute('data-adt-errors-for'));
|
|
30
|
+
if (el && el.tagName !== 'FORM') {
|
|
31
|
+
toggleInvalid(el, hasErrors(errorElement));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
11
35
|
function scrollToFirstError(form) {
|
|
12
36
|
const el = $(form).find('.alert-danger:first, .is-invalid:first')[0];
|
|
13
37
|
|
|
@@ -28,10 +52,17 @@ function run(options) {
|
|
|
28
52
|
if (isList(this)) {
|
|
29
53
|
$(this).parent().parent().find('.is-invalid').removeClass('is-invalid');
|
|
30
54
|
}
|
|
55
|
+
if (this.parentNode.classList.contains('input-group')) {
|
|
56
|
+
this.parentNode.classList.remove('is-invalid');
|
|
57
|
+
}
|
|
31
58
|
});
|
|
32
59
|
}
|
|
33
60
|
|
|
34
61
|
$.nette.ext('live').after(function($el) {
|
|
62
|
+
$el.find('[data-adt-errors-for]').addBack('[data-adt-errors-for]').each(function() {
|
|
63
|
+
markErrorsFor(this);
|
|
64
|
+
});
|
|
65
|
+
|
|
35
66
|
window.addEventListener('pageshow', (e) => {
|
|
36
67
|
if (e.persisted) {
|
|
37
68
|
$el.find('[data-adt-submit-form]').each(function(e) {
|
|
@@ -85,15 +116,7 @@ function run(options) {
|
|
|
85
116
|
errorElement.innerHTML += `<div>${error.message}</div>`;
|
|
86
117
|
}
|
|
87
118
|
|
|
88
|
-
error.element
|
|
89
|
-
// because of radio lists and checkbox lists
|
|
90
|
-
if (isList(error.element)) {
|
|
91
|
-
error.element.parentNode.classList.add('is-invalid');
|
|
92
|
-
}
|
|
93
|
-
// because of https://github.com/twbs/bootstrap/issues/25110
|
|
94
|
-
if (error.element.parentNode.classList.contains('input-group')) {
|
|
95
|
-
error.element.parentNode.classList.add('has-validation');
|
|
96
|
-
}
|
|
119
|
+
toggleInvalid(error.element, true);
|
|
97
120
|
}
|
|
98
121
|
|
|
99
122
|
if (errors.length) {
|
|
@@ -155,7 +178,9 @@ function run(options) {
|
|
|
155
178
|
},
|
|
156
179
|
error: function (xhr, status, error, settings) {
|
|
157
180
|
if (settings.nette && settings.nette.form && settings.nette.form.attr('data-adt-submit-form') !== undefined) {
|
|
158
|
-
|
|
181
|
+
if (status !== 'abort') {
|
|
182
|
+
settings.nette.form.prepend('<div class="alert alert-danger">' + xhr.responseText + '</div>');
|
|
183
|
+
}
|
|
159
184
|
|
|
160
185
|
settings.nette.el.html(settings.nette.el.data('originalContent'));
|
|
161
186
|
settings.nette.form.data('enabledButtons').each(function () {
|