PageSourceSearch

https://www.segurancarodoviaria.pt/App_Plugins/Merchello/client/js/merchello.ui.js

js segurancarodoviaria.pt collected 2026-09-24 12:38:46 UTC 51,774 bytes, 1,465 lines download raw bytes

1/*! MUI
2 * https://merchello.com
3 * Copyright (c) 2018 Across the Pond, LLC.
4 * Licensed 
5 */
6
7
8// Generic Merchello UI scripts.
9// Requires JQuery version 1.10.2 or higher
10//          JQuery unobrusive
11//          underscore.js
12var MUI = (function() {
13    
14    // If DEBUG_MODE is true allows messages to be written to the console
15    // THESE SHOULD be set to false before deploying to production!!!!!
16    var DEBUG_MODE = {
17        events: false,
18        console: false
19    };
20
21    // Private members
22    var eventHandlers = [];
23
24    // Initialization
25    function init() {
26        $(document).ready(function() {
27            // initialize the logger module
28            MUI.Logger.init();
29            // intialize the notifications
30            MUI.Notify.init()
31            // initialize the forms
32            MUI.Forms.init();
33            // initialize the add item module
34            MUI.AddItem.init();
35            // initialize the basket module
36            MUI.Basket.init();
37            // initialize the wish list module
38            MUI.WishList.init();
39            // initialize the checkout module
40            MUI.Checkout.init();
41            // initialize the labels
42            MUI.Labels.init();
43        });
44    }
45    
46    function hasLogger() {
47        return MUI.Logger.hasLogger;
48    }
49
50    //// Registers an MUI event
51    function registerEvent(evt, callback) {
52        try
53        {
54            var existing = _.find(eventHandlers, function(ev) { return ev.event === evt; });
55            if (existing !== undefined && existing !== null) {
56                if (callback !== undefined && typeof callback === "function") {
57                    existing.callbacks.push(callback);
58                }
59            } else {
60                var callbacks = [];
61                if (callback !== undefined && typeof callback === "function") {
62                    callbacks.push(callback);
63                }
64                eventHandlers.push({ event: evt, callbacks: callbacks });
65            }
66        }
67        catch(err) {
68            MUI.Logger.captureError(err);
69        }
70    }
71
72    /// emit the event
73    function trigger(name, args) {
74        var existing = _.find(eventHandlers, function(ev) { return ev.event === name; });
75        if (existing === undefined || existing === null) return;
76
77        // execute each of the registered callbacks
78        _.each(existing.callbacks, function(cb) {
79            try {
80                cb(name, args);
81            }
82            catch(err) {
83                MUI.Logger.captureError(err, {
84                    extra: {
85                        eventName: name,
86                        args: args
87                    }
88                });
89            }
90        });
91    }
92
93    // create a generic cache of functions, where fn is the function to retrieve and execute for a value.
94    // also ensures, the function is executed once and a single value is returned.
95    function createCache(fn) {
96        var cache = {};
97        return function( key, callback ) {
98            if ( !cache[ key ] ) {
99                cache[ key ] = $.Deferred(function( defer ) {
100                    fn( defer, key );
101                }).promise();
102            }
103            return cache[ key ].done( callback );
104        };
105    }
106
107    // utility method to map the event name from an alias so they can be
108    // more easily referenced in other modules
109    function getEventNameByAlias(events, alias) {
110        var evt = _.find(events, function(e) { return e.alias === alias});
111        return evt === undefined ? '' : evt.name;
112    }
113
114    // writes events to debug console
115    function debugConsoleEvents(events) {
116        if (DEBUG_MODE.events) {
117            _.each(events, function(ev) {
118                MUI.on(ev.name, function(name, args) {
119                    console.info(ev);
120                    console.info(args === undefined ? 'No args' : args);
121                });
122            });
123        }
124    }
125
126    // write to debug console if in debug mode
127    function debugConsole(obj) {
128        if (DEBUG_MODE.console) {
129            console.info(obj);
130        }
131    }
132    
133    // exposed members
134    return {
135        // ensures the settings object is created
136        Settings: {
137            Notifications: {},
138            Endpoints: {}
139        },
140        // ensures the services object is created
141        Services: {},
142        init: init,
143        hasLogger: hasLogger,
144        createCache: createCache,
145        on: registerEvent,
146        emit: trigger,
147        getEventNameByAlias: getEventNameByAlias,
148        debugConsoleEvents : debugConsoleEvents,
149        debugConsole: debugConsole
150    }
151
152})();
153
154//// The logger
155// If you have a remove logger you can wire it in here.
156MUI.Logger = {
157
158    hasLogger: false,
159    
160    // Initializes the Sentry (Raven) logger
161    init: function() {
162        return;
163    },
164
165    // Sets the module context
166    setSiteContext: function(siteAlias) {
167        return;
168    },
169
170    setUserContext: function(email) {
171        return;
172    },
173
174    // Captures an error
175    captureError: function(e, args) {
176        var consoleLog = args === undefined ? e : { error: e, args: args };
177        MUI.debugConsole(consoleLog);
178        return;
179    },
180
181    captureMessage: function(msg, args) {
182        var consoleLog = args === undefined ? message : { message: msg, args: args };
183        MUI.debugConsole(consoleLog);
184        return;
185    },
186
187    isReady: function() {
188        return MUI.Logger.hasLogger;
189        // this can be used to test a logger is available ex. getsentry.com -> Raven.isSetup();
190    }
191};
192
193MUI.Services.Braintree = {
194
195    initialized: false,
196
197    acceptCards: [],
198    
199    currentCard: {},
200
201    events : [
202        { attempt: 'unbindValidation', name: 'Braintree.UnbindValidation' },
203        { attempt: 'cardTypeChanged',  name: 'Braintree.CardTypeChange' },
204        { attempt: 'verified', name: 'Braintree.CardVerified' }
205    ],
206
207    // Load the required assets
208    loadAssets: function(callback) {
209        // load the braintree script and validation for cc with a promise
210        // this also asserts if the customer goes back and changes the method to another
211        // braintree method, that these are only loaded once
212        $.when(
213            MUI.Assets.cachedGetScript('/App_Plugins/Merchello/client/lib/card-validator.min.js'), // Verify we don't need this anymore
214            MUI.Assets.cachedGetScript('//js.braintreegateway.com/web/3.38.1/js/client.min.js'),
215            MUI.Assets.cachedGetScript('//js.braintreegateway.com/web/3.38.1/js/hosted-fields.min.js'),
216            MUI.Assets.cachedGetScript('//js.braintreegateway.com/web/3.38.1/js/paypal.min.js')
217            // MUI.Assets.cachedGetScript('//js.braintreegateway.com/v2/braintree.js')
218        ).then(function() {
219                MUI.Services.Braintree.initialized = true;
220                if (callback !== undefined) callback();
221            },
222            function(err) { MUI.Logger.captureError(err); });
223
224    }
225};
226
227//// A class to deal with AddItem box JQuery functions
228//// This looks for a form with data attribute "data-muifrm='additem'"
229MUI.AddItem = {
230
231    events : [
232        { alias: 'added', name: 'AddItem.added' },
233        { alias: 'tableCreated', name: 'AddItem.tableCreated' }
234    ],
235
236    addItemSuccess: 'Successfully added item to basket',
237
238    postUrl: '',
239
240    forms: [],
241
242    // Initializes the AddItem object
243    init: function() {
244
245        if (MUI.Settings.Endpoints.basketSurface !== undefined && MUI.Settings.Endpoints.basketSurface !== '') {
246            MUI.AddItem.postUrl = MUI.Settings.Endpoints.basketSurface + 'AddBasketItem';
247        }
248
249        // find all the product keys to load the data tables
250        var containers = $('[data-muivalue="product"]');
251
252        if (containers.length > 0) {
253            $.each(containers, function(idx, c) {
254                var key = '';
255                key = $(c).data('muikey');
256                if (key !== undefined && key !== '') {
257                    // verify the key does not already exist
258                    // this can happen if the product exists on the page more than once
259                    var found = _.find(MUI.AddItem.bind.keys, function(k) { return k === key });
260                    if (found === undefined) {
261                        MUI.AddItem.bind.keys.push(key);
262                    }
263                }
264            });
265        }
266
267        // keys are all loaded now get the data tables
268        loadData();
269
270        // loads the product data tables after the keys have been acquired
271        function loadData() {
272            if (MUI.Settings.Endpoints.productTableApi !== undefined && MUI.Settings.Endpoints.productTableApi 
272=== '') return;
273            if (MUI.AddItem.bind.keys.length > 0) {
274                var url = MUI.Settings.Endpoints.productTableApi + 'PostGetProductDataTables';
275
276                $.ajax({
277                    type: 'POST',
278                    url: url,
279                    dataType: 'json',
280                    contentType: "application/json; charset=utf-8",
281                    data: JSON.stringify(MUI.AddItem.bind.keys)
282                }).then(function (tables) {
283
284                    // build the product data tables as JS objects so we can use prototypes
285                    if (tables.length > 0) {
286                        $.each(tables, function(tindx, tbl) {
287                           var pdt = new MUI.AddItem.ProductDataTable();
288                            pdt.productKey = tbl.productKey;
289                            $.each(tbl.rows, function(rindx, dataRow) {
290                                pdt.rows.push($.extend(new MUI.AddItem.ProductDataTableRow(), dataRow));
291                            });
292                            MUI.emit('AddItem.tableCreated', pdt);
293                            MUI.AddItem.dataTables.push(pdt);
294                            //MUI.AddItem.bind.controls(pdt);
295                        });
296                    }
297
298
299                    // find all of the AddItem forms
300                    MUI.AddItem.bind.forms();
301
302                }, function (err) {
303                    MUI.Notify.error(err);
304                    MUI.Logger.captureError(err);
305                });
306            }
307        }
308
309        // Debug
310        MUI.debugConsoleEvents(MUI.AddItem.events);
311    },
312
313    bind: {
314
315        // An array of product keys that should be bound
316        keys : [],
317
318        forms: function() {
319             var frms = $('[data-muifrm="additem"]');
320             if (frms.length > 0) {
321                MUI.AddItem.Forms = frms;
322                $.each(frms, function(idx, frm) {
323                    MUI.AddItem.bind.form(frm);
324                });
325             }
326        },
327
328        // Bind the form;
329        form: function(frm) {
330
331            // If the endpoint is set, override the form to do AJAX posts
332            if (MUI.AddItem.postUrl !== '') {
333
334                $(frm).submit(function () {
335                    $.ajax({
336                        type: 'POST',
337                        url: MUI.AddItem.postUrl,
338                        data: $(this).serialize()
339                    }).then(function(result) {
340                        MUI.emit('AddItem.added', result);
341                        MUI.Notify.info(MUI.AddItem.addItemSuccess);
342                    }, function(err) {
343                       MUI.Logger.captureError(err); 
344                    });
345                    return false;
346                });
347            }
348
349            var key = $(frm).closest('[data-muivalue="product"]').data('muikey');
350
351            if (key !== undefined) {
352                MUI.AddItem.bind.controls(frm, key);
353            }
354        },
355
356        // Bind the form
357        // This also requires the API promise to be completed.
358        // parameter: pdt - Product Data Table object
359        // TODO - this will need to change in 2.1.0
360        // TODO - when we can designate which sort of form element to use for option choice selction
361        controls: function(frm, key) {
362            var options = MUI.AddItem.getOptionsForProduct(frm, key);
363
364            if (options.length > 0) {
365                $.each(options, function(idx, o) {
366                   $(o).change(function() {
367                       // TODO filter lists to ensure all choices are available
368                       MUI.AddItem.updateVariantPricing(frm, key);
369                       MUI.AddItem.ensureInventorySettings(frm, key);
370                   });
371                });
372            }
373            // initial pricing (on load)
374            MUI.AddItem.updateVariantPricing(frm, key);
375            MUI.AddItem.ensureInventorySettings(frm, key);
376        }
377    },
378
379
380    // Product data tables
381    // This is essentially a cache of product variant values so that we do not have to hit the API
382    // on every option value change
383    dataTables : [],
384
385    // updates the variant pricing from the product data table data
386    updateVariantPricing: function(frm, key) {
387        // find the append point
388        var well = $(frm).closest('[data-muivalue="product"]');
389        if (well.length === 0) return;
390        var appendTo = $(well).find('[data-muivalue="variantprice"]');
391        if (appendTo.length > 0) {
392            appendTo = appendTo[0];
393        } else {
394            // There is not reason to continue if the append point cannot be found
395            return;
396        }
397
398        var row = MUI.AddItem.findProductDataTableRow(frm, key);
399
400        if (row !== undefined) {
401
402            // update the price
403            var html = '';
404            if (row.onSale) {
405                html = "<span class='sale-price'>" + row.formattedSalePrice + "</span> <span class='original-price'>" + row.formattedPrice + "</span>";
406            } else {
407                html = "<span>" + row.formattedPrice + "</span>";
408            }
409
410            $(appendTo).html(html);
411        }
412
413    },
414
415    ensureInventorySettings: function(frm, key) {
416        var row = MUI.AddItem.findProductDataTableRow(frm, key);
417        if (row !== undefined) {
418            var appendTo = $(frm).find('[data-muiinv="' + row.productKey + '"]');
419            if (appendTo.length > 0) {
420
421                var showBtn = row.outOfStockPurchase || row.inventoryCount > 0;
422
423                var html = '';
424                if (row.inventoryCount > 0) {
425                    html = MUI.Settings.Labels.InStock.replace('@0', row.inventoryCount);
426                } else {
427
428                    if (row.outOfStockPurchase) {
429                        html = MUI.Settings.Labels.OutOfStockAllowPurchase;
430                    } else {
431                        html = MUI.Settings.Labels.OutOfStock;
432                    }
433                    html = "<span>" + html + "</span>";
434                }
435
436                $(appendTo).html(html);
437                var btn = $(frm).find(':submit');
438
439                if (showBtn) {
440                    $(btn).show();
441                } else {
442                    $(btn).hide();
443                }
444            }
445        }
446    },
447
448    getOptionsForProduct: function(frm, key) {
449        var container = $('[data-muikey="' + key + '"]');
450        if (container.length > 0) {
451            var parents = $(frm).find('[data-muioption]');
452            var options = [];
453            $.each(parents, function(idx, p) {
454                // find the select
455                options.push($(p).find('select'));
456            });
457            return options;
458        } else {
459            return [];
460        }
461    },
462
463    findProductDataTableRow: function(frm, key) {
464        // get all of the options associated with this product so we can
465        // find the matching data row in the product data table
466        var options = MUI.AddItem.getOptionsForProduct(frm, key);
467
468        var row = undefined;
469        if (options.length > 0) {
470            // get the current selections
471            var keys = [];
472            $.each(options, function (idx, o) {
473                keys.push($(o).val());
474            });
475
476            var pdt = MUI.AddItem.getProductDataTable(key);
477            if (pdt !== undefined) {
478                if (keys.length > 0) {
479                    row = pdt.getRowByMatchKeys(keys);
480                }
481            }
482        }
483
484        return row;
485    },
486
487    // Gets the data table
488    // uses underscore
489    getProductDataTable: function (pkey) {
490        return _.find(MUI.AddItem.dataTables, function (t) { return t.productKey === pkey; });
491    }
492};
493
494MUI.Assets = {
495
496    // gets a script ensuring it is only ever loaded once.
497    // currently used in loading Braintree scripts
498    // https://learn.jquery.com/code-organization/deferreds/examples/
499    cachedGetScript: MUI.createCache(function(defer, url) {
500        $.getScript( url ).then( defer.resolve, defer.reject );
501    })
502    
503};
504
505//// A class to deal with basket JQuery functions
506//// This looks for a form with data attribute "data-muifrm='basket'"
507MUI.Basket = {
508
509    // initialize the basket
510    init: function() {
511        if (MUI.Settings.Endpoints.basketSurface === undefined || MUI.Settings.Endpoints.basketSurface === '') return;
512        
513        var frm = $('[data-muifrm="basket"]');
514        if (frm.length > 0) {
515            MUI.Basket.bind.form(frm[0]);
516        }
517    },
518
519    // binds the form
520    bind: {
521        form: function(frm) {
522
523            // Watch for changes in the input fields
524            $(frm).find(':input[data-muiaction="updatequantity"]').change(function() {
525                var frmRef = $(this).closest('form');
526
527                // post the form to update the basket quantities
528                var url = MUI.Settings.Endpoints.basketSurface + 'UpdateBasket';
529                $.ajax({
530                    type: 'POST',
531                    url: url,
532                    data: $(frmRef).serialize()
533                }).then(function(results) {
534
535                    // update the line items sub totals
536                    $.each(results.UpdatedItems, function(idx, item) {
537                        var hid = $('input:hidden[value="' + item.Key + '"]')
538                        if (hid.length > 0) {
539                            //var subtotal = $(hid).closest('tr').find('[data-muivalue="linetotal"]');
540                            var subtotal = $(hid).closest('.row').find('[data-muivalue="linetotal"]');
541                            if (subtotal.length > 0) {
542                                $(subtotal).html(item.FormattedTotal);
543                            }
544                        }
545                    });
546
547                    // set the new basket total
548                    var total = $(frmRef).find('[data-muivalue="total"]');
549                    if (total.length > 0) {
550                        $(total).html(results.FormattedTotal);
551                    }
552
553                    // Emit the event so that labels can update if handled
554                    MUI.emit('AddItem.added', results);
555
556                }, function(err) {
557                   MUI.Logger.captureError(err);
558                });
559            });
560
561
562            var btn = $(frm).find('[data-muibtn="update"]');
563            if (btn.length > 0) {
564                $(btn).hide();
565            }
566
567        }
568    }
569};
570
571MUI.Checkout = {
572
573    Payment: {},
574
575    init: function() {
576        // Initialize the address module
577        MUI.Checkout.Address.init();
578        // Initialize the payment module
579        MUI.Checkout.Payment.Braintree.init();
580    }
581    
582};
583
584MUI.Forms = {
585    init: function() {
586
587        // add the custom validator for MVC unobtrusive validation
588        $.validator.addMethod('validateexpiresdate', function(value, element, params) {
589
590            // Make sure the value has a length of 5
591            if (value.length != 5) {
592                return false;
593            }
594
595            var today = new Date();
596            var thisYear = today.getFullYear() - 2000;
597            var expMonth = +value.substr(0, 2);
598            var expYear = +value.substr(3, 4);
599
600            return (expMonth >= 1
601            && expMonth <= 12
602            && (expYear >= thisYear && expYear < thisYear + 20)
603            && (expYear == thisYear ? expMonth >= (today.getMonth() + 1) : true));
604
605        });
606
607        $.validator.unobtrusive.adapters.addBool('validateexpiresdate');
608        
609    },
610
611    // Post a form and return the promise
612    post: function(frm, url) {
613        return MUI.resourcePromise(url, $(frm).serialize());
614    },
615    
616    // rebinds a form unobtrusive validation
617    rebind: function(frm) {
618        $.validator.unobtrusive.parse(frm);
619    },
620    
621    // validates the form
622    validate: function(frm) {
623
624        // obtain validator
625        var validator = $(frm).validate();
626
627        var isValid = true;
628        $(frm).find("input").each(function () {
629
630            // validate every input element inside this step
631            if (!validator.element(this)) {
632                isValid = false;
633            }
634        });
635
636        return isValid;
637    },
638
639    // tests a string to see if it is in a valid email format
640    isValidEmail: function(email) {
641        var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
642        return re.test(email);
643    }
644};
645
646//// A class to assist in updating labels in the UI
647//// This looks for a form with data attribute "data-muilabel='VALUE'"
648MUI.Labels = {
649    
650    init: function() {
651        // Event listener
652        MUI.on('AddItem.added', MUI.Labels.update.basketItemCount);
653    },
654    
655    update: {    
656        basketItemCount: function(evt, args) {
657            if (args.Success) {
658				var labelCount = $('[data-muilabel="basketcount"]');
659				var labelPrice = $('[data-muilabel="basketprice"]'); 
660                if (labelCount.length > 0) {
661					$(labelCount).html(args.ItemCount);
662					//$(labelPrice).html(args.formattedSalePrice);
663                    $(labelPrice).html(args.FormattedTotal);
664                }
665            }
666        }
667    }
668};
669
670MUI.Notify = {
671
672    types: [
673        'success',
674        'info',
675        'error',
676        'warn'
677    ],
678
679    // Value to check to see if notifications are enabled and the bar has been appended to the page
680    enabled: false,
681    
682    bar: undefined,
683
684    overlay: undefined,
685
686    hasOverlay: false,
687
688    // initializes the Notify class
689    init: function() {
690        MUI.Notify.appendNotifyBar();
691    },
692
693    toggleOverlay: function() {
694      if (MUI.Notify.hasOverlay) {
695          var overlay = MUI.Notify.overlay;
696          $(overlay).toggle();
697      }
698    },
699
700
701    // renders an info message
702    info: function(msg) {
703      MUI.Notify.message(msg, 'info', 1000);
704    },
705
706    // renders a success message
707    success: function(msg) {
708      MUI.Notify.message(msg, 'success', 500);
709    },
710
711    // renders an error message
712    error: function(msg) {
713        MUI.Notify.message(msg, 'error', 1000);
714    },
715
716    // renders a warning message
717    warn: function(msg) {
718        MUI.Notify.message(msg, 'warn', 750);
719    },
720
721    // renders a message
722    message: function(msg, type, delay) {
723        if (MUI.Notify.bar !== undefined && MUI.Notify.enabled) {
724
725            if(delay === undefined) delay = 500;
726
727            var bar = MUI.Notify.bar;
728            var container = $(bar).find('[data-muivalue="nofity"]');
729            if (container.length > 0) {
730
731                // removes previous class from notify bar
732                reset();
733                // get the current css class
734                if (type === undefined) type = 'info';
735                var ref = MUI.Notify.getClassRef(type);
736                var css = MUI.Notify.getCssClass(ref);
737                $(bar).addClass(css);
738                $(container).html(msg);
739                $(bar).fadeIn().delay(delay).fadeOut();
740            }
741        }
742
743        // removes all the css classes
744        function reset() {
745            var bar = MUI.Notify.bar;
746            var refs = MUI.Notify.getClassRef('all');
747            $.each(refs, function(ref) {
748                var css = MUI.Notify.getCssClass(ref);
749                $(bar).removeClass(css);
750            });
751        }
752    },
753
754
755    getCssClass: function(ref) {
756        try {
757            var css = MUI.Settings.Notifications[ref];
758            if (css === undefined) {
759                MUI.Logger.captureMessage('Failed to find CSS class for ' + ref + '. Returning alert-info');
760                return 'alert-info';
761            } else {
762                return css;
763            }
764        } catch(err) {
765            MUI.Logger.captureError(err);
766        }
767    },
768
769    getClassRef: function (type) {
770        if (type === 'all') {
771            var refs = [];
772            $.each(MUI.Notify.types, function(t) {
773                refs.push(t + 'Css');
774            });
775            return refs;
776        } else {
777            var found = _.find(MUI.Notify.types, function(t) { return t === type });
778            return found === undefined ? 'infoCss' : type + 'Css';
779        }
780    },
781
782    // Appends the notify bar to the bottom of the current page
783    appendNotifyBar: function() {
784        if (MUI.Settings.Notifications.enabled !== undefined &&
785            MUI.Settings.Notifications.enabled === true &&
786            MUI.Settings.Notifications.template !== undefined &&
787            MUI.Settings.Notifications.template !== '') {
788
789            // ensure not exists
790            if($('[data-muinotify="notifybar"]').length === 0)
791            {
792                var div = MUI.Settings.Notifications.template;
793
794                $('body').append(div);
795                MUI.Notify.bar = $('[data-muinotify="notifybar"]');
796                $(MUI.Notify.bar).hide();
797                MUI.Notify.enabled = true;
798            }
799        }
800
801
802        if (MUI.Settings.Notifications.enabled !== undefined &&
803            MUI.Settings.Notifications.enabled === true &&
804            MUI.Settings.Notifications.overlay !== undefined &&
805            MUI.Settings.Notifications.overlay !== '') {
806
807            if($('[data-muinotify="overaly"]').length == 0) {
808                var div = MUI.Settings.Notifications.overlay;
809
810                $('body').append(div);
811                MUI.Notify.overlay = $('[data-muinotify="overlay"]');
812                $(MUI.Notify.overlay).hide();
813                MUI.Notify.hasOverlay = true;
814            }
815        }
816    }
817};
818
819MUI.Utilities = {
820    // Ensures a null or undefined value has either a value or a default value
821    // defaultValue itself defaults to an empty string.
822    EnsureNullAsValue: function(value, defaultValue) {
823        if (defaultValue === undefined) {
824            defaultValue = '';
825        }
826
827        return value === null || value === undefined ? defaultValue : value;
828    },
829
830    // Gets a query string parameter value
831    // http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
832    getQueryStringValue: function(name, url) {
833        if (!url) url = window.location.href;
834        url = url.toLowerCase(); // This is just to avoid case sensitiveness
835        name = name.replace(/[\[\]]/g, "\\$&").toLowerCase();// This is just to avoid case sensitiveness for query parameter name
836        var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
837            results = regex.exec(url);
838        if (!results) return undefined;
839        if (!results[2]) return '';
840        return decodeURIComponent(results[2].replace(/\+/g, " "));
841    },
842
843
844    // Credit card types
845    // Adapted from https://github.com/braintree/credit-card-type
846    CardTypes : {
847
848        types: [
849            {
850                niceType: 'Visa',
851                type: 'visa',
852                pattern: '^4\\d*$',
853                gaps: [4, 8, 12],
854                lengths: [16],
855                code: {
856                    name: 'CVV',
857                    size: 3
858                }
859            },
860            {
861                niceType: 'MasterCard',
862                type: 'master-card',
863                pattern: '^5[1-5]\\d*$',
864                gaps: [4, 8, 12],
865                lengths: [16],
866                code: {
867                    name: 'CVC',
868                    size: 3
869                }
870            },
871            {
872                niceType: 'American Express',
873                type: 'american-express',
874                pattern: '^3[47]\\d*$',
875                isAmex: true,
876                gaps: [4, 10],
877                lengths: [15],
878                code: {
879                    name: 'CID',
880                    size: 4
881                }
882            },
883            {
884                niceType: 'DinersClub',
885                type: 'diners-club',
886                pattern: '^3(0[0-5]|[689])\\d*$',
887                gaps: [4, 10],
888                lengths: [14],
889                code: {
890                    name: 'CVV',
891                    size: 3
892                }
893            },
894            {
895                niceType: 'Discover',
896                type: 'discover',
897                pattern: '^6(011|5|4[4-9])\\d*$',
898                gaps: [4, 8, 12],
899                lengths: [16],
900                code: {
901                    name: 'CID',
902                    size: 3
903                }
904            },
905            {
906                niceType: 'JCB',
907                type: 'jcb',
908                pattern: '^(2131|1800|35)\\d*$',
909                gaps: [4, 8, 12],
910                lengths: [16],
911                code: {
912                    name: 'CVV',
913                    size: 3
914                }
915            },
916            {
917                niceType: 'UnionPay',
918                type: 'unionpay',
919                pattern: '^62\\d*$',
920                gaps: [4, 8, 12],
921                lengths: [16, 17, 18, 19],
922                code: {
923                    name: 'CVN',
924                    size: 3
925                }
926            },
927            {
928                niceType: 'Maestro',
929                type: 'maestro',
930                pattern: '^(50|5[6-9]|6)\\d*$',
931                gaps: [4, 8, 12],
932                lengths: [12, 13, 14, 15, 16, 17, 18, 19],
933                code: {
934                    name: 'CVC',
935                    size: 3
936                }
937            }
938        ],
939
940        getCardType: function (cardNumber) {
941            var key, value;
942            var noMatch = {};
943
944            if (!isString(cardNumber)) {
945                return noMatch;
946            }
947
948            for (key in types) {
949                if (!MUI.Utilities.CardTypes.hasOwnProperty(key)) {
950                    continue;
951                }
952
953                value = MUI.Utilities.CardTypes.types[key];
954
955                if (RegExp(value.pattern).test(cardNumber)) {
956                    return clone(value);
957                }
958            }
959
960            return noMatch;
961        },
962
963        getCardByType: function (type) {
964            return _.find(MUI.Utilities.CardTypes.types, function (t) {
965                return t.type === type;
966            });
967        }
968    }
969};
970
971//// A class to deal with basket JQuery functions
972//// This looks for a form with data attribute "data-muifrm='wishlist'"
973MUI.WishList = {
974    
975    init: function() {
976        if (MUI.Settings.Endpoints.wishListSurface === undefined || MUI.Settings.Endpoints.wishListSurface === '') return;
977
978        var frm = $('[data-muifrm="wishlist"]');
979        if (frm.length > 0) {
980            MUI.WishList.bind.form(frm[0]);
981        }
982    },
983
984    bind: {
985
986        form: function(frm) {
987            // Watch for changes in the input fields
988            $(frm).find(':input[data-muiaction="updatequantity"]').change(function() {
989                var frmRef = $(this).closest('form');
990
991                // post the form to update the basket quantities
992                var url = MUI.Settings.Endpoints.wishListSurface + 'UpdateWishList';
993                $.ajax({
994                    type: 'POST',
995                    url: url,
996                    data: $(frmRef).serialize()
997                }).then(function(results) {
998
999                    // update the line items sub totals
1000                    $.each(results.UpdatedItems, function(idx, item) {
1001                        var hid = $('input:hidden[value="' + item.Key + '"]')
1002                        if (hid.length > 0) {
1003                            var subtotal = $(hid).closest('tr').find('[data-muivalue="linetotal"]');
1004                            if (subtotal.length > 0) {
1005                                $(subtotal).html(item.FormattedTotal);
1006                            }
1007                        }
1008                    });
1009
1010                    // set the new basket total
1011                    var total = $(frmRef).find('[data-muivalue="total"]');
1012                    if (total.length > 0) {
1013                        $(total).html(results.FormattedTotal);
1014                    }
1015
1016                }, function(err) {
1017                    MUI.Logger.captureError(err);
1018                });
1019            });
1020
1021
1022            var btn = $(frm).find('[data-muibtn="update"]');
1023            if (btn.length > 0) {
1024                $(btn).hide();
1025            }
1026        }
1027    }
1028    
1029}
1030
1031//// Cart model
1032MUI.AddItem.ProductDataTable = function() {
1033    var self = this;
1034    self.productKey = '';
1035    self.rows = [];
1036};
1037
1038/// Cart Model Prototypes
1039//  ProductDataTable
1040MUI.AddItem.ProductDataTable.prototype = (function() {
1041
1042    // used to match a pricing row with the drop down selection
1043    function getRowByMatchKeys(keys) {
1044        var row = _.find(this.rows, function (r) {
1045            var test = _.intersection(r.matchKeys, keys);
1046            return test.length === keys.length;
1047        });
1048        return row;
1049    }
1050
1051    return {
1052        getRowByMatchKeys: getRowByMatchKeys
1053    };
1054
1055}());
1056
1057MUI.AddItem.ProductDataTableRow = function() {
1058    var self = this;
1059    self.productKey = '',
1060    self.productVariantKey = '',
1061    self.sku = '';
1062    self.isForVariant = false;
1063    self.onSale = false;
1064    self.salePrice = 0;
1065    self.salePriceIncVat = 0;
1066    self.price = 0;
1067    self.priceIncVat = 0;
1068    self.isAvailable = true;
1069    self.matchKeys = [];
1070    self.inventoryCount = 0;
1071    self.outOfStockPurchase = false;
1072};
1073
1074MUI.Checkout.Address = {
1075  
1076    addressType: '',
1077    
1078    init: function() {
1079        if (MUI.Settings.Endpoints.countryRegionApi === undefined || MUI.Settings.Endpoints.countryRegionApi === '') return;
1080
1081        var frm = $('[data-muistage="BillingAddress"]');
1082
1083        if (frm.length > 0) {
1084            MUI.Checkout.Address.addressType = "Billing";
1085            MUI.Checkout.Address.bind.form(frm[0]);
1086            var countryddl = frm.find(':input[data-muiaction="populateregion"]');
1087            //if country not selected, set to default
1088            if (countryddl.val().length == 0) {
1089                if (MUI.Settings.Defaults.BillingCountryCode.length > 0) {
1090                    countryddl.val(MUI.Settings.Defaults.BillingCountryCode);
1091                }
1092            }
1093            MUI.Checkout.Address.populateRegion(countryddl);
1094
1095        } else {
1096            frm = $('[data-muistage="ShippingAddress"]');
1097            if (frm.length > 0) {
1098                MUI.Checkout.Address.addressType = "Shipping";
1099                MUI.Checkout.Address.bind.form(frm[0]);
1100                var countryddl = frm.find(':input[data-muiaction="populateregion"]');
1101                //if country not selected, set to default
1102                if (countryddl.val().length == 0) {
1103                    if (MUI.Settings.Defaults.ShippingCountryCode.length > 0) {
1104                        countryddl.val(MUI.Settings.Defaults.ShippingCountryCode);
1105                    }
1106                }
1107                MUI.Checkout.Address.populateRegion(countryddl);
1108            }
1109        }
1110    },
1111
1112    bind: {
1113
1114        form: function (frm) {
1115            // Watch for changes in the input fields
1116            $(frm).find(':input[data-muiaction="populateregion"]').change(function () {
1117                MUI.Checkout.Address.populateRegion($(this));
1118            });
1119
1120            $(frm).find(':input[data-muiaction="updateregion"]').change(function () {
1121                var frmRef = $(this).closest('form');
1122                //keep the select list and the region textbox in sync
1123                frmRef.find(':input[data-muivalue="region"]').val(frmRef.find(':input[data-muiaction="updateregion"]').val());
1124            });
1125        }
1126    },
1127
1128    populateRegion: function (countryCode) {
1129        var frmRef = countryCode.closest('form');
1130        // post the country to get the regions for that country
1131        var url = MUI.Settings.Endpoints.countryRegionApi + 'PostGetRegionsForCountry?countryCode=' + countryCode.val();
1132        $.ajax({
1133            type: 'POST',
1134            url: url
1135        }).then(function (results) {
1136            var regionddl = frmRef.find(':input[data-muiaction="updateregion"]');
1137            var regiontb = frmRef.find(':input[data-muivalue="region"]');
1138            if (results.length > 0) {
1139                //remove all but the first option
1140                $('option', regionddl).not(':eq(0)').remove();
1141                $.each(results, function (idx, item) {
1142                    regionddl.append($("<option></option>").attr("value", item.code).text(item.name));
1143                });
1144                //if region already defined and is present in the drop down, select it
1145                if (regiontb.val().length > 0 && regionddl.find('option[value="' + regiontb.val() + '"]').length > 0) {//and it exists in the drop down) {
1146                    //set ddl to current value
1147                    regionddl.val(regiontb.val());
1148                }
1149                else {
1150                    regiontb.val('');
1151                }
1152                //show region ddl and hide region textbox
1153                regionddl.show();
1154                regiontb.hide();
1155                //$('#UseForShipping').attr('disabled', false);
1156            }
1157            else {
1158                //hide region ddl and show region textbox
1159                regionddl.hide();
1160                regiontb.val('').show();
1161                //also, if there is no region then billing and shipping must be different??
1162                //$('#UseForShipping').prop('checked', false).attr('disabled', true);
1163            }
1164        }, function (err) {
1165            MUI.Logger.captureError(err);
1166        });
1167    }
1168    
1169    
1170};
1171
1172//// A class to manage braintree payments
1173MUI.Checkout.Payment.Braintree = {
1174
1175    invoiceKey: '',
1176
1177    token: '',
1178    
1179    events : [
1180        { alias: 'btsuccess', name: 'Braintree.success' }
1181    ],
1182
1183    // initialize payment form
1184    init: function() {
1185        var btforms = MUI.Checkout.Payment.Braintree.getBraintreeForm();
1186        if ($(btforms).length > 0) {
1187            if (!MUI.Services.Braintree.initialized) {
1188                MUI.Services.Braintree.loadAssets(function() {
1189                    MUI.Checkout.Payment.Braintree.bind.allForms();
1190                });
1191            } else {
1192                MUI.Checkout.Payment.Braintree.bind.allForms();
1193            }
1194        }
1195    },
1196
1197    bind: {
1198
1199        allForms: function() {
1200
1201            if($('.paypal-button').length > 0) {
1202                // binds the PayPal on time transaction
1203                MUI.Checkout.Payment.Braintree.bind.btpaypal.init();
1204            } else {
1205                // binds the standard transaction
1206                MUI.Checkout.Payment.Braintree.bind.btstandard.init();
1207            }
1208        },
1209        
1210        btstandard: {
1211
1212            init: function() {
1213                MUI.debugConsole('Initializing Braintree CC form');
1214
1215                var form = MUI.Checkout.Payment.Braintree.getBraintreeForm();
1216
1217                if (form.length > 0) {
1218
1219
1220                    var token = MUI.Checkout.Payment.Braintree.getBraintreeToken();
1221
1222                    braintree.client.create({
1223                        authorization: token
1224                    }, function (err, clientInstance) {
1225                        if (err) {
1226                            console.error(err);
1227                            return;
1228                        }
1229                        braintree.hostedFields.create({
1230                            client: clientInstance,
1231                            styles: {
1232                                'input': {
1233                                    'font-size': '14px',
1234                                    'font-family': 'helvetica, tahoma, calibri, sans-serif',
1235                                    'color': '#3a3a3a'
1236                                },
1237                                ':focus': {
1238                                    'color': 'black'
1239                                }
1240                            },
1241                            fields: {
1242                                number: {
1243                                    selector: '#card-number',
1244                                    placeholder: '4111 1111 1111 1111'
1245                                },
1246                                cvv: {
1247                                    selector: '#cvv',
1248                                    placeholder: '123'
1249                                },
1250                                expirationMonth: {
1251                                    selector: '#expiration-month',
1252                                    placeholder: 'MM'
1253                                },
1254                                expirationYear: {
1255                                    selector: '#expiration-year',
1256                                    placeholder: 'YY'
1257                                },
1258                                postalCode: {
1259                                    selector: '#postal-code',
1260                                    placeholder: '90210'
1261                                }
1262                            }
1263                        }, function (err, hostedFieldsInstance) {
1264                            if (err) {
1265                                console.error(err);
1266                                return;
1267                            }
1268
1269                            hostedFieldsInstance.on('validityChange', function (event) {
1270                                var field = event.fields[event.emittedBy];
1271
1272                                if (field.isValid) {
1273                                    if (event.emittedBy === 'expirationMonth' || event.emittedBy === 'expirationYear') {
1274                                        if (!event.fields.expirationMonth.isValid || !event.fields.expirationYear.isValid) {
1275                                            return;
1276                                        }
1277                                    } else if (event.emittedBy === 'number') {
1278                                        $('#card-number').next('span').text('');
1279                                    }
1280
1281                                    // Apply styling for a valid field
1282                                    $(field.container).parents('.form-group').addClass('has-success');
1283                                } else if (field.isPotentiallyValid) {
1284                                    // Remove styling  from potentially valid fields
1285                                    $(field.container).parents('.form-group').removeClass('has-warning');
1286                                    $(field.container).parents('.form-group').removeClass('has-success');
1287                                    if (event.emittedBy === 'number') {
1288                                        $('#card-number').next('span').text('');
1289                                    }
1290                                } else {
1291                                    // Add styling to invalid fields
1292                                    $(field.container).parents('.form-group').addClass('has-warning');
1293                                    // Add helper text for an invalid card number
1294                                    if (event.emittedBy === 'number') {
1295                                        $('#card-number').next('span').text('Looks like this card number has an error.');
1296                                    }
1297                                }
1298                            });
1299
1300                            hostedFieldsInstance.on('cardTypeChange', function (event) {
1301                                // Handle a field's change, such as a change in validity or credit card type
1302                                if (event.cards.length === 1) {
1303                                    $('#card-type').text(event.cards[0].niceType);
1304                                } else {
1305                                    $('#card-type').text('Card');
1306                                }
1307                            });
1308
1309                            $('.panel-body').submit(function (event) {
1310                                event.preventDefault();
1311                                hostedFieldsInstance.tokenize(function (err, payload) {
1312                                    if (err) {
1313                                        console.error(err);
1314                                        return;
1315                                    }
1316
1317                                    var data = { nonce: payload.nonce };
1318                                    var method = 'Process';
1319
1320                                    if (MUI.Checkout.Payment.Braintree.invoiceKey !== '') {
1321                                        data.invoiceKey = MUI.Checkout.Payment.Braintree.invoiceKey;
1322                                        method = 'Retry';
1323                                    }
1324
1325                                    var url = MUI.Settings.Endpoints.braintreeStandardCcSurface + method;
1326
1327                                    MUI.Checkout.Payment.Braintree.postBraintreeForm(url, data);
1328                                });
1329                            });
1330                        });
1331                    });
1332                }
1333            }
1334        },
1335
1336        // PayPal payments through Braintree
1337        btpaypal: {
1338
1339            init: function() {
1340                MUI.debugConsole('initializing PayPal OneTime Transaction');
1341
1342                var token = MUI.Checkout.Payment.Braintree.getBraintreeToken();
1343
1344                var paypalButton = document.querySelector('.paypal-button');
1345
1346                // Create a client.
1347                braintree.client.create({
1348                    authorization: token
1349                }, function (clientErr, clientInstance) {
1350
1351                    // Stop if there was a problem creating the client.
1352                    // This could happen if there is a network error or if the authorization
1353                    // is invalid.
1354                    if (clientErr) {
1355                        console.error('Error creating client:', clientErr);
1356                        return;
1357                    }
1358
1359                    // Create a PayPal component.
1360                    braintree.paypal.create({
1361                        client: clientInstance
1362                    }, function (paypalErr, paypalInstance) {
1363
1364                        // Stop if there was a problem creating PayPal.
1365                        // This could happen if there was a network error or if it's incorrectly
1366                        // configured.
1367                        if (paypalErr) {
1368                            console.error('Error creating PayPal:', paypalErr);
1369                            return;
1370                        }
1371
1372                        // Enable the button.
1373                        paypalButton.removeAttribute('disabled');
1374
1375                        // When the button is clicked, attempt to tokenize.
1376                        paypalButton.addEventListener('click', function (event) {
1377
1378                            // Because tokenization opens a popup, this has to be called as a result of
1379                            // customer action, like clicking a button—you cannot call this at any time.
1380                            paypalInstance.tokenize({
1381                                flow: 'vault'
1382                            }, function (tokenizeErr, payload) {
1383
1384                                // Stop if there was an error.
1385                                if (tokenizeErr) {
1386                                    if (tokenizeErr.type !== 'CUSTOMER') {
1387                                        console.error('Error tokenizing:', tokenizeErr);
1388                                    }
1389                                    return;
1390                                }
1391
1392                                // Tokenization succeeded!
1393                                paypalButton.setAttribute('disabled', true);
1394
1395                                if (MUI.Settings.Endpoints.braintreePayPalSurface !== '')
1396                                {
1397                                    var method = 'Process';
1398                                    var data = { nonce: payload.nonce };
1399                                    if (MUI.Checkout.Payment.Braintree.invoiceKey !== '') {
1400                                        method = 'Retry';
1401                                        data.invoiceKey = MUI.Checkout.Payment.Braintree.invoiceKey;
1402                                    }
1403
1404                                    var url = MUI.Settings.Endpoints.braintreePayPalSurface + method;
1405                                    MUI.Checkout.Payment.Braintree.postBraintreeForm(url, data);
1406                                }
1407                            });
1408
1409                        }, false);
1410                    });
1411
1412                });
1413            }
1414
1415        }
1416    },
1417
1418    postBraintreeForm: function(url, data) {
1419        MUI.Notify.toggleOverlay();
1420        $.ajax({
1421            url: url,
1422            type: 'POST',
1423            data: data
1424        }).then(function(result) {
1425            MUI.Checkout.Payment.Braintree.handlePaymentResult(result, 'Braintree.success');
1426        }, function(err) {
1427            MUI.Checkout.Payment.Braintree.handlePaymentException(err);
1428        });
1429    },
1430
1431    handlePaymentResult: function(result, evt) {
1432        MUI.Notify.toggleOverlay();
1433        if (result.Success) {
1434            MUI.emit('AddItem.added', result);
1435            MUI.emit(evt, result);
1436            MUI.Notify.success(result.PaymentMethodName + ' Success!');
1437        } else {
1438            console.info(result);
1439            MUI.Notify.warn(result.PaymentMethodName + ' Error!')
1440        }
1441    },
1442
1443    handlePaymentException: function(err) {
1444        MUI.Notify.toggleOverlay();
1445        MUI.Notify.error('There was an error');
1446        MUI.Logger.captureError(err);
1447    },
1448
1449    getBraintreeToken: function() {
1450        var frm = MUI.Checkout.Payment.Braintree.getBraintreeForm();
1451        var token = $(frm).find('#Token');
1452        return $(token).val();
1453    },
1454
1455    getBraintreeForm: function() {
1456        return $('[data-muiscript="braintree"]');
1457    }
1458};
1459
1460// put prototype methods there
1461
1462// initialize the MUI object
1463MUI.init();
1464
1465

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.