PageSourceSearch

https://favoptic.se/hashed_files/assets/js/main_1e8436803f820363a1c393af78e04413.js

js favoptic.se collected 2026-09-27 08:11:04 UTC 17,808 bytes, 548 lines download raw bytes

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.