PageSourceSearch

https://www.dunnedwards.com/assets/job_records/job_record_projects…1a9431702b76b0fed728e927bc1c76c76911d9ed.js

js dunnedwards.com collected 2026-10-02 03:24:09 UTC 8,191 bytes, 250 lines download raw bytes

1import { JobRecordsToolbar } from "job_records/toolbar";
2import { JobRecordsFileInput } from "job_records/file_input";
3import { BeforeUnloadService } from 'job_records/before_unload_service';
4
5export class JobRecordProjects {
6  constructor() {
7    this.el = '.js-job-record-projects';
8    this.$el = $(this.el);
9    
10    this.$projectImage = $('#job_record_project_image');
11    this.$removeImageLink = $('.image-container .js-clear-fieldset');
12    
13    this.bindEvents();
14    this.initialize();
15  }
16
17  bindEvents() {
18    // Event delegation for dynamically generated elements
19    this.$el.on('click', '.js-add-location', (event) => {
20      this.addLocation(event);
21    });
22
23    this.$el.on('click', '.js-remove-location', (event) => {
24      this.removeLocation(event);
25    });
26
27    this.$el.on('click', '.js-clear-fieldset', (event) => {
28      this.clearFieldset(event);
29    });
30
31    this.$el.on('click', '.js-add-product', (event) => {
32      this.addProduct(event);
33    });
34
35    // Direct event bindings
36    if (this.$projectImage.length) {
37      this.$projectImage.on('change', () => this.toggleRemoveImageLinkState());
38    }
39
40    // Form input change events - select all form controls
41    const $formInputs = this.$el.find('form input, form select, form textarea, form button');
42    $formInputs.each((index, input) => {
43      if ($(input).attr('id') != "form-submit") {
44        $(input).on('change', () => this.bindBeforeUnload());
45      }
46    });
47  }
48
49  initialize() {
50    new JobRecordsToolbar();
51    new JobRecordsFileInput();
52
53    this.toggleRemoveImageLinkState();
54    this._resetLocationRemoveButton();
55    this._resetLocationAddButton();
56  }
57
58  bindBeforeUnload() {
59    // Use the shared service to bind the beforeunload event
60    BeforeUnloadService.bind();
61  }
62
63  addLocation(event) {
64    const $this = $(event.target);
65    
66    // Create location element from template
67    const $location = $(this._locationTemplate()).hide();
68
69    if ($this.hasClass('js-add-location-top')) {
70      $this.parent().after($location);
71    } else {
72      $this.parent().before($location);
73    }
74
75    // Fade in effect
76    $location.fadeIn(1000);
77
78    this._resetPositions();
79    this._resetSerialNumbers();
80    this._resetLocationRemoveButton();
81    this._resetLocationAddButton();
82  }
83
84  removeLocation(event) {
85    event.preventDefault();
86
87    const $this = $(event.target);
88    const $location = $this.closest('.js-location');
89    
90    const _locationName = () => {
91      const $nameInput = $location.find('.job_record_project_locations_name input');
92      return $nameInput.length && $nameInput.val() ? $nameInput.val() : 'this location';
93    };
94
95    if (confirm(`Are you sure you want to delete ${_locationName()}?`)) {
96      $location.remove();
97      this._resetSerialNumbers();
98    }
99
100    this._resetLocationRemoveButton();
101    this._resetLocationAddButton();
102  }
103
104  clearFieldset(event) {
105    event.preventDefault();
106
107    const $this = $(event.target);
108    const $removeImageLink = $this.next().find('.js-file-remove');
109    const selector = $this.data('target');
110    const $fieldset = $this.closest(selector);
111
112    $fieldset.find('input').val('');
113    $fieldset.find('textarea').val('');
114    $fieldset.find('select').each((index, select) => {
115      $(select).val('');
116      // Assuming you're using something like tom-select or similar instead of selecter
117      if (window.Selecter && select.selecter) {
118        select.selecter('update');
119      }
120    });
121
122    this.toggleRemoveImageLinkState();
123    if ($removeImageLink.length) {
124      $removeImageLink.click();
125    }
126  }
127
128  toggleRemoveImageLinkState() {
129    if (this.$projectImage.length && this.$projectImage[0].files && this.$projectImage[0].files.length > 0) {
130      this.$removeImageLink.show();
131    } else if (this.$removeImageLink.length) {
132      this.$removeImageLink.hide();
133    }
134  }
135
136  addProduct(event) {
137    event.preventDefault();
138    const $this = $(event.target);
139    
140    const $hiddenInput = $('<input>', {
141      class: 'hidden',
142      type: 'hidden',
143      name: 'add_product'
144    });
145    $this.after($hiddenInput);
146
147    const $locationContainer = $this.closest('.location-details');
148    const $nameInput = $locationContainer.find('.job_record_project_locations_name input');
149    const name = $nameInput.length ? $nameInput.val() : '';
150
151    const $nameHiddenInput = $('<input>', {
152      class: 'hidden',
153      type: 'hidden',
154      name: 'product_name',
155      value: name
156    });
157    
158    const $buttonContainer = $this.closest('.add-product-button-container');
159    $buttonContainer.append($nameHiddenInput);
160    
161    BeforeUnloadService.unbind();
162    
163    const $saveButton = this.$el.find('#save');
164    if ($saveButton.length) {
165      $saveButton.click();
166    }
167  }
168
169  // Private methods
170
171  _resetLocationRemoveButton() {
172    const $locations = this.$el.find('.js-location');
173    const $buttons = this.$el.find('.js-location .button-remove');
174    
175    $buttons.each((index, button) => {
176      $(button).toggle($locations.length > 1);
177    });
178  }
179
180  _resetLocationAddButton() {
181    const $locations = this.$el.find('.js-location');
182    const $addLocationBtn = this.$el.find('.add-location-button-container');
183    
184    if ($addLocationBtn.length) {
185      console.log('$addLocationBtn.length: ', $addLocationBtn.length);
186      console.log('$$locations.length: ', $locations.length);
187      $addLocationBtn.toggle($locations.length >= 1);
188    }
189  }
190
191  _locationTemplate() {
192    const n = this.$el.find('.js-location').length + 1;
193    
194    return `
195      <div class="js-location">
196        <div class="location-details">
197          <h4><span class="js-serial-number">${this._pad(n)}</span> Location Details</h4>
198          <a href="" class="js-clear-fieldset" data-target=".location-details">Clear</a>
199          <a href="#" class="button-remove js-remove-location"></a>
200          <div class="input hidden job_record_project_locations__position"><input class="hidden js-position" type="hidden" value="" name="job_record_project[locations_attributes][${n}][_position]" id="job_record_project_locations_attributes_${n}__position"></div>
201          <div class="grid two-columns">
202            <div class="column">
203              <div class="input string required job_record_project_locations_name"><label class="string required" for="job_record_project_locations_attributes_2_name"><abbr title="required">*</abbr> Location Title* (Max 54 characters)<div class="notes">Example: Walls, Ceiling, Crown Moulding</div></label><input class="string required" required="required" type="text" name="job_record_project[locations_attributes][${n}][name]" id="job_record_project_locations_attributes_2_name" maxlength="54"></div>
204            </div>
205
206            <div class="column">
207              <div class="input text optional job_record_project_locations_note"><label class="text optional" for="job_record_project_locations_attributes_2_note">Notes<div class="notes">Add notes about this location</div></label><textarea class="text optional" name="job_record_project[locations_attributes][${n}][note]" id="job_record_project_locations_attributes_2_note"  maxlength="250" rows="7"></textarea></div>
208              <p class="notes character-limit">(250)</p>
209            </div>
210          </div>
211
212          <div class="add-product-button-container">
213            <p class="notes">Add products to this location</p>
214            <a href="#" class="js-add-product button-regular">Add product</a>
215          </div>
216        </div>
217      </div>
218    `;
219  }
220
221  _resetPositions() {
222    const $positions = this.$el.find('.js-position');
223    $positions.each((index, position) => {
224      $(position).val(index);
225    });
226  }
227
228  _resetSerialNumbers() {
229    const $serialNumbers = this.$el.find('.js-serial-number');
230    $serialNumbers.each((index, serialNumber) => {
231      $(serialNumber).text(this._pad(index + 1));
232    });
233  }
234
235  _pad(value, padding = 2) {
236    let result = '';
237    for (let i = 0; i < padding; i++) {
238      result += '0';
239    }
240    result += value;
241    return result.slice(-padding);
242  }
243}
244
245// Initialize the component when DOM is loaded
246$(document).ready(() => {
247  if ($('.js-job-record-projects').length) {
248    new JobRecordProjects();
249  }
250});

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.