1var Packlink = window.Packlink || {}; 2 3(function () { 4 function LocationPickerConstructor() { 5 const days = [ 6 'monday', 7 'tuesday', 8 'wednesday', 9 'thursday', 10 'friday', 11 'saturday', 12 'sunday' 13 ]; 14 15 const searchKeys = [ 16 'id', 17 'zip', 18 'name', 19 'address', 20 'city' 21 ]; 22 23 let lang = null; 24 let selectCallback = null; 25 26 let templateContainer = null; 27 28 let selectedLocation = null; 29 let dropOffs = {}; 30 let renderedLocations = []; 31 32 let locations = {}; 33 34 // Register public method and properties. 35 this.display = display; 36 37 /** 38 * Initializes location picker.map.event.trigger('resize'); 39 * 40 * @param {array} locations Array of Drop-Off locations. 41 * @param {function} selectLocationCallback callback that is called when user selects location. 42 * @param {string | null} selectedId Set id of selected location. Set NULL if no location is selected. 43 * @param {string} [language] Translation language code. Defaults to 'en' for English. 44 */ 45 function display(locations, selectLocationCallback, selectedId, language) { 46 templateContainer = document.getElementById('template-container'); 47 lang = typeof language === 'undefined' ? 'en' : language; 48 selectCallback = selectLocationCallback; 49 initializeSearchBox(); 50 renderLocations(locations, selectedId); 51 } 52 53 54 /** 55 * Message receiver. 56 * 57 * @param {object} payload 58 * @param {string | null} selectedId 59 */ 60 function renderLocations(payload, selectedId) { 61 renderedLocations = []; 62 for (let loc of payload) { 63 locations[loc.id] = loc; 64 renderedLocations.push(loc.id); 65 } 66 67 addLocations(selectedId); 68 } 69 70 /** 71 * Initializes search-box. 72 */ 73 function initializeSearchBox() { 74 let searchBox = getElement(document, 'search-box'); 75 76 if (searchBox) { 77 searchBox.addEventListener('keyup', debounce(200, searchBoxKeyupHandler)); 78 79 let searchLabel = getElement(document, 'search-box-label'); 80 searchLabel.innerHTML = getTranslation(lang, ['searchLabel']); 81 } 82 } 83 84 /** 85 * Handles keyup on search-box. 86 * 87 * @param event 88 */ 89 function searchBoxKeyupHandler(event) { 90 let value = event.target.value; 91 92 renderedLocations = Object.keys(locations); 93 94 if (value !== '') { 95 renderedLocations = []; 96 97 for (let term of searchKeys) { 98 let result = filterByKey(locations, term, value); 99 renderedLocations.push(...result); 100 } 101 102 renderedLocations = renderedLocations.filter(function (value, index, array) { 103 return array.indexOf(value) === index; 104 }) 105 } 106 107 addLocations(); 108 } 109 110 /** 111 * Adds Locations. 112 * 113 * {string | null} selectedId 114 */ 115 function addLocations(selectedId) { 116 let locationsNode = getElement(document, 'locations'); 117 118 while (locationsNode.firstChild) { 119 locationsNode.firstChild.remove(); 120 } 121 122 for (let displayed in dropOffs) { 123 if (dropOffs.hasOwnProperty(displayed)) { 124 dropOffs[displayed].remove(); 125 } 126 } 127 128 dropOffs = {}; 129 selectedLocation = null; 130 131 for (let id of renderedLocations) { 132 let location = locations[id]; 133 let locationElement = createLocation(location); 134 dropOffs[id] = locationElement; 135 locationsNode.appendChild(locationElement); 136 137 if (selectedId && selectedId === id) { 138 locationElement.click(); 139 } 140 } 141 } 142 143 144 145 /** 146 * Creates DropOff location. 147 * 148 * @param location 149 * @return {Node} 150 */ 151 function createLocation(location) { 152 let template = getTemplate('location-template'); 153 154 getElement(template, 'location-name').innerHTML = location.name; 155 getElement(template, 'location-street').innerHTML = location.address; 156 getElement(template, 'location-city').innerHTML = location.city + ', ' + location.zip; 157 getElement(template, 'composite-address').innerHTML = '<b>' + location.name + '</b>' + ', ' 158 + location.address + ', ' + location.city + ', ' + location.zip; 159 160 let showOnMapBtn = getElement(template, 'show-on-map'); 161 showOnMapBtn.href = `https://www.google.com/maps/search/?api=1&query=${location['lat']},${location['long']}`; 162 showOnMapBtn.title = getTranslation(lang, ['showOnMapTitle']); 163 164 let showOnMapCompositeBtn = getElement(template, 'composite-show-on-map'); 165 showOnMapCompositeBtn.href = `https://www.google.com/maps/search/?api=1&query=${location['lat']},${location['long']}`; 166 showOnMapCompositeBtn.title = getTranslation(lang, ['showOnMapTitle']); 167 showOnMapCompositeBtn.innerHTML = getTranslation(lang, ['showOnMapTitle']); 168 169 let showWorkingHoursBtn = getElement(template, 'show-working-hours-btn'); 170 showWorkingHoursBtn.innerHTML = getTranslation(lang, ['workingHoursLabel']); 171 showWorkingHoursBtn.setAttribute('data-id', location['id']); 172 showWorkingHoursBtn.addEventListener('click', workingHoursButtonClickHandler); 173 174 let showCompositeHoursBtn = getElement(template, 'show-composite-working-hours-btn'); 175 showCompositeHoursBtn.innerHTML = getTranslation(lang, ['workingHoursLabel']); 176 showCompositeHoursBtn.setAttribute('data-id', location['id']); 177 showCompositeHoursBtn.addEventListener('click', workingHoursButtonClickHandler); 178 179 let workingHoursNode = getElement(template, 'working-hours'); 180 let compositeHoursNode = getElement(template, 'composite-working-hours'); 181 182 for (let day of days) { 183 if (location['workingHours'][day]) { 184 let workingHoursTemplate = getWorkingHoursTemplate(day, location['workingHours'][day]); 185 workingHoursNode.appendChild(workingHoursTemplate); 186 compositeHoursNode.appendChild(workingHoursTemplate.cloneNode(true)); 187 } 188 } 189 190 let selectBtn = getElement(template, 'select-btn'); 191 selectBtn.innerHTML = getTranslation(lang, ['selectLabel']); 192 selectBtn.addEventListener('click', function () { 193 selectCallback(location['id']); 194 }); 195 196 let selectCompositeBtn = getElement(template, 'composite-select-btn'); 197 selectCompositeBtn.innerHTML = getTranslation(lang, ['selectLabel']); 198 selectCompositeBtn.addEventListener('click', function () { 199 selectCallback(location['id']); 200 }); 201 202 template.setAttribute('data-id', location.id); 203 template.addEventListener('click', locationClickedHandler); 204 205 return template; 206 } 207 208 /** 209 * Handles click event on location. 210 */ 211 function locationClickedHandler(event) { 212 if (event.target.classList.contains('excluded') || event.target === selectedLocation) { 213 return; 214 } 215 216 unselectSelectedLocation(); 217 218 let id = event.target.getAttribute('data-id'); 219 220 selectedLocation = dropOffs[id]; 221 selectedLocation.classList.add('selected'); 222 } 223 224 /** 225 * Un-selects location. 226 */ 227 function unselectSelectedLocation() { 228 if (selectedLocation) { 229 selectedLocation.classList.remove('selected'); 230 selectedLocation = null; 231 } 232 } 233 234 /** 235 * Retrieves working hours template. 236 * 237 * @param {string} day 238 * @param {string} hours 239 * @return {Node} 240 */ 241 function getWorkingHoursTemplate(day, hours) { 242 let element = getTemplate('working-hours-template'); 243 244 let dayElement = getElement(element, 'day'); 245 dayElement.innerHTML = getTranslation(lang, ['days', day]); 246 247 let hourElement = getElement(element, 'hours'); 248 hourElement.innerHTML = hours; 249 250 return element; 251 } 252 253 /** 254 * Handles working hours button clicked event. 255 * 256 * @param event 257 */ 258 function workingHoursButtonClickHandler(event) { 259 let id = event.target.getAttribute('data-id'); 260 let workingHoursNode; 261 262 if (event.target.hasAttribute('data-lp-composite')) { 263 workingHoursNode = getElement(dropOffs[id], 'composite-working-hours'); 264 } else { 265 workingHoursNode = getElement(dropOffs[id], 'working-hours'); 266 } 267 268 if (workingHoursNode.classList.contains('enabled')) { 269 event.target.innerHTML = getTranslation(lang, ['workingHoursLabel']); 270 workingHoursNode.classList.remove('enabled'); 271 } else { 272 event.target.innerHTML = getTranslation(lang, ['hideWorkingHoursLabel']); 273 workingHoursNode.classList.add('enabled'); 274 } 275 276 dropOffs[id].click(); 277 } 278 279 /** 280 * Returns template element 281 * 282 * @param {string} template 283 * @return {Node} 284 */ 285 function getTemplate(template) { 286 return templateContainer.querySelector(`[data-lp-id=${template}]`).cloneNode(true); 287 } 288 289 /** 290 * Retrieves element in template. 291 * 292 * @param {Node} node 293 * @param {string} element 294 * @return {Element} 295 */ 296 function getElement(node, element) { 297 return node.querySelector(`[data-lp-id=${element}]`); 298 } 299 300 /** 301 * Retrieves translation. 302 * 303 * @param {string} lang 304 * @param {array} keys 305 * @return {string} 306 */ 307 function getTranslation(lang, keys) { 308 let object = LocationPickerTranslations; 309 310 object = object.hasOwnProperty(lang) ? object[lang] : object['en']; 311 312 for (let key of keys) { 313 object = object[key]; 314 315 if (typeof object === 'undefined') { 316 return keys.join('.'); 317 } 318 } 319 320 return object; 321 } 322 323 /** 324 * Filters hash-map by value. 325 * 326 * @param {object} collection 327 * @param {string} key 328 * @param {string} value 329 */ 330 function filterByKey(collection, key, value) { 331 let result = []; 332 333 value = value.toUpperCase(); 334 335 for (let id in collection) { 336 if (collection.hasOwnProperty(id)) { 337 let target = collection[id][key].toUpperCase(); 338 if (target.match(new RegExp('.*' + value + '.*')) !== null) { 339 result.push(id); 340 } 341 } 342 } 343 344 return result; 345 } 346 347 /** 348 * Debounces function. 349 * 350 * @param {number} delay 351 * @param {function} target 352 * @return {Function} 353 */ 354 function debounce(delay, target) { 355 let timerId; 356 return function (...args) { 357 if (timerId) { 358 clearTimeout(timerId); 359 } 360 361 timerId = setTimeout(function () { 362 target(...args); 363 timerId = null; 364 }, delay); 365 } 366 } 367 } 368 369 Packlink.locationPicker = new LocationPickerConstructor(); 370})();
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.