<NME> pageguide.js
<BEF> /*
 * Tracelytics PageGuide
 *
 * Copyright 2013 Tracelytics
 * Free to use under the MIT license.
 * http://www.opensource.org/licenses/mit-license.php
 *
 * Contributing Author: Tracelytics Team
 */

/*
 * PageGuide usage:
 *
 *  Preferences:
 *  auto_show_first:    Whether or not to focus on the first visible item
 *                      immediately on PG open (default true)
 *  loading_selector:   The CSS selector for the loading element. pageguide
 *                      will wait until this element is no longer visible
 *                      starting up.
 *  track_events_cb:    Optional callback for tracking user interactions
 *                      with pageguide.  Should be a method taking a single
 *                      parameter indicating the name of the interaction.
 *                      (default none)
 *  handle_doc_switch:  Optional callback to enlight or adapt interface
 *                      depending on current documented element. Should be a
 *                      function taking 2 parameters, current and previous
 *                      data-tourtarget selectors. (default null)
 *  custom_open_button: Optional id for toggling pageguide. Default null.
 *                      If not specified then the default button is used.
 *  pg_caption:         Optional - Sets the visible caption
 *  dismiss_welcome:    Optional function to permanently dismiss the welcome
 *                      message, corresponding to check_welcome_dismissed.
 *                      Default: sets a localStorage or cookie value for the
 *                      (hashed) current URL to indicate the welcome message
 *                      has been dismissed, corresponds to default
 *                      check_welcome_dismissed function.
 *  check_welcome_dismissed: Optional function to check whether or not the
 *                      welcome message has been dismissed. Must return true
 *                      or false. This function should check against whatever
 *                      state change is made in dismiss_welcome. Default:
 *                      checks whether a localStorage or cookie value has been
 *                      set for the (hashed) current URL, corresponds to default
 *                      dismiss_welcome function.
 *  ready_callback:     A function to run once the pageguide ready event fires.
tl = window.tl || {};
tl.pg = tl.pg || {};

tl.pg.default_prefs = {
    'auto_show_first': true,
    'loading_selector' : '#loading',
    'track_events_cb': function() { return; },
    'handle_doc_switch': null,
    'custom_open_button': null,
    'pg_caption' : 'page guide',
    'check_welcome_dismissed': function () {
        var key = 'tlypageguide_welcome_shown_' + tl.pg.hashUrl();
        // first, try to use localStorage
        try {
            if (localStorage.getItem(key)) {
                return true;
            }
        // cookie fallback for older browsers
        } catch(e) {
            if (document.cookie.indexOf(key) > -1) {
                return true;
            }
        }
        return false;
    },
    'dismiss_welcome': function () {
        var key = 'tlypageguide_welcome_shown_' + tl.pg.hashUrl();
        try {
            localStorage.setItem(key, true);
        } catch(e) {
            var exp = new Date();
            exp.setDate(exp.getDate() + 365);
            document.cookie = (key + '=true; expires=' + exp.toUTCString());
        }
    }
};

tl.pg.init = function(preferences) {
    preferences = jQuery.extend({}, tl.pg.default_prefs, preferences);
    clearInterval(tl.pg.interval);

    /* page guide object, for pages that have one */
    if (jQuery("#tlyPageGuide").length === 0) {
        return;
    }

    var guide   = jQuery("#tlyPageGuide"),
        wrapper = jQuery('<div>', { id: 'tlyPageGuideWrapper' }),
        message = jQuery('<div>', { id: 'tlyPageGuideMessages'});

    message.append('<a href="#" class="tlypageguide_close" title="Close Guide">close</a>')
      .append('<span></span>')
      .append('<div></div>')
      .append('<a href="#" class="tlypageguide_back" title="Previous">Previous</a>')
      .append('<a href="#" class="tlypageguide_fwd" title="Next">Next</a>');

    if (preferences.custom_open_button == null && $('.tlypageguide_toggle').length < 1) {
        jQuery('<div/>', {
            'title': 'Launch Page Guide',
            'class': 'tlypageguide_toggle'
        }).append(preferences.pg_caption)
          .append('<div><span>' + guide.data('tourtitle') + '</span></div>')
          .append('<a href="#" class="tlypageguide_close" title="close guide">close guide &raquo;</a>')
          .appendTo(wrapper);
    }

    wrapper.append(guide);
    wrapper.append(message);

    // remove any stale pageguides
    jQuery('#tlyPageGuideWrapper').remove();

    jQuery('body').append(wrapper);

    var pg = new tl.pg.PageGuide(jQuery('#tlyPageGuideWrapper'), preferences);
    pg.ready(function() {
        pg.setup_welcome();
        pg.setup_handlers();
        pg.$base.children(".tlypageguide_toggle").animate({ "right": "-120px" }, 250);
    });

    return pg;
};

tl.pg.PageGuide = function (pg_elem, preferences) {
    this.preferences = preferences;
    this.$base = pg_elem;
    this.$all_items = jQuery('#tlyPageGuide > li', this.$base);
    this.$items = jQuery([]); /* fill me with visible elements on pg expand */
    this.$message = jQuery('#tlyPageGuideMessages');
    this.$fwd = jQuery('a.tlypageguide_fwd', this.$base);
    this.$back = jQuery('a.tlypageguide_back', this.$base);
    this.$welcome = jQuery('#tlyPageGuideWelcome');
    this.cur_idx = 0;
    this.track_event = this.preferences.track_events_cb;
    this.handle_doc_switch = this.preferences.handle_doc_switch;
    this.custom_open_button = this.preferences.custom_open_button;
    this.is_open = false;
};

tl.pg.hashUrl = function() {
    var str = window.location.href;
    var hash = 0, i, char;
    if (str.length === 0) {
        return hash;
    }
    for (i = 0; i < str.length; i++) {
        char = str.charCodeAt(i);
        hash = ((hash<<5)-hash)+char;
        hash = hash & hash;
    }
    return hash.toString();
};

tl.pg.isScrolledIntoView = function(elem) {
    var dvtop = jQuery(window).scrollTop(),
        dvbtm = dvtop + jQuery(window).height(),
        eltop = jQuery(elem).offset().top,
        elbtm = eltop + jQuery(elem).height();

    return (elbtm >= dvtop) && (eltop <= dvbtm - 100);
};

tl.pg.PageGuide.prototype.setup_welcome = function () {
    var $welcome = jQuery('#tlyPageGuideWelcome');
    var that = this;
    if ($welcome.length > 0) {
        that.preferences.show_welcome = !that.preferences.check_welcome_dismissed();
        if (that.preferences.show_welcome) {
            if (!jQuery('#tlyPageGuideOverlay').length) {
                jQuery('body').prepend('<div id="tlyPageGuideOverlay"></div>');
            }
            $welcome.appendTo(that.$base);
        }

        if ($welcome.find('.tlypageguide_ignore').length) {
            $welcome.on('click', '.tlypageguide_ignore', function () {
                that.close_welcome();
                that.track_event('PG.ignoreWelcome');
            });
        }
        if ($welcome.find('.tlypageguide_dismiss').length) {
            $welcome.on('click', '.tlypageguide_dismiss', function () {
                that.close_welcome();
                that.preferences.dismiss_welcome();
                that.track_event('PG.dismissWelcome');
            });
        }
        $welcome.on('click', '.tlypageguide_start', function () {
            that.open();
            that.track_event('PG.startFromWelcome');
        });

        if (that.preferences.show_welcome) {
            that.pop_welcome();
        }
    }
};

tl.pg.PageGuide.prototype.ready = function(callback) {
    var that = this;
    tl.pg.interval = window.setInterval(function() {
            if (!jQuery(that.preferences.loading_selector).is(':visible')) {
                callback();
                clearInterval(tl.pg.interval);
            }
        }, 250);
    return this;
};

/* to be executed on pg expand */
tl.pg.PageGuide.prototype._on_expand = function () {
    var that = this,
        $d = document,
        $w = window;

    /* set up initial state */
    this.position_tour();
    this.cur_idx = 0;

    // create a new stylesheet:
    var ns = $d.createElement('style');
    $d.getElementsByTagName('head')[0].appendChild(ns);

    // keep Safari happy
    if (!$w.createPopup) {
        ns.appendChild($d.createTextNode(''));
        ns.setAttribute("type", "text/css");
    }

    // get a pointer to the stylesheet you just created
    var sh = $d.styleSheets[$d.styleSheets.length - 1];

    // space for IE rule set
    var ie = "";

    /* add number tags and PG shading elements */
    this.$items.each(function(i) {
        var $p = jQuery(jQuery(this).data('tourtarget') + ":visible:first");
        $p.addClass("tlypageguide_shadow tlypageguide_shadow" + i);

        var node_text = '.tlypageguide_shadow' + i + ':after { height: ' +
                            $p.outerHeight() + 'px; width: ' + $p.outerWidth(false) + 'px; }';

        if (!$w.createPopup) {
            // modern browsers
            var k = $d.createTextNode(node_text, 0);
            ns.appendChild(k);
        } else {
            // for IE
            ie += node_text;
        }

        jQuery(this).find('ins').remove();
        jQuery(this).prepend('<ins>' + (i + 1) + '</ins>');
        jQuery(this).data('idx', i);
    });

    // is IE? slam styles in all at once:
    if ($w.createPopup) {
        sh.cssText = ie;
    }

    /* decide to show first? */
    if (this.preferences.auto_show_first && this.$items.length > 0) {
        this.show_message(0);
    }
};

tl.pg.PageGuide.prototype.open = function() {
    if (this.preferences.show_welcome) {
        this.preferences.dismiss_welcome();
        this.close_welcome();
    }
    if (this.is_open) {
        return;
    } else {
        this.is_open = true;
    }

    this.track_event('PG.open');

    this._on_expand();
    this.$items.toggleClass('expanded');
    jQuery('body').addClass('tlypageguide-open');
};

tl.pg.PageGuide.prototype.close = function() {
    if (!this.is_open) {
        return;
    } else {
        this.is_open = false;
    }

    this.track_event('PG.close');

    this.$items.toggleClass('expanded');
    this.$message.animate({ height: "0" }, 500, function() {
        jQuery(this).hide();
    });
    /* clear number tags and shading elements */
    $('[class~="tlypageguide_shadow"]').removeClass(function(i, c) {
        return c.match(/tlypageguide_shadow.*?\b/g).join(" ");
    });
    jQuery('ins').remove();
    jQuery('body').removeClass('tlypageguide-open');
};

tl.pg.PageGuide.prototype.setup_handlers = function () {
    var that = this;

    /* interaction: open/close PG interface */
    var interactor = (that.custom_open_button == null) ?
                    jQuery('.tlypageguide_toggle', this.$base) :
                    jQuery(that.custom_open_button);
    interactor.off();
    interactor.on('click', function() {
        if (that.is_open) {
            that.close();
        } else if (that.preferences.show_welcome &&
                  !that.preferences.check_welcome_dismissed() &&
                  !jQuery('body').hasClass('tlyPageGuideWelcomeOpen')) {
            that.pop_welcome();
        } else {
            that.open();
        }
        return false;
    });

    jQuery('.tlypageguide_close', this.$message.add(jQuery('.tlypageguide_toggle')))
        .on('click', function() {
            that.close();
            return false;
    });

    /* interaction: item click */
    this.$all_items.off();
    this.$all_items.on('click', function() {
        var new_index = jQuery(this).data('idx');
        that.track_event('PG.specific_elt');
        that.show_message(new_index);
    });

    /* interaction: fwd/back click */
    this.$fwd.on('click', function() {
        var new_index = (that.cur_idx + 1) % that.$items.length;

        that.track_event('PG.fwd');
        that.show_message(new_index);
        return false;
    });

    this.$back.on('click', function() {
        /*
         * If -n < x < 0, then the result of x % n will be x, which is
         * negative. To get a positive remainder, compute (x + n) % n.
         */
        var new_index = (that.cur_idx + that.$items.length - 1) % that.$items.length;

        that.track_event('PG.back');
        that.show_message(new_index, true);
        return false;
    });

    /* register resize callback */
    jQuery(window).resize(function() { that.position_tour(); });
};

tl.pg.PageGuide.prototype.show_message = function (new_index, left) {
    var old_idx = this.cur_idx,
        old_item = this.$items[old_idx],
        new_item = this.$items[new_index];

    this.cur_idx = new_index;
    if(this.handle_doc_switch){
        this.handle_doc_switch(jQuery(new_item).data('tourtarget'),
                               jQuery(old_item).data('tourtarget'));
    }

    jQuery('div', this.$message).html(jQuery(new_item).children('div').html());
    this.$items.removeClass("tlypageguide-active");
    jQuery(new_item).addClass("tlypageguide-active");

    if (!tl.pg.isScrolledIntoView(jQuery(new_item))) {
        jQuery('html,body').animate({scrollTop: jQuery(new_item).offset().top - 50}, 500);
    }
    var defaultHeight = 100;
    var oldHeight = parseFloat(this.$message.css("height"));
    this.$message.css("height", "auto");
    var height = parseFloat(this.$message.outerHeight());
    this.$message.css("height", oldHeight + 'px');
    if (height < defaultHeight) {
        height = defaultHeight;
    }
    if (height > jQuery(window).height()/2) {
        height = jQuery(window).height()/2;
    }
    height = height + "px";

    this.$message.show().animate({'height': height}, 500);
    this.roll_number(jQuery('span', this.$message), jQuery(new_item).children('ins').html(), left);
};

tl.pg.PageGuide.prototype.roll_number = function (num_wrapper, new_text, left) {
    num_wrapper.animate({ 'text-indent': (left ? '' : '-') + '50px' }, 'fast', function() {
        num_wrapper.html(new_text);
        num_wrapper.css({ 'text-indent': (left ? '-' : '') + '50px' }, 'fast').animate({ 'text-indent': "0" }, 'fast');
    });
};

tl.pg.PageGuide.prototype.position_tour = function () {
    /* set PG element positions for visible tourtargets */
    this.$items = this.$all_items.filter(function () {
        return jQuery(jQuery(this).data('tourtarget')).is(':visible');
    });

    this.$items.each(function() {
        var arrow   = jQuery(this),
            target  = jQuery(arrow.data('tourtarget')).filter(':visible:first'),
            position = arrow.data('position'),
            setLeft = target.offset().left,
            setTop  = target.offset().top;

        if (position == "fixed") {
            setTop  -= jQuery(window).scrollTop();
        }

        if (arrow.hasClass("tlypageguide_top")) {
            setTop -= 60;
        } else if (arrow.hasClass("tlypageguide_bottom")) {
            setTop += target.outerHeight() + 15;
        } else {
            setTop += 5;
        }

        if (arrow.hasClass("tlypageguide_right")) {
            setLeft += target.outerWidth(false) + 15;
        } else if (arrow.hasClass("tlypageguide_left")) {
            setLeft -= 65;
        } else {
            setLeft += 5;
        }

        arrow.css({ "left": setLeft + "px", "top": setTop + "px", "position": position});
    });
};

tl.pg.PageGuide.prototype.pop_welcome = function () {
    jQuery('body').addClass('tlyPageGuideWelcomeOpen');
    this.track_event('PG.welcomeShown');
};

tl.pg.PageGuide.prototype.close_welcome = function () {
    jQuery('body').removeClass('tlyPageGuideWelcomeOpen');
};
                diff.index = newTargetData.index;
            }
            // push diff onto changequeue if changes have been made
            if (diff.targetStyle != null || diff.index != null) {
                self.changeQueue.push(diff);
            }
            $.extend(self.targetData[target], newTargetData);
        }
        self.visibleTargets = newVisibleTargets;
    };

    /**
     * position the shadow elements (and their attached index bubbles) in their
     * appropriate places over the visible targets. executes by iterating through
     * all the changes that have been pushed to changeQueue
     **/
    tl.pg.PageGuide.prototype.positionOverlays = function () {
        for (var i=0; i<this.changeQueue.length; i++) {
            var changes = this.changeQueue[i];
            var selector = '.tlypageguide_shadow' + tl.pg.hashCode(changes.target);
            var $el = this.$content.find(selector);
            if (changes.targetStyle != null) {
                var style = $.extend({}, changes.targetStyle);
                for (var prop in style) {
                    // fix this
                    if (prop === 'z-index') {
                        style[prop] = (typeof style[prop] === 'number') ?
                            style[prop] + 1 : this.preferences.default_zindex;
                    }
                }
                $el.css(style);
            }
            if (changes.index != null) {
                $el.find('.tlyPageGuideStepIndex').text(changes.index);
            }
        }
        this.changeQueue = [];
    };

    /**
     * find all pageguide steps and appropriately position their corresponding pageguide
     * elements. ideal to run on its own whenever pageguide is opened, or when a DOM
     * change takes place that will not affect the visibility of the target elements
     * (e.g. resize)
     **/
    tl.pg.PageGuide.prototype.refreshVisibleSteps = function () {
        this.addSteps();
        this.checkTargets();
        this.positionOverlays();
    };

    /**
     * update visible steps on page, and also navigate to the next available step if
     * necessary. this is especially useful when DOM changes take place while the
     * pageguide is open, meaning its target elements may be affected.
     **/
    tl.pg.PageGuide.prototype.updateVisible = function () {
        this.refreshVisibleSteps();
        if (this.cur_selector != null && this.cur_selector !== this.visibleTargets[this.cur_idx]) {
            // mod by target length in case user was viewing last target and it got removed
            var newIndex = this.cur_idx % this.visibleTargets.length;
            this.show_message(newIndex);
        }
    };

    /**
     * show the step specified by either a numeric index or a selector.
     * index (number): index of the currently visible step to show.
     **/
    tl.pg.PageGuide.prototype.show_message = function (index) {
        var targetKey = this.visibleTargets[index];
        var target = this.targetData[targetKey];
        if (target != null) {
            var selector = '.tlypageguide_shadow' + tl.pg.hashCode(targetKey);

            if (this.handle_doc_switch) {
                var len = this.visibleTargets.length;
                var prevTargetKey = this.visibleTargets[(index - 1 + len) % len];
                this.handle_doc_switch(targetKey, prevTargetKey);
            }

            this.$content.find('.tlypageguide-active').removeClass('tlypageguide-active');
            this.$content.find(selector).addClass('tlypageguide-active');

            this.$message.find('.tlypageguide_text').html(target.content);
            this.cur_idx = index;
            this.cur_selector = targetKey;

            // DOM stuff
            var defaultHeight = 100;
            var oldHeight = parseFloat(this.$message.css("height"));
            this.$message.css("height", "auto");
            var height = parseFloat(this.$message.outerHeight());
            this.$message.css("height", oldHeight);
            if (height < defaultHeight) {
                height = defaultHeight;
            }
            if (height > $(window).height()/2) {
                height = $(window).height()/2;
            }

            this.$message.show().animate({'height': height}, 500);
            if (!tl.pg.isScrolledIntoView($(targetKey), this.$message.outerHeight())) {
                $('html,body').animate({scrollTop: target.targetStyle.top - 50}, 500);
            }
            this.roll_number(this.$message.find('span'), target.index);
        }
    };

    /**
     * navigate to the previous step. if at the first step, loop around to the last.
     **/
    tl.pg.PageGuide.prototype.navigateBack = function () {
        /*
         * If -n < x < 0, then the result of x % n will be x, which is
         * negative. To get a positive remainder, compute (x + n) % n.
         */
        var new_index = (this.cur_idx + this.visibleTargets.length - 1) % this.visibleTargets.length;

        this.track_event('PG.back');
        this.show_message(new_index, true);
        return false;
    };

    /**
     * navigate to the next step. if at last step, loop back to the first.
     **/
    tl.pg.PageGuide.prototype.navigateForward = function () {
        var new_index = (this.cur_idx + 1) % this.visibleTargets.length;

        this.track_event('PG.fwd');
        this.show_message(new_index, true);
        return false;
    };

    /**
     * open the pageguide! can be fired at any time, though it's usually done via
     * the toggle element (either boilerplate or user-specified) or the welcome
     * modal.
     **/
    tl.pg.PageGuide.prototype.open = function() {
        if (this.is_open) {
            return;
        } else {
            tl.pg.closeOpenGuides(this.uuid);
            this._open();
        }
    };

    tl.pg.PageGuide.prototype._open = function () {
        if (this.preferences.show_welcome) {
            this.preferences.dismiss_welcome();
            this.close_welcome();
        }
        this.is_open = true;
        this.track_event('PG.open');

        this.refreshVisibleSteps();

        if (this.preferences.auto_show_first && this.visibleTargets.length) {
            this.show_message(0);
        }
        $('body').addClass('tlypageguide-open');
        this.$toggle.addClass('tlyPageGuideToggleActive');

        var self = this;
        if (self.preferences.auto_refresh) {
            self.updateTimer(function () {
                self.updateVisible();
            }, 'overlay');
        }
    };

    tl.pg.PageGuide.prototype.updateTimer = function (cb, prop) {
        var self = this;
        cb();
        self.timer[prop] = setTimeout(function () {
            self.updateTimer(cb, prop);
        }, self.preferences.refresh_interval);
    };

    /**
     * close the pageguide. can also be fired at any time, though usually done via
     * the toggle or the close button.
     **/
    tl.pg.PageGuide.prototype.close = function() {
        if (!this.is_open) {
            return;
        } else {
            this._close();
        }
    };

    tl.pg.PageGuide.prototype._close = function () {
        this.is_open = false;
        this.track_event('PG.close');
        if (this.preferences.auto_refresh) {
            clearTimeout(this.timer.overlay);
        }

        this.$content.find('.tlypageguide_shadow').css('display', 'none');
        this.$content.find('.tlypageguide-active').removeClass('tlypageguide-active');
        this.$message.animate({ height: "0" }, 500, function() {
            $(this).hide();
        });

        $('body').removeClass('tlypageguide-open');
        this.$toggle.removeClass('tlyPageGuideToggleActive');
    };

    /**
     * bind all relevant event handlers within the document.
     **/
    tl.pg.PageGuide.prototype.setup_handlers = function () {
        var self = this;

        /* interaction: open/close PG interface */
        var interactor = (self.custom_open_button == null) ?
                        self.$base.find('#tlyPageGuideToggle' + self.uuid) :
                        $(self.custom_open_button);
        interactor.off();
        interactor.on('click', function() {
            if (self.is_open) {
                self.close();
            } else if (self.preferences.show_welcome &&
                      !self.preferences.check_welcome_dismissed() &&
                      !$('body').hasClass('tlyPageGuideWelcomeOpen')) {
                self.pop_welcome();
            } else {
                self.open();
            }
        });

        /* close guide */
        $('.tlypageguide_close', self.$message.add($('.tlypageguide_toggle')))
            .on('click', function() {
                self.close();
                return false;
        });

        /* interaction: item click */
        self.$base.on('click', '.tlyPageGuideStepIndex', function () {
            var selector = self.hashTable[$(this).parent().data('selectorhash')];
            var target = self.targetData[selector];
            var index = (target) ? target.index : 1;
            self.track_event('PG.specific_elt');
            self.show_message(index - 1);
        });

        /* interaction: fwd/back click */
        self.$fwd.on('click', function() {
            if (self.is_open) {
                self.navigateForward();
            }
            return false;
        });

        self.$back.on('click', function() {
            if (self.is_open) {
                self.navigateBack();
            }
            return false;
        });

        // pass through click events on overlays if necessary
        if (self.preferences.pointer_fallback) {
            self.$base.on('click', '.tlypageguide_shadow', function (e) {
                $(this).hide();
                var $bottomElement = $(document.elementFromPoint(e.clientX, e.clientY));
                if ($bottomElement.is('a')) {
                    $bottomElement.get(0).click(); // required for anchor click
                } else {
                    $bottomElement.trigger(e.type);
                }
                $(this).show();
            });
        }

        /* register resize callback */
        $(window).resize(function() {
            if (self.is_open) {
                self.refreshVisibleSteps();
            }
        });
    };

    /**
     * animate a given number to roll to the side.
     * num_wrapper (jQuery element): the element whose number to roll
     * new_text (string): the new text to roll across the element
     * left (boolean): whether or not to roll to the left-hand side
     **/
    tl.pg.PageGuide.prototype.roll_number = function (num_wrapper, new_text, left) {
        num_wrapper.animate({ 'text-indent': (left ? '' : '-') + '50px' }, 'fast', function() {
            num_wrapper.html(new_text);
            num_wrapper.css({ 'text-indent': (left ? '-' : '') + '50px' }, 'fast').animate({ 'text-indent': "0" }, 'fast');
        });
    };

    /**
     * pop up the welcome modal.
     **/
    tl.pg.PageGuide.prototype.pop_welcome = function () {
        $('body').addClass('tlyPageGuideWelcomeOpen');
        this.track_event('PG.welcomeShown');
    };

    /**
     * close the welcome modal.
     **/
    tl.pg.PageGuide.prototype.close_welcome = function () {
        $('body').removeClass('tlyPageGuideWelcomeOpen');
    };
}(jQuery));

<MSG> use $ for jQuery; wrap in closure to protect

<DFF> @@ -45,415 +45,418 @@
 tl = window.tl || {};
 tl.pg = tl.pg || {};
 
-tl.pg.default_prefs = {
-    'auto_show_first': true,
-    'loading_selector' : '#loading',
-    'track_events_cb': function() { return; },
-    'handle_doc_switch': null,
-    'custom_open_button': null,
-    'pg_caption' : 'page guide',
-    'check_welcome_dismissed': function () {
-        var key = 'tlypageguide_welcome_shown_' + tl.pg.hashUrl();
-        // first, try to use localStorage
-        try {
-            if (localStorage.getItem(key)) {
-                return true;
+(function ($) {
+
+    tl.pg.default_prefs = {
+        'auto_show_first': true,
+        'loading_selector' : '#loading',
+        'track_events_cb': function() { return; },
+        'handle_doc_switch': null,
+        'custom_open_button': null,
+        'pg_caption' : 'page guide',
+        'check_welcome_dismissed': function () {
+            var key = 'tlypageguide_welcome_shown_' + tl.pg.hashUrl();
+            // first, try to use localStorage
+            try {
+                if (localStorage.getItem(key)) {
+                    return true;
+                }
+            // cookie fallback for older browsers
+            } catch(e) {
+                if (document.cookie.indexOf(key) > -1) {
+                    return true;
+                }
             }
-        // cookie fallback for older browsers
-        } catch(e) {
-            if (document.cookie.indexOf(key) > -1) {
-                return true;
-            }
-        }
-        return false;
-    },
-    'dismiss_welcome': function () {
-        var key = 'tlypageguide_welcome_shown_' + tl.pg.hashUrl();
-        try {
-            localStorage.setItem(key, true);
-        } catch(e) {
-            var exp = new Date();
-            exp.setDate(exp.getDate() + 365);
-            document.cookie = (key + '=true; expires=' + exp.toUTCString());
-        }
-    }
-};
-
-tl.pg.init = function(preferences) {
-    preferences = jQuery.extend({}, tl.pg.default_prefs, preferences);
-    clearInterval(tl.pg.interval);
-
-    /* page guide object, for pages that have one */
-    if (jQuery("#tlyPageGuide").length === 0) {
-        return;
-    }
-
-    var guide   = jQuery("#tlyPageGuide"),
-        wrapper = jQuery('<div>', { id: 'tlyPageGuideWrapper' }),
-        message = jQuery('<div>', { id: 'tlyPageGuideMessages'});
-
-    message.append('<a href="#" class="tlypageguide_close" title="Close Guide">close</a>')
-      .append('<span></span>')
-      .append('<div></div>')
-      .append('<a href="#" class="tlypageguide_back" title="Previous">Previous</a>')
-      .append('<a href="#" class="tlypageguide_fwd" title="Next">Next</a>');
-
-    if (preferences.custom_open_button == null && $('.tlypageguide_toggle').length < 1) {
-        jQuery('<div/>', {
-            'title': 'Launch Page Guide',
-            'class': 'tlypageguide_toggle'
-        }).append(preferences.pg_caption)
-          .append('<div><span>' + guide.data('tourtitle') + '</span></div>')
-          .append('<a href="#" class="tlypageguide_close" title="close guide">close guide &raquo;</a>')
-          .appendTo(wrapper);
-    }
-
-    wrapper.append(guide);
-    wrapper.append(message);
-
-    // remove any stale pageguides
-    jQuery('#tlyPageGuideWrapper').remove();
-
-    jQuery('body').append(wrapper);
-
-    var pg = new tl.pg.PageGuide(jQuery('#tlyPageGuideWrapper'), preferences);
-    pg.ready(function() {
-        pg.setup_welcome();
-        pg.setup_handlers();
-        pg.$base.children(".tlypageguide_toggle").animate({ "right": "-120px" }, 250);
-    });
-
-    return pg;
-};
-
-tl.pg.PageGuide = function (pg_elem, preferences) {
-    this.preferences = preferences;
-    this.$base = pg_elem;
-    this.$all_items = jQuery('#tlyPageGuide > li', this.$base);
-    this.$items = jQuery([]); /* fill me with visible elements on pg expand */
-    this.$message = jQuery('#tlyPageGuideMessages');
-    this.$fwd = jQuery('a.tlypageguide_fwd', this.$base);
-    this.$back = jQuery('a.tlypageguide_back', this.$base);
-    this.$welcome = jQuery('#tlyPageGuideWelcome');
-    this.cur_idx = 0;
-    this.track_event = this.preferences.track_events_cb;
-    this.handle_doc_switch = this.preferences.handle_doc_switch;
-    this.custom_open_button = this.preferences.custom_open_button;
-    this.is_open = false;
-};
-
-tl.pg.hashUrl = function() {
-    var str = window.location.href;
-    var hash = 0, i, char;
-    if (str.length === 0) {
-        return hash;
-    }
-    for (i = 0; i < str.length; i++) {
-        char = str.charCodeAt(i);
-        hash = ((hash<<5)-hash)+char;
-        hash = hash & hash;
-    }
-    return hash.toString();
-};
-
-tl.pg.isScrolledIntoView = function(elem) {
-    var dvtop = jQuery(window).scrollTop(),
-        dvbtm = dvtop + jQuery(window).height(),
-        eltop = jQuery(elem).offset().top,
-        elbtm = eltop + jQuery(elem).height();
-
-    return (elbtm >= dvtop) && (eltop <= dvbtm - 100);
-};
-
-tl.pg.PageGuide.prototype.setup_welcome = function () {
-    var $welcome = jQuery('#tlyPageGuideWelcome');
-    var that = this;
-    if ($welcome.length > 0) {
-        that.preferences.show_welcome = !that.preferences.check_welcome_dismissed();
-        if (that.preferences.show_welcome) {
-            if (!jQuery('#tlyPageGuideOverlay').length) {
-                jQuery('body').prepend('<div id="tlyPageGuideOverlay"></div>');
+            return false;
+        },
+        'dismiss_welcome': function () {
+            var key = 'tlypageguide_welcome_shown_' + tl.pg.hashUrl();
+            try {
+                localStorage.setItem(key, true);
+            } catch(e) {
+                var exp = new Date();
+                exp.setDate(exp.getDate() + 365);
+                document.cookie = (key + '=true; expires=' + exp.toUTCString());
             }
-            $welcome.appendTo(that.$base);
         }
+    };
 
-        if ($welcome.find('.tlypageguide_ignore').length) {
-            $welcome.on('click', '.tlypageguide_ignore', function () {
-                that.close_welcome();
-                that.track_event('PG.ignoreWelcome');
-            });
+    tl.pg.init = function(preferences) {
+        preferences = $.extend({}, tl.pg.default_prefs, preferences);
+        clearInterval(tl.pg.interval);
+
+        /* page guide object, for pages that have one */
+        if ($("#tlyPageGuide").length === 0) {
+            return;
         }
-        if ($welcome.find('.tlypageguide_dismiss').length) {
-            $welcome.on('click', '.tlypageguide_dismiss', function () {
-                that.close_welcome();
-                that.preferences.dismiss_welcome();
-                that.track_event('PG.dismissWelcome');
-            });
+
+        var guide   = $("#tlyPageGuide"),
+            wrapper = $('<div>', { id: 'tlyPageGuideWrapper' }),
+            message = $('<div>', { id: 'tlyPageGuideMessages'});
+
+        message.append('<a href="#" class="tlypageguide_close" title="Close Guide">close</a>')
+          .append('<span></span>')
+          .append('<div></div>')
+          .append('<a href="#" class="tlypageguide_back" title="Previous">Previous</a>')
+          .append('<a href="#" class="tlypageguide_fwd" title="Next">Next</a>');
+
+        if (preferences.custom_open_button == null && $('.tlypageguide_toggle').length < 1) {
+            $('<div/>', {
+                'title': 'Launch Page Guide',
+                'class': 'tlypageguide_toggle'
+            }).append(preferences.pg_caption)
+              .append('<div><span>' + guide.data('tourtitle') + '</span></div>')
+              .append('<a href="#" class="tlypageguide_close" title="close guide">close guide &raquo;</a>')
+              .appendTo(wrapper);
         }
-        $welcome.on('click', '.tlypageguide_start', function () {
-            that.open();
-            that.track_event('PG.startFromWelcome');
+
+        wrapper.append(guide);
+        wrapper.append(message);
+
+        // remove any stale pageguides
+        $('#tlyPageGuideWrapper').remove();
+
+        $('body').append(wrapper);
+
+        var pg = new tl.pg.PageGuide($('#tlyPageGuideWrapper'), preferences);
+        pg.ready(function() {
+            pg.setup_welcome();
+            pg.setup_handlers();
+            pg.$base.children(".tlypageguide_toggle").animate({ "right": "-120px" }, 250);
         });
 
-        if (that.preferences.show_welcome) {
-            that.pop_welcome();
+        return pg;
+    };
+
+    tl.pg.PageGuide = function (pg_elem, preferences) {
+        this.preferences = preferences;
+        this.$base = pg_elem;
+        this.$all_items = $('#tlyPageGuide > li', this.$base);
+        this.$items = $([]); /* fill me with visible elements on pg expand */
+        this.$message = $('#tlyPageGuideMessages');
+        this.$fwd = $('a.tlypageguide_fwd', this.$base);
+        this.$back = $('a.tlypageguide_back', this.$base);
+        this.$welcome = $('#tlyPageGuideWelcome');
+        this.cur_idx = 0;
+        this.track_event = this.preferences.track_events_cb;
+        this.handle_doc_switch = this.preferences.handle_doc_switch;
+        this.custom_open_button = this.preferences.custom_open_button;
+        this.is_open = false;
+    };
+
+    tl.pg.hashUrl = function() {
+        var str = window.location.href;
+        var hash = 0, i, char;
+        if (str.length === 0) {
+            return hash;
         }
-    }
-};
-
-tl.pg.PageGuide.prototype.ready = function(callback) {
-    var that = this;
-    tl.pg.interval = window.setInterval(function() {
-            if (!jQuery(that.preferences.loading_selector).is(':visible')) {
-                callback();
-                clearInterval(tl.pg.interval);
+        for (i = 0; i < str.length; i++) {
+            char = str.charCodeAt(i);
+            hash = ((hash<<5)-hash)+char;
+            hash = hash & hash;
+        }
+        return hash.toString();
+    };
+
+    tl.pg.isScrolledIntoView = function(elem) {
+        var dvtop = $(window).scrollTop(),
+            dvbtm = dvtop + $(window).height(),
+            eltop = $(elem).offset().top,
+            elbtm = eltop + $(elem).height();
+
+        return (elbtm >= dvtop) && (eltop <= dvbtm - 100);
+    };
+
+    tl.pg.PageGuide.prototype.setup_welcome = function () {
+        var $welcome = $('#tlyPageGuideWelcome');
+        var that = this;
+        if ($welcome.length > 0) {
+            that.preferences.show_welcome = !that.preferences.check_welcome_dismissed();
+            if (that.preferences.show_welcome) {
+                if (!$('#tlyPageGuideOverlay').length) {
+                    $('body').prepend('<div id="tlyPageGuideOverlay"></div>');
+                }
+                $welcome.appendTo(that.$base);
+            }
+
+            if ($welcome.find('.tlypageguide_ignore').length) {
+                $welcome.on('click', '.tlypageguide_ignore', function () {
+                    that.close_welcome();
+                    that.track_event('PG.ignoreWelcome');
+                });
+            }
+            if ($welcome.find('.tlypageguide_dismiss').length) {
+                $welcome.on('click', '.tlypageguide_dismiss', function () {
+                    that.close_welcome();
+                    that.preferences.dismiss_welcome();
+                    that.track_event('PG.dismissWelcome');
+                });
             }
-        }, 250);
-    return this;
-};
+            $welcome.on('click', '.tlypageguide_start', function () {
+                that.open();
+                that.track_event('PG.startFromWelcome');
+            });
 
-/* to be executed on pg expand */
-tl.pg.PageGuide.prototype._on_expand = function () {
-    var that = this,
-        $d = document,
-        $w = window;
+            if (that.preferences.show_welcome) {
+                that.pop_welcome();
+            }
+        }
+    };
+
+    tl.pg.PageGuide.prototype.ready = function(callback) {
+        var that = this;
+        tl.pg.interval = window.setInterval(function() {
+                if (!$(that.preferences.loading_selector).is(':visible')) {
+                    callback();
+                    clearInterval(tl.pg.interval);
+                }
+            }, 250);
+        return this;
+    };
+
+    /* to be executed on pg expand */
+    tl.pg.PageGuide.prototype._on_expand = function () {
+        var that = this,
+            $d = document,
+            $w = window;
+
+        /* set up initial state */
+        this.position_tour();
+        this.cur_idx = 0;
+
+        // create a new stylesheet:
+        var ns = $d.createElement('style');
+        $d.getElementsByTagName('head')[0].appendChild(ns);
+
+        // keep Safari happy
+        if (!$w.createPopup) {
+            ns.appendChild($d.createTextNode(''));
+            ns.setAttribute("type", "text/css");
+        }
 
-    /* set up initial state */
-    this.position_tour();
-    this.cur_idx = 0;
+        // get a pointer to the stylesheet you just created
+        var sh = $d.styleSheets[$d.styleSheets.length - 1];
 
-    // create a new stylesheet:
-    var ns = $d.createElement('style');
-    $d.getElementsByTagName('head')[0].appendChild(ns);
+        // space for IE rule set
+        var ie = "";
 
-    // keep Safari happy
-    if (!$w.createPopup) {
-        ns.appendChild($d.createTextNode(''));
-        ns.setAttribute("type", "text/css");
-    }
+        /* add number tags and PG shading elements */
+        this.$items.each(function(i) {
+            var $p = $($(this).data('tourtarget') + ":visible:first");
+            $p.addClass("tlypageguide_shadow tlypageguide_shadow" + i);
 
-    // get a pointer to the stylesheet you just created
-    var sh = $d.styleSheets[$d.styleSheets.length - 1];
+            var node_text = '.tlypageguide_shadow' + i + ':after { height: ' +
+                                $p.outerHeight() + 'px; width: ' + $p.outerWidth(false) + 'px; }';
 
-    // space for IE rule set
-    var ie = "";
+            if (!$w.createPopup) {
+                // modern browsers
+                var k = $d.createTextNode(node_text, 0);
+                ns.appendChild(k);
+            } else {
+                // for IE
+                ie += node_text;
+            }
 
-    /* add number tags and PG shading elements */
-    this.$items.each(function(i) {
-        var $p = jQuery(jQuery(this).data('tourtarget') + ":visible:first");
-        $p.addClass("tlypageguide_shadow tlypageguide_shadow" + i);
+            $(this).find('ins').remove();
+            $(this).prepend('<ins>' + (i + 1) + '</ins>');
+            $(this).data('idx', i);
+        });
 
-        var node_text = '.tlypageguide_shadow' + i + ':after { height: ' +
-                            $p.outerHeight() + 'px; width: ' + $p.outerWidth(false) + 'px; }';
+        // is IE? slam styles in all at once:
+        if ($w.createPopup) {
+            sh.cssText = ie;
+        }
 
-        if (!$w.createPopup) {
-            // modern browsers
-            var k = $d.createTextNode(node_text, 0);
-            ns.appendChild(k);
+        /* decide to show first? */
+        if (this.preferences.auto_show_first && this.$items.length > 0) {
+            this.show_message(0);
+        }
+    };
+
+    tl.pg.PageGuide.prototype.open = function() {
+        if (this.preferences.show_welcome) {
+            this.preferences.dismiss_welcome();
+            this.close_welcome();
+        }
+        if (this.is_open) {
+            return;
         } else {
-            // for IE
-            ie += node_text;
+            this.is_open = true;
         }
 
-        jQuery(this).find('ins').remove();
-        jQuery(this).prepend('<ins>' + (i + 1) + '</ins>');
-        jQuery(this).data('idx', i);
-    });
-
-    // is IE? slam styles in all at once:
-    if ($w.createPopup) {
-        sh.cssText = ie;
-    }
-
-    /* decide to show first? */
-    if (this.preferences.auto_show_first && this.$items.length > 0) {
-        this.show_message(0);
-    }
-};
-
-tl.pg.PageGuide.prototype.open = function() {
-    if (this.preferences.show_welcome) {
-        this.preferences.dismiss_welcome();
-        this.close_welcome();
-    }
-    if (this.is_open) {
-        return;
-    } else {
-        this.is_open = true;
-    }
-
-    this.track_event('PG.open');
-
-    this._on_expand();
-    this.$items.toggleClass('expanded');
-    jQuery('body').addClass('tlypageguide-open');
-};
-
-tl.pg.PageGuide.prototype.close = function() {
-    if (!this.is_open) {
-        return;
-    } else {
-        this.is_open = false;
-    }
-
-    this.track_event('PG.close');
-
-    this.$items.toggleClass('expanded');
-    this.$message.animate({ height: "0" }, 500, function() {
-        jQuery(this).hide();
-    });
-    /* clear number tags and shading elements */
-    $('[class~="tlypageguide_shadow"]').removeClass(function(i, c) {
-        return c.match(/tlypageguide_shadow.*?\b/g).join(" ");
-    });
-    jQuery('ins').remove();
-    jQuery('body').removeClass('tlypageguide-open');
-};
-
-tl.pg.PageGuide.prototype.setup_handlers = function () {
-    var that = this;
-
-    /* interaction: open/close PG interface */
-    var interactor = (that.custom_open_button == null) ?
-                    jQuery('.tlypageguide_toggle', this.$base) :
-                    jQuery(that.custom_open_button);
-    interactor.off();
-    interactor.on('click', function() {
-        if (that.is_open) {
-            that.close();
-        } else if (that.preferences.show_welcome &&
-                  !that.preferences.check_welcome_dismissed() &&
-                  !jQuery('body').hasClass('tlyPageGuideWelcomeOpen')) {
-            that.pop_welcome();
+        this.track_event('PG.open');
+
+        this._on_expand();
+        this.$items.toggleClass('expanded');
+        $('body').addClass('tlypageguide-open');
+    };
+
+    tl.pg.PageGuide.prototype.close = function() {
+        if (!this.is_open) {
+            return;
         } else {
-            that.open();
+            this.is_open = false;
         }
-        return false;
-    });
 
-    jQuery('.tlypageguide_close', this.$message.add(jQuery('.tlypageguide_toggle')))
-        .on('click', function() {
-            that.close();
+        this.track_event('PG.close');
+
+        this.$items.toggleClass('expanded');
+        this.$message.animate({ height: "0" }, 500, function() {
+            $(this).hide();
+        });
+        /* clear number tags and shading elements */
+        $('[class~="tlypageguide_shadow"]').removeClass(function(i, c) {
+            return c.match(/tlypageguide_shadow.*?\b/g).join(" ");
+        });
+        $('ins').remove();
+        $('body').removeClass('tlypageguide-open');
+    };
+
+    tl.pg.PageGuide.prototype.setup_handlers = function () {
+        var that = this;
+
+        /* interaction: open/close PG interface */
+        var interactor = (that.custom_open_button == null) ?
+                        $('.tlypageguide_toggle', this.$base) :
+                        $(that.custom_open_button);
+        interactor.off();
+        interactor.on('click', function() {
+            if (that.is_open) {
+                that.close();
+            } else if (that.preferences.show_welcome &&
+                      !that.preferences.check_welcome_dismissed() &&
+                      !$('body').hasClass('tlyPageGuideWelcomeOpen')) {
+                that.pop_welcome();
+            } else {
+                that.open();
+            }
             return false;
-    });
-
-    /* interaction: item click */
-    this.$all_items.off();
-    this.$all_items.on('click', function() {
-        var new_index = jQuery(this).data('idx');
-        that.track_event('PG.specific_elt');
-        that.show_message(new_index);
-    });
-
-    /* interaction: fwd/back click */
-    this.$fwd.on('click', function() {
-        var new_index = (that.cur_idx + 1) % that.$items.length;
-
-        that.track_event('PG.fwd');
-        that.show_message(new_index);
-        return false;
-    });
-
-    this.$back.on('click', function() {
-        /*
-         * If -n < x < 0, then the result of x % n will be x, which is
-         * negative. To get a positive remainder, compute (x + n) % n.
-         */
-        var new_index = (that.cur_idx + that.$items.length - 1) % that.$items.length;
-
-        that.track_event('PG.back');
-        that.show_message(new_index, true);
-        return false;
-    });
-
-    /* register resize callback */
-    jQuery(window).resize(function() { that.position_tour(); });
-};
-
-tl.pg.PageGuide.prototype.show_message = function (new_index, left) {
-    var old_idx = this.cur_idx,
-        old_item = this.$items[old_idx],
-        new_item = this.$items[new_index];
-
-    this.cur_idx = new_index;
-    if(this.handle_doc_switch){
-        this.handle_doc_switch(jQuery(new_item).data('tourtarget'),
-                               jQuery(old_item).data('tourtarget'));
-    }
-
-    jQuery('div', this.$message).html(jQuery(new_item).children('div').html());
-    this.$items.removeClass("tlypageguide-active");
-    jQuery(new_item).addClass("tlypageguide-active");
-
-    if (!tl.pg.isScrolledIntoView(jQuery(new_item))) {
-        jQuery('html,body').animate({scrollTop: jQuery(new_item).offset().top - 50}, 500);
-    }
-    var defaultHeight = 100;
-    var oldHeight = parseFloat(this.$message.css("height"));
-    this.$message.css("height", "auto");
-    var height = parseFloat(this.$message.outerHeight());
-    this.$message.css("height", oldHeight + 'px');
-    if (height < defaultHeight) {
-        height = defaultHeight;
-    }
-    if (height > jQuery(window).height()/2) {
-        height = jQuery(window).height()/2;
-    }
-    height = height + "px";
-
-    this.$message.show().animate({'height': height}, 500);
-    this.roll_number(jQuery('span', this.$message), jQuery(new_item).children('ins').html(), left);
-};
-
-tl.pg.PageGuide.prototype.roll_number = function (num_wrapper, new_text, left) {
-    num_wrapper.animate({ 'text-indent': (left ? '' : '-') + '50px' }, 'fast', function() {
-        num_wrapper.html(new_text);
-        num_wrapper.css({ 'text-indent': (left ? '-' : '') + '50px' }, 'fast').animate({ 'text-indent': "0" }, 'fast');
-    });
-};
-
-tl.pg.PageGuide.prototype.position_tour = function () {
-    /* set PG element positions for visible tourtargets */
-    this.$items = this.$all_items.filter(function () {
-        return jQuery(jQuery(this).data('tourtarget')).is(':visible');
-    });
-
-    this.$items.each(function() {
-        var arrow   = jQuery(this),
-            target  = jQuery(arrow.data('tourtarget')).filter(':visible:first'),
-            position = arrow.data('position'),
-            setLeft = target.offset().left,
-            setTop  = target.offset().top;
-
-        if (position == "fixed") {
-            setTop  -= jQuery(window).scrollTop();
-        }
+        });
 
-        if (arrow.hasClass("tlypageguide_top")) {
-            setTop -= 60;
-        } else if (arrow.hasClass("tlypageguide_bottom")) {
-            setTop += target.outerHeight() + 15;
-        } else {
-            setTop += 5;
+        $('.tlypageguide_close', this.$message.add($('.tlypageguide_toggle')))
+            .on('click', function() {
+                that.close();
+                return false;
+        });
+
+        /* interaction: item click */
+        this.$all_items.off();
+        this.$all_items.on('click', function() {
+            var new_index = $(this).data('idx');
+            that.track_event('PG.specific_elt');
+            that.show_message(new_index);
+        });
+
+        /* interaction: fwd/back click */
+        this.$fwd.on('click', function() {
+            var new_index = (that.cur_idx + 1) % that.$items.length;
+
+            that.track_event('PG.fwd');
+            that.show_message(new_index);
+            return false;
+        });
+
+        this.$back.on('click', function() {
+            /*
+             * If -n < x < 0, then the result of x % n will be x, which is
+             * negative. To get a positive remainder, compute (x + n) % n.
+             */
+            var new_index = (that.cur_idx + that.$items.length - 1) % that.$items.length;
+
+            that.track_event('PG.back');
+            that.show_message(new_index, true);
+            return false;
+        });
+
+        /* register resize callback */
+        $(window).resize(function() { that.position_tour(); });
+    };
+
+    tl.pg.PageGuide.prototype.show_message = function (new_index, left) {
+        var old_idx = this.cur_idx,
+            old_item = this.$items[old_idx],
+            new_item = this.$items[new_index];
+
+        this.cur_idx = new_index;
+        if(this.handle_doc_switch){
+            this.handle_doc_switch($(new_item).data('tourtarget'),
+                                   $(old_item).data('tourtarget'));
         }
 
-        if (arrow.hasClass("tlypageguide_right")) {
-            setLeft += target.outerWidth(false) + 15;
-        } else if (arrow.hasClass("tlypageguide_left")) {
-            setLeft -= 65;
-        } else {
-            setLeft += 5;
+        $('div', this.$message).html($(new_item).children('div').html());
+        this.$items.removeClass("tlypageguide-active");
+        $(new_item).addClass("tlypageguide-active");
+
+        if (!tl.pg.isScrolledIntoView($(new_item))) {
+            $('html,body').animate({scrollTop: $(new_item).offset().top - 50}, 500);
+        }
+        var defaultHeight = 100;
+        var oldHeight = parseFloat(this.$message.css("height"));
+        this.$message.css("height", "auto");
+        var height = parseFloat(this.$message.outerHeight());
+        this.$message.css("height", oldHeight + 'px');
+        if (height < defaultHeight) {
+            height = defaultHeight;
         }
+        if (height > $(window).height()/2) {
+            height = $(window).height()/2;
+        }
+        height = height + "px";
+
+        this.$message.show().animate({'height': height}, 500);
+        this.roll_number($('span', this.$message), $(new_item).children('ins').html(), left);
+    };
+
+    tl.pg.PageGuide.prototype.roll_number = function (num_wrapper, new_text, left) {
+        num_wrapper.animate({ 'text-indent': (left ? '' : '-') + '50px' }, 'fast', function() {
+            num_wrapper.html(new_text);
+            num_wrapper.css({ 'text-indent': (left ? '-' : '') + '50px' }, 'fast').animate({ 'text-indent': "0" }, 'fast');
+        });
+    };
+
+    tl.pg.PageGuide.prototype.position_tour = function () {
+        /* set PG element positions for visible tourtargets */
+        this.$items = this.$all_items.filter(function () {
+            return $($(this).data('tourtarget')).is(':visible');
+        });
 
-        arrow.css({ "left": setLeft + "px", "top": setTop + "px", "position": position});
-    });
-};
+        this.$items.each(function() {
+            var arrow   = $(this),
+                target  = $(arrow.data('tourtarget')).filter(':visible:first'),
+                position = arrow.data('position'),
+                setLeft = target.offset().left,
+                setTop  = target.offset().top;
+
+            if (position == "fixed") {
+                setTop  -= $(window).scrollTop();
+            }
+
+            if (arrow.hasClass("tlypageguide_top")) {
+                setTop -= 60;
+            } else if (arrow.hasClass("tlypageguide_bottom")) {
+                setTop += target.outerHeight() + 15;
+            } else {
+                setTop += 5;
+            }
+
+            if (arrow.hasClass("tlypageguide_right")) {
+                setLeft += target.outerWidth(false) + 15;
+            } else if (arrow.hasClass("tlypageguide_left")) {
+                setLeft -= 65;
+            } else {
+                setLeft += 5;
+            }
+
+            arrow.css({ "left": setLeft + "px", "top": setTop + "px", "position": position});
+        });
+    };
 
-tl.pg.PageGuide.prototype.pop_welcome = function () {
-    jQuery('body').addClass('tlyPageGuideWelcomeOpen');
-    this.track_event('PG.welcomeShown');
-};
+    tl.pg.PageGuide.prototype.pop_welcome = function () {
+        $('body').addClass('tlyPageGuideWelcomeOpen');
+        this.track_event('PG.welcomeShown');
+    };
 
-tl.pg.PageGuide.prototype.close_welcome = function () {
-    jQuery('body').removeClass('tlyPageGuideWelcomeOpen');
-};
+    tl.pg.PageGuide.prototype.close_welcome = function () {
+        $('body').removeClass('tlyPageGuideWelcomeOpen');
+    };
+}(jQuery));

use $ for jQuery; wrap in closure to protect