"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: "Loading...", // 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$ = $("
    1. "); 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$ = $("
      1. ").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));