This repository has been archived by the owner on Sep 6, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7.6k
Fix #283 #419
Merged
Merged
Fix #283 #419
Changes from 6 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
045517f
Fix #283
jasonsanjose 343fa25
Focus the primary button dialog button when shown
jasonsanjose e23f176
Change dialog keyboard shortcuts to keydown
jasonsanjose 7623626
Stop keyboardevent propogation
jasonsanjose 1e9eb23
Add issue number for FIXME CMD+.
jasonsanjose 228ca6d
Fix comment
jasonsanjose 0ed3141
Allow keyboard events in dialog form elements
jasonsanjose File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
/* | ||
* Copyright 2012 Adobe Systems Incorporated. All Rights Reserved. | ||
*/ | ||
|
||
/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ | ||
/*global define: false, $: false, brackets: false */ | ||
|
||
/** | ||
* Utilities for creating and managing standard modal dialogs. | ||
*/ | ||
define(function (require, exports, module) { | ||
'use strict'; | ||
|
||
var DIALOG_BTN_CANCEL = "cancel", | ||
DIALOG_BTN_OK = "ok", | ||
DIALOG_BTN_DONTSAVE = "dontsave", | ||
DIALOG_CANCELED = "_canceled"; | ||
|
||
// TODO: (issue #258) In future, we should templatize the HTML for the dialogs rather than having | ||
// it live directly in the HTML. | ||
var DIALOG_ID_ERROR = "error-dialog", | ||
DIALOG_ID_SAVE_CLOSE = "save-close-dialog", | ||
DIALOG_ID_EXT_CHANGED = "ext-changed-dialog", | ||
DIALOG_ID_EXT_DELETED = "ext-deleted-dialog"; | ||
|
||
function _dismissDialog(dlg, buttonId) { | ||
dlg.data("buttonId", buttonId); | ||
dlg.modal(true).hide(); | ||
} | ||
|
||
function _hasButton(dlg, buttonId) { | ||
return dlg.find("[data-button-id='" + buttonId + "']"); | ||
} | ||
|
||
var _handleKeyDown = function (e) { | ||
var primaryBtn = this.find(".primary"), | ||
buttonId = null, | ||
which = String.fromCharCode(e.which); | ||
|
||
if (e.which === 13) { | ||
// Click primary button | ||
if (primaryBtn) { | ||
buttonId = primaryBtn.attr("data-button-id"); | ||
} | ||
} else if (e.which === 32) { | ||
// Space bar on focused button | ||
this.find(".dialog-button:focus").click(); | ||
} else if (brackets.platform === "mac") { | ||
// CMD+D Don't Save | ||
if (e.metaKey && (which === 'D')) { | ||
if (_hasButton(this, DIALOG_BTN_DONTSAVE)) { | ||
buttonId = DIALOG_BTN_DONTSAVE; | ||
} | ||
// FIXME (issue #418) CMD+. Cancel swallowed by native shell | ||
} else if (e.metaKey && (e.which === 190)) { | ||
buttonId = DIALOG_BTN_CANCEL; | ||
} | ||
} else { // if (brackets.platform === "win") { | ||
// 'N' Don't Save | ||
if (which === 'N') { | ||
if (_hasButton(this, DIALOG_BTN_DONTSAVE)) { | ||
buttonId = DIALOG_BTN_DONTSAVE; | ||
} | ||
} | ||
} | ||
|
||
if (buttonId) { | ||
_dismissDialog(this, buttonId); | ||
} else { | ||
// Stop the event if not handled by this dialog | ||
e.stopPropagation(); | ||
e.preventDefault(); | ||
} | ||
}; | ||
|
||
/** | ||
* General purpose modal dialog. Assumes that: | ||
* -- the root tag of the dialog is marked with a unique class name (passed as dlgClass), as well as the | ||
* classes "template modal hide". | ||
* -- the HTML for the dialog contains elements with "title" and "message" classes, as well as a number | ||
* of elements with "dialog-button" class, each of which has a "data-button-id". | ||
* | ||
* @param {string} dlgClass The class of the dialog node in the HTML. | ||
* @param {string} title The title of the error dialog. Can contain HTML markup. | ||
* @param {string} message The message to display in the error dialog. Can contain HTML markup. | ||
* @return {Deferred} a $.Deferred() that will be resolved with the ID of the clicked button when the dialog | ||
* is dismissed. Never rejected. | ||
*/ | ||
function showModalDialog(dlgClass, title, message, callback) { | ||
var result = $.Deferred(); | ||
|
||
// We clone the HTML rather than using it directly so that if two dialogs of the same | ||
// type happen to show up, they can appear at the same time. (This is an edge case that | ||
// shouldn't happen often, but we can't prevent it from happening since everything is | ||
// asynchronous.) | ||
var dlg = $("." + dlgClass + ".template") | ||
.clone() | ||
.removeClass("template") | ||
.addClass("instance") | ||
.appendTo(document.body); | ||
|
||
// Set title and message | ||
$(".dialog-title", dlg).html(title); | ||
$(".dialog-message", dlg).html(message); | ||
|
||
var handleKeyDown = _handleKeyDown.bind(dlg); | ||
|
||
// Pipe dialog-closing notification back to client code | ||
dlg.one("hidden", function () { | ||
var buttonId = dlg.data("buttonId"); | ||
if (!buttonId) { // buttonId will be undefined if closed via Bootstrap's "x" button | ||
buttonId = DIALOG_BTN_CANCEL; | ||
} | ||
|
||
// Let call stack return before notifying that dialog has closed; this avoids issue #191 | ||
// if the handler we're triggering might show another dialog (as long as there's no | ||
// fade-out animation) | ||
setTimeout(function () { | ||
result.resolve(buttonId); | ||
}, 0); | ||
|
||
// Remove the dialog instance from the DOM. | ||
dlg.remove(); | ||
|
||
// Remove keydown event handler | ||
document.body.removeEventListener("keydown", handleKeyDown, true); | ||
}).one("shown", function () { | ||
// Set focus to the default button | ||
var primaryBtn = dlg.find(".primary"); | ||
|
||
if (primaryBtn) { | ||
primaryBtn.focus(); | ||
} | ||
|
||
// Listen for dialog keyboard shortcuts | ||
document.body.addEventListener("keydown", handleKeyDown, true); | ||
}); | ||
|
||
// Click handler for buttons | ||
dlg.one("click", ".dialog-button", function (e) { | ||
_dismissDialog(dlg, $(this).attr("data-button-id")); | ||
}); | ||
|
||
// Run the dialog | ||
dlg.modal({ | ||
backdrop: "static", | ||
show: true, | ||
keyboard: true | ||
}); | ||
return result; | ||
} | ||
|
||
/** | ||
* Immediately closes any dialog instances with the given class. The dialog callback for each instance will | ||
* be called with the special buttonId DIALOG_CANCELED (note: callback is run asynchronously). | ||
*/ | ||
function cancelModalDialogIfOpen(dlgClass) { | ||
$("." + dlgClass + ".instance").each(function (dlg) { | ||
if (dlg.is(":visible")) { // Bootstrap breaks if try to hide dialog that's already hidden | ||
_dismissDialog(dlg, DIALOG_CANCELED); | ||
} | ||
}); | ||
} | ||
|
||
exports.DIALOG_BTN_CANCEL = DIALOG_BTN_CANCEL; | ||
exports.DIALOG_BTN_OK = DIALOG_BTN_OK; | ||
exports.DIALOG_BTN_DONTSAVE = DIALOG_BTN_DONTSAVE; | ||
exports.DIALOG_CANCELED = DIALOG_CANCELED; | ||
|
||
exports.DIALOG_ID_ERROR = DIALOG_ID_ERROR; | ||
exports.DIALOG_ID_SAVE_CLOSE = DIALOG_ID_SAVE_CLOSE; | ||
exports.DIALOG_ID_EXT_CHANGED = DIALOG_ID_EXT_CHANGED; | ||
exports.DIALOG_ID_EXT_DELETED = DIALOG_ID_EXT_DELETED; | ||
|
||
exports.showModalDialog = showModalDialog; | ||
exports.cancelModalDialogIfOpen = cancelModalDialogIfOpen; | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We only want to stop the event if it is not targeted for the dialog. This keydown handler is called in the capture phase, so if we stop all events here, we would not be able to have editable text inside a dialog.