1/** 2 * CrmDependentSelect 3 * 4 * CrmDependentSelect is a lightweight, simple JavaScript plugin that creates a custom, dependent 5 * select input element within a container element. The dependencies are managed by data 6 * attributes on the select element's options. The plugin also supports an option for debug 7 * mode and can be imported as a module in environments that support it. 8 * 9 * The plugin is initialized by creating a new instance of the CrmDependentSelect constructor, passing 10 * the selector of the dependent select element and an optional settings object. The dependent 11 * select element is expected to be within an element with the '.crm-container' class and 12 * should have options with 'data-parent-filter' attributes corresponding to the parent element's 13 * values. The select element itself should have a 'data-parent' attribute with the value being 14 * the selector of the parent select element. 15 * 16 * Example usage: 17 * 18 * new crmDependentSelect('.crm-container select[data-parent]', { debug: true }); 19 * 20 * This will initialize the plugin on the '.crm-container select[data-parent]' element with debug mode turned on. 21 */ 22 23(function(global, factory) { 24 'use strict'; 25 26 // Prevent global variable conflicts 27 if (global.crmDependentSelect) { 28 console.warn('crmDependentSelect is already defined. Please check if other scripts use the same global variable name.'); 29 return; 30 } 31 32 // Check if the environment supports module.exports (such as Node.js) 33 if (typeof module === 'object' && typeof module.exports === 'object') { 34 // Export the factory function as a module 35 module.exports = factory(); 36 } else { 37 // Attach the factory function to the global object 38 global.crmDependentSelect = factory(); 39 } 40}(this, function() { 41 'use strict'; 42 43 // Ensure native methods aren't overridden 44 const originalForEach = Array.prototype.forEach; 45 46 // Default configuration options for the plugin 47 var defaultOptions = { 48 debug: false 49 }; 50 51 // Constructor for the plugin object 52 function CrmDependentSelect(element, options) { 53 if (typeof element === 'string') { 54 this.element = document.querySelector(element); 55 } else if (element instanceof HTMLElement) { 56 this.element = element; 57 } else { 58 console.warn('Invalid element parameter: must be a CSS selector or DOM element.'); 59 return; 60 } 61 62 if (!this.element) { 63 console.warn('The specified element does not exist.'); 64 return; 65 } 66 67 this.validateElement(); 68 69 // Merge user options with default options 70 this.settings = Object.assign({}, defaultOptions, options); 71 72 try { 73 this.init(); 74 } catch (e) { 75 console.error('An error occurred while initializing crmDependentSelect:', e); 76 } 77 } 78 79 // Method to get parent information of the element 80 CrmDependentSelect.prototype.getParentInfo = function() { 81 const parentId = this.element.dataset.parent; 82 const isParentCustom = this.element.getAttribute("data-parent-custom"); 83 const finalParentId = isParentCustom !== '0' ? `custom_${parentId}` : parentId; 84 const parent = document.querySelector(`.crm-container select[name^="${finalParentId}"]`); 85 86 return { 87 parentId: parentId, 88 parent: parent 89 }; 90 }; 91 92 // Method to validate the element 93 CrmDependentSelect.prototype.validateElement = function() { 94 if (!this.element) { 95 throw new Error('The selected element does not exist'); 96 } 97 if (!this.element.closest('.crm-container')) { 98 throw new Error('The selected element must be inside a ".crm-container"'); 99 } 100 101 const { parentId, parent } = this.getParentInfo(); 102 103 if (!parentId) { 104 throw new Error('The selected element must have a "data-parent" attribute'); 105 } 106 107 if (!parent) { 108 throw new Error('The dependent parent element does not exist'); 109 } 110 } 111 112 // Method to initialize the plugin 113 CrmDependentSelect.prototype.init = function() { 114 // Assign the element to the container property 115 this.container = this.element; 116 this.setupDependentSelect(); 117 } 118 119 // Method to setup the dependent select element 120 CrmDependentSelect.prototype.setupDependentSelect = function() { 121 // Make sure native Array.forEach method is not overridden 122 if (Array.prototype.forEach !== originalForEach) { 123 throw new Error('Array\'s forEach method seems to be overridden.'); 124 } 125 126 // Get all original options from the select element 127 const originalOptions = Array.from(this.container.querySelectorAll('option')); 128 this.container.classList.add("depended"); 129 130 const { parent } = this.getParentInfo(); 131 132 // Function to update child options based on parent value 133 const updateChildOptions = (parentVal) => { 134 // Clear all current options 135 this.container.innerHTML = ''; 136 137 // Add options that either have no parent filter or match the current parent value 138 originalOptions.forEach(option => { 139 if (!option.getAttribute('data-parent-filter') || option.getAttribute('data-parent-filter') === parentVal) { 140 this.container.appendChild(option.cloneNode(true)); 141 } 142 }); 143 144 // Reset selection to first option (avoid selecting non-existent options) 145 if (this.container.options.length > 0) { 146 this.container.selectedIndex = 0; 147 } 148 149 // Trigger change event to update dependent child selects 150 // Using setTimeout to avoid potential event loop issues 151 setTimeout(() => { 152 const event = new Event('change', { bubbles: true });
153 this.container.dispatchEvent(event); 154 }, 0); 155 } 156 157 // Initialize child options with current parent value 158 updateChildOptions(parent.value); 159 160 // Listen for parent value changes 161 parent.addEventListener('change', function() { 162 updateChildOptions(parent.value); 163 }); 164 } 165 166 // Return the plugin constructor 167 return CrmDependentSelect; 168}));
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.