PageSourceSearch

https://profdistribution.com/catalog/view/javascript/simplecheckout.js?v=4.11.8

js profdistribution.com collected 2026-10-08 17:32:04 UTC 86,488 bytes, 2,249 lines download raw bytes

1(function($) {
2    window.Simplecheckout = function(params) {
3        this.params = params;
4
5        this.callback = params.javascriptCallback || function() {};
6
7        this.selectors = {
8            paymentForm: "#simplecheckout_payment_form",
9            paymentButtons: "#simplecheckout_payment_form .buttons:last",
10            step: ".simplecheckout-step",
11            buttons: "#buttons",
12            buttonPrev: "#simplecheckout_button_prev",
13            buttonNext: "#simplecheckout_button_next",
14            buttonCreate: "#simplecheckout_button_confirm",
15            buttonBack: "#simplecheckout_button_back",
16            stepsMenu: "#simplecheckout_step_menu",
17            stepsMenuItem: ".simple-step",
18            stepsMenuDelimiter: ".simple-step-delimiter",
19            stepsMenuTop: ".simplecheckout-top-menu",
20            stepsMenuBottom: ".simplecheckout-bottom-menu",
21            stepsMenuVerticalItem: ".simple-step-vertical",
22            proceedText: "#simplecheckout_proceed_payment",
23            agreementCheckBox: "#agreement_checkbox",
24            agreementWarning: "#agreement_warning",
25            block: ".simplecheckout-block",
26            overlay: ".simplecheckout_overlay"
27        };
28
29        this.classes = {
30            stepsMenuCompleted: "simple-step-completed",
31            stepsMenuCurrent: "simple-step-current",
32            stepsMenuVerticalCompleted: "simple-step-vertical-completed"
33        };
34
35        this.blocks = [];
36        this.$steps = [];
37        this.requestTimerId = 0;
38        this.currentStep = 1;
39        this.saveStepNumber = this.currentStep;
40        this.stepReseted = false;
41        this.isCreateOrderClicked = false;
42        this.backCount = -1;
43        this.$paymentForm = false;
44        this.storageUsed = false;
45        this.paymentFormObserver = null;
46
47        var checkIsInContainer = function($element, selector) {
48            if ($element.parents(selector).length) {
49                return true;
50            }
51            return false;
52        };
53
54        this.callFunc = function(func, $target) {
55            var self = this;
56
57            if (func && typeof self[func] === "function") {
58                self[func]($target);
59            } else if (func) {
60                //console.log(func + " is not registered");
61            }
62        };
63
64        this.registerBlock = function(object) {
65            var self = this;
66            object.setParent(self);
67            self.blocks.push(object);
68        };
69
70        this.initBlocks = function() {
71            var self = this;
72            for (var i in self.blocks) {
73                if (!self.blocks.hasOwnProperty(i)) continue;
74
75                self.blocks[i].init();
76            }
77        };
78
79        this.init = function(disableScroll, changeStep) {
80            var self = this;
81
82            if (typeof disableScroll === "undefined") {
83                disableScroll = false;
84            }
85
86            var callbackForComplexField = function($target) {
87                self.focusedFieldId = ''
88                var func = $target.attr("data-onchange");
89                if (!func) {
90                    func = $target.attr("data-onchange-delayed");
91                }
92                if (func && typeof self[func] === "function") {
93                    self[func]($target);
94                } else if (func) {
95                    //console.log(func + " is not registered");
96                }
97                self.setDirty();
98            };
99
100            self.requestTimerId = 0;
101
102            if (!self.isPaymentFormEmpty()) {
103                if (self.$paymentForm) {
104                    self.restorePaymentForm(self.$paymentForm);
105                }
106            } else {
107                self.$paymentForm = false;
108            }
109
110            if (self.params.useAutocomplete) {
111                self.initAutocomplete(callbackForComplexField);
112            }
113
114            self.checkIsHuman();
115            self.addObserver();
116            self.initPopups();
117            self.initMasks();
118            self.initTooltips(!self.params.useAutocomplete);
119            self.initDatepickers(callbackForComplexField);
120            self.initTimepickers(callbackForComplexField);
121            self.initSelect2();
122            self.initFileUploader(function() {
123                self.overlayAll();
124            }, function() {
125                self.removeOverlays();
126                self.setDirty();
127            });
128            self.initHandlers();
129            self.initBlocks();
130            self.initSteps(changeStep);
131
132            self.initAbandonedCart();
133
134            if (!disableScroll) {
135                self.scroll();
136            }
137
138            self.initValidationRules();
139
140            if (!self.isPaymentFormEmpty() && self.useReloadingOfPaymentForm()) {
141                self.initReloadingOfPaymentForm();
142            }
143
144            this.syncShippingAndPaymentAddresses();
145
146            if (typeof self.callback === "function") {
147                try {
148                    self.callback();
149                } catch (e) {
150                    console.error(e);
151                }
152            }
153
154            this.checkRedirect();
155
156            $(window).on("unload", function() {
157                var doSomething = 1;
158                doSomething++;
159            });        
160        };
161
162        this.useReloadingOfPaymentForm = function() {
163            if (typeof this.params.enableAutoReloaingOfPaymentFrom !== 'undefined' && this.params.enableAutoReloaingOfPaymentFrom) {
164                if (typeof window.simpleTypingSpeed !== 'undefined' && window.simpleTypingSpeed > 1000) {
165                    return false;
166                }
167
168                return true;
169            }
170
171            return false;
172        },
173
174        this.initHandlers = function() {
175            var self = this;
176            $(self.params.mainContainer).find("*[data-onchange], *[data-onclick]").each(function() {
177                var bind = true,
178                    $element = $(this);
179
180                for (var i in self.blocks) {
181                    if (!self.blocks.hasOwnProperty(i)) continue;
182
183                    if (checkIsInContainer($element, self.blocks[i].currentContainer)) {
184                        bind = false;
185                        break;
186                    }
187                }
188
189                if (bind) {
190                    var funcOnChange = $element.attr("data-onchange");
191
192                    if (funcOnChange) {
193                        $element.on("change", function() {
194                            self.setDirty($(this));
195                            self.callFunc(funcOnChange, $element);
196                        });
197                    }
198
199                    var funcOnClick = $element.attr("data-onclick");
200
201                    if (funcOnClick) {
202                        $element.on("click", function() {
203                            if ($(this).attr("disabled")) {
204                                return;
205                            }
206
207                            var confirmText = $(this).attr("data-confirm-text");
208
209                            self.setDirty();
210                            
211                            if (!confirmText || (confirmText && confirm(confirmText))) {
212                                self.callFunc(funcOnClick, $element);
213                            }
214                        });
215                    }
216                }
217            });
218        };
219
220        this.skipKey = function(keyCode) {
221            if ($.inArray(keyCode,[9,13,16,17,18,19,20,27,35,36,37,38,39,40,91,93,224]) > -1) {
222                return true;
223            }
224
225            return false;
226        };
227
228        this.addObserver = function() {
229            var self = this;
230
231            $(self.params.mainContainer).find("input[type=radio], input[type=checkbox], select").on("change", function() {
232                if (!checkIsInContainer($(this), self.selectors.paymentForm) && !checkIsInContainer($(this), "#simplecheckout_login") && !checkIsInContainer($(this), "#slogin")) {
233                    self.setDirty($(this));
234                }
235            });
236
237            $(self.params.mainContainer).find("input, textarea").on("keydown change paste propertychange keyup input", function(e) {
238                if (self.skipKey(e.keyCode)) {
239                    return;
240                }
241
242                if (!checkIsInContainer($(this), self.selectors.paymentForm) && !checkIsInContainer($(this), "#simplecheckout_login") && !checkIsInContainer($(this), "#slogin")) {
243                    self.setDirty($(this));
244                }
245            });
246        };
247
248        this.initReloadingOfPaymentForm = function() {
249            var self = this;
250
251            var reload = function(disableScroll) {
252                var $field = $(this);
253
254                if (typeof disableScroll === "undefined") {
255                    disableScroll = false;
256                }
257
258                if (!checkIsInContainer($field, self.selectors.paymentForm)) {
259                    self.validate(true).then(function(result) {
260                        if (result) {
261                            self.reloadAll(undefined, disableScroll);
262                        }
263                    });
264                }
265            };
266
267            $(self.params.mainContainer).find("input[data-simple-mask][data-reload-payment-form], input[type=radio][data-reload-payment-form], input[type=checkbox][data-reload-payment-form], select[data-reload-payment-form], input[type=date][data-reload-payment-form], input[type=time][data-reload-payment-form]").on("change", reload);
268
269            var timeoutId = 0;
270
271            $(self.params.mainContainer).find("input[type=text][data-reload-payment-form]:not([data-simple-mask]), input[type=email][data-reload-payment-form]:not([data-simple-mask]), input[type=tel][data-reload-payment-form]:not([data-simple-mask]), textarea[data-reload-payment-form]").on("keydown", function(e) {
272                if (self.skipKey(e.keyCode)) {
273                    return;
274                }
275
276                if (timeoutId) {
277                    clearTimeout(timeoutId);
278                }
279
280                timeoutId = window.setTimeout(function() {
281                    clearTimeout(timeoutId);
282
283                    if ($(self.params.mainContainer).data("timeoutId") != timeoutId) {
284                        return;
285                    }
286
287                    reload(true);
288                }, 500);
289
290                $(self.params.mainContainer).data("timeoutId", timeoutId);
291            });
292        };
293
294        this.savePaymentForm = function() {
295            var self = this;
296
297            if (!self.isPaymentFormEmpty()) {
298                self.$paymentForm = $(self.params.mainContainer).find(self.selectors.paymentForm).find("input[type=text],select,textarea,input[type=radio]:checked,input[type=checkbox]:checked");
299            } else {
300                self.$paymentForm = false;
301            }
302        };
303
304        this.initStorage = function() {
305            var self = this;
306
307            if (!self.storageUsed) {
308                var needReloading = false;
309
310                $(self.params.mainContainer).find("input[type=text], input[type=email], input[type=tel], select, textarea").each(function() {
311                    var $el = $(this);
312                    var id = $el.attr("id");
313                    var value = localStorage.getItem(id);
314                    
315                    if (id && value) {
316                        if ($el.is("select")) {
317                            if ($el.find("option[value='" + value + "']").length) {
318                                if ($el.val() != value) {
319                                    $el.val(value);
320
321                                    if ($el.attr("data-onchange")) {
322                                        needReloading = true;
323                                    }
324                                }
325                            }
326                        } else {
327                            if ($el.val() != value) {
328                                $el.val(value);
329
330                                if ($el.attr("data-onchange")) {
331                                    needReloading = true;
332                                }
333                            }
334                        }
335                    }
336                });
337
338                if (needReloading) {
339                    self.reloadAll();
340                }
341
342                self.storageUsed = true;
343            }
344
345            if (!$(self.params.mainContainer).attr("data-logged")) {
346                $(self.params.mainContainer).find("input[type=text], input[type=email], input[type=tel], select, textarea").on("change", function() {
347                    var $el = $(this);
348
349                    if (checkIsInContainer($el, "#simplecheckout_customer") || checkIsInContainer($el, "#simplecheckout_shipping_address") || checkIsInContainer($el, "#simplecheckout_payment_address")) {
350                        localStorage.setItem($el.attr("id"), $el.val());
351                    }
352                });
353            }
354        }
355
356        this.checkRedirect = function() {
357            var $redirect = $(this.params.mainContainer).find('#simple_redirect_url');
358
359            if ($redirect.length) {
360                window.location = $redirect.val();
361            }
362        }
363
364        this.restorePaymentForm = function($oldForm) {
365            var self = this;
366            var $paymentForm = $(self.params.mainContainer).find(self.selectors.paymentForm);
367
368            $oldForm.each(function() {
369                var $field = $(this);
370                var name = $field.attr("name");
371                var id = $field.attr("id");
372                var value = $field.val();
373
374                if ($field.is("input[type=text]") || $field.is("select") || $field.is("textarea")) {
375                    if (name) {
376                        $paymentForm.find("[name='" + name + "']").val(value);
377                    } else if (id) {
378                        $paymentForm.find("#" + id).val(value);
379                    }
380                }
381
382                if ($field.is("input[type=radio]") || $field.is("input[type=checkbox]")) {
383                    if (name) {
384                        $paymentForm.find("[name='" + name + "'][value='" + value + "']").attr("checked", "checked");
385                    } else if (id) {
386                        $paymentForm.find("#" + id + "[value='" + value + "']").attr("checked", "checked");
387                    }
388                }
389            });
390
391            delete $oldForm;
392        };
393
394        this.initAbandonedCart = function() {
395            var self = this;
396            var $mainContainer = $(self.params.mainContainer);
397            
398            $mainContainer.find("input:not([data-onchange=reloadAll],[data-onclick=reloadAll]), select:not([data-onchange=reloadAll],[data-onclick=reloadAll]), textarea:not([data-onchange=reloadAll],[data-onclick=reloadAll])").on('change', function() {
399                $.ajax({
400                    url: "index.php?" + self.params.additionalParams + "route=checkout/simplecheckout/abandoned",
401                    data: self.createPostData(),
402                    type: "POST",
403                    dataType: "text"                    
404                });
405            });
406        };
407
408        this.setDirty = function($element) {
409            var self = this;
410            var $mainContainer = $(self.params.mainContainer);
411
412            self.stopPaymentFormObserving();
413
414            if (self.useReloadingOfPaymentForm()) {
415                self.savePaymentForm();
416
417                if ($element && ($element.attr("data-reload-payment-form") || ($element.attr("data-onchange") && $element.attr("data-onchange") == "reloadAll"))) {
418                    $mainContainer.find(self.selectors.paymentForm).attr("data-invalid", "true").find("input,select,textarea").attr("disabled", "disabled");
419                }
420            } else {
421                $mainContainer.find(self.selectors.paymentForm).attr("data-invalid", "true").empty();
422            }
423
424            $mainContainer.find("*[data-payment-button=true]").remove();    
425            $mainContainer.find(self.selectors.proceedText).hide();
426
427            self.isCreateOrderClicked = false;
428            
429            if (self.currentStep == self.stepsCount) {
430                $mainContainer.find(self.selectors.buttons).show();
431                $mainContainer.find(self.selectors.buttonCreate).show();
432            }
433        };
434
435        this.preventOrderDeleting = function(callback) {
436            var self = this;
437            $.get("index.php?" + self.params.additionalParams + "route=" + self.params.mainRoute + "/prevent_delete", function() {
438                if (typeof callback === "function") {
439                    callback();
440                }
441            });
442        };
443
444        this.clickOnConfirmButton = function() {
445            var self = this;
446            var $mainContainer = $(self.params.mainContainer);
447            var $paymentForm = $mainContainer.find(self.selectors.paymentForm);
448
449            if (self.isPaymentFormEmpty()) {
450                return;
451            }
452
453            var gatewayLink = $paymentForm.find(".buttons a:last").attr("href");
454            var $submitButton = $paymentForm.find(".buttons input[type=button]:last,.buttons input[type=submit]:last,.buttons input[type=image]:last,.buttons button:last,.buttons a.button:last[href='#'],.buttons a.btn:last[href='#'],.buttons a.button:last[href^='javascript'],.buttons a.btn:last[href^='javascript'],.buttons a.button:last:not([href]),.buttons a.btn:last:not([href])");
455            var $lastButton = $paymentForm.find("input[type=button]:last,input[type=submit]:last,input[type=image]:last,button:last");
456            var lastLink = $paymentForm.find("a:last").attr("href");
457
458            var disableButton = function() {
459                $mainContainer.find(self.selectors.buttonCreate).attr("disabled", "disabled");
460            };
461
462            var enableButton = function() {
463                $mainContainer.find(self.selectors.buttonCreate).removeAttr("disabled");
464            };
465
466            if (typeof gatewayLink !== "undefined" && gatewayLink !== "" && gatewayLink !== "#" && gatewayLink !== "javascript://") {
467                disableButton();
468                self.preventOrderDeleting(function() {
469                    enableButton();
470                    window.location = gatewayLink;
471                    self.blockFieldsDuringPayment();
472                    self.proceed();
473                });
474            } else if ($submitButton.length) {
475                if ($submitButton.attr("href") == "#" || $submitButton.attr("href") == "javascript://") {
476                    $submitButton.removeAttr("href");
477                }
478                disableButton();
479                self.preventOrderDeleting(function() {
480                    enableButton();
481                    if (!$submitButton.attr("disabled")) {
482                        $submitButton.mousedown().click();
483                        self.blockFieldsDuringPayment($submitButton);
484                        self.proceed();
485                    }
486                });
487            } else if ($lastButton.length) {
488                disableButton();
489                self.preventOrderDeleting(function() {
490                    enableButton();
491                    if (!$lastButton.attr("disabled")) {
492                        $lastButton.mousedown().click();
493                        self.blockFieldsDuringPayment($lastButton);
494                        self.proceed();
495                    }
496                });
497            } else if (typeof lastLink !== "undefined" && lastLink !== "" && lastLink !== "#" && lastLink !== "javascript://") {
498                disableButton();
499                self.preventOrderDeleting(function() {
500                    enableButton();
501                    window.location = lastLink;
502                    self.blockFieldsDuringPayment();
503                    self.proceed();
504                });
505            }
506        };
507
508        this.isPaymentFormValid = function() {
509            var self = this;
510            return !self.isPaymentFormEmpty() && !$(self.params.mainContainer).find(self.selectors.paymentForm).attr("data-invalid") ? true : false;
511        };
512
513        this.isPaymentFormVisible = function() {
514            var self = this;
515            var $paymentForm = $(self.params.mainContainer).find(self.selectors.paymentForm);
516            var $elements = $paymentForm.find(":visible:not(form)");
517            var paymentFormHeight = $paymentForm.height();
518            var ignoreHeight = 50;
519
520            return !self.isPaymentFormEmpty() && $elements.length > 0 && paymentFormHeight > ignoreHeight ? true : false;
521        };
522
523        this.isPaymentFormEmpty = function() {
524            var self = this;
525            var $paymentForm = $(self.params.mainContainer).find(self.selectors.paymentForm);
526
527            return $paymentForm.length && $paymentForm.find("*").length > 0 ? false : true;
528        };
529
530        this.startPaymentFormObserving = function() {
531            var self = this;
532            var $paymentForm = $(self.params.mainContainer).find(self.selectors.paymentForm);
533
534            if ($paymentForm.length) {
535                try {
536                    var counter = 0;
537                    var threshold = 3;
538
539                    self.paymentFormObserver = new MutationObserver(function(mutation) {
540                        if (counter < threshold) {
541                            self.replaceCreateButtonWithConfirm();
542                        }
543
544                        counter += 1;
545                    });
546                    
547                    self.paymentFormObserver.observe($paymentForm.get(0), {
548                        childList: true,
549                        subtree: true,
550                        characterData: true,
551                        attributes: false
552                    });
553                } catch (e) {
554
555                }
556            }
557        };
558
559        this.stopPaymentFormObserving = function() {
560            if (this.paymentFormObserver) {
561                this.paymentFormObserver.disconnect();
562                this.paymentFormObserver = null;
563            }
564        };
565
566        this.replaceCreateButtonWithConfirm = function() {
567            var self = this;
568            var $mainContainer = $(self.params.mainContainer);
569            var $paymentForm = $(self.params.mainContainer).find(self.selectors.paymentForm);
570
571            if (self.isPaymentFormEmpty()) {
572                return;
573            }
574
575            var $gatewayLink = $paymentForm.find(".buttons a:last");
576            var $submitButton = $paymentForm.find(".buttons input[type=button]:last,.buttons input[type=submit]:last,.buttons input[type=image]:last,.buttons button:last,.buttons a.button:last[href='#'],.buttons a.btn:last[href='#'],.buttons a.button:last[href^='javascript'],.buttons a.btn:last[href^='javascript'],.buttons a.button:last:not([href]),.buttons a.btn:last:not([href])");
577            var $lastButton = $paymentForm.find("input[type=button]:last,input[type=submit]:last,input[type=image]:last,button:last");
578            var $lastLink = $paymentForm.find("a:last");
579
580            var $obj = false;
581
582            if ($gatewayLink.length) {
583                $obj = $gatewayLink;
584            } else if ($submitButton.length) {
585                $obj = $submitButton;
586            } else if ($lastButton.length) {
587                $obj = $lastButton;
588            } else if ($lastLink.length) {
589                $obj = $lastLink;
590            }
591
592            if ($obj) {
593                if ($obj.attr("href") == "#" || $obj.attr("href") == "javascript://") {
594                    $obj.removeAttr("href");
595                }
596
597                var $clone = $obj.clone(false).removeAttr("onclick").addClass("btn button");
598
599                $mainContainer.find("*[data-payment-button=true]").remove();
600                $mainContainer.find(self.selectors.buttonCreate).hide().before($clone);
601
602                $clone
603                    .attr("data-payment-button", "true")
604                    .bind("mousedown", function() {
605                        if ($obj.attr("disabled")) {
606                            return;
607                        }
608
609                        $obj.mousedown();
610                    })
611                    .bind("click", function() {
612                        if ($obj.attr("disabled")) {
613                            return;
614                        }
615
616                        self.preventOrderDeleting(function() {
617                            self.proceed();
618                            $obj.click();
619                            self.blockFieldsDuringPayment($obj);
620                        });
621                    })
622                    .show();
623
624                $obj.hide();
625            } else {
626                $mainContainer.find(self.selectors.buttons).hide();
627                self.preventOrderDeleting();
628            }
629        };
630
631        this.blockFieldsDuringPayment = function($button) {
632            var self = this;
633
634            self.disableAllFieldsBeforePayment();
635
636            if (typeof $button !== "undefined") {
637                var timerId = setInterval(function() {
638                    if (!$button.attr("disabled")) {
639                        self.enableAllFieldsAfterPayment();
640                        clearInterval(timerId);
641                    }
642                }, 250);
643            }
644        };
645
646        this.disableAllFieldsBeforePayment = function() {
647            var self = this;
648
649            $(self.params.mainContainer).find(self.selectors.block).each(function() {
650                if ($(this).attr("id") == "simplecheckout_payment_form") {
651                    return;
652                }
653                $(this).find("input,select,textarea").attr("disabled", "disabled");
654                $(this).find("[data-onclick]").attr("disabled", "disabled");
655            });
656        };
657
658        this.enableAllFieldsAfterPayment = function() {
659            var self = this;
660
661            $(self.params.mainContainer).find(self.selectors.block).each(function() {
662                if ($(this).attr("id") == "simplecheckout_payment_form") {
663                    return;
664                }
665                $(this).find("input:not([data-dummy]),select,textarea").removeAttr("disabled");
666                $(this).find("[data-onclick]").removeAttr("disabled");
667            });
668        };
669
670        this.proceed = function() {
671            var self = this;
672            if (self.params.displayProceedText && !self.isPaymentFormVisible()) {
673                $(self.params.mainContainer).find(self.selectors.proceedText).show();
674            }
675        };
676
677        this.gotoStep = function($target) {
678            var self = this;
679            var step = $target.attr("data-step");
680            if (step < self.currentStep) {
681                $.when(self.hideCurrentStep()).then(function() {
682                    self.currentStep = step;
683                    self.saveStepNumber = step;
684                    self.setDirty();
685                    self.displayCurrentStep(true);
686                });
687            } else if (step > self.currentStep) {
688                self.nextStep($target);
689            }
690        };
691
692        this.previousStep = function($target) {
693            var self = this;
694            if (self.currentStep > 1) {
695                $.when(self.hideCurrentStep()).then(function() {
696                    self.currentStep--;
697                    self.saveStepNumber--;
698                    self.setDirty();
699                    self.displayCurrentStep(true);
700                });
701            }
702        };
703
704        this.nextStep = function($target) {
705            var self = this;
706
707            if ($target.data("clicked")) {
708                return;
709            }
710
711            $target.data("clicked", true);
712
713            self.validate(false).then(function(result) {
714                if (result) {
715                    if (self.currentStep < self.$steps.length) {
716                        self.currentStep++;
717                    }
718
719                    self.hideCurrentStep();
720                    
721                    self.submitForm(true);
722                } else {
723                    if (typeof toastr !== 'undefined' && self.params.notificationCheckForm) {
724                        toastr.error(self.params.notificationCheckFormText);
725                    }
726
727                    $target.data("clicked", false);
728
729                    self.scroll();
730                }
731            });
732        };
733
734        this.saveStep = function() {
735            var self = this;
736            if (self.currentStep) {
737                $(self.params.mainContainer).append($("<input/>").attr("type", "hidden").attr("name", "next_step").val(self.currentStep));
738            }
739        };
740
741        this.ignorePost = function() {
742            var self = this;
743            $(self.params.mainContainer).append($("<input/>").attr("type", "hidden").attr("name", "ignore_post").val(1));
744        };
745
746        this.addSystemFieldsInForm = function() {
747            var self = this;
748            if (self.isCreateOrderClicked) {
749                $(self.params.mainContainer).append($("<input/>").attr("type", "hidden").attr("name", "create_order").val(1));
750            }
751            if (self.currentStep) {
752                $(self.params.mainContainer).append($("<input/>").attr("type", "hidden").attr("name", "next_step").val(self.currentStep));
753            }
754        };
755
756        this.getAgreementCheckboxStep = function() {
757            var step = typeof this.params.agreementCheckboxStep !== "undefined" && this.params.agreementCheckboxStep !== "" ? (this.params.agreementCheckboxStep + 1) : this.stepsCount - 1;
758
759            if (step > this.stepsCount - 1) {
760                step = this.stepsCount - 1;
761            }
762
763            return step;
764        }
765
766        this.setLocationHash = function(hash) {
767            window.location.hash = hash;
768            this.backCount--;
769        };
770
771        this.initSteps = function(changeStep) {
772            var self = this;
773            var i = 1;
774            var $mainContainer = $(self.params.mainContainer);
775            var $steps = $mainContainer.find(self.selectors.step);
776
777            self.stepReseted = false;
778            self.$steps = [];
779            self.stepsCount = $steps.length || 1;
780
781            $steps.each(function() {
782                var $step = $(this);
783                self.$steps.push($step);
784                // check steps before current for errors and set step with error as current
785                var $errorBlocks = $step.find(self.selectors.block + "[data-error=true]");
786                if (i < self.currentStep && $errorBlocks.length) {
787                    self.currentStep = i;
788                    self.stepReseted = true;
789                }
790                i++;
791            });
792
793            if (self.stepsCount > 1 && !self.stepReseted && (self.currentStep == self.stepsCount || self.currentStep > self.getAgreementCheckboxStep()) && $mainContainer.attr("data-error") == "true") {
794                self.currentStep--;
795                self.stepReseted = true;
796            }
797
798            //a fix for case when some steps are suddenly hidden after aja
798x request
799            if (self.stepsCount > 1 && !self.stepReseted && self.currentStep > self.stepsCount) {
800                self.currentStep = self.stepsCount;
801            }
802
803            $mainContainer.find(self.selectors.paymentButtons).hide();
804
805            if (!self.isPaymentFormVisible()) {
806                $mainContainer.find(self.selectors.paymentForm).css({
807                    "margin": "0",
808                    "padding": "0"
809                });
810            }
811
812            self.displayCurrentStep(changeStep);
813        };
814
815        this.hideCurrentStep = function() {
816            var self = this;
817            var $mainContainer = $(self.params.mainContainer);
818            
819            $mainContainer.find(self.selectors.agreementWarning).hide();
820                
821            if (typeof self.params.menuType !== "undefined" && self.params.menuType == 2) {
822                $mainContainer.find(self.selectors.buttons).hide();
823                return $mainContainer.find(self.selectors.step).slideUp("slow");
824            } else {
825                //return $mainContainer.find(self.selectors.step).hide();
826            }          
827        };
828
829        this.displayCurrentStep = function(changeStep) {
830            var self = this;
831            var $mainContainer = $(self.params.mainContainer);
832
833            var initButtons = function() {
834                if (self.stepsCount > 1) {
835                    if (self.currentStep == 1) {
836                        $mainContainer.find(self.selectors.buttonPrev).hide();
837                    } else {
838                        $mainContainer.find(self.selectors.buttonBack).hide();
839                    }
840
841                    if (self.currentStep < self.stepsCount) {
842                        $mainContainer.find(self.selectors.buttonNext).show();
843                        $mainContainer.find(self.selectors.buttonCreate).hide();                    
844                    }
845
846                    $mainContainer.find(self.selectors.agreementCheckBox).hide();
847
848                    if (self.currentStep == self.getAgreementCheckboxStep()) {
849                        $mainContainer.find(self.selectors.agreementCheckBox).show();
850                    }
851                }
852
853                if (typeof self.params.stepButtons !== 'undefined' && typeof self.params.stepButtons[self.currentStep] !== 'undefined' && self.params.stepButtons[self.currentStep] != '') {
854                    var $button = $mainContainer.find(self.stepsCount > 1 ? self.selectors.buttonNext : self.selectors.buttonCreate);
855
856                    if ($button.find('span').length) {
857                        $button = $button.find('span');
858                    }
859
860                    $button.html(self.params.stepButtons[self.currentStep]);
861                }  
862
863                if (self.currentStep == self.stepsCount) {
864                    $mainContainer.find(self.selectors.buttonNext).hide();
865                    self.replaceCreateButtonWithConfirm();
866                    self.startPaymentFormObserving();
867                }
868            };
869
870            var initStepsMenu = function() {
871                if (typeof self.params.menuType !== "undefined" && self.params.menuType == 2) {
872                    for (var i = 1; i < self.stepsCount + 1; i++) {
873                        var $topItem = $mainContainer.find(self.selectors.stepsMenuTop + " " + self.selectors.stepsMenuVerticalItem + "[data-step=" + i + "]");
874                        var $bottomItem = $mainContainer.find(self.selectors.stepsMenuBottom + " " + self.selectors.stepsMenuVerticalItem + "[data-step=" + i + "]");
875                        
876                        if (i <= self.currentStep) {
877                            $topItem.show();
878                            $topItem.addClass(self.classes.stepsMenuVerticalCompleted);
879                            $bottomItem.hide();
880                        } else {
881                            $topItem.removeClass(self.classes.stepsMenuVerticalCompleted);
882                            $topItem.hide();
883                            $bottomItem.show();
884                        }
885                    }
886                } else {
887                    $mainContainer.find(self.selectors.stepsMenu + " " + self.selectors.stepsMenuItem).removeClass(self.classes.stepsMenuCompleted).removeClass(self.classes.stepsMenuCurrent);
888                    $mainContainer.find(self.selectors.stepsMenu + " " + self.selectors.stepsMenuDelimiter + " img").attr("src", self.params.additionalPath + self.resources.next);
889
890                    for (var i = 1; i < self.currentStep; i++) {
891                        $mainContainer.find(self.selectors.stepsMenu + " " + self.selectors.stepsMenuItem + "[data-step=" + i + "]").addClass(self.classes.stepsMenuCompleted);
892                        $mainContainer.find(self.selectors.stepsMenu + " " + self.selectors.stepsMenuDelimiter + "[data-step=" + (i + 1) + "] img").attr("src", self.params.additionalPath + self.resources.nextCompleted);
893                    }
894
895                    $mainContainer.find(self.selectors.stepsMenu + " " + self.selectors.stepsMenuItem + "[data-step=" + self.currentStep + "]").addClass(self.classes.stepsMenuCurrent);
896                }
897            };
898
899            var hideAllSteps = function() {
900                $mainContainer.find(self.selectors.step).hide();
901            };
902
903            var isLastStepHasOnlyPaymentForm = function() {
904                var $lastStep = $mainContainer.find(self.selectors.step + ":last");
905                return $lastStep.find(self.selectors.block).length == 1 && $lastStep.find(self.selectors.paymentForm).length == 1 ? true : false;
906            };
907
908            hideAllSteps();
909
910            if (typeof self.$steps[self.currentStep - 1] !== "undefined") {
911                if (changeStep) {
912                    if (typeof self.params.menuType !== "undefined" && self.params.menuType == 2) {
913                        self.$steps[self.currentStep - 1].slideDown("slow");
914                        $mainContainer.find(self.selectors.buttons).show();
915                    } else {
916                        self.$steps[self.currentStep - 1].show();
917
918                    }
919                } else {
920                    self.$steps[self.currentStep - 1].show();
921                }                
922            }
923
924            if (self.stepsCount > 1) {
925                self.setLocationHash("step_" + self.currentStep);
926            }
927
928            initStepsMenu();
929            initButtons();
930
931            if (self.currentStep == self.stepsCount && !self.isPaymentFormVisible() && self.isPaymentFormValid() && (isLastStepHasOnlyPaymentForm(
931) || self.isCreateOrderClicked)) {
932                self.clickOnConfirmButton();
933                if (isLastStepHasOnlyPaymentForm()) {
934                    self.currentStep--;
935                }
936            }
937        };
938
939        this.scroll = function() {
940            var self = this,
941                error = false,
942                top = 10000,
943                bottom = 0;
944
945            var $mainContainer = $(self.params.mainContainer);
946
947            var isOutsideOfViewport = function(y) {
948                if (y < $(window).scrollTop() || y > ($(window).scrollTop() + $(window).height())) {
949                    return true;
950                }
951                return false;
952            };
953
954            if (self.params.popup) {
955                return;
956            }
957
958            if (self.params.scrollToError) {
959                $($mainContainer.find("[data-error=true]:visible")).each(function() {
960                    var offset = $(this).offset();
961                    if (offset.top < top) {
962                        top = offset.top;
963                    }
964                    if (offset.bottom > bottom) {
965                        bottom = offset.bottom;
966                    }
967                });
968
969                $($mainContainer.find(".simplecheckout-warning-block:visible")).each(function() {
970                    var offset = $(this).offset();
971                    if (offset.top < top) {
972                        top = offset.top;
973                    }
974                    if (offset.bottom > bottom) {
975                        bottom = offset.bottom;
976                    }
977                });
978
979                $($mainContainer.find(".simplecheckout-rule:visible")).each(function() {
980                    if ($(this).parents(".simplecheckout-block").length) {
981                        var offset = $(this).parents(".simplecheckout-block").offset();
982                        if (offset.top < top) {
983                            top = offset.top;
984                        }
985                        if (offset.bottom > bottom) {
986                            bottom = offset.bottom;
987                        }
988                    }
989                });
990
991                if (top < 10000 && isOutsideOfViewport(top)) {
992                    $("html, body").animate({
993                        scrollTop: top
994                    }, "slow");
995                    error = true;
996                } else if (bottom && isOutsideOfViewport(bottom)) {
997                    $("html, body").animate({
998                        scrollTop: bottom
999                    }, "slow");
1000                    error = true;
1001                }
1002            }
1003
1004            if ($mainContainer.find(self.selectors.stepsMenu).length && self.currentStep != self.saveStepNumber) {
1005                var menu = $mainContainer.find(self.selectors.stepsMenu + " " + "[data-step=" + self.currentStep + "]:visible");
1006
1007                if (menu.length) {
1008                    top = menu.offset().top;
1009
1010                    if (top && isOutsideOfViewport(top)) {
1011                        $("html, body").animate({
1012                            scrollTop: top
1013                        }, "slow");
1014                    }
1015                }
1016            }
1017
1018            var mainContainerTop = $mainContainer.offset().top;
1019            var mainContainerHeight = $mainContainer.outerHeight();
1020
1021            if ((mainContainerTop + mainContainerHeight) < $(window).scrollTop()) {
1022                $("html, body").animate({
1023                    scrollTop: mainContainerTop - 50
1024                }, "slow");
1025            }
1026
1027            if (self.params.scrollToPaymentForm && !error) {
1028                if (self.isPaymentFormVisible()) {
1029                    var formTop = $mainContainer.find(self.selectors.paymentForm).offset().top;
1030                    var formHeight = $mainContainer.find(self.selectors.paymentForm).outerHeight();
1031                    var scrollTo = 0;
1032
1033                    if (formHeight < $(window).height()) {
1034                        if (formTop < $(window).scrollTop()) {
1035                            scrollTo = formTop;
1036                        } else if ((formTop + formHeight) > ($(window).scrollTop() + $(window).height())) {
1037                            scrollTo = formTop + formHeight - $(window).height() + 2 * $mainContainer.find(self.selectors.buttons).outerHeight();
1038                        }
1039                    } else {
1040                        if (isOutsideOfViewport(formTop)) {
1041                            scrollTo = formTop;
1042                        }
1043                    }
1044
1045                    if (scrollTo) {
1046                        $("html, body").animate({
1047                            scrollTop: scrollTo
1048                        }, "slow");
1049                    }
1050                }
1051            }            
1052
1053            self.saveStepNumber = self.currentStep;
1054        };
1055
1056        this.validateAgreements = function(silent) {
1057            var self = this;
1058
1059            var result = true;
1060
1061            var $agreementCheckbox = $(self.params.mainContainer).find(self.selectors.agreementCheckBox).find("input[type=checkbox]");
1062            var $agreementCheckboxChecked = $(self.params.mainContainer).find(self.selectors.agreementCheckBox).find("input[type=checkbox]:checked");
1063            var $agreementWarning = $(self.params.mainContainer).find(self.selectors.agreementWarning);
1064
1065            if ($agreementCheckbox.length && $agreementCheckbox.is(":visible") && $agreementCheckbox.length != $agreementCheckboxChecked.length) {
1066                if ($agreementWarning.length) {
1067                    if (!silent) {
1068                        $agreementWarning.attr("data-error", "true");
1069
1070                        if (self.params.notificationDefault) {
1071                            $agreementWarning.show();
1072
1073                            $agreementCheckbox.each(function() {
1074                                if ($(this).is(":checked")) {
1075                                    $(".agreement_" + $(this).val()).hide();
1076                                } else {
1077                                    $(".agreement_" + $(this).val()).show();
1078                                }                        
1079                            });
1080                        }
1081
1082                        if (self.params.notificationToasts) {
1083                            $agreementCheckbox.each(function() {
1084                                if (!$(this).is(":checked")) {
1085                                    toastr.error($(".agreement_" + $(this).val()).text());
1086                                }                        
1087                            });
1088                        }
1089                    }
1090
1091                    result = false;
1092                }
1093            } else {
1094                $agreementWarning.hide().removeAttr("data-error");
1095            }
1096
1097            return result;
1098        };
1099
1100        this.validate = function(silent) {
1101            var self = this;
1102            var result = true;
1103            var promises = [];
1104
1105            if (typeof silent === "undefined") {
1106                silent = false;
1107            }
1108
1109            if (!self.validateAgreements(silent)) {
1110                result = false;
1111            }
1112
1113            for (var i in self.blocks) {
1114                if (!self.blocks.hasOwnProperty(i)) continue;
1115
1116                var promise = self.blocks[i].validate(silent).then(function(validatorResult) {
1117                    if (!validatorResult) {
1118                        result = false;
1119                    }
1120                });
1121
1122                promises.push(promise);
1123            }
1124
1125            if (typeof simpleValidate === 'function') {
1126                if (!simpleValidate()) {
1127                    result = false; 
1128                }
1129            }
1130
1131            var deferred = $.Deferred();
1132
1133            $.when.apply($, promises).then(function() {
1134                deferred.resolve(result);
1135            });
1136
1137            return deferred.promise();
1138        };
1139
1140        this.backHistory = function() {
1141            var self = this;
1142            history.go(self.backCount);
1143        };
1144
1145        this.createOrder = function() {
1146            var self = this;
1147
1148            self.validate(false).then(function(result) {
1149                if (result) {
1150                    self.isCreateOrderClicked = true;
1151                    self.submitForm();
1152                } else {
1153                    if (typeof toastr !== 'undefined' && self.params.notificationCheckForm) {
1154                        toastr.error(self.params.notificationCheckFormText);
1155                    }
1156
1157                    self.scroll();
1158                }
1159            });
1160        };
1161
1162        this.submitForm = function(changeStep) {
1163            var self = this;
1164            self.requestReloadAll(null, changeStep);
1165        };
1166
1167        /**
1168         * Adds delay for reload execution on 150 ms, it allows to check sequence of events and to execute only the last request to handle of more events in one reloading
1169         * @param  {Function} callback
1170         */
1171        this.requestReloadAll = function(callback, changeStep) {
1172            var self = this;
1173            if (self.requestTimerId) {
1174                clearTimeout(self.requestTimerId);
1175                self.requestTimerId = 0;
1176            }
1177            self.requestTimerId = window.setTimeout(function() {
1178                self.reloadAll(callback, false, changeStep);
1179            }, 150);
1180        };
1181
1182        this.overlayAll = function() {
1183            var self = this;
1184            var $mainContainer = $(self.params.mainContainer);
1185
1186            for (var i in self.blocks) {
1187                if (!self.blocks.hasOwnProperty(i)) continue;
1188
1189                self.blocks[i].overlay();
1190            }
1191
1192            $mainContainer.find(self.selectors.block).each(function() {
1193                if (!$(this).data("initialized")) {
1194                    SimplecheckoutBlock.prototype.overlay.apply(self, [$(this)]);
1195                }
1196            });
1197
1198            $mainContainer.find("[data-onclick]").attr("disabled", "disabled");
1199        };
1200
1201        this.removeOverlays = function() {
1202            var self = this;
1203            var $mainContainer = $(self.params.mainContainer);
1204
1205            $mainContainer.find(self.selectors.overlay).remove();
1206            $mainContainer.find("input:not([data-dummy]),select,textarea").removeAttr("disabled");
1207            $mainContainer.find("[data-onclick]").removeAttr("disabled");
1208        };
1209
1210        this.syncShippingAndPaymentAddresses = function() {
1211            //$("#simplecheckout_payment_address").find("input:not([type=hidden]),select,textarea").on("change", )
1212        };
1213
1214        this.createPostData = function() {
1215            var self = this;
1216            var usedBlocks = [];
1217            var fields = [];
1218
1219            var copyFields = function(serializedFields, skipUsed) {
1220                for (var i in serializedFields) {
1221                    if (!serializedFields.hasOwnProperty(i)) continue;
1222
1223                    var info = serializedFields[i];
1224                    var pair = encodeURIComponent(info.name)+"="+encodeURIComponent(info.value);
1225
1226                    if (typeof skipUsed === "undefined" || (skipUsed && $.inArray(pair, fields) == -1)) {
1227                        fields.push(pair);
1228                    }
1229                }
1230            };
1231
1232            $(self.params.mainContainer + " .simplecheckout-step:visible .simplecheckout-block:not(#simplecheckout_payment_form)").each(function() {
1233                var $block = $(this);
1234
1235                if ($block.attr("id")) {
1236                    usedBlocks.push($block.attr("id"));
1237                }
1238
1239                copyFields(self.serializeFields($block));
1240            });
1241
1242            $(self.params.mainContainer + " .simplecheckout-step:not(:visible) .simplecheckout-block:not(#simplecheckout_payment_form)").each(function() {
1243                var $block = $(this);
1244
1245                if ($block.attr("id") && $.inArray($block.attr("id"), usedBlocks) > -1) {
1246                    return;
1247                }
1248
1249                copyFields(self.serializeFields($block));
1250            });
1251
1252            var otherFields = self.serializeFields($(self.params.mainContainer + " *:not(#simplecheckout_payment_form)"));
1253
1254            copyFields(otherFields, true);
1255
1256            var allFields = self.serializeFields($(self.params.mainContainer));
1257
1258            copyFields(allFields, true);
1259
1260            return fields.join("&");
1261        }
1262
1263        /**
1264         * Reload all blocks via main controller which includes all registered blocks as childs
1265         * @param  {Function} callback
1266         */
1267        this.reloadAll = function(callback, disableScroll, changeStep) {
1268            var self = this;
1269
1270            if (self.isReloading) {
1271                return;
1272            }
1273
1274            if (typeof disableScroll === "undefined") {
1275                disableScroll = false;
1276            }
1277
1278            if (typeof changeStep === "undefined") {
1279                changeStep = false;
1280            }
1281
1282            self.addSystemFieldsInForm();
1283            self.isReloading = true;
1284
1285            var postData = self.createPostData();
1286            var overlayTimeoutId = 0;
1287
1288            $.ajax({
1289                url: self.params.mainUrl,
1290                data: postData + "&simple_ajax=1",
1291                type: "POST",
1292                dataType: "text",
1293                beforeSend: function() {
1294                    $('.tooltip ').remove();
1295
1296                    overlayTimeoutId = window.setTimeout(function() {
1297                        if (overlayTimeoutId && !(typeof self.params.menuType !== "undefined" && self.params.menuType == 2 && changeStep)) {
1298                            self.overlayAll();
1299                        }
1300                    }, 250);
1301                },
1302                success: function(data) {
1303                    clearTimeout(overlayTimeoutId);
1304                    overlayTimeoutId = 0;
1305
1306                    var newData = $(self.params.mainContainer, $(data)).get(0);
1307                    if (!newData && data) {
1308                        newData = data;
1309                    }
1310
1311                    try {
1312                        $(self.params.mainContainer).replaceWith(newData);
1313                    } catch (e) {
1314                        console.error(e);
1315                    }
1316
1317                    self.init(disableScroll, changeStep);
1318
1319                    if (typeof callback === "function") {
1320                        callback.call(self);
1321                    }
1322
1323                    self.removeOverlays();
1324                    self.isReloading = false;
1325                },
1326                error: function(xhr, ajaxOptions, thrownError) {
1327                    clearTimeout(overlayTimeoutId);
1328                    overlayTimeoutId = 0;
1329                    self.removeOverlays();
1330                    self.isReloading = false;
1331                }
1332            });
1333        };
1334
1335        this.reloadBlock = function(container, callback) {
1336            var self = this;
1337            if (self.isReloading) {
1338                return;
1339            }
1340            self.isReloading = true;
1341            var postData = $(self.params.mainContainer).find("input,select,textarea").serialize();
1342            $.ajax({
1343                url: self.params.mainUrl,
1344                data: postData + "&simple_ajax=1",
1345                type: "POST",
1346                dataType: "text",
1347                beforeSend: function() {},
1348                success: function(data) {
1349                    var newData = $(container, $(data)).get(0);
1350
1351                    if (!newData && data) {
1352                        newData = data;
1353                    }
1354
1355                    try {
1356                        $(container).replaceWith(newData);
1357                    } catch (e) {
1358                        console.error(e);
1359                    }
1360
1361                    self.init();
1362
1363                    if (typeof callback === "function") {
1364                        callback.call(self);
1365                    }
1366
1367                    self.isReloading = false;
1368                },
1369                error: function(xhr, ajaxOptions, thrownError) {
1370                    self.isReloading = false;
1371                }
1372            });
1373        };
1374
1375        this.registerBlock(new SimplecheckoutCart("#simplecheckout_cart", "checkout/simplecheckout_cart"));
1376        this.registerBlock(new SimplecheckoutShipping("#simplecheckout_shipping", "checkout/simplecheckout_shipping"));
1377        this.registerBlock(new SimplecheckoutPayment("#simplecheckout_payment", "checkout/simplecheckout_payment"));
1378        this.registerBlock(new SimplecheckoutForm("#simplecheckout_customer", "checkout/simplecheckout_customer"));
1379        this.registerBlock(new SimplecheckoutForm("#simplecheckout_payment_address", "checkout/simplecheckout_payment_address"));
1380        this.registerBlock(new SimplecheckoutForm("#simplecheckout_shipping_address", "checkout/simplecheckout_shipping_address"));
1381        this.registerBlock(new SimplecheckoutComment("#simplecheckout_comment", "checkout/simplecheckout_comment"));
1382        this.registerBlock(new SimplecheckoutBlock("#simplecheckout_summary", "checkout/simplecheckout_summary"));
1383
1384        var login = new SimplecheckoutLogin("#simplecheckout_login", "checkout/simplecheckout_login");
1385        login.setParent(this);
1386        login.init();
1387        login.shareMethod("open", "openLoginBox");
1388
1389        this.instances.push(this);
1390    };
1391
1392    Simplecheckout.prototype = inherit(window.Simple.prototype);
1393
1394    /**
1395     * It is parent of all blocks
1396     */
1397
1398    function SimplecheckoutBlock(container, route) {
1399        this.currentContainer = container;
1400        this.currentRoute = route;
1401    }
1402
1403    SimplecheckoutBlock.prototype.setParent = function(object) {
1404        this.simplecheckout = object;
1405        this.params = object.params;
1406        this.resources = object.resources;
1407    };
1408
1409    SimplecheckoutBlock.prototype.reloadAll = function(callback) {
1410        if (this.simplecheckout) {
1411            this.simplecheckout.requestReloadAll(callback, false);
1412        } else {
1413            this.reload();
1414        }
1415    };
1416
1417    SimplecheckoutBlock.prototype.reload = function(callback) {
1418        var self = this;
1419        if (self.isReloading) {
1420            return;
1421        }
1422        self.isReloading = true;
1423        var postData = $(self.params.mainContainer).find(self.currentContainer + ":visible").find("input,select,textarea").serialize();
1424        $.ajax({
1425            url: "index.php?" + self.params.additionalParams + "route=" + self.currentRoute,
1426            data: postData + "&simple_ajax=1",
1427            type: "POST",
1428            dataType: "text",
1429            beforeSend: function() {
1430                self.overlay();
1431            },
1432            success: function(data) {
1433                var newData = $(self.currentContainer, $(data)).get(0);
1434                
1435                if (!newData && data) {
1436                    newData = data;
1437                }
1438
1439                try {
1440                    $(self.params.mainContainer).find(self.currentContainer + ":visible").replaceWith(newData);
1441                } catch (e) {
1442                    console.error(e);
1443                }
1444
1445                if (typeof callback === "function") {
1446                    callback.call(self);
1447                }
1448                
1449                self.removeOverlay();
1450                self.isReloading = false;
1451                self.init();
1452            },
1453            error: function(xhr, ajaxOptions, thrownError) {
1454                self.removeOverlay();
1455                self.isReloading = false;
1456            }
1457        });
1458    };
1459
1460    SimplecheckoutBlock.prototype.load = function(callback, container, params) {
1461        var self = this;
1462        if (self.isLoading) {
1463            return;
1464        }
1465        if (typeof callback !== "function") {
1466            container = callback;
1467            callback = null;
1468        }
1469        if (typeof params === "undefined") {
1470            params = "";
1471        }
1472        self.isLoading = true;
1473        $.ajax({
1474            url: "index.php?" + self.params.additionalParams + "route=" + self.currentRoute + params,
1475            type: "GET",
1476            dataType: "text",
1477            beforeSend: function() {
1478                self.overlay();
1479            },
1480            success: function(data) {
1481                var newData = $(self.currentContainer, $(data)).get(0);
1482
1483                if (!newData && data) {
1484                    newData = data;
1485                }
1486
1487                if (newData) {
1488                    try {
1489                        if (container) {
1490                            $(container).html(newData);
1491                        } else {
1492                            $(self.currentContainer).replaceWith(newData);
1493                        }
1494                    } catch (e) {
1495                        console.error(e);
1496                    }
1497                }
1498
1499                if (typeof callback === "function") {
1500                    callback();
1501                }
1502
1503                self.removeOverlay();
1504                self.isLoading = false;
1505                self.init();
1506            },
1507            error: function(xhr, ajaxOptions, thrownError) {
1508                self.removeOverlay();
1509                self.isLoading = false;
1510            }
1511        });
1512    };
1513
1514    SimplecheckoutBlock.prototype.overlay = function(useBlock) {
1515        var self = this;
1516        var $block = (useBlock && $(useBlock)) || $(self.params.mainContainer).find(self.currentContainer);
1517
1518        if ($block.length) {
1519            if (~~$block.height() < 50) {
1520                return;
1521            }
1522            $block.find("input,select,textarea").attr("disabled", "disabled");
1523            $block.find("[data-onclick]").attr("disabled", "disabled");
1524            $block.append("<div class='simplecheckout_overlay' id='" + $block.attr("id") + "_overlay'></div>");
1525            $block.find(".simplecheckout_overlay")
1526                .css({
1527                    "background": "url(" + self.params.additionalParams + self.resources.loading + ") no-repeat center center",
1528                    "opacity": 0.4,
1529                    "position": "absolute",
1530                    "width": $block.width(),
1531                    "height": $block.height(),
1532                    "z-index": 5000
1533                })
1534                .offset({
1535                    top: $block.offset().top,
1536                    left: $block.offset().left
1537                });
1538        }
1539    };
1540
1541    SimplecheckoutBlock.prototype.removeOverlay = function() {
1542        var self = this;
1543        var $mainContainer = $(self.params.mainContainer);
1544
1545        if (typeof self.currentContainer !== "undefined") {
1546            $mainContainer.find(self.currentContainer).find("input:not([data-dummy]),select,textarea").removeAttr("disabled");
1547            $mainContainer.find(self.currentContainer).find("[data-onclick]").removeAttr("disabled");
1548            $mainContainer.find(self.currentContainer + "_overlay").remove();
1549        }
1550    };
1551
1552    SimplecheckoutBlock.prototype.hasError = function() {
1553        return $(this.params.mainContainer).find(this.currentContainer).attr("data-error") ? true : false;
1554    };
1555
1556    SimplecheckoutBlock.prototype.init = function(useContainer) {
1557        var self = this;
1558        var $mainContainer = $(self.params.mainContainer);
1559        var $currentContainer = $mainContainer.find(self.currentContainer + ":visible");
1560
1561        if (!$currentContainer.length) {
1562            return;
1563        }
1564
1565        var callFunc = function(func, $target, e) {
1566            if (func && typeof self[func] === "function") {
1567                self[func]($target, e);
1568            } else if (func) {
1569                //console.log(func + " is not registered");
1570            }
1571        };
1572
1573        $currentContainer.find("*[data-onchange]").on("change", function(e) {
1574            if (typeof self.simplecheckout !== "undefined") {
1575                self.simplecheckout.setDirty($(this));
1576            }
1577            callFunc($(this).attr("data-onchange"), $(this), e);
1578        });
1579
1580        $currentContainer.find("*[data-onclick]").on("click", function(e) {
1581            if ($(this).attr("disabled")) {
1582                return;
1583            }
1584
1585            if (typeof self.simplecheckout !== "undefined") {
1586                self.simplecheckout.setDirty();
1587            }
1588
1589            var confirmText = $(this).attr("data-confirm-text");
1590
1591            if (!confirmText || (confirmText && confirm(confirmText))) {
1592                callFunc($(this).attr("data-onclick"), $(this), e);
1593            }
1594        });
1595
1596        $currentContainer.find("*[data-onkeydown]").on("keydown", function(e) {
1597            if (typeof self.simplecheckout !== "undefined") {
1598                self.simplecheckout.setDirty();
1599            }
1600
1601            callFunc($(this).attr("data-onkeydown"), $(this), e);
1602        });
1603
1604        if (self.isEmpty()) {
1605            ////console.log(self.currentContainer + " is empty");
1606        }
1607
1608        if (!self.hasError() && $currentContainer.attr("data-hide")) {
1609            $currentContainer.hide();
1610        }
1611
1612        self.addFocusHandler();
1613        self.restoreFocus();
1614
1615        $currentContainer.data("initialized", true);
1616    };
1617
1618    SimplecheckoutBlock.prototype.validate = function(silent) {
1619        var self = this;
1620
1621        if (typeof silent === "undefined") {
1622            silent = false;
1623        }
1624
1625        return self.simplecheckout.checkRules(self.currentContainer, silent);
1626    };
1627
1628    SimplecheckoutBlock.prototype.isEmpty = function() {
1629        if ($(this.params.mainContainer).find(this.currentContainer).find("*").length) {
1630            return false;
1631        }
1632        return true;
1633    };
1634
1635    SimplecheckoutBlock.prototype.shareMethod = function(name, asName) {
1636        SimplecheckoutBlock.prototype[asName] = bind(this[name], this);
1637    };
1638
1639    SimplecheckoutBlock.prototype.displayWarning = function() {
1640        var container = $(this.params.mainContainer).find(this.currentContainer);
1641
1642        if (this.params.notificationDefault) {
1643            container.find(".simplecheckout-warning-block").show();
1644        }
1645
1646        if (this.params.notificationToasts) {
1647            container.find(".simplecheckout-warning-block").each(function() {
1648                toastr.error($(this).text());
1649            });            
1650        }
1651
1652        if (container.find(".simplecheckout-warning-block").length) {
1653            container.attr('data-error', true);
1654        }
1655    };
1656
1657    SimplecheckoutBlock.prototype.hideWarning = function() {
1658        $(this.params.mainContainer).find(this.currentContainer).find(".simplecheckout-warning-block").hide();
1659    };
1660
1661    SimplecheckoutBlock.prototype.focusedFieldId = "";
1662
1663    SimplecheckoutBlock.prototype.addFocusHandler = function() {
1664        var self = this;
1665        var $currentContainer = $(self.params.mainContainer).find(self.currentContainer + ":visible");
1666
1667        $currentContainer.find("input,textarea,select").focus(function() {
1668            self.simplecheckout.focusedFieldId = $(this).attr("id");
1669        });
1670
1671        $(self.params.mainContainer).find("*:not(input,textarea,select)").focus(function() {
1672            self.simplecheckout.focusedFieldId = "";
1673        });
1674    };
1675
1676    SimplecheckoutBlock.prototype.restoreFocus = function() {
1677        var self = this;
1678        var $currentContainer = $(self.params.mainContainer).find(self.currentContainer + ":visible");
1679        var focusedFieldId = self.simplecheckout.focusedFieldId;
1680
1681        if (focusedFieldId && $currentContainer) {
1682            var focusedField = $currentContainer.find("#" + focusedFieldId);
1683
1684            if (focusedField.length && focusedField.is(":visible") && ((focusedField.attr("type") && focusedField.attr("type") == "text" && !focusedField.attr("data-type") && !focusedField.attr("data-simple-mask")) || focusedField.is("textarea"))) {
1685                var $field = $currentContainer.find("#" + focusedFieldId);
1686                var value = $field.val();
1687                $field.val("").focus().val(value);
1688            }
1689        }
1690    };
1691
1692    function SimplecheckoutCart(container, route) {
1693        this.currentContainer = container;
1694        this.currentRoute = route;
1695
1696        this.init = function() {
1697            var self = this;
1698            SimplecheckoutBlock.prototype.init.apply(self, arguments);
1699            self.initMiniCart();
1700        };
1701
1702        this.validate = function(silent) {
1703            var self = this;
1704            var result = true;
1705            var deferred = $.Deferred();
1706
1707            if (typeof silent === "undefined") {
1708                silent = false;
1709            }
1710
1711            if (!silent && this.hasError()) {
1712                this.displayWarning();
1713
1714                result = false;
1715            }
1716
1717            deferred.resolve(result);
1718
1719            return deferred.promise();
1720        };  
1721
1722        this.clearCart = function() {
1723            var self = this;
1724
1725            $.get("index.php?" + self.params.additionalParams + "route=" + self.currentRoute + "/clear", function() {
1726                self.reloadAll();
1727            });
1728        };
1729
1730        this.initMiniCart = function() {
1731            var self = this;
1732            var $mainContainer = $(self.params.mainContainer);
1733            var total = $mainContainer.find("#simplecheckout_cart_total").html();
1734            var weight = $mainContainer.find("#simplecheckout_cart_weight").text();
1735
1736            if (total) {
1737                $.each(["#cart_total", "#cart-total", "#cart_menu .s_grand_total", "#cart .tb_items", "#cart .tb_total", "#menu_wrap #cart-total"], function(index, selector) {
1738                    $(selector).html(total);
1739                });
1740
1741                $("#weight").text(weight);
1742            }
1743        };
1744
1745        this.increaseProductQuantity = function($target) {
1746
1747            var self = this;
1748
1749            var $quantity = $target.parents(".item_qty").find("input");
1750            var quantity = parseFloat($quantity.val());
1751            var step = +$quantity.attr("data-minimum") || 1;
1752
1753            console.log(quantity);
1754
1755            if (!isNaN(quantity)) {
1756                $quantity.val(quantity + step);
1757
1758                self.copyCartState($quantity);
1759
1760                if (self.timerId) {
1761                    clearTimeout(self.timerId);
1762                    self.timerId = 0;
1763                }
1764                
1765                self.timerId = window.setTimeout(function() {
1766                    self.reloadAll();
1767                }, 300);                
1768            }
1769        };
1770
1771        this.decreaseProductQuantity = function($target) {
1772            var self = this;
1773
1774            var $quantity = $target.parents(".item_qty").find("input");
1775            var quantity = parseFloat($quantity.val());
1776            var step = +$quantity.attr("data-minimum") || 1;
1777            
1778            if (!isNaN(quantity) && quantity > step) {
1779                $quantity.val(quantity - step);
1780                
1781                self.copyCartState($quantity);
1782
1783                if (self.timerId) {
1784                    clearTimeout(self.timerId);
1785                    self.timerId = 0;
1786                }
1787
1788                self.timerId = window.setTimeout(function() {
1789                    self.reloadAll();
1790                }, 300);  
1791            }
1792        };
1793
1794        this.changeProductQuantity = function($target) {
1795            var self = this;
1796
1797            if (typeof $target[0] !== "undefined" && typeof $target[0].tagName !== "undefined" && $target[0].tagName !== "INPUT") {
1798                $target = $target.parents("td").find("input");
1799            }
1800
1801            var $quantity = $target.parents(".item_qty").find("input");
1802            var quantity = parseFloat($quantity.val());
1803            var step = +$quantity.attr("data-minimum") || 1;
1804
1805            quantity = Math.round(quantity / step) * step;
1806
1807            if (!quantity) {
1808                quantity = step;
1809            }
1810
1811            if (!isNaN(quantity)) {
1812                $quantity.val(quantity);
1813
1814                self.copyCartState($target);
1815
1816                self.reloadAll();
1817            }
1818        };
1819
1820        this.removeProduct = function($target) {
1821            var self = this;
1822            var $currentContainer = $(self.params.mainContainer).find(self.currentContainer + ":visible");
1823
1824            var productKey = $target.attr("data-product-key");
1825
1826            var $target = $currentContainer.find("#simplecheckout_remove");
1827            $target.val(productKey);
1828            self.copyCartState($target);
1829
1830            self.reloadAll();
1831        };
1832
1833        this.removeGift = function($target) {
1834            var self = this;
1835            var $currentContainer = $(self.params.mainContainer).find(self.currentContainer + ":visible");
1836
1837            var giftKey = $target.attr("data-gift-key");
1838
1839            var $target = $currentContainer.find("#simplecheckout_remove");
1840            $target.val(giftKey);
1841            self.copyCartState($target);
1842
1843            self.reloadAll();
1844        };
1845
1846        this.removeCoupon = function($target) {
1847            var self = this;
1848            var $currentContainer = $(self.params.mainContainer).find(self.currentContainer + ":visible");
1849
1850            $target = $currentContainer.find("input[name='coupon']");
1851            $target.val("");
1852            self.copyCartState($target);
1853
1854            self.reloadAll();
1855        };
1856
1857        this.removeReward = function($target) {
1858            var self = this;
1859            var $currentContainer = $(self.params.mainContainer).find(self.currentContainer + ":visible");
1860
1861            $target = $currentContainer.find("input[name='reward']");
1862            $target.val("");
1863            self.copyCartState($target);
1864            
1865            self.reloadAll();
1866        };
1867
1868        this.removeVoucher = function($target) {
1869            var self = this;
1870            var $currentContainer = $(self.params.mainContainer).find(self.currentContainer + ":visible");
1871
1872            $target = $currentContainer.find("input[name='voucher']");
1873            $target.val("");
1874            self.copyCartState($target);
1875
1876            self.reloadAll();
1877        };
1878
1879        this.copyCartState = function($target) {
1880            var $mainContainer = $(this.params.mainContainer);
1881            var name = $target.attr('name');
1882            var value = $target.val();
1883
1884            $mainContainer.find('[name="' + name + '"]').each(function() {
1885                if (this != $target[0]) {
1886                    $(this).val(value);
1887                }
1888            });
1889        };
1890    }
1891
1892    SimplecheckoutCart.prototype = inherit(SimplecheckoutBlock.prototype);
1893
1894    function SimplecheckoutLogin(container, route) {
1895        this.currentContainer = container;
1896        this.currentRoute = route;
1897
1898        this.init = function() {
1899            var self = this;
1900            SimplecheckoutBlock.prototype.init.apply(self, arguments);
1901        };
1902
1903        this.removeTransformCss = function() {
1904            $(this.params.mainContainer)
1905                .parents()
1906                .filter(function() {
1907                    var transformStyle = $(this).css("transform");
1908                    if (transformStyle && transformStyle != "none") {
1909                        return true;
1910                    }
1911                    return false;
1912                }).each(function() {
1913                    var style = $(this).css("transform");
1914                    $(this).css("transform", "none").data('transform-style', style);
1915                });
1916        }
1917
1918        this.restoreTransformCss = function() {
1919            $(this.params.mainContainer)
1920                .parents()
1921                .filter(function() {
1922                    return $(this).data("transform-style");
1923                }).each(function() {
1924                    $(this).css("transform", $(this).data("transform-style")).data("transform-style", "");
1925                });
1926        }
1927
1928        this.initPopupLayer = function() {
1929            var self = this;
1930            var position = $("#simple_login_layer").parent().css("position");
1931            if (!$("#simple_login_layer").length || position == "fixed" || position == "relative" || position == "absolute") {
1932                $("#simple_login_layer").remove();
1933                $("#simple_login").remove();
1934                $(self.params.mainContainer).append("<div id='simple_login_layer'></div><div id='simple_login'><div id='temp_popup_container'></div></div>");
1935                $("#simple_login_layer").on("click", function() {
1936                    self.close();
1937                });
1938            }
1939
1940            self.removeTransformCss();
1941
1942            $("#simple_login_layer")
1943                .css("position", "fixed")
1944                .css("top", "0")
1945                .css("left", "0")
1946                .css("right", "0")
1947                .css("bottom", "0");
1948
1949            $("#simple_login_layer").fadeTo(500, 0.8);
1950        };
1951
1952        this.openPopup = function() {
1953            var self = this;
1954            self.initPopupLayer();
1955            if (!$(self.currentContainer).html()) {
1956                self.load(function() {
1957                    if ($(self.currentContainer).html()) {
1958                        self.resizePopup();
1959                    } else {
1960                        self.closePopup();
1961                    }
1962                }, "#temp_popup_container");
1963            } else {
1964                self.hideWarning();
1965                self.resizePopup();
1966            }
1967        };
1968
1969        this.resizePopup = function() {
1970            $("#simple_login").show();
1971            $("#simple_login").css("height", $(this.currentContainer).outerHeight() + 20);
1972            $("#simple_login").css("top", window.innerHeight / 2 - ($("#simple_login").outerHeight() ? $("#simple_login").outerHeight() : $("#simple_login").height()) / 2);
1973            $("#simple_login").css("left", $(window).width() / 2 - ($("#simple_login").outerWidth() ? $("#simple_login").outerWidth() : $("#simple_login").width()) / 2);
1974        };
1975
1976        this.closePopup = function() {
1977            var self = this;
1978            $("#simple_login_layer").fadeOut(500, function() {
1979                $(this).hide().css("opacity", "1");
1980                self.restoreTransformCss();
1981            });
1982            $("#simple_login").fadeOut(500, function() {
1983                $(this).hide();
1984            });
1985        };
1986
1987        this.openFlat = function() {
1988            var self = this;
1989            if (!$(self.currentContainer).length) {
1990            $("<div id='temp_flat_container'><img src='" + self.params.additionalPath + self.resources.loading + "'></div>").insertBefore(self.params.loginBoxBefore);
1991                self.load("#temp_flat_container");
1992            }
1993            self.hideWarning();
1994            $(self.currentContainer).show();
1995        };
1996
1997        this.closeFlat = function() {
1998            $(this.currentContainer).hide();
1999        };
2000
2001        this.isOpened = function() {
2002            return $("#temp_flat_container *:visible").length ? true : false;
2003        };
2004
2005        this.open = function() {
2006            var self = this;
2007            /*if (self.getParam("logged")) {
2008                return;
2009            }*/
2010            if (self.params.loginBoxBefore) {
2011                self.openFlat();
2012            } else {
2013                self.openPopup();
2014            }
2015        };
2016
2017        this.close = function() {
2018            var self = this;
2019            if (self.params.loginBoxBefore) {
2020                self.closeFlat();
2021            } else {
2022                self.closePopup();
2023            }
2024        };
2025
2026        this.login = function() {
2027            var self = this;
2028            this.reload(function() {
2029                if (!self.hasError()) {
2030                    self.closePopup();
2031                    self.closeFlat();
2032                    if (self.simplecheckout) {
2033                        self.simplecheckout.saveStep();
2034                        self.simplecheckout.ignorePost();
2035                        self.simplecheckout.reloadAll();
2036                    } else {
2037                        window.location.reload();
2038                    }
2039                } else {
2040                    self.resizePopup();
2041                }
2042            });
2043        };
2044
2045        this.detectEnterAndLogin = function($target, e) {
2046            if (e.keyCode == 13) {
2047                this.login();
2048            }
2049        };
2050    }
2051
2052    SimplecheckoutLogin.prototype = inherit(SimplecheckoutBlock.prototype);
2053
2054    function SimplecheckoutComment(container, route) {
2055        this.currentContainer = container;
2056        this.currentRoute = route;
2057
2058        this.init = function() {
2059            var self = this;
2060            SimplecheckoutBlock.prototype.init.apply(self, arguments);
2061        };
2062    }
2063
2064    SimplecheckoutComment.prototype = inherit(SimplecheckoutBlock.prototype);
2065
2066    function SimplecheckoutShipping(container, route) {
2067        this.currentContainer = container;
2068        this.currentRoute = route;
2069
2070        this.init = function() {
2071            var self = this;
2072            SimplecheckoutBlock.prototype.init.apply(self, arguments);
2073        };
2074
2075        this.validate = function(silent) {
2076            var self = this;
2077            var result = true;
2078            var $currentContainer = $(self.params.mainContainer).find(self.currentContainer + ":visible");
2079            var deferred = $.Deferred();
2080
2081            if (typeof silent === "undefined") {
2082                silent = false;
2083            }
2084
2085            if ($currentContainer.length && !$currentContainer.find("input:checked").length && !$currentContainer.find("option:selected").length) {
2086                if (!silent) {
2087                    self.displayWarning();
2088                }
2089                result = false;
2090            }
2091
2092            var fieldsValidator = SimplecheckoutBlock.prototype.validate.apply(self, arguments).then(function(validatorResult) {
2093                result = result && validatorResult;
2094            });
2095
2096            var backendValidator = $.ajax({
2097                url: "index.php?" + self.params.additionalParams + "route=" + self.currentRoute + "/validate",
2098                data: $currentContainer.find("input:checked,option:selected"),
2099                type: "POST",
2100                dataType: "json",
2101                success: function(json) {
2102                    if (json['error']) {
2103                        $("<div/>")
2104                            .addClass("alert alert-danger simplecheckout-warning-block simplecheckout-validator-result")
2105                            .html(json['error'])
2106                            .insertBefore($currentContainer.find(".simplecheckout-block-content"));
2107
2108                        result = false;
2109
2110                        return;
2111                    } else {
2112                        $currentContainer.find(".simplecheckout-validator-result").remove();
2113                    }
2114                },                   
2115            });
2116
2117            $.when.apply($, [fieldsValidator, backendValidator]).then(function () {
2118                deferred.resolve(result);
2119            });
2120
2121            return deferred.promise();
2122        };
2123    }
2124
2125    SimplecheckoutShipping.prototype = inherit(SimplecheckoutBlock.prototype);
2126
2127    function SimplecheckoutPayment(container, route) {
2128        this.currentContainer = container;
2129        this.currentRoute = route;
2130
2131        this.init = function() {
2132            var self = this;
2133            SimplecheckoutBlock.prototype.init.apply(self, arguments);
2134        };
2135
2136        this.validate = function(silent) {
2137            var self = this;
2138            var result = true;
2139            var $currentContainer = $(self.params.mainContainer).find(self.currentContainer + ":visible");
2140            var deferred = $.Deferred();
2141
2142            if (typeof silent === "undefined") {
2143                silent = false;
2144            }
2145
2146            if ($currentContainer.length && !$currentContainer.find("input:checked").length && !$currentContainer.find("option:selected").length) {
2147                if (!silent) {
2148                    self.displayWarning();
2149                }
2150                result = false;
2151            }
2152
2153            SimplecheckoutBlock.prototype.validate.apply(self, arguments).then(function(validatorResult) {
2154                deferred.resolve(result && validatorResult);
2155            });
2156
2157            return deferred.promise();
2158        };
2159    }
2160
2161    SimplecheckoutPayment.prototype = inherit(SimplecheckoutBlock.prototype);
2162
2163    function SimplecheckoutForm(container, route) {
2164        this.currentContainer = container;
2165        this.currentRoute = route;
2166
2167        this.init = function() {
2168            var self = this;
2169            SimplecheckoutBlock.prototype.init.apply(self, arguments);
2170        };
2171
2172        this.validate = function(silent) {
2173            var self = this;
2174            var result = true;
2175            var deferred = $.Deferred();
2176
2177            if (typeof silent === "undefined") {
2178                silent = false;
2179            }
2180
2181            SimplecheckoutBlock.prototype.validate.apply(self, arguments).then(function(validatorResult) {
2182                deferred.resolve(result && validatorResult);
2183            });
2184
2185            return deferred.promise();
2186        };
2187
2188        this.reloadAll = function($element) {
2189            var self = this;
2190            window.setTimeout(function() {
2191                if (!$element.attr("data-valid") || $element.attr("data-valid") == "true") {
2192                    SimplecheckoutBlock.prototype.reloadAll.apply(self, arguments);
2193                }
2194            }, 0);
2195
2196        };
2197    }
2198
2199    SimplecheckoutForm.prototype = inherit(SimplecheckoutBlock.prototype);
2200
2201    (function() {
2202        var inputs = {};
2203
2204        $(document).on("keyup", "input[type=text],input[type=email],input[type=tel],textarea", function(e) {
2205            if ($.inArray(e.keyCode,[9,13,16,17,18,19,20,27,35,36,37,38,39,40,91,93,224]) > -1) {
2206                return true;
2207            }
2208
2209            var inputId = $(this).attr("id");
2210
2211            if (inputId) {
2212                var currentTime = new Date().getTime();
2213
2214                var delta = 500;
2215
2216                if (typeof inputs[inputId] !== 'undefined') {
2217                    if ((currentTime - inputs[inputId].time) < 10000) {
2218                        delta = (currentTime - inputs[inputId].time + inputs[inputId].delta) / 2;
2219                    }
2220                }
2221
2222                inputs[inputId] = {
2223                    time: currentTime,
2224                    delta: delta
2225                };
2226            }
2227
2228            var deltaSum = 0;
2229            var deltaCounter = 0;
2230
2231            for (var i in inputs) {
2232                if (!inputs.hasOwnProperty(i)) continue;
2233
2234                deltaSum += inputs[i].delta;
2235                deltaCounter++;
2236            }
2237
2238            if (deltaCounter) {
2239                var speed = deltaSum / deltaCounter;
2240
2241                if (speed > 500) {
2242                    window.simpleTypingSpeed = speed;
2243                } else {
2244                    window.simpleTypingSpeed = 500;
2245                }
2246            }
2247        });
2248    })();
2249})(jQuery || $);

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.