PageSourceSearch

https://www.wemtoyota.com/wp-content/themes/magnetis-dealer/plugin…/shiftdigital.js?ver=1790184938

js wemtoyota.com collected 2026-09-24 13:28:11 UTC 16,193 bytes, 545 lines download raw bytes

1/*jshint esversion: 6 */
2
3
4jQuery(function($) {
5  const shiftDigital = JSON.parse($('[data-class=ShiftDigital]').text());
6
7  console.log(JSON.stringify(window.shiftdigital, null, 2));
8
9  if (!window.shiftdigital.pushEvent) {
10    window.sd = function () {
11      console.log('shiftDigital', JSON.stringify([arguments], null, 2));
12    }
13  }
14
15  sd('create', shiftDigital.sd_oemcode, shiftDigital.sd_retailer_id, shiftDigital.sd_provider_id);
16  sd('dataLayer', shiftDigital.data_layer);
17  sd('send', 'pageview');
18
19  function asShiftDigitalFormType(magnetisFormType) {
20    return shiftDigital.formTypes[magnetisFormType];
21  }
22
23  function getForm(dom) {
24    let form = dom;
25
26    if (dom.name !== 'form') {
27      form = $(dom).parents('form')[0];
28    }
29
30    return form;
31  }
32
33  function getFormField(form, fieldName) {
34    return $(form).find('[name=' + fieldName + ']').val();
35  }
36
37  function linkPathname(href) {
38    try {
39      return new URL(href, window.location.href).pathname.replace(/\/+$/, '').toLowerCase();
40    } catch (e) {
41      return null;
42    }
43  }
44
45  // Booking pages declared by the dealer through the ShiftDigital `pagetype` field.
46  const scheduleServicePaths = (shiftDigital.scheduleServiceUrls || []).map(linkPathname).filter(Boolean);
47
48  // Matching on the link text missed CTAs labelled "Make an appointment" and fired on
49  // every unrelated link mentioning "service", so match the destination instead.
50  $(document).on('click', 'a[href]', function() {
51    const path = linkPathname($(this).attr('href'));
52
53    if (!path || scheduleServicePaths.indexOf(path) === -1) {
54      return;
55    }
56
57    sd('dataLayer', {
58      events: 'scheduleServiceClick'
59    });
60
61    sd('send');
62  });
63
64
65  $(document).on('magnetis.form_visible', 'form', function(event) {
66    const formType = $(this).find('[name=magnetisFormType]').val()
67
68    sd('dataLayer', {
69      events: 'formShown',
70      formType: asShiftDigitalFormType(formType),
71      formVehicle: JSON.parse($('[data-class=ShiftDigitalVehicle]').text())
72    });
73
74    sd('send');
75  });
76
77  $(document).ready(function() {
78    $('form:visible').each(function(frm) {
79      const formType = $(this).find('[name=magnetisFormType]').val()
80      if (!formType) {
81        return;
82      }
83
84
85      sd('dataLayer', {
86        events: 'formShown',
87        formType: asShiftDigitalFormType(formType),
88        formVehicle: JSON.parse($('[data-class=ShiftDigitalVehicle]').text())
89      });
90
91      sd('send');
92    });
93  });
94
95
96
97
98  // Every surface that renders an offer: dealer promotions (listing and slider),
99  // manufacturer offers, and the Mazda services template.
100  const PROMOTION_CONTAINERS = '.promotion_content, .vehicle_promo, .promotions_slider_slide, .promotion';
101
102  function readPromotion(link) {
103    // In-page anchors inside an offer block are affordances (legal text toggle), not CTAs.
104    if ((link.getAttribute('href') || '').charAt(0) === '#') {
105      return null;
106    }
107
108    const container = link.closest(PROMOTION_CONTAINERS);
109    const payload = container && container.querySelector('script[data-type=Promotion]');
110
111    if (!payload) {
112      return null;
113    }
114
115    try {
116      return JSON.parse(payload.textContent);
117    } catch (e) {
118      return null;
119    }
120  }
121
122  function isServiceOffer(promotion) {
123    return [promotion.shiftdigital_type, promotion.shiftdigital_category]
124      .some(function(value) { return String(value || '').toLowerCase() === 'service'; });
125  }
126
127  // Manufacturer offers carry the vehicle they apply to; dealer promotions don't.
128  function offerVehicleProps(promotion) {
129    const props = {
130      vehicleModel: promotion.model,
131      vehicleTrim: promotion.trim,
132      vehicleYear: promotion.year,
133      vehicleBodyStyle: promotion.body_type,
134      vehicleEngine: promotion.engine,
135      vehicleMsrp: promotion.msrp
136    };
137
138    Object.keys(props).forEach(function(key) {
139      if (props[key] === undefined || props[key] === null || props[key] === '') {
140        delete props[key];
141      }
142    });
143
144    return props;
145  }
146
147  document.addEventListener('click', function(event) {
148    const link = event.target.closest('a[href]');
149    const promotion = link && readPromotion(link);
150
151    if (!promotion) {
152      return;
153    }
154
155    const offer = {
156      offerId: promotion.offer_id || null,
157      offerName: promotion.title,
158      offerCategory: promotion.shiftdigital_category,
159      offerType: promotion.shiftdigital_type,
160      offerExpire: promotion.end_date
161    };
162
163    if (isServiceOffer(promotion)) {
164      offer.offerStatus = 'active';
165      sd('dataLayer', { events: 'serviceOfferClick', serviceOfferClick: offer });
166    } else {
167      sd('dataLayer', { events: 'offerClick', offerClick: Object.assign(offer, offerVehicleProps(promotion)) });
168    }
169
170    sd('send');
171  });
172
173  function getInputsValue(inputs) {
174    return inputs.reduce((acc, input) => {
175      if (input.name.includes('[]')) {
176        const name = input.name.replace('[]', '');
177        if (!acc[name]) {
178          acc[name] = [];
179        }
180        acc[name].push(input.value);
181      } else {
182        acc[input.name] = input.value;
183      }
184      return acc;
185    }, {});
186  }
187
188  // WPCF7 events
189  const phoneMap = {
190    'courriel': 'email',
191    'message texte': 'text',
192    'appel téléphonique': 'phone',
193    'email': 'email',
194    'text message': 'text',
195    'phone call': 'phone',
196  };
197
198  document.addEventListener( 'wpcf7mailsent', function( event ) {
199
200    const fields = getInputsValue(event.detail.inputs);
201
202    const formType = fields.formType ?? fields.magnetisFormType ?? 'other';
203
204    const eventFields = {
205      events: 'formSubmission',
206      leadId: fields.form_render_id ?? null,
207      leadType: 'lead',
208      formOptIn: 'in-implicit',
209      prefContact: phoneMap[String(fields['methode-contact'] ?? null).toLowerCase()] ?? 'phone',
210      formType: asShiftDigitalFormType(formType),
211      formVehicle: JSON.parse($('[data-class=ShiftDigitalVehicle]').text()),
212    };
213
214    if (formType.startsWith('trade_in')) {
215      eventFields.tradeIn = {
216        year: fields['trade_vehicle_year'] ?? '',
217        make: fields['trade_vehicle_make'] ?? '',
218        model: fields['trade_vehicle_model'] ?? '',
219      }
220    }
221
222    sd('dataLayer', eventFields);
223
224    sd('send');
225
226  }, false );
227
228  ['wpcf7mailfailed', 'wpcf7invalid', 'wpcf7spam'].forEach((eventName) => {
229      document.addEventListener(eventName, function (event) {
230          const fields = getInputsValue(event.detail.inputs);
231          console.log(event.detail.status);
232
233          const formType = fields.formType ?? fields.magnetisFormType ?? 'other';
234          const displayType = $(event.target).closest('.modal').length ? 'modal' : 'in-page';
235
236          const eventFields = {
237              events: 'formSubmissionError',
238              displayType: displayType,
239              formType: asShiftDigitalFormType(formType),
240              formError: (event.detail.status + ' - ') + (event.detail.apiResponse && event.detail.apiResponse.message ? event.detail.apiResponse.message : null),
241              formVehicle: JSON.parse($('[data-class=ShiftDigitalVehicle]').text()),
242          };
243
244          sd('dataLayer', eventFields);
245          sd('send');
246
247      }, false);
248  });
249
250
251
252  // Click to call events
253  $(document).on('click', 'a.shiftdigital_clicktocall', function(event){
254    var callednum = $(this).attr('href').replace(/\D+/g, '');
255    var departement = $(this).data('shiftdigital-type');
256
257    sd('dataLayer', {
258      events: 'clickToCall',
259      clickToCallDepartment: departement,
260    });
261    sd('send');
262  });
263
264  $('a.shiftdigital_getdirection').on('click', function(event){
265    sd('dataLayer', {
266      events: 'getDirections'
267    });
268    sd('send');
269  });
270
271  $(document).on('open', '#incentive_calculator', () => {
272    // Disabled as requested https://www.wrike.com/open.htm?id=1524356197
273    // sd('dataLayer', {
274    //   events: 'formShown',
275    //   formType: asShiftDigitalFormType('payment_estimator'),
276    //   formVehicle: JSON.parse($('[data-class=ShiftDigitalVehicle]').text())
277    // });
278    //
279    // sd('send');
280  });
281
282  // Carousel Click Events
283  document.addEventListener('click', function(event) {
284    const target = event.target.closest('.carousel-item a[href], a[href].carousel-item, .item a[href]');
285    if (!target) return;
286
287    const node = (target.classList.contains('carousel-item') || target.classList.contains('item')) ? target : target.parentElement;
288    const siblings = Array.from(node.parentElement.children);
289
290    sd('dataLayer', {
291      events: 'carouselClick',
292      carouselClick: {
293        assetPosition: siblings.indexOf(node) + 1,
294        assetName: target.getAttribute('href'),
295      }
296    });
297
298    sd('send');
299  });
300
301  $(document).on('focus click keyup', '.wpcf7-form input, .wpcf7-form textarea, .wpcf7-form select', triggerFormInitiation);
302
303  // Form field interaction
304  document.addEventListener('change', function(event) {
305    const field = event.target;
306    if (!field.closest('.wpcf7-form')) return;
307    if (!['INPUT', 'TEXTAREA', 'SELECT'].includes(field.tagName)) return;
308
309    const form = field.closest('form');
310    const formType = getFormField(form, 'formType') || getFormField(form, 'magnetisFormType') || 'other';
311    const displayType = field.closest('.modal') ? 'modal' : 'in-page';
312
313    sd('dataLayer', {
314      events: 'formFieldInteraction',
315      formType: asShiftDigitalFormType(formType),
316      displayType: displayType,
317      formFieldName: field.name,
318      formVehicle: JSON.parse($('[data-class=ShiftDigitalVehicle]').text()),
319    });
320
321    sd('send');
322  });
323
324  function triggerFormInitiation(event)
325  {
326    const form = getForm(event.target);
327
328    if ($(form).data('initialTriggered')) {
329      return;
330    }
331
332    $(form).data('initialTriggered', true);
333
334    // Find the form type field
335    const formType = getFormField(form, 'formType') || getFormField(form, 'magnetisFormType') || 'other';
336
337    sd('dataLayer',{
338      events: 'formInitiation',
339      formType: asShiftDigitalFormType(formType),
340    });
341
342    sd('send');
343  }
344
345  $(document).on('vehicle-listing.type-search', (e, props) => {
346    sd('dataLayer', {
347      events: 'typedSearch',
348      typedSearchContent: props.search
349    });
350    sd('send');
351  });
352
353  /**
354   * Shift digital search metric.
355   *
356   * @param {object} eventData - Detail from the 'resultsUpdated' CustomEvent.
357   *   activeFilters and totalResults are read directly from the event.
358   */
359  function sd_search(eventData)
360  {
361    if (typeof( sd ) != 'function') return;
362    if (!eventData || eventData.activeFilters == null) return;
363
364    var filters = eventData.activeFilters;
365    var vehiclesCount = eventData.totalResults;
366
367    function getFilterMulti(filterKey) {
368      var v = filters[filterKey];
369      return Array.isArray(v) ? v : (v ? [String(v)] : []);
370    }
371
372    function getFilterSingle(filterKey) {
373      if (filters[filterKey] !== undefined) {
374        return filters[filterKey];
375      }
376      // Range filters use nested {min, max} objects (e.g. filters.price.min).
377      if (filterKey.endsWith('_min')) {
378        var rangeObj = filters[filterKey.slice(0, -4)];
379        if (rangeObj && typeof rangeObj === 'object') return rangeObj.min !== undefined ? rangeObj.min : null;
380      }
381      if (filterKey.endsWith('_max')) {
382        var rangeObj = filters[filterKey.slice(0, -4)];
383        if (rangeObj && typeof rangeObj === 'object') return rangeObj.max !== undefined ? rangeObj.max : null;
384      }
385      return null;
386    }
387
388    var year = '';
389    var year_min = getFilterSingle('year_min');
390    var year_max = getFilterSingle('year_max');
391
392    // We have at least one year filtered
393    if ( year_min || year_max ) {
394      var curr_year = new Date().getFullYear();
395
396      if ( year_min ) {
397        year_min = parseInt( year_min );
398      }
399
400      if ( year_max ) {
401        year_max = parseInt( year_max );
402
403        if ( ! year_min ) {
404          // Limit to 10 years older?
405          year_min = year_max - 10;
406        }
407      }
408      else {
409        // Up to next year I guess
410        year_max = curr_year + 1;
411      }
412
413      // Build list of years with pipe delimiters
414      // Range with min/max
415      if( year_min && year_max ) {
416        var i;
417        for ( i = year_min; i <= year_max; i++ ) {
418          year += i + '|';
419        }
420
421        // Remove last pipe
422        year = year.substring(0, year.length - 1);
423      }
424    }
425    else {
426      year = 'All';
427    }
428
429    var make = getFilterMulti('make');
430    var model = getFilterMulti('model');
431    var exterior_color = getFilterMulti('exterior_color_filter');
432    var bodyStyle = getFilterMulti('body');
433    var drive = getFilterMulti('drive');
434    var transmission = getFilterMulti('transmission');
435
436    var features = [].concat(drive, transmission);
437
438    function isFilterOn(filterKey) {
439      var value = getFilterSingle(filterKey);
440      return value === true || value === 1 || value === '1' || value === 'true';
441    }
442
443    // Demos are reported as new: unregistered vehicles, sold as new.
444    function distinctStatuses(values) {
445      return [...new Set(values.filter(Boolean).map((value) => (String(value) === 'demo' ? 'new' : String(value))))];
446    }
447
448    /**
449     * SD only accepts New, Used, CPO or Mixed (guide, appendix 3) — 'All' is rejected.
450     * What the page is filtered on wins over what it rendered: a listing legitimately
451     * comes back empty, and a hidden filter (is_demo, is_certified) still states intent.
452     */
453    function searchStatus() {
454      if (isFilterOn('is_certified')) {
455        return 'CPO';
456      }
457
458      var filtered = distinctStatuses(getFilterMulti('status'));
459
460      if (filtered.length > 1) {
461        return 'Mixed';
462      }
463
464      if (filtered.length === 1) {
465        return stringUcFirst(filtered[0]);
466      }
467
468      if (isFilterOn('is_demo')) {
469        return 'New';
470      }
471
472      var rendered = distinctStatuses(getSrpVehicles().map((vehicle) => vehicle.status));
473
474      return rendered.length === 1 ? stringUcFirst(rendered[0]) : 'Mixed';
475    }
476
477    sd('dataLayer', {
478      events: 'filterSearch',
479      countSearchResults: vehiclesCount,
480      filterSearch: {
481        status: searchStatus(),
482        year: year,
483        make: make.join('|') || 'All',
484        model: model.join('|') || 'All',
485        trim: 'All',
486        color : exterior_color.join('|') || 'All',
487        driveTrain: drive,
488        minPrice : getFilterSingle('price_min') || 'All',
489        maxPrice: getFilterSingle('price_max') || 'All',
490        bodyStyle : bodyStyle.join('|') || 'All',
491        features : features.join('|') || 'All'
492      }
493    });
494
495    sd('send');
496  }
497
498  window.addEventListener('resultsUpdated', function(e) {
499    sd('send', 'pageview');
500    sd_search(e.detail);
501  });
502
503  document.addEventListener('vehicle-listing.type-search', function(e) {
504    sd('dataLayer', { events: 'typedSearch', typedSearchContent: e.detail && e.detail.search });
505    sd('send');
506  });
507  document.addEventListener('magnetis.form_visible', function(e) {
508    var formType = e.target.querySelector('[name=magnetisFormType]') && e.target.querySelector('[name=magnetisFormType]').value;
509    if (!formType) return;
510    // Prefer vehicle data passed via event detail (e.g. from SRP popup CTA).
511    // Fall back to the global ShiftDigitalVehicle script (set on VDP pages).
512    var formVehicle = (e.detail && e.detail.vehicle) || JSON.parse($('[data-class=ShiftDigitalVehicle]').text());
513    sd('dataLayer', {
514      events: 'formShown',
515      formType: asShiftDigitalFormType(formType),
516      formVehicle: formVehicle
517    });
518    sd('send');
519  });
520
521
522  /*
523   * Build the search payload from the SRP data the server renders on first load.
524   */
525  function getInitialSearchData() {
526    const filterEl = document.querySelector('script[data-type=Filter]');
527    if (!filterEl) {
528      return null;
529    }
530
531    try {
532      const activeFilters = JSON.parse(filterEl.textContent);
533      const metaEl = document.getElementById('search_result_meta');
534      const totalResults = metaEl ? JSON.parse(metaEl.textContent).vehicles_count : null;
535
536      return { activeFilters: activeFilters, totalResults: totalResults };
537    } catch (e) {
538      return null;
539    }
540  }
541
542  sd_search(getInitialSearchData()); // Run on page load
543
544});
545

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.