1/** 2 * @fileOverview 3 * JIRA's layer management has some fundamental shortcomings. I'll do my best to explain them. 4 * 5 * An ideal layer management solution would be something like: 6 * - a single 'layerable' component exists, from which every other component extends or mixes in. 7 * - all layers have a parent-child relationship with something else in the DOM. 8 * - whenever a layer is to be shown or hidden, correct layering would be a case of 9 * -- determining the parent of the layer to be shown/hidden 10 * -- popping every layer that's a child of the parent you found, then 11 * -- pushing the new layer (if it was to be shown). 12 * 13 * JIRA's layer management is nothing like that. 14 * 15 * JIRA has no core layerable component, and no hierarchy of layers. Rather, 16 * each component can decide whether it shows or hides individually. 17 * A consequence of this: no one layerable component knows about (or could know about) another. 18 * There is no layer hierarchy; instead of a "stack" of layers, each component has a 19 * singleton value for the "current" shown instance of that component. 20 * 21 * Because of this, JIRA's layer management consists of: 22 * (1) figuring out what components can be shown or hidden at any given time, 23 * (2) knowing what all layerable events might occur, and 24 * (3) knowing which shown layers should appear/disappear when an event occurs. 25 * 26 * @note 27 * This file is deliberately *not* an AMD module, because the event registration 28 * needs to happen immediately an synchronously so as not to miss any events fired 29 * during page initialisation. 30 */ 31(function () { 32 'use strict'; 33 34 var Events = require('jira/util/events'); 35 var Types = require('jira/util/events/types'); 36 var Strings = require('jira/util/strings'); 37 var LayerHideReason = require('jira/ajs/layer/hide-reasons'); 38 var interactions = require('jira/ajs/layer/layer-interactions'); 39 var topSameOriginWindow = require('jira/util/top-same-origin-window'); 40 var jQuery = require('jquery'); 41 var _ = require('underscore'); 42 var $doc = jQuery(document); 43 function anyStringContains(haystack, needles) { 44 if (typeof needles === "string") { 45 needles = [needles]; 46 } 47 return _.any(needles, function (n) { 48 return Strings.contains(haystack, n); 49 }); 50 } 51 52 // We want to make sure that we don't have any other layers open before we close the dialog 53 Events.bind("Dialog.beforeHide", function (e) { 54 var shouldPrevent = _.any(interactions.preventDialogHide(), function (layerable) { 55 return layerable && layerable.current; 56 }); 57 if (shouldPrevent) { 58 e.preventDefault(); 59 } 60 }); 61 62 // Close any layers that are currently open before allowing a dialog open to occur 63 Events.bind("Dialog.show", function (e) { 64 _.each(interactions.hideBeforeDialogShown(), function (layerable) { 65 if (layerable && layerable.current) { 66 layerable.current.hide(); 67 e.preventDefault(); 68 } 69 }); 70 }); 71 72 // If there are layers that should affect an input, the user is probably still selecting a value 73 // for the input field. Therefore, don't allow the input to be blurred while these layers are active. 74 $doc.bind("beforeBlurInput", function (e) { 75 var shouldPrevent = _.any(interactions.preventInputBlur(), function (layerable) { 76 return layerable && layerable.current; 77 }); 78 if (shouldPrevent) { 79 e.preventDefault(); 80 } 81 }); 82 83 // Some layers are shown to allow picking values for inline editable fields. If one is shown when an inline edit 84 // is cancelled, the user is probably still picking a value; prevent the edit from being cancelled until the layer is gone. 85 if (Types.BEFORE_INLINE_EDIT_CANCEL) { 86 Events.bind(Types.BEFORE_INLINE_EDIT_CANCEL, function (e, id, type, reason) { 87 if (reason !== JIRA.Issues.CANCEL_REASON.escPressed) { 88 return; 89 } 90 var shouldPrevent = _.any(interactions.preventInlineEditCancel(), function (layerable) { 91 return layerable && layerable.current; 92 }); 93 if (shouldPrevent) { 94 e.preventDefault(); 95 } 96 }); 97 } 98 function getWindow() { 99 return topSameOriginWindow(window); 100 } 101 function getLayer(instance) { 102 // instance is: 103 // - AJS.InlineLayer 104 // - JIRA.Dialog 105 // - AJS.dropDown 106 // - AJS.InlineDialog. 107 // - AJS.Dialog 108 return (instance.$layer || instance.$popup || instance.$ || instance.popup || instance.element || instance)[0]; 109 } 110 function listenForLayerEvents($doc) { 111 $doc.bind("showLayer", function (e, type, item) { 112 // User hover and inline edit dialogs don't participate in layer management. 113 if (anyStringContains(item && item.id, ["user-hover-dialog", "aui-inline-edit-error", "watchers", "jira-help-tip"])) { 114 return; 115 } 116 var topWindow = getWindow().AJS; 117 //the user-hover-dialog has a dropdown in it which is why we're not hiding it on showLayer. It hides 118 //itself anyway when the user doesn't hover over it any more with the mouse. 119 if (topWindow.currentLayerItem && item !== topWindow.currentLayerItem && topWindow.currentLayerItem.type !== "popup") { 120 topWindow.currentLayerItem.hide(); 121 } 122 if (item) { 123 topWindow.currentLayerItem = item; 124 topWindow.currentLayerItem.type = type; 125 } 126 }).bind("hideLayer", function (e, type, item) { 127 // User hover dialogs don't participate in layer management. 128 if (!item || anyStringContains(item.id, ["user-hover-dialog", "aui-inline-edit-error", "jira-help-tip"])) { 129 return; 130 } 131 var topWindow = getWindow().AJS; 132 if (topWindow.currentLayerItem) { 133 if (topWindow.currentLayerItem === item) { 134 topWindow.currentLayerItem = null; 135 } else if (jQuery.contains(getLayer(item), getLayer(topWindow.currentLayerItem))) { 136 topWindow.currentLayerItem.hide(); 137 } 138 } 139 }).bind("hideAllLayers", function () { 140 var topWindow = getWindow().AJS; 141 if (topWindow.currentLayerItem) { 142 topWindow.currentLayerItem.hide(); 143 } 144 }).click(function (e) { 145 var topWindow = getWindow().AJS; 146 var layerItem = topWindow.currentLayerItem; 147 if (!layerItem || layerItem.type === "popup" || layerItem.type === "inlineDialog" && layerItem.persistent) { 148 return; 149 } 150 if (layerItem._validateClickToClose) { 151 if (layerItem._validateClickToClose(e)) { 152 if (layerItem instanceof AJS.InlineLayer) { 153 layerItem.hide(LayerHideReason.clickOutside, e); 154 } else { 155 layerItem.hide(); 156 } 157 } 158 } else { 159 layerItem.hide(); 160 } 161 }); 162 } 163 $doc.bind("iframeAppended", function (e, iframe) { 164 iframe = jQuery(iframe); 165 iframe.on('load', function () { 166 listenForLayerEvents(iframe.contents()); 167 }); 168 }); 169 listenForLayerEvents($doc); 170})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.