/*! * Metro JS for jQuery * http://drewgreenwell.com/ * For details and usage info see: http://drewgreenwell.com/projects/metrojs Copyright (C) 2013, Drew Greenwell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ ;(function ($) { // the metrojs object contains helper methods and theme settings $.fn.metrojs = { capabilities: null, checkCapabilities: function(stgs, recheck){ if($.fn.metrojs.capabilities == null || (typeof(recheck) != "undefined" && recheck == true)) $.fn.metrojs.capabilities = new $.fn.metrojs.MetroModernizr(stgs); return $.fn.metrojs.capabilities; } }; var metrojs = $.fn.metrojs; 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 { image: defaultModules.imageSwap, html: defaultModules.htmlSwap },*/ addContentModule: function (moduleName, module) { if (typeof (this.modules) !== "object") this.modules = {}; this.modules[moduleName] = module; }, hasContentModule: function (moduleName) { if (typeof (moduleName) === "undefined" || typeof (this.modules) !== "object") return false; return (typeof (this.modules[moduleName]) !== "undefined"); } }; // 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 playOnHover: false, // should "play" be called on hover playOnHoverEvent: 'both', // play is called on mousenter, 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 stgs = {}; $.extend(stgs, $.fn.liveTile.defaults, options); // checks for browser feature support to enable hardware acceleration metrojs.checkCapabilities(stgs); helperMethods.getBrowserPrefix(); // setup the default content modules if (!$.fn.liveTile.contentModules.hasContentModule("image")) $.fn.liveTile.contentModules.addContentModule("image", defaultModules.imageSwap); if (!$.fn.liveTile.contentModules.hasContentModule("html")) $.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, stgs); // 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: 'move' }).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; } } 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; 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, tdata; 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, "slide-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), runEvents: false, isReversed: false, loopCount: 0, contentModules: {}, listData: [], height: $tile.height(), width: $tile.width(), tempValues: {} }; } else { tdata = $.extend({ 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 && typeof ($.fn.liveTile.contentModules.modules[swaps[i]]) !== "undefined") { tdata.contentModules[swaps[i]] = $.fn.liveTile.contentModules.modules[swaps[i]]; } } // 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 = {}, module; for (module in tdata.contentModules) $.extend(mergedData, tdata.contentModules[module].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 (module in mergedData.contentModules) { if (typeof (mergedData.contentModules[module].initData) === "function") mergedData.contentModules[module].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 = $('
').appendTo($tile); else // just keep an empty placeholder ret.$back = $(''); 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 = $('').appendTo($tile); else // just keep an empty placeholder ret.$back = $(''); // stack mode if (tdata.stack == true) { var vertical = tdata.direction === "vertical", prop = vertical ? "top" : "left", translate = vertical ? 'translate(0%, -100%) translateZ(0)' : '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); //{ top: tdata.height + 'px', left:'0px' }); } else { tileCss = tdata.useTranslate ? helperMethods.appendStyleProperties({}, ['transform'], ['translate(100%, 0%) translateZ(0)']) : { left: '100%', top: '0%' }; $slide.css(tileCss); //{ left: tdata.width + 'px', top: '0px' }); } } 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); //{ top: -tdata.height + 'px', left:'0px' }); } else { tileCss = tdata.useTranslate ? helperMethods.appendStyleProperties({}, ['transform'], ['translate(-100%, 0%) translateZ(0)']) : { left: '-100%', top: '0%' }; $slide.css(tileCss); //{ left: -tdata.width + 'px', top:'0px' }); } } // 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(""); // 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: 'move' }).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 = $('').appendTo($tile); } else { // just keep an empty placeholder ret.$back = $(''); } $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; data.pOnHoverMethods = { over: function (e) { if (isOver || isPending) return; if (data.runEvents) { isPending = true; data.eventTimeout = window.setTimeout(function () { isPending = false; isOver = true; data.timer.pause(); if (data.mode === "flip-list") { data.faces.$listTiles.each(function (idx, li) { window.clearTimeout($(li).data("metrojs.tile").completeTimeout); }); } }, data.onHoverDelay); } }, out: function (e) { if (isPending) { window.clearTimeout(data.eventTimeout); isPending = false; return; } if (!isOver && !isPending) return; if (data.runEvents) data.timer.start(data.hasRun ? data.delay : data.initDelay); isOver = false; } }; var playFront = (data.playOnHoverEvent == "both" || data.playOnHoverEvent == "mouseover" || data.playOnHoverEvent == "mouseenter"), playBack = (data.playOnHoverEvent == "both" || data.playOnHoverEvent == "mouseout"); if (!metrojs.capabilities.canTouch) { if (playFront) $tile.bind("mouseover.liveTile", data.pOnHoverMethods.over); if (playBack) $tile.bind("mouseout.liveTile", data.pOnHoverMethods.out); } else { if (window.navigator.msPointerEnabled) { // move if (playFront) $tile[0].addEventListener('MSPointerOver', data.pOnHoverMethods.over, false); if (playBack) $tile[0].addEventListener('MSPointerOut', data.pOnHoverMethods.out, false); } else { // touch events if (playFront) $tile.bind("touchstart.liveTile", data.pOnHoverMethods.over); if (playBack) $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; 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; isOver = true; pubMethods["play"].apply($tile[0], [0]); }, data.onHoverDelay); } }, out: function (event) { if (isPending) { window.clearTimeout(data.eventTimeout); isPending = false; return; } 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); } }; $tile.addClass("noselect"); if (!metrojs.capabilities.canTouch) { $tile.bind('mouseenter.liveTile', function (e) { data.onHoverMethods.over(e); }); $tile.bind('mouseleave.liveTile', function (e) { data.onHoverMethods.out(e); }); //$tile.bind("mouseenter.liveTile", over); //$tile.bind("mouseleave.liveTile", out); } else { if (window.navigator.msPointerEnabled) { // move $tile[0].addEventListener('MSPointerDown', data.onHoverMethods.over, false); // mouseleave gives a more consistent effect than out when the children are transformed $tile.bind("mouseleave.liveTile", data.onHoverMethods.out); } else { // touch events $tile.bind("touchstart.liveTile", data.onHoverMethods.over); $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"); (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.canTransform) 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: 'move' }).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; }); } }, 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 for (var module in tdata.contentModules) tdata.contentModules[module].action(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 for (var module in tdata.contentModules) tdata.contentModules[module].action(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(); }); }, 200); }, 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, module; if (!isReversed) { for (module in tdata.contentModules) tdata.contentModules[module].action(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 for (module in tdata.contentModules) tdata.contentModules[module].action(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 () { for (var module in tdata.contentModules) tdata.contentModules[module].action(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 () { for (var module in tdata.contentModules) tdata.contentModules[module].action(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 () { for (var module in tdata.contentModules) tdata.contentModules[module].action(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 = { 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: { 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 }; swapData.frontIsRandom = !tdata.ignoreDataAttributes ? privMethods.getDataOrDefault($ele, "front-israndom", tdata.frontIsRandom) : tdata.frontIsRandom; swapData.frontIsInGrid = !tdata.ignoreDataAttributes ? privMethods.getDataOrDefault($ele, "front-isingrid", tdata.frontIsInGrid) : tdata.frontIsInGrid; swapData.backIsRandom = !tdata.ignoreDataAttributes ? privMethods.getDataOrDefault($ele, "back-israndom", tdata.backIsRandom) : tdata.backIsRandom; swapData.backIsInGrid = !tdata.ignoreDataAttributes ? privMethods.getDataOrDefault($ele, "back-isingrid", tdata.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: { 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 }; swapData.imageCssSelector = !tdata.ignoreDataAttributes ? privMethods.getDataOrDefault($ele, "image-css", tdata.imageCssSelector) : tdata.imageCssSelector; swapData.fadeSwap = !tdata.ignoreDataAttributes ? privMethods.getDataOrDefault($ele, "fadeswap", tdata.fadeSwap) : tdata.fadeSwap; swapData.frontIsRandom = !tdata.ignoreDataAttributes ? privMethods.getDataOrDefault($ele, "front-israndom", tdata.frontIsRandom) : tdata.frontIsRandom; swapData.frontIsInGrid = !tdata.ignoreDataAttributes ? privMethods.getDataOrDefault($ele, "front-isingrid", tdata.frontIsInGrid) : tdata.frontIsInGrid; swapData.frontIsBackgroundImage = !tdata.ignoreDataAttributes ? privMethods.getDataOrDefault($ele, "front-isbg", tdata.frontIsBackgroundImage) : tdata.frontIsBackgroundImage; swapData.backIsRandom = !tdata.ignoreDataAttributes ? privMethods.getDataOrDefault($ele, "back-israndom", tdata.backIsRandom) : tdata.backIsRandom; swapData.backIsInGrid = !tdata.ignoreDataAttributes ? privMethods.getDataOrDefault($ele, "back-isingrid", tdata.backIsInGrid) : tdata.backIsInGrid; swapData.backIsBackgroundImage = !tdata.ignoreDataAttributes ? privMethods.getDataOrDefault($ele, "back-isbg", tdata.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.ignoreDataAttributes ? privMethods.getDataOrDefault($ele, "front-alwaysswap", tdata.alwaysSwapFront) : tdata.alwaysSwapFront; swapData.alwaysSwapBack = !tdata.ignoreDataAttributes ? privMethods.getDataOrDefault($ele, "back-alwaysswap", tdata.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 () { // set src, alt, css and attribute values defaultModules.imageSwap.setImageProperties($img, image, tdata.imgSwap.frontIsBackgroundImage); }; if (tdata.fadeSwap) { $img.fadeOut(function () { swap(); $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(); $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 (interval) { 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(interval); } 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); }; }; jQuery.fn.metrojs.theme = { loadDefaultTheme: function (stgs) { if (typeof (stgs) === "undefined" || stgs == null) { stgs = jQuery.fn.metrojs.theme.defaults; } else { var stg = jQuery.fn.metrojs.theme.defaults; jQuery.extend(stg, stgs); stgs = stg; } //get theme from local storage or set base theme var hasLocalStorage = typeof (window.localStorage) !== "undefined"; var hasKeyAndValue = function (key) { return (typeof (window.localStorage[key]) !== "undefined" && window.localStorage[key] != null); }; if (hasLocalStorage && (!hasKeyAndValue("Metro.JS.AccentColor") || !hasKeyAndValue("Metro.JS.BaseAccentColor"))) { //base theme window.localStorage["Metro.JS.AccentColor"] = stgs.accentColor; window.localStorage["Metro.JS.BaseAccentColor"] = stgs.baseTheme; jQuery(stgs.accentCssSelector).addClass(stgs.accentColor).data("accent", stgs.accentColor); jQuery(stgs.baseThemeCssSelector).addClass(stgs.baseTheme); if (typeof (stgs.loaded) === "function") stgs.loaded(stgs.baseTheme, stgs.accentColor); //preload light theme image if (typeof (stgs.preloadAltBaseTheme) !== "undefined" && stgs.preloadAltBaseTheme) jQuery([(stgs.baseTheme == 'dark') ? stgs.metroLightUrl : stgs.metroDarkUrl]).metrojs.preloadImages(function () { }); } else { if (hasLocalStorage) { stgs.accentColor = window.localStorage["Metro.JS.AccentColor"]; stgs.baseTheme = window.localStorage["Metro.JS.BaseAccentColor"]; jQuery(stgs.accentCssSelector).addClass(stgs.accentColor).data("accent", stgs.accentColor); jQuery(stgs.baseThemeCssSelector).addClass(stgs.baseTheme); if (typeof (stgs.loaded) === "function") stgs.loaded(stgs.baseTheme, stgs.accentColor); } else { jQuery(stgs.accentCssSelector).addClass(stgs.accentColor).data("accent", stgs.accentColor); jQuery(stgs.baseThemeCssSelector).addClass(stgs.baseTheme); if (typeof (stgs.loaded) === "function") stgs.loaded(stgs.baseTheme, stgs.accentColor); //preload light theme image if (typeof (stgs.preloadAltBaseTheme) !== "undefined" && stgs.preloadAltBaseTheme) jQuery([(stgs.baseTheme == 'dark') ? stgs.metroLightUrl : stgs.metroDarkUrl]).metrojs.preloadImages(function () { }); } } }, applyTheme: function (tColor, aColor, stgs) { if (typeof (stgs) === "undefined" || stgs == null) { stgs = jQuery.fn.metrojs.theme.defaults; } else { var stg = jQuery.fn.metrojs.theme.defaults; stgs = jQuery.extend({}, stg, stgs); } if (typeof (tColor) !== "undefined" && tColor != null) { if (typeof (window.localStorage) !== "undefined") { window.localStorage["Metro.JS.BaseAccentColor"] = tColor; } var $theme = jQuery(stgs.baseThemeCssSelector); if ($theme.length > 0) { if (tColor == "dark") $theme.addClass("dark").removeClass("light"); else if (tColor == "light") $theme.addClass("light").removeClass("dark"); } } if (typeof (aColor) !== "undefined" && aColor != null) { if (typeof (window.localStorage) !== "undefined") { window.localStorage["Metro.JS.AccentColor"] = aColor; } var $accent = jQuery(stgs.accentCssSelector); if ($accent.length > 0) { var themeset = false; $accent.each(function () { jQuery(this).addClass(aColor); var dAccent = jQuery(this).data("accent"); if (dAccent != aColor) { var cleanClass = jQuery(this).attr("class").replace(dAccent, ""); cleanClass = cleanClass.replace(/(\s)+/, ' '); jQuery(this).attr("class", cleanClass); jQuery(this).data("accent", aColor); themeset = true; } }); if (themeset && typeof (stgs.accentPicked) === "function") stgs.accentPicked(aColor); } } }, appendAccentColors: function (stgs) { if (typeof (stgs) === "undefined" || stgs == null) { stgs = jQuery.fn.metrojs.theme.defaults; } else { var stg = jQuery.fn.metrojs.theme.defaults; stgs = jQuery.extend({}, stg, stgs); } var themeList = ""; var themes = stgs.accentColors; var template = stgs.accentListTemplate; for (var i = 0; i < themes.length; i++) { themeList += template.replace(/\{0\}/g, themes[i]); } $(themeList).appendTo(stgs.accentListContainer); }, appendBaseThemes: function (stgs) { if (typeof (stgs) === "undefined" || stgs == null) { stgs = jQuery.fn.metrojs.theme.defaults; } else { var stg = jQuery.fn.metrojs.theme.defaults; stgs = jQuery.extend({}, stg, stgs); } var themeList = "", themes = stgs.baseThemes, template = stgs.baseThemeListTemplate; for (var i = 0; i < themes.length; i++) { themeList += template.replace(/\{0\}/g, themes[i]); } $(themeList).appendTo(stgs.baseThemeListContainer); }, // default options for theme defaults: { baseThemeCssSelector: 'body', // selector to place dark or light class after load or selection accentCssSelector: '.tiles', // selector to place accent color class after load or selection accentColor: 'blue', // the default accent color. options are blue, brown, green, lime, magenta, mango, pink, purple, red, teal baseTheme: 'dark', // the default theme color. options are dark, light accentColors: [ 'amber', 'blue', 'brown', 'cobalt', 'crimson', 'cyan', 'magenta', 'lime', 'indigo', 'green', 'emerald', 'mango', 'mauve', 'olive', 'orange', 'pink', 'red', 'sienna', 'steel', 'teal', 'violet', 'yellow' ], baseThemes: [ 'light', 'dark' ], accentListTemplate: "