/* * 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
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 + '"]'); 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() { const hash = context.location.hash; const 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 = '' + val['text'] + ' '; // $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 = "" + val["text"] + " "; $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);