"use strict";
void(Date.now || (Date.now = function() { return new Date().getTime(); }));
void(Date.getDateOrdinal || (Date.getDateOrdinal = function(d) { var ord = "th"; if (d <= 10 || d > 20) ord = (["th","st","nd","rd"])[d % 10] || "th"; return d + ord; }));
void(Date.prototype.getDateOrdinal || (Date.prototype.getDateOrdinal = function() { return Date.getDateOrdinal(this.getDate()); }));
(function($) {
$(function() {
var Constants = {
API_TIMEOUT: 45e3,
TAB_DATA_TTL: 60e3, // how long does show data for a tab live before it is refetched with an API call, (0 to Infinity)
MIN_LATEST_SHOWS: 15, // minimum number of shows to grab for LATEST tab
OLDEST_LATEST_SHOWS_BEYOND_MIN: 1000 * 60 * 60 * 48, // once minium shows are reached, how far into the past to get latest shows beyond MIN
LOADING_SHOWS_CONTENT: "
", //
NO_SHOWS_CONTENT: "
Nothing Yet. Check back soon.
", // message displayed when a shows tab is empty
TIMEOUT_SHOWS_CONTENT: "Request timed out. Please try again.
", // message to display when shows loading times out
PLACEHOLDER_IMAGE: "/images/homepage/fashion-season-promo/placeholder.jpg",
SCHEDULE_UPDATE_INTERVAL: 60e3,
SCHEDULE_KEEP_TIME: 45 * 60e3,
SHOW_PREVIEW_CONTAINER_OFFSET: 10, // how many pixels to offset the show preview from the list of shows
SHOW_PREVIEW_IMAGES: 1, // how many images are fetched and displayed for a show preview animation (mouseover)
SHOW_HOVER_DATA_FETCH_DELAY: 150, // how long to wait on mouseover before fetching show preview data (to prevent fast scrolling from generating tons of requests)
SHOW_PREVIEW_IMAGE_RENDITION: "GRID", // which image rendition to use in the show preview
SHOW_PREVIEW_LOOPS: 1, // how many times does the show preview loop (repeat) (0 to Infinity)
SHOW_PREVIEW_SECOND_LOOK_DELAY: 1e3, // after the first look is displayed in the show preview look, how long to wait before showing the second look
SHOW_PREVIEW_INTER_LOOK_DELAY: 667, // after the second look, how long to wait between looks
SHOW_PREVIEW_END_ENABLED: false, // whether to show an end message after the last show preview look
SHOW_PREVIEW_END_MESSAGE: "Click The Designer To See The " + // message displayed after the last show preview slide loop
"Full Collection →",
INSTANT_IMAGE_RENDITION: "REVIEW", // which image rendition to use in the instant section
INSTANT_IMAGE_FAILSAFE: "/images/homepage/fashion-season-promo/placeholder.jpg"
};
var root$ = $(document.documentElement);
var body$ = root$.children("body");
var canvas$ = $("#canvas");
var content$ = $("#content");
var placeholder$ = $("
").attr("src", Constants.PLACEHOLDER_IMAGE);
content$.on("click", "a.instant", function(e) {
e.preventDefault();
window.open($(this).attr("href"), "slideshow" + Math.round(Math.random() * 1e10), "directories=0,titlebar=0,toolbar=0,location=0,status=0,menubar=0,scrollbars=yes,resizable=no,width=1024,height=740");
});
$(".fashion-season-promo").each(SeasonPromo);
function SeasonPromo() {
var fashionSeasonPromo$ = $(this);
var flags = (fashionSeasonPromo$.attr("data-flags") || "").split(" ");
var instantTag$ = $("", { "class": "instant" }).html("instant");
var showList$ = $("
");
var showsContent$ = fashionSeasonPromo$.find(".container > .shows-content");
var listBlocks$ = showsContent$.children("div[data-content]");
var showPreviewImageCache = { };
var currentInstant = null;
start();
function start() {
var activateMap = (function() {
function latest(data) {
data.sort(function(a, b) {
return b.eventDateUTC - a.eventDateUTC;
});
var now = Date.now();
for (var i = Constants.MIN_LATEST_SHOWS; i < data.length; ++i) {
if (now - data[i].eventDateUTC > Constants.OLDEST_LATEST_SHOWS_BEYOND_MIN)
break;
}
data.length = Math.min(data.length, i);
}
function dateSorted(data) {
data.sort(function(a, b) {
return b.eventDateUTC - a.eventDateUTC;
});
}
return {
"latest-shows": function(seasonCode) {
//loadShows(getListBlock("latest-shows"), "/rest/fashion-show/latest?number=20", "latest-shows", true);
loadShows(getListBlock("latest-shows"), "/rest/fashion-show/" + seasonUrl(seasonCode) + "/collections/", "latest-shows", true, latest);
},
"all-shows": function(seasonCode) {
loadShows(getListBlock("all-shows"), "/rest/fashion-show/" + seasonUrl(seasonCode) + "/collections/", "all-shows", true);
},
"menswear": function(seasonCode) {
loadShows(getListBlock("menswear"), "/rest/fashion-show/" + seasonUrl(seasonCode) + "/collections/", "menswear", true);
},
"videos": function(seasonCode) {
loadShows(getListBlock("videos"), "/rest/fashion-show/" + seasonUrl(seasonCode) + "/collections/", "videos", false, function(data) {
data.sort(function(a, b) {
return b.hasVideo - a.hasVideo;
});
for (var i = 0; i < data.length; ++i) {
if (data[i].hasVideo === false)
break;
}
data.length = Math.min(data.length, i);
});
}
};
})();
// This stops mousewheel scrolling on the list blocks from bubbling up and causing the entire webpage to scroll.
// E.g., if you're looking for 'Zac Posen', and you swipe very quickly to get to the end of the list, under
// normal conditions, you will reach the end of the list and still have so much 'momentum' that the browser
// will keep scrolling well past the end of the list, which is very annoying.
showsContent$.on("mousewheel", "div", function(e) {
if (this.scrollHeight <= this.offsetHeight)
return;
if (e.originalEvent.wheelDelta < 0) {
if (this.scrollTop + this.offsetHeight >= this.scrollHeight)
e.preventDefault();
}
else {
if (this.scrollTop <= 0)
e.preventDefault();
}
});
fashionSeasonPromo$.find(".shows-content > nav > ol > li").on("click", function() {
$(this).trigger("select");
}).on("select", function() {
var tab$ = $(this);
//if (tab$.hasClass("selected")) return;
tab$.siblings().removeClass("selected").end().addClass("selected");
var activateName = tab$.attr("data-activate");
if (typeof activateMap[activateName] === "function") {
activateMap[activateName](tab$.attr("data-season"));
}
else {
listBlocks$.stop().hide().filter("[data-content=\"" + activateName + "\"]").fadeIn("medium");
}
}).filter("[data-default]").trigger("select");
if (flags.indexOf("instant") >= 0)
getInstant();
if (flags.indexOf("calendar") >= 0)
getSchedule();
}
function seasonUrl(seasonCode) {
var match = /([fs])?(\d{4})(rtw|ctr|men|rst|pf)/.exec(seasonCode.toLowerCase());
var season = "";
if (Boolean(match[1]))
season += { "s": "spring", "f": "fall" }[match[1]] + "-";
season += match[2] + "-";
season += { "rtw": "ready-to-wear", "ctr": "couture", "men": "menswear", "rst": "resort", "pf": "pre-fall" }[match[3]];
return season;
}
function getListBlock(content) {
var listBlock$ = listBlocks$.filter("[data-content=\"" + content + "\"]");
if (listBlock$.length === 0) {
listBlock$ = $("").hide().attr("data-content", content).appendTo(showsContent$);
listBlocks$ = listBlocks$.add(listBlock$);
}
return listBlock$;
}
function loadShows(listBlock$, apiCall, seasonDataName, showPreview, dataFilter) {
showPreview = Boolean(showPreview);
dataFilter = dataFilter || $.noop;
var seasonData = fashionSeasonPromo$.data(seasonDataName);
listBlocks$.stop().hide(); /*.html(Constants.LOADING_SHOWS_CONTENT); */
listBlock$.html(Constants.LOADING_SHOWS_CONTENT);
if (typeof seasonData === "object" && Date.now() - seasonData.timestamp < Constants.TAB_DATA_TTL) {
processShows(listBlock$, seasonData, showPreview);
}
else {
if (!showsContent$.hasClass("loading-" + seasonDataName)) {
showsContent$.addClass("loading-" + seasonDataName);
$.ajax({
url: apiCall,
//cache: false,
/*
data: {
"_": String(o(Date.now() / 30e3) * 30e3)
},
*/
dataType: "json",
timeout: Constants.API_TIMEOUT,
success: function(data) {
dataFilter(data);
if (data.length > 0) {
fashionSeasonPromo$.data(seasonDataName, data);
processShows(listBlock$, data, showPreview);
}
else {
noShows(listBlock$, Constants.NO_SHOWS_CONTENT);
}
},
error: function() {
noShows(listBlock$, Constants.TIMEOUT_SHOWS_CONTENT);
},
complete: function() {
showsContent$.removeClass("loading-" + seasonDataName);
}
});
}
}
listBlock$.fadeIn("medium");
}
function noShows(listBlock$, message) {
listBlock$.empty().html(message);
}
function processShows(listBlock$, shows, preview) {
preview = Boolean(preview);
shows.timestamp = Date.now();
listBlock$.empty();
var list$ = $("
");
list$.appendTo(listBlock$);
var showPreview$ = $("", { "class": "show-preview" });
if (fashionSeasonPromo$.attr("data-preview-class"))
showPreview$.addClass(fashionSeasonPromo$.attr("data-preview-class"));
var showPreviewContainer$ = canvas$;
var showPreviewDelayTimer = null;
var showPreviewDataRequest = null;
var showPreviewTimer = null;
$.each(shows, function(index, show) {
var show$ = $("");
show$.data("show", show);
var reviewLink$ = $("").html(this.designerName);
reviewLink$.appendTo(show$);
if (this.live) {
show$.append(instantTag$.clone());
var designerCode = this.designerCode;
var seasonCode = this.seasonCode;
var href = "/fashionshows/complete/slideshow/" + seasonCode + "-" + designerCode;
reviewLink$.attr("href", href).addClass("instant");
}
else {
reviewLink$.attr("href", this.reviewUrl);
}
show$.appendTo(list$);
if (preview) {
reviewLink$.hover(function() {
over.call(show$, show);
}, function() {
out.call(show$);
});
}
});
// this is triggered asynchronous by user interaction, and it has side effects on the DOM, so, to ensure program integrity, it is synchronized as an atomic operation.
function addShowPreview(content$, align) {
fashionSeasonPromo$.queue(function(next) {
showPreview$.empty().css("visibility", "hidden").append(content$);
showPreviewContainer$.append(showPreview$);
alignShowPreview(align);
next();
});
}
// this is triggered asynchronous by user interaction, and it has side effects on the DOM, so, to ensure program integrity, it is synchronized as an atomic operation.
function removeShowPreview() {
fashionSeasonPromo$.queue(function(next) {
showPreview$.empty().detach();
next();
});
}
function alignShowPreview(align) {
var containerOffset = showPreviewContainer$.offset();
var top =
Math.min(
Math.max(
align.top - showPreview$.outerHeight() / 2,
showsContent$.offset().top
),
showsContent$.offset().top + showsContent$.outerHeight() - showPreview$.outerHeight()
)
- containerOffset.top;
var left = align.left - showPreview$.outerWidth() - Constants.SHOW_PREVIEW_CONTAINER_OFFSET - containerOffset.left;
showPreview$.css({
"top": top + "px",
"left": left + "px",
"visibility": "visible"
});
}
function addCachedPreviewImage(show, url, image) {
var season = showPreviewImageCache[show.seasonCode];
if (!Boolean(season))
season = showPreviewImageCache[show.seasonCode] = { };
var designer = season[show.designerCode];
if (!Boolean(designer))
designer = season[show.designerCode] = { };
designer[url] = image;
}
function getCachedPreviewImage(show, url) {
var season = showPreviewImageCache[show.seasonCode];
if (!Boolean(season))
return null;
var designer = season[show.designerCode];
if (!Boolean(designer))
return null;
return designer[url] || null;
}
// triggered by asynchronous user interaction, so processing is queued to ensure program integrity
function over(show) {
var show$ = $(this);
fashionSeasonPromo$.queue(function(next) {
stop();
var align = show$.offset();
align.top += show$.height() / 2;
var showPreviewContainer$ = $("");
showPreviewDelayTimer = setTimeout(function() {
addShowPreview(showPreviewContainer$, align);
showPreviewDelayTimer = null;
var previewData = show$.data("preview-data");
var isLive = show$.data("show").live;
// live (instant) shows ignore cached data and always refetch
if (previewData && !isLive) {
process(previewData);
}
else {
var requestData = null;
/*
if (isLive) {
requestData = {
"_": String(~~(Date.now() / 30e3) * 30e3)
};
}
*/
if (Constants.SHOW_PREVIEW_IMAGES < Infinity) {
requestData = requestData || {};
requestData["start"] = 0;
requestData["end"] = Constants.SHOW_PREVIEW_IMAGES;
}
showPreviewDataRequest = $.ajax({
url: "/rest/fashion-show/" + show.seasonUrlName + "/" + show.designerUrlName + "/collection/looks/",
//cache: !isLive,
data: requestData,
dataType: "json",
timeout: Constants.API_TIMEOUT,
success: function(previewData) {
show$.data("preview-data", previewData);
process(previewData);
},
error: function() {
showPreviewContainer$.queue(function(next) {
placeholder$.clone().appendTo(showPreviewContainer$);
next();
});
}
});
}
}, Constants.SHOW_HOVER_DATA_FETCH_DELAY);
next();
function process(previewData) {
if (previewData.length < 1)
return;
var currentSlide = 0;
var loops = 0;
updateSlide();
function updateSlide() {
var startTime = Date.now();
var thumbUrl = null;
for (var i = 0; i < previewData[currentSlide].runwaySlide.images.length; ++i) {
var image = previewData[currentSlide].runwaySlide.images[i];
thumbUrl = image.url;
if (image.renditionGrid === Constants.SHOW_PREVIEW_IMAGE_RENDITION)
break;
}
if (thumbUrl != null) {
var thumb = getCachedPreviewImage(show, thumbUrl);
if (thumb !== null) {
showNextImage($(thumb));
alignShowPreview(align);
delayOrStop();
}
else {
$("
").one("load", function() {
addCachedPreviewImage(show, thumbUrl, this);
showNextImage($(this));
alignShowPreview(align);
}).one("error", function() {
showNextImage(placeholder$.clone());
alignShowPreview(align);
}).one("load error", delayOrStop).attr("src", thumbUrl);
}
}
function showNextImage(image$) {
showPreviewContainer$.queue(function(next) {
if (currentSlide > 0 || loops > 0) {
image$.css("z-index", 2048).hide();
}
image$.appendTo(showPreviewContainer$);
if (currentSlide > 0 || loops > 0) {
image$.fadeIn("fast", function() {
$(this).css("z-index", "").siblings("img").remove();
next();
});
}
else
next();
});
}
function delayOrStop() {
// if the container is no longer attached to the DOM, then updating was interrupted (mouse off) while an image was asynchronously loading from the network, so we stop.
if (!showPreviewContainer$.parents(":last").is(document.documentElement))
return;
var elapsedTime = new Date() - startTime;
var delay = Math.max(0, ((currentSlide === 0 && loops === 0) ? Constants.SHOW_PREVIEW_SECOND_LOOK_DELAY : Constants.SHOW_PREVIEW_INTER_LOOK_DELAY) - elapsedTime);
++currentSlide;
if (currentSlide >= previewData.length) {
++loops;
if (loops < Constants.SHOW_PREVIEW_LOOPS) {
currentSlide = 0;
}
}
if (currentSlide < previewData.length) {
showPreviewTimer = setTimeout(updateSlide, delay);
}
else {
if (Constants.SHOW_PREVIEW_END_ENABLED) {
showPreviewContainer$.queue(function(next) {
$("").html(Constants.SHOW_PREVIEW_END_MESSAGE).hide().appendTo(showPreviewContainer$).delay(Constants.SHOW_PREVIEW_INTER_LOOK_DELAY).fadeIn("fast");
next();
});
}
}
}
}
}
});
}
// triggered by asynchronous user interaction, so processing is queued to ensure program integrity
function out() {
fashionSeasonPromo$.queue(function(next) {
stop();
next();
});
}
function stop() {
if (showPreviewDelayTimer !== null) {
clearTimeout(showPreviewDelayTimer);
showPreviewDelayTimer = null;
}
if (showPreviewDataRequest !== null) {
showPreviewDataRequest.abort();
showPreviewDataRequest = null;
}
if (showPreviewTimer !== null) {
clearTimeout(showPreviewTimer);
showPreviewTimer = null;
}
removeShowPreview();
}
}
function getSchedule() {
$.ajax({
url: "/rest/fashion-show/" + seasonUrl(fashionSeasonPromo$.attr("data-season")) + "/collections/calendar/",
dataType: "json",
timeout: Constants.API_TIMEOUT
}).done(process);
function process(data) {
filter();
if (data.length) {
make();
}
function filter() {
data.sort(function(a, b) {
return a.calendar.date - b.calendar.date;
});
var now = Date.now();
var i = 0;
while (true) {
if (i >= data.length)
break;
if (!data[i].calendar.instant)
data.splice(i, 1);
else if (new Date(data[i].calendar.date) < now - Constants.SCHEDULE_KEEP_TIME)
data.splice(i, 1);
else
++i;
}
}
function make() {
fashionSeasonPromo$.data("schedule", data).addClass("instant-schedule");
var schedule$ = $("", { "class": "schedule" });
var hed$ = $("").html("next instant show");
var cta$ = $("").attr("href", "/fashionshows/#calendar").html("view the complete calendar").prepend(
$("", { "class": "Icons" }).html("")
).wrap("").parent()
var list$ = $("
");
var prev$ = $("", { "class": "prev Icons", "unselectable": "on" }).html("❬").on("selectstart", false);
var next$ = $("", { "class": "next Icons", "unselectable": "on" }).html("❭").on("selectstart", false);
$.each(data, function() {
var item$ = $("").hide();
var label$ = $("", { "class": "label" });
var time$ = $("", { "class": "time" });
label$.html(this.designerName);
var time = new Date(this.calendar.date);
var day = (["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"])[time.getDay()];
var month = (["January","February","March","April","May","June","July","August","September","October","November","December"])[time.getMonth()];
var date = String(time.getDate());
var hours = String(time.getHours() % 12 || 12);
var minutes = String(time.getMinutes());
if (minutes.length < 2)
minutes = "0" + minutes;
var meridian = (["am","pm"])[Math.floor(time.getHours() / 12)];
time$.html(day + " " + month + " " + date + " at " + hours + ":" + minutes + " " + meridian);
item$.append(label$).append(time$).appendTo(list$);
});
var items$ = list$.children("li");
if (items$.length <= 1)
next$.hide();
prev$.hide();
items$.first().addClass("active").css("display", "table-cell");
prev$.on("click", function() {
var currentItem$ = items$.filter(".active");
var prevItem$ = currentItem$.prev("li");
currentItem$.removeClass("active").hide();
prevItem$.addClass("active").css({
"display": "table-cell",
"opacity": 0
}).animate({
"opacity": 1
}, "medium");
next$.show();
if (!prevItem$.prev("li").length)
prev$.hide();
});
next$.on("click", function() {
var currentItem$ = items$.filter(".active");
var nextItem$ = currentItem$.next("li");
currentItem$.removeClass("active").hide();
nextItem$.addClass("active").css({
"display": "table-cell",
"opacity": 0
}).animate({
"opacity": 1
}, "medium");
prev$.show();
if (!nextItem$.next("li").length)
next$.hide();
});
list$.append(prev$).append(next$);
schedule$.append(hed$).append(list$).append(cta$).hide().appendTo(showsContent$).delay(750).fadeIn("medium");
}
}
}
function getInstant() {
var season = seasonUrl(fashionSeasonPromo$.attr("data-season"));
$.ajax({
//url: "/rest/fashion-show/" + season + "/collections/",
url: "/rest/fashion-show/latest/",
data: {
"number": 20
},
dataType: "json",
timeout: Constants.API_TIMEOUT,
}).done(function(data) {
var i = 0;
while (i < data.length) {
if (! data[i].live) {
data.splice(i, 1);
}
else {
var delta = Date.now() - (data[i].eventDateUTC); // assume 5-hour UTC bug
if (delta >= 45 * 60 * 1e3) {
data.splice(i, 1);
}
else {
++i;
}
}
}
data.sort(function(a, b) { return b.eventDateUTC - a.eventDateUTC; });
if (data.length)
makeInstant(data[0]);
});
}
function makeInstant(instant) {
$.ajax({
url: "/rest/fashion-show/" + instant.seasonUrlName + "/" + instant.designerUrlName + "/collection/looks/",
dataType: "json",
timeout: Constants.API_TIMEOUT
}).done(function(data) {
if (data.length < 3)
return;
data.length = 3;
var instant$ = $("", { "id": "instant" }).css("display", "block");
var wrapper$ = $("");
$.each(data, function() {
var rendition = null;
$.each(this.runwaySlide.images, function() {
if (this.renditionGrid === Constants.INSTANT_IMAGE_RENDITION) {
rendition = this.url;
return false;
}
});
if (!rendition)
rendition = Constants.INSTANT_IMAGE_FAILSAFE;
$("
").css("opacity", 0).on("error", function() {
$(this).attr("src", Constants.INSTANT_IMAGE_FAILSAFE);
}).on("load", function() {
var img$ = $(this);
instant$.queue(function(next) {
img$.animate({
"opacity": 1
}, "medium");
next();
});
}).appendTo(wrapper$).attr("src", rendition);
});
var hed$ = $("");
hed$.html("Instant");
wrapper$.append(hed$);
$("").html("show in progress").wrap("").parent().appendTo(wrapper$);
$("", { "class": "label" }).html(instant.designerName).wrap("").parent().appendTo(wrapper$);
$("").html("view now").wrap("").parent().appendTo(wrapper$);
$("", { "href": "/fashionshows/complete/slideshow/" + instant.seasonCode + "-" + instant.designerCode, "class": "instant" }).append(wrapper$).appendTo(instant$);
$("", { "class": "close Icons" }).html("✖").on("click", function(e) {
e.stopPropagation();
instant$.slideUp("slow", function() {
instant$.remove();
});
}).appendTo(instant$);
instant$.hide().prependTo(content$).slideDown("slow");
});
}
}
});
}(jQuery));