PageSourceSearch

https://cure-connect-d13a8afda010.herokuapp.com/assets/controllers…760fe41cd55a835fd2ff6b8a08b1b135fb5b3dfe.js

js cure-connect-d13a8afda010.herokuapp.com collected 2026-10-03 14:49:12 UTC 13,460 bytes, 371 lines download raw bytes

1import { Controller } from "@hotwired/stimulus"
2
3export default class extends Controller {
4  /**
5   * GetAddress API configuration
6   * @private
7   */
8  API_KEY = 'm-Xt3XwMO0CKb4duGF6QoA45555';
9
10  connect() {
11    this.initializePostcodeLookup()
12    this.observeRepresentativeChanges()
13    this.addPostcodeLookupClasses()
14  }
15
16  /**
17   * Initialize the postcode lookup functionality
18   * This method sets up both the getAddress plugin and enhances its UI
19   */
20  initializePostcodeLookup() {
21    this.initializeGetAddressPlugin();
22    this.initializePostcodeLookupStyling();
23  }
24
25  /**
26   * Initialize the getAddress plugin for both billing and delivery addresses
27   * This sets up the API connection and field mappings
28   */
29  initializeGetAddressPlugin() {
30    // Initialize Patient billing address lookup
31    this.initializeLookupForElement('#patient_billing_postcode_lookup', {
32      line_1: '#patient_form_billing_address_line1',
33      line_2: '#patient_form_billing_address_line2',
34      post_town: '#patient_form_billing_address_city',
35      county: '#patient_form_billing_address_county',
36      postcode: '#patient_form_billing_address_postal_code'
37    });
38
39    // Initialize Patient delivery address lookup
40    this.initializeLookupForElement('#patient_delivery_postcode_lookup', {
41      line_1: '#patient_form_delivery_address_line1',
42      line_2: '#patient_form_delivery_address_line2',
43      post_town: '#patient_form_delivery_address_city',
44      county: '#patient_form_delivery_address_county',
45      postcode: '#patient_form_delivery_address_postal_code'
46    });
47
48    // Initialize billing address lookup
49    this.initializeLookupForElement('#billing_postcode_lookup', {
50      line_1: '#addresses_form_billing_address_line1',
51      line_2: '#addresses_form_billing_address_line2',
52      post_town: '#addresses_form_billing_address_city',
53      county: '#addresses_form_billing_address_county',
54      postcode: '#addresses_form_billing_address_postal_code'
55    });
56
57    // Initialize delivery address lookup
58    this.initializeLookupForElement('#delivery_postcode_lookup', {
59      line_1: '#addresses_form_delivery_address_line1',
60      line_2: '#addresses_form_delivery_address_line2',
61      post_town: '#addresses_form_delivery_address_city',
62      county: '#addresses_form_delivery_address_county',
63      postcode: '#addresses_form_delivery_address_postal_code'
64    });
65
66
67    this.initializeLookupForElement('#partner_delivery_postcode_lookup', {
68      line_1: '#partner_delivery_address_attributes_line1',
69      line_2: '#partner_delivery_address_attributes_line2',
70      post_town: '#partner_delivery_address_attributes_city',
71      county: '#partner_delivery_address_attributes_county',
72      postcode: '#partner_delivery_address_attributes_postal_code'
73    });
74
75    this.initializeLookupForElement('#patient_modal_billing_postcode_lookup', {
76      line_1: '#addresses_form_billing_address_line1',
77      line_2: '#addresses_form_billing_address_line2',
78      post_town: '#addresses_form_billing_address_city',
79      county: '#addresses_form_billing_address_county',
80      postcode: '#addresses_form_billing_address_postal_code'
81    });
82
83    // Initialize prescription delivery address lookup (admin modal)
84    this.initializeLookupForElement('#postcode_lookup_container', {
85      line_1: '#prescription_delivery_line1',
86      line_2: '#prescription_delivery_line2',
87      post_town: '#prescription_delivery_city',
88      county: '#prescription_delivery_county',
89      postcode: '#prescription_delivery_postal_code'
90    });
91
92    // Initialize all existing representative lookups
93    this.initializeAllRepresentativeLookups();
94  }
95
96  /**
97   * Initialize a single postcode lookup element
98   * @param {string} selector - CSS selector for the lookup element
99   * @param {Object} outputFields - Mapping of output fields
100   */
101  initializeLookupForElement(selector, outputFields) {
102    const element = document.querySelector(selector);
103    if (element && !element.hasAttribute('data-getaddress-initialized')) {
104      $(selector).getAddress({
105        api_key: this.API_KEY,
106        output_fields: outputFields
107      });
108      element.setAttribute('data-getaddress-initialized', 'true');
109    }
110  }
111
112  /**
113   * Initialize all representative postcode lookups (both existing and new)
114   */
115  initializeAllRepresentativeLookups() {
116    // Find all representative rows
117    const representativeRows = document.querySelectorAll('.representative-row');
118    
119    representativeRows.forEach((row, index) => {
120      this.initializeRepresentativeLookup(row, index);
121    });
122  }
123
124  /**
125   * Initialize postcode lookup for a specific representative row
126   * @param {HTMLElement} representativeRow - The representative row element
127   * @param {number} index - Index or timestamp for unique identification
128   */
129  initializeRepresentativeLookup(representativeRow, index = null) {
130    const postcodeDiv = representativeRow.querySelector('[id*="representative_postcode_lookup"]');
131    if (!postcodeDiv) return;
132
133    // If no unique ID exists, create one
134    if (!postcodeDiv.id || postcodeDiv.id === 'representative_postcode_lookup') {
135      const uniqueId = index !== null ? index : new Date().getTime();
136      postcodeDiv.id = `representative_postcode_lookup_${uniqueId}`;
137    }
138
139    // Skip if already initialized
140    if (postcodeDiv.hasAttribute('data-getaddress-initialized')) return;
141
142    // Find the address fields within this representative row
143    const addressFields = this.getRepresentativeAddressFields(representativeRow);
144    
145    if (addressFields.line1 && addressFields.postcode) {
146      // Initialize the getAddress plugin
147      $(`#${postcodeDiv.id}`).getAddress({
148        api_key: this.API_KEY,
149        output_fields: {
150          line_1: `#${addressFields.line1.id}`,
151          line_2: `#${addressFields.line2.id}`,
152          post_town: `#${addressFields.city.id}`,
153          county: `#${addressFields.county.id}`,
154          postcode: `#${addressFields.postcode.id}`
155        }
156      });
157
158      // Mark as initialized
159      postcodeDiv.setAttribute('data-getaddress-initialized', 'true');
160    }
161  }
162
163  /**
164   * Get address field elements within a representative row
165   * @param {HTMLElement} representativeRow - The representative row element
166   * @returns {Object} Object containing address field elements
167   */
168  getRepresentativeAddressFields(representativeRow) {
169    const fields = {};
170    
171    // Find fields by name patterns or labels
172    fields.line1 = representativeRow.querySelector('input[name*="[line1]"]');
173    fields.line2 = representativeRow.querySelector('input[name*="[line2]"]');
174    fields.city = representativeRow.querySelector('input[name*="[city]"]');
175    fields.county = representativeRow.querySelector('input[name*="[county]"]');
176    fields.postcode = representativeRow.querySelector('input[name*="[postal_code]"]');
177
178    // Ensure all fields have IDs for getAddress to target
179    Object.keys(fields).forEach(key => {
180      if (fields[key] && !fields[key].id) {
181        const timestamp = new Date().getTime();
182        fields[key].id = `representative_${key}_${timestamp}`;
183      }
184    });
185
186    return fields;
187  }
188
189  /**
190   * Observe changes in the representatives container to initialize new lookups
191   */
192  observeRepresentativeChanges() {
193    const container = document.getElementById('representatives-container');
194    if (!container) return;
195
196    const observer = new MutationObserver((mutations) => {
197      mutations.forEach((mutation) => {
198        if (mutation.addedNodes.length) {
199          mutation.addedNodes.forEach((node) => {
200            if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains('representative-row')) {
201              // Wait a bit for the DOM to settle, then initialize
202              setTimeout(() => {
203                this.initializeRepresentativeLookup(node);
204                this.initializePostcodeLookupStyling();
205              }, 100);
206            }
207          });
208        }
209      });
210    });
211
212    observer.observe(container, { childList: true, subtree: true });
213  }
214
215  /**
216   * Add CSS classes to postcode lookup containers for easier styling
217   */
218  addPostcodeLookupClasses() {
219    // Add class to static lookups
220    const staticLookups = [
221      'patient_billing_postcode_lookup',
222      'patient_delivery_postcode_lookup',
223      'billing_postcode_lookup', 
224      'delivery_postcode_lookup',
225      'partner_delivery_postcode_lookup',
226      'patient_modal_billing_postcode_lookup',
227      'postcode_lookup_container'
228    ];
229
230    staticLookups.forEach(id => {
231      const element = document.getElementById(id);
232      if (element) {
233        element.classList.add('postcode-lookup-container');
234      }
235    });
236
237    // Add class to all representative lookups (both existing and dynamic)
238    const representativeLookups = document.querySelectorAll('[id^="representative_postcode_lookup"]');
239    representativeLookups.forEach(element => {
240      element.classList.add('postcode-lookup-container');
241    });
242  }
243
244  /**
245   * Initialize the styling enhancements for the postcode lookup
246   * This wraps the input and button in a container and handles placeholder text
247   */
248  initializePostcodeLookupStyling() {
249    // Handle static lookups
250    const staticIds = ['patient_billing_postcode_lookup', 'patient_delivery_postcode_lookup', 'billing_postcode_lookup', 'delivery_postcode_lookup', 'partner_delivery_postcode_lookup', 'patient_modal_billing_postcode_lookup', 'postcode_lookup_container'];
251    staticIds.forEach((id) => {
252      this.stylePostcodeLookup(id);
253    });
254
255    // Handle all representative lookups
256    const representativeDivs = document.querySelectorAll('[id*="representative_postcode_lookup"]');
257    representativeDivs.forEach((div) => {
258      this.stylePostcodeLookup(div.id);
259    });
260  }
261
262  /**
263   * Style a specific postcode lookup element
264   * @param {string} id - The ID of the postcode lookup element
265   */
266  stylePostcodeLookup(id) {
267    const postcodeDiv = document.getElementById(id);
268    if (postcodeDiv) {
269      if (postcodeDiv.querySelector('input') && postcodeDiv.querySelector('button')) {
270        this.wrapSearchElements(postcodeDiv);
271      } else {
272        this.observeAndWrapElements(postcodeDiv);
273      }
274    }
275  }
276
277  /**
278   * Wrap the search elements (input and button) in a container div
279   * This is needed to:
280   * 1. Apply consistent styling to the input and button
281   * 2. Handle the placeholder text properly
282   * 3. Prevent the default "Search.." text from appearing
283   * @param {HTMLElement} postcodeDiv - The container div for the postcode lookup
284   */
285  wrapSearchElements(postcodeDiv) {
286    // Check if already wrapped
287    if (postcodeDiv.querySelector('.getaddress-search-box')) return;
288    
289    const input = postcodeDiv.querySelector('input');
290    const button = postcodeDiv.querySelector('button');
291    if (input && button) {
292      // Set placeholder text
293      input.placeholder = "Enter your postal code";
294      
295      // Watch for value changes to prevent "Search.." text
296      this.observeInputValue(input);
297      
298      // Create wrapper and move elements into it
299      const wrapper = document.createElement('div');
300      wrapper.className = 'getaddress-search-box';
301      input.parentNode.insertBefore(wrapper, input);
302      wrapper.appendChild(input);
303      wrapper.appendChild(button);
304    }
305  }
306
307  /**
308   * Observe the postcode div for changes and wrap elements when they appear
309   * This is needed because the getAddress plugin creates its elements asynchronously
310   * @param {HTMLElement} postcodeDiv - The container div to observe
311   */
312  observeAndWrapElements(postcodeDiv) {
313    const observer = new MutationObserver((mutations) => {
314      mutations.forEach((mutation) => {
315        if (mutation.addedNodes.length) {
316          if (postcodeDiv.querySelector('input') && postcodeDiv.querySelector('button')) {
317            this.wrapSearchElements(postcodeDiv);
318            observer.disconnect();
319          }
320        }
321      });
322    });
323    
324    observer.observe(postcodeDiv, { childList: true });
325  }
326
327  /**
328   * Observe input value changes to prevent "Search.." text from appearing
329   * This is needed because the getAddress plugin tries to set this text by default
330   * @param {HTMLInputElement} input - The input element to observe
331   */
332  observeInputValue(input) {
333    // Create a MutationObserver to watch for value changes
334    const observer = new MutationObserver((mutations) => {
335      mutations.forEach((mutation) => {
336        if (mutation.type === 'attributes' && mutation.attributeName === 'value') {
337          if (input.value === 'Search..') {
338            input.value = '';
339          }
340        }
341      });
342    });
343
344    // Observe the input for value changes
345    observer.observe(input, { attributes: true });
346
347    // Also set up an interval as backup
348    const intervalId = setInterval(() => {
349      if (input.value === 'Search..') {
350        input.value = '';
351      }
352      // Clean up interval if input is removed from DOM
353      if (!document.contains(input)) {
354        clearInterval(intervalId);
355      }
356    }, 100);
357  }
358
359  /**
360   * Method to be called after a new representative is added
361   * This should be called from your addRepresentative method
362   */
363  reinitializeRepresentativeLookups() {
364    // Small delay to ensure DOM is updated
365    setTimeout(() => {
366      this.initializeAllRepresentativeLookups();
367      this.initializePostcodeLookupStyling();
368      this.addPostcodeLookupClasses();
369    }, 100);
370  }
371};

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.