PageSourceSearch

https://www.sanar-tver.ru/local/templates/redesign_new/assets/js/product.js?174497814422087

js sanar-tver.ru collected 2026-09-28 22:46:06 UTC 22,087 bytes, 522 lines download raw bytes

1function ProductSanar() {
2  this.selector = { // Все данные о селекторах
3    basketConteiners: '.basket-table',
4    basketDeleteConteiners: '.basket-table__delete',
5    basketConteinersActive: '.product-block.active',
6    basketProductConteinersActive: '.product-act.active',
7    mainConteiners: '.product-block, .product-content__nw_act',
8    productConteiners: '.product-act',
9    basketConteiner: '.basket-table[data-id="#ID#"]',
10    mainConteiner: '.product-block[data-id="#ID#"], .product-content__nw_act[data-id="#ID#"]',
11    productConteiner: '.product-act[data-id="#ID#"]',
12    basketCount: '.count-cart',
13    basketFullPrice: '.full_price',
14    basketAddBtn: '.add-basket',
15    basketPlusBtn: '.plus-cart',
16    basketMinusBtn: '.minus-cart',
17    basketDeleteBtn: '.delete-cart',
18    basketDeleteNoReturnBtn: '.full-delete',
19    basketReturnBtn: '.return-item',
20    basketName: '.item-name',
21    basketInformer: '.basket-informer span.text',
22    basketTotal: '.basket-total p',
23    basketSuccessAdd: '#success-add',
24    favoriteBtn: '.set-fav',
25    favoriteActiveBtn: '.active.set-fav',
26    oneClickBtn: '.oneclick',
27    modalOneClick: '#oneclick',
28    oneClickInput: 'input[name="PRODUCT"]'
29  };
30  this.intervals = {};
31  this.stateItems = {};
32  this.stateLoading = true;
33  this.pathPostFile = '/local/include/product.php';
34  this.addListener = () => {
35    document.addEventListener('click', e => {
36      let currentTarget = e.target;
37      if (currentTarget.matches(this.selector.basketPlusBtn)) {
38        this.setCount(e, 'plus');
39      }
40      if (currentTarget.matches(this.selector.basketMinusBtn)) {
41        this.setCount(e, 'minus');
42      }
43      if (currentTarget.matches(this.selector.basketDeleteBtn)) {
44        this.deleteFromBasket(e);
45      }
46      if (currentTarget.matches(this.selector.basketReturnBtn)) {
47        this.returnToBasket(e);
48      }
49      if (currentTarget.matches(this.selector.basketAddBtn)) {
50        this.addBasket(e);
51      }
52      if (currentTarget.matches(this.selector.basketDeleteNoReturnBtn)) {
53        this.deleteFromBasketNoReturn(e);
54      }
55      if (currentTarget.matches(this.selector.favoriteBtn)) {
56        this.setFavorite(e);
57      }
58      if (currentTarget.matches(this.selector.oneClickBtn)) {
59        let
60          modalContent = document.querySelector(this.selector.modalOneClick),
61          modal = bootstrap.Modal.getOrCreateInstance(modalContent),
62          oneClickInput = modalContent.querySelector(this.selector.oneClickInput);
63        oneClickInput.value = currentTarget.dataset.id;
64        modal.show();
65      }
66    });
67    document.addEventListener('DOMContentLoaded', () => {
68      this.getState();
69    })
70  };
71  this.setFavorite = (event) => {
72    let
73      currentTarget = event.target,
74      parentElement = this._findParentElement(currentTarget, this.selector.mainConteiners),
75      data = {
76        ID: parentElement.dataset.id,
77        METHOD: 'SET_FAVORITES',
78      };
79    this._request(this.pathPostFile, data, this.setState);
80  }
81  this.addBasket = (event) => {
82    let
83      currentTarget = event.target,
84      parentElement = (this._findParentElement(currentTarget, this.selector.mainConteiners)) ? this._findParentElement(currentTarget, this.selector.mainConteiners) : this._findParentElement(currentTarget, this.selector.productConteiners),
85      data = {
86        METHOD: 'ADD_BASKET',
87        COUNT: (currentTarget.dataset.measureRatio) ? currentTarget.dataset.measureRatio : 1,
88        ID: parentElement.dataset.id
89      };
90    if (!parentElement.classList.contains('active')) {
91      event.preventDefault();
92      if (parentElement.classList.contains('product-act')) {
93        let basketCount = parentElement.querySelector(this.selector.basketCount);
94        data.COUNT = basketCount.innerHTML;
95        let
96          modalContent = document.querySelector(this.selector.basketSuccessAdd),
97          modal = bootstrap.Modal.getOrCreateInstance(modalContent);
98        modal.show();
99      }
100      this._request(this.pathPostFile, data, this.setState);
101    }
102  };
103  this.deleteFromBasketNoReturn = (event) => {
104    let
105      currentTarget = event.target,
106      parentBasketElement = this._findParentElement(currentTarget, this.selector.basketDeleteConteiners);
107    parentBasketElement.remove();
108    let basketConteiners = document.querySelector('.basket-table');
109    if (basketConteiners === null || basketConteiners === undefined) {
110      window.location.reload();
111    }
112  };
113  this.setCount = (event, type) => {
114    let
115      currentTarget = event.target,
116      parentElement = (this._findParentElement(currentTarget, this.selector.mainConteiners)) ? this._findParentElement(currentTarget, this.selector.mainConteiners) : this._findParentElement(currentTarget, this.selector.productConteiners),
117      parentBasketElement = this._findParentElement(currentTarget, this.selector.basketConteiners),
118      data = {
119        METHOD: 'EDIT_BASKET',
120      };
121
122    if (parentElement) {
123      if (parentElement.classList.contains('active')) {
124        let basketCount = parentElement.querySelector(this.selector.basketCount);
125        data.ID = parentElement.dataset.id;
126        if (type !== 'minus') {
127          if (basketCount.dataset.measureRatio.toString().includes('.')) {
128            data.COUNT = (parseFloat(basketCount.innerHTML) + parseFloat(basketCount.dataset.measureRatio)).toFixed(2);
129          } else {
130            data.COUNT = parseInt(basketCount.innerHTML) + parseInt(basketCount.dataset.measureRatio);
131          }
132        } else {
133          if (basketCount.dataset.measureRatio.toString().includes('.')) {
134            data.COUNT = (parseFloat(basketCount.innerHTML) - parseFloat(basketCount.dataset.measureRatio)).toFixed(2);
135          } else {
136            data.COUNT = parseInt(basketCount.innerHTML) - parseInt(basketCount.dataset.measureRatio);
137          }
138
139          if (data.COUNT < parseFloat(basketCount.dataset.measureRatio)) {
140            this.deleteFromBasket(event);
141            return;
142          }
143        }
144        this._request(this.pathPostFile, data, this.setState);
145      } else {
146        let basketCount = parentElement.querySelector(this.selector.basketCount);
147        if (type !== 'minus') {
148          if (basketCount.dataset.measureRatio.toString().includes('.')) {
149            basketCount.innerHTML = parseFloat((parseFloat(basketCount.innerHTML) + parseFloat(basketCount.dataset.measureRatio)).toFixed(2));
150          } else {
151            basketCount.innerHTML = parseInt(basketCount.innerHTML) + parseInt(basketCount.dataset.measureRatio);
152          }
153        } else {
154          if (parseFloat(basketCount.innerHTML) > parseFloat(basketCount.dataset.measureRatio)) {
155            if (basketCount.dataset.measureRatio.toString().includes('.')) {
156              basketCount.innerHTML = parseFloat((parseFloat(basketCount.innerHTML) - parseFloat(basketCount.dataset.measureRatio)).toFixed(2));
157            } else {
158              basketCount.innerHTML = parseInt(basketCount.innerHTML) - parseInt(basketCount.dataset.measureRatio);
159            }
160          }
161        }
162      }
163
164    } else if (parentBasketElement) {
165      let basketCount = parentBasketElement.querySelector(this.selector.basketCount);
166      data.ID = parentBasketElement.dataset.id;
167      if (type !== 'minus') {
168        data.COUNT = parseFloat(basketCount.innerHTML) + parseFloat(basketCount.dataset.measureRatio);
169      } else {
170        data.COUNT = parseFloat(basketCount.innerHTML) - parseFloat(basketCount.dataset.measureRatio);
171        if (data.COUNT < parseFloat(basketCount.dataset.measureRatio)) {
172          this.deleteFromBasket(event);
173          return;
174        }
175      }
176      this._request(this.pathPostFile, data, this.setState);
177    }
178
179  };
180  this.deleteFromBasket = (event) => {
181    let
182      currentTarget = event.target,
183      parentElement = (this._findParentElement(currentTarget, this.selector.mainConteiners)) ? this._findParentElement(currentTarget, this.selector.mainConteiners) : this._findParentElement(currentTarget, this.selector.productC
183onteiners),
184      parentBasketElement = this._findParentElement(currentTarget, this.selector.basketConteiners),
185      data = {
186        METHOD: 'DELETE_BASKET',
187      },
188      actionDelete = (response) => {
189        if (parentBasketElement) {
190          let name = parentBasketElement.querySelector(this.selector.basketName).innerHTML;
191          parentBasketElement.replaceChildren();
192          parentBasketElement.classList.add('basket-table__delete');
193          parentBasketElement.classList.remove('basket-table');
194          let
195            table = document.createElement('table'),
196            tbody = document.createElement('tbody'),
197            tr = document.createElement('tr'),
198            td = document.createElement('td'),
199            div = document.createElement('div'),
200            a = document.createElement('a');
201          table.append(tbody);
202          tbody.append(tr);
203          td.innerHTML = 'Товар ' + name + ' был удален из корзины';
204          tr.append(td);
205          td = document.createElement('td');
206          div.classList.add('basket-table__act');
207          a.classList.add('link', 'return-item');
208          a.innerHTML = 'Восстановить';
209          a.href = 'javascript:void(0)';
210          div.append(a);
211          a = document.createElement('a');
212          a.classList.add('delete', 'full-delete');
213          a.insertAdjacentHTML("beforeend", `<svg xmlns="http://www.w3.org/2000/svg" width="15" height="16" viewBox="0 0 15 16"> <path id="Path_94" data-name="Path 94" d="M241.119,148.27H237.71V147.6a1.978,1.978,0,0,0-.6-1.414,2.069,2.069,0,0,0-1.447-.586h-2.727a2.069,2.069,0,0,0-1.447.586,1.978,1.978,0,0,0-.6,1.414v.667h-3.409a.667.667,0,1,0,0,1.333h1.364v.04l.607,10.077a1.982,1.982,0,0,0,.64,1.339,2.071,2.071,0,0,0,1.406.544h5.608a2.071,2.071,0,0,0,1.406-.544,1.982,1.982,0,0,0,.64-1.339l.6-10.077v-.04h1.364a.667.667,0,1,0,0-1.333Zm-8.864-.667a.674.674,0,0,1,.682-.667h2.727a.69.69,0,0,1,.482.2.659.659,0,0,1,.2.471v.667h-4.091Zm5.533,12.04a.675.675,0,0,1-.682.627h-5.612a.675.675,0,0,1-.682-.627l-.6-10.04h8.182Z" transform="translate(-226.8 -145.603)" fill="#99b3c5"></path></svg>`);
214          a.href = 'javascript:void(0)';
215          div.append(a);
216          td.append(div);
217          tr.append(td);
218          parentBasketElement.append(table);
219        }
220        this.setState(response)
221      };
222    if (parentElement) {
223      data.ID = parentElement.dataset.id;
224    } else if (parentBasketElement) {
225      data.ID = parentBasketElement.dataset.id;
226    }
227    this._request(this.pathPostFile, data, actionDelete);
228  };
229  this.returnToBasket = (event) => {
230    let
231      currentTarget = event.target,
232      parentBasketElement = this._findParentElement(currentTarget, this.selector.basketDeleteConteiners),
233      data = {
234        METHOD: 'ADD_BASKET',
235      },
236      returnAction = (response) => {
237        let currentState = this.stateItems[parentBasketElement.dataset.id].basket,
238          template = `
239      <table>
240        <tbody>
241          <tr>
242            <td>
243              <div class="basket-table__product">
244                <div class="image">
245                  <a href="#URL#">
246                    <img src="#IMAGE#" alt="">
247                  </a>
248                </div>
249                <div class="text">
250                  <a class="link item-name" href="#URL#">#NAME#</a>
251                </div>
252              </div>
253            </td>
254            <td class="price">#PRICE# руб. <strong>/#MEASURE#</strong></td>
255            <td>
256              <div class="input-group spinner">
257                <button class="btn increase minus-cart" type="button">
258                  <img src="/local/templates/redesign/assets/images/minusblack.svg" alt="">
259                </button>
260                <span class="spinner-input count-cart" data-measure-ratio="#RATIO#">#COUNT#</span>
261                <button class="btn decrease plus-cart" type="button">
262                  <img src="/local/templates/redesign/assets/images/plus.svg" alt="">
263                </button>
264              </div>
265            </td>
266            <td>
267              <div class="basket-table__act">
268                <p class="full_price">#FULL_PRICE# руб.</p>
269                <a class="delete delete-cart" href="javascript:void(0)">
270                  <svg xmlns="http://www.w3.org/2000/svg" width="15" height="16" viewB
270ox="0 0 15 16">
271                    <path id="Path_94" data-name="Path 94" d="M241.119,148.27H237.71V147.6a1.978,1.978,0,0,0-.6-1.414,2.069,2.069,0,0,0-1.447-.586h-2.727a2.069,2.069,0,0,0-1.447.586,1.978,1.978,0,0,0-.6,1.414v.667h-3.409a.667.667,0,1,0,0,1.333h1.364v.04l.607,10.077a1.982,1.982,0,0,0,.64,1.339,2.071,2.071,0,0,0,1.406.544h5.608a2.071,2.071,0,0,0,1.406-.544,1.982,1.982,0,0,0,.64-1.339l.6-10.077v-.04h1.364a.667.667,0,1,0,0-1.333Zm-8.864-.667a.674.674,0,0,1,.682-.667h2.727a.69.69,0,0,1,.482.2.659.659,0,0,1,.2.471v.667h-4.091Zm5.533,12.04a.675.675,0,0,1-.682.627h-5.612a.675.675,0,0,1-.682-.627l-.6-10.04h8.182Z" transform="translate(-226.8 -145.603)" fill="#99b3c5"></path>
272                  </svg>
273                </a>
274              </div>
275            </td>
276          </tr>
277        </tbody>
278      </table>
279        `;
280        parentBasketElement.replaceChildren();
281        parentBasketElement.classList.remove('basket-table__delete');
282        parentBasketElement.classList.add('basket-table');
283        template = this._replaceMacros(template, '#URL#', currentState.URL);
284        template = this._replaceMacros(template, '#NAME#', currentState.NAME);
285        template = this._replaceMacros(template, '#IMAGE#', currentState.IMAGE);
286        template = this._replaceMacros(template, '#PRICE#', this._number_format(parseFloat(currentState.PRICE), 2, '.', ' '));
287        template = this._replaceMacros(template, '#FULL_PRICE#', this._number_format(parseFloat(currentState.PRICE * currentState.QUANTITY), 2, '.', ' '));
288        template = this._replaceMacros(template, '#COUNT#', parseInt(currentState.QUANTITY));
289        template = this._replaceMacros(template, '#RATIO#', parseInt(currentState.RATIO));
290        template = this._replaceMacros(template, '#MEASURE#', currentState.MEASURE);
291        parentBasketElement.insertAdjacentHTML("beforeend", template);
292        this.setState(response);
293      };
294    let currentState = this.stateItems[parentBasketElement.dataset.id].basket;
295    data.ID = parentBasketElement.dataset.id;
296    data.COUNT = currentState.QUANTITY;
297    this._request(this.pathPostFile, data, returnAction);
298  };
299  this.setState = (response) => {
300    let
301      data = JSON.parse(response),
302      basketInformer = document.querySelectorAll(this.selector.basketInformer),
303      basketTotal = document.querySelectorAll(this.selector.basketTotal),
304      basketProductConteinersActive = document.querySelectorAll(this.selector.basketProductConteinersActive),
305      basketConteinersActive = document.querySelectorAll(this.selector.basketConteinersActive),
306      favoriteActiveBtn = document.querySelectorAll(this.selector.favoriteActiveBtn);
307
308    basketInformer.forEach(elem => {
309      if (data.totalPrice > 0) {
310        this._replaceIntRecursive(elem, data.totalPrice);
311        elem.closest('.basket-informer').classList.add('active');
312      } else {
313        elem.textContent = 'Корзина';
314        elem.closest('.basket-informer').classList.remove('active');
315      }
316
317    });
318
319    basketTotal.forEach(elem => {
320      this._replaceIntRecursive(elem, data.totalPrice, true);
321    });
322    for (let key in data.basket) {
323      if (this.stateItems[key] === undefined) {
324        this.stateItems[key] = {};
325      }
326      this.stateItems[key].basket = data.basket[key]
327    }
328    basketConteinersActive.forEach(elem => {
329      let isActive = false;
330      for (let key in data.basket) {
331        if (elem.dataset.id == key) {
332          isActive = true;
333        }
334      }
335      if (!isActive) {
336        elem.classList.remove('active');
337        delete this.stateItems[elem.dataset.id];
338      }
339    });
340    basketProductConteinersActive.forEach(elem => {
341      let isActive = false;
342      for (let key in data.basket) {
343        if (elem.dataset.id == key) {
344          isActive = true;
345        }
346      }
347      if (!isActive) {
348        elem.classList.remove('active');
349        delete this.stateItems[elem.dataset.id];
350      }
351    });
352
353    favoriteActiveBtn.forEach(elem => {
354      elem.classList.remove('active');
355    })
356    for (let key in data.favorite) {
357      let mainConteiner = document.querySelectorAll(this._replaceMacros(this.selector.mainConteiner, '#ID#', data.favorite[key]));
358      mainConteiner.forEach(elem => {
359        let favoriteBtn = elem.querySelector(this.selector.favoriteBtn);
360        favoriteBtn.classList.add('active');
361      });
362    }
363    //устанавливаем полученные данные
364    for (let key in this.stateItems) {
365      // корзина
366      let
367        basketConteiner = document.querySelectorAll(this._replaceMacros(this.selector.basketConteiner, '#ID#', key)),
368        mainConteiner = document.querySelectorAll(this._replaceMacros(this.selector.mainConteiner, '#ID#', key)),
369        productConteiner = document.querySelectorAll(this._replaceMacros(this.selector.productConteiner, '#ID#', key));
370      productConteiner.forEach(elem => {
371        elem.classList.add('active');
372        let basketCount = elem.querySelector(this.selector.basketCount);
373        basketCount.innerHTML = parseFloat(this.stateItems[key].basket.QUANTITY);
374      })
375      basketConteiner.forEach(elem => {
376        let basketCount = elem.querySelector(this.selector.basketCount);
377        let basketFullPrice = elem.querySelector(this.selector.basketFullPrice);
378        this._replaceIntRecursive(basketFullPrice, this.stateItems[key].basket.QUANTITY * this.stateItems[key].basket.PRICE);
379        basketCount.innerHTML = parseFloat(this.stateItems[key].basket.QUANTITY);
380      });
381      mainConteiner.forEach(elem => {
382        elem.classList.add('active');
383        let basketCount = elem.querySelector(this.selector.basketCount);
384        basketCount.innerHTML = parseFloat(this.stateItems[key].basket.QUANTITY);
385      });
386    }
387  };
388  this.getState = () => {
389    this._request(this.pathPostFile, {}, this.setState);
390  };
391  this._replaceIntRecursive = (elem, newNum, needTotal = false) => {
392    let _self = this;
393    let value = parseFloat(elem.innerText.replaceAll(' ', '').replaceAll('Итого:', ''));
394
395    if (newNum < value) {
396      let speed = (value - newNum) / 100;
397      recursiveMinus(speed, _self, needTotal);
398    } else {
399      let speed = (newNum - value) / 100;
400      recursivePlus(speed, _self, needTotal);
401    }
402
403    function recursiveMinus(speed, _self, needTotal = false) {
404      setTimeout(() => {
405        let value = parseFloat(elem.innerText.replaceAll(' ', '').replaceAll('Итого:', ''));
406        let totalText = '';
407        if (needTotal) {
408          totalText = 'Итого: ';
409        }
410        if ((value - speed) > newNum) {
411          elem.innerText = totalText + _self._number_format(parseFloat(value - speed), 2, '.', ' ') + ' ₽';
412        } else {
413          elem.innerText = totalText + _self._number_format(parseFloat(newNum), 2, '.', ' ') + ' ₽';
414        }
415        if ((value - speed) > newNum) {
416          recursiveMinus(speed, _self, needTotal);
417        }
418      }, 1);
419    }
420
421    function recursivePlus(speed, _self, needTotal = false) {
422      setTimeout(() => {
423        let value = parseFloat(elem.innerText.replaceAll(' ', '').replaceAll('Итого:', ''));
424        let totalText = '';
425        if (needTotal) {
426          totalText = 'Итого: ';
427        }
428        if ((value + speed) < newNum) {
429          elem.innerText = totalText + _self._number_format(parseFloat(value + speed), 2, '.', ' ') + ' ₽';
430        } else {
431          elem.innerText = totalText + _self._number_format(parseFloat(newNum), 2, '.', ' ') + ' ₽';
432        }
433        if ((value + speed) < newNum) {
434          recursivePlus(speed, _self, needTotal);
435        }
436      }, 1);
437    }
438  };
439  this.start = () => {
440    this.addListener();
441  };
442  this.start();
443}
444
445ProductSanar.prototype._replaceMacros = (text, macros, value) => {
446  return text.replaceAll(macros, value);
447}
448
449ProductSanar.prototype._request = (url, data, action) => {
450  let
451    request = new XMLHttpRequest(),
452    sendData = new FormData();
453  for (let key in data) {
454    sendData.append(key, data[key]);
455  }
456  request.open("POST", url);
457  request.addEventListener('load', () => {
458    action(request.response);
459  });
460  request.send(sendData);
461};
462
463ProductSanar.prototype._getRequest = (url, data, action) => {
464  let
465    request = new XMLHttpRequest(),
466    sendData = '?';
467  for (let key in data) {
468    if (sendData == '?') {
469      sendData += key + '=' + data[key];
470    } else {
471      sendData += '&' + key + '=' + data[key];
472    }
473  }
474  request.open("GET", url + sendData);
475  request.addEventListener('load', () => {
476    action(request.response);
477  });
478  request.send();
479};
480
481ProductSanar.prototype._number_format = (number, decimals = 0, dec_point = '.', thousands_sep = ',') => {
482
483  let sign = number < 0 ? '-' : '';
484
485  let s_number = Math.abs(parseInt(number = (+number || 0).toFixed(decimals))) + "";
486  let len = s_number.length;
487  let tchunk = len > 3 ? len % 3 : 0;
488
489  let ch_first = (tchunk ? s_number.substr(0, tchunk) + thousands_sep : '');
490  let ch_rest = s_number.substr(tchunk)
491    .replace(/(\d\d\d)(?=\d)/g, '$1' + thousands_sep);
492  let ch_last = decimals
493    ? dec_point + (Math.abs(number) - s_number)
494    .toFixed(decimals)
495    .slice(2)
496    : '';
497
498  return sign + ch_first + ch_rest + ch_last;
499};
500
501ProductSanar.prototype._findParentElement = (elem, selector) => {
502  if (selector) {
503    while (elem !== undefined && elem !== null) {
504      elem = getParent(elem);
505      if (elem !== undefined && elem !== null) {
506        if (elem.matches(selector)) {
507          return elem;
508        }
509      }
510    }
511    return;
512  } else {
513    return;
514  }
515
516  function getParent(elem) {
517    return elem.parentElement;
518  }
519}
520
521
522let productSanar = new ProductSanar();

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.