PageSourceSearch

https://commercialtype.com/assets/gray_matter/forms/errors-ce79b4b…7575a13b938f39189559c40ac69e3abe2ff789fd.js

js commercialtype.com collected 2026-09-24 15:24:55 UTC 7,163 bytes, 193 lines download raw bytes

1// Copyright (c) 2021-2025 GrayBits LLC
2
3import * as Builder from 'gray_matter/utility/builder'
4import * as Accessibility from 'gray_matter/utility/accessibility'
5
6export default class Errors{
7  constructor(form, form_errors = false){
8    this.form = form;
9    this.form_errors = form_errors || this.form.querySelector('.form_errors');
10    this.current_error_elements = [];
11  }
12  show(data, no_scroll){
13    this.current_error_elements = [];
14    this.current_error_index = 0;
15    this.form.classList.remove('with_errors');
16   
17    if (data.errors) this.showAttributeErrors(data.errors);
18    this.showCellErrors(data.cell_errors);
19    
20    if (data.message && this.form_errors){
21      this.form_errors.innerHTML = data.message;
22      this.form_errors.classList.add('shown');
23    }
24    if (this.form.classList.contains('item_form') && 
25        this.current_error_elements.length > 0){
26      if (this.current_error_elements.length > 1){
27        let next_error = Builder.createElement('button', {class: ['button', 'next', 'icon_only', 'error']});
28        let previous_error = Builder.createElement('button', {class: ['button', 'previous', 'icon_only', 'error']});
29        let errors_nav = document.getElementById('errors_navigation');
30        errors_nav.innerHTML = '<span id="errors_nav_text">Jump to error</span>';
31        errors_nav.append(previous_error);
32        errors_nav.append(next_error)
33        errors_nav.classList.add('shown');
34        previous_error.addEventListener('click', (e) => this.showError(-1));
35        next_error.addEventListener('click', (e) => this.showError(1));
36      }
37      this.showCurrentError(no_scroll);
38    }
39  }
40
41  showError(direction){
42    this.current_error_index = (this.current_error_index + direction + this.current_error_elements.length) % this.current_error_elements.length;
43    this.showCurrentError();
44  }
45
46  showCurrentError(no_scroll){
47    let tab_navigation = document.querySelector('.tab_navigation');
48    let error_element = this.current_error_elements[this.current_error_index];
49    let in_tab = error_element.closest('.tab');
50    if (in_tab){
51      in_tab.openTab();
52    }
53    if (!no_scroll) error_element.scrollIntoView();
54    Accessibility.focus(error_element);
55    
56    if (tab_navigation){
57      let scrollable = tab_navigation.closest('.scrollable');
58      scrollable.scrollTop = scrollable.scrollTop - tab_navigation.offsetHeight;
59    }
60  }
61
62  showAttributeErrors(errors, options = {}){
63    for (const [attribute, error] of Object.entries(errors)){
64      this.showAttributeError(attribute, error, options);
65    }
66    this.form.classList.add('with_errors');
67  }
68
69  showCellErrors(cell_errors, options = {}){
70    if (!cell_errors) return;
71
72    let has_errors = false;
73    if (cell_errors.cell_groups){
74      for (const [cell_group, errors] of Object.entries(cell_errors.cell_groups)){
75        this.showCellGroupErrors(this.cellsForErrors(cell_group, options), errors, options);
76        has_errors = true;
77      }
78    }
79
80    has_errors = has_errors || this.showNestedBelongsToErrors(cell_errors.nested_belongs_to);
81    has_errors = has_errors || this.showMediaSetErrors(cell_errors.media_sets, document, options);
82    
83    if (has_errors){
84      this.form.classList.add('with_errors');
85    }
86  }
87
88  cellsForErrors(cell_group, options){
89    let base_selector = `.field_wrapper[data-attribute="${cell_group}"]`
90    if (options.nested_belongs_to){      
91      return document.querySelector(`${base_selector}[data-belongs-to-attribute="${options.nested_belongs_to}"] .cells`);
92    } else {
93      return document.querySelector(`${base_selector}:not(data-belongs-to-attribute) .cells`);
94    }
95  }
96
97  showNestedBelongsToErrors(nested_belongs_to_errors){
98    if (!nested_belongs_to_errors) return;
99
100    for (const [attribute, errors] of Object.entries(nested_belongs_to_errors)){
101      if (errors.errors) this.showAttributeErrors(errors.errors, { nested_belongs_to: attribute });
102      this.showCellErrors(errors.cell_errors, { nested_belongs_to: attribute });
103    }
104  }
105
106  showMediaSetErrors(media_set_errors, parent, options){
107    if(!media_set_errors) return;
108
109    let has_errors = false;
110    for (const [media_set, errors] of Object.entries(media_set_errors)){
111      let media_cells = parent.querySelector(`.field_wrapper[data-attribute="${media_set}"] .media_cells`);
112      for (const [media_cell_position, media_cell_errors] of Object.entries(errors)){
113        let media_cell = media_cells.children[media_cell_position * 1];
114        this.showAttributeErrors(media_cell_errors, { cell: media_cell });
115        has_errors = true;
116      }
117    } 
118    return has_errors;
119  }
120
121  showCellGroupErrors(cells, errors, options = {}){
122    for (const [cell_position, cell_errors] of Object.entries(errors)){
123      let cell = cells.children[cell_position * 1];
124      this.showAttributeErrors(cell_errors.errors, { cell: cell, nested_belongs_to: options.nested_belongs_to });
125      this.showMediaSetErrors(cell_errors.media_set_errors, cell, options);
126    }
127  }
128
129  showAttributeError(attribute, errors, options = {}){
130    let field_wrapper = this.getFieldWrapper(attribute, options);
131    
132    if (!field_wrapper){
133      return;
134    }
135    let label = field_wrapper.querySelector('label');
136    let error_wrapper = field_wrapper.querySelector('.attribute_errors');
137    if (!error_wrapper){
138      error_wrapper = Builder.createElement('div', { class: ['attribute_errors', 'errors'] });
139      if (label){
140        Builder.insertAfter(label, error_wrapper);
141      } else {
142        field_wrapper.innerHTML = '';
143        field_wrapper.appendChild(error_wrapper);
144      }
145    }
146    this.current_error_elements.push(field_wrapper);
147    error_wrapper.innerHTML = Array.isArray(errors) ? errors.join(', ') : errors;
148    field_wrapper.classList.add('field_with_errors');
149  }
150
151
152  getFieldWrapper(attribute, options){   
153    if (attribute == 'base'){
154      return document.getElementById('base_error');
155    } else {
156      let base_selector = '.field_wrapper'
157      if (options.nested_belongs_to){
158        if (options.cell){
159          base_selector += `[data-belongs-to-attribute="${options.nested_belongs_to}"] .field_wrapper`;
160        } else {
161          base_selector += `[data-belongs-to-attribute="${options.nested_belongs_to}"]`;
162        }
163      }
164      if (options.cell){
165        return options.cell.querySelector(`${base_selector}[data-attribute="${attribute}"]`);
166      } else {
167        return this.form.querySelector(`${base_selector}[data-attribute="${attribute.replace(/_cells$/g,'')}"]:not(.cell_field)`);
168      }
169    }
170  }
171
172  clear(){
173    if (this.form_errors){
174      this.form_errors.classList.remove('shown');
175    }
176    if (this.form_errors){
177      this.form_errors.innerHTML = '';
178    }
179    let errors_nav = document.getElementById('errors_navigation');
180    if (errors_nav){
181      errors_nav.classList.remove('shown', 'single');
182      errors_nav.innerHTML = '';
183    }
184    this.form.querySelectorAll('.field_with_errors').forEach((e) => {
185      e.classList.remove('field_with_errors');
186      let error_message = e.querySelector('.attribute_errors');
187      if (error_message){
188        error_message.remove();
189      }
190    });
191  }
192}
193;

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.