PageSourceSearch

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

js orthodoxyinamerica.org collected 2026-10-02 13:42:26 UTC 96,001 bytes, 2,308 lines download raw bytes

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

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.