PageSourceSearch

https://register.hbsands.org/public/js/catalog/widget.cartDropdown.js?1790366516

js hbsands.org collected 2026-09-30 14:57:35 UTC 61,210 bytes, 1,508 lines download raw bytes

1/**
2 * Cart Dropdown Widget for UI
3 * Handles DOM and UI representation of the state of a Catalog object
4 * @author Jake Grice
5 */
6(function ($, undefined) {
7
8    "use strict";
9
10    $.widget("rec1.cartDropdown", {
11
12        options: {
13            cart: null,
14            catalogWidet: null,
15            newItemCount: 0,
16            basePath: '',
17            removable: false,
18            editable: false,
19            childrenRemovable: false,
20            checkoutButton: true,
21            quickPay: false,
22            quickPayData: null,
23            receiptPrinterConfig: null,
24            quickpayTypes: null,
25            highlightClass: "bg-primary",
26            selectLatestAddons: false,
27        },
28
29        $cartIcon: null,
30        $catalogTabs: null,
31
32        _create: function () {
33            var _this = this;
34
35            this._resetQuickPayData();
36
37            // Cleanup old dropdown menus
38            $('.rec1-catalog-cart').remove();
39
40            // Create the dropdown widget
41            this.dropdown = $('<div class="rec1-catalog-cart"></div>');
42            this.dropdown.appendTo("body").dropdown({
43                useOffsetParent: false,
44                align: "right",
45                beforeOpen: function (event) {
46                    _this.refresh();
47                    return true;
48                },
49                open: function (event) {
50                    _this.scrollToNewItems();
51                    return true;
52                },
53                close: function () {
54                    _this._hideAddonPanel();
55                }
56            });
57
58            this._isFloatingControls();
59
60            this.element.addClass('rec1-catalog-minicart').html(this._renderButton()).attr({
61                tabindex: "0",
62                role: "button",
63            }).on("keydown", function (event) {
64                if (event.key === "Enter" || event.key === " ") {
65                    $(this).trigger("click");
66                    event.preventDefault(); // Prevent page scrolling when pressing space
67                }
68            });
69
70
71            if (this.options.quickPay) {
72                this.dropdown.css({minWidth: 600});
73            }
74
75            this._on(this.dropdown, {
76                "click .cart-item": "_handleCartItemClick",
77                "click .cart-item-remove": "_removeItem",
78                "change .cart-item-quantity": "_updateQuantity",
79                "change .cart-item-price": "_updatePrice",
80                "click .cart-checkout-button": "_checkoutClicked",
81                "click .cart-item-quantity-decrease": "_decreaseQuantity",
82                "click .cart-item-quantity-increase": "_increaseQuantity",
83                "click input.cart-item-price": "_focusItemPrice",
84                "keydown .cart-item-note": "_handleCartItemNoteKeydown",
85                "blur .cart-item-note": "_handleCartItemNoteBlur",
86                "click .cart-item-note-save": "_handleCartItemNoteSaveClick",
87                "click .quickpay-thermal-print": "_handleThermalPrintClick",
88                "change .quickpay-total": "_handleQuickPayAmountChange"
89            });
90
91            _this._initQuickPayEvents();
92
93            // if not internal, turn off
94            if (this.options.cart.catalog.view === "internal") {
95                _this._initQuickPaySwipe();
96            }
97
98            this.dropdown.dropdown('option', 'triggerSelector', this.element);
99
100            if (this.options.catalogWidget) {
101                this.options.receiptPrinterConfig = this.options.catalogWidget.catalog("option", "receiptPrinterConfig");
102            }
103
104            this.$window = $(window);
105            this.$document = $(document);
106
107            this.$cartIcon = this.$document.find(".rec1-catalog-minicart");
108            this.$catalogTabs = this.$document.find(".rec1-catalog-tabs");
109
110            this.refresh();
111
112            this._on(this.$window, {
113                "scroll": _this._adjustCartPosition,
114                "resize": _this._adjustCartPosition
115            });
116
117            this._observeElementVisibility(["rec1-catalog-cart-container"], 
118                this._handleCartFloatableState.bind(this)
119            );
120        },
121
122        _handleCartFloatableState: function (isVisible) {
123            var $cartIcon = this.$cartIcon;
124            var $cartDropdown = this.dropdown;
125
126            if (isVisible) {
127                $cartIcon.removeClass("floatable");
128                $cartDropdown.removeClass("floatable");
129            } else {
130                $cartIcon.addClass("floatable");
131                $cartDropdown.addClass("floatable");
132            }
133        },
134
135        _observeElementVisibility: function (elementIds, callback) {
136            const ids = Array.isArray(elementIds) ? elementIds : [elementIds];
137            
138            ids.forEach(function(elementId) {
139                const element = document.getElementById(elementId);
140
141                if (!element) {
142                    return;
143                }
144
145                const observer = new IntersectionObserver(
146                    function(entries) {
147                        callback(entries[0].isIntersecting, elementId);
148                    },
149                    {
150                        root: null,
151                        threshold: 0.01
152                    }
153                );
154
155                observer.observe(element);
156            });
157        },
158
159        _isFloatingControls: function () {
160            if ($("#rec1-container").hasClass("floating-controls")) {
161                this.dropdown.addClass("floating-controls");
162            } else {
163                this.dropdown.removeClass("floating-controls");
164            }
165        },
166
167        _adjustCartPosition: function (event) {
168            var $cartIcon = this.$cartIcon;
169            var $cartDropdown = this.dropdown;
170            var $catalogTabs = this.$catalogTabs;
171
172            var startingOffset = 0;
173            if ($catalogTabs.length == 0) {
174                startingOffset = $cartIcon.offset().top;
175            } else {
176                startingOffset = $catalogTabs.offset().top;
177            }
178
179            if ($(".rec1-header-cp-toolbar").length) {
180                var toolbarHeight = parseInt($(".rec1-header-cp-toolbar").height());
181                if (parseInt(this.$window.scrollTop()) > toolbarHeight - 10) {
182                    $cartDropdown.addClass("scrollMarginTop");
183                } else {
184                    $cartDropdown.removeClass("scrollMarginTop");
185                }
186            }
187
188            this._isFloatingControls();
189
190            if (this.$highlightedItem) {
191                this._positionAddonPanel();
192            }
193            
194            // Re-adjust mobile cart height on window resize/orientation change
195            if ($cartDropdown.is(':visible')) {
196                this._adjustCartHeightForMobile();
197            }
198        },
199
200        _focusItemPrice: function (event) {
201
202            $(event.currentTarget).focus().select();
203        },
204
205        open: function (autoFocus) {
206            if (autoFocus === undefined) {
207                autoFocus = true;
208            }
209            this.dropdown.dropdown('open', null, autoFocus);
210            
211            // Adjust cart height on mobile to prevent checkout button obstruction
212            this._adjustCartHeightForMobile();
213            
214            if (this.options.selectLatestAddons) {
215                // Get last item in the cart
216                var items = this.options.cart.items;
217                if (items.length > 0) {
218                    var lastItem = items[items.length - 1];
219                    for (var i = 1; i <= items.length; i++) {
220                        lastItem = items[items.length - i];
221                        if (lastItem instanceof CatalogItemMerchandise) {
222                            break;
223                        }
224                    }
225                    var $lastItem = this.dropdown.find(".cart-item:last");
226                    this._notifyItemClicked($lastItem, lastItem);
227                }
228            }
229        },
230
231        _adjustCartHeightForMobile: function () {
232            var _this = this;
233            
234            // Only run on mobile/small screens
235            if (window.innerWidth >= 768) {
236                return;
237            }
238            
239            // Use requestAnimationFrame to ensure DOM is ready
240            requestAnimationFrame(function() {
241                var $checkoutButton = _this.dropdown.find('.cart-checkout-button, .cart-catalog-button');
242                
243                if ($checkoutButton.length === 0) {
244                    return;
245                }
246                
247                var checkoutRect = $checkoutButton[0].getBoundingClientRect();
248                var viewportHeight = window.innerHeight;
249                
250                // Check if checkout button bottom is beyond viewport bottom
251                // Add 20px buffer for safe measure
252                if (checkoutRect.bottom > (viewportHeight - 20)) {
253                    // Get the totals section height to ensure it's always visible
254                    var $totalsSection = _this.dropdown.find('.cart-contents-total');
255                    var totalsHeight = $totalsSection.length > 0 ? $totalsSection.outerHeight(true) : 0;
256                    
257                    // Calculate how much we need to reduce
258                    var overflow = checkoutRect.bottom - viewportHeight + 40; // 40px total buffer
259                    var currentHeight = _this.dropdown.outerHeight();
260                    
261                    // Minimum height should accommodate:
262                    // - At least some visible items
263                    // - Full totals section
264                    // - Checkout button
265                    // We'll use 50vh or enough space for totals + 200px for items, whichever is larger
266                    var minHeight = Math.max(viewportHeight * 0.5, totalsHeight + 200);
267                    var newHeight = Math.max(currentHeight - overflow, minHeight);
268                    
269                    // Set custom property for dynamic height
270                    _this.dropdown[0].style.setProperty('--cart-height', newHeight + 'px');
271                    _this.dropdown.css({
272                        'height': newHeight + 'px',
273                        'max-height': newHeight + 'px'
274                    });
275                }
276            });
277        },
278
279        refresh: function () {
280            if (this.options.quickPay && this.quickPayComplete) {
281                this.dropdown.html(this._renderQuickPayComplete()).enhanceWithin();
282            } else {
283                this.dropdown.html(this._renderCartContents()).enhanceWithin();
284                this._cacheDom();
285            }
286
287            this._refreshQuickPayDisplay();
288            this._maybeHideAddonPanel();
289        },
290
291        _cacheDom: function () {
292            this.$quickPayTotal = this.dropdown.find(".quickpay-total");
293            this.$quickPayPaymentType = this.dropdown.find(".quickpay-payment-type");
294            this.$quickPaySubmit = this.dropdown.find(".quickpay-submit");
295            this.$quickPayOpenDrawer = this.dropdown.find(".quickpay-open-drawer");
296            this.$quickPayCardMessage = this.dropdown.find(".quickpay-card-swipe-message");
297            this.$quickPayCardPlaceholder = this.dropdown.find(".quickpay-card-swipe-placeholder");
298            this.$quickPayCardInfo = this.dropdown.find(".quickpay-card-swipe-info");
299            this.$quickPayCardCvv = this.dropdown.find(".quickpay-card-cvv");
300            this.$quickPayChangeDue = this.dropdown.find(".quickpay-change-due");
301            this.$quickPayAmount = this.dropdown.find("input[name='quickpayAmount']");
302            this.$addonPanel = this._getAddonPanel();
303        },
304
305        _initQuickPayEvents: function () {
306
307            this._on(this.dropdown, {
308                "click .btn-quickpay-cash": "_quickPayCash",
309                "click .btn-quickpay-type": "_quickPayOther",
310                "click .quickpay-payment-clear": "_quickPayClearPayment",
311                "click .quickpay-card-clear": "_quickPayClearCard",
312                "click .quickpay-submit": "_quickPaySubmit",
313                "keyup .quickpay-card-cvv": "_quickPayCvvChange",
314                "change .quickpay-card-cvv": "_quickPayCvvChange",
315                "click .quickpay-done": "_quickPayDone"
316            });
317        },
318
319        _initQuickPaySwipe: function () {
320            var _this = this;
321            var cardReader = this.dropdown.cardReader();
322            cardReader.cardReader("enable");
323            cardReader.cardReader({
324                scanned: function (event, data) {
325                    // data.data contains keys for:
326                    // firstname, lastname, cardNum, last4, expMonth, expYear, trackData
327
328                    _this.options.quickPayData.cardData = data.data;
329                    _this.options.quickPayData.paymentMethod = 'CREDIT/DEBIT';
330                    _this._refreshQuickPayDisplay();
331                    _this.$quickPayCardCvv.focus();
332
333                },
334                error: function (event, data) {
335                    // data.error contains an error message
336                    // data.format contains the card format that could not be read
337                    console.log("card read error ", data);
338                    alert(data.error);
339                }
340            });
341
342        },
343
344        _quickPaySwipe: function () {
345        },
346
347        _quickPayReceipt: function () {
348            // TODO: Leverage existing ui-receipt class with data-id or whatever it is
349        },
350
351        close: function () {
352            this.dropdown.dropdown('close');
353        },
354
355        _renderButton: function () {
356            var cart = this.options.cart,
357                empty = cart.isEmpty();
358            this.element[empty ? 'addClass' : 'removeClass']('empty');
359            var html = '<div class="ui-row"><div class="ui-col-4 text-center cart-icon"><i class="flaticon-cart6"></i></div>' +
360                '<div class="ui-col-8 cart-text"><div class="rec1-catalog-minicart-total">
360' + cart.getTotalString() + '</div>' +
361                '<div class="rec1-catalog-minicart-count">' + cart.getItemCountString() + '</div></div></div>';
362            this.refreshMobileButton();
363
364            return html;
365        },
366
367        refreshButton: function () {
368            var cart = this.options.cart,
369                empty = cart.isEmpty();
370            this.element[empty ? 'addClass' : 'removeClass']('empty');
371            this.element.find(".rec1-catalog-minicart-total").html(cart.getTotalString());
372            this.element.find(".rec1-catalog-minicart-count").html(cart.getItemCountString());
373            this.refreshMobileButton();
374        },
375
376        refreshMobileButton: function () {
377            var cart = this.options.cart;
378            $('.header-mobile').find(".rec1-catalog-minicart-count").html(cart.getTotalItems());
379        },
380
381        _allowMultipleQuantity: function () {
382            var cart = this.options.cart,
383                items = cart.items,
384                i, item, j, childItem;
385
386            for (i in items) {
387                item = items[i];
388                if (item.allowMultipleQuantity()) {
389                    return true;
390                }
391                if (typeof item.children === 'object') {
392                    for (j in item.children) {
393                        childItem = item.children[j];
394                        if (childItem.allowMultipleQuantity()) {
395                            return true;
396                        }
397                    }
398                }
399            }
400            return false;
401        },
402
403        quickPayClearCompleted: function () {
404            if (this.quickPayComplete) {
405
406                this._quickPayDone();
407            }
408        },
409
410        _quickPayCash: function (event) {
411            var $target = $(event.currentTarget),
412                amount = $target.data('amount');
413
414            var paymentType = this._getCashPaymentType();
415
416            var surchargeAmount = (paymentType == null) ? 0 : Math.ceil(paymentType.surchargeAmt);
417            var surchargeAmountPercent = (paymentType == null) ? 0 : ((paymentType.surchargeAmtPercent * this.options.cart.totalCharges) / 100);
418            var surchargeAppliesOver = (paymentType == null) ? 0 : paymentType.surchargeAppliesOver;
419            var surchargeInternal = (paymentType == null) ? 0 : Math.ceil(paymentType.surchargeInternal);
420            var totalSurchargeAmt = surchargeInternal ? (surchargeAmount + surchargeAmountPercent) : 0;
421
422            if (amount === 'exact') {
423                this.options.quickPayData.amount = this.options.cart.totalCharges;
424                if (this.options.cart.totalCharges >= Math.ceil(surchargeAppliesOver)) {
425                    this.options.quickPayData.amount += totalSurchargeAmt;
426                }
427            } else {
428                this.options.quickPayData.amount = this._addValue(
429                    this.options.quickPayData.amount,
430                    parseFloat(amount)
431                );
432            }
433            this.options.quickPayData.paymentMethod = 'CASH';
434            this.options.quickPayData.totalSurchargeAmt = totalSurchargeAmt;
435            this.options.quickPayData.surchargeAppliesOver = surchargeAppliesOver;
436            this.options.quickPayData.paymentTypeCash = true;
437
438            this._refreshQuickPayDisplay();
439        },
440
441        _getCashPaymentType: function () {
442            var cashPaymentType = null;
443            $.each(this.options.quickpayTypes, function (index, paymentType) {
444                if (paymentType.description.toUpperCase() === 'CASH') {
445                    cashPaymentType = paymentType;
446                    return false;
447                }
448            });
449            return cashPaymentType;
450        },
451
452        _quickPayOther: function (event) {
453            var $target = $(event.currentTarget),
454                id = $target.data('id');
455
456            this.options.quickPayData.amount = this.options.cart.totalCharges;
457            this.options.quickPayData.paymentMethod = 'CUSTOM';
458            this.options.quickPayData.paymentType = this._getPaymentType(id);
459
460            this._refreshQuickPayDisplay();
461        },
462
463        _getPaymentType: function (id) {
464            var matchedPaymentType = null;
465            $.each(this.options.quickpayTypes, function (index, paymentType) {
466                if (paymentType.id == id) {
467                    matchedPaymentType = paymentType;
468                    return false;
469                }
470            });
471            return matchedPaymentType;
472        },
473
474        _addValue: function (value1, value2) {
475            var result = value1 + value2;
476            // Prevent floating point errors
477            return parseFloat(result.toFixed(10));
478        },
479
480        _handleQuickPayAmountChange: function () {
481
482            var $input = this.$quickPayAmount,
483                options = this.options,
484                quickPayData = options.quickPayData,
485                amount = $input.val();
486
487            this.options.quickPayData.amount = parseFloat(amount);
488            this.options.quickPayData.paymentMethod = 'CASH';
489
490            this._refreshQuickPayDisplay();
491        },
492
493        _refreshQuickPayDisplay: function () {
494            var options = this.options;
495            var quickPayData = options.quickPayData;
496            var changeDue = '';
497            var surchargeAmount = (this.options.quickPayData.paymentType == null) ? 0 : Math.ceil(this.options.quickPayData.paymentType.surchargeAmt);
498            var surchargeAmountPercent = (this.options.quickPayData.paymentType == null) ? 0 : ((this.options.quickPayData.paymentType.surchargeAmtPercent * options.cart.totalCharges) / 100);
499            var surchargeAppliesOver = (this.options.quickPayData.paymentType == null) ? 0 : this.options.quickPayData.paymentType.surchargeAppliesOver;
500            var surchargeInternal = (this.options.quickPayData.paymentType == null) ? 0 : Math.ceil(this.options.quickPayData.paymentType.surchargeInternal);
501            var totalSurchargeAmt = surchargeInternal ? (surchargeAmount + surchargeAmountPercent) : 0;
502
503            if (!options.quickPay || this.quickPayComplete) {
504                return;
505            }
506
507            const pageData = $(".ui-page").data("pageData");
508            if (pageData && pageData.checkoutData) {
509                const $hostedPaymentContainer = $(".rec1-checkout-hosted-payment");
510
511                if ($hostedPaymentContainer.data("checkoutHostedPayment") !== undefined) {
512                    $hostedPaymentContainer.checkoutHostedPayment("option", "amount", this.options.cart.catalog.owedAmountCC);
513                } else {
514                    $hostedPaymentContainer.checkoutHostedPayment({
515                        catalog: this.options.cart.catalog,
516                        amount: this.options.cart.catalog.owedAmountCC,
517                        buttonText: "Pay with Credit/Debit"
518                    });
519                }
520            }
521
522            var displayedPaymentMethod = quickPayData.paymentMethod;
523
524            if (quickPayData.paymentMethod === 'CREDIT/DEBIT') {
525                quickPayData.amount = options.cart.totalCharges;
526                var cardInfo = quickPayData.cardData.cardType + ' Ending In ' + quickPayData.cardData.last4;
527                changeDue = 'N/A (Credit/Debit)';
528                quickPayData.changeDue = 0;
529                this.$quickPayCardInfo.html(cardInfo);
530                this.$quickPayCardMessage.hide();
531                this.$quickPayCardPlaceholder.show();
532                this.$quickPayTotal.attr('disabled', 'disabled').addClass('disabled');
533            } else if (quickPayData.paymentMethod === 'CUSTOM') {
534                quickPayData.amount = options.cart.totalCharges;
535
536                if (options.cart.totalCharges >= Math.ceil(surchargeAppliesOver)) {
537                    quickPayData.amount += totalSurchargeAmt;
538                }
539
540                quickPayData.changeDue = 0;
541                displayedPaymentMethod = quickPayData.paymentType.description;
542                changeDue = 'N/A';
543                this.$quickPayCardInfo.empty();
544                this.$quickPayCardMessage.show();
545                this.$quickPayCardPlaceholder.hide();
546                this.$quickPayTotal.removeAttr('disabled').removeClass('disabled');
547            } else {
548                quickPayData.paymentMethod = 'CASH';
549
550                var changeDueRaw = parseFloat((quickPayData.amount - options.cart.totalCharges).toFixed(10));
551
552                if (options.cart.totalCharges >= Math.ceil(surchargeAppliesOver)) {
553                    changeDueRaw -= totalSurchargeAmt;
554                }
555
556                quickPayData.changeDue = changeDueRaw;
557
558                if (changeDueRaw > 0) {
559                    changeDue = '$' + changeDueRaw.toFixed(2);
560                } else {
561                    changeDue = '$0.00';
562                }
563
564                this.$quickPayCardInfo.empty();
565                this.$quickPayCardMessage.show();
566                this.$quickPayCardPlaceholder.hide();
567                this.$quickPayTotal.removeAttr('disabled').removeClass('disabled');
568            }
569
570            if (this.options.quickPayData.paymentMethod === 'CASH' && this.options.quickPayData.paymentTypeCash) {
571
572                if (options.cart.totalCharges >= Math.ceil(this.options.quickPayData.surchargeAppliesOver)) {
573                    changeDueRaw -= this.options.quickPayData.totalSurchargeAmt;
574                }
575
576                if (changeDueRaw > 0) {
577                    changeDue = '$' + changeDueRaw.toFixed(2);
578                } else {
579                    changeDue = '$0.00';
580                }
581            }
582
583            quickPayData.changeDueFormatted = changeDue;
584            var enableQuickPaySubmit = quickPayData.amount >= options.cart.totalCharges;
585            if (quickPayData.paymentMethod === 'CREDIT/DEBIT' && !quickPayData.cardData.cvv) {
586                this.$quickPayCardCvv.parent().addClass("has-danger");
587                enableQuickPaySubmit = false;
588            } else {
589                this.$quickPayCardCvv.parent().removeClass("has-danger");
590            }
591
592            if (this.options.cart.items.length == 0) {
593                enableQuickPaySubmit = false;
594            }
595
596            this.$quickPaySubmit.btn(enableQuickPaySubmit ? 'enable' : 'disable');
597            this.$quickPayTotal.cash('val', quickPayData.amount);
598            this.$quickPayPaymentType.html(displayedPaymentMethod);
599            this.$quickPayChangeDue.html(changeDue);
600
601            this.dropdown.trigger("quickpayDisplayed");
602        },
603
604        _quickPayClearPayment: function () {
605            this.options.quickPayData.amount = 0;
606            this.options.quickPayData.paymentMethod = 'CASH';
607            this.options.quickPayData.cardData = null;
608            this.options.quickPayData.paymentType = null;
609            this.options.quickPayData.paymentTypeCash = false;
610            this._refreshQuickPayDisplay();
611        },
612
613        _quickPayClearCard: function () {
614            this._quickPayClearPayment();
615        },
616
617        submitCapturedPayment: function (transactionData, amount) {
618            if (typeof transactionData !== "string") {
619                transactionData = JSON.stringify(transactionData);
620            }
621            this.options.quickPayData.transactionData = transactionData;
622            this.options.quickPayData.paymentMethod = 'CREDIT/DEBIT';
623            this.options.quickPayData.amount = amount;
624            this._quickPaySubmit();
625        },
626
627        _quickPaySubmit: function () {
628            var _this = this;
629            this.dropdown.loadingmask();
630            this.options.cart.catalog.submitQuickPay(
631                this.options.quickPayData,
632                this._quickPaySubmitted.bind(this),
633                {
634                    // Supress alert for errors, we'll handle manually
635                    // and display a nice UI for the error
636                    errorAlert: false
637                }
638            ).always(function (data) {
639                if (data.useApgCashDrawerApp === "1" && data.cashDrawerKick) {
640                    var cashDrawer = new CashDrawerSocket();
641                    cashDrawer.openDrawer();
642                }
643                _this.dropdown.loadingmask('remove');
644            }).fail(function (errors) {
645                _this._displayQuickpayErrors(errors);
646            });
647        },
648
649        _quickPayCvvChange: function () {
650            this.options.quickPayData.cardData.cvv = this.$quickPayCardCvv.val();
651            this._refreshQuickPayDisplay();
652        },
653
654        _quickPaySubmitted: function (data) {
655
656            // data should have changeDue, transactionId, receipt link type of stuff, etc
657            this.quickPayComplete = true;
658
659            this.quickPayResult = data;
660
661            this.options.catalogWidget.catalog('createCart');
662            this.refresh();
663            this._printReceipt();
664            // Refresh current tab after a quickpay
665            this.options.cart.catalog.selectTab(this.options.cart.catalog.tabIndex);
666        },
667
668        displayQuickpayErrors: function (errors) {
669            this._displayQuickpayErrors(errors);
670        },
671
672        _displayQuickpayErrors: function (errors) {
673            var errorHtml = errors.join('<br>');
674            this.dropdown.find(".quickpay-error").html(errorHtml).show();
675        },
676
677        _hideQuickpayErrors: function () {
678            this.dropdown.find(".quickpay-error").hide();
679        },
680
681        /**
682         * @param isTriggeredByUser Used to indicate a user interaction triggered this print command
683         * @private
684         */
685        _printReceipt: function (isTriggeredByUser) {
686
687            var receiptPrinterConfig = this.options.receiptPrinterConfig,
688                _this = this;
689
690            if (receiptPrinterConfig.isUsed) {
691                var $receiptPrinterStatus = this.dropdown.find(".receipt-printer-status");
692                //$iframe = $dropdown.find("iframe"),
693                //$iframeDocument = $();
694
695
696                var shouldPrint = true;
697                if (!isTriggeredByUser && (receiptPrinterConfig.promptType == 'always' || receiptPrinterConfig.promptType == 'pos')) {
698                    //shouldPrint = confirm("Print thermal receipt?");
699                    // Let's have a print button they can click instead of having this confirm popup
700                    shouldPrint = false;
701                }
702
703                if (shouldPrint || _this.quickPayResult.cashDrawerKick) {
704
705                    this.document.one("printerDonePrinting", function () {
706                        $receiptPrinterStatus.slideUp();
707                        //$iframe[0].contentWindow.$("body").trigger("loadingCompleted");
708                    });
709
710                    var cashDrawerKick = isTriggeredByUser ? false : _this.quickPayResult.cashDrawerKick;
711
712                    if ($receiptPrinterStatus.data('rec1TextReceiptPrinter')) {
713                        $receiptPrinterStatus.textReceiptPrinter('option', 'transaction', _this.quickPayResult.transactionId);
714                        $receiptPrinterStatus.textReceiptPrinter('option', 'autoDrawerKick', cashDrawerKick);
715                        $receiptPrinterStatus.textReceiptPrinter('option', 'drawerNonce', _this.quickPayResult.drawerNonce);
716                        $receiptPrinterStatus.textReceiptPrinter('printWhenReady');
717                    } else {
718                        $receiptPrinterStatus.textReceiptPrinter({
719                            transaction: _this.quickPayResult.transactionId,
720                            config: receiptPrinterConfig,
721                            autoPrint: shouldPrint,
722                            autoDrawerKick: cashDrawerKick,
723                            drawerNonce: _this.quickPayResult.drawerNonce
724                        });
725                    }
726
727                    //console.log($receiptPrinterStatus);
728                }
729
730            }
731
732        },
733
734        _quickPayDone: function () {
735            this.quickPayComplete = false;
736            this.quickPayResult = null;
737            this._resetQuickPayData();
738            this.refresh();
739        },
740
741        _resetQuickPayData: function () {
742            this.options.quickPayData = {
743                amount: 0,
744                paymentMethod: '',
745                paymentType: null,
746                cardData: null
747            };
748        },
749
750        _renderQuickPay: function () {
751            var html = '<div class="quickpay-container form-horizontal">' +
752                '<input type="hidden" class="unpaid-amount" name="unpaidAmount" value="' + this.options.cart.catalog.owedAmountCC + '">' +
753                '<div class="heading-muted">QuickPay &mdash; (Cash/Credit/POS Only)</div>' +
754                '<div class="quickpay-error ui-notice-block bg-danger" style="display:none;"></div>' +
755                '<div class="quickpay-inner-container"><div>' +
756                '<div class="form-group row">' +
757                '<label class="col-xs-4 col-4 control-label col-form-label">CASH:</label>' +
758                '<div class="col-xs-8 col-8">' +
759                '<div class="btn-group">' +
760                '<button data-amount="1" class="btn btn-default btn-quickpay-cash">$1</button>' +
761                '<button data-amount="5" class="btn btn-default btn-quickpay-cash">$5</button>' +
762                '<button data-amount="10" class="btn btn-default btn-quickpay-cash">$10</button>' +
763                '<button data-amount="20" class="btn btn-default btn-quickpay-cash">$20</button>' +
764                '<button data-amount="exact" class="btn btn-default btn-quickpay-cash">Exact Change</button>' +
765                '</div>' +
766                '</div>' +
767                '</div>' +
768                this._renderCreditCardPayment() +
769                this._renderQuickPayTypes() +
770                '<div class="form-group row">' +
771                '<label class="col-xs-4 col-4 control-label col-form-label">PAYMENT SUMMARY:</label>' +
772                '<div class="col-xs-8 col-8">' +
773                '<input type="text" class="ui-cash input-size-5 quickpay-total form-control" name="quickpayAmount" value="' + this.options.quickPayData.amount + '"> ' +
774                '<span class="quickpay-payment-type">CASH</span> ' +
775                '<span class="quickpay-payment-clear">' +
776                '<button class="btn btn-sm btn-link quickpay-payment-clear">CLEAR</button>' +
777                '</span>' +
778                '</div>' +
779                '</div>' +
780                '<div class="form-group row">' +
781                '<label class="col-xs-4 col-4 control-label col-form-label">CHANGE DUE:</label>' +
782                '<div class="col-xs-8 col-8 form-control-static quickpay-change-due">TBD</div>' +
783                '</div>' +
784                '</div>' +
785                '<div class="text-right ui-voffset-lg">' +
786                '<div class="receipt-printer-status-notrans"></div>' +
787                '<button class="btn btn-link ui-dropdown-close" type="button">Close</button> ' +
788                '<button class="btn btn-lg btn-primary disabled quickpay-submit" data-icon="cart-checkout" disabled>Submit QuickPay</button>' +
789                '</div>' +
790                '</div>'; // End of inner-container
791            return html;
792        },
793
794        _renderQuickPayEmv: function () {
795            // Don't include button when no EMV is configured
796            if (!$(".ui-page").data("pageData").emvConfiguration) {
797                return '';
798            }
799
800            return '<span>' +
801                '<button type="button" class="btn btn-default cc-local-service-init disabled" disabled>Use EMV/Chip Reader</button>' +
802                '</span>';
803        },
804
805        _renderCPPay: function () {
806            // Don't include button when no EMV is configured
807            if (!$(".ui-page").data("pageData").isCPPay) {
808                return '';
809            }
810
811            return '<div class="rec1-checkout-hosted-payment" style="display: inline"></div>';
812        },
813
814        _renderCreditCardPayment: function () {
815            var html = '<div class="form-group row">' +
816                '<label class="col-xs-4 col-4 control-label col-form-label" style="padding-top: 12px;">CREDIT/DEBIT:</label>' +
817                '<div class="col-xs-8 col-8 form-control-static">' +
818                '<div class="row">';
819
820            if (!$(".ui-page").data("pageData").isCPPay) {
821                html += '<div class="col-xs-4 col-4" style="padding-top: 6px;">' +
822                    '<span class="quickpay-card-swipe-message">Swipe Card Now</span>' +
823                    '</div>';
824            }
825
826            html += '<div class="col-xs-8 col-8">' +
827                this._renderQuickPayEmv() +
828                this._renderCPPay() +
829                '</div>' +
830                '</div>' +
831                '<span class="quickpay-card-swipe-placeholder" style="display:none;">' +
832                '<span class="quickpay-card-swipe-info"></span>' +
833                '<span class="quickpay-card-swipe-cvv numeric ui-offset-lg">CVV: <input type="text" name="cvv" class="quickpay-card-cvv" size="4"></span>' +
834                '<span><btn class="btn btn-link btn-sm quickpay-card-clear">CLEAR</btn></span>' +
835                '</span>' +
836                '</div>' +
837                '</div>';
838
839            return html;
840        },
841
842        _renderQuickPayTypes: function () {
843            var html = '';
844            if (this.options.quickpayTypes && this.options.quickpayTypes.length) {
845                html = '<div class="form-group row">' +
846                    '<label class="col-xs-4 col-4 control-label col-form-label">OTHER:</label>' +
847                    '<div class="col-xs-8 col-8">' +
848                    '<div class="btn-group">';
849
850                $.each(this.options.quickpayTypes, function (index, paymentType) {
851                    html += '<button data-id="' + paymentType.id + '" class="btn btn-default btn-quickpay-type">' + paymentType.description + '</button>';
852                });
853
854                html += '</div>' +
855                    '</div>' +
856                    '</div>';
857            }
858            return html;
859        },
860
861        _renderQuickPayComplete: function () {
862            var data = this.quickPayResult;
863            var html = '<div class="form-horizontal">' +
864                '<h2>TRANSACTION COMPLETE</h2>' +
865                '<div class="form-group">' +
866                '<label class="form-label col-xs-4">Transaction:</label>' +
867                '<div class="col-xs-8 form-control-static">' + data.transactionLink + '</div>' +
868                '</div>' +
869                '<div class="form-group">' +
870                '<label class="form-label col-xs-4">Amount Tendered:</label>' +
871                '<div class="col-xs-8 form-control-static">$' + (this.options.quickPayData.amount).toFixed(2) + '   ' + this.options.quickPayData.paymentMethod + '</div>' +
872                '</div>' +
873                '<div class="form-group">' +
874                '<label class="form-label col-xs-4">Change Due:</label>' +
875                '<div class="col-xs-8 form-control-static">' + this.options.quickPayData.changeDueFormatted + '</div>' +
876                '</div>' +
877                '</div>' +
878                '<div class="ui-voffset-lg text-center">' +
879                '<button class="btn btn-lg btn-info ui-receipt-popup" data-id="' + data.transactionId + '" type="button" data-icon="file8">Receipt (' + data.transactionId + ')</button></div>' +
880                '</div>' +
881                this._renderThermalPrintButton(data) +
882                '<div class="ui-voffset-lg text-center">' +
883                '<button class="btn btn-default quickpay-done" type="button" data-icon="cart6">Done: New Cart</button></div>' +
884                '</div>' +
885                '<div class="receipt-printer-status"></div>';
886
887            return html;
888        },
889
890        _renderThermalPrintButton: function (data) {
891            if (!this.options.receiptPrinterConfig.isUsed) {
892                return '';
893            }
894            return '<div class="ui-voffset-lg text-center">' +
895                '<button class="btn btn-default quickpay-thermal-print" t
895ype="button" data-icon="file8">Thermal Print</button></div>' +
896                '</div>';
897        },
898
899        _renderCartContents: function () {
900            var cart = this.options.cart,
901                items = cart.items,
902                total = cart.totalChargesFormatted,
903                checkoutKey = cart.catalog.checkoutKey,
904                showQuantity = this._allowMultipleQuantity(),
905                showQuantityClass = showQuantity ? ' has-quantity' : '';
906
907            // Heading
908            var html = '<div class="rec1-cart-flex"><div class="cart-contents ' + showQuantityClass + '"><table class="ui-table">' +
909                '<thead><tr>' +
910                '<th>#</th>' +
911                '<th>Item</th>';
912
913            if (showQuantity) {
914                html += '<th align="center">Quantity</th>';
915            }
916
917            html += '<th align="right">Total</th>' +
918                '<th> </th>' +
919                '</tr></thead>';
920
921            // All items
922            var itemCount = items.length,
923                newIndex = itemCount - this.options.newItemCount;
924
925            for (var i in items) {
926                var item = items[i],
927                    newItem = i >= newIndex;
928
929                if (!item.displayInCart) {
930                    continue;
931                }
932
933                html += '<tbody class="cart-item-group">' + this._renderItem(item, i, newItem, showQuantity) + '</tbody>';
934
935                if (item.children && item.children.length > 0) {
936                    for (var j in item.children) {
937                        var childItem = item.children[j];
938                        html += this._renderItem(childItem, i, newItem, showQuantity, j, item);
939                    }
940                }
941            }
942
943            if (itemCount == 0) {
944                html += '<tr><td colspan="5"><div class="cart-contents-empty"><div class="empty-icon"><i class="flaticon-cart"></i></div> Your cart is empty</div></td></tr>';
945            }
946
947            // Footer
948            html += '</table></div><div class="cart-contents-total ' + showQuantityClass + '"><table class="ui-table">';
949
950            // Tax totals
951            if (Object.keys(cart.taxTotals).length > 0) {
952                for (var i in cart.taxTotals) {
953                    html += '<tr><th>&nbsp;</th>';
954
955                    if (showQuantity) {
956                        html += '<th>&nbsp;</th>';
957                    }
958
959                    html += '<th>' + cart.taxTotals[i].description + '</th>' +
960                        '<th align="right">' + cart.taxTotals[i].totalFormatted + '</th>' +
961                        '<th></th>' +
962                        '</tr>';
963                }
964            }
965
966            // Total
967            html += '<tr>' +
968                '<th> </th>';
969
970            if (showQuantity) {
971                html += '<th> </th>';
972            }
973
974            html += '<th>TOTAL</th>' +
975                '<th align="right">' + total + '</th>' +
976                '<th> </th>' +
977                '</tr>';
978
979
980            var hasItems = items.length !== 0;
981
982
983            // Checkout Link
984            var footerColspan = showQuantity ? 5 : 4;
985
986            html += '<tr>' +
987                '<td colspan="' + footerColspan + '" align="right">' +
988                '<button class="btn btn-link ui-dropdown-close" type="button">Close</button> ';
989
990            if (this.options.checkoutButton) {
991                var checkoutDisabled = hasItems ? '' : 'disabled';
992                html += '<a ' + checkoutDisabled + ' class="btn btn-primary cart-checkout-button" href="' + this.options.basePath + 'checkout/index/' + checkoutKey + '">' +
993                    '<i class="flaticon-cart-checkout"></i><span>Checkout</span></a>';
994            } else {
995                html += '<a class="btn btn-primary cart-catalog-button" href="' + this.options.basePath + 'catalog/index/' + checkoutKey + '">' +
996                    '<i class="flaticon-cart6"></i><span>Browse Catalog</span></a>';
997            }
998
999            html += '</td>' +
1000                '</tr></table></div>';
1001
1002
1003            // Quickpay box
1004            if (this.options.quickPay && (this.options.cart.catalog.canQuickPay)) {
1005                html += '<div class="cart-contents-quickpay"><table class="ui-table quickpay-table"><tr class="bordered-top"><td colspan="' + footerColspan + '">' + this._renderQuickPay() + '</td></tr></table></div>';
1006                this.dropdown.addClass("cart-quickpay");
1007            } else {
1008                this.dropdown.removeClass("cart-quickpay");
1009            }
1010
1011
1012            html += '</table></div>';
1013
1014            // Reset "new" items to 0
1015            this.options.newItemCount = 0;
1016
1017            return html;
1018        },
1019
1020        _renderItem: function (item, index, isNew, displayQuantity, subIndex, parentItem) {
1021            var rowNum = (parseInt(index) + 1) + '.',
1022                rowClass = 'cart-item',
1023                nameClass = 'cart-item-name',
1024                parentId = '';
1025
1026            if (parentItem) {
1027                rowClass += ' cart-item-child';
1028                nameClass = 'cart-item-name-child';
1029                parentId = ' data-parent="' + parentItem.hash + '"';
1030                rowNum = '&nbsp';
1031            }
1032
1033            if (isNew) {
1034                rowClass += ' new-item';
1035            }
1036
1037            var html = '<tr class="' + rowClass + '" data-hash="' + item.hash + '" ' + parentId + '>' +
1038                '<td>' + rowNum + '</td>' +
1039                '<td class="' + nameClass + '" title="' + item.description.replace(/"/g, '&quot;') + '">' + item.description + '</td>';
1040
1041            if (displayQuantity) {
1042                if (item.allowMultipleQuantity()) {
1043                    var overriddenHtml = item.quantityOverridden ? '<div class="small text-warning text-center"><strong>(Quantity limit exceeded)</strong></div>' : '';
1044                    html += '<td align="center"><div class="cart-item-quantity-wrapper input-group input-group-inline">' +
1045                        '<div class="input-group-btn"><button type="button" class="btn btn-default cart-item-quantity-decrease" data-icon="minus"></button></div>' +
1046                        '<input type="text" value="' + item.quantity +
1047                        '" name="item-quantity[' + item.hash + ']" class="cart-item-quantity integer numeric form-control">' +
1048                        '<div class="input-group-btn"><button type="button" class="btn btn-default cart-item-quantity-increase" data-icon="plus"></button></div>' +
1049                        '</div>
1049' + overriddenHtml + '</td>';
1050                } else {
1051                    html += '<td align="center">' + item.quantity + '</td>'
1052                }
1053            }
1054
1055            html += '<td align="right">';
1056            if (this.options.editable) {
1057                html += '<input type="text" class="ui-cash cart-item-price" value="' + item.itemTotal + '">';
1058            } else {
1059                html += item.itemTotalFormatted;
1060            }
1061            html += '</td>';
1062
1063
1064            // "Remove" column
1065            html += '<td>';
1066
1067            if ((parentItem && (this.options.childrenRemovable === true
1068                    || (this.options.childrenRemovable === 'auto' && item.removable)))
1069                || (!parentItem && this.options.removable && item.removable)) {
1070                html += '<a href="Javascript:void(0);" class="flaticon-close cart-item-remove"></a>';
1071            }
1072            html += '</td></tr>';
1073
1074            if (item.allowNote) {
1075                var colspan = displayQuantity ? 3 : 2;
1076                html += '<tr data-hash="' + item.hash + '"><td>&nbsp;</td>' +
1077                    '<td colspan="' + colspan + '">' +
1078                    '<input type="text" value="' + item.getNoteEscaped() +
1079                    '" name="item-note[' + item.hash + ']" class="cart-item-note" placeholder="Note...">' +
1080                    '</div>' +
1081                    '</td><td>&nbsp;</td>';
1082            }
1083
1084
1085            return html;
1086        },
1087
1088        scrollToNewItems: function () {
1089            if (this.dropdown.length !== 0) {
1090                var $newItems = this.dropdown.find(".new-item");
1091                if ($newItems.length !== 0) {
1092                    var $newItem = $newItems.eq(0);
1093                    this.dropdown.scrollTop($newItem.position().top);
1094                }
1095            }
1096        },
1097
1098        _removeItem: function (event) {
1099            var _this = this,
1100                $item = $(event.currentTarget).closest("tr"),
1101                hash = $item.data("hash"),
1102                parent = $item.data("parent");
1103
1104            $item.loadingmask();
1105
1106            // Cart.removeItem returns a jQuery promise,
1107            // which will resolve when the item is removed on the server side
1108            this.options.cart.removeItem(hash, parent).done(function (data) {
1109                if (data.errors && data.errors.length) {
1110                    return;
1111                }
1112                if (_this.options.catalogWidget) {
1113                    _this.options.catalogWidget.catalog('updateCart');
1114                }
1115                if ($item.data("loadingmask") !== undefined) {
1116                    $item.loadingmask("remove");
1117                }
1118                _this.refresh();
1119                _this._updateDropdownPosition();
1120            }).always(function () {
1121                if ($item && $item.data('uiLoadingmask')) {
1122                    $item.loadingmask('remove');
1123                }
1124            });
1125
1126            return false;
1127        },
1128
1129        _updateQuantity: function (event) {
1130            const _this = this,
1131                $target = $(event.currentTarget),
1132                $item = $target.closest("tr"),
1133                hash = $item.data("hash"),
1134                parent = $item.data("parent"),
1135                quantity = parseInt($target.val()),
1136                item = this.options.cart.getItem(hash, parent);
1137
1138            if (isNaN(quantity) || quantity < 1) {
1139                $target.val(item.quantity);
1140                return;
1141            }
1142
1143            $item.loadingmask();
1144
1145            const oldQuantity = item.quantity;
1146            item.quantity = quantity;
1147            $target.val(quantity);
1148
1149            // CatalogItem.updateQuantity returns a jQuery promise,
1150            // which will resolve when the item is removed on the server side
1151            item.updateQuantity(quantity)
1152                .fail(this._handleCountUpdateFailed($target, item, oldQuantity))
1153                .always(function () {
1154                    if (_this.options.catalogWidget) {
1155                        _this.options.catalogWidget.catalog('updateCart');
1156                    }
1157                    $item.loadingmask('remove');
1158                    _this._updateDropdownPosition();
1159                    _this.refresh();
1160                });
1161
1162            return false;
1163        },
1164
1165        _updatePrice: function (event) {
1166            var _this = this,
1167                $target = $(event.currentTarget),
1168                $item = $target.closest("tr"),
1169                hash = $item.data("hash"),
1170                parent = $item.data("parent"),
1171                price = parseFloat($target.cash('val')),
1172                item = this.options.cart.getItem(hash, parent);
1173
1174            if (isNaN(price) || price < 0) {
1175                $target.cash('val', item.itemTotal);
1176                return;
1177            }
1178
1179            $item.loadingmask();
1180
1181            // CatalogItem.updateQuantity returns a jQuery promise,
1182            // which will resolve when the item is removed on the server side
1183            item.setPrice(price).always(function () {
1184                if (_this.options.catalogWidget) {
1185                    _this.options.catalogWidget.catalog('updateCart');
1186                }
1187                $item.loadingmask('remove');
1188                _this._updateDropdownPosition();
1189                _this.refresh();
1190            });
1191
1192            return false;
1193        },
1194
1195        _checkoutClicked: function (event) {
1196            this.dropdown.dropdown('close');
1197        },
1198
1199        _decreaseQuantity: function (event) {
1200            const _this = this,
1201                $target = $(event.currentTarget),
1202                $item = $target.closest("tr"),
1203                $quantity = $item.find(".cart-item-quantity"),
1204                $buttons = $item.find(".cart-item-quantity-decrease, .cart-item-quantity-increase"),
1205                hash = $item.data("hash"),
1206                parent = $item.data("parent"),
1207                item = this.options.cart.getItem(hash, parent),
1208                quantity = parseInt($quantity.val()) - 1;
1209
1210            if (quantity <= 0) {
1211                return;
1212            }
1213
1214            $item.loadingmask();
1215            $buttons.prop('disabled', true);
1216            const oldQuantity = item.quantity;
1217            item.quantity = quantity;
1218            $quantity.val(quantity);
1219
1220            // CatalogItem.updateQuantity returns a jQuery promise,
1221            // which will resolve when the item is removed on the server side
1222            item.updateQuantity(quantity)
1223                .fail(this._handleCountUpdateFailed($quantity, item, oldQuantity))
1224                .always(function () {
1225                    _this._changedQuantity.bind(_this);
1226                    $buttons.prop('disabled', false);
1227                    $item.loadingmask('remove');
1228                    if (_this.options.catalogWidget) {
1229                        _this.options.catalogWidget.catalog('updateCart');
1230                    }
1231                    _this._updateDropdownPosition();
1232                    _this.refresh();
1233                });
1234
1235            return false;
1236        },
1237
1238        _handleCountUpdateFailed: function ($quantityControl, item, oldQuantity) {
1239            return function () {
1240                item.quantity = oldQuantity;
1241                item.refreshVisibleItems();
1242                $quantityControl.val(oldQuantity);
1243            };
1244        },
1245
1246        _increaseQuantity: function (event) {
1247            const _this = this,
1248                $target = $(event.currentTarget),
1249                $item = $target.closest("tr"),
1250                $quantity = $item.find(".cart-item-quantity"),
1251                $buttons = $item.find(".cart-item-quantity-decrease, .cart-item-quantity-increase"),
1252                hash = $item.data("hash"),
1253                parent = $item.data("parent"),
1254                item = this.options.cart.getItem(hash, parent),
1255                quantity = parseInt($quantity.val()) + 1;
1256
1257            $item.loadingmask();
1258            $buttons.prop('disabled', true);
1259            const oldQuantity = item.quantity;
1260            item.quantity = quantity;
1261            $quantity.val(quantity);
1262
1263            // CatalogItem.updateQuantity returns a jQuery promise,
1264            // which will resolve when the item is removed on the server side
1265            item.updateQuantity(quantity)
1266                .fail(this._handleCountUpdateFailed($quantity, item, oldQuantity))
1267                .always(function () {
1268                    _this._changedQuantity.bind(_this);
1269                    $buttons.prop('disabled', false);
1270                    $item.loadingmask('remove');
1271                    if (_this.options.catalogWidget) {
1272                        _this.options.catalogWidget.catalog('updateCart');
1273                    }
1274                    _this._updateDropdownPosition();
1275                    _this.refresh();
1276                });
1277
1278            return false;
1279        },
1280
1281        _changedQuantity: function () {
1282            this.refresh();
1283        },
1284
1285        _handleCartItemNoteKeydown: function (event) {
1286            if (event.which === $.ui.keyCode.ENTER) {
1287                var _this = this,
1288                    $target = $(event.currentTarget),
1289                    $item = $target.closest("tr"),
1290                    $note = $item.find(".cart-item-note"),
1291                    hash = $item.data("hash"),
1292                    parent = $item.data("parent"),
1293                    item = this.options.cart.getItem(hash);
1294
1295                item.setNote($note.val());
1296
1297                $note.blur();
1298            }
1299        },
1300
1301        _handleCartItemNoteBlur: function (event) {
1302            var _this = this,
1303                $target = $(event.currentTarget),
1304                $item = $target.closest("tr"),
1305                $note = $item.find(".cart-item-note"),
1306                hash = $item.data("hash"),
1307                parent = $item.data("parent"),
1308                item = this.options.cart.getItem(hash);
1309
1310            item.setNote($note.val());
1311        },
1312
1313        _handleCartItemNoteSaveClick: function (event) {
1314            var _this = this,
1315                $target = $(event.currentTarget),
1316                $item = $target.closest("tr"),
1317                $note = $item.find(".cart-item-note"),
1318                hash = $item.data("hash"),
1319                parent = $item.data("parent"),
1320                item = this.options.cart.getItem(hash);
1321
1322            item.setNote($note.val());
1323        },
1324
1325        _handleCartItemClick: function (e) {
1326            var $item = $(e.currentTarget),
1327                hash = $item.data("hash"),
1328                parent = $item.data("parent"),
1329                item = this.options.cart.getItem(hash, parent);
1330
1331            this._notifyItemClicked($item, item);
1332        },
1333
1334        _notifyItemClicked: function ($item, item) {
1335            if (this.isHighlightedItem($item)) {
1336                this._hideAddonPanel();
1337                this._highlightNone();
1338                return;
1339            }
1340
1341            // Only items with addons can be selected
1342            if (!item.setAddonsByIds) {
1343                return;
1344            }
1345
1346            if (item.possibleAddons && item.possibleAddons.length) {
1347                this._showAddonPanel(item);
1348                this._highlightItem($item, item);
1349            } else {
1350                this._hideAddonPanel();
1351            }
1352        },
1353
1354        _showAddonPanel: function (item) {
1355            this._getAddonPanel().show();
1356            this._positionAddonPanel();
1357            this.refreshAddonPanelContents(item);
1358        },
1359
1360        _maybeHideAddonPanel: function () {
1361            if (this.$addonPanel) {
1362                this._hideAddonPanel();
1363            }
1364        },
1365
1366        _hideAddonPanel: function () {
1367            this._getAddonPanel().hide();
1368            this._highlightNone();
1369        },
1370
1371        _createAddonPanel: function (e) {
1372            var _this = this,
1373                $panel = $("<div/>");
1374            $panel[0].className = 'rec1-cart-dropdown-addon-panel ui-dropdown';
1375            $panel[0].style.display = 'none';
1376            $panel.on("click", ".addon-panel-save", function (e) {
1377                _this._saveAddonPanel();
1378                _this._hideAddonPanel();
1379
1380                if (_this.options.selectLatestAddons) {
1381                    _this.options.selectLatestAddons = false;
1382                } else {
1383                    e.preventDefault();
1384                    e.stopPropagation();
1385                }
1386            });
1387            // Don't let our dropdown close
1388            $panel.on("click", function (event) {
1389                event.stopPropagation();
1390            });
1391            this.dropdown.before($panel);
1392            return $panel;
1393        },
1394
1395        _renderAddonPanelContents: function (item) {
1396            var _this = this;
1397            var html = '<div class="heading-muted text-center">OPTIONS/ADDONS (' + item.description + ')</div>';
1398            html += '<ul class="ui-listview" data-checkable="true" data-inset="false">';
1399            //'<li class="ui-listview-heading">ADDONS</li>';
1400
1401
1402            //<li class="checkable
1403            $.each(item.possibleAddons, function (index, addon) {
1404                var hasAddon = item.hasAddon(addon.id),
1405                    checked = hasAddon ? 'checked' : '';
1406
1407                html += '<li class="checkable">' +
1408                    '<label>' +
1409                    '<input type="checkbox" name="addons[]" value="' + addon.id + '" ' + checked + '>
1409' +
1410                    '<h2>' + addon.name + '</h2>' +
1411                    _this._renderAddonAmountBadge(addon)
1412                '</label>' +
1413                '</li>';
1414            });
1415
1416            html += '</ul>';
1417
1418            html += '<div class="ui-offset-lg text-center">' +
1419                '<button class="btn btn-primary addon-panel-save" type="button">Save</button>' +
1420                '</div>';
1421
1422            return html;
1423        },
1424
1425        _renderAddonAmountBadge: function (addon) {
1426            if (addon.amount != 0) {
1427                var displayAmount = addon.meas == "1"
1428                    ? addon.amount + '%'
1429                    : '$' + parseFloat(addon.amount).toFixed(2);
1430
1431                var html = '<div class="badge listview-badge">' + displayAmount + '</div>';
1432                return html;
1433            }
1434            return '';
1435        },
1436
1437        refreshAddonPanelContents: function (item) {
1438            this.$addonPanel.html(this._renderAddonPanelContents(item)).enhanceWithin();
1439        },
1440
1441
1442        _positionAddonPanel: function () {
1443            this.$addonPanel.position({
1444                my: 'right top',
1445                at: 'left top+40',
1446                of: this.dropdown
1447            });
1448        },
1449
1450        _getAddonPanel: function () {
1451            this.$addonPanel = this.$addonPanel || this._createAddonPanel();
1452            return this.$addonPanel;
1453        },
1454
1455        _saveAddonPanel: function () {
1456            var item = this._highlightedItem,
1457                addonIds = $.map(this.$addonPanel.find("input").serializeArray(), function (paramElement) {
1458                    return paramElement.value;
1459                });
1460
1461            // do the save here
1462            item.setAddonsByIds(addonIds);
1463        },
1464
1465        _highlightNone: function () {
1466            if (this.$highlightedItem) {
1467                this.$highlightedItem.removeClass(this.options.highlightClass);
1468                this.$highlightedItem = null;
1469                this._highlightedItem = null;
1470            }
1471        },
1472
1473        _highlightItem: function ($item, item) {
1474            this._highlightNone();
1475            this.$highlightedItem = $item;
1476            this._highlightedItem = item;
1477            $item.addClass(this.options.highlightClass).removeClass("new-item");
1478        },
1479
1480        isHighlightedItem: function ($item) {
1481            return this.$highlightedItem && this.$highlightedItem[0] === $item[0];
1482        },
1483
1484        _handleThermalPrintClick: function (e) {
1485            this._printReceipt(true);
1486        },
1487
1488        logDecline: function (paymentData, amount) {
1489            var data = {
1490                amount: amount,
1491                payer: 0,
1492                preCapturedData: JSON.stringify(paymentData)
1493            };
1494
1495            var result = this.options.cart.catalog.logDecline(0, data);
1496            return result;
1497        },
1498
1499        _updateDropdownPosition: function() {
1500            // For Bootstrap 3: recalculate dropdown position after content changes
1501            if (typeof $.fn.dropdown === 'function' && typeof bootstrap === 'undefined') {
1502                this.dropdown.dropdown('update');
1503            }
1504        }
1505
1506    });
1507
1508})(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.