PageSourceSearch

https://moscowfish.net/served_assets/public/shop/common_ru.js?1790875599

js moscowfish.net collected 2026-10-02 06:58:36 UTC 36,502 bytes, 1,071 lines download raw bytes

1//=== trigger custom
2(function() {
3  var CustomEvent;
4
5  if (typeof window.CustomEvent !== 'function') {
6    CustomEvent = function(event, params) {
7      var evt;
8      params = params || {
9        bubbles: false,
10        cancelable: false,
11        detail: void 0
12      };
13      evt = document.createEvent('CustomEvent');
14      evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
15      return evt;
16    };
17    CustomEvent.prototype = window.Event.prototype;
18    window.CustomEvent = CustomEvent;
19  }
20
21  $.fn['triggerCustom'] = function(type, data, options) {
22    if (options == null) {
23      options = {};
24    }
25    options = $.extend({}, {
26      bubbles: true,
27      cancelable: true,
28      detail: data
29    }, {
30      bubbles: options.bubbles,
31      cancelable: options.cancelable
32    });
33    return this.each(function() {
34      var e;
35      e = new window.CustomEvent(type, options);
36      return this.dispatchEvent(e);
37    });
38  };
39
40}).call(this);
41
42
43//=== Global functions ===
44function compact(array) {
45  var new_array = [];
46  for (var i = 0; i < array.length; i++) {
47    if ( array[i] ) new_array.push(array[i]);
48  }
49  return new_array;
50}
51
52function calculate_total_cost(product, variant, quantity) {
53  if(product.using_price_types) {
54    var price = variant.price;
55    var price_type;
56    for(var j in product.price_types) {
57        if ( product.price_types[j].min_quantity > quantity )
58            break;
59        price_type = product.price_types[j];
60    }
61    if (price_type) {
62        for(var i in variant.prices) {
63            if ( variant.prices[i].price_type_id == price_type.id ) {
64                price = variant.prices[i].price;
65                break;
66            }
67        }
68    }
69    return price*quantity;
70  } else {
71    var price = variant.price;
72    var price_kind;
73    var price_kinds = product.price_kinds.sort(function(a,b){
74      return a.value - b.value;
75    });
76
77    for(var j in price_kinds) {
78        if ( price_kinds[j].value > quantity )
79            break;
80        if( variant.prices[product.price_kinds[j].price_index - 1] ){
81          price_kind = product.price_kinds[j];
82        }
83    }
84    if (price_kind) {
85        price = variant.prices[price_kind.price_index - 1];
86    }
87    return price*quantity;
88  }
89}
90
91function get_prices(product, variant) {
92  if(product.using_price_types) {
93    var price_by_type = {};
94    for(var i in variant.prices) {
95        var price = variant.prices[i];
96        price_by_type[price.price_type_id] = price;
97    }
98    var previous = null;
99    var table = []
100    for(var j in product.price_types) {
101        var price_type = product.price_types[j];
102        var price = variant.price;
103        var to = price_type.min_quantity - 1;
104        var from = 1;
105        if (previous) {
106            price = price_by_type[previous.id].price;
107            from = previous.min_quantity;
108        }
109        table.push({ from: from, to: to, price: price});
110        previous = price_type;
111    }
112    if (previous) {
113        table.push({ from: previous.min_quantity, to: null, price: price_by_type[previous.id].price});
114        return table;
115    } else {
116        return false;
117    }
118  } else {
119    var prices = variant.prices;
120    var previous = null;
121    var table = [];
122    var price_kinds = product.price_kinds.sort(function(a,b){
123      return a.value - b.value;
124    });
125
126    for(var j in price_kinds) {
127        var price_kind = price_kinds[j];
128        if(prices[price_kind.price_index - 1]){
129          var price = variant.price;
130          var to = price_kind.value - 1;
131          var from = 1;
132          if (previous) {
133              price = prices[previous.price_index-1];
134              from = previous.value;
135          }
136          table.push({ from: from, to: to, price: price});
137          previous = price_kind;
138        }
139    }
140    if (previous) {
141        table.push({ from: previous.value, to: null, price: prices[previous.price_index - 1]});
142        return table;
143    } else {
144        return false;
145    }
146  }
147}
148
149function floatToString(numeric, decimals) {
150    numeric = parseFloat(numeric) || 0;
151    var amount = numeric.toFixed(decimals).toString();
152    if(amount.match(/^\.\d+/)) {return "0"+amount;}
153    else {return amount;}
154};
155
156// Инициализировать добавление товара в корзин
156у через Ajax.
157function initAjaxAddToCartButton(handle, onAddToCart) {
158    jQuery(handle).click(function(e){
159        e.preventDefault();
160        jQuery(this).triggerCustom('clicked:insales:add_to_cart_button');
161        addOrderItem( jQuery(this).parents("form:first"), onAddToCart);
162    });
163}
164
165// Добавление товара в корзину
166function addOrderItem(form, onAddToCart, dontShowPreloader) {
167    var fields = form.serialize();
168    var action = form.attr("action").split("?");
169    var url    = action[0] + ".json";
170    var lang   = action[1] ? "?"+action[1] : "";
171    var path   = url + lang;
172    if (!dontShowPreloader) {
173      jQuery(document).one('before_additem:insales:cart', function(e){
174        if (!e.isDefaultPrevented()) {
175          show_preloader();
176        }
177      });
178      jQuery(document).triggerCustom('before_additem:insales:cart');
179    }
180    jQuery.ajax({
181        url:      path,
182        type:     'post',
183        data:     fields,
184        dataType: 'json',
185        success:  function() {
186              jQuery(document).triggerCustom('add_item:insales:cart');
187              jQuery(document).triggerCustom('insales:cart:add_item');
188              onAddToCart.apply(this, arguments);
189        },
190        error:    hide_preloader
191    });
192}
193
194// функция вытаскивает сообщения об ошибке из хэша в массив.
195function errors_to_arr(errors){
196    arr = [];
197    $.each( errors, function(obj, msg){
198        arr.push(msg);
199    });
200    return arr;
201}
202
203
204// Вывести индикатор работы (колесико)
205function show_preloader() {
206    var preloader = jQuery("#own_preloader");
207    if ( !preloader.attr("id") ) {
208        jQuery("body").append('<div id="own_preloader"><img src="/served_assets/loading.gif"/></div>');
209        preloader = jQuery("#own_preloader");
210    }
211    preloader.show();
212
213    changeCss(preloader);
214    jQuery(window).bind("resize", function(){
215        changeCss(preloader);
216    });
217    jQuery(window).bind("scroll", function(){
218        changeCss(preloader);
219    });
220}
221
222// Скрыть индикатор
223function hide_preloader() {
224    var preloader = jQuery("#own_preloader");
225    if ( !preloader.attr("id") ) return;
226    jQuery(window).unbind("resize");
227    jQuery(window).unbind("scroll");
228    preloader.remove();
229}
230
231// Заменить индикатор на сообщение
232function set_preloaders_message(message) {
233    var preloader = jQuery("#own_preloader");
234    if ( !preloader.attr("id") ) return;
235    preloader.html(message);
236}
237
238function changeCss(OBJ){
239    var imageHeight  = OBJ.height();
240    var imageWidth   = OBJ.width();
241    var windowWidth  = jQuery(window).width();
242    var windowHeight = jQuery(window).height();
243    OBJ.css({
244        "position" : "absolute",
245        "left" : windowWidth / 2 - imageWidth / 2,
246        "top" : getPageScroll()[1] + (getPageHeight() - imageHeight) / 2
247    });
248};
249
250// getPageScroll() by quirksmode.com
251function getPageScroll() {
252    var xScroll, yScroll;
253    if (self.pageYOffset) {
254        yScroll = self.pageYOffset;
255        xScroll = self.pageXOffset;
256    } else if (document.documentElement && document.documentElement.scrollTop) {   // Explorer 6 Strict
257        yScroll = document.documentElement.scrollTop;
258        xScroll = document.documentElement.scrollLeft;
259    } else if (document.body) {// all other Explorers
260        yScroll = document.body.scrollTop;
261        xScroll = document.body.scrollLeft;
262    }
263    return new Array(xScroll,yScroll)
264}
265
266// Adapted from getPageSize() by quirksmode.com
267function getPageHeight() {
268    var windowHeight
269    if (self.innerHeight) {  // all except Explorer
270        windowHeight = self.innerHeight;
271    } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
272        windowHeight = document.documentElement.clientHeight;
273    } else if (document.body) { // other Explorers
274        windowHeight = document.body.clientHeight;
275    }
276    return windowHeight
277}
278
279// === InSales ===
280
281if ((typeof InSales) == 'undefined' || !InSales.Cart) {
282  if (typeof InSales == 'undefined') {
283    InSales = null;
284  }
285  InSales = jQuery.extend(InSales, (function($){
286    var slice = [].slice
287
288    var $on = function() {
289      var args, jq;
290      jq = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
291      if (jq.on != null) {
292        return jq.on.apply(jq, args);
293      } else if (args.length > 2) {
294        // delegate принимает селектор в первом аргументе,
295        // а событие - во втором
296        ref = [args[0], args[1]], args[1] = ref[0], args[0] = ref[1];
297        return jq.delegate.apply(jq, args);
298      } else {
299        return jq.bind.apply(jq, args);
300      }
301    };
302
303    var $off = function() {
304      var args, jq;
305      jq = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
306      if (jq.off != null) {
307        return jq.off.apply(jq, args);
308      } else {
309        return jq.unbind.apply(jq, args);
310      }
311    };
312
313    var InSales = {};
314    var cart = null;
315
316    InSales.isDefined = function(obj) {
317      return ((typeof obj == 'undefined') ? false : true);
318    };
319
320    InSales.money_format = "{{amount}} "+"руб.";
321
322    /*
323      number - number to add delimiter
324      delimiter - value to use as digits delimiter (e.g. delimiter = '-'; 1000 -> 1-000)
325    */
326    InSales.numberWithDelimiter = function(number, delimiter) {
327      if (delimiter == undefined) delimiter = ' ';
328      number+=''
329      var split = number.split('.');
330      split[0] = split[0].replace(
331          /(\d)(?=(\d\d\d)+(?!\d))/g,
332        '$1'+delimiter
333      );
334      return split.join('.');
335    }
336
337    // Старая версия функции formatMoney, оставлена для совместимости
338    InSales.formatMoneyOld = function(amount, format) {
339      var patt = /\{\{\s*(\w+)\s*\}\}/;
340      var formatString = (format || this.money_format);
341      // формат вывода должен совпадать c форматом вывода в Liquid
342      value = floatToString(amount, 2).replace(/,00/, '').replace(/\.00/, '');
343      return formatString.replace(patt, value);
344    }
345
346    // в старой версии функции параметры приходили через string, в новой в виде JSON
347    InSales.formatMoneyIsOld = function(params) {
348      var oldFormat = false;
349      // проверка что в params JSON через try для jQuery > 1.9
350      try {
351        var moneyParams = $.parseJSON(params);
352        // проверка старым способом для jQuery <= 1.9
353        if ( moneyParams == null || typeof moneyParams != 'object') oldFormat = true;
354      } catch(err) {
355        oldFormat = true;
356      }
357      return oldFormat;
358    }
359
360    InSales.formatMoney = function(amount, params) {
361      if (amount == null) return '';
362      if (params == null && window.CURRENCY_FORMAT) params = CURRENCY_FORMAT;
363      if (params == null && window.cv_currency_format) params = cv_currency_format;
364      //FIXME оставлено для совместимости
365      // если приходит JSON - используем эту функцие, если нет - старую версию функции
366      if (this.formatMoneyIsOld(params)) return this.formatMoneyOld(amount, params);
367
368      amount = parseFloat(amount).toFixed(2) || 0;
369      if (typeof amount == 'string') amount = amount.replace(/\.00/, '');
370
371      var moneyParams = $.parseJSON(params);
372      value = moneyParams['show_price_without_cents'] ? Math.round(amount) : amount;
373      value = this.numberWithDelimiter(value, moneyParams['delimiter']);
374      value = value.replace(/\./, moneyParams['separator']);
375      return moneyParams['format'].replace('%n', value).replace('%u',moneyParams['unit']);
376    };
377
378    InSales.OrderLine = function(options) {
379      this.init = function(options) {
380        var line = this;
381        $.each(options, function(key,value) { line[key] = value; });
382        if (options.title) this.title = options.title.replace(/\+/g, ' ');
383        if (options.sku) this.sku = options.sku.replace(/\+/g, ' ');
384        this.url = '/product/?product_id=' + options.product_id;
385      }
386
387      this.image = function(format) {
388        return this.image_url.replace('thumb', format);
389      }
390
391      this.init(options);
392      return this;
393    }
394
395    InSales.Cart = function(drawCallback, format) {
396      // singleton
397      if (cart) {
398        return cart;
399      }
400
401      if (!(this instanceof InSales.Cart)) {
402        return new InSales.Cart(drawCallback, format);
403      }
404
405      cart = this;
406      this.get_text = function(text) {
407        if (!text) return text;
408        return text.replace(/\+/g, ' ');
409      }
410
411      this.setCart = function(order) {
412        if (!order) {
413          this.total_price = 0;
414          this.items_price = 0;
415          this.order_lines = [];
416          this.items_count = 0;
417          this.discounts = [];
418          return;
419        }
420        this.total_price = order.total_price;
421        this.items_price = order.items_price;
422        var items_count = 0;
423        var order_lines = [];
424        $(order.order_lines).each(function(index, order_line) {
425          items_count += order_line.quantity;
426          order_lines[index] = new InSales.OrderLine(order_line);
427        });
428        this.order_lines = order_lines;
429        this.items_count = items_count;
430        this.discounts = order.discounts;
431        this.discount_code = order.discount_code;
432      }
433
434      this.reloadCart = function(callback) {
435        InSales.Cart.loadCartData(function(order){
436          cart.setCart(order);
437          jQuery(document).triggerCustom('reloaded:insales:cart', cart);
438          callback()
439        });
440      }
441
442      this.removeItemTrigger = function(){
443        $('[id^="delete_"]').each(function(i) {
444          if ( $(this).attr('processed') ) { return; }
445          $(this).attr('processed',true);
446          var link = $(this);
447          var variant_id = link.attr("id").replace("delete_","");
448
449          $(this).click(function(e){
450            e.preventDefault();
451            cart.removeItem(variant_id);
452          });
453        });
454      }
455
456      this.removeItem = function(variant_id) {
457        var fields =  new Object;
458        fields['_method'] = 'delete';
459        var path   = '/cart_items/' + variant_id + '.json';
460        show_preloader();
461        $.ajax({
462          url:      path,
463          type:     'post',
464          data:     fields,
465          dataType: 'json',
466          success:  function(response) {
467            cart.reloadCart(function(){
468              hide_preloader();
469              cart.draw();
470              $(document).triggerCustom('remove_item:insales:cart');
471            });
472          },
473          error:  function(response) {
474            hide_preloader();
475            console.log("Cant remove item from cart");
476          }
477        });
478      }
479
480      this.addCallback = function() {
481        this.reloadCart(function(){
482          cart.draw();
483          set_preloaders_message('<div id="add_product_notification">
483'+"Товар добавлен в корзину"+'</div>');
484          window.setTimeout( hide_preloader, 1000);
485        });
486      }
487
488      this.draw = function() {
489        this.drawCallback(this);
490        this.removeItemTrigger();
491      }
492
493      this.refresh = function() {
494        this.reloadCart( function() { cart.draw() } );
495      }
496
497      this.init = function(options) {
498        if (!options) options = {};
499        this.drawCallback = options.draw || (function(){});
500        this.selector = options.selector;
501        this.format = format;
502        this.reloadCart(function(){
503          cart.draw();
504          initAjaxAddToCartButton(cart.selector, function(response){ cart.addCallback(); });
505        });
506        $on($(document), 'insales:cart:refresh', function(){ cart.refresh() });
507        $on($(document), 'refresh:insales:cart', function(){ cart.refresh() });
508      }
509
510      this.init(drawCallback);
511    }
512
513    InSales.Cart.loadCartData = function(callback) {
514      if ($.cookie('cart') && $.cookie('cart') != 'json') {
515        try {
516          var cart_json = $.parseJSON($.cookie('cart'));
517        } catch(e) {
518          var cart_json = null;
519        }
520        callback(cart_json);
521      } else {
522        $.ajax({
523          url: "/cart_items.json",
524          dateType: 'json',
525          success: callback
526        });
527      }
528    };
529
530    InSales.Cart.isEmpty = function(callback) {
531      if (cart) {
532        return callback(cart.items_count == 0);
533      }
534
535      InSales.Cart.loadCartData(function(order) {
536        for (var i = 0; i < order.order_lines.length; i++) {
537          if (order.order_lines[i].quantity > 0) {
538            return callback(false);
539          }
540        }
541        callback(true);
542      });
543    };
544
545    InSales.QueryString = function () {
546      // This function is anonymous, is executed immediately and
547      // the return value is assigned to QueryString!
548      var query_string = {};
549      var query = window.location.search.substring(1);
550      var vars = query.split("&");
551      for (var i=0;i<vars.length;i++) {
552        var pair = vars[i].split("=");
553        // If first entry with this name
554        if (typeof query_string[pair[0]] === "undefined") {
555          query_string[pair[0]] = pair[1];
556          // If second entry with this name
557        } else if (typeof query_string[pair[0]] === "string") {
558          var arr = [ query_string[pair[0]], pair[1] ];
559          query_string[pair[0]] = arr;
560          // If third or later entry with this name
561        } else {
562          query_string[pair[0]].push(pair[1]);
563        }
564      }
565      return query_string;
566    } ();
567    /*
568      свои сообщения можно передать через options
569      { _textAdd: '', _textLink: '', _textRepeat: '', _textCompareEnd: '' }
570    */
571    InSales.Compare = function(options) {
572      /* Сообщения по умолчанию */
573      var textAdd = "Товар добавлен к сравнению";
574      var textLink = "Сравнить";
575      var textRepeat = "Этот товар уже добавлен";
576      var textCompareEnd = "Сравнивать можно не более 4-х товаров";
577
578      var compare = this;
579      var productsCounter = 0;
580      var ProductId = 0;
581      var ProductIdRepeat = false;
582
583      this.otherText = function() {
584        if(this._textAdd) {
585          textAdd = this._textAdd;
586        }
587        if(this._textLink) {
588          textLink = this._textLink;
589        }
590        if(this._textRepeat) {
591          textRepeat = this._textRepeat;
592        }
593        if(this._textCompareEnd) {
594          textCompareEnd = this._textCompareEnd;
595        }
596      }
597
598      this.get_text = function(text) {
599        if (!text) return text;
600        return text.replace(/\+/g, ' ');
601      }
602
603      this.reload = function() {
604        try {
605          this.products = $.parseJSON($.cookie('compare'));
606        } catch(e) {
607          this.products = null;
608        }
609
610        if (!this.products) {
611          this.products = [];
612          return;
613        }
614
615        productsCounter = this.products.length;
616
617        $(this.products).each(function(index, product) {
618          $('a[rel='+this.id+']').not('.del_compare').text(""+textAdd+"").addClass('compare-added');
619          product['title'] = compare.get_text(product['title']);
620        });
621      }
622
623      this.removeItemTrigger = function(){
624        $('.remove_compare').each(function(i) {
625          if ( $(this).attr('processed') ) { return; }
626          $(this).attr('processed',true);
627          var product_id = $(this).attr('rel');
628
629          $(this).click(function(e){
630            e.preventDefault();
631            compare.removeItem(product_id);
632          });
633        });
634      }
635
636      this.removeItem = function(product_id) {
637        var fields =  new Object;
638        fields['_method'] = 'delete';
639        if (InSales.QueryString["lang"]) {
640          fields["lang"] = InSales.QueryString["lang"];
641        }
642        var path   = '/compares/' + product_id + '.json';
643        show_preloader();
644        $.ajax({
645          url:      path,
646          type:     'post',
647          data:     fields,
648          dataType: 'json',
649          success:  function(response) {
650            $('.compare_' + product_id).remove();
651            $('a[rel='+product_id+']').text(""+textLink+"").removeClass('compare-added');
652            compare.reload();
653            hide_preloader();
654            compare.draw();
655          },
656          error:  function(response) {
657            hide_preloader();
658          }
659        });
660      }
661
662      this.addItemTrigger = function() {
663        handler = function(e){
664          if (e.addedToCompare) { return; }
665          e.addedToCompare = true;
666          productsCounter = productsCounter+1
667          var product_id = $(this).attr('rel');
668          var path   = '/compares.json';
669
670          ProductId = product_id;
671
672          compare.checkIsRepeated();
673          show_preloader();
674
675          if(productsCounter > 4) {
676            set_preloaders_message('<div id="add_product_notification">'+textCompareEnd+'</div>');
677            window.setTimeout(hide_preloader, 3000);
678          } else {
679            $(this).text(""+textAdd+"").addClass('compare-added');
680            var data = {'product[id]': product_id};
681            if (InSales.QueryString["lang"]) {
682              data["lang"] = InSales.QueryString["lang"];
683            }
684            $.ajax({
685              url:      path,
686              type:     'post',
687              data:     data,
688              dataType: 'json',
689              success:  function(response) {
690                compare.reload();
691                compare.draw();
692                if (ProductIdRepeat == true) {
693                  set_preloaders_message('<div id="add_product_notification">'+textRepeat+'</div>');
694                  window.setTimeout(hide_preloader, 3000);
695                } else {
696                  set_preloaders_message('<div id="add_product_notification">'+textAdd+'</div>');
697                  window.setTimeout(hide_preloader, 3000);
698                }
699              },
700              error:  function(response) {
701                hide_preloader();
702              }
703            });
704          }
705        };
706
707        $on($(this.selector), 'click', handler);
708        $on($(document), this.selector, 'click', handler);
709      }
710
711      this.draw = function() {
712        this.drawCallback(this.products);
713        this.removeItemTrigger();
714      }
715
716      this.checkIsRepeated = function() {
717        ProductIdRepeat = false;
718        $.each(this.products, function() {
719          if(ProductId == this.id) {
720            ProductIdRepeat = true;
721            return false;
722          } else {
723            ProductIdRepeat = false;
724          }
725        });
726      }
727
728      this.init = function(options) {
729        this.drawCallback = options.draw;
730        this.selector = options.selector;
731        this._textAdd = options._textAdd;
732        this._textLink = options._textLink;
733        this._textRepeat = options._textRepeat;
734        this._textCompareEnd = options._textCompareEnd;
735        this.otherText();
736        this.reload();
737        this.draw();
738        this.addItemTrigger();
739      }
740
741      this.init(options);
742    }
743
744    InSales.Product = function(json) {
745
746      // Вспомогательные методы используемые при инициализации
747      this.addOption = function(option) {
748        this.options_by_id[option['id']] = {
749          title: option['title'],
750          values: []
751        };
752      };
753
754      this.addVariant = function(variant) {
755        var option_values = variant['option_values'];
756        for (var value in option_values) {this.addValue(option_values[value]);};
757      };
758
759      this.addValue = function(value) {
760        var option = this.options_by_id[value['option_name_id']];
761        option['values'][value['position']] = value;
762      };
763
764      // Инициализация
765      this.init = function(json) {
766        for (property in json) {this[property] = json[property];}
767        this.options_by_id = {};
768        for (var option  in this.option_names) {this.addOption(this.option_names[option]);}
769        for (var variant in this.variants)  {this.addVariant(this.variants[variant]);};
770        jQuery.each(this.options_by_id, function(index,option) {option['values'] = compact(option['values']);});
771
772      };
773
774      // returns array of option names for product
775      this.optionNames = function() {
776        return this.option_names;
777      };
778
779      // returns array of all option values (in order) for a given option name index
780      this.optionValues = function(index) {
781        var option = this.option_names[index];
782        if (!option) {return null;}
783        return this.options_by_id[option['id']]['values'];
784      };
785
786      // return the variant object if exists with given values, otherwise return null
787      this.getVariant = function(selectedValues) {
788        for (var i in this.variants) {
789          var variant = this.variants[i];
790          var satisfied = true;
791          jQuery.each(variant['option_values'], function(index,value) {
792            if (selectedValues[value['option_name_id']] != value['id']) {
793              satisfied = false;
794            }
795          });
796          if (satisfied == true) {return variant;}
797        }
798        return null;
799      };
800
801      this.variantById = function(id) {
802        for (var i in this.variants) {
803          if (parseInt(this.variants[i].id) == parseInt(id)) {
804            return this.variants[i];
805          }
806        }
807
808        return null;
809      };
810
811      this.init(json);
812    };
813
814    InSales.OptionSelectors = function(existingSelectorId, options) {
815      if ($("#"+existingSelectorId).attr("id") == undefined) return false;
816
817      // insert new multi-selectors and hide original selector
818      this.replaceSelector = function(domId) {
819        var oldSelector = document.getElementById(domId);
820        var parent = oldSelector.parentNode;
821        jQuery.each(this.buildSelectors(), function(index,el) {
822          parent.insertBefore(el, oldSelector);
823        });
824        oldSelector.style.display = 'none';
825        this.variantIdField = oldSelector;
826      };
827
828      // buildSelectors(index)
829      // create and return new selector element for given option
830      this.buildSelectors = function() {
831        // build selectors
832        for (var i = 0; i < this.product.optionNames().length; i++) {
833          var sel = new InSales.SingleOptionSelector(this, i, this.product.optionNames()[i], this.product.optionValues(i));
834          sel.element.disabled = false;
835          this.selectors.push(sel);
836        }
837
838        // replace existing selector with new selectors, new hidden input field, new hidden messageElement
839        var divClass = this.selectorDivClass;
840        var divId = this.optionIdFieldIdPrefix
841        var optionNames = this.product.optionNames();
842        var elements = [];
843        jQuery.each(this.selectors, function(index,selector) {
844          var div = document.createElement('div');
845          div.setAttribute('class', divClass);
846          div.setAttribute('id', divId + selector.option_id)
847          // create label if more than 1 option (ie: more than one drop down)
848          // безопасно ли переименовывать?
849          if (optionNames.length > 1 || optionNames[0].title != 'Вариант' || optionNames[0].title != 'Модификация') {
850            // create and appened a label into div
851            var label = document.createElement('label');
852            label.innerHTML = selector.name;
853            div.appendChild(label);
854          }
855          div.appendChild(selector.element);
856          elements.push(div);
857        });
858        return elements;
859      };
860
861      // returns array of currently selected values from all multiselectors
862      this.selectedValues = function() {
863        var currValues = {};
864        for (var i = 0; i < this.selectors.length; i++) {
865          var selector = this.selectors[i]
866          var thisValue = selector.element.value;
867          currValues[selector.option_id] = thisValue;
868        }
869        return currValues;
870      };
871
872      this.getCurrVariant = function() {
873        var currValues = this.selectedValues(); // get current values
874        return this.product.getVariant(currValues);
875      };
876
877      // callback when a selector is updated.
878      this.updateSelectors = function(index) {
879        var variant    = this.getCurrVariant();
880        if (variant) {
881          this.variantIdField.disabled = false;
882          this.variantIdField.value = variant.id; // update hidden selector with new variant id
883        } else {
884          this.variantIdField.disabled = true;
885        }
886        this.onVariantSelected(variant, this);  // callback
887      };
888
889      this.selectAvailableVariant = function() {
890        var values = {};
891        for (var i = 0; i < this.product.variants.length; i++) {
892          var variant = this.product.variants[i];
893          if (!variant.available) continue;
894          jQuery.each(variant.option_values, function(index,option_value) {
895            values[option_value.option_name_id] = option_value.id;
896          });
897          break;
898        }
899        jQuery.each(this.selectors, function(index,selector) {
900          selector.selectValue(values[selector.option_id]);
901        });
902        this.selectors[0].element.onchange();     // init the new dropdown
903      };
904
905      this.filterOptionValues = function(){
906        // Создаём массив значений св-в каждого варианта
907        var variants_option_values = [];
908        for (var i = 0; i < this.product.variants.length; i++) {
909          var variant = this.product.variants[i];
910          //if (!variant.available) continue;
911          var variant_option_values = []
912          jQuery.each(variant.option_values, function(index,option_value) {
913            variant_option_values.push(option_value);
914          });
915          variants_option_values.push(variant_option_values)
916        }
917
918        var id_prefix = 'variant-select-option-';
919        var max_option_name_index = jQuery('.single-option-selector').size() - 1;
920        // При выборе значения св-ва у следующего св-ва оставляем только те значения, которые могут быть у товара при выбранных предыдущих значениях
921        $on($('.single-option-selector'), 'change', function() {
922          var selected_option_value_id = $(this).val();
923          var id = $(this).attr('id');
924          var option_name_index = parseInt(id.replace(id_prefix,''));
925          if (option_name_index == max_option_name_index) return;
926          var next_option_name_index = option_name_index + 1;
927          var option_values = [];
928          $.each(variants_option_values, function(index,variant_option_values){
929            var current_option_value = variant_option_values[option_name_index];
930            if (current_option_value.id == selected_option_value_id) {
931              var allowed_option_value = true
932              // проходимся по ранее выбранным значениям св-в
933              for (var i=option_name_index; i >= 0; i--){
934                var prev_current_option_value_id = $("#"+id_prefix+i).val();
935                if (variant_option_values[i].id != prev_current_option_value_id) allowed_option_value = false;
936              }
937              if (allowed_option_value){
938                var next_option_value = variant_option_values[next_option_name_index];
939                option_values = $.grep(option_values, function (option_value) {return option_value.id != next_option_value.id;});
940                option_values.push(next_option_value);
941              }
942            }
943          });
944
945          var next_option_values_select = $("#"+id_prefix+next_option_name_index);
946          var next_selected_option_value_id = next_option_values_select.val();
947
948          var options = "";
949          $.each(option_values, function(){
950            var selected = "";
951            if (this.id == next_selected_option_value_id) selected = "selected='selected'";
952            options += "<option value='"+this.id+"' "+ selected +">"+this.title+"</option>";
953          });
954
955          next_option_values_select.html(options);
956          next_option_values_select.trigger('change');
957        });
958
959        $("#"+id_prefix+0).trigger('change');
960      }
961
962      this.getUrlParameter = function(name) {
963        return decodeURI((RegExp(name + '=' + '(.+?)(&|$)').exec(location.search) || [0, null])[1]);
964      };
965
966      // если вариант указан в URL, выбираем его
967      this.selectVariantFromUrl = function() {
968        var variant_id = this.getUrlParameter('variant_id');
969        if (variant_id == null) return;
970
971        // if(variant_id != 'null') {
972        //   $('select option[value="'+ this.getUrlParameter('variant_id') +'"]').attr('selected', 'selected').change();
973        // }
974
975        var variant = this.product.variantById(variant_id);
976        if (variant == null) return;
977
978        for (var i in variant.option_values) {
979          var opt_id = variant.option_values[i].id;
980          $(this.variantIdField).parent().find('select option[value="' + opt_id + '"]').attr('selected', 'selected');
981        }
982
983        this.updateSelectors();
984      };
985
986      this.selectorDivClass       = 'selector-wrapper';
987      this.selectorClass          = 'single-option-selector';
988      this.variantIdFieldIdSuffix = '-variant-id';
989      this.optionIdFieldIdPrefix  = 'selector-option-id-';
990
991      this.variantIdField    = null;
992      this.selectors         = [];
993      this.domIdPrefix       = existingSelectorId;
994      this.product           = new InSales.Product(options['product']);
995
996      this.onVariantSelected = InSales.isDefined(options.onVariantSelected) ? options.onVariantSelected : function(){};
997
998      this.replaceSelector(existingSelectorId); // create the dropdowns
999
1000      this.selectAvailableVariant();
1001      this.selectVariantFromUrl();
1002
1003      if (options['filterOptionValues']) this.filterOptionValues(); // Поочерёдная фильтрация значения св-в.
1004
1005      return true;
1006    };
1007
1008
1009    InSales.SingleOptionSelector = function(multiSelector, index, option, values) {
1010      this.selectValue = function(value) {
1011        $(this.element).find('option[value="'+value+'"]').each(function(){
1012          $(this).attr("selected","selected")
1013        });
1014      };
1015
1016      this.multiSelector = multiSelector;
1017      this.values = values;
1018      this.index = index;
1019      this.name = option['title'];
1020      this.option_id = option['id'];
1021      this.element = document.createElement('select');
1022      for (var i = 0; i < values.length; i++) {
1023        var opt = document.createElement('option');
1024        opt.value = values[i].id;
1025        opt.innerHTML = values[i].title;
1026        this.element.appendChild(opt);
1027      }
1028      this.element.setAttribute('class', this.multiSelector.selectorClass);
1029      this.element.id = multiSelector.domIdPrefix + '-option-' + index;
1030      this.element.onchange = function() {
1031        multiSelector.updateSelectors(index);
1032      };
1033
1034      return true;
1035    };
1036
1037    return InSales;
1038  })(jQuery));
1039}
1040
1041// сабмит формы отзыва
1042jQuery(function($) {
1043  $("#feedback_commit").click(function(e){
1044    e.preventDefault();
1045    var form = $('#feedback_form');
1046    var fields = form.serialize();
1047    $.ajax({
1048      url:      form.attr('action') + '.json',
1049      type:     'post',
1050      data:     fields,
1051      dataType: 'json',
1052      beforeSend: function() {show_preloader();},
1053      complete: function() {hide_preloader();},
1054      success:  function(response) {
1055        if ( response.status == 'ok' ) {
1056          $("textarea[name='feedback[content]']").val("");
1057          var thanks = $("#thanks");
1058          thanks.html(response.notice);
1059          thanks.show();
1060          window.setTimeout(function() {
1061            thanks.fadeOut("slow", function(){thanks.hide();});
1062          }, 6000);
1063        } else {
1064          alert(errors_to_arr(response.errors).join("\n"));
1065        }
1066      }
1067    });
1068  });
1069})
1070
1071;

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.