PageSourceSearch

https://gigisplayhouse.org/wp-content/plugins/superstorefinder-wp/js/init.js?ver=1.0

js gigisplayhouse.org collected 2026-09-24 08:52:16 UTC 95,520 bytes, 2,303 lines download raw bytes

1var FE = {};
2jQuery(function() {
3"use strict";
4    homebrew.Carousel.prototype.options.transitions.enable = Modernizr.csstransitions;
5    homebrew.Tooltip.prototype.options.appendTo = '#mainContent';
6    homebrew.Tooltip.prototype.options.transitions.enable = Modernizr.csstransitions;
7
8
9    jQuery.extend(FE, {
10        $els : {
11            backToTop : jQuery('#globalBackToTop'),
12            body : jQuery('body'),
13            mainSite : jQuery('#mainSite'),
14            pageOverlay : jQuery('#pageOverlay'),
15            root : jQuery('html')
16        },
17
18        events: {
19            transitionEnd : homebrew.events.transitionEnd || 'transitionend oTransitionEnd otransitionend webkitTransitionEnd msTransitionEnd',
20            animationEnd: homebrew.events.animationEnd || 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'
21        },
22
23        browser : homebrew.browser,
24
25        baseURL : (typeof fileUrl === 'string')
26                    ? fileUrl
27                    : (window.location.href.indexOf('') > -1)
28                        ? ''
29                        : ''
30    });
31
32    if(!FE.$els.pageOverlay.length) {
33        FE.$els.pageOverlay = jQuery('<div class="page-overlay" id="pageOverlay" />').appendTo(FE.$els.mainSite);
34    }
35
36    if(!FE.$els.backToTop.length) {
37        FE.$els.backToTop = jQuery('<a href="#top" title="Back To Top" class="main-back-to-top" id="mainBackToTop" />').appendTo('#mainContent');
38
39        FE.$els.backToTop.on('click', function(e) {
40            e.preventDefault();
41
42            jQuery('html, body').animate({
43                scrollTop : 0
44            }, 400);
45        });
46
47        jQuery(window).on('scroll.toggleBackToTop', homebrew.utils.throttle(function() {
48            FE.$els.backToTop.toggleClass('is-shown', jQuery(window).scrollTop() > jQuery(window).height()*0.5);
49        }, 50));
50    }
51
52    if(typeof FE.browser === 'undefined') {
53        FE.browser = {
54            ie      : FE.$els.root.hasClass('ie'),
55            ie9     : FE.$els.root.hasClass('ie9'),
56            lt9     : FE.$els.root.hasClass('lt9'),
57            ie8     : FE.$els.root.hasClass('ie8'),
58            lt8     : FE.$els.root.hasClass('lt8'),
59            ie7     : FE.$els.root.hasClass('ie7'),
60            firefox : (window.mozIndexedDB !== undefined)
61        }
62    }
63
64
65    jQuery.extend(true, FE, {
66        alert : function(args) {
67            this.showAlertPopup(args, 'alert');
68        },
69
70        confirm : function(args) {
71            this.showAlertPopup(args, 'confirm');
72        },
73
74        showAlertPopup : function(args, method) {
75            args = args || {};
76
77            var $popup = jQuery('#FE__alertPopup');
78
79            if(!$popup.length) {
80                $popup = jQuery([
81                        '<div class="ssf-popup text-center" id="FE__alertPopup">',
82                            '<h2 class="title space-bottom-2x" />',
83                            '<div class="popup-message" />',
84                            '<div class="space-top-2x">',
85                                '<a href="#/" class="button padded xlarge okay-button">Okay</a>',
86                                '<a href="#/" class="button padded xlarge space-left cancel-button" style="display: none;">Cancel</a>',
87                            '</div>',
88                        '</div>'
89                    ].join('')).appendTo('body');
90
91                $popup.popupify({
92                    addCloseBtns : false,
93                    closeOnOverlay : false
94                });
95            }
96
97            /* Alerts/Confirm dialog boxes should only happen one at a time,
98             * so if it is already shown, then do not proceed. */
99            if(jQuery('body').hasClass('popup-is-shown')
100            && $popup.hasClass('is-shown')) {
101                return;
102            }
103
104            var $title = $popup.find('.title'),
105                $message = $popup.find('.popup-message'),
106                $prevPopup = jQuery('.popup-is-shown').find('.ssf-popup.is-shown');
107
108            if(args.title) $title.html(args.title);
109            if(args.message) $message.html(args.message);
110
111            $popup.find('.cancel-button').toggle(method === 'confirm');
112
113            $popup.find('.okay-button, .cancel-button')
114                .off('.alert')
115                .on('click.alert', function() {
116                    if($prevPopup.length
117                    && typeof $prevPopup.eq(0).data('popupify') !== 'undefined') {
118                        $prevPopup.data('popupify').reveal();
119                    } else {
120                        $popup.data('popupify').conceal();
121                    }
122
123                    if(typeof args.onConfirm === 'function') {
124                        if(method === 'confirm') {
125                            args.onConfirm(jQuery(this).is('.okay-button'));
126                        } else {
127                            args.onConfirm();
128                        }
129                    }
130                });
131
132            $popup.data('popupify').reveal();
133        },
134
135
136        FootnoteAnchor : function() {
137            this.init.apply(this, arguments);
138        },
139
140        Hasher : function(options) {
141            this.options = options;
142        },
143
144        HeightSlider : function(heightSlider) {
145            if(typeof heightSlider === 'string') heightSlider = jQuery(heightSlider);
146
147            var self = this;
148
149            jQuery.extend(self, {
150                reveal : function() {
151                    if(Modernizr.csstransitions) {
152                        if(heightSlider.height() !== 0) return;
153
154                        var targetHeight = 0;
155                        heightSlider.css('height', 'auto');
156                        targetHeight = heightSlider.height();
157                        heightSlider.css('height', '');
158
159                        setTimeout(function() {
160                            heightSlider
161                                .off(FE.events.transitionEnd)
162                                .on(FE.events.transitionEnd, function() {
163                                    heightSlider
164                                        .off(FE.events.transitionEnd)
165                                        .removeClass('is-transitionable')
166                                        .css('height', '')
167                                        .addClass('is-toggled');
168                                })
169                                .addClass('is-transitionable')
170                                .css('height', targetHeight + 'px');
171                        }, 1);
172                    } else {
173                        if(heightSlider.css('display') !== 'none') return;
174
175                        heightSlider.slideDown(400, function() {
176                            jQuery(this).addClass('is-toggled');
177                        });
178                    }
179                },
180
181                conceal : function() {
182                    if(Modernizr.csstransitions) {
183                        if(heightSlider.height() === 0) return;
184
185                        heightSlider
186                            .css('height', heightSlider.height() + 'px')
187                            .removeClass('is-toggled');
188
189                        setTimeout(function() {
190                            heightSlider
191                                .off(FE.events.transitionEnd)
192                                .on(FE.events.transitionEnd, function() {
193                                    heightSlider.off(FE.events.transitionEnd);
194                                        heightSlider.removeClass('is-transitionable');
195                                        heightSlider.css('height', '');
196                                })
197                                .addClass('is-transitionable')
198                                .css('height', '0px');
199                        }, 1);
200                    } else {
201                        if(heightSlider.css('display') === 'none') return;
202
203                        heightSlider
204                            .removeClass('is-toggled')
205                            .slideUp(400);
206                    }
207                },
208
209                toggle : function() {
210                    if(Modernizr.csstransitions) {
211                        heightSlider.trigger((heightSlider.height() === 0) ? 'reveal' : 'conceal');
212                    } else {
213                        heightSlider.trigger((heightSlider.css('display') === 'none') ? 'reveal' : 'conceal');
214                    }
215                }
216            });
217
218            heightSlider.data('height-slider', self);
219        },
220        /* End HeightSlider */
221
222        StickyColumn : function() {
223            this.load.apply(this, arguments);
224        },
225
226        getHash : function() {
227            var hash = window.location.hash;
228
229            /*
230             * Need to check if second character is a period; sometimes
231             * AddThis adds its own hash to the URL, and its second
232             * character is always a period, which is problematic.
233             */
234            if(hash === '' || hash.substr(1,1) === '.') {
235                return '';
236            } else {
237                return hash;
238            }
239        },
240        /* End getHash */
241
242        getQueryStr: function(str) {
243            var search = window.location.search;
244            if(!search) return false;
245
246            if(search.substr(0,1) === '?') {
247                search = search.substr(1);
248            }
249
250            var splitSearch = search.split('&');
251                keyValuePairs = {};
252
253            splitSearch.map(function(searchStr) {
254                var splitSearchStr = searchStr.split('=');
255
256                splitSearchStr = splitSearchStr.map(function(str) {
257                    return jQuery.trim(str);
258                });
259
260                keyValuePairs[splitSearchStr[0]] = splitSearchStr[1];
261            });
262
263            if(typeof str === 'string') {
264                return (typeof keyValuePairs[str] === 'undefined') ? false : keyValuePairs[str];
265            } else {
266                return keyValuePairs;
267            }
268        },
269        /* End getQueryStr */
270
271        initContentTabbers : function() {
272            /* For tabs that toggle contents instead of going to new pages */
273
274            jQuery('.js-togglerify-tabs').each(function() {
275                var $this = jQuery(this),
276                    $nestedTabs = $this.find('.js-togglerify-tabs').find('.tab'),
277                    $tabs = $this.find('.tab').not($nestedTabs),
278                    $nestedContents = $this.find('.js-togglerify-tabs').find('.tab-content'),
279                    $contents = $this.find('.tab-content').not($nestedContents);
280
281                $tabs
282                    .togglerify('destroy')
283                    .togglerify({
284                        toggledClass: 'current',
285                        selfToggleable: false,
286                        singleActive: true,
287                        content: function(index) {
288                            return $contents.eq(index);
289                        }
290                    })
291                    .on('afterToggleOn', function() {
292                        jQuery(window).trigger('resize');
293                    })
294                    .on('toggleOff', function(e, $thisTab, $thisContent) {
295                        /* Pause videos if available */
296                        $thisContent.find('.video-holder').find('iframe').each(function() {
297                            this.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*');
298                        });
299                    });
300            });
301        },
302        /* End initContentTabbers */
303
304        initContentShowers : function() {
305            var types = {
306                    TAB: 'tab',
307                    TOGGLER: 'toggler',
308                    NOTES: 'notes'
309                },
310                $notesToggler = jQuery('#notesToggler'),
311                $notesContent = jQuery('#notesContent');
312
313            jQuery('.js-show-content').each(function() {
314                var $this = jQuery(this),
315                    thisHref = $this.attr('href');
316
317                if(typeof thisHref !== 'string') {
318                    console.log($this);
319                    console.error("No target specified. Set the target's ID as the `href` attribute on the element.");
320                    return
321                }
322                
323                var $target = jQuery($this.attr('href'));
324
325                if(!$target.length) {
326                    console.log($this);
327                    console.error("Target `" + $this.attr('href') + "` does not exist.");
328                    return;
329                }
330
331                var data = $this.data('show-content-options');
332
333                if(!data) {
334                    console.log($this);
335                    console.error('Options undeclared. Declare the options using the `data-show-content-options` attribute on the element.');
336                    return;
337                }
338
339                var options = (typeof data === 'string')
340                        ? homebrew.getKeyValuePairsFromString(data)
341                        : {};
342
343                if(!options.type) {
344                    console.log($this);
345                    console.error('Options declared, but no type specified.');
346                    return;
347                }
348
349                var scrollArgs = [$target];
350
351                if(options.smoothScroll !== true) {
352                    scrollArgs.push('snap');
353                }
354
355                switch(options.type) {
356                    case types.TAB:
357                        $this.on('click', function(e) {
358                            e.preventDefault();
359
360                            var $tabContent = $target.closest('.tab-content'),
361                                $heightSyncers = $tabContent.find('.js-sync-height-wrapper');
362
363                            if($tabContent.hasClass('current')) {
364                                FE.scrollTo.apply(null, scrollArgs);
365                            } else {
366                                if($heightSyncers.length) {
367                                    $heightSyncers.last().one('afterSync', function() {
368                                        FE.scrollTo.apply(null, scrollArgs);
369                                    });
370                                    $tabContent.data('togglerify').toggleOn();
371                                } else {
372                                    $tabContent.data('togglerify').toggleOn();
373                                    FE.scrollTo.apply(null, scrollArgs);
374                                }
375                            }
376                        });
377                    break;
378
379                    case types.NOTES:
380                        if(!$notesToggler.length || !$notesContent.length) {
381                            return;
382                        }
383
384                        $this.on('click', function(e) {
385                            e.preventDefault();
386
387                            if(!$notesToggler.hasClass('is-toggled')) {
388                                $notesToggler.togglerify('toggleOn', { noSlide : true });
389                            }
390
391                            FE.scrollTo($target);
392                        });
393                    break;
394
395                    default:
396                        console.log($this);
397
398                        var errorMsg = [
399                                'The type `', options.type, '` is not supported. Our currently supported types are: '
400                            ];
401
402                        jQuery.each(types, function(key, value) {
403                            errorMsg.push('\n' + value);
404                        });
405
406                        errorMsg.push('\n\nCheck the type again.');
407
408                        console.error(errorMsg.join(''));
409                    break;
410                }
411            });
412        },
413        /* End initContentShowers */
414
415        initDropdownSwitchers : function($scope) {
416            var $holders = ($scope && $scope.length)
417                               ? $scope.find('.dropdown-switcher-holder')
418                               : jQuery('.dropdown-switcher-holder');
419
420            if(!$holders.length) return;
421
422            $holders.each(function() {
423                var $dropdown = jQuery(this).find('.dropdown-switcher'),
424                    $contents = jQuery(this).find('.dropdown-content');
425
426                $dropdown
427                    .on('change', function() {
428                        $contents
429                            .hide()
430                            .eq(this.selectedIndex)
431                                .show();
432                    })
433                    .trigger('change');
434            });
435        },
436      
437
438        initNumberTabbers : function() {
439            jQuery('.number-tabber').each(function() {
440                var $tabs = jQuery(this).find('.number-tabber__tabs').children('li'),
441                    $contents = jQuery(this).find('.number-tabber__contents').children('li');
442
443                $tabs
444                    .togglerify('destroy')
445                    .togglerify({
446                        toggledClass: 'current',
447                        singleActive: true,
448                        selfToggleable: false,
449                        content: function(index) {
450                            return $contents.eq(index);
451                        }
452                    });
453            });
454        },
455        /* End initNumberTabbers */
456
457        initPage : function() {
458            jQuery('.js-dropdownify').dropdownify();
459            jQuery('.js-inputify').inputify();
460
461            jQuery.each(FE, function(key, value) {
462                if(key.indexOf('init') === 0 && key !== 'initPage') value();
463            });
464
465            FE.loadXMLForm(jQuery('.js-xml-form-holder'));
466            FE.loadXMLTable(jQuery('.js-xml-tbl-holder'));
467            FE.loadXMLLocations(jQuery('.js-xml-locations-holder'));
468
469            if(FE.browser.ie) jQuery('[placeholder]').placeholderify();
470        },
471        /* End initPage */
472
473        initSectionSnap : function() {
474            /* Only needed for touchscreens, where the header does not
475             * automatically hide itself. */
476            if(!Modernizr.touch) return;
477
478            var hash = FE.getHash();
479            if(!hash || hash.indexOf('#/') > -1) return;
480
481            var $hash = jQuery(hash);
482            if(!$hash.length || !$hash.is('.section')) return;
483
484            setTimeout(function() {
485                FE.scrollTo($hash);
486            }, 25);
487        },
488        /* End initSectionSnap */
489
490        initSelfTogglers : function() {
491            /* Self Togglers - for things like FAQ questions */
492
493            jQuery('.self-toggler__toggler').togglerify({
494                singleActive: false,
495                slide: true,
496                content: function() {
497                    return jQuery(this).next();
498                }
499            });
500        },
501        /* End initSelfTogglers */
502
503        initSectionContentToggler : function() {
504            /* Section Content togglers - for pages like Popular FAQs */
505
506            var $sectionContentTogglers = jQuery('.section-content-toggler');
507            if(!$sectionContentTogglers.length) return;
508
509            var $sectionToggleableContents = jQuery('.section-toggleable-content'),
510                $sectionDropdown = jQuery('<select>'),
511                optionsStrArray = [];
512
513            $sectionContentTogglers
514                .togglerify({
515                    selfToggleable: false,
516                    singleActive : true,
517                    content : function(index) {
518                        return $sectionToggleableContents.eq(index);
519                    }
520                })
521                .each(function(index) {
522                    var $dropdownOption = jQuery(['<option>', jQuery.trim(jQuery(this).find('.toggler__title').text()), '</option>'].join(''));
523                    $dropdownOption.data('section-content-toggler', jQuery(this));
524                    $dropdownOption.appendTo($sectionDropdown);
525                });
526
527            jQuery('<div class="column show-for-small-down" />')
528                .prepend($sectionDropdown)
529                .prependTo($sectionContentTogglers.eq(0).closest('.row'));
530
531            $sectionDropdown
532                .dropdownify({
533                    markups : {
534                        holder : '<div class="expand space-bottom" />'
535                    }
536                })
537                .on('change', function() {
538                    var $thisToggler = jQuery(this).find('option:checked').data('section-content-toggler');
539                    $thisToggler.togglerify('toggleOn');
540                });
541
542            FE.watchSize('small', function(isSmallScreen) {
543                var method = (isSmallScreen) ? 'deactivate' : 'activate';
544                $sectionContentTogglers.togglerify(method);
545            });
546        },
547        /* End initSectionContentToggler */
548
549        initSmallNav : function() {
550            if(Modernizr.touch || jQuery('#mainSmallNav').data('initSmallNav') === true) return;
551
552            jQuery('#mainSmallNav').data('initSmallNav', true);
553
554            var prevScrollTop = jQuery(window).scrollTop(),
555                navHeight = jQuery('#mainSmallNav').height() + jQuery('#mainNav').height();
556
557            jQuery(window)
558                .on('resize.navHider', function() {
559                    navHeight = jQuery('#mainSmallNav').height() + jQuery('#mainNav').height();
560                })
561                .trigger('resize.navHider')
562                .on('scroll.navHider', function() {
563                    var currentScrollTop = jQuery(window).scrollTop(),
564                        partialHiddenBool = currentScrollTop > 47,
565                        hiddenBool = currentScrollTop >= prevScrollTop && currentScrollTop > navHeight && !FE.$els.body.hasClass('mobile-nav-is-shown');
566
567                    jQuery('body').toggleClass('main-header-is-partially-hidden', partialHiddenBool);
568                    jQuery('body').toggleClass('main-header-is-hidden', hiddenBool);
569                    prevScrollTop = currentScrollTop;
570                })
571                .trigger('scroll.navHider');
572        },
573        /* End initSmallNav */
574
575        initSyncHeightWrappers : function($scope) {
576            var $syncHeightWrappers = jQuery('.js-sync-height-wrapper');
577            if(!$syncHeightWrappers.length) return;
578
579            $syncHeightWrappers.each(function() {
580                var selectors = jQuery(this).data('sync-height-selector');
581                if(!selectors) return;
582
583                var selectorsSplitArray = selectors.split(',');
584
585                for(var i = selectorsSplitArray.length-1; i > -1; i--) {
586                    selectorsSplitArray[i] = jQuery.trim(selectorsSplitArray[i]);
587                }
588                
589                jQuery(this).heightSyncify({
590                    items : selectorsSplitArray
591                });
592            });
593        },
594        /* End initSyncHeightWrappers */
595
596        initTooltips : function() {
597            var $els = jQuery('.icon--question[title], .js-tooltipify');
598
599            $els.tooltipify();
600        },
601        /* End initTooltips */
602
603        initQuickLinks : function() {
604            var $quicklinksToggler = jQuery('#mainNav__quicklinksToggler'),
605                $quicklinks = jQuery('#mainNav__quicklinks');
606
607            if(!$quicklinksToggler.length || !$quicklinks.length) return;
608            if($quicklinksToggler.data('initQuickLinks') === true) return;
609
610            $quicklinksToggler.data('initQuickLinks', true);
611
612            var mouseupEv = 'mouseup.mainNavQuicklinksFauxBlur',
613                resizeEv = 'resize.limitQuicklinksHeight';
614
615            $quicklinksToggler
616                .togglerify({
617                    slide: true,
618                    content: function() {
619                        return $quicklinks;
620                    }
621                })
622                .togglerify('deactivate')
623                .on({
624                    click : function(e) {
625                        e.preventDefault();
626                        
627                        var $thisToggler = jQuery(this);
628
629                        if($thisToggler.hasClass('is-toggled')) {
630                            $thisToggler.togglerify('toggleOff');
631                        } else {
632                            var heightLimit = jQuery(window).height() - jQuery('#mainNav').height(),
633                                contentHeight;
634
635                            $quicklinks.css('height', 'auto');
636                            contentHeight = $quicklinks.height();
637                            $quicklinks.css('height', '');
638
639                            if(contentHeight >= heightLimit) {
640                                $thisToggler.togglerify('toggleOn', { contentHeight : heightLimit });
641                            } else {
642                                $thisToggler.togglerify('toggleOn');
643                            }
644                        }
645                    },
646
647                    toggleOn : function(e, $thisToggler, $target) {
648                        jQuery(document).on(mouseupEv, function(e) {
649                            var container = $thisToggler.add($target);
650                        
651                            if(!container.is(e.target) && container.has(e.target).length === 0) {
652                                jQuery(document).off(mouseupEv);
653                                $target.data('togglerify').toggleOff();
654                            }
655                        });
656
657                        jQuery(window).on(resizeEv, function() {
658                            if(!$thisToggler.hasClass('is-toggled')) return;
659
660                            var heightLimit = jQuery(window).height() - jQuery('#mainNav').height();
661
662                            $quicklinks.css('height', '');
663                            
664                            if($quicklinks.height() >= heightLimit) {
665                                $quicklinks.height(heightLimit);
666                            }
667                        });
668                    },
669
670                    toggleOff : function() {
671                        jQuery(document).off(mouseupEv);
672                        jQuery(window).off(resizeEv);
673                    }
674                });
675        },
676        /* End initQuickLinks */
677
678        loadXMLForm : function($holders) {
679            $holders = jQuery($holders);
680            if(!$holders.length) return;
681
682            $holders.each(function() {
683                var $thisHolder = jQuery(this);
684                if(jQuery.data(this, 'loaded-xml-form') === true) return;
685
686                var xmlURL = $thisHolder.data('xml-form-url');
687                if(typeof xmlURL !== 'string') return;
688
689                jQuery.ajax({
690                    type: 'GET',
691                    url: xmlURL,
692                    dataType: 'xml',
693                    success: function(data) {
694                        makeXMLForm(data, $thisHolder);
695                        FE.initFancyPlaceholders($thisHolder);
696                        FE.initInputFocus($thisHolder);
697                        $thisHolder.find('.js-inputify').inputify();
698                        $thisHolder.find('.js-dropdownify').dropdownify();
699                        $thisHolder.data('loaded-xml-form', true);
700                    },
701                    error: function(d) {
702                        console.log(d);
703                    }
704                });
705            });
706
707            function makeXMLForm(data, $holder) {
708                var $data = jQuery(data),
709                    $inputs = $data.find('formInput'),
710                    uniqueID = new Date().getTime() + '_' + Math.round(Math.random()*10000),
711                    $xmlForm = jQuery([
712                        '<form action="https://docs.google.com/forms/d/', getText($data.find('formID')), '/formResponse"',
713                        'target="hiddenIframe_', uniqueID, '"',
714                        'method="post" class="xml-form-asset xml-form-contents" />'
715                    ].join('')).prependTo($holder),
716                    xmlFormStrArray = [];
717
718                xmlFormStrArray.push([
719                    '<div class="row collapse   space-bottom medium-space-bottom-2x">',
720                        makeTitle($data.find('formTitle')),
721                    '</div>'
722                ].join(''));
723
724                $inputs.each(function(index) {
725                    var $thisInput = jQuery(this),
726                        uniqueInputID = new Date().getTime() + '_' + index,
727                        name = getText($thisInput.find('name')),
728                        label = getText($thisInput.find('label')),
729                        type = getText($thisInput.find('type')).toLowerCase(),
730                        required = getText($thisInput.find('required')).toLowerCase(),
731                        pattern = getText($thisInput.find('pattern')),
732                        tooltip = getText($thisInput.find('tooltip'));
733
734                    switch(type) {
735                        case 'text':
736                        case 'number':
737                        case 'email':
738                        case 'textarea':
739                            var validifyStrArray = ['response: #response_' + uniqueInputID],
740                                floaterStrArray = [];
741
742                            if(!pattern) {
743                                if(type === 'number') {
744                                    validifyStrArray.unshift('pattern: numbers');
745                                } else if(type === 'email') {
746                                    validifyStrArray.unshift('pattern: email');
747                                }
748                            }
749
750                            if(required === 'yes') {
751                                validifyStrArray.unshift('required: true');
752                            }
753
754                            validifyStrArray.push('floater: #floater_roi_' + uniqueInputID);
755
756                            floaterStrArray.push('<div class="validify-floater" id="floater_roi_', uniqueInputID, '">');
757                                if(required === 'yes') {
758                                    floaterStrArray.push([
759                                        '<span class="validify-floater__message validify-floater__message--failed-required">',
760                                            'This input is required.',
761                                        '</span>'
762                                    ].join(''));
763                                }
764
765                                var errorMessage = getText($thisInput.find('errorMessage'));
766
767                                /*if(!errorMessage) {*/
768                                    if(type === 'number') {
769                                        errorMessage = 'This input should consist of numbers only.'
770                                    } else if(type === 'email') {
771                                        errorMessage = 'The address should be in this format: [email protected]'
772                                    } else if(!errorMessage) {
773                                        errorMessage = 'Invalid input.'
774                                    }
775                                /*}*/
776
777                                floaterStrArray.push([
778                                    '<span class="validify-floater__message validify-floater__message--failed-pattern">',
779                                        errorMessage,
780                                    '</span>'
781                                ].join(''));
782
783                            floaterStrArray.push('</div>');
784                            
785                            var elementStr;
786
787                            if(type === 'textarea') {
788                                elementStr = [
789                                    '<textarea'
790                                ];
791                            } else {
792                                elementStr = [
793                                    '<input',
794                                        ' type="', (type === 'email') ? 'email' : 'text', '"'
795                                ];
796                            }
797
798                                elementStr.push(
799                                        ' data-validify="', validifyStrArray.join(', '), '"',
800                                        (pattern)
801                                            ? ' data-validify-pattern="' + pattern + '"'
802                                            : '',
803                                        ' name="', name, '"',
804                                        ' class="field expand"',
805                                        ' id="roi_', uniqueInputID ,'"',
806                                    (type === 'textarea')
807                                    ? '></textarea>'
808                                    : ' />'
809                                );
810
811                            xmlFormStrArray.push([
812                                '<div class="form-obj', (tooltip) ? ' form-obj--with-tooltip' : '', '">',
813                                    '<div class="field-holder expand">',
814                                        elementStr.join(''),
815                                        '<label for="roi_', uniqueInputID, '" class="field-holder__label">',
816                                            label, (required === 'yes') ? '<span class="text-red">*</span>' : '',
817                                        '</label>',
818                                    '</div>',
819
820                                    '<span class="validify-responses  form-obj__icon" id="response_', uniqueInputID, '">',
821                                        '<span class="validify-response validify-response--success">',
822                                            '<i class="icon icon--success-tick"></i>',
823                                        '</span>',
824                                    '</span>',
825
826                                    (tooltip)
827                                    ? '<i title="' + tooltip + '" class="icon icon--question round-circle   form-obj__icon"></i>'
828                                    : '',
829
830                                    floaterStrArray.join(''),
831                                '</div>'
832                            ].join(''));
833                        break;
834
835                        case 'dropdown':
836                            var validifyStrArray = ['response: #response_' + uniqueInputID],
837                                floaterStr = '';
838
839                            if(required === 'yes') {
840                                validifyStrArray.unshift('required: true');
841                                validifyStrArray.push('floater: #floater_roi_' + uniqueInputID);
842
843                                floaterStr = [
844                                    '<div class="validify-floater" id="floater_roi_', uniqueInputID, '">',
845                                        '<span class="validify-floater__message validify-floater__message--failed-required">',
846                                            'This input is required.',
847                                        '</span>',
848                                    '</div>'
849                                ].join('');
850                            }
851
852                            var optionsArray = getText($thisInput.find('options')).split('\n'),
853                                options = [];
854
855                            while(optionsArray.length) {
856                                options.push([
857                                    '<option value="', optionsArray[0], '">',
858                                        optionsArray[0],
859                                    '</option>'
860                                ].join(''));
861
862                                optionsArray.shift();
863                            }
864
865                            xmlFormStrArray.push([
866                                '<div class="form-obj', (tooltip) ? ' form-obj--with-tooltip' : '', '">',
867                                    '<span class="expand js-dropdownify">',
868                                        '<select data-validify="', validifyStrArray.join(', '), '" name="', name, '">',
869                                            '<option value="">', label, (required === 'yes') ? '*' : '', '</option>',
870                                            options.join(''),
871                                        '</select>',
872                                    '</span>',
873
874                                    '<span class="validify-responses  form-obj__icon" id="response_', uniqueInputID, '">',
875                                        '<span class="validify-response validify-response--success">',
876                                            '<i class="icon icon--success-tick"></i>',
877                                        '</span>',
878                                    '</span>',
879
880                                    (tooltip)
881                                    ? '<i title="' + tooltip + '" class="icon icon--question round-circle   form-obj__icon"></i>'
882                                    : '',
883
884                                    floaterStr,
885                                '</div>'
886                            ].join(''));
887                        break;
888
889                        case 'radio':
890                        case 'checkbox':
891                            var validifyStrArray = ['groupResponse: #response_' + uniqueInputID],
892                                validifyRequiredStr = '';
893
894                            if(required === 'yes') {
895                                validifyStrArray.unshift('required: true');
896
897                                validifyRequiredStr = [
898                                    '<span class="validify-response validify-response--failed-required">',
899                                        '<br />This input is required.',
900                                    '</span>'
901                                ].join('');
902                            }
903
904                            var optionsArray = getText($thisInput.find('options')).split('\n'),
905                                optionsLabelsArray = getText($thisInput.find('optionsLabels')).split('\n'),
906                                options = [],
907                                currentOption;
908
909                            for(var i = 0, ii = optionsArray.length; i < ii; i++) {
910                                if(optionsArray[i] === '__other_option__') {
911
912                                } else {
913                                    options.push([
914                                        '<label>',
915                                            '<input type="', type, '" name="', name, '" data-validify value="', optionsArray[i], '" class="js-inputify" /> ',
916                                            (typeof optionsLabelsArray[i] === 'string'
917                                            && optionsLabelsArray[i])
918                                            ? optionsLabelsArray[i]
919                                            : optionsArray[i],
920                                        '</label>'
921                                    ].join(''));
922                                }
923                            }
924
925                            xmlFormStrArray.push([
926                                '<div class="form-obj" data-validify-group="', validifyStrArray.join(', '), '">',
927                                    '<p class="form-obj__label">',
928                                        label, (required === 'yes') ? '<span class="text-red">*</span>' : '',
929
930                                        (tooltip)
931                                        ? ' <i title="' + tooltip + '" class="icon icon--question round-circle"></i>'
932                                        : '',
933
934                                        ' <span class="validify-responses" id="response_', uniqueInputID, '">',
935                                            '<span class="validify-response validify-response--success">',
936                                                '<i class="icon icon--success-tick"></i>',
937                                            '</span>',
938
939                                            validifyRequiredStr,
940                                        '</span>',
941                                    '</p>',
942
943                                    options.join(''),
944                                '</div>'
945                            ].join(''));
946                        break;
947                    }
948                });
949
950                xmlFormStrArray.push([
951                    '<div class="text-center space-top medium-space-top-2x">',
952                        '<input type="submit" value="Submit" class="button padded xlarge" />',
953                    '</div>'
954                ].join(''));
955
956                $xmlForm.append(xmlFormStrArray.join(''));
957                $xmlForm.find('.icon--question[title]').tooltipify();
958
959                var $hiddenIframe = jQuery('<iframe data-submitted="false" name="hiddenIframe_' + uniqueID + '" style="display: none;" />').appendTo($xmlForm);
960
961                $hiddenIframe.on('load', function() {
962                    if(jQuery(this).data('submitted') !== true) return;
963                    
964                    $xmlForm.addClass('is-hidden');
965                    $holder.find('.xml-form-thanks').addClass('is-shown');
966                    FE.scrollTo($holder.find('.xml-form-thanks'));
967                });
968
969                $xmlForm.validify().on('submit', function(e) {
970                    var validifyData = jQuery.data(this, 'validify');
971
972                    validifyData.set('strict', true).validate();
973
974                    if(validifyData.valid !== true) {
975                        //alert('Please check the form!');
976
977                        var $errorInput = jQuery(this).find('.is-failed').eq(0);
978                        
979                        FE.scrollTo($errorInput, function() {
980                            $errorInput.focus().select();
981                        });
982
983                        e.preventDefault();
984                    } else {
985                        $hiddenIframe.data('submitted', true);
986                        $xmlForm.find('input[type="submit"]').addClass('is-loading').prop('disabled', true);
987                    }
988                });
989            }
990
991            function getText($node) {
992                return ($node.length) ? jQuery.trim($node.text()) : '';
993            }
994
995            function makeTitle($title) {
996                var title = getText($title);
997
998                if(title) {
999                    return [
1000                        '<div class="large-6 column">',
1001                            '<h3>', title, '</h3>',
1002                        '</div>',
1003
1004                        '<div class="large-6 column   medium-down-pad-vertical large-text-right align-to-h3 text-dark-grey">',
1005                            '<span class="text-red">*</span> Please fill in the mandatory fields.',
1006                        '</div>'
1007                    ].join('');
1008                } else {
1009                    return [
1010                        '<div class="column   medium-down-pad-vertical large-text-right text-dark-grey">',
1011                            '<span class="text-red">*</span> Please fill in the mandatory fields.',
1012                        '</div>'
1013                    ].join('');
1014                }
1015            }
1016        },
1017        /* End loadXMLForm */
1018
1019        loadXMLTable : function($holders) {
1020            $holders = jQuery($holders);
1021            if(!$holders.length) return;
1022
1023            var worksheets = {},
1024                xmlSerializer = new XMLSerializer(); // Needed for makeCellInlineFormats
1025
1026            $holders.each(function() {
1027                var $thisHolder = jQuery(this);
1028                if($thisHolder.data('loaded-xml-table') === true) return;
1029
1030                var xmlURL = $thisHolder.data('xml-table-url');
1031                if(typeof xmlURL !== 'string') return;
1032
1033                jQuery.ajax({
1034                    type: 'GET',
1035                    url: xmlURL,
1036                    dataType: 'xml',
1037                    success: function(data) {
1038                        var xmlStyles = getXMLStyles(data);
1039                        makeXMLTable(data, xmlStyles, $thisHolder);
1040                        attachRowHoverers();
1041                        $thisHolder.data('loaded-xml-table', true);
1042                    },
1043                    error: function(d) {
1044                        console.log(d);
1045                    }
1046                });
1047            });
1048
1049            function makeXMLTable(data, xmlStyles, $holder) {
1050                var $data = jQuery(data),
1051                    $worksheets = $data.find('Worksheet'),
1052                    dropdown = {},
1053                    finalTableStrArr = [];
1054
1055                worksheets = {};
1056
1057                $worksheets.each(function() {
1058                    var $thisWorksheet = jQuery(this),
1059                        thisWorksheetName = $thisWorksheet.attr('ss:Name').toLowerCase();
1060
1061                    if(thisWorksheetName.indexOf('dropdown') === 0) {
1062                        $thisWorksheet.find('Data').each(function() {
1063                            var $Data = jQuery(this),
1064                                DataIndex = $Data.index(),
1065                                DataText = jQuery(this).text().toLowerCase();
1066
1067                            switch(DataText) {
1068                                case 'dropdown label':
1069                                    dropdown.__label = jQuery(this).closest('Row').next('Row').find('Data').text();
1070                                break;
1071
1072                                case 'option label':
1073                                    jQuery(this).closest('Row').nextUntil().each(function() {
1074                                        var optionLabelText = jQuery(this).find('Data').eq(DataIndex).text();
1075                                        if(optionLabelText === '') return;
1076                                        dropdown[jQuery(this).find('Data').eq(DataIndex).text()] = jQuery(this).find('Data').eq(DataIndex+1).text().toLowerCase();
1077                                    });
1078                                break;
1079                            }
1080                        });
1081                    } else {
1082                        worksheets[thisWorksheetName] = $thisWorksheet;
1083                    }
1084                });
1085
1086                if(jQuery.isEmptyObject(dropdown)) {
1087                    var worksheetNames = [];
1088
1089                    jQuery.each(worksheets, function(key, value) {
1090                        worksheetNames.push(key);
1091                    });
1092
1093                    finalTableStrArr.push(iterateWorksheetsArray(worksheetNames, xmlStyles));
1094                } else {
1095                    var dropdownOptionsArray = [],
1096                        dropdownContentsArray = [];
1097
1098                    jQuery.each(dropdown, function(key, value) {
1099                        if(key === '__label') return;
1100
1101                        dropdownOptionsArray.push(key);
1102
1103                        var dropdownWorksheets = {},
1104                            valueSplitArray = value.split('\n');
1105
1106                        if(valueSplitArray.length === 1) {
1107                            valueSplitArray = valueSplitArray[0].split(',');
1108                        }
1109
1110                        for(var i = valueSplitArray.length-1; i > -1; i--) {
1111                            valueSplitArray[i] = jQuery.trim(valueSplitArray[i]);
1112                        }
1113
1114                        dropdownContentsArray.push(iterateWorksheetsArray(valueSplitArray, xmlStyles));
1115                    });
1116
1117                    var dropdownOptionsStrArray = [],
1118                        dropdownContentsStrArray = [];
1119
1120                    while(dropdownOptionsArray.length) {
1121                        dropdownOptionsStrArray.push([
1122                            '<option>', dropdownOptionsArray.shift(), '</option>'
1123                        ].join(''));
1124                    }
1125
1126                    while(dropdownContentsArray.length) {
1127                        dropdownContentsStrArray.push([
1128                            '<div class="dropdown-content">',
1129                                dropdownContentsArray.shift(),
1130                            '</div>'
1131                        ].join(''));
1132                    }
1133
1134                    finalTableStrArr.push([
1135                        '<div class="dropdown-switcher-holder">',
1136                            '<div class="text-center text-large">',
1137                                dropdown.__label,
1138                                '<span class="js-dropdownify tbl-dropdown">',
1139                                    '<select class="dropdown-switcher">',
1140                                        dropdownOptionsStrArray.join(''),
1141                                    '</select>',
1142                                '</span>',
1143                            '</div>',
1144
1145                            '<div class="dropdown-contents-holder">',
1146                                dropdownContentsStrArray.join(''),
1147                            '</div>',
1148                        '</div>'
1149                    ].join(''));
1150                }
1151
1152                $holder.append(finalTableStrArr.join(''));
1153
1154                FE.initFootnotes($holder);
1155
1156                if(!$holder.find('.js-dropdownify').length) return;
1157
1158                $holder.find('.js-dropdownify').dropdownify();
1159                FE.initDropdownSwitchers($holder);
1160            }
1161
1162            function iterateWorksheetsArray(worksheetNames, xmlStyles) {
1163                var worksheetStrArr = [],
1164                    currentWorksheetName,
1165                    mediaClass;
1166
1167                while(worksheetNames.length) {
1168                    currentWorksheetName = worksheetNames.shift();
1169
1170                    if(typeof worksheets[currentWorksheetName] === 'undefined') {
1171                        console.error("FE.loadXMLTable(): Can't find the worksheet '" + currentWorksheetName + "'");
1172                        continue;
1173                    }
1174                    
1175                    if(currentWorksheetName.indexOf('desktop') === 0) {
1176                        mediaClass = 'show-for-large-up';
1177                    } else if(currentWorksheetName.indexOf('tabletup') === 0) {
1178                        mediaClass = 'show-for-medium-up';
1179                    } else if(currentWorksheetName.indexOf('tabletonly') === 0) {
1180                        mediaClass = 'show-for-medium-only';
1181                    } else if(currentWorksheetName.indexOf('tabletdown') === 0) {
1182                        mediaClass = 'show-for-medium-down';
1183                    } else if(currentWorksheetName.indexOf('mobile') === 0) {
1184                        mediaClass = 'show-for-small-down';
1185                    }
1186
1187                    worksheetStrArr.push([
1188                        '<div class="tbl-holder', (typeof mediaClass === 'string') ? ' ' + mediaClass : '', '">'
1189                    ].join(''));
1190                        worksheetStrArr.push(makeTableString(worksheets[currentWorksheetName], xmlStyles));
1191                    worksheetStrArr.push('</div>');
1192                }
1193
1194                return (worksheetStrArr.length) ? worksheetStrArr.join('') : false;
1195            }
1196
1197            function getColumnCount($worksheet) {
1198                var $worksheetRows = $worksheet.find('Row'),
1199                    _columnCount = [];
1200
1201                $worksheetRows.each(function() {
1202                    var $cells = jQuery(this).find('Cell');
1203
1204                    /* Filter out empty cells */
1205                    $cells.each(function() {
1206                        if(!jQuery(this).text()) {
1207                            $cells = $cells.not(jQuery(this));
1208                        }
1209                    });
1210
1211                    _columnCount.push($cells.length);
1212                });
1213
1214                return Math.max.apply(null, _columnCount);
1215            }
1216
1217            function getXMLStyles(data) {
1218                var $styles = jQuery(data).find('Style'),
1219                    styleObj = {};
1220                
1221                $styles.each(function() {
1222                    var $thisStyle = jQuery(this),
1223                        $alignment = $thisStyle.find('Alignment'),
1224                        $interior = $thisStyle.find('Interior'),
1225                        $numberFormat = $thisStyle.find('NumberFormat'),
1226                        $font = $thisStyle.find('Font'),
1227                        thisStyleObj = {};
1228
1229                    /* Allow adjustment of alignment in the Excel spreadsheet */
1230                    if($alignment.length && hasAttributes($alignment, ['ss:Horizontal', 'ss:Vertical'])) {
1231                        var verticalAlignment, horizontalAlignment;
1232
1233                        switch($alignment.attr('ss:Vertical')) {
1234                            case 'Top' : verticalAlignment = 'top'; break;
1235                            case 'Center' : verticalAlignment = 'middle'; break;
1236                            case 'Bottom' : verticalAlignment = 'bottom'; break;
1237                        }
1238
1239                        switch($alignment.attr('ss:Horizontal')) {
1240                            case 'Top' : horizontalAlignment = 'top'; break;
1241                            case 'Center' : horizontalAlignment = 'center'; break;
1242                            case 'Bottom' : horizontalAlignment = 'bottom'; break;
1243                        }
1244
1245                        thisStyleObj.alignment = {
1246                            'vertical' : verticalAlignment,
1247                            'horizontal' : horizontalAlignment
1248                        };
1249                    }
1250
1251                    /* Interior is where the cell's background color data is stored */
1252                    if($interior.length && hasAttributes($interior, ['ss:Color'])) {
1253                        thisStyleObj.bgColor = $interior.attr('ss:Color');
1254                    }
1255
1256                    if($font.length && hasAttributes($font, ['ss:Italic', 'ss:Bold', 'ss:Underline', 'ss:Color', 'ss:Size'])) {
1257                        thisStyleObj.font = {
1258                            'italic' : $font.attr('ss:Italic'),
1259                            'bold' : $font.attr('ss:Bold'),
1260                            'underline' : $font.attr('ss:Underline'),
1261                            'color' : $font.attr('ss:Color'),
1262                            'size' : $font.attr('ss:Size')
1263                        };
1264                    }
1265
1266                    if($numberFormat.length && hasAttributes($numberFormat, ['ss:Format'])) {
1267                        thisStyleObj.numberFormat = $numberFormat.attr('ss:Format');
1268                    }
1269
1270                    styleObj[$thisStyle.attr('ss:ID')] = thisStyleObj;
1271                });
1272
1273                return styleObj;
1274            }
1275
1276            function makeTableString($worksheet, xmlStyles) {
1277                var $rows = filterOutEmptyRows($worksheet.find('Row'));                    
1278                if(!$rows.length) return '';
1279
1280                var totalColumns = parseInt(getColumnCount($worksheet), 10),
1281                    totalRows = $rows.length,
1282                    i, ii, j, jj,
1283             
1284                    tableLayout = [];
1285
1286                for(i = $rows.length-1; i >-1; i--) {
1287                    tableLayout.push(new Array(totalColumns));
1288                }
1289
1290                $rows.each(function(rowIndex) {
1291                   
1292                    var cellCount = 0;
1293
1294                    jQuery(this).find('Cell').each(function(cellIndex) {
1295                        if(cellCount >= totalColumns) return;
1296
1297                        var $thisCell = jQuery(this),
1298                            cellMergeDown = parseInt($thisCell.attr('ss:MergeDown'), 10) || 0,
1299                            cellMergeAcross = parseInt($thisCell.attr('ss:MergeAcross'), 10) || 0;
1300
1301                       
1302                        while(typeof tableLayout[rowIndex][cellCount] === 'number') {
1303                            cellCount++;
1304                        }
1305
1306                        tableLayout[rowIndex][cellCount] = $thisCell;
1307
1308                        /* If there's colspan, then denote the overlapping cells
1309                         * as 0 so that it won't be rendered later. */
1310                        if(cellMergeAcross) {
1311                            for(i = cellCount, ii = cellCount+cellMergeAcross; i < ii; i++) {
1312                                tableLayout[rowIndex][i+1] = 0;
1313                            }
1314
1315                            cellCount += cellMergeAcross;
1316                        }
1317
1318                        /* Same thing, if there's rowspan, then denote the
1319                         * overlapping cells as 0 so that it won't be
1320                         * rendered later. */
1321                        if(cellMergeDown) {
1322                            for(i = rowIndex+1, ii = rowIndex+cellMergeDown; i <= ii; i++) {
1323                                tableLayout[i][cellCount] = 0;
1324
1325                                /* Same thing; this is to account for both rowspan
1326                                 * and colspan from the same cell. */
1327                                if(cellMergeAcross) {
1328                                    for(j = cellCount+1, jj = cellCount+cellMergeAcross; j <= jj; j++) {
1329                                        tableLayout[i][j] = 0;
1330                                    }
1331                                }
1332                            }
1333                        }
1334
1335                        cellCount++;
1336                    });
1337                });
1338
1339                var tableStrArr = [],
1340                    tableStyleID = $worksheet.find('Table').attr('ss:StyleID'),
1341                    defaultStyle = (xmlStyles[tableStyleID]) ? xmlStyles[tableStyleID] : {};
1342
1343                tableStrArr.push('<table class="default-tbl">');
1344
1345                var cellStrArr, cellArray, cell, k, kk;
1346                while(tableLayout.length) {
1347                    cellStrArr = [];
1348
1349                    cellArray = tableLayout.shift();
1350
1351                    for(k = 0, kk = cellArray.length; k < kk; k++) {
1352                        if(typeof cellArray[k] === 'undefined') {
1353                            cellStrArr.push('<td></td>');
1354                        } else if(typeof cellArray[k] === 'object') {
1355                            cellStrArr.push(makeCellString(cellArray[k], k));
1356                        }
1357                    }
1358
1359                    if(cellStrArr.length) {
1360                        cellStrArr.unshift('<tr>');
1361                        cellStrArr.push('</tr>');
1362                        tableStrArr.push(cellStrArr.join(''));
1363                    }
1364                }
1365
1366                tableStrArr.push('</table>');
1367
1368                return tableStrArr.join('');
1369
1370                function makeCellString($cell, cellIndex, log) {
1371                    var $thisCell = $cell,
1372                        cellContent = $thisCell.text();
1373
1374                    if(cellContent === '') {
1375                        if(cellIndex % 2 === 1) {
1376                            return '<td class="alt"></td>';
1377                        } else {
1378                            return '<td></td>';
1379                        }
1380                    }
1381
1382                    var cellStyle = jQuery.extend({}, defaultStyle),
1383                        cellData = $thisCell.find('Data'),
1384                        cellDataType = cellData.attr('ss:Type');
1385
1386                    if(cellDataType === 'Number') {
1387                        var decimalSplit = cellContent.split('.');
1388
1389                        if(decimalSplit.length > 1 && decimalSplit[1].length > 2) {
1390                            cellContent = parseFloat(cellContent, 10).toFixed(2);
1391                        }
1392                    }
1393
1394                    if(xmlStyles[$thisCell.attr('ss:StyleID')]) {
1395                        jQuery.extend(cellStyle, xmlStyles[$thisCell.attr('ss:StyleID')]);
1396                    }
1397
1398                    var cellNumberFormat = cellStyle.numberFormat,
1399                        cellMergeDown = parseInt($thisCell.attr('ss:MergeDown'), 10) || 0,
1400                        cellMergeAcross = parseInt($thisCell.attr('ss:MergeAcross'), 10) || 0,
1401                        cellSSData = $thisCell.children('ss\\:Data'),
1402                        cellInlineTags = {
1403                            'italic' : 0,
1404                            'bold' : 0,
1405                            'underline' : 0
1406                        },
1407
1408                        cellTagName = (cellStyle.bgColor && cellStyle.bgColor !== '#FFFFFF') ? 'th' : 'td',
1409                        cellClassNames = [],
1410                        cellClassName = '',
1411                        cellRowspan = '',
1412                        cellColspan = '',
1413                        cellInlineStyles = [];
1414
1415                    if(cellIndex % 2 === 1) {
1416                        cellClassNames.push('alt');
1417                    }
1418
1419                    if(cellSSData.length) {
1420                        cellInlineTags = {
1421                            'bold' : cellSSData.find('B').length,
1422                            'italic' : cellSSData.find('I').length,
1423                            'underline' : cellSSData.find('U').length
1424                        };
1425
1426                        cellContent = makeCellInlineFormats(cellSSData);
1427                    }
1428
1429                    if(typeof cellStyle.bgColor === 'string') {
1430                        cellInlineStyles.push('background-color: ' + cellStyle.bgColor);
1431                    }
1432
1433                    if(typeof cellStyle.alignment === 'object') {
1434                        if(typeof cellStyle.alignment.horizontal === 'string') {
1435                            cellInlineStyles.push('text-align: ' + cellStyle.alignment.horizontal);
1436                        }
1437                        if(typeof cellStyle.alignment.vertical === 'string') {
1438                            cellInlineStyles.push('vertical-align: ' + cellStyle.alignment.vertical);
1439                        }
1440                    }
1441
1442                    if(typeof cellStyle.font === 'object') {
1443                        if(typeof cellStyle.font.italic !== 'undefined') {
1444                            cellInlineStyles.push('font-style: italic');
1445                        }
1446                        if(typeof cellStyle.font.bold !== 'undefined') {
1447                            cellInlineStyles.push('font-weight: bold');
1448                        }
1449                        if(typeof cellStyle.font.underline !== 'undefined') {
1450                            cellInlineStyles.push('text-decoration: underline');
1451                        }
1452                        if(typeof cellStyle.font.color !== 'undefined') {
1453                            cellInlineStyles.push('color: ' + cellStyle.font.color);
1454                        }
1455                    }
1456
1457
1458                    /* Compile all the attributes */
1459
1460                    if(cellClassNames.length) {
1461                        cellClassName = [' class="', cellClassNames.join(' '), '"'].join('');
1462                    }
1463
1464                    if(cellMergeDown) {
1465                        cellRowspan = [' rowspan="', cellMergeDown+1, '"'].join('');
1466                    }
1467
1468                    if(cellMergeAcross) {
1469                        cellColspan = [' colspan="', cellMergeAcross+1, '"'].join('');
1470                    }
1471
1472                    if(typeof cellNumberFormat !== 'undefined') {
1473                        cellContent = makeCellCurrency(cellNumberFormat, cellContent);
1474                    }
1475
1476                    if(cellInlineStyles.length) {
1477                        cellInlineStyles = [' style="', cellInlineStyles.join('; '), '"'].join('');
1478                    } else {
1479                        cellInlineStyles = '';
1480                    }
1481
1482                    cellContent = cellContent.replace(/\n/g, '<br />');
1483
1484                    if(typeof cellStyle.font === 'object'
1485                    && typeof cellStyle.font.size !== 'undefined'
1486                    && cellStyle.font.size !== '11') {
1487                        cellContent = [
1488                            '<span style="font-size: ', Math.round(parseFloat(cellStyle.font.size, 10) / 11 * 100, 10), '%;">',
1489                                cellContent,
1490                            '</span>'
1491                        ].join('');
1492                    }
1493
1494                    return [
1495                        '<', cellTagName, cellClassName, cellRowspan, cellColspan, cellInlineStyles, '>',
1496                            cellContent,
1497                        '</', cellTagName, '>'
1498                    ].join('');
1499                }
1500            }
1501
1502            function makeCellInlineFormats($ssData) {
1503                /*
1504                 * $ssData.html() was initially used to get the string of the
1505                 * contents. However, that produced an error in all versions
1506                 * of IE as well as Safari. This was with jQuery 1.11.0.
1507                 *
1508                 * To work around this, an XMLSerializer is used instead. 
1509                 * https://developer.mozilla.org/en-US/docs/XMLSerializer
1510                 */
1511                var ssDataChildNodes = jQuery.makeArray($ssData[0].childNodes),
1512                    contentsStr = [];
1513
1514                while(ssDataChildNodes.length) {
1515                    contentsStr.push(xmlSerializer.serializeToString(ssDataChildNodes.shift()));
1516                }
1517
1518                contentsStr = contentsStr.join('');
1519
1520                var cellContentStr = contentsStr,
1521                    fontMatchArray = cellContentStr.match(/<Font+(.*?)>/g);
1522
1523                var inlineStyles;
1524                for(var i = 0, ii = fontMatchArray.length; i < ii; i++) {
1525                    inlineStyles = getInlineStyles(fontMatchArray[i]);
1526
1527                    if(inlineStyles === '') {
1528                        cellContentStr = cellContentStr
1529                                            .replace(fontMatchArray[i], '')
1530                                            .replace('</Font>', '');
1531                    } else {
1532                        cellContentStr = cellContentStr
1533                                            .replace(fontMatchArray[i], '<span style="' + inlineStyles + '">')
1534                                            .replace('</Font>', '</span>');
1535                    }
1536                }
1537
1538                var inlineTags = ['b', 'i', 'u', 'sup'],
1539                    tagName;
1540
1541                while(inlineTags.length) {
1542                    tagName = inlineTags.shift();
1543
1544                    cellContentStr = cellContentStr
1545                                        .replace(new RegExp('<' + tagName + '+(.*?)>', 'gi'), '<' + tagName + '>')
1546                                        .replace(new RegExp('<\/' + tagName + '+(.*?)>', 'gi'), '</' + tagName + '>');
1547                }
1548
1549                return cellContentStr;
1550
1551                function getInlineStyles(fontHTMLStr) {
1552                    /*
1553                     * The subtstring is to remove the opening and closing
1554                     * angled brackets (`<` and `>`) at the start and end
1555                     * of the string.
1556                     */
1557                    var splitArray = fontHTMLStr.substring(1, fontHTMLStr.length-1).split(' '),
1558                        attrStr,
1559                        splitAttrArray,
1560                        inlineStylesArray = [];
1561
1562                    while(splitArray.length) {
1563                        attrStr = splitArray.shift();
1564                        if(attrStr.indexOf('html:') === -1) continue;
1565
1566                        splitAttrArray = attrStr.replace(/(html\:)|"/g, '').toLowerCase().split('=');
1567
1568
1569                        switch(splitAttrArray[0]) {
1570                            case 'color':
1571                                if(splitAttrArray[1] === '#000000') continue;
1572                                inlineStylesArray.push(['color: ', splitAttrArray[1]].join(''));
1573                            break;
1574
1575                            case 'size':
1576                                inlineStylesArray.push(['font-size: ', Math.round((parseFloat(splitAttrArray[1], 10) / 11 * 100), 10), '%'].join(''));
1577                            break;
1578                        }
1579                    }
1580
1581                    return inlineStylesArray.join('; ');
1582                }
1583            }
1584
1585            function makeCellCurrency(cellNumberFormat, cellContent) {
1586                if(cellNumberFormat === '@'
1587                || cellNumberFormat === '0') return cellContent;
1588
1589                var splitCellContentArray;
1590
1591                if(cellNumberFormat === 'Currency') {
1592                    splitCellContentArray = parseFloat(cellContent, 10).toFixed(2).split('.');
1593
1594                    cellContent = 'RM' + contentWithcommas();
1595
1596                    if(splitCellContentArray.length > 1) {
1597                        cellContent += '.' + splitCellContentArray[1];
1598                    }
1599                } else if(cellNumberFormat === 'Fixed') {
1600                    splitCellContentArray = parseFloat(cellContent, 10).toFixed(2).split('.');
1601
1602                    cellContent = contentWithcommas();
1603
1604                    if(splitCellContentArray.length > 1) {
1605                        cellContent += '.' + splitCellContentArray[1];
1606                    }
1607                } else {
1608 
1609                    var currencyFormatArray = cellNumberFormat.split(';')[0].split(','),
1610
1611                        currency = currencyFormatArray[0].replace(/\[|\]| |#|\\|"/g, '').replace(/\$/, '').split('-'),
1612                        decimals = currencyFormatArray[1].split('.');
1613
1614                    if(currency.length > 1) currency = currency[0];
1615
1616                    if(decimals.length === 1) {
1617                        decimals = 0;
1618                    } else {
1619                        decimals = decimals[1].replace(/_\)/g, '').length;
1620                    }
1621
1622                    splitCellContentArray = parseFloat(cellContent, 10).toFixed(decimals).split('.');
1623                    cellContent = currency + contentWithcommas();
1624                    if(splitCellContentArray.length > 1) {
1625                        cellContent += '.' + splitCellContentArray[1];
1626                    }
1627                }
1628
1629                return cellContent;
1630
1631                function contentWithcommas() {
1632                    return splitCellContentArray[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
1633                }
1634            }
1635
1636            function attachRowHoverers() {
1637                $holders.find('[rowspan]').each(function() {
1638                    var $thisCell = jQuery(this),
1639                        rowspan = $thisCell.attr('rowspan');
1640
1641                    if(typeof rowspan !== 'string') return;
1642
1643                    rowspan = parseInt(rowspan, 10);
1644
1645                    var $thisRow = $thisCell.closest('tr'),
1646                        $nextRows = $thisRow.nextAll('tr').slice(0, rowspan-1);
1647
1648                    $nextRows.each(function() {
1649                        jQuery(this).on({
1650                            mouseenter : function() {
1651                                $thisCell.addClass('is-hovered');
1652                            },
1653
1654                            mouseleave : function() {
1655                                $thisCell.removeClass('is-hovered');
1656                            }
1657                        });
1658                    });
1659                });
1660            }
1661
1662            function filterOutEmptyRows($rows) {
1663                /* Filter out rows that only have empty cells */
1664                $rows.each(function() {
1665                    var empty = true;
1666
1667                    jQuery(this).find('Cell').each(function() {
1668                        if(jQuery(this).text() !== '') empty = false;
1669                        return false;
1670                    });
1671
1672                    if(empty) $rows = $rows.not(jQuery(this));
1673                });
1674
1675                return $rows;
1676            }
1677
1678            function hasAttributes($obj, attrArr) {
1679                while(attrArr.length) {
1680                    if(typeof $obj.attr(attrArr.shift()) !== 'undefined') {
1681                        return true;
1682                    }
1683                }
1684
1685                return false;
1686            }
1687        },
1688        /* End loadXMLTable */
1689
1690        loadXMLLocations : function($holders) {
1691            $holders = jQuery($holders);
1692            if(!$holders.length) return;
1693
1694            $holders.each(function() {
1695                var $thisHolder = jQuery(this);
1696                if($thisHolder.data('loaded-xml-locations') === true) return;
1697
1698                var xmlURL = $thisHolder.data('xml-locations-url');
1699                if(typeof xmlURL !== 'string') return;
1700
1701                jQuery.ajax({
1702                    type: 'GET',
1703                    url: xmlURL,
1704                    dataType: 'xml',
1705                    success: function(data) {
1706                        makeLocationsList(data, $thisHolder);
1707                        $thisHolder.data('loaded-xml-locations', true);
1708                    },
1709                    error: function(d) {
1710                        console.log(d);
1711                    }
1712                });
1713            });
1714
1715            function makeLocationsList(data, $holder) {
1716                var $locations = jQuery(data).find('obj'),
1717                    regions = [];
1718
1719                $locations.each(function() {
1720                    var $region = jQuery(this).find('region'),
1721                        regionStr = jQuery.trim($region.text());
1722
1723                    regionStr = (regionStr) ? regionStr : 'Default';
1724
1725                    if(regions.length) {
1726                        for(var i = regions.length-1; i > -1; i--) {
1727                            if(regions[i].name === regionStr) {
1728                                regions[i].$els.push(jQuery(this));
1729                                return;
1730                            }
1731                        }
1732                    }
1733
1734                    regions.push({
1735                        'name' : regionStr,
1736                        '$els' : [jQuery(this)]
1737                    });
1738                });
1739
1740                regions.sort(function(a,b) {
1741                    var strA = a.name.toLowerCase(),
1742                        strB = b.name.toLowerCase();
1743
1744                    if(strA < strB) return -1; // sort ascending
1745                    if(strA > strB) return 1;
1746                    return 0; // do nothing
1747                });
1748
1749                var output = [],
1750                    switcherStrArr = ['<span class="location-dropdown"><select>', '', '</select></span>'],
1751                    rowMarkup = ['<div class="row location-list', 1, '"><div class="row__flexi-width ', 3, '">'],
1752                    i, ii, totalLocationsInRegion;
1753                
1754                for(i = 0, ii = regions.length; i < ii; i++) {
1755                    rowMarkup[1] = (i === 0) ? ' current' : '';
1756
1757                    totalLocationsInRegion = regions[i].$els.length;
1758
1759                    if(totalLocationsInRegion < 2) {
1760                        rowMarkup[3] = 'has-1-item';
1761                    } else if(totalLocationsInRegion === 2) {
1762                        rowMarkup[3] = 'has-2-items';
1763                    } else if(totalLocationsInRegion === 3) {
1764                        rowMarkup[3] = 'has-3-items';
1765                    } else {
1766                        rowMarkup[3] = 'more-than-3-items';
1767                    }
1768
1769                    output.push(rowMarkup.join(''));
1770                        while(regions[i].$els.length) {
1771                            output.push([
1772                                '<div class="column">',
1773                                    '<div class="location">',
1774                                        '<div class="location__name">',
1775                                            jQuery.trim(regions[i].$els[0].find('storeName').text()),
1776                                        '</div>',
1777
1778                                        '<div class="location__address">',
1779                                            jQuery.trim(regions[i].$els[0].find('storeAddress').text()),
1780                                        '</div>',
1781
1782                                        '<div class="location__footer">',
1783                                            '<a href="https://maps.google.com/maps?daddr=',
1784                                                jQuery.trim(regions[i].$els[0].find('storeLat').text()), ',',
1785                                                jQuery.trim(regions[i].$els[0].find('storeLng').text()), '" target="_blank">',
1786                                                'Get Directions',
1787                                            '</a>',
1788                                        '</div>',
1789                                    '</div>',
1790                                '</div>'
1791                            ].join(''));
1792
1793                            regions[i].$els.shift();
1794                        }
1795                    output.push('</div></div>');
1796                }
1797
1798                $holder.html(output.join(''));
1799
1800                if(regions.length > 1) {
1801                    for(i = 0, ii = regions.length; i < ii; i++) {
1802                        switcherStrArr[1] += [
1803                            '<option value="', i, '">', regions[i].name, '</option>'
1804                        ].join('');
1805                    }
1806
1807                    var $switcher = jQuery(switcherStrArr.join(''));
1808
1809                    jQuery('<div class="text-center" />')
1810                        .prependTo($holder)
1811                        .append($switcher);
1812
1813                    $switcher.dropdownify();
1814
1815                    var $locationLists = $holder.find('.location-list');
1816
1817                    $holder.find('select').on('change', function() {
1818                        var targetIndex = parseInt(jQuery(this).val(), 10);
1819
1820                        $locationLists.each(function(index) {
1821                            if(index === targetIndex) {
1822                                jQuery(this)
1823                                    .addClass('current')
1824                                    .heightSyncify('enable')
1825                                    .heightSyncify('sync');
1826                            } else {
1827                                jQuery(this)
1828                                    .removeClass('current')
1829                                    .heightSyncify('disable');
1830                            }
1831                        });
1832                    });
1833                }
1834
1835                var syncHeightSelectors = ['.location__name', '.location__address'];
1836
1837                $holder.find('.location-list').heightSyncify({
1838                    items : syncHeightSelectors
1839                });
1840            }
1841        },
1842        /* End loadXMLLocations */
1843
1844        scrollTo : function($obj, callback) {
1845            if(typeof $obj !== 'object'
1846            || !$obj instanceof jQuery
1847            || !$obj.length) {
1848                return;
1849            }
1850
1851            var targetScrollPos = $obj.offset().top - 50;
1852
1853            if(targetScrollPos < jQuery(window).scrollTop()) {
1854                targetScrollPos -= jQuery('#mainNav').outerHeight();
1855
1856                if(jQuery('#mainSmallNav').css('display') !== 'none'
1857                && !jQuery('#mainHeader').hasClass('is-partially-hidden')) {
1858                    targetScrollPos -= jQuery('#mainSmallNav').outerHeight();
1859                }
1860            }
1861
1862            if(callback === 'snap') {
1863                jQuery(window).scrollTop(targetScrollPos);
1864            } else {
1865                jQuery('html, body').animate({
1866                    scrollTop : targetScrollPos
1867                }, 400, callback);
1868            }
1869        },
1870        /* End scrollTo */
1871
1872        watchSize : function() {
1873            homebrew.watchSize.apply(homebrew, arguments);
1874        }
1875        /* End watchSize */
1876    });
1877
1878
1879    jQuery.extend(FE.FootnoteAnchor.prototype, {
1880        init : function($el, $footnote) {
1881            if(!$el.length || !$footnote.length) return;
1882
1883            var instance = this;
1884
1885            instance.$el = $el;
1886            instance.$footnote = $footnote;
1887
1888            $el.tooltipify({
1889                contents : $footnote.html()
1890            });
1891
1892            instance.$el.data('footnote-anchor', this);
1893
1894            instance.enable();
1895        },
1896
1897        enable : function() {
1898            if(Modernizr.touch) return this;
1899
1900            var instance = this;
1901
1902            instance.$el.on({
1903                'click.footnotesTooltip' : function(e) {
1904                    e.preventDefault();
1905                    e.stopPropagation();
1906
1907                    var $thisFootnote = instance.$footnote;
1908
1909                    if(!jQuery('#notesToggler').hasClass('is-toggled')) {
1910                        jQuery('#notesToggler').togglerify('toggleOn', { noSlide : true });
1911                    }
1912
1913                    FE.scrollTo($thisFootnote);
1914                    $thisFootnote.addClass('is-active');
1915
1916                    jQuery(document)
1917                        .off('mouseup.notesFauxBlur')
1918                        .on('mouseup.notesFauxBlur', function(e) {
1919                            if(!$thisFootnote.is(e.target) && $thisFootnote.has(e.target).length === 0) {
1920                                jQuery(document).off('mouseup.notesFauxBlur');
1921                                $thisFootnote.removeClass('is-active');
1922                            }
1923                        });
1924                }
1925            });
1926
1927            return this;
1928        },
1929
1930        disable : function() {
1931            var instance = this;
1932
1933            instance.$el.off('click.footnotesTooltip');
1934            instance.$el.tooltipify('disable');
1935
1936            return instance;
1937        }
1938    });
1939
1940
1941    jQuery.extend(FE.Hasher.prototype, {
1942        get : function(args) {
1943            var instance = this,
1944                hash = window.location.hash;
1945
1946            if(hash === ''
1947            || hash === '#/'
1948            || hash.substr(0,2) !== '#/') {
1949                instance.data = {};
1950                return instance;
1951            }
1952
1953            var splitHash = hash.substring(2).split('&'),
1954                keyValuePairs,
1955                dataObj = {};
1956
1957            while(splitHash.length) {
1958                keyValuePairs = splitHash.shift().split('=');
1959                dataObj[jQuery.trim(keyValuePairs[0])] = decodeURIComponent(jQuery.trim(keyValuePairs[1]));
1960            }
1961
1962            instance.data = dataObj;
1963
1964            return instance;
1965        },
1966
1967        setData : function(data) {
1968            if(!data || !jQuery.isPlainObject(data)) return this;
1969            this.data = jQuery.extend({}, data);
1970            return this;
1971        },
1972
1973        extendData : function(data) {
1974            if(!data || !jQuery.isPlainObject(data)) return this;
1975
1976            if(this.data) {
1977                jQuery.extend(this.data, data);
1978            } else {
1979                this.data = data;
1980            }
1981            
1982
1983            return this;
1984        },
1985
1986        set : function(args) {
1987            args = args || {};
1988
1989            var instance = this,
1990                options = jQuery.extend(instance.options, args);
1991
1992            if(options.ignoreDefault && !instance.data) return instance;
1993
1994            instance.data = instance.data || {};
1995
1996            var hashKeys = [],
1997                hash = [],
1998                sequence = options.sequence;
1999
2000            if(sequence === 'alphabetical'
2001            || typeof sequence === 'function') {
2002                jQuery.each(instance.data, function(key, value) {
2003                    if(key === '' || value === '') return;
2004                    hashKeys.push(key);
2005                });
2006
2007                var sorter;
2008
2009                if(sequence === 'alphabetical') {
2010                    sorter = function(a, b) {
2011                        var _a = a.toLowerCase(),
2012                            _b = b.toLowerCase();
2013
2014                        if(_a < _b) return -1;
2015                        if(_a > _b) return 1;
2016                        return 0;
2017                    };
2018                } else if(typeof sequence === 'function') {
2019                    sorter = sequence;
2020                }
2021
2022                hashKeys.sort(sorter);
2023
2024                var currentHashKey;
2025
2026                while(hashKeys.length) {
2027                    currentHashKey = hashKeys.shift();
2028                    hash.push([currentHashKey, encodeURIComponent(instance.data[currentHashKey])].join('='));
2029                }
2030            } else if(typeof sequence === 'string') {
2031                var splitSequence = sequence.split(','),
2032                    i, ii, currentSequenceKey;
2033
2034                for(i = 0, ii = splitSequence.length; i < ii; i++) {
2035                    currentSequenceKey = splitSequence[i];
2036
2037                    if(instance.data[currentSequenceKey]) {
2038                        hash.push([currentSequenceKey, encodeURIComponent(instance.data[currentSequenceKey])].join('='));
2039                    }
2040                }
2041
2042                jQuery.each(instance.data, function(key, value) {
2043                    if(key === '' || value === '') return;
2044
2045                    for(i = splitSequence.length-1; i > -1; i--) {
2046                        if(key === splitSequence[i]) return;
2047                    }
2048
2049                    hash.push([key, encodeURIComponent(value)].join('='));
2050                });
2051            } else {
2052                jQuery.each(instance.data, function(key, value) {
2053                    if(key === '' || value === '') return;
2054                    hash.push([key, encodeURIComponent(value)].join('='));
2055                });
2056            }
2057
2058            window.location.hash = '#/' + hash.join('&');
2059        },
2060
2061        isSameWith : function(data) {
2062            if(!data) return false;
2063
2064            var same = true,
2065                count = 0,
2066                instance = this;
2067
2068            jQuery.each(data, function(key, value) {
2069                if(typeof instance.data[key] === 'undefined'
2070                || instance.data[key] !== value) {
2071                    same = false;
2072                    return false;
2073                } else {
2074                    count++;
2075                }
2076            });
2077
2078            if(same) {
2079                var refCount = 0;
2080
2081                jQuery.each(instance.data, function() {
2082                    refCount++;
2083                });
2084
2085                same = (count === refCount);
2086            }
2087
2088            return same;
2089        }
2090    });
2091
2092
2093    jQuery.extend(FE.StickyColumn.prototype, {
2094        callbacks : jQuery({}),
2095
2096        hasMousewheel : Boolean(jQuery.fn.mousewheel),
2097
2098        classes : {
2099            stickyColumn : 'sticky-column',
2100            sticky : 'is-sticky',
2101            stickBottom : 'is-stick-to-bottom'
2102        },
2103
2104        load : function(args) {
2105            var instance = this,
2106                proto = FE.StickyColumn.prototype;
2107
2108            /* If Mousewheel isn't available, load it now. */
2109            if(!proto.hasMousewheel) {
2110                if(!proto.loadingMousewheel) {
2111                    proto.loadingMousewheel = true;
2112
2113                    jQuery.getScript(FE.baseURL + 'js/plugins/jquery.mousewheel.min.js', function() {
2114                        FE.StickyColumn.prototype.hasMousewheel = true;
2115                        proto.callbacks.dequeue('mousewheelLoaded');
2116                    });
2117                }
2118
2119                proto.callbacks.queue('mousewheelLoaded', function(next) {
2120                    instance.init(args);
2121                    if(typeof next === 'function') next();
2122                });
2123            }
2124        },
2125
2126        init : function(args) {
2127            var $el = args.$el,
2128                $comparee = args.$comparee;
2129
2130            if(!$el || !$comparee
2131            || !$el instanceof jQuery || !$comparee instanceof jQuery
2132            || !$el.length || !$comparee.length) {
2133                return;
2134            }
2135
2136            var instance = this,
2137                proto = FE.StickyColumn.prototype;
2138
2139            instance.$el = $el;
2140            instance.$comparee = $comparee;
2141            instance.uniqueID = homebrew.generateUniqueID();
2142
2143            $el.addClass(instance.classes.stickyColumn);
2144            
2145            FE.watchSize('large', function(isLargeScreen) {
2146                if(isLargeScreen) {
2147                    instance.watch();
2148                } else {
2149                    instance.rest();
2150                    instance.unstick();
2151                    instance.updatePosition();
2152                }
2153            });
2154
2155            return instance;
2156        },
2157
2158        determineState : function() {
2159            if(!homebrew.screenSize.large) return 'should-be-unstuck';
2160
2161            var instance = this,
2162                $stickyCol = instance.$el,
2163                $comparee = instance.$comparee,
2164                windowTop = jQuery(window).scrollTop(),
2165                stickyColMarginTop = parseInt($stickyCol.css('margin-top'), 10);
2166
2167            instance.offsetTop = (jQuery('#mainHeader').css('position') === 'fixed') ? /*instance.$els.mainNav.height()*/ 0 : 0;
2168            instance.offsetBottom = parseInt($comparee.find('.column').css('margin-bottom'), 10)*2;
2169
2170            if(!$comparee.is(':visible') || $stickyCol.height() >= $comparee.height()) {
2171                return 'should-be-unstuck';
2172            } else if($stickyCol.hasClass(instance.classes.stickBottom) && $stickyCol.offset().top >= windowTop + instance.offsetTop) {
2173                return 'should-be-stuck';
2174            } else if((windowTop + $stickyCol.height() + stickyColMarginTop + instance.offsetTop) > ($comparee.offset().top + $comparee.height() - instance.offsetBottom)) {
2175                return 'should-be-stuck-to-bottom';
2176            } else if($comparee.offset().top <= windowTop + instance.offsetTop) {
2177                return 'should-be-stuck';
2178            } else {
2179                return 'should-be-unstuck';
2180            }
2181        },
2182
2183        updatePosition : function() {
2184            var instance = this,
2185                $el = instance.$el;
2186
2187            if($el.hasClass(instance.classes.stickBottom) || !$el.hasClass(instance.classes.sticky)) {
2188                $el.css('top', '');
2189            } else {
2190                $el.css('top', jQuery(window).scrollTop() - instance.$comparee.offset().top + instance.offsetTop + 'px');
2191            }
2192
2193            return instance;
2194        },
2195
2196        stick : function() {
2197            var instance = this;
2198            
2199            instance.$el
2200                .addClass(instance.classes.sticky)
2201                .removeClass(instance.classes.stickBottom);
2202            
2203            return instance;
2204        },
2205
2206        stickToBottom : function() {
2207            var instance = this;
2208
2209            instance.$el
2210                .addClass([
2211                    instance.classes.sticky,
2212                    instance.classes.stickBottom
2213                ].join(' '))
2214                .css('margin-top', '');
2215            
2216            return instance;
2217        },
2218
2219        unstick : function() {
2220            var instance = this;
2221            
2222            instance.$el
2223                .removeClass([
2224                    instance.classes.sticky,
2225                    instance.classes.stickBottom
2226                ].join(' '))
2227                .css('margin-top', '');
2228            
2229            return instance;
2230        },
2231
2232        watch : function() {
2233            var instance = this;
2234            
2235            jQuery(window).on([
2236                'scroll.', instance.uniqueID,
2237                ' resize.', instance.uniqueID
2238            ].join(''), function() {
2239                var state = instance.determineState();
2240                
2241                switch(state) {
2242                    case 'should-be-stuck' :           instance.stick();         break;
2243                    case 'should-be-stuck-to-bottom' : instance.stickToBottom(); break;
2244                    case 'should-be-unstuck' :         instance.unstick();       break;
2245                }
2246                
2247                instance.updatePosition();
2248            }).trigger('scroll');
2249            
2250            instance.$el.on('mousewheel', function(e) {
2251                if(instance.determineState() !== 'should-be-stuck') return;
2252
2253                var vector;
2254
2255                if(e.deltaY < 0) {
2256                    var bottomDiff = jQuery(window).scrollTop() + jQuery(window).height() - jQuery(this).offset().top - jQuery(this).height();
2257
2258                    if(bottomDiff >= 0) {
2259                        return;
2260                    } else if(Math.abs(bottomDiff) < 100) {
2261                        vector = bottomDiff;
2262                    } else {
2263                        vector = -100;
2264                    }
2265                } else if(e.deltaY > 0) {
2266                    var topDiff = jQuery(window).scrollTop() + instance.offsetTop - jQuery(this).offset().top;
2267
2268                    if(topDiff === 0) {
2269                        return;
2270                    } else if(topDiff < 100) {
2271                        vector = topDiff;
2272                    } else {
2273                        vector = 100;
2274                    }
2275                }
2276
2277                e.preventDefault();
2278                instance.$el.css('margin-top', [parseInt(instance.$el.css('margin-top'), 10) + vector, 'px'].join(''));
2279            });
2280
2281            return instance;
2282        },
2283
2284        rest : function() {
2285            jQuery(window).off('.' + this.uniqueID);
2286            return this;
2287        }
2288    });
2289
2290
2291    /*---- Init page ----*/
2292
2293    FE.initPage();
2294
2295    /* Run through any queued callbacks if available. */
2296    if(typeof FE__callbacks === 'object'
2297    && FE__callbacks instanceof Array
2298    && FE__callbacks.length) {
2299        while(FE__callbacks.length) {
2300            FE__callbacks.shift()();
2301        }
2302    }
2303});

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.