PluginProbe ʕ •ᴥ•ʔ
JetFormBuilder — Dynamic Blocks Form Builder / 3.1.1
JetFormBuilder — Dynamic Blocks Form Builder v3.1.1
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
454 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, setAttrs, 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 setAttrs( this );
205
206 this.reporting = createReport( this );
207
208 this.loading.watch( () => this.onChangeLoading() );
209
210 this.path = [ ...this.getParentPath(), this.name ];
211
212 if (
213 // is ajax
214 !this.getSubmit().submitter.hasOwnProperty( 'status' ) ||
215 this.hasParent()
216 ) {
217 return;
218 }
219
220 this.getSubmit().submitter.watchReset( () => this.onClear() );
221 };
222 InputData.prototype.onChangeLoading = function () {
223 this.getSubmit().lockState.current = this.loading.current;
224
225 const [ node ] = this.nodes;
226 const wrapper = node.closest( '.jet-form-builder-row' );
227
228 node.readOnly = this.loading.current;
229 wrapper.classList.toggle( 'is-loading', this.loading.current );
230 };
231 /**
232 * @param observable {Observable}
233 */
234 InputData.prototype.setRoot = function ( observable ) {
235 this.root = observable;
236 };
237 /**
238 * By default it runs only if repeater item was removed
239 */
240 InputData.prototype.onRemove = function () {
241 };
242 /**
243 * @returns {string}
244 */
245 InputData.prototype.getName = function () {
246 return this.name;
247 };
248 /**
249 * @returns {array|string}
250 */
251 InputData.prototype.getValue = function () {
252 return this.value.current;
253 };
254 /**
255 * @returns {array}
256 */
257 InputData.prototype.getNode = function () {
258 return this.nodes;
259 };
260 /**
261 * @returns {boolean}
262 */
263 InputData.prototype.isArray = function () {
264 return this.rawName.includes( '[]' );
265 };
266 /**
267 * @param callable {Function|mixed}
268 * @param inputContext {InputData|Boolean}
269 */
270 InputData.prototype.beforeSubmit = function ( callable, inputContext = false ) {
271 this.getSubmit().submitter.promise( callable, inputContext );
272 };
273 /**
274 * @returns {FormSubmit}
275 */
276 InputData.prototype.getSubmit = function () {
277 return this.getRoot().form;
278 };
279 /**
280 * @returns {Observable}
281 */
282 InputData.prototype.getRoot = function () {
283 if ( ! this.root?.parent ) {
284 return this.root;
285 }
286 return this.root.parent.getRoot();
287 };
288
289 InputData.prototype.isVisible = function () {
290 const wrapper = this.getWrapperNode();
291
292 return isVisible( wrapper );
293 };
294
295 InputData.prototype.onClear = function () {
296 this.silenceSet( null );
297 };
298
299 InputData.prototype.getReactive = function () {
300 return new ReactiveVar();
301 };
302
303 InputData.prototype.checkIsRequired = function () {
304 const [ node ] = this.nodes;
305
306 return node.required ?? !!node.dataset.required?.length;
307 };
308
309 InputData.prototype.silenceSet = function ( value ) {
310 /**
311 * Related to issue
312 * @link https://github.com/Crocoblock/issues-tracker/issues/1261#issuecomment-1293290291
313 */
314 const tempReport = this.report.bind( this );
315
316 this.report = () => {};
317
318 this.value.current = value;
319
320 this.report = tempReport;
321 };
322
323 InputData.prototype.silenceNotify = function () {
324 const tempReport = this.report.bind( this );
325
326 this.report = () => {};
327
328 this.value.notify();
329
330 this.report = tempReport;
331 };
332
333 /**
334 * @return {boolean}
335 */
336 InputData.prototype.hasParent = function () {
337 return !!this.root?.parent;
338 };
339
340 /**
341 * For insert errors in advanced validation mode
342 * @returns {*}
343 */
344 InputData.prototype.getWrapperNode = function () {
345 return this.nodes[ 0 ].closest( '.jet-form-builder-row' );
346 };
347
348 InputData.prototype.handleEnterKey = function ( event ) {
349 // not enter
350 if ( event.key !== 'Enter' ) {
351 return;
352 }
353
354 event.preventDefault();
355
356 this.onEnterKey();
357 };
358
359 InputData.prototype.onEnterKey = function () {
360 const canSubmit = this.enterKey.applyFilters( true );
361
362 if ( canSubmit ) {
363 this.getSubmit().submit();
364 }
365 };
366
367 InputData.prototype.initNotifyValue = function () {
368 this.silenceNotify();
369 };
370
371 InputData.prototype.onFocus = function () {
372 this.scrollTo();
373 this.focusRaw();
374 };
375 InputData.prototype.focusRaw = function () {
376 const [ node ] = this.nodes;
377
378 /**
379 * @see https://github.com/Crocoblock/issues-tracker/issues/2265#issuecomment-1447988718
380 */
381 if ( [ 'date', 'time', 'datetime-local' ].includes( node.type ) ) {
382 return;
383 }
384
385 node?.focus( { preventScroll: true } );
386 };
387 InputData.prototype.scrollTo = function () {
388 const wrapper = this.getWrapperNode();
389
390 window.scrollTo( {
391 top: getOffsetTop( wrapper ) - this.offsetOnFocus,
392 behavior: 'smooth',
393 } );
394 };
395
396 /**
397 * @return {ReportingContext}
398 */
399 InputData.prototype.getContext = function () {
400 return this.root.getContext();
401 };
402
403 /**
404 * @return {boolean|InputData[]}
405 */
406 InputData.prototype.populateInner = function () {
407 return false;
408 };
409
410 /**
411 * Executed with default browser validation
412 *
413 * @returns {boolean}
414 */
415 InputData.prototype.hasAutoScroll = function () {
416 return true;
417 };
418
419 /**
420 * @returns {HTMLInputElement|HTMLElement}
421 */
422 InputData.prototype.getReportingNode = function () {
423 return this.nodes[ 0 ];
424 };
425
426 InputData.prototype.getParentPath = function () {
427 if ( !this.root?.parent ) {
428 return [];
429 }
430
431 /**
432 * @type {Array|Object}
433 */
434 const value = this.root.parent.value.current;
435
436 if ( 'object' !== typeof value ) {
437 return [];
438 }
439
440 for ( const [ index, row ] of Object.entries( value ) ) {
441 if ( row !== this.root ) {
442 continue;
443 }
444 return [
445 ...this.root.parent.getParentPath(),
446 this.root.parent.name,
447 index,
448 ];
449 }
450
451 return [];
452 };
453
454 export default InputData;