PageSourceSearch

https://www.railbookers.com.au/modules/custom/verb_algolia/js/autocomplete_cards.js?v=2.0.8

js railbookers.com.au collected 2026-09-26 11:54:14 UTC 14,868 bytes, 484 lines download raw bytes

1(function ($, Drupal, settings, once){
2  
3  Drupal.behaviors.DoliData = {
4    brandObject : Drupal.behaviors.getBrandInfo,
5    data: null,
6
7    // Not In Use
8    grabData: async function() {
9      var data = this.data; 
10
11      // If can't get from local variable call Ajax  to Server
12      if (!data){
13        data = await this.requestCurrentBrandData();
14        this.data = data;
15      }
16
17      return data;
18
19    },
20
21    // Not In Use
22    requestCurrentBrandData :async function(){
23      if (!drupalSettings || !drupalSettings.verbAlgolia) {
24        return null;
25      }
26      const brand             = drupalSettings.verbAlgolia.brand;
27      const clientId          = brand.toLowerCase(); // No need to change "railbooker" to "railbookers" unless you are directly calling Dolli, but we grabbing from internal server;
28      const fileDestination   = "/sites/default/files/json/";
29      const fileName          = clientId + "--dollie-products.json";
30      const url               = fileDestination + fileName;
31
32      try {
33        var response = await $.ajax({
34          url: url,
35          dataType: 'json'
36        });
37        return response;
38      } catch (error) {
39        throw new Error('Error fetching JSON: ' + error);
40      }
41    },
42
43    // This is the new way to Grab Data
44    requestGroupCall : async function(productsCodes) {
45      if (!productsCodes) {return []}
46      if (!drupalSettings || !drupalSettings.verbAlgolia) {
47        return [];
48      }
49      const dollieurl         = drupalSettings.verbAlgolia.dollieUrl;
50      const brand             = drupalSettings.verbAlgolia.brand.toLowerCase();
51      const clientId          = brand == 'railbooker' ? "railbookers" : brand;
52      const provider          = "1";
53      const path              = "/tour/products";
54      const url               = dollieurl  + path + 
55      "?provider=" + provider + 
56      "&clientId="+ clientId +
57      "&productCode="+ productsCodes;
58
59      try {
60        var response = await $.ajax({
61          url: url,
62          dataType: 'json'
63        });
64        return response;
65      } catch (error) {
66        throw new Error('Error fetching JSON: ' + error);
67      }
68
69    }
70  }
71
72  Drupal.behaviors.filterDoliData = {
73    filter: function(ids, items){
74      var filterData = items.filter((item) => {
75        return ids.includes(item.id);
76      });
77      return filterData;
78    },
79
80    getAllId: function (items) {
81      let idArray = [];
82
83      items.forEach((item)=>{
84        if(item.id){
85          idArray.push(item.id);
86        }
87      });
88      return idArray;
89    },
90
91    grabImageData: function(images) {
92      const OrderToGrabBy = ['image',  'heroImage', 'detailsImage' ]; // This is what normally returns from item
93
94      var newImage = {path:'null',description:"null"};
95
96      // The reason using some is beacue we need to break out
97      //https://stackoverflow.com/questions/2641347/short-circuit-array-foreach-like-calling-break
98      OrderToGrabBy.some((imageType)=>{
99        if((img = images[imageType]) ){
100          if (typeof img == "object"){
101            newImage = img;
102            return true;
103          }
104          else if (typeof img == "string") {
105            newImage = {path: img, description: "" }
106            return true;
107          }
108        }
109      });
110
111      return newImage;
112    },
113
114    // Not sorting, like Order
115    // Sorting like grabbing only the data we need
116    sortItems : function (items){
117      var newitems = [];
118      items.forEach((item)=>{
119        if(newItem = this.sortItem(item)) {
120          newitems.push(newItem);
121        }
122      });
123
124      return newitems;
125    },
126
127    sortItem : function(item){
128      
129      if(!item?.basicInfo?.name) return; // Don't add empty items
130
131      var newItem= {};
132      newItem.title = item?.basicInfo?.name
133      newItem.url = item?.viewExtensions?.url;
134      newItem.image = item?.viewExtensions?.images ? this.grabImageData(item?.viewExtensions?.images) : {path:'null',description:"null"};
135
136      const offers = item?.viewExtensions?.offers;// Cant use [0] cause some offer dont start in 0
137      if(offers && Object.values(offers)[0]) {
138        var offer = Object.values(offers)[0]
139        const isFlashSale = offer?.isFlashSale;
140        const flag  = offer?.flag;
141
142        newItem.isFlashSale = isFlashSale;
143        newItem.flag = flag;
144      }
145
146      return newItem;
147    }
148  }
149
150  Drupal.behaviors.renderHelper = {
151
152    parser: new DOMParser(),
153
154    anchortemplate: `
155      <a href="{{url}}"> 
156        {{item}}
157      </a>`,
158
159    cardTemplate : ` 
160      <article class="card-grid__single-card card">
161        <div class="card__inner">
162          <div class="card__image">
163            {{image}}
164          </div>
165          <div class="card__content">
166            {{content}}
167          </div>
168        </div> 
169      </article>`,
170
171    imageTemplate : `<img src="{{image_url}}" alt="{{alt_text}}" />`,
172
173    flagTemplate : `<span class="card__tag">{{text}}</span>`,
174
175    noTripsMessage: 
176    `<section class="no-trips-found">
177      <h2> Sorry! </h2>
178      <p>
179        No results were found<br>
180        Try searching something else
181      </p>
182    </section>`,
183
184    createCard : function(image, content){
185      var renderedItem = 
186        this.cardTemplate
187        .replace('{{image}}', image)
188        .replace('{{content}}', content);
189      return renderedItem;
190    },
191
192    wrapInAnchor : function (url, item) {
193      var renderedItem = 
194        this.anchortemplate
195        .replace("{{url}}", url)
196        .replace('{{item}}', item);
197      return renderedItem;
198    },
199
200    createImageField: function (image_url, alt_text){
201      var renderedItem = 
202        this.imageTemplate
203        .replace("{{image_url}}", image_url)
204        .replace('{{alt_text}}', alt_text);
205      return renderedItem;
206    },
207    
208    createFlagField: function (text){
209      var renderedItem = 
210        this.flagTemplate
211        .replace("{{text}}", text);
212      return renderedItem;;
213    },
214
215    createCardFromItem: function(item){
216      var image = this.createImageField(item.image.path, item.image.description);
217      var title = "<h4 class='card__title'>" + item.title + "</h4>";
218
219      if(item.flag) {
220        image += this.createFlagField(item.flag);
221      }
222
223      var card = this.createCard(image, title)
224      var Anchorcard = this.wrapInAnchor(item.url, card); 
225      const DomItem = this.makeIntoDomElement(Anchorcard);
226    
227      return DomItem;
228
229    },
230
231    createCards : function(items) {
232      var array = [];
233      items.forEach((item)=>{
234        array.push(this.createCardFromItem(item));
235      });
236      return array;
237    },
238
239    makeIntoDomElement: function(htmlString){
240      var HtmlDomItem =  this.parser.parseFromString(htmlString, "text/html"); // This creates an html tag which we don't want
241      var DomItem = HtmlDomItem.documentElement.querySelector("body"); // Have to Grab the node it created
242      return DomItem.firstChild;
243    }
244
245  }
246
247  Drupal.behaviors.placeItems = {
248
249    placeItems : function(items){
250      const grid = document.querySelector('.card-grid__inner');
251      grid.innerHTML = '';
252
253      items.forEach((item)=>{
254        grid.appendChild(item);
255      });
256      
257    },
258
259    displayEmptyMessage: function(text) {
260      const grid = document.querySelector('.mm__bottom-left');
261      const noMessageDom = Drupal.behaviors.renderHelper.makeIntoDomElement(text);
262      grid.prepend(noMessageDom);
263    },
264
265    removeEmptyMessage: function(){
266      const item = document.querySelector('.no-trips-found');
267      if(item) {
268        item.remove();
269      }
270    }
271  }
272
273  Drupal.behaviors.algoliaEventListener = {
274    init: false,
275    
276    attach:  function(){
277      if(!this.init) {
278
279        document.addEventListener("algolia-render", (e)=>{
280          Drupal.behaviors.autoCompleteCards.searchDolliCards(e.detail.hits); 
281        });
282
283        this.init = true;
284      }
285    },
286  }
287
288  Drupal.behaviors.autoCompleteCards = {
289
290    filterHelper : Drupal.behaviors.filterDoliData,
291    DolliHelper : Drupal.behaviors.DoliData,
292    renderHelper : Drupal.behaviors.renderHelper,
293    placeHelper : Drupal.behaviors.placeItems,
294
295    searchDolliCards:  async function (algoliaHits) {
296      var IDs = this.filterHelper.getAllId(algoliaHits);
297      var dollieFilterItems = await this.DolliHelper.requestGroupCall(IDs.toString());
298      var newItems = this.filterHelper.sortItems(dollieFilterItems);
299      var render = this.renderHelper.createCards(newItems);
300
301      this.placeHelper.removeEmptyMessage();
302      if (render.length > 0){
303        this.placeHelper.placeItems(render);
304      }
305      else {
306        this.placeHelper.displayEmptyMessage(this.renderHelper.noTripsMessage);
307      }
308    }
309  }
310
311  // Make the View more items to submit
312  Drupal.behaviors.submitFormInMegaMenu = {
313    attach : function(context, settings) {
314      once('algolia-view-more','.js-algoia-view-more', context).forEach(function(element) {
315        $form = document.querySelector('.ais-SearchBox .ais-SearchBox-form');
316        element.addEventListener('click', function(){
317          $form.submit();
318        });
319
320      });
321    }
322  }
323
324
325
326
327  // https://www.drupal.org/forum/support/module-development-and-code-questions/2016-12-06/load-a-ajax-form-with-ajax-call
328  // https://drupal.stackexchange.com/questions/273249/how-to-load-and-render-a-webform-with-ajax
329  Drupal.behaviors.loadAgentAjaxForm = {
330    attach: function (context, settings) {
331      
332      once('loadFormOnceVisible', '.js-load-form', context).forEach((element)=>{
333        // Originally I only wanted load form when it was visible by opening the modal, but I guess it ignores css styles and runs when page is loaded anyway
334        let observer = new IntersectionObserver(this.intersectionObserverCallback.bind(this), {});
335        observer.observe(element);
336      });
337    },
338
339    // This function Runs the observer
340    intersectionObserverCallback: function(entries, observer) {
341      entries.forEach((entry) => {
342        this.loadForm();
343        observer.unobserve(entry.target);
344      })
345    }, 
346
347
348    loadForm: function() {
349      var that = this;
350
351      $.ajax({
352        url: Drupal.url('admin/agent-form'),
353        type:"GET",
354        contentType:"application/json; charset=utf-8",
355        dataType:"json",
356        success: (response) => {
357           that.onloadSuccess(response);
358        }
359      });
360    },
361
362    onloadSuccess : function(response) {
363      // return;
364      var that = this;
365      var $wrapper = $('.js-load-form');        
366      $wrapper.html(response.form_html);
367      // Drupal.attachBehaviors($wrapper[0]); // This break js items that arent using once in js as it run through Behaviors again
368      var submitButton = $wrapper[0].querySelector('#edit-actions-submit'); // this is how the form is submitted;
369      var webform = $wrapper[0].querySelector('form');
370
371
372      submitButton.addEventListener('click', (e)=> {
373        e.preventDefault();
374        that.submitForm(webform);
375      }); 
376    },
377
378    submitForm: function(webform){
379      var actionURL =  Drupal.url('admin/submit-agent-form/'); 
380      var that = this;
381      $.ajax({
382        url: actionURL,
383        type: 'POST',
384        data: $(webform).serialize(),
385        dataType: 'json',
386        success: function (response) {
387          that.onSubmitSuccess(response);
388        }
389      })
390    },
391
392    onSubmitSuccess : function(response) {
393      if (response.status === 'success') {
394        if(response.confirm_data.confirmation_type == "url"){
395          var newUrl = response.confirm_data.confirmation_url;
396          window.location = newUrl;
397        }
398
399      } else {
400        // Handle and display form errors.
401        var errors = response.errors;
402        $.each(errors, function (field, message) {
403          alert(message);
404        });
405      }
406    }
407
408  }
409
410
411  Drupal.behaviors.toggleAgentFormContainer = {
412
413    AgentFormContainer : document.querySelector('header .afc'),
414    
415    attach: function (context, settings) {
416      // Add event listeners so anything can toggle these
417      // const event = new Event("hideResultsContainer"); dispatch document.dispatchEvent(event);
418      once('setListenersAgentFormContainer','html').forEach(()=>{
419        document.addEventListener('hideAgentFormContainer', this.hideAgentFormContainer.bind(this));
420        document.addEventListener('showAgentFormContainer', this.showAgentFormContainer.bind(this));
421        document.addEventListener('toggleAgentFormContainer', this.toggleAgentFormContainer.bind(this));
422      });
423
424      once('closeAgentFormContainer', "html").forEach(() => {
425        document.addEventListener('click', (event) => {
426          if(!document.querySelector('header .afc').contains(event.target)) {
427            this.hideAgentFormContainer();
428          }
429        })
430      });
431
432      once('hideAgentFormContainer','.js-hide-afc', context).forEach((button)=>{
433        button.addEventListener('click', this.hideAgentFormContainer.bind(this));
434      });
435
436      once('showAgentFormContainer','.js-show-afc', context).forEach((button)=>{
437        button.addEventListener('click', this.showAgentFormContainer.bind(this));
438      });
439
440      once('toggleAgentFormContainer','.js-toggle-afc', context).forEach((button)=>{
441        button.addEventListener('click', this.toggleAgentFormContainer.bind(this));
442      });
443
444      once('hoverAgentFormContainer','.js-hover-show-afc ', context).forEach((button)=>{
445        button.addEventListener('mouseover', this.mouseOverButton.bind(this));
446        // document.querySelector('header').addEventListener('mouseleave',this.mouseUpButton.bind(this));
447      });
448
449    },
450
451    showAgentFormContainer : function () {
452      this.AgentFormContainer.classList.add ('afc--show');
453      // Drupal.behaviors.toggleInputContainer.showInputContainer(); // Container has been removed
454    },
455
456    hideAgentFormContainer : function() {
457      this.AgentFormContainer.classList.remove ('afc--show');
458      // Drupal.behaviors.toggleInputContainer.hideInputContainer();
459    },
460
461    toggleAgentFormContainer : function() {
462      (this.isVisibleAgentFormContainer()) ? this.hideAgentFormContainer() : this.showAgentFormContainer(); // If Visible hide, if not show
463    },
464
465    isVisibleAgentFormContainer : function () {
466      return this.AgentFormContainer.classList.contains('afc--show');
467    },
468
469    mouseOverButton: function(element) {
470      if (this.isVisibleAgentFormContainer()) { return; } // If Form is already visible ignore hover feature
471      this.AgentFormContainer.classList.add('js-afc-is-hovered'); // This class is to let the hover know it can hide container when no longer in hover
472      this.showAgentFormContainer();
473    },
474
475    mouseUpButton: function() {
476      if (!this.AgentFormContainer.classList.contains('js-afc-is-hovered')) { return; }
477      this.AgentFormContainer.classList.remove('js-afc-is-hovered');
478      this.hideAgentFormContainer();
479    }
480
481  }
482  
483
484})(jQuery, Drupal, drupalSettings, once);

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.