1/*jshint esversion: 11 */ 2 3jQuery(function($) { 4 5 $.fn.priceBuilder = JQueryPlugin({ 6 options: { 7 // WP ajax url 8 ajaxUrl: '', 9 10 builderUrl: 'http://pricebuilder.magnetisdealer.com/builder?model_id=36766', 11 12 // form or stripe 13 reservationMode: 'form', 14 15 vehicleId: 0, 16 lang: 'fr' 17 }, 18 methods: { 19 init: function () { 20 const parser = document.createElement('a'); 21 parser.href = this.options.builderUrl; 22 const url = parser.protocol + '//' + parser.host ; 23 24 const gtmEventMap = { 25 Open: { event: 'magnetis.started' }, 26 Close: { event: 'magnetis.close', reason: 'unload' }, 27 BackToSite: { event: 'magnetis.close', reason: 'back_to_site' }, 28 Inventory: { event: 'magnetis.close', reason: 'inventory' }, 29 StyleChange: { event: 'magnetis.configuration_change', type: 'build_model' }, 30 KeyOptionChange: { event: 'magnetis.configuration_change', type: 'build_model' }, 31 OptionChange: { event: 'magnetis.configuration_change', type: 'build_option' }, 32 PaymentOption: { event: 'magnetis.interaction', action: 'payment_options' }, 33 Legal: { event: 'magnetis.interaction', action: 'legal' }, 34 Reserve: { event: 'magnetis.form_started', mode: 'reserve' }, 35 RequestInfo: { event: 'magnetis.form_started', mode: 'lead' }, 36 }; 37 38 const buildVehicleContext = (data) => { 39 const model = data?.vehicle?.model || {}; 40 const style = data?.vehicle?.style || {}; 41 const summary = data?.summary || {}; 42 return { 43 vehicle_make: model.divisionName, 44 vehicle_model: model.modelName, 45 vehicle_year: model.modelYear, 46 vehicle_trim: style.consumerFriendlyStyleName, 47 vehicle_style_id: style.styleId, 48 vehicle_color: data?.vehicle?.color, 49 vehicle_price: summary.price, 50 session_id: data?.session?.actual, 51 }; 52 }; 53 54 window.addEventListener("message", (message) => { 55 if (message.origin != url) { 56 return; 57 } 58 59 //console.log('PriceBuilder', message, url); 60 61 const gtmPayload = gtmEventMap[message.data.event]; 62 if (gtmPayload && typeof window.gtmPushNewEvent === 'function') { 63 window.gtmPushNewEvent({ 64 Component: 'plateforme.priceBuilder', 65 ...gtmPayload, 66 ...buildVehicleContext(message.data), 67 }); 68 } 69 70 this.element.trigger(message.data.event, message.data); 71 }); 72 73 this.element.on('Reserve', this.onReserve.bind(this)); 74 75 this.element.on('RequestInfo', this.onRequestInfo.bind(this)); 76 77 // On builder ready ('Open'), forward the open-time context (fresh fees + per-trim incentives) 78 this.element.on('Open', () => { 79 Promise.resolve(this.openDataPromise).then((data) => this.postContextToIframe(data)); 80 }); 81 82 if (window.location.hash === '#' + this.element.attr('id')) { 83 // Open the UI if the anchor related to current id is present 84 this.open(); 85 } 86 }, 87 88 onReserve: function(event, messagePayload) { 89 console.log('---------------------'); 90 console.log(messagePayload); 91 92 $.ajax({
93 contentType: "application/json", 94 url: this.options.ajaxUrl + "?action=reservation_pricebuilder", 95 method: 'POST', 96 data: JSON.stringify(messagePayload), 97 }) 98 .done(function (response) { 99 document.location.href = response.redirect; 100 console.log(response); 101 }); 102 103 }, 104 105 onRequestInfo: function(event, messagePayload) { 106 console.log('---------------------'); 107 console.log(messagePayload); 108 109 const commentString = this.buildOptionsMessage(messagePayload); 110 $('.js-price-builder-modal textarea[name="comments"]').val(commentString); 111 $('.js-price-builder-modal').modal('show'); 112 }, 113 114 open: function() { 115 const priceBuilderUrl = new URL(this.options.builderUrl); 116 const priceBuilderUrlParams = new URLSearchParams(priceBuilderUrl.search); 117 118 const fromUrl = new URL(document.location); 119 priceBuilderUrlParams.set('url', fromUrl.protocol + '//' + fromUrl.host); 120 121 // Pre-select the trim + color the user picked on the VDP so the builder opens on the same configuration 122 const chromeStyleId = $('#native_trim_selector option:selected').attr('data-sid'); 123 const chromeColorCode = $('.trim_color_picker span.active').attr('data-color-chrome-code'); 124 if (chromeStyleId) { 125 priceBuilderUrlParams.set('style_id', chromeStyleId); 126 } 127 if (chromeColorCode) { 128 priceBuilderUrlParams.set('color', chromeColorCode); 129 } 130 131 priceBuilderUrl.search = priceBuilderUrlParams; 132 133 // Kick off the open-time data fetch; forwarded to the iframe on 'Open'. 134 this.openDataPromise = this.fetchOpenData(); 135 136 jQuery('body').addClass('no-scroll'); 137 138 this.element.find('iframe').attr('src', priceBuilderUrl); 139 this.element.removeClass('d-none'); 140 141 setTimeout(() => { 142 this.element.addClass('open'); 143 }, 50); 144 145 this.element.trigger('open'); 146 147 //Sending Optimy widget to the back 148 let launcher = $('optimy-launcher'); 149 if (launcher.length) { 150 let shadowRoot = launcher[0].shadowRoot; 151 $(shadowRoot).find('.optimy').css('z-index', '45'); 152 } 153 154 //check if iframe #podium is present if yes set it's z-index to 50 using jquery 155 let podium = $('iframe#podium-bubble'); 156 if (podium.length) { 157 podium.css('z-index', '44'); 158 } 159 }, 160 161 close: function() { 162 this.element.removeClass('open'); 163 // Wait for the transition to complete 164 this.element.on('transitionend', () => { 165 this.element.find('iframe').attr('src', 'about:blank'); 166 jQuery('body').removeClass('no-scroll'); 167 this.element.addClass('d-none'); 168 this.element.off('transitionend'); 169 170 this.element.trigger('close'); 171 172 // Reset z-index of the Optimy widget 173 let launcher = $('optimy-launcher'); 174 if (launcher.length) { 175 let shadowRoot = launcher[0].shadowRoot; 176 $(shadowRoot).find('.optimy').css('z-index', ''); 177 } 178 let podium = $('iframe#podium-bubble'); 179 if (podium.length) { 180 podium.css('z-index', '1000'); 181 } 182 183 if (window.location.hash === '#' + this.element.attr('id')) { 184 const newUrl = window.location.href.split('#')[0]; 185 history.replaceState({}, document.title, newUrl); 186 } 187 }); 188 }, 189 190 /* Fetch fresh fees + per-trim incentives for this VDP from the host. */ 191 fetchOpenData: function () { 192 const ajaxUrl = this.options.ajaxUrl; 193 const vehicleId = this.options.vehicleId; 194 const lang = this.options.lang || 'fr'; 195 196 if (!ajaxUrl || !vehicleId) { 197 return Promise.resolve(null); 198 } 199 200 const url = ajaxUrl + '?action=pricebuilder_pricing_context' 201 + '&vehicle_id=' + encodeURIComponent(vehicleId) 202 + '&lang=' + encodeURIComponent(lang); 203 204 return fetch(url, {method: 'GET'}) 205 .then((response) => (response.ok ? response.json() : null)) 206 .catch(() => null); 207 }, 208 209 /* Forward the open-time context (fees + incentives) into the iframe. */ 210 postContextToIframe: function (data) { 211 if (!data) { 212 return; 213 } 214 215 const iframe = this.element.find('iframe')[0]; 216 if (!iframe || !iframe.contentWindow) { 217 return; 218 } 219 220 const parser = document.createElement('a'); 221 parser.href = this.options.builderUrl; 222 const builderOrigin = parser.protocol + '//' + parser.host; 223 224 iframe.contentWindow.postMessage( 225 { 226 type: 'magnetis:context', 227 fees: data.fees || [], 228 inPriceAdditionalFees: data.inPriceAdditionalFees || 0, 229 incentives: data.incentives || {}, 230 }, 231 builderOrigin 232 ); 233 }, 234 235 /** 236 * Convert PriceBuilder reservation into a human-readable string 237 * @param messagePayload 238 * @return {string} 239 */ 240 buildOptionsMessage: function (messagePayload) { 241 const optionsInfo = messagePayload.summary.categories 242 .filter(category => category.options && category.options.length > 0) // filter out empty or non-existing options 243 .map(category => { 244 const optionsString = category.options.map(option => `${option.name}: ${option.value}$`).join(', '); 245 return `${category.name}: [${optionsString}]`; 246 }) 247 .join('\n'); 248 249 // Format the information into a string 250 const message = [ 251 `Model: ${messagePayload.vehicle.model.divisionName} ${messagePayload.vehicle.model.modelName} ${messagePayload.vehicle.model.modelYear}`, 252 `Style: ${messagePayload.vehicle.style.consumerFriendlyStyleName}`,
253 `Color Code: ${messagePayload.vehicle.color}`, 254 `Options:\n${optionsInfo}` 255 ].join("\n"); 256 257 if (messagePayload.reservation && messagePayload.reservation.url) { 258 message += `\nBuild URL: ${messagePayload.reservation.url}`; 259 } 260 return message; 261 } 262 }, 263 }); 264 265 $('.js-price-builder').priceBuilder(); 266});
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.