1/* 2Plugin: location.l3g0 3Author: Tobias Ratzberger (RIS GmbH) 4Written: 10.02.2022 5*/ 6 7class DistanceCalcluation { 8 #clientLocationInstance; 9 #boundHandleLocationRequestInvoke; 10 #boundHandleLocationRequest; 11 #boundHandleLocationRequestSuccess; 12 #boundHandleLocationRequestError; 13 14 constructor() { 15 this.valueContainerSelector = arguments[1]; 16 17 this.#boundHandleLocationRequestInvoke = this.#handleLocationRequestInvoke.bind(this); 18 this.#boundHandleLocationRequest = this.#handleLocationRequest.bind(this); 19 this.#boundHandleLocationRequestSuccess = this.#handleLocationRequestSuccess.bind(this); 20 this.#boundHandleLocationRequestError = this.#handleLocationRequestError.bind(this); 21 22 this.#clientLocationInstance = this.#getClientLocation({ 23 enableHighAccuracy: false, 24 timeout: 60000, 25 maximumAge: 0 26 }); 27 28 this.dataContainer = null; 29 this.valueContainer = null; 30 this.destLocData = null; 31 32 if (arguments[0] && arguments[0].nodeType === Node.ELEMENT_NODE) { 33 if (arguments[0].querySelector(this.valueContainerSelector)) { 34 this.dataContainer = arguments[0]; 35 this.valueContainer = arguments[0].querySelector(this.valueContainerSelector); 36 37 this.destLocData = { 38 lat: this.dataContainer.getAttribute("data-lat"), 39 lon: this.dataContainer.getAttribute("data-lon") 40 } 41 } else { 42 console.error("You passed an invalid Element-Structure or an wrong Selector"); 43 } 44 } else { 45 console.error("You passed an invalid Argument!"); 46 } 47 } 48 49 init() { 50 document.addEventListener("location-request", this.#boundHandleLocationRequest); 51 document.addEventListener("location-request-success", this.#boundHandleLocationRequestSuccess); 52 document.addEventListener("location-request-error", this.#boundHandleLocationRequestError); 53 54 this.#clientLocationInstance.permission.then((permissionState) => { 55 if (permissionState == "granted") { 56 this.#clientLocationInstance.requestLocation().catch((error) => { console.debug(error) }); 57 } else if (permissionState == "prompt") { 58 this.dataContainer.addEventListener("click", this.#boundHandleLocationRequestInvoke); 59 this.dataContainer.addEventListener("keydown", this.#boundHandleLocationRequestInvoke); 60 } else { 61 this.#destroy(); 62 } 63 }).catch((error) => { 64 this.#destroy(); 65 66 console.error("L3G0 Location: Error occoured while requesting permission for location.\n" + error.message); 67 }); 68 } 69 70 //Method: Handles when user invoked manually 71 #handleLocationRequestInvoke(event) { 72 if (event.type == "keydown" && event.key != "Enter") { 73 return 74 } 75 76 this.#removeInvokeListener(); 77 this.#clientLocationInstance.requestLocation().catch((error) => { console.debug(error) }); 78 } 79 80 //Method: Handles location request 81 #handleLocationRequest(event) { 82 this.#removeLocationRequestListener(); 83 84 this.#setLoader(); 85 } 86 87 //Method: Handles successful location request. 88 #handleLocationRequestSuccess(event) { 89 this.#removeInvokeListener(); 90 this.#removeLocationRequestSuccessListener(); 91 this.#removeLocationRequestErrorListener(); 92 93 const clientLat = event.detail.coords.latitude; 94 const clientLon = event.detail.coords.longitude; 95 96 this.valueContainer.innerText = this.#formatBeeLine(this.#calcBeeLine(this.destLocData.lat, this.destLocData.lon, clientLat, clientLon)); 97 } 98 99 //Mehtod: Handles error while requesting location. 100 #handleLocationRequestError(event) { 101 this.#destroy(); 102 103 console.error("L3G0 Location: Error occoured while requesting location.\n" + event.detail.message); 104 } 105 106 //Method: Initializes ClientLocationData Instance 107 #getClientLocation(options) { 108 return new ClientLocationData(options); 109 } 110 111 //Method: Sets Loader to valueContainer 112 #setLoader() { 113 this.valueContainer.innerHTML = "<span class='fad fa-spinner fa-spin' aria-hidden='true'></span>"; 114 } 115 116 //Mehtod: Calculated As-The-Crow-Flies Line 117 #calcBeeLine(destLat, destLon, clientLat, clientLon) { 118 const earthRadiusKm = 6371; 119 120 const latDelta = this.#degreesToRadians(clientLat - destLat); 121 const lonDelta = this.#degreesToRadians(clientLon - destLon); 122 123 const destLatRad = this.#degreesToRadians(destLat); 124 const clientLatRad = this.#degreesToRadians(clientLat); 125 126 const $a = Math.sin(latDelta / 2) * Math.sin(latDelta / 2) + Math.sin(lonDelta / 2) * Math.sin(lonDelta / 2) * Math.cos(destLatRad) * Math.cos(clientLatRad); 127 const $b = 2 * Math.atan2(Math.sqrt($a), Math.sqrt(1 - $a)); 128 129 const distance = earthRadiusKm * $b; 130 131 return distance; 132 } 133 134 //Method: Formatting As-The-Crow-Flies Line 135 #formatBeeLine(beeLine) { 136 let formattedBeeLine = beeLine; 137 138 if (formattedBeeLine < 0.1) { 139 formattedBeeLine = "< " + 0.1; 140 } else if (formattedBeeLine < 1) { 141 formattedBeeLine = formattedBeeLine.toFixed(2); 142 } else if (formattedBeeLine < 10) { 143 formattedBeeLine = formattedBeeLine.toFixed(1); 144 } else { 145 formattedBeeLine = Math.round(formattedBeeLine); 146 } 147 148 return formattedBeeLine += " km"; 149 } 150
151 //Method: Converting Degree to Radian 152 #degreesToRadians(val) { 153 return val * Math.PI / 180; 154 } 155 156 //Method: Removes all Eventlisteners and removes dataContainer from DOM 157 #destroy() { 158 this.#removeInvokeListener(); 159 this.#removeLocationRequestListener(); 160 this.#removeLocationRequestSuccessListener(); 161 this.#removeLocationRequestErrorListener(); 162 this.#removeEl(); 163 } 164 165 //Method: Remove DataContainer from DOM 166 #removeEl() { 167 this.dataContainer.remove(); 168 } 169 170 //Method Group: Removing diverse eventListener 171 #removeInvokeListener() { 172 this.dataContainer.removeEventListener("click", this.#boundHandleLocationRequestInvoke); 173 this.dataContainer.removeEventListener("keydown", this.#boundHandleLocationRequestInvoke); 174 } 175 176 #removeLocationRequestListener() { 177 document.removeEventListener("location-request", this.#boundHandleLocationRequest); 178 } 179 180 #removeLocationRequestSuccessListener() { 181 document.removeEventListener("location-request-success", this.#boundHandleLocationRequestSuccess); 182 } 183 184 #removeLocationRequestErrorListener() { 185 document.removeEventListener("location-request-error", this.#boundHandleLocationRequestError); 186 } 187} 188 189class DistanceOrder { 190 #invokedOrderChange; 191 #clientLocationInstance; 192 #coordRegex; 193 #transferValueToElHasValidCoordinates;
194 #boundHandleLocationRequestInvoke; 195 #boundHandleLocationRequest; 196 #boundHandleLocationRequestSuccess; 197 #boundHandleLocationRequestError; 198 199 constructor($optionElement, $querySelector_transferValueToEl, $querySelector_filterContainer, $instantSubmit = true) { 200 this.instantSubmit = $instantSubmit; 201 this.#invokedOrderChange = false; 202 203 this.#coordRegex = new RegExp(/^\d+\.?\d*,\d+\.?\d*$/); 204 this.#boundHandleLocationRequestInvoke = this.#handleLocationRequestInvoke.bind(this); 205 this.#boundHandleLocationRequest = this.#handleLocationRequest.bind(this); 206 this.#boundHandleLocationRequestSuccess = this.#handleLocationRequestSuccess.bind(this); 207 this.#boundHandleLocationRequestError = this.#handleLocationRequestError.bind(this); 208 209 this.#clientLocationInstance = this.#getClientLocation({ 210 enableHighAccuracy: false, 211 timeout: 60000, 212 maximumAge: 0 213 }); 214 215 this.optionElement = null; 216 this.selectElement = null; 217 this.optionElementValueId = null; 218 this.#transferValueToElHasValidCoordinates = false; 219 220 //Has to be in same filterContainer as optionEl 221 this.transferValueToEl = null; 222 223 if ($optionElement && $optionElement.nodeType === Node.ELEMENT_NODE && $optionElement.tagName == "OPTION") { 224 if ($optionElement.parentElement.tagName == "SELECT" && $optionElement.closest($querySelector_filterContainer).querySelector($querySelector_transferValueToEl)) { 225 this.optionElement = $optionElement; 226 this.selectElement = this.optionElement.parentElement; 227 this.optionElementValueId = parseInt(this.optionElement.value); 228 this.transferValueToEl = this.selectElement.closest($querySelector_filterContainer).querySelector($querySelector_transferValueToEl); 229 230 if (this.#coordRegex.test(this.transferValueToEl.value)) { 231 this.#transferValueToElHasValidCoordinates = true; 232 } 233 234 } else { 235 console.error("You passed an invalid Element-Structure or an wrong Selector"); 236 } 237 } else { 238 console.error("You passed an invalid Argument!"); 239 } 240 } 241 242 init() { 243 document.addEventListener("location-request", this.#boundHandleLocationRequest); 244 document.addEventListener("location-request-success", this.#boundHandleLocationRequestSuccess); 245 document.addEventListener("location-request-error", this.#boundHandleLocationRequestError); 246 247 //Has to be added outside promise bco eventlistener order 248 this.selectElement.addEventListener("change", this.#boundHandleLocationRequestInvoke); 249 250 this.#clientLocationInstance.permission.then((permissionState) => { 251 if (permissionState == "granted" && !this.#transferValueToElHasValidCoordinates) { 252 this.#removeInvokeListener(); 253 this.#clientLocationInstance.requestLocation().catch((error) => { console.debug(error) }); 254 } else if (permissionState == "denied") { 255 this.#destroy(); 256 } 257 258 if (this.#transferValueToElHasValidCoordinates) { 259 this.#removeInvokeListener(); 260 this.#removeLocationRequestListener(); 261 this.#removeLocationRequestSuccessListener(); 262 this.#removeLocationRequestErrorListener(); 263 } 264 }).catch((error) => { 265 this.#destroy(); 266 267 console.error("L3G0 Location: Error occoured while requesting permission for location.\n" + error.message); 268 }); 269 } 270 271 //Method: Handles when user invoked manually 272 #handleLocationRequestInvoke(event) { 273 if (this.selectElement.value != this.optionElementValueId) { 274 return 275 } 276 277 event.stopImmediatePropagation(); 278 279 this.#invokedOrderChange = true; 280 this.#removeInvokeListener(); 281 this.#clientLocationInstance.requestLocation().catch((error) => { console.debug(error) }); 282 } 283 284 //Method: Handles location request 285 #handleLocationRequest(event) { 286 this.#removeLocationRequestListener(); 287 } 288 289 //Method: Handles successful location request. 290 #handleLocationRequestSuccess(event) { 291 this.#removeInvokeListener(); 292 this.#removeLocationRequestSuccessListener(); 293 this.#removeLocationRequestErrorListener(); 294 295 const clientLat = event.detail.coords.latitude; 296 const clientLon = event.detail.coords.longitude; 297 298 this.transferValueToEl.value = (clientLat + "," + clientLon); 299 300 if (this.#coordRegex.test(this.transferValueToEl.value)) { 301 this.#transferValueToElHasValidCoordinates = true; 302 } 303 304 if (this.instantSubmit && this.#invokedOrderChange && this.#invokedOrderChange) {
305 this.selectElement.closest("#aspnetForm").submit(); 306 } 307 } 308 309 //Mehtod: Handles error while requesting location. 310 #handleLocationRequestError(event) { 311 this.#destroy(); 312 313 console.error("L3G0 Location: Error occoured while requesting location.\n" + event.detail.message); 314 } 315 316 //Method: Initializes ClientLocationData Instance 317 #getClientLocation(options) { 318 return new ClientLocationData(options); 319 } 320 321 //Method: Removes all Eventlisteners and removes dataContainer from DOM 322 #destroy() { 323 this.#removeInvokeListener(); 324 this.#removeLocationRequestListener(); 325 this.#removeLocationRequestSuccessListener(); 326 this.#removeLocationRequestErrorListener(); 327 this.#removeEl(); 328 } 329 330 //Method: Remove DataContainer from DOM 331 #removeEl() { 332 this.optionElement.remove(); 333 } 334 335 //Method Group: Removing diverse eventListener 336 #removeInvokeListener() { 337 this.selectElement.removeEventListener("change", this.#boundHandleLocationRequestInvoke); 338 } 339 340 #removeLocationRequestListener() { 341 document.removeEventListener("location-request", this.#boundHandleLocationRequest); 342 } 343 344 #removeLocationRequestSuccessListener() { 345 document.removeEventListener("location-request-success", this.#boundHandleLocationRequestSuccess); 346 } 347 348 #removeLocationRequestErrorListener() { 349 document.removeEventListener("location-request-error", this.#boundHandleLocationRequestError); 350 } 351} 352 353class ClientLocationData { 354 #options; 355 356 constructor() { 357 if (navigator.geolocation) { 358 this.#options = arguments[0] 359 } else { 360 console.error("This Client does not support the Browser GeoLocation API"); 361 } 362 } 363 364 get permission() { 365 return new Promise((resolve, reject) => { 366 navigator.permissions.query({ name: "geolocation" }).then((permission) => { 367 resolve(permission.state); 368 }).catch((error) => { 369 reject(error); 370 }); 371 }); 372 } 373 374 requestLocation() { 375 const eventLocation_Request = new Event("location-request"); 376 document.dispatchEvent(eventLocation_Request); 377 378 return new Promise((resolve, reject) => { 379 this.#getLocPromise().then((GeolocationPositionObj) => { 380 const eventLocation_RequestSuccess = new CustomEvent("location-request-success", { detail: GeolocationPositionObj }); 381 document.dispatchEvent(eventLocation_RequestSuccess); 382 383 resolve(GeolocationPositionObj); 384 }).catch((error) => { 385 const eventLocation_RequestError = new CustomEvent("location-request-error", { detail: error }); 386 document.dispatchEvent(eventLocation_RequestError); 387 388 reject(error); 389 }); 390 }); 391 } 392 393 #getLocPromise() { 394 return new Promise((resolve, reject) => { 395 navigator.geolocation.getCurrentPosition(resolve, reject, this.#options); 396 }); 397 } 398}
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.