%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /home/riacommer/domains/gasworld.com.my/public_html/admin/vendor/MetroJS/src/js/
Upload File :
Create Path :
Current File : /home/riacommer/domains/gasworld.com.my/public_html/admin/vendor/MetroJS/src/js/liveTile.js

var MAX_LOOP_COUNT = 99000;
// .liveTile
$.fn.liveTile = function (method) {
    if (pubMethods[method]) {
        var args = [];
        for (var i = 1; i <= arguments.length; i++) {
            args[i - 1] = arguments[i];
        }
        return pubMethods[method].apply(this, args);
    } else if (typeof method === 'object' || !method) {
        return pubMethods.init.apply(this, arguments);
    } else {
        $.error('Method ' + method + ' does not exist on jQuery.liveTile');
        return null;
    }
};


$.fn.liveTile.contentModules = {
    modules: [],
    /* the default module layout
    [
                defaultModules.imageSwap,
        defaultModules.htmlSwap
    ],*/
    addContentModule: function (moduleName, module) {
        if (!(this.modules instanceof Array))
            this.modules = [];
        this.modules.push(module);
    },
    hasContentModule: function (moduleName) {
        if (typeof (moduleName) === "undefined" || !(this.modules instanceof Array))
                return -1;
        for (var i = 0; i < this.modules.length; i++) {
                if (typeof (this.modules[i].moduleName) != "undefined" && this.modules[i].moduleName == moduleName)
                        return i;
        }
            return -1;
    }
};

// default option values for .liveTile
$.fn.liveTile.defaults = {
    mode: 'slide',                          // 'fade', 'slide', 'flip', 'flip-list', carousel
    speed: 500,                             // how fast should animations be performed, in milliseconds
    initDelay: -1,                          // how long to wait before the initial animation
    delay: 5000,                            // how long to wait between animations 
    stops: "100%",                          // how much of the back tile should 'slide' reveal before starting a delay
    stack: false,                           // should tiles in slide mode appear stacked (e.g Me tile) 
    direction: 'vertical',                  // which direction should animations be performed(horizontal | vertical)
    animationDirection: 'forward',          // the direction that carousel mode uses to determine which way to slide in tiles
    tileSelector: '>div,>li,>p,>img,>a',    // the selector used by carousel mode and flip-list to choose tile containers
    tileFaceSelector: '>div,>li,>p,>img,>a',// the selector used to choose the front and back containers
    ignoreDataAttributes: false,            // should data attributes be ignored
    click: null,                            // function ($tile, tdata) { return true; }
    link: '',                               // a url to go to when clicked
    newWindow: false,                       // should the link be opened in a new window
    bounce: false,                          // should the tile shrink when tapped
    bounceDirections: 'all',                // which direction the tile will tile 'all', 'edges, 'corners'
    bounceFollowsMove: true,                // should a tile in bounce state tilt in the direction of the mouse as it moves
    pauseOnHover: false,                    // should tile animations be paused on hover in and restarted on hover out
    pauseOnHoverEvent: 'both',              // pause is called on mouseover, mouseout, or both
    playOnHover: false,                     // should "play" be called on hover
    playOnHoverEvent: 'both',               // play is called on mouseover, mouseout, or both
    onHoverDelay: 0,                        // the amount of time to wait before the onHover event is fired
    repeatCount: -1,                        // number of times to repeat the animation        
    appendBack: true,                       // appends the .last tile if one doesnt exist (slide and flip only)        
    alwaysTrigger: false,                   // should every item in a flip list trigger every time a delay passes 
    flipListOnHover: false,                 // should items in flip-list flip and stop when hovered
    flipListOnHoverEvent: 'mouseout',       // which event should be used to trigger the flip-list faces
    noHAflipOpacity: '1',                   // the opacity level set for the backside of the flip animation on unaccelerated browsers
    haTransFunc: 'ease',                    // the tranisiton-timing function to use in hardware accelerated mode
    noHaTransFunc: 'linear',                // the tranisiton-timing function to use in non hardware accelerated mode
    currentIndex: 0,                        // what is the current stop index for slide mode or slide index for carousel mode
    startNow: true,                         // should the tile immediately start or wait util play or restart has been called
    useModernizr: (typeof (window.Modernizr) !== "undefined"), // checks to see if modernizer is already in use
    useHardwareAccel: true,                 // should css animations, transitions and transforms be used when available
    useTranslate: true,
    faces: {
        $front: null,                        // the jQuery element to use as the front face of the tile; this will bypass tileCssSelector
        $back: null                          // the jQuery element to use as the back face of the tile; this will bypass tileCssSelector
    },
    animationStarting: function (tileData, $front, $back) {
        // returning false will cancel the animation
    },
    animationComplete: function (tileData, $front, $back) {
    },
    triggerDelay: function (idx) {          // used by flip-list to decide how random the tile flipping should be
        return Math.random() * 3000;
    },
    swap: '',                               // which swap modules are active for this tile (image, html)
    swapFront: '-',                         // override the available swap modules for the front face
    swapBack: '-',                          // override the available swap modules for the back face
    contentModules: []
};
// public methods that can be called via .liveTile(method name)
var pubMethods = {
    init: function (options) {
        // Setup the public options for the livetile
        var settings = $.extend({}, $.fn.liveTile.defaults, options);
        // checks for browser feature support to enable hardware acceleration                        
        metrojs.checkCapabilities(settings);
        helperMethods.getBrowserPrefix();
        // setup the default content modules
        if ($.fn.liveTile.contentModules.hasContentModule("image") == -1)
            $.fn.liveTile.contentModules.addContentModule("image", defaultModules.imageSwap);
        if ($.fn.liveTile.contentModules.hasContentModule("html") == -1)
            $.fn.liveTile.contentModules.addContentModule("html", defaultModules.htmlSwap);
        // this is where the magic happens
        return $(this).each(function (tileIndex, ele) {
            var $this = $(ele),
            data = privMethods.initTileData($this, settings);
            // append back tiles and add appropriate classes to prepare tiles
            data.faces = privMethods.prepTile($this, data);
            // action methods
            data.fade = function (count) { privMethods.fade($this, count); };
            data.slide = function (count) { privMethods.slide($this, count); };
            data.carousel = function (count) { privMethods.carousel($this, count); };
            data.flip = function (count) { privMethods.flip($this, count); };
            data.flipList = function (count) { privMethods.flipList($this, count); };
            var actions = {
                fade: data.fade,
                slide: data.slide,
                carousel: data.carousel,
                flip: data.flip,
                'flip-list': data.flipList
            };
            data.doAction = function (count) {
                // get the action for the current mode
                var action = actions[data.mode];
                if (typeof (action) === "function") {
                    action(count);
                    data.hasRun = true;
                }
                // prevent pauseOnHover from resuming a tile that has finished
                if (count == data.timer.repeatCount)
                    data.runEvents = false;
            };

            // create a new tile timer
            data.timer = new $.fn.metrojs.TileTimer(data.delay, data.doAction, data.repeatCount);
            // apply the data
            $this.data("LiveTile", data);
            // handle events
            // only bind pause / play on hover if we are not using a fliplist or flipListOnHover isn't set set
            if (data.mode !== "flip-list" || data.flipListOnHover == false) {
                if (data.pauseOnHover) {
                    privMethods.bindPauseOnHover($this);
                } else if (data.playOnHover) {
                    privMethods.bindPlayOnHover($this, data);
                }
            }
            // add a click / link to the tile
            if (data.link.length > 0 || typeof (data.click) === "function") {
                $this.css({ cursor: 'pointer' }).bind("click.liveTile", function (e) {
                    var proceed = true;
                    if (typeof (data.click) === "function") {
                        proceed = data.click($this, data) || false;
                    }
                    if (proceed && data.link.length > 0) {
                        e.preventDefault();
                        if (data.newWindow)
                            window.open(data.link);
                        else
                            window.location = data.link;
                    }
                });
            }
            // add bounce if set            
            privMethods.bindBounce($this, data);
            // start timer
            if (data.startNow && data.mode != "none") {
                data.runEvents = true;
                data.timer.start(data.initDelay);
            }
        });
    },
    // goto is a future reserved word
    'goto': function (options) {
        var opts, t = typeof (options);
        if (t === "undefined") {
            opts = {
                index: -99, //  same as next
                delay: 0,
                autoAniDirection: false
            };
        }
        if (t === "number" || !isNaN(options)) {
            opts = {
                index: parseInt(options, 10),
                delay: 0
            };
        } else if (t === "string") {
            if (options == "next") {
                opts = {
                    index: -99,
                    delay: 0
                };
            } else if (options.indexOf("prev") === 0) {
                opts = {
                    index: -100,
                    delay: 0
                };
            } else {
                $.error(options + " is not a recognized action for .liveTile(\"goto\")");
                return $(this);
            }
        } else if (t === "object") {
            if (typeof (options.delay) === "undefined") {
                options.delay = 0;
            }
            var ti = typeof (options.index);
            if (ti === "undefined") {
                options.index = 0;
            } else if (ti === "string") {
                if (options.index === "next")
                    options.index = -99;
                else if (options.index.indexOf("prev") === 0)
                    options.index = -100;
            }
            opts = options;
        }
        return $(this).each(function (tileIndex, ele) {
            var $tile = $(ele),
                data = $tile.data("LiveTile"),
                aniData = $tile.data("metrojs.tile"),
                goTo = opts.index;
            if (aniData.animating === true)
                return $(this);
            if (data.mode === "carousel") {
                // get the index based off of the active carousel slide
                var $cur = data.faces.$listTiles.filter(".active");
                var curIdx = data.faces.$listTiles.index($cur);
                // carousel will look for these values as triggers
                if (goTo === -100) { // prev
                    // autoAniDirection determines if a forward or backward animation should be used based on the goTo index
                    if (typeof (opts.autoAniDirection) === "undefined" || opts.autoAniDirection == true)
                        data.tempValues.animationDirection = typeof (opts.animationDirection) === "undefined" ? "backward" : opts.animationDirection;
                    goTo = curIdx === 0 ? data.faces.$listTiles.length - 1 : curIdx - 1;
                } else if (goTo === -99) { // next
                    if (typeof (opts.autoAniDirection) === "undefined" || opts.autoAniDirection == true)
                        data.tempValues.animationDirection = typeof (opts.animationDirection) === "undefined" ? "forward" : opts.animationDirection;
                    goTo = curIdx + 1;
                }
                if (curIdx == goTo) {
                    return;
                }
                if (typeof (opts.direction) !== "undefined") {
                    data.tempValues.direction = opts.direction;
                }
                if (typeof (opts.animationDirection) !== "undefined") {
                    data.tempValues.animationDirection = opts.animationDirection;
                }
                // the index is offset by 1 and incremented on animate
                if (goTo == 0)
                    data.currentIndex = data.faces.$listTiles.length;
                else
                    data.currentIndex = goTo - 1;
            } else // slide mode will use the index directly
                data.currentIndex = goTo;
            // start the timer
            data.runEvents = true;
            data.timer.start(opts.delay >= 0 ? opts.delay : data.delay);
        });
    },
    play: function (options) {
        var opts, t = typeof (options);
        if (t === "undefined") {
            opts = {
                delay: -1
            };
        } else if (t === "number") {
            opts = {
                delay: options
            };
        } else if (t === "object") {
            if (typeof (options.delay) === "undefined") {
                options.delay = -1;
            }
            opts = options;
        }
        return $(this).each(function (tileIndex, ele) {
            var $tile = $(ele),
                data = $tile.data("LiveTile");
            data.runEvents = true;
            if (opts.delay < 0 && !data.hasRun)
                opts.delay = data.initDelay;
            data.timer.start(opts.delay >= 0 ? opts.delay : data.delay);
        });
    },
    animate: function () { // this is really only useful for certain edge cases in slide mode, use 'play' to toggle animations
        return $(this).each(function (tileIndex, ele) {
            var $tile = $(ele),
                data = $tile.data("LiveTile");
            data.doAction();
        });
    },
    stop: function () {
        return $(this).each(function (tileIndex, ele) {
            var $tile = $(ele),
                data = $tile.data("LiveTile");
            data.hasRun = false;
            data.runEvents = false;
            data.timer.stop();
            window.clearTimeout(data.eventTimeout);
            window.clearTimeout(data.flCompleteTimeout);
            window.clearTimeout(data.completeTimeout);
            if (data.mode === "flip-list") {
                data.faces.$listTiles.each(function (idx, li) {
                    var ldata = $(li).data("metrojs.tile");
                    window.clearTimeout(ldata.eventTimeout);
                    window.clearTimeout(ldata.flCompleteTimeout);
                    window.clearTimeout(ldata.completeTimeout);
                });
            }
        });
    },
    pause: function () {
        return $(this).each(function (tileIndex, ele) {
            var $tile = $(ele),
                data = $tile.data("LiveTile");
            data.timer.pause();
            data.runEvents = false;
            window.clearTimeout(data.eventTimeout);
            window.clearTimeout(data.flCompleteTimeout);
            window.clearTimeout(data.completeTimeout);
            if (data.mode === "flip-list") {
                data.faces.$listTiles.each(function (idx, li) {
                    var ldata = $(li).data("metrojs.tile");
                    window.clearTimeout(ldata.eventTimeout);
                    window.clearTimeout(ldata.flCompleteTimeout);
                    window.clearTimeout(ldata.completeTimeout);
                });
            }
        });
    },
    restart: function (options) {
        var opts, t = typeof (options);
        if (t === "undefined") {
            opts = {
                delay: -1
            };
        } else if (t === "number") {
            opts = {
                delay: options
            };
        } else if (t === "object") {
            if (typeof (options.delay) === "undefined") {
                options.delay = -1;
            }
            opts = options;
        }
        return $(this).each(function (tileIndex, ele) {
            var $tile = $(ele),
                data = $tile.data("LiveTile");
            if (opts.delay < 0 && !data.hasRun)
                opts.delay = data.initDelay;
            data.hasRun = false;
            data.runEvents = true;
            data.timer.restart(opts.delay >= 0 ? opts.delay : data.delay);
        });
    },
    rebind: function (options) {
        return $(this).each(function (tileIndex, ele) {
            if (typeof (options) !== "undefined") {
                if (typeof (options.timer) !== "undefined" && options.timer != null) {
                    options.timer.stop();
                }
                options.hasRun = false;
                pubMethods["init"].apply(ele, options);
            } else {
                pubMethods["init"].apply(ele, {});
            }
        });
    },
    destroy: function (options) {
        var t = typeof (options), opts;
        if (t === "undefined") {
            opts = {
                removeCss: false
            };
        } else if (t === "boolean") {
            opts = {
                removeCss: options
            };
        } else if (t === "object") {
            if (typeof (options.removeCss) === "undefined") {
                options.removeCss = false;
            }
            opts = options;
        }
        return $(this).each(function (tileIndex, ele) {
            var $tile = $(ele);
            var data = $tile.data("LiveTile");
            if (typeof (data) === "undefined")
                return;
            $tile.unbind(".liveTile");
            var resetCss = helperMethods.appendStyleProperties({ margin: '', cursor: '' }, ['transform', 'transition'], ['', '']);
            data.timer.stop();
            window.clearTimeout(data.eventTimeout);
            window.clearTimeout(data.flCompleteTimeout);
            window.clearTimeout(data.completeTimeout);
            if (data.faces.$listTiles != null) {
                data.faces.$listTiles.each(function (idx, li) {
                    var $li = $(li);
                    if (data.mode === "flip-list") {
                        var ldata = $li.data("metrojs.tile");
                        window.clearTimeout(ldata.eventTimeout);
                        window.clearTimeout(ldata.flCompleteTimeout);
                        window.clearTimeout(ldata.completeTimeout);
                    } else if (data.mode === "carousel") {
                        var sdata = data.listData[idx];
                        if (sdata.bounce) {
                            privMethods.unbindMsBounce($li, sdata);
                        }
                    }
                    if (opts.removeCss) {
                        $li.removeClass("ha");
                        $li.find(data.tileFaceSelector)
                            .unbind(".liveTile")
                            .removeClass("bounce flip-front flip-back ha slide slide-front slide-back")
                            .css(resetCss);
                    } else {
                        $li.find(data.tileFaceSelector).unbind(".liveTile");
                    }
                    $li.removeData("metrojs.tile");
                }).unbind(".liveTile");
            }
            if (data.faces.$front != null && opts.removeCss) {
                data.faces.$front.removeClass("flip-front flip-back ha slide slide-front slide-back")
                    .css(resetCss);
            }
            if (data.faces.$back != null && opts.removeCss) {
                data.faces.$back.removeClass("flip-front flip-back ha slide slide-front slide-back")
                    .css(resetCss);
            }
            // remove the bounce and hover methods
            // todo: combine all mouse/touch events (down, move, up)
            if (data.bounce) {
                privMethods.unbindMsBounce($tile, data);
            }
            if (data.playOnHover) {
                privMethods.unbindMsPlayOnHover($tile, data);
            }
            if (data.pauseOnhover) {
                privMethods.unbindMsPauseOnHover($tile, data);
            }
            $tile.removeClass("ha");
            $tile.removeData("LiveTile");
            $tile.removeData("metrojs.tile");
            data = null;
        });
    }
};

// private methods that are called by .liveTile
var privMethods = {
    //getDataOrDefault for older versions of jQuery that dont look for 'data-' properties
    dataAtr: function ($ele, name, def) {
        return typeof ($ele.attr('data-' + name)) !== "undefined" ? $ele.attr('data-' + name) : def;
    },
    dataMethod: function ($ele, name, def) {
        return typeof ($ele.data(name)) !== "undefined" ? $ele.data(name) : def;
    },
    getDataOrDefault: null,
    initTileData: function ($tile, stgs) {
        var useData = stgs.ignoreDataAttributes == false,
            tdata = null;
        if (this.getDataOrDefault == null)
            this.getDataOrDefault = metrojs.capabilities.isOldJQuery ? this.dataAtr : this.dataMethod;
        if (useData) {
            tdata = { //an object to store settings for later access                
                speed: this.getDataOrDefault($tile, "speed", stgs.speed),
                delay: this.getDataOrDefault($tile, "delay", stgs.delay),
                stops: this.getDataOrDefault($tile, "stops", stgs.stops),
                stack: this.getDataOrDefault($tile, "stack", stgs.stack),
                mode: this.getDataOrDefault($tile, "mode", stgs.mode),
                direction: this.getDataOrDefault($tile, "direction", stgs.direction),
                useHardwareAccel: this.getDataOrDefault($tile, "ha", stgs.useHardwareAccel),
                repeatCount: this.getDataOrDefault($tile, "repeat", stgs.repeatCount),
                swap: this.getDataOrDefault($tile, "swap", stgs.swap),
                appendBack: this.getDataOrDefault($tile, "appendback", stgs.appendBack),
                currentIndex: this.getDataOrDefault($tile, "start-index", stgs.currentIndex),
                animationDirection: this.getDataOrDefault($tile, "ani-direction", stgs.animationDirection),
                startNow: this.getDataOrDefault($tile, "start-now", stgs.startNow),
                tileSelector: this.getDataOrDefault($tile, "tile-selector", stgs.tileSelector),
                tileFaceSelector: this.getDataOrDefault($tile, "face-selector", stgs.tileFaceSelector),
                bounce: this.getDataOrDefault($tile, "bounce", stgs.bounce),
                bounceDirections: this.getDataOrDefault($tile, "bounce-dir", stgs.bounceDirections),
                bounceFollowsMove: this.getDataOrDefault($tile, "bounce-follows", stgs.bounceFollowsMove),
                click: this.getDataOrDefault($tile, "click", stgs.click),
                link: this.getDataOrDefault($tile, "link", stgs.link),
                newWindow: this.getDataOrDefault($tile, "new-window", stgs.newWindow),
                alwaysTrigger: this.getDataOrDefault($tile, "always-trigger", stgs.alwaysTrigger),
                flipListOnHover: this.getDataOrDefault($tile, "flip-onhover", stgs.flipListOnHover),
                pauseOnHover: this.getDataOrDefault($tile, "pause-onhover", stgs.pauseOnHover),
                playOnHover: this.getDataOrDefault($tile, "play-onhover", stgs.playOnHover),
                onHoverDelay: this.getDataOrDefault($tile, "hover-delay", stgs.onHoverDelay),
                noHAflipOpacity: this.getDataOrDefault($tile, "flip-opacity", stgs.noHAflipOpacity),
                useTranslate: this.getDataOrDefault($tile, "use-translate", stgs.useTranslate),
                runEvents: false,
                isReversed: false,
                loopCount: 0,
                contentModules: [],
                listData: [],
                height: $tile.height(),
                width: $tile.width(),
                tempValues: {}
            };
        } else {
            tdata = $.extend(true,{
                runEvents: false,
                isReversed: false,
                loopCount: 0,
                contentModules: [],
                listData: [],
                height: $tile.height(),
                width: $tile.width(),
                tempValues: {}
            }, stgs);
        }
        tdata.useTranslate = tdata.useTranslate && tdata.useHardwareAccel && metrojs.capabilities.canTransform && metrojs.capabilities.canTransition;
        // set the margin to half of the height or width based on the direction
        tdata.margin = (tdata.direction === "vertical") ? tdata.height / 2 : tdata.width / 2;
        // convert stops if needed
        tdata.stops = (typeof (stgs.stops) === "object" && (stgs.stops instanceof Array)) ? stgs.stops : ("" + tdata.stops).split(",");
        // add a return stop
        if (tdata.stops.length === 1)
            tdata.stops.push("0px");
        // add content modules, start with global swaps            
        var swaps = tdata.swap.replace(' ', '').split(",");
        // get the front and back swap data
        var sf = useData ? this.getDataOrDefault($tile, "swap-front", stgs.swapFront) : stgs.swapFront;
        var sb = useData ? this.getDataOrDefault($tile, "swap-back", stgs.swapBack) : stgs.swapBack;
        // set the data to the global value if its still the default
            tdata.swapFront = sf === '-' ? swaps : sf.replace(' ', '').split(",");
        tdata.swapBack = sb === '-' ? swaps : sb.replace(' ', '').split(",");
        // make sure the swaps includes all front and back swaps
        var i;
        for (i = 0; i < tdata.swapFront.length; i++) {
            if (tdata.swapFront[i].length > 0 && $.inArray(tdata.swapFront[i], swaps) === -1)
                swaps.push(tdata.swapFront[i]);
        }
        for (i = 0; i < tdata.swapBack.length; i++) {
            if (tdata.swapBack[i].length > 0 && $.inArray(tdata.swapBack[i], swaps) === -1)
                swaps.push(tdata.swapBack[i]);
        }
        tdata.swap = swaps;        
        // add all required content modules for the swaps
        for (i = 0; i < swaps.length; i++) {
                if (swaps[i].length > 0) {
                        var moduleIdx = $.fn.liveTile.contentModules.hasContentModule(swaps[i]);
                        if (moduleIdx > -1) {
                                tdata.contentModules.push($.fn.liveTile.contentModules.modules[moduleIdx]);
                        }
                }
        }
        // set the initDelay value to the delay if it's not set
        tdata.initDelay = useData ? this.getDataOrDefault($tile, "initdelay", stgs.initDelay) : stgs.initDelay;
        // if the delay is -1 call the triggerDelay function to get a value
        if (tdata.delay < -1)
            tdata.delay = stgs.triggerDelay(1);
        else if (tdata.delay < 0)
            tdata.delay = 3500 + (Math.random() * 4501);
        // match the delay value if less than 0
        if (tdata.initDelay < 0)
            tdata.initDelay = tdata.delay;
        // merge the objects
        var mergedData = {};
        for (i = 0; i < tdata.contentModules.length; i++)
            $.extend(mergedData, tdata.contentModules[i].data);
        $.extend(mergedData, stgs, tdata);
        // add flip-list / carousel data
        var $tiles;
        if (mergedData.mode === "flip-list") {
            $tiles = $tile.find(mergedData.tileSelector).not(".tile-title");
            $tiles.each(function (idx, ele) {
                var $li = $(ele);
                var ldata = {
                    direction: useData ? privMethods.getDataOrDefault($li, "direction", mergedData.direction) : mergedData.direction,
                    newWindow: useData ? privMethods.getDataOrDefault($li, "new-window", false) : false,
                    link: useData ? privMethods.getDataOrDefault($li, "link", "") : "",
                    faces: { $front: null, $back: null },
                    height: $li.height(),
                    width: $li.width(),
                    isReversed: false
                };
                ldata.margin = ldata.direction === "vertical" ? ldata.height / 2 : ldata.width / 2;
                mergedData.listData.push(ldata);
            });
        } else if (mergedData.mode === "carousel") {
            mergedData.stack = true;
            $tiles = $tile.find(mergedData.tileSelector).not(".tile-title");
            $tiles.each(function (idx, ele) {
                var $slide = $(ele);
                var sdata = {
                    bounce: useData ? privMethods.getDataOrDefault($slide, "bounce", false) : false,
                    bounceDirections: useData ? privMethods.getDataOrDefault($slide, "bounce-dir", "all") : "all",
                    link: useData ? privMethods.getDataOrDefault($slide, "link", "") : "",
                    newWindow: useData ? privMethods.getDataOrDefault($slide, "new-window", false) : false,
                    animationDirection: useData ? privMethods.getDataOrDefault($slide, "ani-direction", "") : "",
                    direction: useData ? privMethods.getDataOrDefault($slide, "direction", "") : ""
                };
                mergedData.listData.push(sdata);
            });
        }
        // get any additional options from the modules
        for (i = 0; i < tdata.contentModules.length; i++){
            if (typeof (mergedData.contentModules[i].initData) === "function")
                mergedData.contentModules[i].initData(mergedData, $tile);
        }
        tdata = null;
        return mergedData;
    },
    prepTile: function ($tile, tdata) {
        //add the mode to the tile if it's not already there.
        $tile.addClass(tdata.mode);
        var ret = {
            $tileFaces: null,     // all possible tile faces in a liveTile in a non list mode
            $listTiles: null,     // all possible tiles in a liveTile in a list mode
            $front: null,         // the front face of a tile in a non list mode
            $back: null          // the back face of a tile in a non list mode
        };
        var rotateDir, frontCss, backCss, tileCss;
        // prepare the tile based on the current mode
        switch (tdata.mode) {
            case "fade":
                // front and back tile faces
                ret.$tileFaces = $tile.find(tdata.tileFaceSelector).not(".tile-title");
                ret.$front = (tdata.faces.$front != null && tdata.faces.$front.length > 0) ?
                               tdata.faces.$front.addClass('fade-front') :
                               ret.$tileFaces.filter(":first").addClass('fade-front');
                // get back face from settings, via selector, or append it if necessary
                if (tdata.faces.$back != null && tdata.faces.$back.length > 0)    // use $back option
                    ret.$back = tdata.faces.$back.addClass('fade-back');
                else if (ret.$tileFaces.length > 1)                             // get the last tile face
                    ret.$back = ret.$tileFaces.filter(":last").addClass('fade-back');
                else if (tdata.appendBack)                                       // append the back tile
                    ret.$back = $('<div class="fade-back"></div>').appendTo($tile);
                else                                                            // just keep an empty placeholder
                    ret.$back = $('<div></div>');
                break;
            case "slide":
                // front and back tile faces
                ret.$tileFaces = $tile.find(tdata.tileFaceSelector).not(".tile-title");
                // get front face from settings or via selector
                ret.$front = (tdata.faces.$front != null && tdata.faces.$front.length > 0) ?
                                tdata.faces.$front.addClass('slide-front') :
                                ret.$tileFaces.filter(":first").addClass('slide-front'); // using :first for pre jQuery 1.4
                // get back face from settings, via selector, or append it if necessary
                if (tdata.faces.$back != null && tdata.faces.$back.length > 0)    // use $back option
                    ret.$back = tdata.faces.$back.addClass('slide-back');
                else if (ret.$tileFaces.length > 1)                             // get the last tile face
                    ret.$back = ret.$tileFaces.filter(":last").addClass('slide-back');
                else if (tdata.appendBack)                                       // append the back tile
                    ret.$back = $('<div class="slide-back"></div>').appendTo($tile);
                else                                                            // just keep an empty placeholder
                    ret.$back = $('<div></div>');
                // stack mode
                if (tdata.stack == true) {
                    var prop,
                        translate;
                    if (tdata.direction === "vertical") {
                        prop = "top",
                        translate = 'translate(0%, -100%) translateZ(0)';
                    } else {
                        prop = "left",
                        translate = 'translate(-100%, 0%) translateZ(0)';
                    }
                    backCss = {};
                    if (tdata.useTranslate)
                        helperMethods.appendStyleProperties(backCss, ['transform'], [translate]);
                    else
                        backCss[prop] = "-100%";
                    ret.$back.css(backCss);
                }
                $tile.data("metrojs.tile", { animating: false });
                if (metrojs.capabilities.canTransition && tdata.useHardwareAccel) {   // hardware accelerated :)                        
                    $tile.addClass("ha");
                    ret.$front.addClass("ha");
                    ret.$back.addClass("ha");
                }
                break;
            case "carousel":
                ret.$listTiles = $tile.find(tdata.tileSelector).not(".tile-title");
                var numberOfSlides = ret.$listTiles.length;
                $tile.data("metrojs.tile", { animating: false });
                tdata.currentIndex = Math.min(tdata.currentIndex, numberOfSlides - 1);
                ret.$listTiles.each(function (idx, ele) {
                    var $slide = $(ele).addClass("slide");
                    var sdata = tdata.listData[idx],
                        aniDir = typeof (sdata.animationDirection) === "string" && sdata.animationDirection.length > 0 ? sdata.animationDirection : tdata.animationDirection,
                        dir = typeof (sdata.direction) === "string" && sdata.direction.length > 0 ? sdata.direction : tdata.direction;
                    if (idx == tdata.currentIndex) {
                        $slide.addClass("active");
                    } else if (aniDir === "forward") {
                        if (dir === "vertical") {
                            tileCss = tdata.useTranslate ? helperMethods.appendStyleProperties({}, ['transform'], ['translate(0%, 100%) translateZ(0)']) :
                                                   { left: '0%', top: '100%' };
                            $slide.css(tileCss);
                        } else {
                            tileCss = tdata.useTranslate ? helperMethods.appendStyleProperties({}, ['transform'], ['translate(100%, 0%) translateZ(0)']) :
                                                   { left: '100%', top: '0%' };
                            $slide.css(tileCss);
                        }
                    } else if (aniDir === "backward") {
                        if (dir === "vertical") {
                            tileCss = tdata.useTranslate ? helperMethods.appendStyleProperties({}, ['transform'], ['translate(0%, -100%) translateZ(0)']) :
                                                   { left: '0%', top: '-100%' };
                            $slide.css(tileCss);
                        } else {
                            tileCss = tdata.useTranslate ? helperMethods.appendStyleProperties({}, ['transform'], ['translate(-100%, 0%) translateZ(0)']) :
                                                   { left: '-100%', top: '0%' };
                            $slide.css(tileCss);
                        }
                    }
                    // link and bounce can be bound per slide
                    // add the click handler and link property
                    privMethods.bindLink($slide, sdata);
                    // add the bounce effect
                    if (tdata.useHardwareAccel && metrojs.capabilities.canTransition)
                        privMethods.bindBounce($slide, sdata);
                    $slide = null;
                    sdata = null;
                });
                // hardware accelerated :)
                if (metrojs.capabilities.canFlip3d && tdata.useHardwareAccel) {
                    $tile.addClass("ha");
                    ret.$listTiles.addClass("ha");
                }
                break;
            case "flip-list":
                // the tile containers inside the list
                ret.$listTiles = $tile.find(tdata.tileSelector).not(".tile-title");
                ret.$listTiles.each(function (idx, ele) {
                    var $li = $(ele).addClass("tile-" + (idx + 1));
                    // add the flip class to the front face
                    var $lFront = $li.find(tdata.tileFaceSelector).filter(":first").addClass("flip-front").css({ margin: "0px" });
                    // append a back tile face if one isnt present
                    if ($li.find(tdata.tileFaceSelector).length === 1 && tdata.appendBack == true)
                        $li.append("<div></div>");
                    // add the flip class to the back face
                    var $lBack = $li.find(tdata.tileFaceSelector).filter(":last").addClass("flip-back").css({ margin: "0px" });
                    // update the tdata object with the faces
                    tdata.listData[idx].faces.$front = $lFront;
                    tdata.listData[idx].faces.$back = $lBack;
                    // set data for overrides and easy access
                    $li.data("metrojs.tile", {
                        animating: false,
                        count: 1,
                        completeTimeout: null,
                        flCompleteTimeout: null,
                        index: idx
                    });
                    var ldata = $li.data("metrojs.tile");
                    // add the hardware accelerated classes
                    if (metrojs.capabilities.canFlip3d && tdata.useHardwareAccel) {   // hardware accelerated :)
                        $li.addClass("ha");
                        $lFront.addClass("ha");
                        $lBack.addClass("ha");
                        rotateDir = tdata.listData[idx].direction === "vertical" ? "rotateX(180deg)" : "rotateY(180deg)";
                        backCss = helperMethods.appendStyleProperties({}, ["transform"], [rotateDir]);
                        $lBack.css(backCss);
                    } else { // not hardware accelerated :(
                        // the front tile face will take up the entire tile
                        frontCss = (tdata.listData[idx].direction === "vertical") ?
				{ height: '100%', width: '100%', marginTop: '0px', opacity: '1' } :
				{ height: '100%', width: '100%', marginLeft: '0px', opacity: '1' };
                        // the back tile face is hidden by default and expanded halfway through a flip
                        backCss = (tdata.listData[idx].direction === "vertical") ?
				{ height: '0px', width: '100%', marginTop: tdata.listData[idx].margin + 'px', opacity: tdata.noHAflipOpacity } :
				{ height: '100%', width: '0px', marginLeft: tdata.listData[idx].margin + 'px', opacity: tdata.noHAflipOpacity };
                        $lFront.css(frontCss);
                        $lBack.css(backCss);
                    }
                    var flipEnded = function () {
                        ldata.count++;
                        if (ldata.count >= MAX_LOOP_COUNT)
                            ldata.count = 1;
                    };
                    if (tdata.flipListOnHover) {
                        var event = tdata.flipListOnHoverEvent + ".liveTile";
                        $lFront.bind(event, function () {
                            privMethods.flip($li, ldata.count, tdata, flipEnded);
                        });
                        $lBack.bind(event, function () {
                            privMethods.flip($li, ldata.count, tdata, flipEnded);
                        });
                    }
                    if (tdata.listData[idx].link.length > 0) {
                        $li.css({ cursor: 'pointer' }).bind("click.liveTile", function () {
                            if (tdata.listData[idx].newWindow)
                                window.open(tdata.listData[idx].link);
                            else
                                window.location = tdata.listData[idx].link;
                        });
                    }
                });
                break;
            case "flip":
                // front and back tile faces
                ret.$tileFaces = $tile.find(tdata.tileFaceSelector).not(".tile-title");
                // get front face from settings or via selector
                ret.$front = (tdata.faces.$front != null && tdata.faces.$front.length > 0) ?
                                tdata.faces.$front.addClass('flip-front') :
                                ret.$tileFaces.filter(":first").addClass('flip-front');
                // get back face from settings, via selector, or append it if necessary
                if (tdata.faces.$back != null && tdata.faces.$back.length > 0) {
                    // use $back option
                    ret.$back = tdata.faces.$back.addClass('flip-back');
                } else if (ret.$tileFaces.length > 1) {
                    // get the last tile face
                    ret.$back = ret.$tileFaces.filter(":last").addClass('flip-back');
                } else if (tdata.appendBack) {
                    // append the back tile
                    ret.$back = $('<div class="flip-back"></div>').appendTo($tile);
                } else {
                    // just keep an empty placeholder
                    ret.$back = $('<div></div>');
                }
                $tile.data("metrojs.tile", { animating: false });
                if (metrojs.capabilities.canFlip3d && tdata.useHardwareAccel) {
                    // hardware accelerated :)
                    $tile.addClass("ha");
                    ret.$front.addClass("ha");
                    ret.$back.addClass("ha");
                    rotateDir = tdata.direction === "vertical" ? "rotateX(180deg)" : "rotateY(180deg)";
                    backCss = helperMethods.appendStyleProperties({}, ["transform"], [rotateDir]);
                    ret.$back.css(backCss);

                } else {
                    // not hardware accelerated :(
                    // the front tile face will take up the entire tile
                    frontCss = (tdata.direction === "vertical") ?
			{ height: '100%', width: '100%', marginTop: '0px', opacity: '1' } :
			{ height: '100%', width: '100%', marginLeft: '0px', opacity: '1' };
                    // the back tile face is hidden by default and expanded halfway through a flip
                    backCss = (tdata.direction === "vertical") ?
			{ height: '0%', width: '100%', marginTop: tdata.margin + 'px', opacity: '0' } :
			{ height: '100%', width: '0%', marginLeft: tdata.margin + 'px', opacity: '0' };
                    ret.$front.css(frontCss);
                    ret.$back.css(backCss);
                }
                break;
        }
        return ret;
    },
    bindPauseOnHover: function ($tile) {
        // stop the tile when hovered and resume after a delay
        (function () {
            var data = $tile.data("LiveTile"),
                isOver = false,
                isPending = false,
                pauseIn = (data.pauseOnHoverEvent == "both" || data.pauseOnHoverEvent == "mouseover" || data.pauseOnHoverEvent == "mouseenter"),
                pauseOut = (data.pauseOnHoverEvent == "both" || data.pauseOnHoverEvent == "mouseout" || data.pauseOnHoverEvent == "mouseleave");
            data.pOnHoverMethods = {
                pause: function () {
                    data.timer.pause();
                    if (data.mode === "flip-list") {
                        data.faces.$listTiles.each(function (idx, li) {
                            window.clearTimeout($(li).data("metrojs.tile").completeTimeout);
                        });
                    }
                },
                over: function (e) {
                    if (isOver || isPending)
                        return;
                    if (data.runEvents) {
                        isPending = true;
                        data.eventTimeout = window.setTimeout(function () {
                            isPending = false;
                            if (pauseOut)
                                isOver = true;
                            data.pOnHoverMethods.pause();
                        }, data.onHoverDelay);
                    }
                },
                out: function (e) {
                    if (isPending) {
                        window.clearTimeout(data.eventTimeout);
                        isPending = false;
                        return;
                    }
                    if (pauseIn) {
                        if (!isOver && !isPending)
                            return;
                        if (data.runEvents) {
                        	// todo: use a custom value if provided
                        	data.timer.start(data.hasRun ? data.delay : data.initDelay);
                        }
                    } else {
                        data.pOnHoverMethods.pause();
                    }                  
                    isOver = false;
                }
            };            
            if (!metrojs.capabilities.canTouch) {
                if (pauseIn)
                    $tile.bind("mouseover.liveTile", data.pOnHoverMethods.over);
                if (pauseOut)
                    $tile.bind("mouseout.liveTile", data.pOnHoverMethods.out);
            } else {
                if (window.navigator.msPointerEnabled) { // pointer
                    if (pauseIn)
                        $tile[0].addEventListener('MSPointerOver', data.pOnHoverMethods.over, false);
                    if (pauseOut)
                        $tile[0].addEventListener('MSPointerOut', data.pOnHoverMethods.out, false);
                } else { // touch events
                    if (pauseIn)
                        $tile.bind("touchstart.liveTile", data.pOnHoverMethods.over);
                    if (pauseOut)
                        $tile.bind("touchend.liveTile", data.pOnHoverMethods.out);
                }
            }
        })();
    },
    unbindMsPauseOnHover: function ($tile, data) {
        if (typeof (data.pOnHoverMethods) !== "undefined" && window.navigator.msPointerEnabled) {
            $tile[0].removeEventListener('MSPointerOver', data.pOnHoverMethods.over, false);
            $tile[0].removeEventListener('MSPointerOut', data.pOnHoverMethods.out, false);
        }
    },
    bindPlayOnHover: function ($tile, data) {
        // play the tile immediately when hovered
        (function () {
            var isOver = false,
                isPending = false,
                playIn = (data.playOnHoverEvent == "both" || data.playOnHoverEvent == "mouseover" || data.playOnHoverEvent == "mouseenter"),
                playOut = (data.playOnHoverEvent == "both" || data.playOnHoverEvent == "mouseout" || data.playOnHoverEvent == "mouseleave");
            data.onHoverMethods = {
                over: function (event) {
                    if (isOver || isPending || (data.bounce && data.bounceMethods.down != "no"))
                        return;
                    // if startNow is set use the opposite of isReversed so we're in sync            
                    var rev = (data.mode == "flip") || (data.startNow ? !data.isReversed : data.isReversed);
                    window.clearTimeout(data.eventTimeout);
                    if ((data.runEvents && rev) || !data.hasRun) {
                        isPending = true;
                        data.eventTimeout = window.setTimeout(function () {
                            isPending = false;
                            if(playOut)
                                isOver = true;
                            pubMethods["play"].apply($tile[0], [0]);
                        }, data.onHoverDelay);
                    }
                },
                out: function (event) {
                    if (isPending) {                        
                        window.clearTimeout(data.eventTimeout);
                        isPending = false;
                        return;
                    }
                    if (playIn) {
                        if (!isOver && !isPending) {                            
                            return;
                        }
                    }
                    window.clearTimeout(data.eventTimeout);
                    data.eventTimeout = window.setTimeout(function () {
                        var rev = (data.mode == "flip") || (data.startNow ? data.isReversed : !data.isReversed);
                        if (data.runEvents && rev) {
                            pubMethods["play"].apply($tile[0], [0]);
                        }
                        isOver = false;
                    }, data.speed + 200);
                }
            };            
            if (!metrojs.capabilities.canTouch) {                
                if (playIn)
                    $tile.bind('mouseenter.liveTile', data.onHoverMethods.over);
                if (playOut)
                    $tile.bind('mouseleave.liveTile', data.onHoverMethods.out);                
            } else {
                if (window.navigator.msPointerEnabled) { // pointer
                    if(playIn)
                        $tile[0].addEventListener('MSPointerDown', data.onHoverMethods.over, false);
                    // mouseleave gives a more consistent effect than out when the children are transformed
                    if(playOut)
                        $tile.bind("mouseleave.liveTile", data.onHoverMethods.out);
                } else { // touch events
                    if(playIn)
                        $tile.bind("touchstart.liveTile", data.onHoverMethods.over);
                    if(playOut)
                        $tile.bind("touchend.liveTile", data.onHoverMethods.out);
                }

            }
        })();
    },
    unbindMsPlayOnHover: function ($tile, data) {
        if (typeof (data.onHoverMethods) !== "undefined" && window.navigator.msPointerEnabled) {
            $tile[0].removeEventListener('MSPointerDown', data.onHoverMethods.over, false);
        }
    },
    bindBounce: function ($tile, data) {
        // add bounce
        if (data.bounce) {
            $tile.addClass("bounce");
            $tile.addClass("noselect");
            (function () {
                data.bounceMethods = {
                    down: "no",
                    threshold: 30,
                    zeroPos: { x: 0, y: 0 },
                    eventPos: { x: 0, y: 0 },
                    inTilePos: { x: 0, y: 0 },
                    pointPos: { x: 0, y: 0 },
                    regions: {
                        c: [0, 0],      // center
                        tl: [-1, -1],   // top left
                        tr: [1, -1],    // top right
                        bl: [-1, 1],    // bottom left
                        br: [1, 1],     // bottom right
                        t: [null, -1],  // top
                        r: [1, null],   // right
                        b: [null, 1],   // bottom
                        l: [-1, null]   // left
                    },
                    targets: {
                        all: ['c', 't', 'r', 'b', 'l', 'tl', 'tr', 'bl', 'br'],
                        edges: ['c', 't', 'r', 'b', 'l'],
                        corners: ['c', 'tl', 'tr', 'bl', 'br']
                    },
                    hitTest: function ($el, pos, targetRegions, omegaC) {
                        var regions = data.bounceMethods.regions,
                            checkFor = data.bounceMethods.targets[targetRegions],
                            i = 0,
                            strictMatch = null,
                            looseMatch = null,
                            defResult = { hit: [0, 0], name: 'c' };
                        // scale only for android 2.x and old ie
                        if (metrojs.capabilities.isOldAndroid || !metrojs.capabilities.canTransition)
                            return defResult;
                        if (typeof (checkFor) == "undefined") {
                            if (typeof (targetRegions) === "string")
                                checkFor = targetRegions.split(',');
                            // only default to center if explicitly requested
                            if ($.isArray(checkFor) && $.inArray('c') == -1) {
                                omegaC = 0;
                                defResult = null;
                            }
                        }
                        // check for a matching region
                        var w = $el.width(),
                           h = $el.height(),
                           // center threshold -  maximum amount from center
                           ct = [w * omegaC, h * omegaC],
                           // how far from the center is the point
                           diffX = pos.x - (w * 0.5),
                           diffY = pos.y - (h * 0.5),
                            // if we're beyond the center threshold, set -1 or 1 else 0
                           hit = [
                               diffX > 0 ? (Math.abs(diffX) <= ct[0] ? 0 : 1) : (Math.abs(diffX) <= ct[0] ? 0 : -1),
                               diffY > 0 ? (Math.abs(diffY) <= ct[1] ? 0 : 1) : (Math.abs(diffY) <= ct[1] ? 0 : -1)
                           ];
                        for (; i < checkFor.length; i++) {
                            if (strictMatch != null)
                                return strictMatch;
                            var r = checkFor[i],
                                region = regions[r];
                            if (r == "*") {
                                r = checkFor[i + 1];
                                return { region: regions[r], name: r };
                            }
                            if (hit[0] == region[0] && hit[1] == region[1]) {
                                // found the region with a strict lookup
                                strictMatch = { hit: region, name: r };
                            } else if ((hit[0] == region[0] || region[0] == null) && (hit[1] == region[1] || region[1] == null)) {
                                // found the region with a loose lookup
                                looseMatch = { hit: region, name: r };
                            }
                        }
                        // prefer a strict match
                        if (strictMatch != null)
                            return strictMatch;
                        else if (looseMatch != null)
                            return looseMatch;
                        else // no matches were found, return center
                            return defResult;
                    },
                    bounceDown: function (e) {
                        if (e.target.tagName == "A" && !$(e).is(".bounce"))
                            return;
                        var point = e.originalEvent && e.originalEvent.touches ? e.originalEvent.touches[0] : e,
                            offsetOfTile = $tile.offset(),
                            scrollX = window.pageXOffset,
                            scrollY = window.pageYOffset;
                        data.bounceMethods.pointPos = {
                            x: point.pageX,
                            y: point.pageY
                        };
                        data.bounceMethods.inTilePos = {
                            x: point.pageX - offsetOfTile.left,
                            y: point.pageY - offsetOfTile.top
                        };

                        if (!data.$tileParent) {
                            data.$tileParent = $tile.parent();
                        }
                        var offsetOfParent = data.$tileParent.offset();
                        data.bounceMethods.eventPos = {
                            x: (offsetOfTile.left - offsetOfParent.left) + ($tile.width() / 2),
                            y: (offsetOfTile.top - offsetOfParent.top) + ($tile.height() / 2)
                        };
                        var hit = data.bounceMethods.hitTest($tile, data.bounceMethods.inTilePos, data.bounceDirections, 0.25);
                        if (hit == null)
                            data.bounceMethods.down = "no";
                        else {
                            if (window.navigator.msPointerEnabled) {
                                document.addEventListener('MSPointerUp', data.bounceMethods.bounceUp, false);
                                $tile[0].addEventListener('MSPointerUp', data.bounceMethods.bounceUp, false);
                                document.addEventListener('MSPointerCancel', data.bounceMethods.bounceUp, false);
                                if (data.bounceFollowsMove)
                                    $tile[0].addEventListener('MSPointerMove', data.bounceMethods.bounceMove, false);
                            } else {
                                $(document).bind("mouseup.liveTile, touchend.liveTile, touchcancel.liveTile, dragstart.liveTile", data.bounceMethods.bounceUp);
                                if (data.bounceFollowsMove) {
                                    $tile.bind("touchmove.liveTile", data.bounceMethods.bounceMove);
                                    $tile.bind("mousemove.liveTile", data.bounceMethods.bounceMove);
                                }
                            }
                            var bClass = "bounce-" + hit.name;
                            $tile.addClass(bClass);
                            data.bounceMethods.down = bClass;
                            data.bounceMethods.downPcss = helperMethods.appendStyleProperties({}, ['perspective-origin'], [data.bounceMethods.eventPos.x + "px " + data.bounceMethods.eventPos.y + "px"]);                            
                            data.$tileParent.css(data.bounceMethods.downPcss);
                        }
                    },
                    bounceUp: function () {
                        if (data.bounceMethods.down != "no") {
                            data.bounceMethods.unBounce();
                            if (window.navigator.msPointerEnabled) {
                                document.removeEventListener('MSPointerUp', data.bounceMethods.bounceUp, false);
                                $tile[0].removeEventListener('MSPointerUp', data.bounceMethods.bounceUp, false);
                                document.removeEventListener('MSPointerCancel', data.bounceMethods.bounceUp, false);
                                if (data.bounceFollowsMove)
                                    $tile[0].removeEventListener('MSPointerMove', data.bounceMethods.bounceMove, false);

                            } else
                                $(document).unbind("mouseup.liveTile, touchend.liveTile, touchcancel.liveTile, dragstart.liveTile", data.bounceMethods.bounceUp);
                            if (data.bounceFollowsMove) {
                                $tile.unbind("touchmove.liveTile", data.bounceMethods.bounceMove);
                                $tile.unbind("mousemove.liveTile", data.bounceMethods.bounceMove);
                            }
                        }
                    },// not currently used
                    bounceMove: function (e) {
                        if (data.bounceMethods.down != "no") {
                            var point = e.originalEvent && e.originalEvent.touches ? e.originalEvent.touches[0] : e,
                                x = Math.abs(point.pageX - data.bounceMethods.pointPos.x),
                                y = Math.abs(point.pageY - data.bounceMethods.pointPos.y);
                            if (x > data.bounceMethods.threshold || y > data.bounceMethods.threshold) {
                                var bounceClass = data.bounceMethods.down;
                                data.bounceMethods.bounceDown(e);
                                if (bounceClass != data.bounceMethods.down)
                                    $tile.removeClass(bounceClass);
                            }
                        }
                    },
                    unBounce: function () {
                        $tile.removeClass(data.bounceMethods.down);
                        if (typeof (data.bounceMethods.downPcss) == "object") {
                            var names = ['perspective-origin', 'perspective-origin-x', 'perspective-origin-y'],
                                vals = ['', '', ''];
                            data.bounceMethods.downPcss = helperMethods.appendStyleProperties({}, names, vals);
                            // let the bounce finish and then strip out the perspective
                            window.setTimeout(function () {
                                data.$tileParent.css(data.bounceMethods.downPcss);
                            }, 200);
                        }
                        data.bounceMethods.down = "no";
                        data.bounceMethods.inTilePos = data.bounceMethods.zeroPos;
                        data.bounceMethods.eventPos = data.bounceMethods.zeroPos;
                    }
                };
                // IE 10+
                if (window.navigator.msPointerEnabled) {// touch only -> // && window.navigator.msMaxTouchPoints) {
                    $tile[0].addEventListener('MSPointerDown', data.bounceMethods.bounceDown, false);
                } else if (metrojs.capabilities.canTouch) {
                    // everybody else                    
                    $tile.bind("touchstart.liveTile", data.bounceMethods.bounceDown);

                } else {
                    $tile.bind("mousedown.liveTile", data.bounceMethods.bounceDown);
                }
            })();
        }
    },
    unbindMsBounce: function ($tile, data) {
        if (data.bounce && window.navigator.msPointerEnabled) {// touch only -> // && window.navigator.msMaxTouchPoints) {
            $tile[0].removeEventListener('MSPointerDown', data.bounceMethods.bounceDown, false);
            $tile[0].removeEventListener('MSPointerCancel', data.bounceMethods.bounceUp, false);
            $tile[0].removeEventListener('MSPointerOut', data.bounceMethods.bounceUp, false);
            //$tile[0].removeEventListener('MSPointerMove', data.bounceMethods.bounceMove, false);
        }
    },
    bindLink: function ($tile, data) {
        if (data.link.length > 0) {
            $tile.css({ cursor: 'pointer' }).bind("click.liveTile", function (e) {
                if (e.target.tagName == "A" && !$(e).is(".live-tile,.slide,.flip"))
                    return;
                if (data.newWindow)
                    window.open(data.link);
                else
                    window.location = data.link;
            });
        }
    },
    runContenModules: function (data, $front, $back, index) {
        for (var i = 0; i < data.contentModules.length; i++) {
                var currentModule = data.contentModules[i];
                if (typeof (currentModule.action) == "function")
                        currentModule.action(data, $front, $back, index);
            }
    },
    fade: function ($tile, count, data) {
        var tdata = typeof (data) === "object" ? data : $tile.data("LiveTile"),
            resumeTimer = function () {
                // if the tile should run again start the timer back with the current delay
                if (tdata.timer.repeatCount > 0 || tdata.timer.repeatCount == -1) {
                    if (tdata.timer.count != tdata.timer.repeatCount) {
                        tdata.timer.start(tdata.delay);
                    }
                }
            };

        if (tdata.faces.$front.is(":animated"))
            return;
        tdata.timer.pause();
        var loopCount = tdata.loopCount + 1;
        tdata.isReversed = loopCount % 2 === 0; // the count starts at 1
        var start = tdata.animationStarting.call($tile[0], tdata, tdata.faces.$front, tdata.faces.$back);
        if (typeof (start) != "undefined" && start == false) {
            resumeTimer();
            return;
        }
        tdata.loopCount = loopCount;
        var faded = function () {
            resumeTimer();
            // run content modules and animationComplete callback
            privMethods.runContenModules(tdata, tdata.faces.$front, tdata.faces.$back);
            tdata.animationComplete.call($tile[0], tdata, tdata.faces.$front, tdata.faces.$back);
        };
        if (tdata.isReversed)
            tdata.faces.$front.fadeIn(tdata.speed, tdata.noHaTransFunc, faded);
        else
            tdata.faces.$front.fadeOut(tdata.speed, tdata.noHaTransFunc, faded);
    },
    slide: function ($tile, count, data, stopIndex, callback) {
        var tdata = typeof (data) === "object" ? data : $tile.data("LiveTile"),
            aniData = $tile.data("metrojs.tile");
        if (aniData.animating == true || $tile.is(":animated")) {
            tdata = null;
            aniData = null;
            return;
        }
        var resumeTimer = function () {
            // if the tile should run again start the timer back with the current delay
            if (tdata.timer.repeatCount > 0 || tdata.timer.repeatCount == -1) {
                if (tdata.timer.count != tdata.timer.repeatCount) {
                    tdata.timer.start(tdata.delay);
                }
            }
        };
        if (tdata.mode !== "carousel") {
            tdata.isReversed = tdata.currentIndex % 2 !== 0;  // the count starts at 1
            // carousel mode maintains its own timer
            tdata.timer.pause();
            var start = tdata.animationStarting.call($tile[0], tdata, tdata.faces.$front, tdata.faces.$back);
            if (typeof (start) != "undefined" && start == false) {
                resumeTimer();
                return;
            }
            tdata.loopCount = tdata.loopCount + 1;
        } else {
            // in carousel mode the face that just left the stage is always the $back
            tdata.isReversed = true;
        }
        // get temp values passed in from data methods
        var direction;
        if (typeof (tdata.tempValues.direction) === "string" && tdata.tempValues.direction.length > 0)
            direction = tdata.tempValues.direction;
        else
            direction = tdata.direction;
        tdata.tempValues.direction = null;
        var css = {},
            cssback = {},
            // the stop index is overridden in carousel mode
            stopIdx = typeof (stopIndex) === "undefined" ? tdata.currentIndex : stopIndex,
            stop = $.trim(tdata.stops[Math.min(stopIdx, tdata.stops.length - 1)]),
            pxIdx = stop.indexOf('px'),
            offset = 0,
            amount = 0,
            metric = (direction === "vertical") ? tdata.height : tdata.width,
            tProp = (direction === "vertical") ? "top" : "left",
            stack = tdata.stack == true;
        // when the slide is complete increment the index or call the callback
        var slideFinished = function () {
            if (typeof (callback) === "undefined") {
                tdata.currentIndex = tdata.currentIndex + 1;
                if (tdata.currentIndex > tdata.stops.length - 1) {
                    tdata.currentIndex = 0;
                }
            } else {
                callback();
            }
            if (tdata.mode != "carousel") {
                resumeTimer();
            }
            // run content modules and animationComplete callback            
            privMethods.runContenModules(tdata, tdata.faces.$front, tdata.faces.$back, tdata.currentIndex);
            tdata.animationComplete.call($tile[0], tdata, tdata.faces.$front, tdata.faces.$back);
            tdata = null;
            aniData = null;
        };
        if (pxIdx > 0) {
            amount = parseInt(stop.substring(0, pxIdx), 10);
            offset = (amount - metric) + 'px';
        } else {
            //is a percentage
            amount = parseInt(stop.replace('%', ''), 10);
            offset = (amount - 100) + '%';
        }
        // hardware accelerated :)
        if (metrojs.capabilities.canTransition && tdata.useHardwareAccel) {
            if (typeof (aniData.animating) !== "undefined" && aniData.animating == true)
                return;
            aniData.animating = true;
            var props = ['transition-property', 'transition-duration', 'transition-timing-function'],
                vals = [tdata.useTranslate ? "transform" : tProp, tdata.speed + 'ms', tdata.haTransFunc];
            vals[helperMethods.browserPrefix + 'transition-property'] = helperMethods.browserPrefix + "transform";
            css = helperMethods.appendStyleProperties(css, props, vals);
            cssback = helperMethods.appendStyleProperties(cssback, props, vals);
            var vertical = direction === "vertical",
                prop = vertical ? "top" : "left",
                translateTo;
            if (!tdata.useTranslate) {
                css[prop] = stop;
                if (stack)
                    cssback[prop] = offset;
            } else {
                translateTo = vertical ? "translate(0%, " + stop + ")" : "translate(" + stop + ", 0%)";
                css = helperMethods.appendStyleProperties(css, ['transform'], [translateTo + "translateZ(0)"]);
                if (stack) {
                    translateTo = vertical ? "translate(0%, " + offset + ")" : "translate(" + offset + ", 0%)";
                    cssback = helperMethods.appendStyleProperties(cssback, ['transform'], [translateTo + "translateZ(0)"]);
                }
            }
            tdata.faces.$front.css(css);
            if (stack)
                tdata.faces.$back.css(cssback);
            window.clearTimeout(tdata.completeTimeout);
            tdata.completeTimeout = window.setTimeout(function () {
                aniData.animating = false;
                slideFinished();
            }, tdata.speed);
        } else {
            // not hardware accelerated :(
            css[tProp] = stop;
            cssback[tProp] = offset;
            aniData.animating = true;
            var $front = tdata.faces.$front.stop(),
                $back = tdata.faces.$back.stop();
            $front.animate(css, tdata.speed, tdata.noHaTransFunc, function () {
                aniData.animating = false;
                slideFinished();
            });
            // change the css value to the offset
            if (stack)
                $back.animate(cssback, tdata.speed, tdata.noHaTransFunc, function () { });
        }
    },
    carousel: function ($tile, count) {
        var tdata = $tile.data("LiveTile");
        // dont update css or call slide if animated or if there's only one face
        var aniData = $tile.data("metrojs.tile");
        if (aniData.animating == true || tdata.faces.$listTiles.length <= 1) {
            aniData = null;
            return;
        }
        var resumeTimer = function () {
            if (tdata.timer.repeatCount > 0 || tdata.timer.repeatCount == -1) {
                if (tdata.timer.count != tdata.timer.repeatCount) {
                    tdata.timer.start(tdata.delay);
                }
            }
        };
        // pause the timer and use a per slide delay
        tdata.timer.pause();
        var $cur = tdata.faces.$listTiles.filter(".active"),
            idx = tdata.faces.$listTiles.index($cur),
            goTo = tdata.currentIndex,
            eq = goTo != idx ? goTo : idx,
            nxtIdx = eq + 1 >= tdata.faces.$listTiles.length ? 0 : eq + 1,
            sdata = tdata.listData[nxtIdx];
        if (idx == nxtIdx) {
            aniData = null;
            $cur = null;
            return;
        }
        // get temp values passed in from data methods
        var animationDirection;
        if (typeof (tdata.tempValues.animationDirection) === "string" && tdata.tempValues.animationDirection.length > 0)
            animationDirection = tdata.tempValues.animationDirection;
        else if (typeof (sdata.animationDirection) === "string" && sdata.animationDirection.length > 0) {
            animationDirection = sdata.animationDirection;
        } else
            animationDirection = tdata.animationDirection;
        // the temp value for animation direction is not used in slide so i'm setting it to null
        tdata.tempValues.animationDirection = null;
        var direction;
        if (typeof (tdata.tempValues.direction) === "string" && tdata.tempValues.direction.length > 0) {
            direction = tdata.tempValues.direction;
        } else if (typeof (sdata.direction) === "string" && sdata.direction.length > 0) {
            direction = sdata.direction;
            tdata.tempValues.direction = direction;
        } else {
            direction = tdata.direction;
        }
        var $nxt = tdata.faces.$listTiles.eq(nxtIdx),
            start = tdata.animationStarting.call($tile[0], tdata, $cur, $nxt);
        if (typeof (start) != "undefined" && start == false) {
            resumeTimer();
            return;
        }
        tdata.loopCount = tdata.loopCount + 1;
        var nxtCss = helperMethods.appendStyleProperties({}, ['transition-duration'], ['0s']),
            vertical = direction === "vertical",
            translateTo;
        if (animationDirection === "backward") {
            if (!tdata.useTranslate || !metrojs.capabilities.canTransition) {
                if (vertical) {
                    nxtCss.top = "-100%";
                    nxtCss.left = "0%";
                } else {
                    nxtCss.top = "0%";
                    nxtCss.left = "-100%";
                }
                tdata.stops = ['100%'];
            } else {
                translateTo = vertical ? "translate(0%, -100%)" : "translate(-100%, 0%)";
                nxtCss = helperMethods.appendStyleProperties(nxtCss, ["transform"], [translateTo + " translateZ(0)"]);
                tdata.stops = ['100%'];
            }
            tdata.faces.$front = $cur;
            tdata.faces.$back = $nxt;

        } else {
            if (!tdata.useTranslate || !metrojs.capabilities.canTransition) {
                if (vertical) {
                    nxtCss.top = "100%";
                    nxtCss.left = "0%";
                } else {
                    nxtCss.top = "0%";
                    nxtCss.left = "100%";
                }
            } else {
                translateTo = vertical ? "translate(0%, 100%)" : "translate(100%, 0%)";
                nxtCss = helperMethods.appendStyleProperties(nxtCss, ["transform"], [translateTo + " translateZ(0)"]);
            }
            tdata.faces.$front = $nxt;
            tdata.faces.$back = $cur;
            tdata.stops = ['0%'];
        }
        $nxt.css(nxtCss);
        // the timeout wrapper gives the css call above enough time to finish in case we dynamically set the direction
        window.setTimeout(function () {
            $cur.removeClass("active");
            $nxt.addClass("active");
            privMethods.slide($tile, count, tdata, 0, function () {
                tdata.currentIndex = nxtIdx;
                aniData = null;
                $cur = null;
                $nxt = null;
                resumeTimer();
            });
        }, 150);

    },
    flip: function ($tile, count, data, callback) {
        var aniData = $tile.data("metrojs.tile");
        if (aniData.animating == true) {
            aniData = null;
            return;
        }
        var tdata = typeof (data) === "object" ? data : $tile.data("LiveTile");
        var $front, $back, direction, deg, rotateDir, css,
            raiseEvt = typeof (callback) === "undefined",
            index = 0,
            isReversed,  // the count starts at 1
            resumeTimer = function () {
                // if the tile should run again start the timer back with the current delay
                if (tdata.timer.repeatCount > 0 || tdata.timer.repeatCount == -1) {
                    if (tdata.timer.count != tdata.timer.repeatCount) {
                        tdata.timer.start(tdata.delay);
                    }
                }
            };
        // the timer is only paused if animationComplete is fired
        if (raiseEvt) {
            tdata.timer.pause();
            var loopCount = tdata.loopCount + 1;
            isReversed = loopCount % 2 === 0;
            tdata.isReversed = isReversed;
            $front = tdata.faces.$front;
            $back = tdata.faces.$back;
            var args = isReversed ? [tdata, $back, $front] : [tdata, $front, $back];
            var start = tdata.animationStarting.apply($tile[0], args);
            if (typeof (start) != "undefined" && start == false) {
                resumeTimer();
                return;
            }
            direction = tdata.direction;
            height = tdata.height;
            width = tdata.width;
            margin = tdata.margin;
            tdata.loopCount = loopCount;
        } else {
            isReversed = count % 2 === 0;
            index = aniData.index;
            $front = tdata.listData[index].faces.$front;
            $back = tdata.listData[index].faces.$back;
            tdata.listData[index].isReversed = isReversed;
            direction = tdata.listData[index].direction;
            height = tdata.listData[index].height;
            width = tdata.listData[index].width;
            margin = tdata.listData[index].margin;
        }

        if (metrojs.capabilities.canFlip3d && tdata.useHardwareAccel) { // Hardware accelerated :)
            deg = !isReversed ? "180deg" : "360deg";
            rotateDir = direction === "vertical" ? "rotateX(" + deg + ")" : "rotateY(" + deg + ")";
            css = helperMethods.appendStyleProperties({}, ["transform", "transition"], [rotateDir, "all " + tdata.speed + "ms " + tdata.haTransFunc + " 0s"]);
            var bDeg = !isReversed ? "360deg" : "540deg";
            var bRotateDir = direction === "vertical" ? "rotateX(" + bDeg + ")" : "rotateY(" + bDeg + ")";
            var bCss = helperMethods.appendStyleProperties({}, ["transform", "transition"], [bRotateDir, "all " + tdata.speed + "ms " + tdata.haTransFunc + " 0s"]);
            $front.css(css);
            $back.css(bCss);

            var action = function () {
                aniData.animating = false;
                var resetDir, newCss;
                if (!isReversed) {                    
                    privMethods.runContenModules(tdata, $back, $front, index);
                    if (raiseEvt) {
                        resumeTimer();
                        tdata.animationComplete.call($tile[0], tdata, $back, $front);
                    } else
                        callback(tdata, $back, $front);
                } else {
                    resetDir = direction === "vertical" ? "rotateX(0deg)" : "rotateY(0deg)";
                    newCss = helperMethods.appendStyleProperties({}, ["transform", "transition"], [resetDir, "all 0s " + tdata.haTransFunc + " 0s"]);
                    $front.css(newCss);
                    //call content modules
                    privMethods.runContenModules(tdata, $front, $back, index);
                    if (raiseEvt) {
                        resumeTimer();
                        tdata.animationComplete.call($tile[0], tdata, $front, $back);
                    } else
                        callback(tdata, $front, $back);
                    $front = null;
                    $back = null;
                    tdata = null;
                    aniData = null;
                }
            };
            if (tdata.mode === "flip-list") {
                window.clearTimeout(tdata.listData[index].completeTimeout);
                tdata.listData[index].completeTimeout = window.setTimeout(action, tdata.speed);
            } else {
                window.clearTimeout(tdata.completeTimeout);
                tdata.completeTimeout = window.setTimeout(action, tdata.speed);
            }
        } else { // not Hardware accelerated :(
            var speed = tdata.speed / 2;
            var hideCss = (direction === "vertical") ?
						{ height: '0px', width: '100%', marginTop: margin + 'px', opacity: tdata.noHAflipOpacity } :
						{ height: '100%', width: '0px', marginLeft: margin + 'px', opacity: tdata.noHAflipOpacity };
            var showCss = (direction === "vertical") ?
                        { height: '100%', width: '100%', marginTop: '0px', opacity: '1' } :
                        { height: '100%', width: '100%', marginLeft: '0px', opacity: '1' };
            var noHaAction;
            if (!isReversed) {
                aniData.animating = true;
                $front.stop().animate(hideCss, { duration: speed });
                noHaAction = function () {
                    aniData.animating = false;
                    $back.stop().animate(showCss, {
                        duration: speed,
                        complete: function () {
                            privMethods.runContenModules(tdata, $back, $front, index);
                            if (raiseEvt) {
                                resumeTimer();
                                tdata.animationComplete.call($tile[0], tdata, $back, $front);
                            } else
                                callback(tdata, $back, $front);
                            $front = null;
                            $back = null;
                            tdata = null;
                            aniData = null;
                        }
                    });
                };
                if (tdata.mode === "flip-list") {
                    window.clearTimeout(tdata.listData[aniData.index].completeTimeout);
                    tdata.listData[aniData.index].completeTimeout = window.setTimeout(noHaAction, speed);
                } else {
                    window.clearTimeout(tdata.completeTimeout);
                    tdata.completeTimeout = window.setTimeout(noHaAction, speed);
                }
            } else {
                aniData.animating = true;
                $back.stop().animate(hideCss, { duration: speed });
                noHaAction = function () {
                    aniData.animating = false;
                    $front.stop().animate(showCss, {
                        duration: speed,
                        complete: function () {                            
                            privMethods.runContenModules(tdata, $front, $back, index);
                            if (raiseEvt) {
                                resumeTimer();
                                tdata.animationComplete.call($tile[0], tdata, $front, $back);
                            } else
                                callback(tdata, $front, $back);
                            aniData = null;
                            $front = null;
                            $back = null;
                        }
                    });
                };
                if (tdata.mode === "flip-list") {
                    window.clearTimeout(tdata.listData[aniData.index].completeTimeout);
                    tdata.listData[aniData.index].completeTimeout = window.setTimeout(noHaAction, speed);
                } else {
                    window.clearTimeout(tdata.completeTimeout);
                    tdata.completeTimeout = window.setTimeout(noHaAction, speed);
                }
            }

        }
    },
    flipList: function ($tile, count) {
        var tdata = $tile.data("LiveTile"),
            maxDelay = tdata.speed,
            triggered = false,
            resumeTimer = function () {
                if (tdata.timer.repeatCount > 0 || tdata.timer.repeatCount == -1) {
                    if (tdata.timer.count != tdata.timer.repeatCount) {
                        tdata.timer.start(tdata.delay);
                    }
                }
            };
        tdata.timer.pause();
        var start = tdata.animationStarting.call($tile[0], tdata, null, null);
        if (typeof (start) != "undefined" && start == false) {
            resumeTimer();
            return;
        }
        tdata.loopCount = tdata.loopCount + 1;
        tdata.faces.$listTiles.each(function (idx, ele) {
            var $li = $(ele),
                ldata = $li.data("metrojs.tile"),
                tDelay = tdata.triggerDelay(idx),
                triggerDelay = tdata.speed + Math.max(tDelay, 0),
                trigger = tdata.alwaysTrigger;
            if (!trigger)
                trigger = (Math.random() * 351) > 150 ? true : false;
            if (trigger) {
                triggered = true;
                maxDelay = Math.max(triggerDelay + tdata.speed, maxDelay);
                window.clearTimeout(ldata.flCompleteTimeout);
                ldata.flCompleteTimeout = window.setTimeout(function () {
                    // call the flip method with the merged data, but dont fire animationComplete
                    privMethods.flip($li, ldata.count, tdata, function (data) {
                        ldata.count++;
                        if (ldata.count >= MAX_LOOP_COUNT)
                            ldata.count = 1;
                        $li = null;
                        ldata = null;
                    });
                }, triggerDelay);
            }
        });
        if (triggered) {
            window.clearTimeout(tdata.flCompleteTimeout);
            tdata.flCompleteTimeout = window.setTimeout(function () {
                privMethods.runContenModules(tdata, null, null, -1);
                tdata.animationComplete.call($tile[0], tdata, null, null);
                resumeTimer();
            }, maxDelay + tdata.speed); // add some padding to make sure the final callback finished

        }
    }
};


// methods that can be called more universally
var helperMethods = {
    stylePrefixes: 'Webkit Moz O ms Khtml '.split(' '),
    domPrefixes: '-webkit- -moz- -o- -ms- -khtml- '.split(' '),
    browserPrefix: null,
    // a method to append css3 properties for each browser
    // note: values are identical for each property
    appendStyleProperties: function (obj, names, values) {
        for (var i = 0; i <= names.length - 1; i++) {
            obj[$.trim(this.browserPrefix + names[i])] = values[i];
            obj[$.trim(names[i])] = values[i];
        }
        return obj;
    },
    applyStyleValue: function (obj, name, value) {        
            obj[$.trim(this.browserPrefix + name)] = value;
            obj[name] = value;
        return obj;
    },
    getBrowserPrefix: function () {
        if (this.browserPrefix == null) {
            var prefix = "";
            for (var i = 0; i <= this.domPrefixes.length - 1; i++) {
                if (typeof (document.body.style[this.domPrefixes[i] + "transform"]) != "undefined")
                    prefix = this.domPrefixes[i];
            }
            return this.browserPrefix = prefix;
        }
        return this.browserPrefix;
    },    
    //a shuffle method to provide more randomness than sort
    //credit: http://javascript.about.com/library/blshuffle.htm
    //note: avoiding prototype for sharepoint compatability
    shuffleArray: function (array) {
        var s = [];
        while (array.length) s.push(array.splice(Math.random() * array.length, 1));
        while (s.length) array.push(s.pop());
        return array;
    }
};

var defaultModules = {
        moduleName: 'custom',
    customSwap: {
        data: {
            customDoSwapFront: function () { return false; },
            customDoSwapBack: function () { return false; },
            customGetContent: function (tdata, $front, $back, index) { return null; }
        },
        initData: function (tdata, $ele) {
            var swapData = {};
            swapData.doSwapFront = $.inArray('custom', tdata.swapFront) > -1 && tdata.customDoSwapFront();
            swapData.doSwapBack = $.inArray('custom', tdata.swapBack) > -1 && tdata.customDoSwapBack();
            if (typeof (tdata.customSwap) !== "undefined")
                tdata.customSwap = $.extend(swapData, tdata.customSwap);
            else
                tdata.customSwap = swapData;
        },
        action: function (tdata, $front, $back, index) {

        }
    },
    htmlSwap: {
        moduleName: 'html',
        data: { // public data for the swap module                
            frontContent: [],                       // a list of html to use for the front
            frontIsRandom: true,                    // should html be chosen at random or in order                
            frontIsInGrid: false,                   // only chooses one item for each iteration in flip-list                
            backContent: [],                        // a list of html to use for the back
            backIsRandom: true,                     // should html be chosen at random or in order                
            backIsInGrid: false                     // only chooses one item for each iteration in flip-list                
        },
        initData: function (tdata, $ele) {
            var swapData = { // private data for the swap module
                backBag: [],
                backIndex: 0,
                backStaticIndex: 0,
                backStaticRndm: -1,
                prevBackIndex: -1,
                frontBag: [],
                frontIndex: 0,
                frontStaticIndex: 0,
                frontStaticRndm: -1,
                prevFrontIndex: -1
            };
            if (!tdata.ignoreDataAttributes) {
                swapData.frontIsRandom = privMethods.getDataOrDefault($ele, "front-israndom", tdata.frontIsRandom);
                swapData.frontIsInGrid = privMethods.getDataOrDefault($ele, "front-isingrid", tdata.frontIsInGrid);
                swapData.backIsRandom = privMethods.getDataOrDefault($ele, "back-israndom", tdata.backIsRandom);
                swapData.backIsInGrid = privMethods.getDataOrDefault($ele, "back-isingrid", tdata.backIsInGrid);
            } else {
                swapData.frontIsRandom = tdata.frontIsRandom;
                swapData.frontIsInGrid = tdata.frontIsInGrid;
                swapData.backIsRandom = tdata.backIsRandom;
                swapData.backIsInGrid = tdata.backIsInGrid;
                        }
            swapData.doSwapFront = $.inArray('html', tdata.swapFront) > -1 && (tdata.frontContent instanceof Array) && tdata.frontContent.length > 0;
            swapData.doSwapBack = $.inArray('html', tdata.swapBack) > -1 && (tdata.backContent instanceof Array) && tdata.backContent.length > 0;
            if (typeof (tdata.htmlSwap) !== "undefined")
                tdata.htmlSwap = $.extend(swapData, tdata.htmlSwap);
            else
                tdata.htmlSwap = swapData;
            if (tdata.htmlSwap.doSwapFront) {
                tdata.htmlSwap.frontBag = this.prepBag(tdata.htmlSwap.frontBag, tdata.frontContent, tdata.htmlSwap.prevFrontIndex);
                tdata.htmlSwap.frontStaticRndm = tdata.htmlSwap.frontBag.pop();
            }
            if (tdata.htmlSwap.doSwapBack) {
                tdata.htmlSwap.backBag = this.prepBag(tdata.htmlSwap.backBag, tdata.backContent, tdata.htmlSwap.prevBackIndex);
                tdata.htmlSwap.backStaticRndm = tdata.htmlSwap.backBag.pop();
            }
        },
        prepBag: function (bag, content, prevIdx) {
            bag = bag || [];
            var bagCount = 0;
            for (var i = 0; i < content.length; i++) {
                //make sure there's not an immediate repeat
                if (i != prevIdx || bag.length === 1) {
                    bag[bagCount] = i;
                    bagCount++;
                }
            }
            return helperMethods.shuffleArray(bag);
        },
        getFrontSwapIndex: function (tdata) {
            var idx = 0;
            if (!tdata.htmlSwap.frontIsRandom) {
                idx = tdata.htmlSwap.frontIsInGrid ? tdata.htmlSwap.frontStaticIndex : tdata.htmlSwap.frontIndex;
            } else {
                if (tdata.htmlSwap.frontBag.length === 0) {
                    tdata.htmlSwap.frontBag = this.prepBag(tdata.htmlSwap.frontBag, tdata.frontContent, tdata.htmlSwap.prevFrontIndex);
                }
                if (tdata.htmlSwap.frontIsInGrid) {
                    idx = tdata.htmlSwap.frontStaticRndm;
                } else {
                    idx = tdata.htmlSwap.frontBag.pop();
                }
            }
            return idx;
        },
        getBackSwapIndex: function (tdata) {
            var idx = 0;
            if (!tdata.htmlSwap.backIsRandom) {
                idx = tdata.htmlSwap.backIsInGrid ? tdata.htmlSwap.backStaticIndex : tdata.htmlSwap.backIndex;
            } else {
                if (tdata.htmlSwap.backBag.length === 0) {
                    tdata.htmlSwap.backBag = this.prepBag(tdata.htmlSwap.backBag, tdata.backContent, tdata.htmlSwap.prevBackIndex);
                }
                if (tdata.htmlSwap.backIsInGrid) {
                    idx = tdata.htmlSwap.backStaticRndm;
                } else {
                    idx = tdata.htmlSwap.backBag.pop();
                }
            }
            return idx;
        },
        action: function (tdata, $front, $back, index) {
            if (!tdata.htmlSwap.doSwapFront && !tdata.htmlSwap.doSwapBack)
                return;
            var isList = tdata.mode === "flip-list";
            var swapIndex = 0;
            var isReversed = isList ? tdata.listData[Math.max(index, 0)].isReversed : tdata.isReversed;
            if (isList && index == -1) {
                // flip list completed
                if (!isReversed) {
                    if (tdata.htmlSwap.doSwapFront) {
                        // update the random value for grid mode
                        if (tdata.htmlSwap.frontBag.length === 0)
                            tdata.htmlSwap.frontBag = this.prepBag(tdata.htmlSwap.frontBag, tdata.frontContent, tdata.htmlSwap.frontStaticRndm);
                        tdata.htmlSwap.frontStaticRndm = tdata.htmlSwap.frontBag.pop();
                        // update the static index
                        tdata.htmlSwap.frontStaticIndex++;
                        if (tdata.htmlSwap.frontStaticIndex >= tdata.frontContent.length)
                            tdata.htmlSwap.frontStaticIndex = 0;
                    }
                } else {
                    if (tdata.htmlSwap.doSwapBack) {
                        // update the random value for grid mode
                        if (tdata.htmlSwap.backBag.length === 0)
                            tdata.htmlSwap.backBag = this.prepBag(tdata.htmlSwap.backBag, tdata.backContent, tdata.htmlSwap.backStaticRndm);
                        tdata.htmlSwap.backStaticRndm = tdata.htmlSwap.backBag.pop();
                        // update the static index
                        tdata.htmlSwap.backStaticIndex++;
                        if (tdata.htmlSwap.backStaticIndex >= tdata.backContent.length)
                            tdata.htmlSwap.backStaticIndex = 0;
                    }
                }
                return;
            }
            if (!isReversed) {
                if (!tdata.htmlSwap.doSwapFront)
                    return;
                swapIndex = this.getFrontSwapIndex(tdata);
                tdata.htmlSwap.prevFrontIndex = swapIndex;
                if (tdata.mode === "slide") {
                    if (!tdata.startNow)
                        $front.html(tdata.frontContent[swapIndex]);
                    else
                        $back.html(tdata.frontContent[swapIndex]);
                } else
                    $back.html(tdata.frontContent[swapIndex]);
                // increment the front index to get the next item from the list
                tdata.htmlSwap.frontIndex++;
                if (tdata.htmlSwap.frontIndex >= tdata.frontContent.length)
                    tdata.htmlSwap.frontIndex = 0;
                if (!isList) {
                    // increment the static index if we're not in list mode
                    tdata.htmlSwap.frontStaticIndex++;
                    if (tdata.htmlSwap.frontStaticIndex >= tdata.frontContent.length)
                        tdata.htmlSwap.frontStaticIndex = 0;
                } else {
                    // flip list
                }
            } else {
                if (!tdata.htmlSwap.doSwapBack)
                    return;
                swapIndex = this.getBackSwapIndex(tdata);
                tdata.htmlSwap.prevBackIndex = swapIndex;
                $back.html(tdata.backContent[tdata.htmlSwap.backIndex]);
                tdata.htmlSwap.backIndex++;
                if (tdata.htmlSwap.backIndex >= tdata.backContent.length)
                    tdata.htmlSwap.backIndex = 0;
                if (!isList) {
                    tdata.htmlSwap.backStaticIndex++;
                    if (tdata.htmlSwap.backStaticIndex >= tdata.backContent.length)
                        tdata.htmlSwap.backStaticIndex = 0;
                } else {
                    // flip list
                }
            }
        }
    },
    imageSwap: {
            moduleName: 'image',
        data: {
            preloadImages: false,
            imageCssSelector: '>img,>a>img',        // the selector used to choose a an image to apply a src or background to
            fadeSwap: false,                        // fade the image before swapping
            frontImages: [],                        // a list of images to use for the front
            frontIsRandom: true,                    // should images be chosen at random or in order
            frontIsBackgroundImage: false,          // set the src attribute or css background-image property
            frontIsInGrid: false,                   // only chooses one item for each iteration in flip-list
            backImages: null,                       // a list of images to use for the back
            backIsRandom: true,                     // should images be chosen at random or in order
            backIsBackgroundImage: false,           // set the src attribute or css background-image property
            backIsInGrid: false                     // only chooses one item for each iteration in flip-list                
        },
        initData: function (tdata, $ele) {
            var swapData = {
                backBag: [],
                backIndex: 0,
                backStaticIndex: 0,
                backStaticRndm: -1,
                frontBag: [],
                frontIndex: 0,
                frontStaticIndex: 0,
                frontStaticRndm: -1,
                prevBackIndex: -1,
                prevFrontIndex: -1
                }, useData = tdata.ignoreDataAttributes;
                if (useData) {
                        swapData.imageCssSelector = privMethods.getDataOrDefault($ele, "image-css", tdata.imageCssSelector);
                        swapData.fadeSwap = privMethods.getDataOrDefault($ele, "fadeswap", tdata.fadeSwap);
                        swapData.frontIsRandom = privMethods.getDataOrDefault($ele, "front-israndom", tdata.frontIsRandom);
                        swapData.frontIsInGrid = privMethods.getDataOrDefault($ele, "front-isingrid", tdata.frontIsInGrid);
                        swapData.frontIsBackgroundImage = privMethods.getDataOrDefault($ele, "front-isbg", tdata.frontIsBackgroundImage);
                        swapData.backIsRandom = privMethods.getDataOrDefault($ele, "back-israndom", tdata.backIsRandom);
                        swapData.backIsInGrid = privMethods.getDataOrDefault($ele, "back-isingrid", tdata.backIsInGrid);
                        swapData.backIsBackgroundImage = privMethods.getDataOrDefault($ele, "back-isbg", tdata.backIsBackgroundImage);
                        swapData.doSwapFront = $.inArray('image', tdata.swapFront) > -1 && (tdata.frontImages instanceof Array) && tdata.frontImages.length > 0;
                        swapData.doSwapBack = $.inArray('image', tdata.swapBack) > -1 && (tdata.backImages instanceof Array) && tdata.backImages.length > 0;
                        swapData.alwaysSwapFront = privMethods.getDataOrDefault($ele, "front-alwaysswap", tdata.alwaysSwapFront);
                        swapData.alwaysSwapBack = privMethods.getDataOrDefault($ele, "back-alwaysswap", tdata.alwaysSwapBack);
                } else {
                        swapData.imageCssSelector = tdata.imageCssSelector;
                        swapData.fadeSwap = tdata.fadeSwap;
                        swapData.frontIsRandom = tdata.frontIsRandom;
                        swapData.frontIsInGrid = tdata.frontIsInGrid;
                        swapData.frontIsBackgroundImage = tdata.frontIsBackgroundImage;
                        swapData.backIsRandom = tdata.backIsRandom;
                        swapData.backIsInGrid = tdata.backIsInGrid;
                        swapData.backIsBackgroundImage = tdata.backIsBackgroundImage;
                        swapData.doSwapFront = $.inArray('image', tdata.swapFront) > -1 && (tdata.frontImages instanceof Array) && tdata.frontImages.length > 0;
                        swapData.doSwapBack = $.inArray('image', tdata.swapBack) > -1 && (tdata.backImages instanceof Array) && tdata.backImages.length > 0;
                        swapData.alwaysSwapFront = tdata.alwaysSwapFront;
                        swapData.alwaysSwapBack = tdata.alwaysSwapBack;
                        }
            if (typeof (tdata.imgSwap) !== "undefined")
                tdata.imgSwap = $.extend(swapData, tdata.imgSwap);
            else
                tdata.imgSwap = swapData;
            if (tdata.imgSwap.doSwapFront) {
                tdata.imgSwap.frontBag = this.prepBag(tdata.imgSwap.frontBag, tdata.frontImages, tdata.imgSwap.prevFrontIndex);
                tdata.imgSwap.frontStaticRndm = tdata.imgSwap.frontBag.pop();
                if (tdata.preloadImages)
                    $(tdata.frontImages).metrojs.preloadImages(function () { });
            }
            if (tdata.imgSwap.doSwapBack) {
                tdata.imgSwap.backBag = this.prepBag(tdata.imgSwap.backBag, tdata.backImages, tdata.imgSwap.prevBackIndex);
                tdata.imgSwap.backStaticRndm = tdata.imgSwap.backBag.pop();
                if (tdata.preloadImages)
                    $(tdata.backImages).metrojs.preloadImages(function () { });
            }
        },
        prepBag: function (bag, content, prevIdx) {
            bag = bag || [];
            var bagCount = 0;
            for (var i = 0; i < content.length; i++) {
                //make sure there's not an immediate repeat
                if (i != prevIdx || content.length === 1) {
                    bag[bagCount] = i;
                    bagCount++;
                }
            }
            return helperMethods.shuffleArray(bag);
        },
        getFrontSwapIndex: function (tdata) {
            var idx = 0;
            if (!tdata.imgSwap.frontIsRandom) {
                idx = tdata.imgSwap.frontIsInGrid ? tdata.imgSwap.frontStaticIndex : tdata.imgSwap.frontIndex;
            } else {
                if (tdata.imgSwap.frontBag.length === 0) {
                    tdata.imgSwap.frontBag = this.prepBag(tdata.imgSwap.frontBag, tdata.frontImages, tdata.imgSwap.prevFrontIndex);
                }
                if (tdata.imgSwap.frontIsInGrid) {
                    idx = tdata.imgSwap.frontStaticRndm;
                } else {
                    idx = tdata.imgSwap.frontBag.pop();
                }
            }
            return idx;
        },
        getBackSwapIndex: function (tdata) {
            var idx = 0;
            if (!tdata.imgSwap.backIsRandom) {
                idx = tdata.imgSwap.backIsInGrid ? tdata.imgSwap.backStaticIndex : tdata.imgSwap.backIndex;                
            } else {
                if (tdata.imgSwap.backBag.length === 0) {
                    tdata.imgSwap.backBag = this.prepBag(tdata.imgSwap.backBag, tdata.backImages, tdata.imgSwap.prevBackIndex);
                }
                if (tdata.imgSwap.backIsInGrid) {
                    idx = tdata.imgSwap.backStaticRndm;                    
                } else {                
                    idx = tdata.imgSwap.backBag.pop();                    
                }
            }            
            return idx;
        },
        setImageProperties: function ($img, image, isBackground) {
            var css = {}, // css object to apply
                attr = {}; // attribute values to apply
            // get image source
            if (typeof (image.src) !== 'undefined') {
                if (!isBackground)
                    attr.src = image.src;
                else
                    css.backgroundImage = "url('" + image.src + "')";
            }
            // get alt text
            if (typeof (image.alt) !== 'undefined')
                attr.alt = image.alt;
            // set css
            if (typeof (image.css) === 'object')
                $img.css($.extend(css, image.css));
            else
                $img.css(css);
            // set attributes
            if (typeof (image.attr) === 'object')
                $img.attr($.extend(attr, image.attr));
            else
                $img.attr(attr);
        },
        action: function (tdata, $front, $back, index) {
                if (!tdata.imgSwap.doSwapFront && !tdata.imgSwap.doSwapBack)
                return;
            var isList = tdata.mode === "flip-list",
                isSlide = tdata.mode == "slide",
                swapIndex = 0,
                isReversed = isList ? tdata.listData[Math.max(index, 0)].isReversed : tdata.isReversed;
            if (isList && index == -1) {
                // flip list completed
                if (tdata.alwaysSwapFront || !isReversed) {
                    if (tdata.imgSwap.doSwapFront) {                        
                        // update the random value for grid mode
                        if (tdata.imgSwap.frontBag.length === 0)
                            tdata.imgSwap.frontBag = this.prepBag(tdata.imgSwap.frontBag, tdata.frontImages, tdata.imgSwap.frontStaticRndm);
                        tdata.imgSwap.frontStaticRndm = tdata.imgSwap.frontBag.pop();
                        // update the static index
                        tdata.imgSwap.frontStaticIndex++;
                        if (tdata.imgSwap.frontStaticIndex >= tdata.frontImages.length)
                            tdata.imgSwap.frontStaticIndex = 0;
                    }
                }
                if (tdata.alwaysSwapBack || isReversed) {                    
                    if (tdata.imgSwap.doSwapBack) {                        
                        // update the random value for grid mode
                        if (tdata.imgSwap.backBag.length === 0)
                            tdata.imgSwap.backBag = this.prepBag(tdata.imgSwap.backBag, tdata.backImages, tdata.imgSwap.backStaticRndm);
                        tdata.imgSwap.backStaticRndm = tdata.imgSwap.backBag.pop();                        
                        // update the static index
                        tdata.imgSwap.backStaticIndex++;
                        if (tdata.imgSwap.backStaticIndex >= tdata.backImages.length)
                            tdata.imgSwap.backStaticIndex = 0;
                    }
                }
                return;
            }
            var $face, // face being swapped
                $img, // image to apply values
                image,// image object to hold properties
                swap; // wrapper for setimageProperties for fade
            if (tdata.alwaysSwapFront || !isReversed) {
                if (!tdata.imgSwap.doSwapFront)
                    return;
                swapIndex = this.getFrontSwapIndex(tdata);
                tdata.imgSwap.prevFrontIndex = swapIndex;
                // slide mode has a static front and back face
                $face = (tdata.mode === "slide") ? $front : $back;
                $img = $face.find(tdata.imgSwap.imageCssSelector);
                image = typeof (tdata.frontImages[swapIndex]) === "object" ? tdata.frontImages[swapIndex] : { src: tdata.frontImages[swapIndex] };
                swap = function (callback) {
                        // set src, alt, css and attribute values
                        var isBg = tdata.imgSwap.frontIsBackgroundImage;
                        if (typeof(callback) == "function") {
                                if (isBg)
                                        window.setTimeout(callback, 100);
                                else
                                        $img[0].onload = callback;
                        }
                        defaultModules.imageSwap.setImageProperties($img, image, isBg);
                       
                };
                if (tdata.fadeSwap) {
                    $img.fadeOut(function () {
                        swap(function () {
                                $img.fadeIn();
                        });
                    });
                } else
                    swap();
                // increment indexes
                tdata.imgSwap.frontIndex++;
                if (tdata.imgSwap.frontIndex >= tdata.frontImages.length)
                    tdata.imgSwap.frontIndex = 0;
                if (!isList) {
                    tdata.imgSwap.frontStaticIndex++;
                    if (tdata.imgSwap.frontStaticIndex >= tdata.frontImages.length)
                        tdata.imgSwap.frontStaticIndex = 0;
                } else {

                }
            }
            if (tdata.alwaysSwapBack || isReversed) {
                if (!tdata.imgSwap.doSwapBack)
                    return;
                // get the new index
                swapIndex = this.getBackSwapIndex(tdata);
                tdata.imgSwap.prevBackIndex = swapIndex;
                // use the $face var for consistency
                $face = $back;
                $img = $face.find(tdata.imgSwap.imageCssSelector);
                image = typeof (tdata.backImages[swapIndex]) === "object" ? tdata.backImages[swapIndex] : { src: tdata.backImages[swapIndex] };
                swap = function () {
                    // set src, alt, css and attribute values
                    defaultModules.imageSwap.setImageProperties($img, image, tdata.imgSwap.backIsBackgroundImage);
                };
                if (tdata.fadeSwap) {
                    $img.fadeOut(function () {
                        swap(function () {
                                $img.fadeIn();
                        });
                    });
                } else
                    swap();
                // increment indexes
                tdata.imgSwap.backIndex++;
                if (tdata.imgSwap.backIndex >= tdata.backImages.length)
                    tdata.imgSwap.backIndex = 0;
                if (!isList) {
                    tdata.imgSwap.backStaticIndex++;
                    if (tdata.imgSwap.backStaticIndex >= tdata.backImages.length)
                        tdata.imgSwap.backStaticIndex = 0;
                } else {

                }
            }
        }
    }
};

// object to maintain timer state
$.fn.metrojs.TileTimer = function (interval, callback, repeatCount) {
    this.timerId = null;                                                        // the id of the current timeout
    this.interval = interval;                                                   // the amount of time to wait between each action call
    this.action = callback;                                                     // the method that is fired on each tick
    this.count = 0;                                                             // the number of times the action has been fired
    this.repeatCount = typeof (repeatCount) === "undefined" ? 0 : repeatCount;   // the number of times the action will be fired        
    // call the action method after a delay and call start | stop based on repeat count
    this.start = function (delay) {
        window.clearTimeout(this.timerId);
        var t = this;
        this.timerId = window.setTimeout(function () {
            t.tick.call(t, interval);
        }, delay);
    };

    this.tick = function (when) {
        this.action(this.count + 1);
        this.count++;
        // reset the loop count
        if (this.count >= MAX_LOOP_COUNT)
            this.count = 0;
        if (this.repeatCount > 0 || this.repeatCount == -1) {
            if (this.count != this.repeatCount) {
                this.start(when);
            } else
                this.stop();
        }
    }
    // clear the timer and reset the count
    this.stop = function () {
        this.timerId = window.clearTimeout(this.timerId);
        this.reset();
    };

    this.resume = function () {
        if (this.repeatCount > 0 || this.repeatCount == -1) {
            if (this.count != this.repeatCount) {
                this.start(interval);
            }
        }
    };

    // clear the timer but leave the count intact
    this.pause = function () {
        this.timerId = window.clearTimeout(this.timerId);
    };

    // reset count
    this.reset = function () {
        this.count = 0;
    };

    // reset count and timer
    this.restart = function (delay) {
        this.stop();
        this.start(delay);
    };
};

Zerion Mini Shell 1.0