PluginProbe ʕ •ᴥ•ʔ
JetFormBuilder — Dynamic Blocks Form Builder / 3.1.6
JetFormBuilder — Dynamic Blocks Form Builder v3.1.6
3.6.3.1 3.6.3 3.6.2.2 3.6.2.1 3.6.2 3.6.1.1 3.6.1 3.6.0.1 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.1.0 2.1.1 2.1.10 2.1.11 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 3.0.0 3.0.0.1 3.0.0.2 3.0.0.3 3.0.1 3.0.1.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.0.1 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 3.2.0 3.2.1 3.2.2 3.2.3 3.3.0 3.3.1 3.3.2 3.3.3 3.3.3.1 3.3.4 3.3.4.1 3.3.4.2 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 3.4.5.1 3.4.5.2 3.4.6 3.4.7 3.4.7.1 3.5.0 3.5.1 3.5.1.1 3.5.1.2 3.5.2 3.5.2.1 3.5.3 3.5.4 3.5.5 3.5.6 3.5.6.1 3.5.6.2 3.5.6.3 3.6.0
jetformbuilder / assets / src / frontend / main / inputs / InputData.js
jetformbuilder / assets / src / frontend / main / inputs Last commit date
ChangeData.js 2 years ago CheckboxData.js 2 years ago InputData.js 2 years ago InputMaskedData.js 2 years ago MultiSelectData.js 2 years ago NoListenData.js 2 years ago RadioData.js 2 years ago RangeData.js 2 years ago RenderStateData.js 2 years ago WysiwygData.js 2 years ago functions.js 2 years ago
InputData.js
452 lines
1 import LoadingReactiveVar from '../reactive/LoadingReactiveVar';
2 import ReactiveVar from '../reactive/ReactiveVar';
3 import ReactiveHook from '../reactive/ReactiveHook';
4 import { getSignal } from '../signals/functions';
5 import { createReport } from '../reporting/functions';
6 import { getParsedName } from './functions';
7 import { getOffsetTop, isVisible } from '../functions';
8 import { STRICT_MODE } from '../signals/BaseSignal';
9
10 const { doAction } = JetPlugins.hooks;
11
12 /**
13 * @property {string} rawName
14 * @property {string} name
15 * @property {Node|boolean} comment
16 * @property {HTMLInputElement|*[]} nodes
17 * @property {ReactiveVar} value
18 * @property {ConditionChecker|null} checker
19 * @property {*} calcValue
20 * @property {AdvancedReporting|BrowserReporting} reporting
21 * @property {Observable} root
22 * @property {PageState} page
23 * @property {LoadingReactiveVar} loading
24 * @property {Object<ReactiveVar>} attrs
25 * @property {boolean} isRequired
26 * @property {null|ReactiveHook} enterKey
27 * @property {null|string} inputType
28 *
29 * @constructor
30 */
31 function InputData() {
32 this.rawName = '';
33 this.name = '';
34 this.comment = false;
35 /**
36 * @type {HTMLElement[]|HTMLInputElement[]}
37 */
38 this.nodes = [];
39 this.attrs = {};
40 this.enterKey = null;
41 this.inputType = null;
42 this.offsetOnFocus = 75;
43
44 /**
45 * Path from top of form to current field name
46 * Ex.: [ 'repeater_name', 0, 'text_field' ]
47 * Where:
48 * - 'repeater_name': name of repeater, where current field is placed
49 * - 0: index of repeater row, where current field is placed
50 * - 'text_field': name of current field name
51 * @type {Array<String|Number>}
52 */
53 this.path = [];
54
55 /**
56 * @type {ReactiveVar}
57 */
58 this.value = this.getReactive();
59 this.value.watch( this.onChange.bind( this ) );
60
61 this.isRequired = false;
62 this.calcValue = null;
63
64 /**
65 * @type {AdvancedReporting|BrowserReporting}
66 */
67 this.reporting = null;
68
69 this.checker = null;
70
71 /**
72 * @type {Observable}
73 */
74 this.root = null;
75
76 this.loading = new LoadingReactiveVar( false );
77 this.loading.make();
78 }
79
80 InputData.prototype.attrs = {};
81
82 /**
83 * @param node {HTMLElement}
84 * @returns {boolean}
85 */
86 InputData.prototype.isSupported = function ( node ) {
87 return true;
88 };
89 InputData.prototype.addListeners = function () {
90 const [ node ] = this.nodes;
91
92 node.addEventListener( 'input', event => {
93 this.value.current = event.target.value;
94 } );
95
96 node.addEventListener( 'blur', event => {
97 this.reportOnBlur();
98 } );
99
100 /**
101 * @since 3.0.1
102 */
103 !STRICT_MODE && jQuery( node ).on( 'change', event => {
104 if ( this.value.current == event.target.value ) {
105 return;
106 }
107 this.callable.lockTrigger();
108 this.value.current = event.target.value;
109 this.callable.unlockTrigger();
110 } );
111
112 if ( 'input' !== this.inputType ) {
113 return;
114 }
115
116 this.enterKey = new ReactiveHook();
117 node.addEventListener( 'keydown', this.handleEnterKey.bind( this ) );
118 };
119 InputData.prototype.makeReactive = function () {
120 this.onObserve();
121 this.addListeners();
122 this.setValue();
123 this.initNotifyValue();
124
125 this.value.make();
126
127 doAction( 'jet.fb.input.makeReactive', this );
128 };
129 InputData.prototype.onChange = function ( prevValue ) {
130 this.calcValue = this.value.current;
131
132 // apply changes in DOM
133 this?.callable?.run( prevValue );
134
135 // show errors
136 this.report();
137 };
138 InputData.prototype.report = function () {
139 this.reporting.validateOnChange();
140 };
141 InputData.prototype.reportOnBlur = function () {
142 this.reporting.validateOnBlur();
143 };
144 /**
145 * @param callable
146 * @returns {(function(): *|*[])|*}
147 */
148 InputData.prototype.watch = function ( callable ) {
149 return this.value.watch( callable );
150 };
151 InputData.prototype.watchValidity = function ( callable ) {
152 return this.reporting.validityState.watch( callable );
153 };
154 /**
155 * @param callable
156 * @returns {(function(): *|*[])|*}
157 */
158 InputData.prototype.sanitize = function ( callable ) {
159 return this.value.sanitize( callable );
160 };
161 /**
162 * @param inputData {InputData}
163 */
164 InputData.prototype.merge = function ( inputData ) {
165 this.nodes = [ ...inputData.getNode() ];
166 };
167 InputData.prototype.setValue = function () {
168 if ( this.isArray() ) {
169 this.value.current = Array.from( this.nodes ).
170 map( ( { value } ) => value );
171 }
172 else {
173 this.value.current = this.nodes[ 0 ]?.value;
174 }
175 this.calcValue = this.value.current;
176 };
177 /**
178 * @param node {HTMLElement|HTMLInputElement}
179 */
180 InputData.prototype.setNode = function ( node ) {
181 this.nodes = [ node ];
182 this.rawName = node.name ?? '';
183 this.name = getParsedName( this.rawName );
184
185 this.inputType = node.nodeName.toLowerCase();
186 };
187 /**
188 * Runs once in lifecycle.
189 */
190 InputData.prototype.onObserve = function () {
191 const [ node ] = this.nodes;
192
193 /**
194 * Save link to this object
195 * @type {InputData}
196 */
197 node.jfbSync = this;
198
199 this.isRequired = this.checkIsRequired();
200
201 this.callable = getSignal( node, this );
202 this.callable.setInput( this );
203
204 this.reporting = createReport( this );
205
206 this.loading.watch( () => this.onChangeLoading() );
207
208 this.path = [ ...this.getParentPath(), this.name ];
209
210 if (
211 // is ajax
212 !this.getSubmit().submitter.hasOwnProperty( 'status' ) ||
213 this.hasParent()
214 ) {
215 return;
216 }
217
218 this.getSubmit().submitter.watchReset( () => this.onClear() );
219 };
220 InputData.prototype.onChangeLoading = function () {
221 this.getSubmit().lockState.current = this.loading.current;
222
223 const [ node ] = this.nodes;
224 const wrapper = node.closest( '.jet-form-builder-row' );
225
226 node.readOnly = this.loading.current;
227 wrapper.classList.toggle( 'is-loading', this.loading.current );
228 };
229 /**
230 * @param observable {Observable}
231 */
232 InputData.prototype.setRoot = function ( observable ) {
233 this.root = observable;
234 };
235 /**
236 * By default it runs only if repeater item was removed
237 */
238 InputData.prototype.onRemove = function () {
239 };
240 /**
241 * @returns {string}
242 */
243 InputData.prototype.getName = function () {
244 return this.name;
245 };
246 /**
247 * @returns {array|string}
248 */
249 InputData.prototype.getValue = function () {
250 return this.value.current;
251 };
252 /**
253 * @returns {array}
254 */
255 InputData.prototype.getNode = function () {
256 return this.nodes;
257 };
258 /**
259 * @returns {boolean}
260 */
261 InputData.prototype.isArray = function () {
262 return this.rawName.includes( '[]' );
263 };
264 /**
265 * @param callable {Function|mixed}
266 * @param inputContext {InputData|Boolean}
267 */
268 InputData.prototype.beforeSubmit = function ( callable, inputContext = false ) {
269 this.getSubmit().submitter.promise( callable, inputContext );
270 };
271 /**
272 * @returns {FormSubmit}
273 */
274 InputData.prototype.getSubmit = function () {
275 return this.getRoot().form;
276 };
277 /**
278 * @returns {Observable}
279 */
280 InputData.prototype.getRoot = function () {
281 if ( ! this.root?.parent ) {
282 return this.root;
283 }
284 return this.root.parent.getRoot();
285 };
286
287 InputData.prototype.isVisible = function () {
288 const wrapper = this.getWrapperNode();
289
290 return isVisible( wrapper );
291 };
292
293 InputData.prototype.onClear = function () {
294 this.silenceSet( null );
295 };
296
297 InputData.prototype.getReactive = function () {
298 return new ReactiveVar();
299 };
300
301 InputData.prototype.checkIsRequired = function () {
302 const [ node ] = this.nodes;
303
304 return node.required ?? !!node.dataset.required?.length;
305 };
306
307 InputData.prototype.silenceSet = function ( value ) {
308 /**
309 * Related to issue
310 * @link https://github.com/Crocoblock/issues-tracker/issues/1261#issuecomment-1293290291
311 */
312 const tempReport = this.report.bind( this );
313
314 this.report = () => {};
315
316 this.value.current = value;
317
318 this.report = tempReport;
319 };
320
321 InputData.prototype.silenceNotify = function () {
322 const tempReport = this.report.bind( this );
323
324 this.report = () => {};
325
326 this.value.notify();
327
328 this.report = tempReport;
329 };
330
331 /**
332 * @return {boolean}
333 */
334 InputData.prototype.hasParent = function () {
335 return !!this.root?.parent;
336 };
337
338 /**
339 * For insert errors in advanced validation mode
340 * @returns {*}
341 */
342 InputData.prototype.getWrapperNode = function () {
343 return this.nodes[ 0 ].closest( '.jet-form-builder-row' );
344 };
345
346 InputData.prototype.handleEnterKey = function ( event ) {
347 // not enter
348 if ( event.key !== 'Enter' ) {
349 return;
350 }
351
352 event.preventDefault();
353
354 this.onEnterKey();
355 };
356
357 InputData.prototype.onEnterKey = function () {
358 const canSubmit = this.enterKey.applyFilters( true );
359
360 if ( canSubmit ) {
361 this.getSubmit().submit();
362 }
363 };
364
365 InputData.prototype.initNotifyValue = function () {
366 this.silenceNotify();
367 };
368
369 InputData.prototype.onFocus = function () {
370 this.scrollTo();
371 this.focusRaw();
372 };
373 InputData.prototype.focusRaw = function () {
374 const [ node ] = this.nodes;
375
376 /**
377 * @see https://github.com/Crocoblock/issues-tracker/issues/2265#issuecomment-1447988718
378 */
379 if ( [ 'date', 'time', 'datetime-local' ].includes( node.type ) ) {
380 return;
381 }
382
383 node?.focus( { preventScroll: true } );
384 };
385 InputData.prototype.scrollTo = function () {
386 const wrapper = this.getWrapperNode();
387
388 window.scrollTo( {
389 top: getOffsetTop( wrapper ) - this.offsetOnFocus,
390 behavior: 'smooth',
391 } );
392 };
393
394 /**
395 * @return {ReportingContext}
396 */
397 InputData.prototype.getContext = function () {
398 return this.root.getContext();
399 };
400
401 /**
402 * @return {boolean|InputData[]}
403 */
404 InputData.prototype.populateInner = function () {
405 return false;
406 };
407
408 /**
409 * Executed with default browser validation
410 *
411 * @returns {boolean}
412 */
413 InputData.prototype.hasAutoScroll = function () {
414 return true;
415 };
416
417 /**
418 * @returns {HTMLInputElement|HTMLElement}
419 */
420 InputData.prototype.getReportingNode = function () {
421 return this.nodes[ 0 ];
422 };
423
424 InputData.prototype.getParentPath = function () {
425 if ( !this.root?.parent ) {
426 return [];
427 }
428
429 /**
430 * @type {Array|Object}
431 */
432 const value = this.root.parent.value.current;
433
434 if ( 'object' !== typeof value ) {
435 return [];
436 }
437
438 for ( const [ index, row ] of Object.entries( value ) ) {
439 if ( row !== this.root ) {
440 continue;
441 }
442 return [
443 ...this.root.parent.getParentPath(),
444 this.root.parent.name,
445 index,
446 ];
447 }
448
449 return [];
450 };
451
452 export default InputData;