jam-cloud/web/app/assets/javascripts/layout.js

1559 lines
45 KiB
JavaScript

/*
* View framework for JamKazam.
*
* Processes proprietary attributes in markup to convert a set of HTML elements
* into the JamKazam screen layout. This module is only responsible for size
* and position. All other visual aspects should be elsewhere.
*
* See the layout-example.html file for a simple working example.
*/
(function (context, $) {
"use strict";
context.JK = context.JK || {};
// Static function to hide the 'curtain' which hides the underlying
// stuff until we can get it laid out. Called from both the main
// client as well as the landing page.
context.JK.hideCurtain = function (duration) {
context.setTimeout(function () {
$(".curtain").fadeOut(2 * duration);
}, duration);
};
context.JK.Layout = function () {
// privates
var logger = context.JK.logger;
var EVENTS = context.JK.EVENTS;
var NOT_HANDLED = "not handled";
var me = null; // Reference to this instance for context sanity.
var opts = {
headerHeight: 75,
sidebarWidth: 300,
notifyHeight: 150,
notifyGutter: 10,
collapsedSidebar: 30,
panelHeaderHeight: 36,
alwaysOpenPanelHeaderHeight: 78, // for the search bar
gutter: 60, // Margin around the whole UI
screenMargin: 0, // Margin around screens (not headers/sidebar)
gridOuterMargin: 6, // Outer margin on Grids (added to screenMargin if screen)
gridPadding: 8, // Padding around grid cells. Added to outer margin.
animationDuration: 400,
allowBodyOverflow: false, // Allow tests to disable the body-no-scroll policy
sizeOverlayToContent: false, // if true, use the size of <body> tag to decide overlay size everytime overlay is shown. should be used in non-client settings
};
var width = $(context).width();
var height = $(context).height();
var resizing = null;
var sidebarVisible = true;
var expandedPanel = null;
var previousScreen = null;
var currentScreen = null;
var currentHash = null;
var screenBindings = {};
var dialogBindings = {};
var wizardShowFunctions = {};
var openDialogs = []; // FIFO stack
var resettingHash = false;
function setup() {
requiredStyles();
hideAll();
setInitialExpandedSidebarPanel();
sizeScreens(width, height, '[layout="screen"]', true);
positionOffscreens(width, height);
$('[layout="sidebar"]').show();
$('[layout="panel"]').show();
layout();
}
function setInitialExpandedSidebarPanel() {
if (gon.global.chat_opened_by_default) {
expandedPanel = "panelChat";
} else {
expandedPanel = "panelFriends";
}
}
function layout() {
width = $(context).width();
height = $(context).height();
// TODO
// Work on naming. File is layout, class is Layout, this method
// is layout and every other method starts with 'layoutX'. Perhaps
// a little redundant?
layoutCurtain(width, height);
layoutDialogOverlay(width, height);
layoutScreens(width, height);
layoutSidebar(width, height);
layoutHeader(width, height);
layoutNotify1(width, height);
layoutNotify2(width, height);
layoutNotify3(width, height);
layoutFooter(width, height);
$(document).triggerHandler("layout_resized");
}
function layoutCurtain(screenWidth, screenHeight) {
var curtainStyle = {
position: "absolute",
width: screenWidth + "px",
height: screenHeight + "px",
};
$(".curtain").css(curtainStyle);
}
function layoutDialogOverlay(screenWidth, screenHeight) {
var style = {
position: "absolute",
width: screenWidth + "px",
height: screenHeight + "px",
};
$(".dialog-overlay").css(style);
}
function layoutScreens(screenWidth, screenHeight) {
var previousScreenSelector = '[layout-id="' + previousScreen + '"]';
var currentScreenSelector = '[layout-id="' + currentScreen + '"]';
$(currentScreenSelector).show();
var width = screenWidth - (2 * opts.gutter + 2 * opts.screenMargin);
var left = -1 * width - 100;
if (currentScreenSelector === previousScreenSelector) {
left = $(currentScreenSelector).css("left");
if (left) {
left = left.split("px")[0];
}
}
$(previousScreenSelector).animate(
{ left: left },
{ duration: opts.animationDuration, queue: false }
);
sizeScreens(screenWidth, screenHeight, '[layout="screen"]');
positionOffscreens(screenWidth, screenHeight);
positionOnscreen(screenWidth, screenHeight);
}
function sizeScreens(screenWidth, screenHeight, selector, immediate) {
var duration = opts.animationDuration;
if (immediate) {
duration = 0;
}
var width = screenWidth - (2 * opts.gutter + 2 * opts.screenMargin);
if (sidebarVisible) {
width -= opts.sidebarWidth + 2 * opts.gridPadding;
} else {
width -= opts.collapsedSidebar + 2 * opts.gridPadding;
width += opts.gutter; // Add back in the right gutter width.
}
var height =
screenHeight -
opts.headerHeight -
(2 * opts.gutter + 2 * opts.screenMargin);
var css = {
width: width,
height: height,
};
var $screens = $(selector);
$screens.animate(css, { duration: duration, queue: false });
layoutHomeScreen(width, height);
}
/**
* Postition all screens that are not the current screen.
*/
function positionOffscreens(screenWidth, screenHeight) {
var top = opts.headerHeight + opts.gutter + opts.screenMargin;
var left = -1 * (screenWidth + 2 * opts.gutter);
var $screens = $('[layout="screen"]').not(
'[layout-id="' + currentScreen + '"]'
);
$screens.css({
top: top,
left: left,
});
}
/**
* Position the current screen
*/
function positionOnscreen(screenWidth, screenHeight, immediate) {
var duration = opts.animationDuration;
if (immediate) {
duration = 0;
}
var top = opts.headerHeight + opts.gutter + opts.screenMargin;
var left = opts.gutter + opts.screenMargin;
var $screen = $('[layout-id="' + currentScreen + '"]');
$screen.animate(
{
top: top,
left: left,
overflow: "auto",
},
duration
);
}
function layoutHomeScreen(homeScreenWidth, homeScreenHeight) {
var $grid = $("[layout-grid]");
var gridWidth = homeScreenWidth;
var gridHeight = homeScreenHeight;
$grid.css({ width: gridWidth, height: gridHeight });
var layout = $grid.attr("layout-grid");
if (!layout) return;
var gridRows = layout.split("x")[0];
var gridCols = layout.split("x")[1];
var gutterWidth = 0;
var findCardLayout;
var feedCardLayout;
$grid.find(".homecard").each(function () {
var childPosition = $(this).attr("layout-grid-position");
var childRow = childPosition.split(",")[1];
var childCol = childPosition.split(",")[0];
var childRowspan = $(this).attr("layout-grid-rows");
var childColspan = $(this).attr("layout-grid-columns");
var childLayout = me.getCardLayout(
gridWidth,
gridHeight,
gridRows,
gridCols,
childRow,
childCol,
childRowspan,
childColspan
);
if ($(this).is(".jamtrack")) {
feedCardLayout = childLayout;
} else if ($(this).is(".findsession")) {
findCardLayout = childLayout;
}
$(this).animate(
{
width: childLayout.width,
height: childLayout.height,
top: childLayout.top,
left: childLayout.left,
},
opts.animationDuration
);
});
var broadcastWidth = findCardLayout.width + feedCardLayout.width; //+ opts.gridPadding * 2;
//layoutBroadcast(broadcastWidth, findCardLayout.left);
layoutTopMessage(broadcastWidth, findCardLayout.left);
}
/**
function layoutBroadcast(width, left) {
var css = {
width:width + opts.gridPadding * 2,
left:left
}
$('[data-react-class="BroadcastHolder"]').animate(css, opts.animationDuration)
}*/
function layoutTopMessage(width, left) {
var css = {
width: width + opts.gridPadding * 2,
left: left,
};
$('[data-react-class="TopMessageHolder"]').animate(
css,
opts.animationDuration
);
}
function layoutSidebar(screenWidth, screenHeight) {
var width = opts.sidebarWidth;
var expanderHeight = $("[layout-sidebar-expander]").height();
var height =
screenHeight - opts.headerHeight - 2 * opts.gutter + expanderHeight;
var right = opts.gutter;
if (!sidebarVisible) {
// Negative right to hide most of sidebar
right = 0 - opts.sidebarWidth + opts.collapsedSidebar;
}
var top = opts.headerHeight + opts.gutter - expanderHeight;
var css = {
width: width,
height: height,
top: top,
right: right,
};
$('[layout="sidebar"]').animate(css, opts.animationDuration);
layoutPanels(width, height);
if (sidebarVisible) {
$('[layout-panel="collapsed"]').hide();
$('[layout-panel="expanded"]').show();
$('[layout-sidebar-expander="hidden"]').hide();
$('[layout-sidebar-expander="visible"]').show();
} else {
$('[layout-panel="collapsed"]').show();
$('[layout-panel="expanded"]').hide();
$('[layout-sidebar-expander="hidden"]').show();
$('[layout-sidebar-expander="visible"]').hide();
}
}
function layoutPanels(sidebarWidth, sidebarHeight) {
// TODO - don't like the accordian - poor usability. Requires longest mouse
// reach when switching panels. Probably better to do tabs.
if (!sidebarVisible) {
return;
}
var $expandedPanel = $('[layout-id="' + expandedPanel + '"]');
var $expandedPanelContents = $expandedPanel.find(
'[layout-panel="contents"]'
);
var combinedHeaderHeight =
($('[layout-panel="contents"]').length - 1) * opts.panelHeaderHeight +
opts.alwaysOpenPanelHeaderHeight;
var expanderHeight = $("[layout-sidebar-expander]").height();
var expandedPanelHeight =
sidebarHeight - (combinedHeaderHeight + expanderHeight);
$('[layout-panel="contents"]').hide();
$('[layout-panel="contents"]').css({ height: "1px" });
$expandedPanelContents.show();
$expandedPanel.triggerHandler("open");
$expandedPanelContents.animate(
{ height: expandedPanelHeight + "px" },
opts.animationDuration,
function () {
$expandedPanel.triggerHandler("fullyOpen");
}
);
}
function layoutHeader(screenWidth, screenHeight) {
var width = screenWidth - 2 * opts.gutter;
var height = opts.headerHeight - opts.gutter;
var top = opts.gutter;
var left = opts.gutter;
var css = {
width: width + "px",
height: height + "px",
top: top + "px",
left: left + "px",
};
$('[layout="header"]').css(css);
}
function layoutNotify1(screenWidth, screenHeight) {
var $notify1 = $('[layout="notify1"]');
//var nHeight = $notify1.height();
var notifyStyle = {
bottom: "0px",
position: "fixed",
padding: "20px",
};
$notify1.css(notifyStyle);
}
function layoutNotify2(screenWidth, screenHeight) {
var $notify1 = $('[layout="notify1"]');
var $notify2 = $('[layout="notify2"]');
var nHeight = $notify1.height();
var notifyStyle = {
bottom: (nHeight + 41).toString() + "px",
position: "fixed",
padding: "20px",
};
$notify2.css(notifyStyle);
}
function layoutNotify3(screenWidth, screenHeight) {
var $notify1 = $('[layout="notify1"]');
var $notify2 = $('[layout="notify2"]');
var $notify3 = $('[layout="notify3"]');
var nHeight1 = $notify1.height();
var nHeight2 = $notify2.height();
var notifyStyle = {
bottom: (nHeight1 + nHeight2 + 41 + 41).toString() + "px",
position: "fixed",
padding: "20px",
};
$notify3.css(notifyStyle);
}
function layoutFooter(screenWidth, screenHeight) {
if (!opts.layoutFooter) {
return;
}
var $footer = $("#footer");
$footer.show();
var nHeight = $footer.height();
var footerStyle = {
top: screenHeight - 80 + "px",
};
var width = screenWidth - (2 * opts.gutter + 2 * opts.screenMargin);
var left = -1 * width - 100;
$footer.animate(
{ left: opts.gutter, width: width, top: screenHeight - 78 + "px" },
opts.animationDuration
);
}
function requiredLayoutStyles() {
var layoutStyle = {
position: "absolute",
margin: "0px",
padding: "0px",
};
$("[layout]").css(layoutStyle);
// JW: Setting z-index of notify to 1001, so it will appear above the dialog overlay.
// This allows dialogs to use the notification.
$('[layout^="notify"]').css({ "z-index": "1001", padding: "20px" });
$('[layout="panel"]').css({ position: "relative" });
var $headers = $('[layout-panel="expanded"] [layout-panel="header"]');
context._.each($headers, function ($header) {
$header = $($header);
var isAlwaysOpenHeader = $header.is(".always-open");
$header.css({
margin: "0px",
padding: "0px",
height:
(isAlwaysOpenHeader
? opts.alwaysOpenPanelHeaderHeight
: opts.panelHeaderHeight) + "px",
});
});
$("[layout-grid]").css({
position: "relative",
});
$("[layout-grid]").children().css({
position: "absolute",
});
}
function requiredStyles() {
var bodyStyle = {
margin: "0px",
padding: "0px",
overflow: "hidden",
};
if (opts.allowBodyOverflow) {
delete bodyStyle.overflow;
}
$("body").css(bodyStyle);
requiredLayoutStyles();
var curtainStyle = {
position: "absolute",
margin: "0px",
padding: "0px",
overflow: "hidden",
zIndex: 100,
};
$(".curtain").css(curtainStyle);
}
function hideAll() {
$("[layout]").hide();
$('[layout="header"]').show();
}
function showSidebar() {
sidebarVisible = true;
layout();
}
function hideSidebar() {
sidebarVisible = false;
layout();
}
function toggleSidebar() {
if (sidebarVisible) {
hideSidebar();
} else {
showSidebar();
}
}
function hideDialogs() {
// TODO - may need dialogEvents here for specific dialogs.
$('[layout="dialog"]').hide();
$(".dialog-overlay").hide();
}
function tabClicked(evt) {
evt.preventDefault();
var destination = $(evt.currentTarget).attr("tab-target");
$("[tab-target]").removeClass("selected");
$(evt.currentTarget).addClass("selected");
$(".tab").hide();
$('[tab-id="' + destination + '"]').show();
}
function linkClicked(evt) {
evt.preventDefault();
var $currentTarget = $(evt.currentTarget);
// allow links to be disabled
if ($currentTarget.hasClass("disabled")) {
return;
}
var destination = $(evt.currentTarget).attr("layout-link");
var $destination = $('[layout-id="' + destination + '"]');
//force user to the new site if they click on the jamtrack tile
if(destination === "jamtrack") {
var urlToOpen = gon.spa_origin_url + '/jamtracks';
if(gon.isNativeClient) {
context.JK.popExternalLink(urlToOpen);
return;
}else{
window.open(urlToOpen, '_blank');
return;
}
}
var destinationType = $destination.attr("layout");
if (destinationType === "screen") {
if (
!context.JK.currentUserId &&
!$destination.is(".no-login-required")
) {
// there is no user, and this item does not support 'no-login', so warn user
showDialog("login-required-dialog");
return;
}
context.location = "/client#/" + destination;
} else if (destinationType === "dialog") {
showDialog(destination);
}
}
function close(evt) {
var $target = $(evt.currentTarget).closest("[layout]");
var layoutId = $target.attr("layout-id");
var isDialog = $target.attr("layout") === "dialog";
if (isDialog) {
closeDialog(layoutId);
} else {
$target.hide();
}
return false;
}
function cancel(evt) {
var $target = $(evt.currentTarget).closest("[layout]");
var layoutId = $target.attr("layout-id");
var isDialog = $target.attr("layout") === "dialog";
if (isDialog) {
cancelDialog(layoutId);
} else {
// ?
logger.warn("unable to handle cancel layout-action for %o", $target);
}
return false;
}
function cancelDialog(dialog) {
logger.debug("cancelling dialog: " + dialog);
var $dialog = $('[layout-id="' + dialog + '"]');
var result = dialogEvent(dialog, "onCancel");
if (result !== false) {
closeDialog(dialog, true);
} else {
logger.debug("dialog refused cancel");
}
}
function closeDialog(dialog, canceled) {
logger.debug("closing dialog: " + dialog);
var $dialog = $('[layout-id="' + dialog + '"]');
dialogEvent(dialog, "beforeHide");
var $overlay = $(".dialog-overlay");
unstackDialogs($overlay);
$dialog.hide();
$dialog.triggerHandler(EVENTS.DIALOG_CLOSED, {
name: dialog,
dialogCount: openDialogs.length,
result: $dialog.data("result"),
canceled: canceled,
});
$(context).triggerHandler(EVENTS.DIALOG_CLOSED, {
name: dialog,
dialogCount: openDialogs.length,
result: $dialog.data("result"),
canceled: canceled,
});
dialogEvent(dialog, "afterHide");
$.btOffAll(); // add any prod bubbles if you close a dialog
}
function screenProperty(screen, property) {
if (screen && screen in screenBindings) {
return screenBindings[screen][property];
}
return null;
}
function screenEvent(screen, evtName, data) {
if (screen && screen in screenBindings) {
if (evtName in screenBindings[screen]) {
return screenBindings[screen][evtName].call(me, data);
}
}
return NOT_HANDLED;
}
function dialogEvent(dialog, evtName, data) {
if (dialog && dialog in dialogBindings) {
if (evtName in dialogBindings[dialog]) {
return dialogBindings[dialog][evtName].call(me, data);
}
}
return NOT_HANDLED;
}
function activeElementEvent(evtName, data) {
var result = {};
var currDialog = currentDialog();
if (currDialog) {
result.dialog = dialogEvent(
currDialog.attr("layout-id"),
evtName,
data
);
}
if (currentScreen) {
result.screen = screenEvent(currentScreen, evtName, data);
}
return result;
}
function onHashChange(e, postFunction) {
if (currentHash == context.location.hash) {
return;
}
if (resettingHash) {
resettingHash = false;
e.preventDefault();
return false;
}
try {
var location = context.RouteMap.parse(context.location.hash);
} catch (e) {
// this is nowhere in the rich client; just let it go through
return postFunction(e);
}
var screen = location.page.substring(1); // remove leading slash
var accepted = screenEvent(currentScreen, "beforeLeave", {
screen: screen,
hash: context.location.hash,
});
if (accepted === false) {
logger.debug(
"navigation to " +
context.location.hash +
" rejected by " +
currentScreen
);
//resettingHash = true;
// reset the hash to where it just was
context.location.hash = currentHash;
} else {
screen;
return postFunction(e);
}
}
function changeToScreen(screen, data) {
changeScreen(screen, data);
}
function changeScreen(screen, data) {
previousScreen = currentScreen;
currentScreen = screen;
currentHash = context.location.hash;
var accepted = screenEvent(previousScreen, "beforeHide", data);
if (accepted === false) return;
logger.debug("layout: changing screen to " + currentScreen);
// notify everyone
$(document).triggerHandler(EVENTS.SCREEN_CHANGED, {
previousScreen: previousScreen,
newScreen: currentScreen,
});
window.NavActions.screenChanged(
currentScreen,
screenProperty(currentScreen, "navName")
);
context.JamTrackPreviewActions.screenChange();
screenEvent(currentScreen, "beforeShow", data);
// For now -- it seems we want it open always.
// TODO - support user preference here? Remember how they left it?
sidebarVisible = true;
/*
var openSidebarScreens = [
'home', 'session', 'createSession',
'findSession', 'searchResults'
];
$.each(openSidebarScreens, function() {
logger.debug("comparing " + this + " to " + currentScreen);
if (this === currentScreen) {
sidebarVisible = true;
return false;
}
});
*/
layout();
// add an attribute to any dialogs, which let's it know it's current screen (useful for contextual styling)
context._.each(openDialogs, function (dialog) {
addScreenContextToDialog($(dialog));
});
screenEvent(previousScreen, "afterHide", data);
screenEvent(currentScreen, "afterShow", data);
jQuery.btOffAll(); // add any prod bubbles if you change screens
// Show any requested dialog
if ("d" in data) {
// if the dialog is a text message dialog, and the user is not logged in, show the login dialog instead
if (!context.JK.currentUserId) {
if (isTextMessageInUrl()) {
showDialog("login-required-dialog");
return false;
}
}
showDialog(data.d, data);
}
}
function isTextMessageInUrl() {
var hash = context.location.hash;
var regexp = /\S+\/text-message\/d1=/
return regexp.test(hash);
}
// if no arguments passed, then see if any dialog is showing
// if string passed, see if dialog is showing (even if buried) of a given name
function isDialogShowing() {
if (arguments.length == 1) {
// user passed in dialog id
var dialogId = arguments[0];
var result = false;
context._.each(openDialogs, function (dialog) {
if ($(dialog).attr("layout-id") == dialogId) {
result = true;
return false;
}
});
return result;
} else {
// user passed in nothing
return openDialogs.length > 0;
}
}
function currentDialog() {
if (openDialogs.length == 0) return null;
return openDialogs[openDialogs.length - 1];
}
// payload is a notification event from websocket gateway
function dialogObscuredNotification(payload) {
var openDialog = currentDialog();
if (!openDialog) return false;
if (typeof openDialog.handledNotification === "function") {
return !openDialog.handledNotification(payload);
} else {
return true;
}
}
// payload is a notification event from websocket gateway
function isNoisyNotification(payload) {
var openDialog = currentDialog();
if (!openDialog) return false;
if (typeof openDialog.isNoisyNotification === "function") {
return !openDialog.isNoisyNotification(payload);
} else {
return true;
}
}
/**
* Responsible for keeping N dialogs in correct stacked order,
* also moves the .dialog-overlay such that it hides/obscures all dialogs except the highest one
*/
function stackDialogs($dialog, $overlay) {
// don't push a dialog on the stack that is already on there; remove it from where ever it is currently
// and the rest of the code will make it end up at the top
var layoutId = $dialog.attr("layout-id");
for (var i = openDialogs.length - 1; i >= 0; i--) {
if (openDialogs[i].attr("layout-id") === layoutId) {
openDialogs.splice(i, 1);
}
}
// pull out a topmost one, if present
var topMost = null;
for (var i = openDialogs.length - 1; i >= 0; i--) {
if (openDialogs[i].attr("topmost") === "true") {
topMost = openDialogs[i];
openDialogs.splice(i, 1);
}
}
openDialogs.push($dialog);
if (topMost) openDialogs.push(topMost);
var zIndex = 1000;
for (var i in openDialogs) {
var $openDialog = openDialogs[i];
$openDialog.css("zIndex", zIndex);
zIndex++;
}
$overlay.css("zIndex", zIndex - 1);
}
function unstackDialogs($overlay) {
if (openDialogs.length > 0) {
openDialogs.pop();
}
var zIndex = 1000 + openDialogs.length;
$overlay.css("zIndex", zIndex - 1);
if (openDialogs.length == 0) {
$overlay.hide();
}
}
function addScreenContextToDialog($dialog) {
$dialog.attr("current-screen", currentScreen); // useful for contextual styling of dialogs
}
function showDialog(dialog, options) {
if (dialogEvent(dialog, "beforeShow", options) === false) {
return null;
}
logger.debug("opening dialog: " + dialog);
var $dialog = $('[layout-id="' + dialog + '"]');
if ($dialog.length == 0) {
logger.debug("unknown dialog encountered: " + dialog);
return;
}
var $overlay = $(".dialog-overlay");
if (opts.sizeOverlayToContent) {
var $body = $("body");
$(".dialog-overlay").css({
width: $body.width() + "px",
height: $body.height() + "px",
});
}
$overlay.show();
centerDialog(dialog);
stackDialogs($dialog, $overlay);
addScreenContextToDialog($dialog);
$dialog.show();
// maintain center (un-attach previous sensor if applicable, then re-add always)
window.ResizeSensor.detach($dialog.get(0));
new window.ResizeSensor($dialog, function () {
centerDialog(dialog);
});
dialogEvent(dialog, "afterShow", options);
$.btOffAll(); // add any prod bubbles if you open a dailog
return $dialog;
}
function centerDialog(dialog) {
var $dialog = $('[layout-id="' + dialog + '"]');
$dialog.css({
left: width / 2 - $dialog.width() / 2 + "px",
top: height / 2 - $dialog.height() / 2 + "px",
});
}
function panelHeaderClicked(evt) {
evt.preventDefault();
if (!context.JK.currentUserId) {
showDialog("login-required-dialog");
return false;
}
expandedPanel = $(evt.currentTarget)
.closest('[layout="panel"]')
.attr("layout-id");
layout();
return false;
}
function expandNotificationPanel() {
if (!context.JK.currentUserId) {
showDialog("login-required-dialog");
return false;
}
expandedPanel = "panelNotifications";
layout();
return false;
}
function wizardLinkClicked(evt) {
evt.preventDefault();
var targetStepId = $(evt.currentTarget).attr("layout-wizard-link");
setWizardStep(targetStepId);
return false;
}
function startNewFtue() {
// var step = 0;
//setWizardStep(step);
//wizardShowFunctions[step]();
return showDialog("gear-wizard");
}
function setWizardStep(targetStepId) {
var selector = '[layout-wizard-step="' + targetStepId + '"]';
var $targetStep = $(selector);
var stepDialogTitle = $targetStep.attr("dialog-title");
if (stepDialogTitle) {
var $myDialog = $targetStep.closest('[layout="dialog"]');
var $myTitle = $(".content-head h1", $myDialog);
$myTitle.html(stepDialogTitle);
}
// Hide all steps:
// Invoke the 'show' function, if present prior to actually showing.
if (
context._.contains(context._.keys(wizardShowFunctions), targetStepId)
) {
wizardShowFunctions[targetStepId]();
}
$("[layout-wizard-step]").hide();
$targetStep.show();
var ftuePurpose = $targetStep.attr("dialog-purpose");
context.JK.GA.trackFTUECompletion(ftuePurpose, context.JK.detectOS());
}
function trackLocationChange(e) {
context.JK.GA.virtualPageView(
location.pathname + location.search + location.hash
);
}
function onHandleKey(e) {
if (e.keyCode == 27 /** esc */) {
if (isDialogShowing()) {
var $dialog = currentDialog();
if (!$dialog) {
logger.error("unable to find current dialog on ESC");
return;
}
cancelDialog($dialog.attr("layout-id"));
}
}
}
function handleDialogState() {
var rawDialogState = $.cookie("dialog_state");
try {
var dialogState = JSON.parse(rawDialogState);
if (!dialogState) {
$.removeCookie("dialog_state");
return;
}
} catch (e) {
$.removeCookie("dialog_state");
return;
}
var dialogName = dialogState["name"];
if (dialogName) {
setTimeout(function () {
// TODO: we need a 'everything is initialized' event
showDialog(dialogName);
}, 0);
}
$.removeCookie("dialog_state");
}
// on next page load, a dialog of this name will show
function queueDialog(name) {
$.cookie("dialog_state", JSON.stringify({ name: name }));
}
function events() {
$(context).resize(function () {
if (resizing) {
context.clearTimeout(resizing);
}
resizing = context.setTimeout(layout, 80);
});
$("body").on("click", "[layout-link]", linkClicked);
$('[layout-action="close"]').on("click", close);
$('[layout-action="cancel"]').on("click", cancel);
$("[layout-sidebar-expander]").on("click", toggleSidebar);
$('[layout-panel="expanded"] [layout-panel="header"]').on(
"click",
panelHeaderClicked
);
$("[layout-wizard-link]").on("click", wizardLinkClicked);
$("[tab-target]").on("click", tabClicked);
$(context).on("hashchange", trackLocationChange);
$(document).keyup(onHandleKey);
}
// public functions
this.getOpts = function () {
return opts;
};
// used for concurrent notifications
var notifyQueue = [];
var notify1Showing = false;
var notify2Showing = false;
var notify3Showing = false;
var isMouseOverNotify1 = false;
var isMouseOverNotify2 = false;
var isMouseOverNotify3 = false;
var notify1Elapsed = false;
var notify2Elapsed = false;
var notify3Elapsed = false;
var $notify1 = $('[layout="notify1"]');
var $notify2 = $('[layout="notify2"]');
var $notify3 = $('[layout="notify3"]');
$notify1.on("mouseenter", mouseEnterNotification);
$notify1.on("mouseleave", mouseLeaveToNotification);
$notify2.on("mouseenter", mouseEnterNotification);
$notify2.on("mouseleave", mouseLeaveToNotification);
$notify3.on("mouseenter", mouseEnterNotification);
$notify3.on("mouseleave", mouseLeaveToNotification);
function mouseEnterNotification(evt) {
var $notify = $(evt.target);
//console.log("mouseEnter", $notify.prop("id"));
switch ($notify.prop("id")) {
case "notification1":
isMouseOverNotify1 = true;
break;
case "notification2":
isMouseOverNotify2 = true;
break;
case "notification3":
isMouseOverNotify3 = true;
break;
}
}
function mouseLeaveToNotification(evt) {
var $notify = $(evt.target);
//console.log("mouseLeave", $notify.prop("id"));
switch ($notify.prop("id")) {
case "notification1":
if (notify1Elapsed) {
$notify1.hide(0);
notify1Showing = false;
}
isMouseOverNotify1 = false;
break;
case "notification2":
if (notify2Elapsed) {
$notify2.hide(0);
notify2Showing = false;
}
isMouseOverNotify2 = false;
break;
case "notification3":
if (notify3Elapsed) {
$notify3.hide(0);
notify3Showing = false;
}
isMouseOverNotify3 = false;
break;
}
}
//var firstNotification = false;
//var notifyDetails;
var okButton = {
id: "btn-okay",
text: "OKAY",
"layout-action": "close",
href: "#",
class: "button-orange",
};
var cancelButton = {
id: "btn-cancel",
text: "CANCEL",
"layout-action": "close",
href: "#",
class: "button-grey",
};
var defaultButtons = [okButton, cancelButton];
// this.notify = function (message, buttons, noCancel) {
// if (!buttons) {
// if (noCancel) {
// buttons = [okButton];
// }
// else {
// buttons = defaultButtons;
// }
// }
// // this allows clients to just specify the important action button without having to repeat the cancel descripton everywhere
// if (buttons.length === 1) {
// // jkolyer: changed default to remove cancel as this is used as an alert, not a confirmation (see ConfirmDialog)
// // buttons.push(cancelButton);
// }
// var $notify = $('[layout="notify"]');
// if (notifyQueue.length === 0) {
// firstNotification = true;
// setNotificationInfo(message, buttons, $notify);
// }
// notifyQueue.push({message: message, descriptor: buttons});
// // JW - speeding up the in/out parts of notify. Extending non-moving time.
// $notify.hide(0)
// .show({
// duration: 0,
// queue: true,
// complete: function () {
// notifyDetails = notifyQueue.shift();
// // shift 1 more time if this is first notification being displayed
// if (firstNotification) {
// notifyDetails = notifyQueue.shift();
// firstNotification = false;
// }
// if (notifyDetails !== undefined) {
// setNotificationInfo(notifyDetails.message, notifyDetails.descriptor, $notify);
// }
// notifyDetails = {};
// }
// })
// .delay(8000)
// .hide(0)
// };
this.notify = function (message, buttons, noCancel) {
console.log("call this.notify");
if (!buttons) {
if (noCancel) {
buttons = [okButton];
} else {
buttons = defaultButtons;
}
}
notifyQueue.push({ message: message, descriptor: buttons });
showNotificationToast();
};
function showNotificationToast() {
if (!notify1Showing) {
var notifyDetails1 = notifyQueue.shift();
if (notifyDetails1 !== undefined) {
setNotificationInfo(
notifyDetails1.message,
notifyDetails1.descriptor,
$notify1
);
notify1Elapsed = false;
$notify1.hide(0).show({
duration: 0,
//queue: true,
complete: function () {
console.log("Show notification1");
notify1Showing = true;
notifyDetails1 = {};
},
});
//.delay(8000);
setTimeout(function () {
notify1Elapsed = true;
if (!isMouseOverNotify1) {
$notify1.hide(0, function () {
notify1Showing = false;
console.log("Hide notification1");
});
}
}, 8000);
}
} else if (!notify2Showing) {
var notifyDetails2 = notifyQueue.shift();
if (notifyDetails2 !== undefined) {
setNotificationInfo(
notifyDetails2.message,
notifyDetails2.descriptor,
$notify2
);
notify2Elapsed = false;
$notify2.hide(0).show({
duration: 0,
//queue: true,
complete: function () {
console.log("Show notification2");
notify2Showing = true;
notifyDetails2 = {};
},
});
// .delay(8000)
// .hide(0, function(){
// notify2Showing = false;
// console.log("Hide notification2")
// })
setTimeout(function () {
notify2Elapsed = true;
if (!isMouseOverNotify2) {
$notify2.hide(0, function () {
notify2Showing = false;
console.log("Hide notification2");
});
}
}, 8000);
}
} else if (!notify3Showing) {
var notifyDetails3 = notifyQueue.shift();
if (notifyDetails3 !== undefined) {
setNotificationInfo(
notifyDetails3.message,
notifyDetails3.descriptor,
$notify3
);
notify3Elapsed = false;
$notify3.hide(0).show({
duration: 0,
//queue: true,
complete: function () {
console.log("Show notification3");
notify3Showing = true;
notifyDetails3 = {};
},
});
// .delay(8000)
// .hide(0, function(){
// notify3Showing = false;
// console.log("Hide notification3")
// })
setTimeout(function () {
notify3Elapsed = true;
if (!isMouseOverNotify3) {
$notify3.hide(0, function () {
notify3Showing = false;
console.log("Hide notification3");
});
}
}, 8000);
}
} else if (notify1Showing && notify2Showing && notify3Showing) {
//try again after 1 second
setTimeout(function () {
showNotificationToast();
}, 1000);
}
}
// function setNotificationInfo(message, buttons, notificationSelector) {
// var $notify = $('[layout="notify"]');
// $('h2', $notify).text(message.title);
// $('p', $notify).empty();
// if (message.text instanceof jQuery) {
// $('p', $notify).append(message.text)
// }
// else {
// $('p', $notify).html(message.text);
// }
// if (message.icon_url) {
// $('#avatar', $notify).attr('src', message.icon_url);
// $('#notify-avatar', $notify).show();
// }
// else {
// $('#notify-avatar', $notify).hide();
// }
// if (message.detail) {
// $('div.detail', $notify).html(message.detail).show();
// }
// else {
// $('div.detail', $notify).hide();
// }
// var $buttonDiv = $('#buttons', $notify);
// $buttonDiv.empty();
// $.each(buttons, function(index, val) {
// // build button HTML based on KV pairs
// var keys = Object.keys(val);
// var buttonHtml = '<a';
// for (var i=0; i < keys.length; i++) {
// //console.log("keys[i]=%o, val[keys[i]]=%o", keys[i], val[keys[i]]);
// // ignore button text and callback info
// if (keys[i] !== 'text' && keys[i] !== 'callback' && keys[i] !== 'callback_args') {
// buttonHtml += ' ' + keys[i] + '="' + val[keys[i]] + '"';
// }
// }
// buttonHtml += '>' + val['text'] + '</a>&nbsp;&nbsp;';
// $buttonDiv.append(buttonHtml);
// // ensure it doesn't fire twice
// $('#' + val.id, $notify).unbind('click');
// $('#' + val.id, $notify).click(function() {
// if (val.callback !== undefined) {
// if (val.callback_args) {
// val.callback(val.callback_args);
// }
// else {
// val.callback();
// }
// return false;
// }
// else {
// notificationSelector.hide();
// return false;
// }
// });
// });
// }
function setNotificationInfo(message, buttons, notificationSelector) {
var $notify = notificationSelector;
$("h2", $notify).text(message.title);
$("p", $notify).empty();
if (message.text instanceof jQuery) {
$("p", $notify).append(message.text);
} else {
$("p", $notify).html(message.text);
}
if (message.icon_url) {
$("#avatar", $notify).attr("src", message.icon_url);
$("#notify-avatar", $notify).show();
} else {
$("#notify-avatar", $notify).hide();
}
if (message.detail) {
$("div.detail", $notify).html(message.detail).show();
} else {
$("div.detail", $notify).hide();
}
var $buttonDiv = $("#buttons", $notify);
$buttonDiv.empty();
$.each(buttons, function (index, val) {
// build button HTML based on KV pairs
var keys = Object.keys(val);
var buttonHtml = "<a";
for (var i = 0; i < keys.length; i++) {
//console.log("keys[i]=%o, val[keys[i]]=%o", keys[i], val[keys[i]]);
// ignore button text and callback info
if (
keys[i] !== "text" &&
keys[i] !== "callback" &&
keys[i] !== "callback_args"
) {
buttonHtml += " " + keys[i] + '="' + val[keys[i]] + '"';
}
}
buttonHtml += ">" + val["text"] + "</a>&nbsp;&nbsp;";
$buttonDiv.append(buttonHtml);
// ensure it doesn't fire twice
$("#" + val.id, $notify).unbind("click");
$("#" + val.id, $notify).click(function () {
if (val.callback !== undefined) {
if (val.callback_args) {
val.callback(val.callback_args);
} else {
val.callback();
}
return false;
} else {
$notify.hide();
return false;
}
});
});
}
this.setWizardStep = setWizardStep;
this.startNewFtue = startNewFtue;
this.changeToScreen = function (screen, data) {
changeToScreen(screen, data);
};
this.onHashChange = function (e, postFunction) {
return onHashChange(e, postFunction);
};
this.showDialog = function (dialog, options) {
return showDialog(dialog, options);
};
this.dialogObscuredNotification = function (payload) {
return dialogObscuredNotification(payload);
};
this.isNoisyNotification = function (payload) {
return isNoisyNotification(payload);
};
this.shouldFreezeAppOnDisconnect = function () {
return shouldFreezeAppOnDisconnect();
};
this.isDialogShowing = function () {
return isDialogShowing(arguments[0]);
};
this.activeElementEvent = function (evtName, data) {
return activeElementEvent(evtName, data);
};
this.getCurrentScreen = function () {
return currentScreen; // will be a string of the layout-id of the active screen
};
this.close = function (evt) {
close(evt);
};
this.beforeDisconnect = function () {
fireEvents();
};
this.afterReconnect = function () {
fireEvents();
};
this.closeDialog = closeDialog;
this.cancelDialog = cancelDialog;
this.handleDialogState = handleDialogState;
this.queueDialog = queueDialog;
/**
* Given information on a grid, and a given card's grid settings, use the
* margin options and return a list of [top, left, width, height]
* for the cell.
*/
this.getCardLayout = function (
gridWidth,
gridHeight,
gridRows,
gridCols,
row,
col,
rowspan,
colspan
) {
var _gridWidth = gridWidth + 3 * opts.gridPadding;
var _gridHeight = gridHeight + 3 * opts.gridPadding;
var cellWidth, cellHeight, top, left, width, height;
cellWidth = Math.floor(
(_gridWidth - 2 * opts.gridOuterMargin) / gridCols
);
cellHeight = Math.floor(
(_gridHeight - 2 * opts.gridOuterMargin) / gridRows
);
width = colspan * cellWidth - 2 * opts.gridPadding;
height = rowspan * cellHeight - 2 * opts.gridPadding;
top = row * cellHeight; // + opts.gridOuterMargin; // + opts.gridPadding;
left = col * cellWidth; // + opts.gridOuterMargin; // + opts.gridPadding;
return {
top: top,
left: left,
width: width,
height: height,
};
};
this.bindScreen = function (screen, handler) {
screenBindings[screen] = handler;
};
this.bindDialog = function (dialog, handler) {
dialogBindings[dialog] = handler;
};
this.registerWizardStepFunction = function (stepId, showFunction) {
wizardShowFunctions[stepId] = showFunction;
};
this.initialize = function (inOpts) {
me = this;
opts = $.extend(opts, inOpts);
setup();
events();
};
this.expandNotificationPanel = function () {
return expandNotificationPanel();
};
return this;
};
})(window, jQuery);