﻿// G7th_typeText
// Joel Evans 07/07/2011
// Types out the text inside an HTML element

(function( $ ){

    $.fn.g7th_typeText = function(opts) {
 
        // Defaults
        var o = $.extend( {
            selfClosingTags: ['br', 'hr', 'img'],
            beforeDelay: 1000,
            afterDelay: 5000,
            charDelay: 30, /* millisecond delay between characters */
            cursorSpeed: 10, /* # characters to type before cursor switches on / off */
            after: null
        },opts);
 
        this.each(function(){
        
            // Get reference to current base element to avoid
            // scoping issues
            var el = $(this);
            
            // Remove inner HTML and store in a string
            var sHTML = el.html();
            el.html('');
            
            // Show element if it's been hidden
            el.show()
            
            // HTML currently visible to user
            var sVisHTML = '';
            
            // Wait beforeDelay, then start typing
            var to = setTimeout(typeChar, o.beforeDelay);
            
            // Cursor state
            var bCursorOn = true;
            var iCursorCounter = 0;
            
            function getCursorHTML() {
                if(bCursorOn) {
                    return '<span class="cursor">&nbsp;</span>';
                } else {
                    return '';
                }
            }
            
            function advanceCursor() {
                iCursorCounter ++;
                if (iCursorCounter == o.cursorSpeed) {
                    bCursorOn = !bCursorOn;
                    iCursorCounter = 0;
                }
            }
            
            // Position of current character
            var c = 0;
            
            function getNextChar() {
                var s = sHTML.substr(c,1);
                c++;
                return s;
            }
            
            // Stack of tags to close
            var aTagsToClose = new Array();
            
            function closeOpenTags() {
                var s = '';
                for (i=aTagsToClose.length - 1;i>0;i--) {
                    s += '</' + aTagsToClose[i] + '>';
                }
                return s;
            }
            
            function typingComplete(stopNow) {
                
                // Ensure HTML is restored to its original state,
                // and remove cursor
                el.html(sHTML)
                
                // Was stopNow passed to indicate all events must stop
                // now?
                if (stopNow) {
                    
                    // Yes - clear timeouts
                    clearTimeout(to);
                    
                } else {
                
                    // Was a function pointer passed to the 'after' value?
                    if (typeof(o.after == 'function')) {
                        
                        // Yes - call function
                        to = setTimeout(o.after, o.afterDelay);
                    }
                }
            }
            
            // Listen for a 'stopTyping' event - if it's triggered,
            // reset slide to initial state
            el.bind('stopTyping', function(e) {
                typingComplete(true);
            });
            
            function typeChar() {
                
                // Check if we've reached the end of the text
                if (c >= sHTML.length) {
                    return typingComplete(false);
                }
                
                // If we add a tag that's made no visible change for
                // the user (e.g. an opening <em> tag), we can un-set
                // the following flag and type the next character
                // without a delay.
                var bWaitBeforeNextChar = true;
                
                // Character at current position
                var ch = getNextChar()
                
                // Is this the start of an entity?
                if (ch == '&') {
                    
                    // Yes - get the rest of the entity's HTML
                    var sEntityHTML = ch;
                    while ( !( ch == ';' || ch == ' ') ) {
                        ch = getNextChar();
                        sEntityHTML += ch;
                    } 
                    
                    // Add entire entity HTML to visible HTML in one chunk
                    sVisHTML += sEntityHTML;
                    
                } else {
                
                    // Is this the start of a tag?
                    if (ch == '<') {
                        
                        // Yes - get HTML for entire tag
                        var sTagHTML = ch;
                        while (ch && ch != '>') {
                            ch = getNextChar(); 
                            sTagHTML += ch;
                        }
                        
                        // Was this a closing tag?
                        if (sTagHTML.match(/^<\//)) {
                        
                            // Yes - remove a tag from the tags-to-close stack
                            aTagsToClose.pop();
                            
                            // Made no visible difference to user - don't wait before
                            // typing next character
                            bWaitBeforeNextChar = false;
                            
                        } else {
                        
                            // Not a closing tag - get the tag name
                            var sTagName = sTagHTML.replace(/<([\w]+)\b.*$/, '$1');
                            
                            // Was this a self-closing tag?
                            var bSelfClosing = false;
                            
                            // Check if second-last character is a slash
                            if (sTagHTML.substr(sTagHTML.length - 2,1) == '/') {
                                bSelfClosing = true;
                            } else {
                                // Double check tag against list of self-closing tags,
                                // in case e.g. someone put a <br> instead of <br />
                                for (i=0; i< o.selfClosingTags.length; i++) {
                                    if (sTagName == o.selfClosingTags[i]) {
                                        bSelfClosing = true;
                                        break;
                                    }
                                }
                            }
                            
                            if (!bSelfClosing) {
                            
                                // Not a self-closing tag - add it to the tags-to-close stack
                                aTagsToClose.push(sTagName);
                            
                                // Made no visible difference to user - don't wait before
                                // typing next character
                                bWaitBeforeNextChar = false;
                            }
                        }
                        
                        // Add tag HTML to visible HTML
                        sVisHTML += sTagHTML;
                        
                    } else {
                        
                        // Not in a tag or an entity - just add
                        // current char to visible HTML
                        sVisHTML += ch;
                    }
                }
                
                // Update HTML inside base element 
                el.html(sVisHTML + getCursorHTML() + closeOpenTags());
                
                if (bWaitBeforeNextChar) {
                    to = setTimeout(typeChar, o.charDelay);
                    advanceCursor();  
                } else {
                    typeChar();
                }
            }
        });
 
    };

})( jQuery );

/*
 * Special event for image load events
 * Needed because some browsers does not trigger the event on cached images.

 * MIT License
 * Paul Irish     | @paul_irish | www.paulirish.com
 * Andree Hansson | @peolanha   | www.andreehansson.se
 * 2010.
 *
 * Usage:
 * $(images).bind('load', function (e) {
 *   // Do stuff on load
 * });
 * 
 * Note that you can bind the 'error' event on data uri images, this will trigger when
 * data uri images isn't supported.
 * 
 * Tested in:
 * FF 3+
 * IE 6-8
 * Chromium 5-6
 * Opera 9-10
 */
(function ($) {
	$.event.special.load = {
		add: function (hollaback) {
			if ( this.nodeType === 1 && this.tagName.toLowerCase() === 'img' && this.src !== '' ) {
				// Image is already complete, fire the hollaback (fixes browser issues were cached
				// images isn't triggering the load event)
				if ( this.complete || this.readyState === 4 ) {
					hollaback.handler.apply(this);
				}

				// Check if data URI images is supported, fire 'error' event if not
				else if ( this.readyState === 'uninitialized' && this.src.indexOf('data:') === 0 ) {
					$(this).trigger('error');
				}
				
				else {
					$(this).bind('load', hollaback.handler);
				}
			}
		}
	};
}(jQuery));

(function ($) {
    $.fn.preload = function() {
        this.each(function(){
            $(new Image()).src = this;
        });
    }
}(jQuery));

$(document).ready(function() {

    var slideCont = $('#slides');
    var slExpr = '.slide';
    var indExpr = '.indicator';
    var buttonHtml = '<a href="#" class="arrow" id="prev">Next Slide</a>\n'
     + '<a href="#" class="arrow" id="next">Previous Slide</a>';
    
    // 'Off switch' for screenreader users - otherwise the text typing
    // & cycle effects are likely to play havoc with their software
    var offSwHtml = '<a class="offSw" href="#">Turn off the slide show (recommended for screen-reader users)</a>'
     
    // Hide all text on slides to begin with
    $('p', slideCont).hide();
    
    // When slideshow starts with timeout = 0, onBefore is called
    // for first slide, then onAfter immediately afterwards. To prevent
    // onAfter cancelling the typing and stopping the slideshow before
    // it's even started, we need a method to stop 'stopTyping' event
    // being fired the first time this function is triggered
    var bFirstOnAfterCall = true;
    
    var bFirstSlide = true;
    var n = $(slExpr).length;
    if (n > 1) {
        
        // Add next and prev buttons
        var c = $('#slides');
        c.html(buttonHtml + c.html())
        
        // Add off switch for screen-reader users
        $('.outerWrap').prepend(offSwHtml);
        
        // Bind turn off slideshow event to this link
        $('a.offSw').bind('click', turnOffSlideShow);
        
        // Set width of current slide indicator
        var w = (15 * n) - 9;
        $(indExpr).width(w);
        
        // Start cycle
        slideCont.cycle({
            pause: 1, 
            prev: '#prev', 
            next: '#next', 
            timeout: 0,
            speed: 1000,
            slideExpr: slExpr,
            fx: 'scrollHorz',
            before: onBefore,
            after: onAfter
        });
    }
    
    function onBefore(curr, next, opts) {
        
        // Function to switch to next slide after typing complete
        var fNext = function() {slideCont.cycle('next')};
        var o = {after: fNext};
        
        var imgSrcs = function(slide) {
            var a = new Array();
            $('img', slide).each(function() {
                a.push(this.src);
            });
            return a;
        }
        var initSlide = function(slide, opts) {
            
            // Ensure all images on slide have been loaded
            $( imgSrcs(slide) ).preload();
            
            var fType = function () {
                $('p', slide).g7th_typeText(opts);
            };
            
            if (bFirstSlide) {
                bFirstSlide = false;
                // First slide - fade slides in, then start typing
                slideCont.css({
                    opacity: 0.01,
                    visibility: 'visible'
                }).fadeTo(3000, 1, fType);
            } else {
                // Start typing immediately
                fType();
            }
            
        }
        
        if (bFirstSlide) {
            
            // Set a shorter pre-delay
            o = $.extend({beforeDelay: 0}, o);
            
            initSlide(curr, o);
        
        } else {
            
            initSlide(next, o);
        }
        
    }
    
    function onAfter(curr, next, opts) {
        var index = opts.currSlide;
        var x = -300 + (15*index);
        $('.indicator').css('background-position', x + 'px 0px');
        
        if (bFirstOnAfterCall) {
            bFirstOnAfterCall = false;
        } else {
            // Trigger stopTyping event on current slide
            $('p', curr).trigger('stopTyping');
        }
    }
    
    function turnOffSlideShow(e) {
    
        slideCont.cycle('destroy').stop(true,true).css({
            visibility:'visible',
            overflow:'visible',
            height:'inherit'
        });
        
        // Stop typing effect on all slides 
        $('p', slideCont).trigger('stopTyping');
        
        // remove inline styles added to slide so they're all
        // visible stacked on top of each other
        $('.slide').removeAttr('style');
        
        // hide controls for slideshow (not needed any more)
        $('a.arrow, a.offSw, .indicator').hide();
        
        // show all text on slides if it hasn;t been typed out yet
        $('.slide p').show();
        
        // prevent browser following the '#' link
        e.preventDefault();
        e.stopPropagation();
    }
});

// Hide slides for js-enabled browsers - this is
// slightly brutal coding but it prevents flash-
// of-unstyled-content problems in Internet Explorer,
// whilst maintaining usability for non-js users
document.write('<style type="text/css">#slides{visibility:hidden;overflow:hidden;height:468px}</style>');
