adt-js-components 2.1.1 → 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 +1 -1
- package/src/AjaxSelect/index.js +86 -9
- package/src/Select2/index.js +8 -0
- package/src/SubmitForm/index.js +3 -1
package/package.json
CHANGED
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 }
|
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
|
@@ -178,7 +178,9 @@ function run(options) {
|
|
|
178
178
|
},
|
|
179
179
|
error: function (xhr, status, error, settings) {
|
|
180
180
|
if (settings.nette && settings.nette.form && settings.nette.form.attr('data-adt-submit-form') !== undefined) {
|
|
181
|
-
|
|
181
|
+
if (status !== 'abort') {
|
|
182
|
+
settings.nette.form.prepend('<div class="alert alert-danger">' + xhr.responseText + '</div>');
|
|
183
|
+
}
|
|
182
184
|
|
|
183
185
|
settings.nette.el.html(settings.nette.el.data('originalContent'));
|
|
184
186
|
settings.nette.form.data('enabledButtons').each(function () {
|