'; } }; const preselectFilterOptions = (form, params, currentDate, flexibilityInput, currentAirport) => { for(k of Object.keys(params)) { switch(k) { case 'tripLength': { selectDuration(form.find('.duration-group'), $(`input[name="tripLength"][value="${params[k]}"]`).closest('.duration')); break; } case 'budget': { let budget = params[k].split('_'); let budgetGroup = form.find('.budget-group'); budgetGroup.find('#budget-slider').slider('option', 'values', [budget[0], budget[1]]); budgetGroup.find('.min-budget').html(budget[0]); budgetGroup.find('.max-budget').html(budget[1]); break; } case 'membersOnly': { form.find('input[name="membersOnly"]').prop('checked', true); break; } case 'includeRentalCar': { form.find('input[name="includeRentalCar"]').prop('checked', true); break; } case 'groundServicesOnly': { form.find('input[name="groundServicesOnly"]').prop('checked', true); break; } } }; if(params['departureAirport']) { currentAirport.next({ name: params['departureAirport'], code: params['departureAirport'] }); } if(params['departureDate']) { currentDate.next({...currentDate.value, date: params['departureDate']}); } if(params['flexibility']) { flexibilityInput.val(params['flexibility']); flexibilityInput.trigger('change'); } } const selectDuration = (durationGroup, elm) => { const currentRadio = elm.find(':radio'); elm.toggleClass('active'); currentRadio.prop('checked', !currentRadio.prop('checked')); // Disable other radios. durationGroup.find('.duration') .not(elm[0]) .removeClass('active') .find(':radio') .prop('checked', false); } const setEvents = (root, currentDate, flexibilityInput, currentAirport) => { const { Observable, BehaviorSubject, fromEvent, merge } = rxjs; const { map, tap, filter, debounceTime, switchMap } = rxjs.operators; const form = root.find('#filters-form'); // Sort deals by. const sortOption = root.find('.sort-deals-criteria'); const sortObs = fromEvent(sortOption, 'click'); const sortSubmitObs = sortObs.pipe( debounceTime(200), tap(e => { const elm = $(e.currentTarget); root.find('.current-order').html(elm.text()); const extraParams = { sortBy: elm.data('sortby'), sortOrder: elm.data('sortorder') }; getDeals(root, form, extraParams); }) ).subscribe(); // Departure date. const departureDateGroup = form.find('.departure-date-group'); const departureDateInput = departureDateGroup.find('#departure-date-input'); const departureDateForm = departureDateGroup.find('#departure-date-form'); const dpContainer = departureDateForm.find('.dp-container'); const dateLabel = departureDateGroup.find('.date-label'); dpContainer.datepicker({ inline: true, language: "en", minDate: new Date(), maxDate: new Date(new Date().setFullYear(new Date().getFullYear() + 1)), dateFormat: 'yyyy-mm-dd', onSelect: (dateText, date, datepicker) => { currentDate.next({...currentDate.value, date: dateText}); } }); flexibilityInput.on('change', _ => { currentDate.next({...currentDate.value, flexibility: flexibilityInput.val()}); }); departureDateGroup.find('.filter-input').on('click', _ => departureDateForm.show()); departureDateGroup.find('.clear').on('click', _ => { dpContainer.datepicker('setDate', null); currentDate.next({date: '', flexibility: flexibilityInput.val()}); }); currentDate.subscribe(data => { dateLabel.text( data.date ? `${data.date}${data.flexibility == 0 ? '' : ` ± ${data.flexibility} days`}` : 'Departure Date' ); departureDateInput.val(data.date); departureDateForm.hide(); if(forceFormChange) { form.trigger('change'); } }); // Departure airport. const departureAirportGroup = form.find('.departure-airport-group'); const departureAirportSearch = departureAirportGroup.find('#departure-airport-search'); const departureAirportInput = departureAirportGroup.find('#departure-airport-input'); const departureAiportResults = departureAirportGroup.find('.airport-results'); // Airport autocomplete. const searchAirportKeyup = fromEvent(departureAirportSearch, 'keyup'); const airportSearchObs = searchAirportKeyup.pipe( debounceTime(500), map(e => $(e.currentTarget).val()), filter(v => v.length > 2), switchMap(t => getAirportSuggestions(t)) ).subscribe(data => { departureAiportResults.html(renderAirportSuggestions(data.suggestions || [])).show(); departureAiportResults.find('.departure-airport').on('click', e => { const elm = $(e.currentTarget); currentAirport.next({name: elm.data('airport-name'), code: elm.data('airport-code')}); departureAiportResults.hide(); }); }); // Hide results when no text. const closeAirportResults = searchAirportKeyup.pipe( map(e => $(e.currentTarget).val()), filter(v => v.length == 0) ).subscribe(_ => departureAiportResults.hide()); // Show all airports when clicking on input group. const airportDisplayObs = fromEvent(departureAirportGroup.find('.filter-input'), 'click').pipe( switchMap(_ => getAirportSuggestions(''))) .subscribe(data => { departureAiportResults.html(renderAirportSuggestions(data.suggestions || [])).show(); departureAiportResults.find('.departure-airport').on('click', e => { const elm = $(e.currentTarget); currentAirport.next({name: elm.data('airport-name'), code: elm.data('airport-code')}); departureAiportResults.show(); }); }); // Clear field. departureAirportGroup.find('.clear').on('click', _ => { currentAirport.next({name: '', code: ''}); }); currentAirport.subscribe(a => { departureAirportSearch.val(a.name); departureAirportInput.val(a.code); departureAiportResults.hide(); if(forceFormChange) { form.trigger('change'); } }); // Destinations. const destinationGroup = form.find('.destination-group'); const destinationSearchInput = destinationGroup.find('#search-destinations-input'); destinationGroup.find('.destination').on('click', e => { const elm = $(e.currentTarget); const currentRadio = elm.find(':radio'); elm.toggleClass('active'); currentRadio.prop('checked', !currentRadio.prop('checked')); // Disable other radios. destinationGroup.find('.destination') .not(elm[0]) .removeClass('active') .find(':radio') .prop('checked', false); if(forceFormChange) { form.trigger('change'); } }); // Search destinations autocomplete. const destinationResults = destinationGroup.find('.destination-results') const searchDestinationsKeyup = fromEvent(destinationSearchInput, 'keyup'); const searchDestinationsPaste = fromEvent(destinationSearchInput, 'paste'); const currentDestination = new BehaviorSubject(''); // Destinations autocomplete. const searchDestinations = merge( searchDestinationsKeyup, searchDestinationsPaste	).pipe( debounceTime(500), map(e => $(e.currentTarget).val()), filter(v => v.length > 1), switchMap(getDestinationSuggestions) ).subscribe(data => { destinationResults.html(renderDestinationSuggestions(data || [])).show(); // Destination suggestion click event. fromEvent(destinationResults.find('.destination-result'), 'click').pipe( map(e => $(e.currentTarget).data('destination')) ).subscribe(dest => { currentDestination.next(dest); destinationResults.hide(); }); }); // Hide results when no text. const closeDestinationResults = searchDestinationsKeyup.pipe( map(e => $(e.currentTarget).val()), filter(v => v.length == 0) ).subscribe(_ => destinationResults.hide()); // Clear input. const clearDestinationBtn = destinationGroup.find('.clear'); clearDestinationBtn.on('click', _ => { destinationSearchInput.val(''); clearDestinationBtn.hide(); form.trigger('change'); }); currentDestination.pipe(filter(d => d.length)).subscribe(data => { // Disable destination radios. destinationGroup.find('.destination') .removeClass('active') .find(':radio') .prop('checked', false); // If destination belongs to default list, select it. destinationGroup.find('.destination').each((_, e) => { const radio = $(e).find(':radio'); if(radio.val() == data) { $(e).trigger('click'); } }); // Show clear button. clearDestinationBtn.show(); destinationSearchInput.val(data); if(forceFormChange) { form.trigger('change'); } }); // Trip length radios. const durationGroup = form.find('.duration-group'); durationGroup.find('.duration').on('click', e => { selectDuration(durationGroup, $(e.currentTarget)); if(forceFormChange) { form.trigger('change'); } }); // Budget slider. const budgetGroup = form.find('.budget-group'); const minBudgetValue = 0; const maxBudgetValue = 6000; budgetGroup.find('#budget-slider').slider({ range: true, min: minBudgetValue, max: maxBudgetValue, values: [minBudgetValue, maxBudgetValue], slide: (e, s) => { const min = s.values[0]; const max = s.values[1]; budgetGroup.find('#budget-input').val(`${min}_${max}`); budgetGroup.find('.min-budget').html(min); budgetGroup.find('.max-budget').html(max); if(forceFormChange) { form.trigger('change'); } } }); // Interests checkboxes. const interestsGroup = form.find('.interests-group'); interestsGroup.find('.checkbox-wrapper').on('click', e => { const checkbox = $(e.currentTarget).find(':checkbox'); checkbox.prop('checked', !checkbox.prop('checked')); }); // Clear filters. root.find('.clear-filters').on('click', _ => { form.trigger('reset'); form.trigger('change'); }); // Pop filters on mobile. const filtersRoot = root.find('#filters-root'); const filtersPopup = root.find('#filters-popup'); const attachFilters = new BehaviorSubject({previousContainer: null, currentContainer: null}); root.find('.pop-filters, .close-btn, .close-popup-btn').on('click', e => { const popup = $(e.currentTarget).data('popup'); attachFilters.next({ currentContainer: popup ? filtersPopup : filtersRoot, previousContainer: popup ? filtersRoot : filtersPopup, }); }); attachFilters.pipe( filter(data => data.previousContainer && data.currentContainer) ).subscribe(data => { // Prevent body from scrolling while popup is active. $('body').toggleClass('freeze'); // Re-attach form. form.detach(); data.currentContainer.prepend(form).show(); data.previousContainer.hide(); }); // Submit form after changes. const formSubmitObs = fromEvent(form, 'change').pipe( debounceTime(500) ).subscribe(_ => getDeals(root, form)); // Watch form reset. form.on('reset', e => { getMinPrice = true; destinationGroup.find('#search-destinations-input').val('').attr('value', ''); destinationGroup.find(':radio').removeAttr('checked'); form.find('.filter-group').find('.active').removeClass('active'); budgetGroup.find('#budget-slider').slider('option', 'values', [minBudgetValue, maxBudgetValue]); budgetGroup.find('.min-budget').html(minBudgetValue); budgetGroup.find('.max-budget').html(maxBudgetValue); }); // Closing form popovers when clicking out of popover bounds. $(document).on('click', e => { const elm = $(e.target); if(elm.parents('#departure-date-form, .filter-input, .datepicker--nav-action').length || elm.is('.datepicker--nav-title') || elm.is('.filter-input')) return; departureDateForm.hide(); departureAiportResults.hide(); destinationResults.hide(); }) }; window.addEventListener("load", () => { const { Observable, BehaviorSubject, of } = rxjs; const { tap, debounceTime } = rxjs.operators; const root = $('#search-deals'); const deals = root.find('#deals-root'); const form = root.find('#filters-form'); const entryParams = stringToParams(new URL(window.location).search); const flexibilityInput = form.find('#departure-date-flexibility'); const currentAirport = new BehaviorSubject({name: '', code: ''}); const currentDate = new BehaviorSubject({date: '', flexibility: flexibilityInput.val()}); setEvents(root, currentDate, flexibilityInput, currentAirport); preselectFilterOptions(form, entryParams, currentDate, flexibilityInput, currentAirport); getDeals(root, form, entryParams); }, false);