PageSourceSearch

https://tategu-oonami.jp/wp-ont/wp-content/plugins/form-nest/assets/js/shared/utilities.js?ver=1.0.9

js tategu-oonami.jp collected 2026-09-30 23:45:59 UTC 19,430 bytes, 602 lines download raw bytes

1/**
2 * WP Advanced Form - Shared Utility Functions
3 * Common helper functions used across the plugin
4 * 
5 * @package FN_Plugin
6 * @since 1.0.0
7 */
8
9(function($, FN) {
10    'use strict';
11    
12    // Initialize FN namespace
13    if (!window.FormNest) {
14        window.FormNest = {};
15    }
16    
17    if (!window.FormNest.Utils) {
18        window.FormNest.Utils = {};
19    }
20    
21    /**
22     * Parse JSON string safely with fallback
23     * Handles both string and already-parsed objects
24     * 
25     * @param {*} data - Data to parse (string, object, or other)
26     * @param {*} defaultValue - Default value if parsing fails (default: null)
27     * @returns {*} Parsed data or default value
28     * 
29     * @example
30     * // Parse array
31     * var options = FN.Utils.parseJSON('["a","b"]', []);
32     * // Returns: ["a", "b"]
33     * 
34     * @example
35     * // Parse object
36     * var settings = FN.Utils.parseJSON('{"key":"value"}', {});
37     * // Returns: {key: "value"}
38     * 
39     * @example
40     * // Already parsed
41     * var options = FN.Utils.parseJSON(["a","b"], []);
42     * // Returns: ["a", "b"] (no parsing needed)
43     * 
44     * @example
45     * // Invalid JSON
46     * var options = FN.Utils.parseJSON('invalid', []);
47     * // Returns: [] (default value)
48     */
49    window.FormNest.Utils.parseJSON = function(data, defaultValue) {
50        // Set default value if not provided
51        if (defaultValue === undefined) {
52            defaultValue = null;
53        }
54        
55        // Already parsed object or array
56        if (typeof data === 'object' && data !== null) {
57            return data;
58        }
59        
60        // Empty or invalid string
61        if (!data || (typeof data === 'string' && !data.trim())) {
62            return defaultValue;
63        }
64        
65        // Parse string
66        if (typeof data === 'string') {
67            try {
68                var parsed = JSON.parse(data);
69                return parsed !== null ? parsed : defaultValue;
70            } catch (e) {
71                return defaultValue;
72            }
73        }
74        
75        // Unknown type (number, boolean, etc.)
76        return defaultValue;
77    };
78    
79    /**
80     * Parse field options safely
81     * Convenience wrapper for parseJSON with array default
82     * Used for select/radio/checkbox options
83     * 
84     * @param {*} data - Options data (string or array)
85     * @returns {Array} Parsed options array
86     * 
87     * @example
88     * var options = FN.Utils.parseFieldOptions('["Option 1","Option 2"]');
89     * // Returns: ["Option 1", "Option 2"]
90     */
91    window.FormNest.Utils.parseFieldOptions = function(data) {
92        var result = window.FormNest.Utils.parseJSON(data, []);
93        
94        // Ensure array result
95        if (!Array.isArray(result)) {
96            return [];
97        }
98        
99        return result;
100    };
101    
102    /**
103     * Parse field validation safely
104     * Convenience wrapper for parseJSON with object default
105     * Used for field validation rules
106     * 
107     * @param {*} data - Validation data (string or object)
108     * @returns {Object} Parsed validation object
109     * 
110     * @example
111     * var validation = FN.Utils.parseFieldValidation('{"required":true}');
112     * // Returns: {required: true}
113     */
114    window.FormNest.Utils.parseFieldValidation = function(data) {
115        var result = window.FormNest.Utils.parseJSON(data, {});
116        
117        // Ensure object result (silently convert array to object)
118        if (typeof result !== 'object' || Array.isArray(result)) {
119            // Array [] is treated as empty validation object {}
120            if (Array.isArray(result) && result.length === 0) {
121                return {}; // Silent conversion for empty array
122            }
123            
124            // Non-empty arrays or invalid types use default
125            return {};
126        }
127        
128        return result;
129    };
130    
131    /**
132     * Escape HTML special characters
133     * Prevents XSS attacks
134     * 
135     * @param {string} text - Text to escape
136     * @returns {string} Escaped text
137     * 
138     * @example
139     * var safe = FN.Utils.escapeHtml('<script>alert("xss")</script>');
140     * // Returns: "&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;"
141     */
142    window.FormNest.Utils.escapeHtml = function(text) {
143        if (!text) return '';
144        
145        var map = {
146            '&': '&amp;',
147            '<': '&lt;',
148            '>': '&gt;',
149            '"': '&quot;',
150            "'": '&#039;'
151        };
152        
153        return String(text).replace(/[&<>
153"']/g, function(char) {
154            return map[char];
155        });
156    };
157    
158    /**
159     * Deep clone object
160     * Creates independent copy of object/array
161     * 
162     * @param {*} obj - Object to clone
163     * @returns {*} Cloned object
164     * 
165     * @example
166     * var original = {a: 1, b: {c: 2}};
167     * var clone = FN.Utils.deepClone(original);
168     * clone.b.c = 3;
169     * // original.b.c is still 2 (independent copy)
170     */
171    window.FormNest.Utils.deepClone = function(obj) {
172        // Primitives
173        if (obj === null || typeof obj !== 'object') {
174            return obj;
175        }
176        
177        // Date
178        if (obj instanceof Date) {
179            return new Date(obj.getTime());
180        }
181        
182        // Array
183        if (obj instanceof Array) {
184            return obj.map(function(item) {
185                return window.FormNest.Utils.deepClone(item);
186            });
187        }
188        
189        // Object
190        if (obj instanceof Object) {
191            var cloned = {};
192            for (var key in obj) {
193                if (obj.hasOwnProperty(key)) {
194                    cloned[key] = window.FormNest.Utils.deepClone(obj[key]);
195                }
196            }
197            return cloned;
198        }
199        
200        return obj;
201    };
202    
203    /**
204     * Check if value is empty
205     * Considers null, undefined, empty string, empty array, empty object as empty
206     * 
207     * @param {*} value - Value to check
208     * @returns {boolean} True if empty
209     * 
210     * @example
211     * FN.Utils.isEmpty(null)      // true
212     * FN.Utils.isEmpty('')        // true
213     * FN.Utils.isEmpty([])        // true
214     * FN.Utils.isEmpty({})        // true
215     * FN.Utils.isEmpty('text')    // false
216     * FN.Utils.isEmpty([1, 2])    // false
217     */
218    window.FormNest.Utils.isEmpty = function(value) {
219        if (value === null || value === undefined) {
220            return true;
221        }
222        
223        if (typeof value === 'string' && value.trim() === '') {
224            return true;
225        }
226        
227        if (Array.isArray(value) && value.length === 0) {
228            return true;
229        }
230        
231        if (typeof value === 'object' && Object.keys(value).length === 0) {
232            return true;
233        }
234        
235        return false;
236    };
237    
238    /**
239     * Debounce function
240     * Delays function execution until after wait time
241     * 
242     * @param {Function} func - Function to debounce
243     * @param {number} wait - Wait time in milliseconds
244     * @param {boolean} immediate - Whether to execute immediately on first call (optional)
245     * @returns {Function} Debounced function
246     * 
247     * @example
248     * var debouncedSearch = FN.Utils.debounce(function() {
249     *     performSearch();
250     * }, 300);
251     * 
252     * $input.on('keyup', debouncedSearch);
253     * 
254     * @example
255     * // Immediate execution
256     * var immediateDebounce = FN.Utils.debounce(function() {
257     *     performAction();
258     * }, 300, true);
259     */
260    window.FormNest.Utils.debounce = function(func, wait, immediate) {
261        var timeout;
262        return function() {
263            var context = this;
264            var args = arguments;
265            var later = function() {
266                timeout = null;
267                if (!immediate) func.apply(context, args);
268            };
269            var callNow = immediate && !timeout;
270            clearTimeout(timeout);
271            timeout = setTimeout(later, wait);
272            if (callNow) func.apply(context, args);
273        };
274    };
275    
276    /**
277     * Generate unique ID
278     * Creates timestamp-based unique identifier
279     * 
280     * @param {string} prefix - Optional prefix (default: 'formnest')
281     * @returns {string} Unique ID
282     * 
283     * @example
284     * var id = FN.Utils.generateId('field');
285     * // Returns: "field_1234567890123"
286     */
287    window.FormNest.Utils.generateId = function(prefix) {
288        prefix = prefix || 'formnest';
289        return prefix + '_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
290    };
291    
292    /**
293     * Escape HTML attribute values
294     * Sanitize string for HTML attribute values (prevents XSS)
295     * 
296     * @param {string} str - String to escape
297     * @returns {string} Escaped string
298     * 
299     * @example
300     * var safe = FN.Utils.escapeAttr('<script>alert("xss")</script>');
301     * // Returns: "&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;"
302     */
303    window.FormNest.Utils.escapeAttr = function(str) {
304        if (typeof str !== 'string') {
305            return '';
306        }
307        
308        return str
309            .replace(/&/g, '&amp;')
310            .replace(/"/g, '&quot;')
311            .replace(/'/g, '&#39;')
312            .replace(/</g, '&lt;')
313            .replace(/>/g, '&gt;');
314    };
315    
316    /**
317     * Validate email address
318     * 
319     * @param {string} email - Email address to validate
320     * @returns {boolean} True if valid email format
321     * 
322     * @example
323     * FN.Utils.isValidEmail('[email protected]')  // true
324     * FN.Utils.isValidEmail('invalid')           // false
325     */
326    window.FormNest.Utils.isValidEmail = function(email) {
327        if (!email || typeof email !== 'string') {
328            return false;
329        }
330        // CRITICAL: Trim and remove all spaces before validation
331        // Emails cannot contain spaces (RFC 5322)
332        var cleaned = email.trim().replace(/\s+/g, '');
333        if (!cleaned) {
334            return false;
335        }
336        var pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
337        return pattern.test(cleaned);
338    };
339    
340    /**
341     * Validate URL
342     * 
343     * @param {string} url - URL to validate
344     * @returns {boolean} True if valid URL format
345     * 
346     * @example
347     * FN.Utils.isValidUrl('https://example.com')  // true
348     * FN.Utils.isValidUrl('invalid')                // false
349     */
350    window.FormNest.Utils.isValidUrl = function(url) {
351        if (!url || typeof url !== 'string') {
352            return false;
353        }
354        try {
355            new URL(url);
356            return true;
357        } catch (e) {
358            return false;
359        }
360    };
361    
362    /**
363     * Validate phone number (basic validation)
364     * 
365     * @param {string} phone - Phone number to validate
366     * @returns {boolean} True if valid phone format
367     * 
368     * @example
369     * FN.Utils.isValidPhone('09012345678')  // true
370     * FN.Utils.isValidPhone('invalid')      // false
371     */
372    window.FormNest.Utils.isValidPhone = function(phone) {
373        if (!phone || typeof phone !== 'string') {
374            return false;
375        }
376        
377        // Allow: +81, -, spaces, parentheses
378        // Remove allowed characters for validation, then check remaining digits
379        var cleaned = phone.replace(/[\s\-\(\)]/g, '');
380        
381        // Extract digits only (remove + sign for length check)
382        var digitsOnly = cleaned.replace(/\D/g, '');
383        
384        // Check length
385        if (digitsOnly.length < 6 || digitsOnly.length > 15) {
386            return false;
387        }
388        
389        // Check if starts with +81 or 81 (international format)
390        var hasPlusPrefix = cleaned.charAt(0) === '+';
391        var startsWith81 = cleaned.indexOf('81') === 0 || (hasPlusPrefix && cleaned.indexOf('+81') === 0);
392        
393        if (startsWith81) {
394            // International format: +81 or 81 prefix
395            // Remove country code (81) and check remaining digits
396            var withoutCountryCode = digitsOnly.substring(2); // Remove "81"
397            
398            // Japanese phone without leading 0: should be 9-10 digits
399            if (withoutCountryCode.length >= 9 && withoutCountryCode.length <= 10) {
400                // Add leading 0 and validate as Japanese phone
401                var withLeadingZero = '0' + withoutCountryCode;
402                return isValidJapanesePhoneFormat(withLeadingZero);
403            }
404            return false;
405        }
406        
407        // Check Japanese format (starts with 0)
408        if (digitsOnly.charAt(0) === '0') {
409            // Must be 10-11 digits for Japanese phone
410            if (digitsOnly.length >= 10 && digitsOnly.length <= 11) {
411                return isValidJapanesePhoneFormat(digitsOnly);
412            }
413            return false;
414        }
415        
416        // For other international formats (not +81), use basic validation
417        // 7-15 digits for international numbers
418        if (digitsOnly.length >= 7 && digitsOnly.length <= 15) {
419            return true;
420        }
421        
422        return false;
423    };
424    
425    /**
426     * Validate Japanese phone number format (digits only, starts with 0)
427     * @param {string} digits - Phone number digits only (e.g., "09012341234")
428     * @returns {boolean} Is valid Japanese phone format
429     */
430    function isValidJapanesePhoneFormat(digits) {
431        if (!digits || typeof digits !== 'string') {
432            return false;
433        }
434        
435        // Mobile: 090, 080, 070, 050 (10-11 digits)
436        // Accept both 10 and 11 digits for mobile: 0901234567 (10) or 09012345678 (11)
437        // Pattern: 0 + [789] + 0 + 7-8 digits = 10-11 digits total
438        if (/^0[789]0\d{7,8}$/.test(digits) || /^050\d{7,8}$/.test(digits)) {
439            return true;
440        }
441        
442        // Japanese landline: 03, 06, 052, etc. (10-11 digits)
443        // Pattern: 0 + [1-9] + 8-9 digits = 10-11 digits total
444        // Note: This also matches mobile numbers like 090, but mobile is checked first above
445        if (/^0[1-9]\d{8,9}$/.test(digits)) {
446            return true;
447        }
448        
449        // Japanese toll-free: 0120, 0800, 0570 (10-11 digits)
450        // Pattern: 0 + [1-9] + 2 digits + 7-8 digits = 10-11 digits total
451        // This is redundant with landline pattern above, but kept for clarity
452        if (/^0[1-9]\d{2}\d{7,8}$/.test(digits)) {
453            return true;
454        }
455        
456        return false;
457    }
458    
459    /**
460     * Type checking utilities
461     */
462    window.FormNest.Utils.isFunction = function(obj) {
463        return typeof obj === 'function';
464    };
465    
466    window.FormNest.Utils.isObject = function(obj) {
467        return typeof obj === 'object' && obj !== null && !Array.isArray(obj);
468    };
469    
470    window.FormNest.Utils.isArray = function(obj) {
471        return Array.isArray(obj);
472    };
473    
474    /**
475     * Simple object extend (shallow merge)
476     * 
477     * @param {Object} target - Target object
478     * @param {Object} source - Source object
479     * @returns {Object} Extended target object
480     * 
481     * @example
482     * var result = FN.Utils.extend({a: 1}, {b: 2});
483     * // Returns: {a: 1, b: 2}
484     */
485    window.FormNest.Utils.extend = function(target, source) {
486        if (!this.isObject(target) || !this.isObject(source)) {
487            return target;
488        }
489        
490        for (var key in source) {
491            if (source.hasOwnProperty(key)) {
492                target[key] = source[key];
493            }
494        }
495        return target;
496    };
497    
498    /**
499     * Throttle function execution
500     * Limits function execution to once per time period
501     * 
502     * @param {Function} func - Function to throttle
503     * @param {number} limit - Time limit in milliseconds
504     * @returns {Function} Throttled function
505     * 
506     * @example
507     * var throttledScroll = FN.Utils.throttle(function() {
508     *     handleScroll();
509     * }, 300);
510     * 
511     * $(window).on('scroll', throttledScroll);
512     */
513    window.FormNest.Utils.throttle = function(func, limit) {
514        var inThrottle;
515        return function() {
516            var args = arguments;
517            var context = this;
518            if (!inThrottle) {
519                func.apply(context, args);
520                inThrottle = true;
521                setTimeout(function() { 
522                    inThrottle = false; 
523                }, limit);
524            }
525        };
526    };
527    
528    /**
529     * Show button loading state
530     * Disables button and shows spinner animation
531     * 
532     * @param {jQuery} $button - Button element
533     * @param {string} loadingText - Loading text to display (optional)
534     * 
535     * @example
536     * FN.Utils.showButtonLoading($('#save-btn'), 'Saving...');
537     * 
538     * @example
539     * // Just spinner, no text
540     * FN.Utils.showButtonLoading($('#save-btn'));
541     */
542    window.FormNest.Utils.showButtonLoading = function($button, loadingText) {
543        if (!$button || $button.length === 0) {
544            return;
545        }
546        
547        // Store original content for restoration
548        $button.data('formnest-original-text', $button.text());
549        $button.data('formnest-original-html', $button.html());
550        $button.data('formnest-was-disabled', $button.prop('disabled'));
551        
552        // Add loading class and disable button
553        $button.addClass('formnest-loading');
554        $button.prop('disabled', true);
555        
556        // Build spinner HTML
557        var spinnerHtml = '<span class="formnest-spinner"></span>';
558        if (loadingText) {
559            spinnerHtml += ' ' + loadingText;
560        }
561        
562        $button.html(spinnerHtml);
563    };
564    
565    /**
566     * Hide button loading state
567     * Re-enables button and restores original content
568     * 
569     * @param {jQuery} $button - Button element
570     * 
571     * @example
572     * FN.Utils.hideButtonLoading($('#save-btn'));
573     */
574    window.FormNest.Utils.hideButtonLoading = function($button) {
575        if (!$button || $button.length === 0) {
576            return;
577        }
578        
579        // Remove loading class
580        $button.removeClass('formnest-loading');
581        
582        // Restore disabled state (only if it wasn't disabled before)
583        var wasDisabled = $button.data('formnest-was-disabled');
584        $button.prop('disabled', wasDisabled === true);
585        
586        // Restore original content
587        var originalHtml = $button.data('formnest-original-html');
588        if (originalHtml) {
589            $button.html(originalHtml);
590        } else {
591            var originalText = $button.data('formnest-original-text');
592            $button.text(originalText || '');
593        }
594        
595        // Clean up data
596        $button.removeData('formnest-original-text');
597        $button.removeData('formnest-original-html');
598        $button.removeData('formnest-was-disabled');
599    };
600    
601})(jQuery, window.FormNest || {});
602

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.