metrojs
Version:
Metro JS is a plugin to help make Metro ( also known as flat or modern ) interfaces on the web. Example features include Live Tiles that can fade, flip, slide at custom points or in a carousel. Theme and accent colors are supported along with custom tile
1,170 lines (1,160 loc) • 97.2 kB
JavaScript
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
onHoverOutDelay: 200, // the amount of time in addition to the speed to wait before the onHoverOut 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: [],
rebindMessage: "tile data is missing. Are you missing a call to rebind or destroy? You may also be able to avoid this error by calling stop or pause"
};
// 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),
onHoverOutDelay: this.getDataOrDefault($tile, "hoverout-delay", stgs.onHoverOutDelay),
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 instanceof Array ? tdata.swap : 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
if (sf instanceof Array) {
tdata.swapFront = sf;
} else {
tdata.swapFront = sf === '-' ? swaps : sf.replace(' ', '').split(",");
}
if (sb instanceof Array) {
tdata.swapBack = sb;
} else {
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,
touchStartedOver = false,
touchStartedOut = 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, isTouch) {
isTouch = typeof (isTouch) == "undefined" ? false : isTouch;
if (!isTouch && touchStartedOver) {
return;
}
if (isOver || isPending)
return;
if (data.runEvents) {
isPending = true;
data.eventTimeout = window.setTimeout(function () {
isPending = false;
if (pauseOut)
isOver = true;
touchStartedOver = false;
data.pOnHoverMethods.pause();
}, data.onHoverDelay);
}
},
out: function (e, isTouch) {
isTouch = typeof (isTouch) == "undefined" ? false : isTouch;
if (!isTouch && touchStartedOut == true) {
return;
}
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;
touchStartedOut = 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.PointerEvent || window.MSPointerEvent) { // pointer
var eventPrefix = window.MSPointerEvent ? "MS" : "";
if (pauseIn) {
$tile[0].addEventListener(eventPrefix + 'PointerOver', data.pOnHoverMethods.over, false);
}
if (pauseOut) {
$tile[0].addEventListener(eventPrefix + 'PointerOut', data.pOnHoverMethods.out, false);
}
} else { // touch events
if (pauseIn) {
$tile.bind("mouseover.liveTile", data.pOnHoverMethods.over);
$tile.bind("touchstart.liveTile", function (event) {
touchStartedOver = false;
data.pOnHoverMethods.over.apply($tile[0], [event, true]);
});
}
if (pauseOut) {
$tile.bind("mouseout.liveTile", data.pOnHoverMethods.out);
$tile.bind("touchend.liveTile", function (event) {
touchStartedOut = false;
data.pOnHoverMethods.out.apply($tile[0], [event, true]);
});
}
}
}
})();
},
unbindMsPauseOnHover: function ($tile, data) {
if (typeof (data.pOnHoverMethods) !== "undefined" && (window.PointerEvent || window.MSPointerEvent)) {
var eventPrefix = window.MSPointerEvent ? "MS" : "";
$tile[0].removeEventListener(eventPrefix + 'PointerOver', data.pOnHoverMethods.over, false);
$tile[0].removeEventListener(eventPrefix + 'PointerOut', data.pOnHoverMethods.out, false);
}
},
bindPlayOnHover: function ($tile, data) {
// play the tile immediately when hovered
(function () {
var isOver = false,
isPending = false,
touchStartedOver = false,
touchStartedOut = 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, isTouch) {
isTouch = typeof (isTouch) == "undefined" ? false : isTouch;
if (!isTouch && touchStartedOver) {
return;
}
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]);
touchStartedOver = false;
}, data.onHoverDelay);
}
},
out: function (event, isTouch) {
isTouch = typeof(isTouch) == "undefined" ? false : isTouch;
if (!isTouch && touchStartedOut == true) {
return;
}
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]);
}
touchStartedOut = false;
isOver = false;
}, data.speed + data.onHoverOutDelay);
}
};
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.PointerEvent || window.MSPointerEvent) { // pointer
var eventPrefix = window.MSPointerEvent ? "MS" : "";
if (playIn) {
//$tile[0].addEventListener(eventPrefix + 'PointerDown', data.onHoverMethods.over, false);
$tile[0].addEventListener(eventPrefix + 'PointerEnter', data.onHoverMethods.over, false);
}
// mouseleave gives a more consistent effect than out when the children are transformed
if (playOut)
$tile[0].addEventListener(eventPrefix + 'PointerLeave', data.onHoverMethods.out, false);
} else { // touch events
if (playIn) {
$tile.bind("touchstart.liveTile", function (event) {
touchStartedOver = true;
data.onHoverMethods.over.apply($tile[0], [event, true]);
});
$tile.bind("mouseenter.liveTile", data.onHoverMethods.over);
}
if (playOut) {
$tile.bind("touchend.liveTile,touchcancel.liveTile", function (event) {
touchStartedOut = false;
data.onHoverMethods.out.apply($tile[0], [event, true]);
});
$tile.bind("mouseleave.liveTile", data.onHoverMethods.out);
}
}
}
})();
},
unbindMsPlayOnHover: function ($tile, data) {
if (typeof (data.onHoverMethods) !== "undefined" && (window.PointerEvent || window.MSPointerEvent)) {
var eventPrefix = window.MSPointerEvent ? "MS" : "";
$tile[0].removeEventListener(eventPrefix + 'PointerOver', 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);