A "modal bar" component. This is a lightweight replacement for modal dialogs that appears at the top of the editor area for operations like Find and Quick Open.
Creates a modal bar whose contents are the given template.
Dispatches one event:
function ModalBar(template, autoClose, animate) {
if (animate === undefined) {
animate = true;
}
this._handleKeydown = this._handleKeydown.bind(this);
this._handleFocusChange = this._handleFocusChange.bind(this);
this._$root = $("<div class='modal-bar'/>")
.html(template)
.insertBefore("#editor-holder");
if (animate) {
this._$root.addClass("popout offscreen");
// Forcing the renderer to do a layout, which will cause it to apply the transform for the "offscreen"
// class, so it will animate when you remove the class.
window.getComputedStyle(this._$root.get(0)).getPropertyValue("top");
this._$root.removeClass("popout offscreen");
}
// If something *other* than an editor (like another modal bar) has focus, set the focus
// to the editor here, before opening up the new modal bar. This ensures that the old
// focused item has time to react and close before the new modal bar is opened.
// See bugs #4287 and #3424
MainViewManager.focusActivePane();
if (autoClose) {
this._autoClose = true;
this._$root.on("keydown", this._handleKeydown);
window.document.body.addEventListener("focusin", this._handleFocusChange, true);
// Set focus to the first input field, or the first button if there is no input field.
// TODO: remove this logic?
var $firstInput = $("input[type='text']", this._$root).first();
if ($firstInput.length > 0) {
$firstInput.focus();
} else {
$("button", this._$root).first().focus();
}
}
// Preserve scroll position of the current full editor across the editor refresh, adjusting for the
// height of the modal bar so the code doesn't appear to shift if possible.
MainViewManager.cacheScrollState(MainViewManager.ALL_PANES);
WorkspaceManager.recomputeLayout(); // changes available ht for editor area
MainViewManager.restoreAdjustedScrollState(MainViewManager.ALL_PANES, this.height());
}
EventDispatcher.makeEventDispatcher(ModalBar.prototype);
A jQuery object containing the root node of the ModalBar.
ModalBar.prototype._$root = null;
True if this ModalBar is set to autoclose.
ModalBar.prototype._autoClose = false;
Allows client code to block autoClose from closing the ModalBar: if set, this function is called whenever autoClose would normally close the ModalBar. Returning true prevents the close from occurring. Programmatically calling close() will still close the bar, however.
ModalBar.prototype.isLockedOpen = null;
ModalBar.CLOSE_ESCAPE = "escape";
ModalBar.CLOSE_BLUR = "blur";
ModalBar.CLOSE_API = "api";
If autoClose is set, detects when something other than the modal bar is getting focus and dismisses the modal bar. DOM nodes with "attached-to" jQuery metadata referencing an element within the ModalBar are allowed to take focus without closing it.
ModalBar.prototype._handleFocusChange = function (e) {
if (this.isLockedOpen && this.isLockedOpen()) {
return;
}
var effectiveElem = $(e.target).data("attached-to") || e.target;
if (!$.contains(this._$root.get(0), effectiveElem)) {
this.close(undefined, undefined, ModalBar.CLOSE_BLUR);
}
};
If autoClose is set, close the bar when Escape is pressed
ModalBar.prototype._handleKeydown = function (e) {
if (e.keyCode === KeyEvent.DOM_VK_ESCAPE) {
e.stopPropagation();
e.preventDefault();
this.close(undefined, undefined, ModalBar.CLOSE_ESCAPE);
}
};
Closes the modal bar and returns focus to the active editor. Returns a promise that is resolved when the bar is fully closed and the container is removed from the DOM.
ModalBar.prototype.close = function (restoreScrollPos, animate, _reason) {
var result = new $.Deferred(),
self = this;
if (restoreScrollPos === undefined) {
restoreScrollPos = true;
}
if (animate === undefined) {
animate = true;
}
// If someone hasn't already called `prepareClose()` to pop the ModalBar out of the flow
// and resize the editor, then do that here.
if (!this._$root.hasClass("popout")) {
this.prepareClose(restoreScrollPos);
}
if (this._autoClose) {
window.document.body.removeEventListener("focusin", this._handleFocusChange, true);
}
this.trigger("close", _reason, result);
function doRemove() {
self._$root.remove();
result.resolve();
}
if (animate) {
AnimationUtils.animateUsingClass(this._$root.get(0), "offscreen")
.done(doRemove);
} else {
doRemove();
}
MainViewManager.focusActivePane();
return result.promise();
};
ModalBar.prototype.getRoot = function () {
return this._$root;
};
exports.ModalBar = ModalBar;
});
ModalBar.prototype.height = function () {
return this._$root.outerHeight();
};
Prepares the ModalBar for closing by popping it out of the main flow and resizing/
rescrolling the Editor to maintain its current apparent code position. Useful if
you want to do that as a separate operation from actually animating the ModalBar
closed and removing it (for example, if you need to switch full editors in between).
If you don't call this explicitly, it will get called at the beginning of close()
.
ModalBar.prototype.prepareClose = function (restoreScrollPos) {
if (restoreScrollPos === undefined) {
restoreScrollPos = true;
}
this._$root.addClass("popout");
// Since the modal bar has now an absolute position relative to the editor holder,
// when there are html menus we need to adjust the top position
if (!brackets.nativeMenus) {
var top = $("#titlebar").outerHeight();
this._$root.css("top", top + "px");
}
// Preserve scroll position of all visible views
// adjusting for the height of the modal bar so the code doesn't appear to shift if possible.
var barHeight = this.height();
if (restoreScrollPos) {
MainViewManager.cacheScrollState(MainViewManager.ALL_PANES);
}
WorkspaceManager.recomputeLayout(); // changes available ht for editor area
// restore scroll position of all views
if (restoreScrollPos) {
MainViewManager.restoreAdjustedScrollState(MainViewManager.ALL_PANES, -barHeight);
}
};