1$(function() { 2 'use strict'; 3 4 function LiveChatWidgetCall(x) { 5 try { 6 LiveChatWidget.call(x); 7 } 8 catch (e) { 9 if (!(e instanceof ReferenceError)) 10 console.log(e); 11 } 12 } 13 14 // Add a click event listener to the mobile search button 15 $('#mobile-search-btn').on('click', function() { 16 $('#navbar-toggle, #mobile-navbar').toggleClass('active'); 17 18 // Additional actions if needed 19 LiveChatWidgetCall($('#mobile-navbar').hasClass('active') ? "hide" : "minimize"); 20 21 // Additional actions if needed 22 $('.summer_offer_section').toggle(!$('#mobile-navbar').hasClass('active')); 23 24 // Focus the input field 25 $('#mobile-navbar input[name="search_field"]').focus(); 26 }); 27 28 // Add a click event listener to the navbar toggle button (assuming you have this already) 29 $('.navbar-toggle').on('click', function() { 30 $('#navbar-toggle, #mobile-navbar').toggleClass('active'); 31 32 // Additional actions if needed 33 LiveChatWidgetCall($('#mobile-navbar').hasClass('active') ? "hide" : "minimize"); 34 35 // Additional actions if needed 36 $('.summer_offer_section').toggle(!$('#mobile-navbar').hasClass('active')); 37 }); 38 39 40 const countryDropdownToggle = $('.country-dropdown-toggle'); 41 const countryDropdownMenu = $('.country-dropdown-menu'); 42 43 countryDropdownToggle.on('click', function(e) { 44 e.preventDefault(); 45 countryDropdownMenu.toggleClass('show'); 46 }); 47 48 $(window).on('click', function(e) { 49 if ( 50 !countryDropdownToggle.is(e.target) && 51 countryDropdownToggle.has(e.target).length === 0 && 52 !countryDropdownMenu.is(e.target) && 53 countryDropdownMenu.has(e.target).length === 0 54 ) { 55 countryDropdownMenu.removeClass('show'); 56 } 57 }); 58 59 60 $('.nav-dropdown-toggle').on('click', function(e) { 61 e.preventDefault(); 62 e.stopPropagation(); 63 64 // Close all other dropdowns 65 $('.nav-dropdown-menu').not($(this).next()).removeClass('show'); 66 $('.nav-dropdown').not($(this).parent()).removeClass('open'); 67 68 // Toggle the clicked dropdown 69 $(this).next().toggleClass('show'); 70 $(this).parent().toggleClass('open'); 71 }); 72 73 $('.nav-dropdown.specialglas .nav-dropdown-toggle-specialglas').on('click', function(e) { 74 e.preventDefault(); 75 e.stopPropagation(); 76 77 // Toggle the second level dropdown 78 $(this).next().toggleClass('show'); 79 $(this).parent().toggleClass('open'); 80 }); 81 82 $(window).on('click', function(e) { 83 if ( 84 !$('.nav-dropdown-toggle').is(e.target) && 85 $('.nav-dropdown-toggle').has(e.target).length === 0 && 86 !$('.nav-dropdown-menu').is(e.target) && 87 $('.nav-dropdown-menu').has(e.target).length === 0 88 ) { 89 $('.nav-dropdown-menu').removeClass('show'); 90 $('.nav-dropdown').removeClass('open'); 91 } 92 }); 93 94 95 $('.mobile-filter-icon').on('click', function() { 96 $('#mobile-filter-menu').addClass('active'); 97 $(this).addClass('active'); 98 LiveChatWidgetCall("hide"); 99 }); 100 101 $('.close-filter-menu, .show-products').on('click', function() { 102 $('#mobile-filter-menu').removeClass('active'); 103 $('.mobile-filter-icon').removeClass('active'); 104 LiveChatWidgetCall("minimize"); 105 }); 106 107 108 $('body').on('click', '.dropdown-menu', function(e) { 109 e.stopPropagation(); 110 }); 111 112 function updateFilterDisplayForCheckbox(checkbox) { 113 const filterWrapper = checkbox.closest('.dropdown, .accordion-body'); 114 const checkboxes = filterWrapper.find('input[type="checkbox"]'); 115 const selectedCheckboxes = checkboxes.filter(':checked'); 116 117 const total = selectedCheckboxes.length; 118 if (total === 0) { 119 filterWrapper.find('.dropdown-text .selected-text').remove(); 120 } else { 121 // Add a span for styling the updated number 122 filterWrapper.find('.dropdown-text .selected-text').remove(); 123 filterWrapper.find('.dropdown-text').prepend('<span class="selected-text">' + total + ' </span>'); 124 } 125 126 // Update the number of active filters for each accordion item 127 $('.accordion-item').each(function() { 128 const accordionItem = $(this); 129 const totalActiveFilters = accordionItem.find('input[type="checkbox"]:checked').length; 130 const numberOfActiveFilters = accordionItem.find('.numberOfActiveFilters'); 131 132 if (totalActiveFilters > 0) { 133 numberOfActiveFilters.text('' + totalActiveFilters + ''); 134 // Add the new class for mobile styling 135 numberOfActiveFilters.addClass('selected-text-mobile'); 136 } else { 137 numberOfActiveFilters.text(''); 138 // Remove the new class for mobile styling if there are no active filters 139 numberOfActiveFilters.removeClass('selected-text-mobile'); 140 } 141 }); 142 } 143 144
145 146 $("#slider-before-after").on("input change", (e)=>{ 147 let sliderPos = e.target.value; 148 sliderPos = Math.min(Math.max(sliderPos, 1), 100); // Constrain the value between 1 and 100 149 $('.foreground-img').css('width', `${sliderPos}%`); 150 $('.slider-button').css('left', `calc(${sliderPos}% - 20px)`); // Adjust for button width 151 }); 152 153 154 155 // Target both desktop and mobile checkboxes 156 const allCheckboxes = $('#desktop-filtering input[type="checkbox"], #mobile-filter-menu input[type="checkbox"]'); 157 allCheckboxes.on('change', function() { 158 updateFilterDisplayForCheckbox($(this)); 159 updateDesktopFilterButton(); 160 updateMobileFilterCount(); 161 }); 162 163 // update filter display on page load too 164 allCheckboxes.each(function() { 165 updateFilterDisplayForCheckbox($(this)); 166 }); 167 updateDesktopFilterButton(); 168 updateMobileFilterCount(); 169 170 // Clear filters functionality 171 $('.clear-filters').on('click', function() { 172 $('#desktop-filtering input[type="checkbox"], #mobile-filter-menu input[type="checkbox"]').prop('checked', false); 173 $('.dropdown-text .selected-text').remove(); 174 $('.numberOfActiveFilters').text(''); 175 updateDesktopFilterButton(); 176 updateMobileFilterCount(); 177 178 filterFrames(); 179 }); 180 181 function updateDesktopFilterButton() { 182 const total = $('#desktop-filtering input[type="checkbox"]:checked').length; 183 $('#desktop-filtering .clear-filters').toggle(total != 0); 184 } 185 186 function updateMobileFilterCount() { 187 const total = $('#mobile-filter-menu input[type="checkbox"]:checked').length; 188 $('.mobile-filter-icon output').val(total); 189 } 190 191 192// flickityResponsive only exists on the frame product page 193if (jQuery.fn.flickityResponsive) { 194/* 195 $('.carousel-main-modal').flickityResponsive({ 196 pageDots: false, 197 prevNextButtons: false, 198 contain: true, 199 draggable: true, 200 resize: true, 201 sync: '.carousel-nav-modal', 202 responsive: [ 203 { 204 breakpoint: 1024, 205 settings: { 206 pageDots: true, 207 draggable: true 208 } 209 } 210 ] 211 }); 212 213 $('.carousel-nav-modal').flickityResponsive({ 214 pageDots: false, 215 prevNextButtons: false, 216 contain: true, 217 draggable: true, 218 cellAlign: 'left', 219 asNavFor: '.carousel-main-modal', 220 responsive: [ 221 { 222 breakpoint: 1024, 223 settings: { 224 pageDots: true, 225 draggable: true 226 } 227 } 228 ] 229 }); 230 */ 231 232 233 $('.shop-frame-slider').flickityResponsive({ 234 pageDots: true, 235 prevNextButtons: false, 236 contain: true, 237 draggable: false, 238 cellAlign: 'center', 239 resize: true, 240 responsive: [ 241 { 242 breakpoint: 1024, 243 settings: { 244 pageDots: true, 245 draggable: true 246 } 247 } 248 ] 249 }); 250 251} 252 253// Favorites 254function inFavorites(obj) { 255 // Check if the object is in the favorites list. Both frame_product_id and 256 // personal_print_id must match. Note that personal_print_id could be null. 257 const matching_favorites = favorites.filter(function(el) { 258 return el.frame_product_id == obj.frame_product_id && el.personal_print_id == obj.personal_print_id; 259 }); 260 return matching_favorites.length > 0; 261} 262 263function addRemoveFavorite() { 264 const svg = $(this); 265 const frameProductId = svg.data('frame-product-id'); 266 const obj = {frame_product_id: frameProductId}; 267 let url; 268 if (inFavorites(obj)) { 269 // The frame should be removed from favorites 270 url = '/siteservices/favorites/del_favorite'; 271 } else { 272 // The frame is not among the favorites and should therefore be added 273 url = '/siteservices/favorites/add_favorite'; 274 // TODO: also pass item_name: frame name and colour (or design) 275 gtag('event', 'add_to_wishlist', {items: [{item_id: frameProductId}]}); 276 } 277 $.getJSON(url, obj, function(data) { 278 favorites = data; 279 // Update favorite count 280 const count = favorites.length; 281 const c = $('.myfavorites .favorite-count'); 282 c.attr('data-count', count); 283 c.text(count); 284 285 svg.toggleClass('active', inFavorites(obj)); 286 }); 287} 288 289// show on page load 290function showFavorites() { 291 const frameProductId = $(this).data('frame-product-id'); 292 const obj = {frame_product_id: frameProductId}; 293 294 $(this).toggleClass('active', inFavorites(obj)); 295} 296 297function showInCart() { 298 const frameProductId = $(this).data('frame-product-id'); 299 300 $(this).toggleClass('active', the_cart.includes(frameProductId)); 301} 302 303 304// Pagination 305function showMore() { 306 const m = $('#more-frames'); 307 let numFrames = parseInt(m.data('num-frames')); 308 $.get('/frames/more', {q: m.data('more-frames')}, function(data) { 309 $('#more-frames').replaceWith(data); 310 // The returned snippet has data-num-frames set to the number of frames it contains. 311 // To keep track of the running total, we need to add the previous number of frames to it. 312 const m = $('#more-frames'); 313 numFrames = numFrames + parseInt(m.data('num-frames')); 314 m.data('num-frames', numFrames); 315 $('#num-frames').val(numFrames); // update the output 316 317 // in case not all of the submitted frame product IDs could be returned, the total might now be less. 318 // So retrieve the number of frames left and add that to how many frames we've already displayed as the new total. 319 const moreFrames = m.data('more-frames'); 320 let numMoreFrames = 0; 321 if (moreFrames == '') { 322 $('#show-more').hide(); // none more left 323 } else { 324 numMoreFrames = moreFrames.split(',').length; 325 } 326 const totalNumFrames = numFrames + numMoreFrames; 327 $('.total-num-frames').val(totalNumFrames); // update the output 328 329 const width = numFrames / totalNumFrames * 100 | 0; 330 $('.progress-fill').css('width', width + '%'); 331 332 // bump page number in URL 333 let l = new URL(location); 334 let page = (parseInt(l.searchParams.get('page')) || 1) + 1; 335 l.searchParams.set('page', page); 336 history.replaceState(null, '', l.toString()); 337 338 // make newly loaded stuff work correctly 339 register_product_events(); 340 }); 341} 342 343// Filtering and sorting 344function update_url() {
345 let new_url = $('#desktop-sorting, #desktop-filtering input:checked').map(function() { 346 if ($(this).prop('name') == 'sort') 347 return 'sort' + $(this).prop('value'); 348 else 349 return $(this).prop('name').replace('_',''); 350 }).get().join('_'); 351 history.replaceState(null, '', new_url); 352} 353 354function filterFrames() { 355 const f_sort = $('#desktop-sorting').val(); 356 let filters = {f_sort: f_sort}; 357 $('.filtering input[type=checkbox]:checked').each(function() { 358 const filter = $(this).data('filter'); 359 if (!(filter in filters)) 360 filters[filter] = new Set(); 361 filters[filter].add($(this).attr('name')); 362 }); 363 for (const filter in filters) 364 filters[filter] = Array.from(filters[filter]).join(','); 365 $.get('/frames/filter', filters, function(data) { 366 $('#frame-products').replaceWith(data); 367 // The returned snippet has data-total-num-frames for the total number of frames that match the filtering. 368 // Update the total displayed on the page. 369 $('.total-num-frames').val($('#frame-products').data('total-num-frames')); 370 // make URL work for saving the filter 371 update_url(); 372 // make newly loaded stuff work correctly 373 register_product_events(); 374 }); 375}; 376 377// keep desktop and mobile filters in sync. This is needed because a) we have two sets of filters and the code above reads from both sets, and b) some devices when rotated (eg iPad Mini) alternate between desktop and mobile versions. 378$('#desktop-filtering input[type="checkbox"]').on('change', function() { 379 $('#mobile-filter-menu input[name="'+this.getAttribute('name')+'"]').prop('checked', this.checked); 380 filterFrames(); 381}); 382$('#mobile-filter-menu input[type="checkbox"]').on('change', function() { 383 $('#desktop-filtering input[name="'+this.getAttribute('name')+'"]').prop('checked', this.checked); 384 filterFrames(); 385}); 386 387$('#desktop-sorting').on('change', function() { 388 $('#mobile-sorting').val(this.value); 389 filterFrames(); 390}); 391$('#mobile-sorting').on('change', function() { 392 $('#desktop-sorting').val(this.value); 393 filterFrames(); 394}); 395 396// Cart 397function update_too_many_too_few_display() { 398 const too_many = the_cart.length - 4; 399 $('#too-many-plural').toggle(too_many > 1); 400 $('#too-many-singular').toggle(too_many == 1); 401 $('.trial-cart .end_order_btn_wrapper').toggle(too_many < 1); 402 $('.trial-cart .check-block').toggle(too_many < 1); 403 // and the "too few" info 404 $('.borrow-info').toggle(the_cart.length < 4); 405 $('.borrow-info .singular').toggle(the_cart.length == 3); 406 $('.borrow-info .plural').toggle(the_cart.length < 3); 407} 408 409function update_cart_display() { 410 const count = the_cart.length; 411 const c = $('.the_cart .favorite-count'); 412 c.attr('data-count', count); 413 c.text(count); 414 415 update_too_many_too_few_display(); 416} 417 418update_too_many_too_few_display(); // update visiblity on page load too. Display is set to None by default in the CSS. 419 420function addRemoveCart() { 421 const $this = $(this) 422 if ($this.hasClass('disabled')) { 423 return; 424 } 425 426 const old_cart_size = the_cart.length; 427 const frame_product_id = $(this).data('frame-product-id'); 428 let url; 429 let analytics_event; 430 if (the_cart.includes(frame_product_id)) { 431 url = '/trial/remove_cart'; 432 analytics_event = 'remove_from_cart'; 433 } else { 434 url = '/trial/add_cart'; 435 analytics_event = 'add_to_cart'; 436 } 437 438 $.getJSON(url, {cart: frame_product_id}, function(data) { 439 the_cart = data; 440 update_cart_display(); 441 442 if (the_cart.includes(frame_product_id)) { 443 $this.addClass('active'); 444 $('.home-try-on-btn').addClass('active'); 445 } else { 446 $this.removeClass('active'); 447 $('.home-try-on-btn').removeClass('active'); 448 // cart manipulation 449 $('.cart-remove[data-frame-product-id="'+frame_product_id+'"]').closest('.prodbox').remove(); 450 if (the_cart.length < 4) 451 $('#default-prodbox-template').contents().clone().appendTo('.trial_section .row'); 452 $('#more-frames-count').text(4 - the_cart.length); 453 $('.too-many-count').text(the_cart.length - 4); 454 } 455 456 if (old_cart_size == 0 && the_cart.length == 1) { 457 gtag('event', 'sampleincart'); 458 } 459 gtag('event', analytics_event, { 460 items: [{item_id: frame_product_id, item_category: 'Provbågar'}] 461 }); 462 }); 463} 464 465$('.home-try-on-btn').on('click', addRemoveCart); 466$('.cart-remove').on('click', addRemoveCart); 467 468// Login TODO: refactor the login form so it's no longer needed. Use eg /logout for logging out. 469$('.submit_login_form').on('click', function() { 470 $('#login_form').submit(); 471}); 472 473 474 var emailHelpIcon = document.getElementById('email-help-icon'); 475 var popoverContent = emailHelpIcon.querySelector('.popover-content').textContent.trim(); 476 var popover = new bootstrap.Popover(emailHelpIcon, { 477 trigger: 'focus', 478 placement: 'right', 479 content: popoverContent 480 }); 481 482 // Prevent default action on anchor click 483 emailHelpIcon.addEventListener('click', function (event) { 484 event.preventDefault(); 485 }); 486 487 488// Try Online 489function set_f_show() { 490 $('main').attr('data-f-show', this.value); 491 $.get('/frames/set_f_show', {'f_show': this.value}); 492} 493 494function register_product_events() { 495 const totalNumFrames = $('#frame-products').data('totalNumFrames'); 496 $('.total-frames-count .singular').toggle(totalNumFrames == 1); 497 $('.total-frames-count .plural').toggle(totalNumFrames > 1); 498 499 $('.add-favorite').off('click').on('click', addRemoveFavorite); 500 $('.add-favorite').each(showFavorites); 501 $('.add-borrow').off('click').on('click', addRemoveCart); 502 $('.add-borrow').each(showInCart); 503 504 $('input[type=radio][name=f_show]').on('change', set_f_show); 505 $('#show-more').off('click').on('click', showMore); 506
507 $('#try-glasses-online-modal-adjust').on('show.bs.modal', function() { 508 LiveChatWidgetCall("hide"); 509 }); 510 $('#try-glasses-online-modal-adjust').on('hide.bs.modal', function() { 511 LiveChatWidgetCall("minimize"); 512 }); 513 $('#try-glasses-online-modal').on('show.bs.modal', function() { 514 LiveChatWidgetCall("hide"); 515 enable_upload(); 516 }); 517 $('#try-glasses-online-modal').on('hide.bs.modal', function() { 518 LiveChatWidgetCall("minimize"); 519 disable_upload(); 520 }); 521} 522 523register_product_events(); // do it on page load 524 525$('#add_sunkit_to_cart').on('click', function() { 526 $.getJSON('/trial/add_cart', {cart: 'sunkit'}, function(data) { 527 the_cart = data; 528 update_cart_display(); 529 $.facebox(sunkit_added_to_cart); 530 }); 531}); 532 533$('.optional-addons .option input[type="checkbox"]').on('click', function() { 534 var f_show = 2; 535 if ($('#check-dark').prop('checked') || $('#check-polarized').prop('checked')) 536 f_show = 4; 537 set_f_show.call({value: f_show}); 538}); 539 540}); // end of the document ready function 541 542function show_alert_modal(title, message) { 543 $('#generic_modal_title').text(title); 544 $('#generic_modal_body').html(message); 545 var modal = new bootstrap.Modal(document.getElementById('generic_modal')); 546 modal.show(); 547} 548
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.