PageSourceSearch

https://www.interton.com/assets/javascript/Feature/cart-line/index.js

js interton.com collected 2026-10-01 15:34:18 UTC 23,628 bytes, 649 lines download raw bytes

1import { Modal } from 'bootstrap.native';
2/* eslint-disable-next-line */
3import { event, loader } from '../../Foundation';
4/* eslint-disable-next-line */
5import throttle from 'lodash.throttle';
6import { AmendPrices } from './components/amend-prices';
7import domready from 'domready';
8import { NumberInput } from '../../Feature';
9
10export class CartLine {
11  /**
12   * Creates a new instance of the Video class for handling HTML5 Video, Youtube Videos and 23Videos
13   * @param {HTMLElement} element Video DOM element
14   */
15  constructor(element) {
16    // Name, reference
17    element.CartLine = this;
18    this.el = element;
19
20    // Data
21    this._data = JSON.parse(element.dataset.cartline);
22
23    // Props
24    this._props = {
25      isFavorite: this.el.classList.contains('is-favorite'),
26    };
27
28    // Elements
29    this._refs = {
30      el: element,
31      numberInput: element.querySelector('.number-input'),
32      accessoriesNumberInputs: element.getElementsByClassName('accessories-number-input'),
33      flexpoints: element.querySelector('.cart-line__flex-points'),
34      editButton: element.querySelector('.is-editable'),
35      configuratorSide: element.querySelector('.configurator__selector__side'),
36      buttons: {
37        delete: element.querySelector('.js-cartline-remove'),
38        amendPrice: element.querySelector('.change-price'),
39      },
40      dialogs: {
41        delete: element.querySelector('.js-cartline-remove-dialog'),
42        quantity: element.querySelector('.js-cartline-quantity-update-dialog'),
43        quantityError: element.querySelector('.js-cartline-quantity-error-dialog'),
44      },
45      config: {
46        links: Array.from(element.querySelectorAll('.cart-line__controls-start a')),
47        contentarea: element.querySelector('.cart-line__configuration'),
48      },
49      table: {
50        columns: Array.from(element.querySelectorAll('td.hover')),
51      },
52    };
53
54    this._refs.editButton?.addEventListener('click', this._onItemClick.bind(this));
55
56    // Load
57    if (
58      this._refs.config.contentarea.classList.contains('is-active') &&
59      this._refs.config.show != null &&
60      this._refs.config.hide != null
61    ) {
62      this._refs.config.show.classList.add('is-hidden');
63      this._refs.config.hide.classList.remove('is-hidden');
64    }
65
66    // Toggle configuration
67    if (this._refs.buttons.delete != null && !element.classList.contains('constructorFinished')) {
68      this._refs.buttons.delete.addEventListener('click', this._onRemoveItemClick.bind(this));
69    }
70
71    // Number input
72    if (this._refs.numberInput != null && !element.classList.contains('constructorFinished')) {
73      this._refs.numberInput.addEventListener(
74        'numberinput.updated',
75        this._onNumberinputUpdate.bind(this),
76      );
77    }
78
79    // Accessories number input
80    if (this._refs.accessoriesNumberInputs.length > 0) {
81      for (var i = 0; i < this._refs.accessoriesNumberInputs.length; i++) {
82        var accessoryNumberInput = this._refs.accessoriesNumberInputs[i];
83        accessoryNumberInput.addEventListener(
84          'accessoriesnumberinput.updated',
85          this._onAccessoriesNumberInputUpdate.bind(this, accessoryNumberInput),
86        );
87        var accessoryCartLineId = accessoryNumberInput.querySelector(
88          '#AccessoryLineIdHiddenField',
89        ).value;
90        var accessoryItemNo = accessoryNumberInput.querySelector(
91          '#AccessoryItemNoHiddenField',
92        ).value;
93        new NumberInput(accessoryNumberInput, true, accessoryCartLineId, accessoryItemNo);
94      }
95    }
96
97    if (this._refs.buttons.amendPrice != null) {
98      this._refs.buttons.amendPrice.addEventListener(
99        'click',
100        this._onAmendPriceLinkClick.bind(this),
101      );
102    }
103
104    // Checking if page has loaded
105    if (this._refs.buttons.amendPrice != null) {
106      if (document.readyState === 'complete') {
107        this._refs.buttons.amendPrice.classList.remove('disableClick');
108      } else {
109        this._refs.buttons.amendPrice.classList.add('disableClick');
110        domready(() => {
111          this._refs.buttons.amendPrice.classList.remove('disableClick');
112        });
113      }
114    }
115
116    this._addLoaderForRemoveAccessories();
117    this.hideEmptyModelText();
118
119    element.classList.add('constructorFinished');
120  }
121
122  /**
123   * Calculator for flexpoints in case a flexpoint-item has been added as cartLine
124   * @param {Event} detail - Details from updated number-input
125   */
126  /* eslint-disable-next-line */
127  _onNumberInputUpdate({ detail }) {}
128
129  _addLoaderForRemoveAccessories() {
130    const accessoriesBinIcons = document.querySelectorAll('.remove-accessory');
131
132    accessoriesBinIcons.forEach((accessoriesBinIcon) => {
133      accessoriesBinIcon.addEventListener('click', () => loader.show(), { once: true });
134    });
135  }
136
137  /**
138   * onItemClick
139   * When a user clicks an editable field inside product-table
140   * @param {Event} e - event interface
141   */
142  /* eslint-disable-next-line */
143  _onItemClick(e) {
144    const currentPropertyIdHiddenField = document.getElementById('CurrentPropertyIdHiddenField'),
145      /* eslint-disable-next-line */
146      selectedPropertyIdHiddenField = document.getElementById('SelectedPropertyIdHiddenField'),
147      //propertyIdHiddenField = e.target.closest('td').querySelector('#PropertyIdHiddenField'),
148      selectedEditLineIdHiddenField = document.getElementById('SelectedEditLineIdHiddenField'),
149      lineIdHiddenField = this._refs.el.querySelector('#LineIdHiddenField'),
150      isEditClickedHiddenField = document.getElementById('IsEditClickedHiddenField');
151
152    currentPropertyIdHiddenField.value = selectedEditLineIdHiddenField.value;
153    //selectedPropertyIdHiddenField.value = propertyIdHiddenField.value;
154
155    selectedEditLineIdHiddenField.value = lineIdHiddenField.value;
156    isEditClickedHiddenField.value = 'true';
157
158    loader.show();
159
160    __doPostBack('OverviewUpdatePanel', '');
161  }
162
163  /**
164   * _onRemoveItemClick
165   * Remove an item from cart
166   * @param {Event} e - event interface
167   */
168  _onRemoveItemClick(e) {
169    e.preventDefault();
170
171    let id = this._refs.buttons.delete.dataset.target,
172      el = this.el.querySelector(id),
173      modal = el.Modal || new Modal(el),
174      handler = () => {
175        modal.hide();
176
177        let lineId = this.el.querySelector('#LineIdHiddenField');
178        if (lineId != null) {
179          let oLineId = document.getElementById('SelectedDeleteLineIdHiddenField');
180          if (oLineId != null) {
181            oLineId.value = lineId.value;
182          }
183        }
184
185        const calendarFields = document.querySelectorAll('[data-datepicker]');
186        const calendarFieldsAsElements = [...calendarFields];
187
188        calendarFieldsAsElements.forEach((cf) => {
189          cf.disabled = true;
190        });
191
192        // Execute postback
193        __doPostBack(this._data.RemoveCartItemLinkButtonId, '');
194      };
195
196    // Display
197    modal.show();
198
199    // Events
200    if (el) {
201      el.addEventListener(
202        'shown.bs.modal',
203        (event) => {
204          let x = event.target,
205            trigger = x.querySelector('.btn-primary');
206
207          trigger.addEventListener('click', handler);
208        },
209        false,
210      );
211
212      el.addEventListener(
213        'hide.bs.modal',
214        (event) => {
215          let x = event.target,
216            trigger = x.querySelector('.btn-primary');
217
218          trigger.removeEventListener('click', handler);
219        },
220        false,
221      );
222    }
223  }
224
225  /**
226   * _onConfigurationToggle
227   * @param {Event} e - Event Interface
228   */
229  _onConfigurationToggle(e) {
230    let target = e.target;
231
232    e.preventDefault();
233
234    if (target.classList.contains('js-hide-configurator')) {
235      this._refs.config.show.classList.remove('is-hidden');
236
237      this._refs.config.contentarea.classList.remove('is-active');
238
239      this._refs.config.hide.classList.add('is-hidden');
240    } else {
241      this._refs.config.show.classList.add('is-hidden');
242
243      this._refs.config.contentarea.classList.add('is-active');
244
245      this._refs.config.hide.classList.remove('is-hidden');
246    }
247  }
248
249  /**
250   * _onNumberinputUpdate
251   * Update quantity for cartLine once user has changed input
252   * @param {HTMLEvent} e - event interface
253   */
254  _onNumberinputUpdate({ detail }) {
255    const updateFavoritesApi = '/api/favorites/updatequantity';
256    const updateCartlineApi = '/api/cart/cartline/updatequantity';
257    const quantity = detail.value,
258      lineId = this.el.querySelector('#LineIdHiddenField'),
259      api = this._props.isFavorite ? updateFavoritesApi : updateCartlineApi;
260
261    // Flexpoints calculation
262    if (this._refs.flexpoints !== null) {
263      // Flexpoint calculation
264      const oItemUnitValue = parseInt(this._refs.flexpoints.dataset.point),
265        oItemPointCalculation = this._refs.flexpoints.querySelector('div');
266
267      if (oItemUnitValue === null || oItemPointCalculation === null) return;
268
269      oItemPointCalculation.innerHTML = (oItemUnitValue * detail.value).toLocaleStr
269ing('en-US', {
270        maximumFractionDigits: 2,
271      });
272    }
273    loader.show();
274    fetch(api, {
275      method: 'PATCH',
276      headers: {
277        'Content-Type': 'application/json',
278      },
279      body: JSON.stringify({
280        Id: lineId.value,
281        Quantity: quantity,
282      }),
283    })
284      .then((response) => {
285        if (response.status === 200) {
286          var oldValue = this.el.getAttribute('oldvalue') ?? detail.initial;
287          //var currentCartTotalItemsCount = +document.querySelector('.main-navigation__basket>span').innerHTML;
288
289          //if (detail.value != oldValue)
290          //currentCartTotalItemsCount = currentCartTotalItemsCount - oldValue + detail.value;
291
292          //this._setNewTotalCartItemsCount(currentCartTotalItemsCount);
293          this.el.setAttribute('oldvalue', detail.value);
294
295          // Send to google analytics
296          if (oldValue != detail.value) {
297            var numberChange = Math.abs(detail.value - oldValue);
298            var gaEventName = oldValue > detail.value ? 'remove_from_cart' : 'add_to_cart';
299            var productNumber = this.el.querySelector('.product_number').innerText;
300            var productName = this.el.querySelector('.product-item__modelText').innerText;
301
302            dataLayer.push({ ecommerce: null });
303            dataLayer.push({
304              event: gaEventName,
305              ecommerce: {
306                currency: 'EUR',
307                items: [
308                  {
309                    item_id: productNumber,
310                    item_name: productName,
311                    item_category: 'n/a',
312                    item_category2: 'n/a',
313                    item_category3: 'n/a',
314                    item_category4: 'n/a',
315                    price: 1.0,
316                    quantity: numberChange,
317                  },
318                ],
319              },
320            });
321
322            response.json().then((body) => {
323              const cartData = JSON.parse(body);
324              if (cartData !== undefined) {
325                if (api === updateCartlineApi) {
326                  this.syncReceiversAmount(cartData);
327                }
328                cartData.forEach((product) => {
329                  cartItems.push({
330                    item_id: product.ProductId,
331                    item_name: product.ModelName,
332                    item_brand: product.Brand,
333                    item_category: product.PricePoint,
334                    item_category2: product.FamilyCode,
335                    item_category3: product.ShellType,
336                    item_category4: product.Color,
337                    item_variant: product.ModelNumber,
338                    price: 1,
339                    quantity: product.Quantity,
340                  });
341                });
342                dataLayer.push({
343                  event: 'cart_state',
344                  ecommerce: {
345                    currency: 'EUR',
346                    items: cartItems,
347                  },
348                });
349
350                this._updatePackagingText(lineId.value, productNumber, cartData);
351                this._pushDataLayerCartState(cartData);
352              }
353            });
354          }
355        }
356        // Minimum display time for the loader
357        setTimeout(() => loader.hide(), 250);
358        /* eslint-disable-next-line */
359      })
360      .catch((response) => {
361        console.log(response);
362        loader.hide();
363      });
364  }
365
366  _updatePackagingText(lineId, productNumber, cartData) {
367    const tableEl = this._refs.el.querySelector('.productDetailTable');
368    const rowsEl = tableEl?.getElementsByTagName('tr') ?? [];
369
370    for (let i = 0; i < rowsEl.length; i++) {
371      const inputsEl = [...rowsEl[i].getElementsByTagName('input')];
372      const packagingInput = !!inputsEl.find((inputEl) => inputEl.value === 'Packaging');
373
374      if (packagingInput) {
375        const rowLiElements = rowsEl[i].getElementsByTagName('li');
376        const cartDataItem = cartData.find(
377          (d) => d.LineId === lineId && productNumber.includes(d.ProductId),
378        );
379
380        if (rowLiElements.length && cartDataItem) {
381          rowLiElements[0].textContent = cartDataItem.Packaging;
382        }
383        break;
384      }
385    }
386  }
387
388  _pushDataLayerCartState(cartData) {
389    const cartItems = [];
390
391    cartData.forEach((product) => {
392      cartItems.push({
393        item_id: product.ProductId,
394        item_name: product.ModelName,
395        item_brand: product.Brand,
396        item_category: product.PricePoint,
397        item_category2: product.FamilyCode,
398        item_category3: product.ShellType,
399        item_category4: product.Color,
400        item_variant: product.ModelNumber,
401        price: 1,
402        quantity: product.Quantity,
403      });
404    });
405
406    dataLayer.push({
407      event: 'cart_state',
408      ecommerce: {
409        currency: 'EUR',
410        items: cartItems,
411      },
412    });
413  }
414
415  /**
416   * syncReceiversAmount
417   * Synchronization receivers amount on the UI based on the response from the backend once user has changed quantity
418   * @param {HTMLEvent} cartData - cart data from the API call
419   */
420  syncReceiversAmount(cartData) {
421    const productIds = cartData.map((d) => d.ProductId);
422    const receiverNumberSpans = [...this.el.querySelectorAll('span.product_number')];
423    receiverNumberSpans.forEach((span) => {
424      if (!productIds.includes(span.innerText)) {
425        const liElement = span.closest('li');
426        if (liElement) {
427          liElement.remove();
428        }
429      }
430    });
431  }
432
433  /**
434   * __onAccessoriesNumberInputUpdate
435   * Update quantity for accessories cartLine once user has changed input
436   * @param {HTMLEvent} e - event interface
437   */
438  _onAccessoriesNumberInputUpdate(accessoryNumberInputElement, e) {
439    const quantity = e.detail.value,
440      lineId = accessoryNumberInputElement.querySelector('#AccessoryLineIdHiddenField'),
441      itemNo = accessoryNumberInputElement.querySelector('#AccessoryItemNoHiddenField'),
442      api = this._props.isFavorite
443        ? '/api/favorites/updatequantity'
444        : '/api/cart/accessorycartline/updatequantity';
445
446    // Flexpoints calculation
447    if (this._refs.flexpoints !== null) {
448      // Flexpoint calculation
449      const oItemUnitValue = parseInt(this._refs.flexpoints.dataset.point),
450        oItemPointCalculation = this._refs.flexpoints.querySelector('div');
451
452      if (oItemUnitValue === null || oItemPointCalculation === null) return;
453
454      oItemPointCalculation.innerHTML = (oItemUnitValue * detail.value).toLocaleString('en-US', {
455        maximumFractionDigits: 2,
456      });
457    }
458
459    loader.show();
460    fetch(api, {
461      method: 'PATCH',
462      headers: {
463        'Content-Type': 'application/json',
464      },
465      body: JSON.stringify({
466        Id: lineId.value,
467        Quantity: quantity,
468        ItemNo: itemNo.value,
469      }),
470    })
471      .then((response) => {
472        if (response.status === 200) {
473          var oldValue = accessoryNumberInputElement.getAttribute('oldvalue') ?? e.detail.initial;
474          var currentCartTotalItemsCount = +document.querySelector('.main-navigation__basket>span')
475            .innerHTML;
476
477          if (e.srcElement.classList.contains('accessories-number-input')) {
478            if (e.detail.value != oldValue)
479              currentCartTotalItemsCount = currentCartTotalItemsCount - oldValue + e.detail.value;
480          }
481
482          this._setNewTotalCartItemsCount(currentCartTotalItemsCount);
483          accessoryNumberInputElement.setAttribute('oldvalue', e.detail.value);
484        }
485        // Minimum display time for the loader
486        setTimeout(() => loader.hide(), 250);
487        /* eslint-disable-next-line */
488      })
489      .catch((response) => {
490        console.log(response);
491        loader.hide();
492      });
493  }
494  /**
495   * _onChangePriceItemClick
496   * Method for abort the amend prices flow
497   * @param {Event} e - event interface
498   */
499  _onAmendPriceLinkClick(e) {
500    e.preventDefault();
501
502    let id = this._refs.buttons.amendPrice.dataset.target,
503      el = this.el.querySelector(id),
504      modal = el.Modal || new Modal(el);
505
506    // Display
507    modal.show();
508
509    /* eslint-disable-next-line */
510    const oAmendPrices = new AmendPrices(el);
511  }
512
513  removeOrderTypeRadioButton() {
514    const stockQuantity = document.querySelector('.cart-line__quantity');
515    document.querySelector('.cart-line__delivery-date .form-group')?.appendChild(stockQuantity);
516    let radioButtons = document.getElementsByClassName('RadiobuttonSelection');
517    [...radioButtons].forEach((radio) => {
518      if (!radio.firstChild.getAttribute('checked')) {
519        radio.remove();
520      }
521    });
522  }
523
524  appendProductQuantity() {
525    [...document.querySelectorAll('.cart-line:not(.is-favorite)')].forEach((cartLine) => {
526      let quantity = cartLine.querySelector('.cart-line__quantity');
527      let quantityTable = cartLine.querySelector('.cart-line__quantity table');
528      if (quantity) {
529        cartLine.querySelector('.cart-line__delivery-date .form-group')?.appendChild(quantity);
530        if (quantityTable) {
531          quantityTable.classList.add('is-hidden');
532        }
533      }
534    });
535  }
536
537  getConfiguratorSideText() {
538    [...document.querySelectorAll('.cart-line')].forEach((cartLineSection) => {
539      const sideValue = cartLineSection.querySelector('.configurator__selector__side label');
540      let orderTypeText = cartLineSection.querySelector(
541        '.productDetailTable .RadiobuttonSelection label',
542      );
543      if (sideValue !== null) {
544        orderTypeText.innerHTML += `, ${sideValue.textContent}`;
545      }
546    });
547  }
548
549  addMarginBusinessWrapperDescription() {
550    if (document.querySelector('.top-free-text') !== null) {
551      document.querySelector('.top-free-text').classList.add('col-md-10');
552      document.querySelector('.top-free-text').classList.add('cart-top-free-text');
553    }
554  }
555
556  //_setNewTotalCartItemsCount(newTotalCartItemsCount) {
557  //    const topMainTitle = document.querySelector('.top-main-title').textContent.substring(0, document.querySelector('.top-main-title').textContent.indexOf('('));
558
559  //    document.querySelector('.main-navigation__basket>span').innerHTML = newTotalCartItemsCount;
560  //    document.querySelector('.products-counter').innerHTML = newTotalCartItemsCount;
561  //    document.getElementById('cssCart')?.setAttribute('cart-title-overwrite-counter', newTotalCartItemsCount);
562  //    document.querySelector('.top-main-title').textContent = `${topMainTitle} (${newTotalCartItemsCount} ${parseInt(newTotalCartItemsCount) > 1 ? document.getElementById('cssCart')?.getAttribute('cart-title-overwrite-text-plural') : document.getElementById('cssCart')?.getAttribute('cart-title-overwrite-text-singular')})`
563  //}
564
565  adjustProductColorRow() {
566    [...document.querySelectorAll('.configurator__controller-color')].forEach((colorDivNode) => {
567      if (colorDivNode.nextSibling?.textContent.trim() === '') {
568        colorDivNode.nextSibling.remove();
569      }
570
571      let parentNode = colorDivNode.parentNode;
572      let colorText = parentNode.querySelector('span');
573      colorText.classList.add('capitalize-letters');
574
575      if (parentNode) {
576        parentNode.insertBefore(colorText, colorDivNode);
577      }
578    });
579  }
580
581  adjustEarmoldTitleStyling() {
582    [...document.querySelectorAll('.product-item__familyCode')].forEach((productCode) => {
583      if (productCode.textContent === '') {
584        productCode.parentNode.classList.add('hide-dots');
585      }
586    });
587  }
588
589  adjustAccessoriesTitleStyling() {
590    [...document.querySelectorAll('.product-item__familyCode')].forEach((productCode) => {
591      if (productCode.textContent === '') {
592        const productItemName = productCode.parentNode;
593        productItemName.previousElementSibling.style.display = 'none';
594
595        const accessoryNameElement = productItemName.nextElementSibling;
596
597        if (!accessoryNameElement) return;
598
599        const headingContainer = productItemName.parentNode;
600        const cartLineHeadStart = headingContainer.parentNode;
601        const accessoryName = accessoryNameElement.textContent;
602
603        let PKUNumber = document.createElement('p');
604        let accessoryDetails = document.createElement('span');
605        accessoryDetails.textContent = cartLineHeadStart
606          .querySelector('h3.cart-line__sku > span')
607          ?.textContent.split('- ')[1];
608        accessoryDetails.classList.add('product_number');
609        headingContainer.classList.add('product-details-line-height');
610        PKUNumber.textContent = accessoryName.split('- ')[0];
611        PKUNumber.style.fontSize = '1.2rem';
612        PKUNumber.style.marginBottom = '2px';
613        if (!/^\d/.test(PKUNumber.textContent)) PKUNumber.style.fontSize = '2rem';
614        const splitAccesoryArray = accessoryName.split('- ');
615        const accesoryText = splitAccesoryArray[splitAccesoryArray.length - 1] || accessoryName;
616        accessoryNameElement.textContent = accesoryText;
617        if (productCode.nextElementSibling.innerText === '') {
618          headingContainer.appendChild(PKUNumber);
619          headingContainer.appendChild(accessoryDetails);
620        }
621      }
622    });
623  }
624
625  hideEmptyModelText() {
626    [...document.querySelectorAll('.product-item__modelText')].forEach((modelText) => {
627      if (modelText.textContent === '' && modelText.previousElementSibling.textContent !== '') {
628        modelText.parentNode.removeChild(modelText);
629      }
630    });
631  }
632
633  addEarmoldSideToTitle() {
634    [...document.querySelectorAll('h3.cart-line__sku > span')].forEach((productInfo) => {
635      if (productInfo.textContent !== '') {
636        const side = productInfo.textContent.split(' - ', 1)[0];
637        const productDetails = productInfo.parentNode.nextElementSibling;
638        if (!/^\d/.test(side))
639          productDetails.lastElementChild.innerText = `${side}, ${productDetails.lastElementChild.innerText}`;
640      }
641    });
642  }
643
644  updateDeleteButtonAlignment() {
645    [...document.querySelectorAll('.cart-line__configuration')].forEach((removeIcon, index) => {
646      removeIcon?.appendChild([...document.querySelectorAll('.js-cartline-remove')][index]);
647    });
648  }
649}

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.