PageSourceSearch

https://www.tidf.org.tw/sites/all/modules/civicrm/js/crmdependentselect.js?tm7yoc

js tidf.org.tw collected 2026-10-02 04:50:43 UTC 5,951 bytes, 168 lines download raw bytes

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.