Commit 8a522b89 by Waheed Ahmed Committed by GitHub

Merge pull request #13245 from edx/waheed/ecom-4188-fix-focus-on-tab-remain-within-modal

Fixed keyboard focus remains within modal simulated dialogs.
parents e86ff28f 3c84b7a1
...@@ -372,7 +372,7 @@ $body-line-height: golden-ratio(.875em, 1) !default; ...@@ -372,7 +372,7 @@ $body-line-height: golden-ratio(.875em, 1) !default;
$base-font-color: rgb(60,60,60) !default; $base-font-color: rgb(60,60,60) !default;
$baseFontColor: $base-font-color; $baseFontColor: $base-font-color;
$lighter-base-font-color: rgb(100,100,100) $base-font-color; $lighter-base-font-color: rgb(100,100,100) !default;
$very-light-text: rgb(255,255,255) !default; $very-light-text: rgb(255,255,255) !default;
$text-color: rgb(51, 51, 51) !default; $text-color: rgb(51, 51, 51) !default;
......
...@@ -18,16 +18,17 @@ from xmodule.tabs import CourseTabList ...@@ -18,16 +18,17 @@ from xmodule.tabs import CourseTabList
<a href="#help-modal" rel="leanModal" role="button">${_("Help")}</a> <a href="#help-modal" rel="leanModal" role="button">${_("Help")}</a>
</div> </div>
<section id="help-modal" class="modal" aria-hidden="true" role="dialog" aria-label="${_("{platform_name} Help").format(platform_name=static.get_platform_name())}"> <section id="help-modal" class="modal" aria-hidden="true" role="dialog" tabindex="-1" aria-label="${_("{platform_name} Help").format(platform_name=static.get_platform_name())}">
<div class="inner-wrapper" id="help_wrapper"> <div class="inner-wrapper">
## TODO: find a way to refactor this ## TODO: find a way to refactor this
<button class="close-modal "tabindex="0"> <button class="close-modal" tabindex="0">
<span class="icon fa fa-remove" aria-hidden="true"></span> <span class="icon fa fa-remove" aria-hidden="true"></span>
<span class="sr"> <span class="sr">
## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen) ## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen)
${_('Close')} ${_('Close')}
</span> </span>
</button> </button>
<div id="help_wrapper">
<header> <header>
<h2> <h2>
...@@ -38,12 +39,12 @@ from xmodule.tabs import CourseTabList ...@@ -38,12 +39,12 @@ from xmodule.tabs import CourseTabList
<hr> <hr>
</header> </header>
<% <%
discussion_tab = CourseTabList.get_discussion(course) if course else None discussion_tab = CourseTabList.get_discussion(course) if course else None
discussion_link = discussion_tab.link_func(course, reverse) if (discussion_tab and discussion_tab.is_enabled(course, user=user)) else None discussion_link = discussion_tab.link_func(course, reverse) if (discussion_tab and discussion_tab.is_enabled(course, user=user)) else None
%> %>
% if discussion_link: % if discussion_link:
<p>${Text(_('For {strong_start}questions on course lectures, homework, tools, or materials for this course{strong_end}, post in the {link_start}course discussion forum{link_end}.')).format( <p>${Text(_('For {strong_start}questions on course lectures, homework, tools, or materials for this course{strong_end}, post in the {link_start}course discussion forum{link_end}.')).format(
strong_start=HTML('<strong>'), strong_start=HTML('<strong>'),
strong_end=HTML('</strong>'), strong_end=HTML('</strong>'),
...@@ -53,12 +54,12 @@ from xmodule.tabs import CourseTabList ...@@ -53,12 +54,12 @@ from xmodule.tabs import CourseTabList
link_end=HTML('</a>'), link_end=HTML('</a>'),
)} )}
</p> </p>
% endif % endif
<p>${Text(_('Have {strong_start}general questions about {platform_name}{strong_end}? You can find lots of helpful information in the {platform_name} {link_start}FAQ{link_end}.')).format( <p>${Text(_('Have {strong_start}general questions about {platform_name}{strong_end}? You can find lots of helpful information in the {platform_name} {link_start}FAQ{link_end}.')).format(
strong_start=HTML('<strong>'), strong_start=HTML('<strong>'),
strong_end=HTML('</strong>'), strong_end=HTML('</strong>'),
link_start=HTML('<a href="{url}" target="_blank">').format( link_start=HTML('<a href="{url}" id="feedback-faq-link" target="_blank">').format(
url=marketing_link('FAQ') url=marketing_link('FAQ')
), ),
link_end=HTML('</a>'), link_end=HTML('</a>'),
...@@ -84,48 +85,36 @@ from xmodule.tabs import CourseTabList ...@@ -84,48 +85,36 @@ from xmodule.tabs import CourseTabList
</div> </div>
<div class="inner-wrapper" id="feedback_form_wrapper"> <div id="feedback_form_wrapper">
<button class="close-modal">
<span class="icon fa fa-remove" aria-hidden="true"></span>
<span class="sr">
## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen)
${_('Close')}
</span>
</button>
<header></header> <header></header>
<form id="feedback_form" class="feedback_form" method="post" data-remote="true" action="/submit_feedback"> <form id="feedback_form" class="feedback_form" method="post" data-remote="true" action="/submit_feedback">
<div class="feedback-form-error" aria-live="polite">
<div id="feedback_error" class="modal-form-error" tabindex="-1"></div> <div id="feedback_error" class="modal-form-error" tabindex="-1"></div>
% if not user.is_authenticated(): </div>
% if not user.is_authenticated():
<label data-field="name" for="feedback_form_name">${_('Name')}*</label> <label data-field="name" for="feedback_form_name">${_('Name')}*</label>
<input name="name" type="text" id="feedback_form_name" aria-required="true"> <input name="name" type="text" id="feedback_form_name" required>
<label data-field="email" for="feedback_form_email">${_('E-mail')}*</label> <label data-field="email" for="feedback_form_email">${_('E-mail')}*</label>
<input name="email" type="text" id="feedback_form_email" aria-required="true"> <input name="email" type="text" id="feedback_form_email" required>
% endif % endif
<label data-field="subject" for="feedback_form_subject">${_('Briefly describe your issue')}*</label> <label data-field="subject" for="feedback_form_subject">${_('Briefly describe your issue')}*</label>
<input name="subject" type="text" id="feedback_form_subject" aria-required="true"> <input name="subject" type="text" id="feedback_form_subject" required>
<label data-field="details" for="feedback_form_details">${_('Tell us the details')}* <label data-field="details" for="feedback_form_details">${_('Tell us the details')}*</label>
<span class="tip">${_('Include error messages, steps which lead to the issue, etc')}</span></label> <span class="tip" id="feedback_form_details_tip">${_('Describe what you were doing when you encountered the issue. Include any details that will help us to troubleshoot, including error messages that you saw.')}</span>
<textarea name="details" id="feedback_form_details" aria-required="true"></textarea> <textarea name="details" id="feedback_form_details" required aria-describedby="feedback_form_details_tip"></textarea>
<input name="issue_type" type="hidden"> <input name="issue_type" type="hidden">
% if course: % if course:
<input name="course_id" type="hidden" value="${unicode(course.id)}"> <input name="course_id" type="hidden" value="${unicode(course.id)}">
% endif % endif
<div class="submit"> <div class="submit">
<input name="submit" type="submit" value="${_('Submit')}" id="feedback_submit"> <input name="submit" type="submit" value="${_('Submit')}" id="feedback_submit">
</div> </div>
</form> </form>
</div> </div>
<div class="inner-wrapper" id="feedback_success_wrapper" tabindex="0"> <div id="feedback_success_wrapper">
<button class="close-modal" tabindex="0">
<span class="icon fa fa-remove" aria-hidden="true"></span>
<span class="sr">
## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen)
${_('Close')}
</span>
</button>
<header> <header>
<h2>${_('Thank You!')}</h2> <h2>${_('Thank You!')}</h2>
...@@ -150,75 +139,124 @@ from xmodule.tabs import CourseTabList ...@@ -150,75 +139,124 @@ from xmodule.tabs import CourseTabList
)).format( )).format(
open_time=open_time, open_time=open_time,
close_time=close_time, close_time=close_time,
link_start=HTML('<a href="{}" target="_blank" id="feedback-faq-link" tabindex="0">').format(marketing_link('FAQ')), link_start=HTML('<a href="{}" target="_blank" id="success-feedback-faq-link">').format(marketing_link('FAQ')),
link_end=HTML('</a>') link_end=HTML('</a>')
)} )}
</p> </p>
</div> </div>
</div>
</section> </section>
<script type="text/javascript"> <script type="text/javascript">
(function() { (function() {
var onModalClose = function() { var $helpModal = $("#help-modal"),
$("#help-modal .close-modal").off("click"); $closeButton = $("#help-modal .close-modal"),
$("#lean_overlay").off("click"); $leanOverlay = $("#lean_overlay"),
$("#help-modal").attr("aria-hidden", "true"); $feedbackForm = $("#feedback_form"),
focusableChildren,
numElements,
currentIndex,
onModalClose = function() {
$closeButton.off("click");
$leanOverlay.off("click");
$helpModal.attr("aria-hidden", "true");
$('area,input,select,textarea,button').removeAttr('tabindex'); $('area,input,select,textarea,button').removeAttr('tabindex');
$(".help-tab a").focus(); $(".help-tab a").focus();
$leanOverlay.removeAttr('tabindex');
},
initializeTabKeyValues = function(element_name) {
focusableChildren = $(element_name).find('a, input[type=text], input[type=submit], select, textarea, button');
focusableChildren = focusableChildren.add($closeButton);
numElements = focusableChildren.length;
currentIndex = 0;
}, },
cycle_modal_tab = function(from_element_name, to_element_name) { focusElement = function() {
$(from_element_name).on('keydown', function(e) { var focusableElement = focusableChildren[currentIndex];
var keyCode = e.keyCode || e.which; if (focusableElement) {
if (keyCode === 9) { focusableElement.focus();
}
},
focusPrevious = function () {
currentIndex--;
if (currentIndex < 0) {
currentIndex = numElements - 1;
}
focusElement();
return false;
},
focusNext = function () {
currentIndex++;
if (currentIndex >= numElements) {
currentIndex = 0;
}
focusElement();
return false;
};
$helpModal.on('keydown', function (e) {
var keyCode = e.keyCode || e.which,
escapeKeyCode = 27,
tabKeyCode = 9;
if (keyCode === escapeKeyCode) {
e.preventDefault(); e.preventDefault();
$(to_element_name).focus(); $closeButton.click();
}
if (keyCode == tabKeyCode && e.shiftKey) {
e.preventDefault();
focusPrevious();
}
else if (keyCode == tabKeyCode) {
e.preventDefault();
focusNext();
} }
}); });
};
$helpModal.click(function () {
$helpModal.focus();
});
$(".help-tab").click(function() { $(".help-tab").click(function() {
initializeTabKeyValues("#help_wrapper");
$(".field-error").removeClass("field-error"); $(".field-error").removeClass("field-error");
$("#feedback_form")[0].reset(); $feedbackForm[0].reset();
$("#feedback_form input[type='submit']").removeAttr("disabled"); $("#feedback_form input[type='submit']").removeAttr("disabled");
$("#feedback_form_wrapper").css("display", "none"); $("#feedback_form_wrapper").css("display", "none");
$("#feedback_error").css("display", "none"); $("#feedback_error").css("display", "none");
$("#feedback_form_details_tip").css("display", "none");
$("#feedback_success_wrapper").css("display", "none"); $("#feedback_success_wrapper").css("display", "none");
$("#help_wrapper").css("display", "block"); $("#help_wrapper").css("display", "block");
$("#help-modal").attr("aria-hidden", "false"); $helpModal.attr("aria-hidden", "false");
$("#help-modal .close-modal").click(onModalClose); $closeButton.click(onModalClose);
$("#lean_overlay").click(onModalClose); $leanOverlay.click(onModalClose);
$("#help_wrapper .close-modal").focus(); $("button.close-modal").attr('tabindex', 0);
$closeButton.focus();
}); });
showFeedback = function(event, issue_type, title, subject_label, details_label) { showFeedback = function(event, issue_type, title, subject_label, details_label) {
$("#help_wrapper").css("display", "none"); $("#help_wrapper").css("display", "none");
initializeTabKeyValues("#feedback_form_wrapper");
$("#feedback_form input[name='issue_type']").val(issue_type); $("#feedback_form input[name='issue_type']").val(issue_type);
$("#feedback_form_wrapper").css("display", "block"); $("#feedback_form_wrapper").css("display", "block");
$("#feedback_form_wrapper header").html("<h2>" + title + "</h2><hr>"); $("#feedback_form_wrapper header").html("<h2>" + title + "</h2><hr>");
$("#feedback_form_wrapper label[data-field='subject']").html(subject_label); $("#feedback_form_wrapper label[data-field='subject']").html(subject_label);
$("#feedback_form_wrapper label[data-field='details']").html(details_label); $("#feedback_form_wrapper label[data-field='details']").html(details_label);
$("#feedback_form_wrapper .close-modal").focus(); $closeButton.focus();
event.preventDefault(); event.preventDefault();
}; };
cycle_modal_tab("#feedback_link_question", "#help_wrapper .close-modal");
cycle_modal_tab("#feedback_submit", "#feedback_form_wrapper .close-modal");
cycle_modal_tab("#feedback-faq-link", "#feedback_success_wrapper .close-modal");
$("#help-modal").on("keydown", function(e) {
var keyCode = e.keyCode || e.which;
if (keyCode === 27) {
e.preventDefault();
$("#help_wrapper .close-modal").click();
}
});
$("#feedback_link_problem").click(function(event) { $("#feedback_link_problem").click(function(event) {
$("#feedback_form_details_tip").css("display", "block");
showFeedback( showFeedback(
event, event,
"${_('problem') | n, js_escaped_string}", "${_('problem') | n, js_escaped_string}",
"${_('Report a Problem') | n, js_escaped_string}", "${_('Report a Problem') | n, js_escaped_string}",
"${_('Brief description of the problem') + '*' | n, js_escaped_string}" , "${_('Brief description of the problem') + '*' | n, js_escaped_string}" ,
"${Text(_('Details of the problem you are encountering{asterisk}{begin_span}Include error messages, steps which lead to the issue, etc.{end_span}')).format( "${Text(_('Details of the problem you are encountering{asterisk}')).format(
asterisk='*', asterisk='*',
begin_span=HTML('<span class=tip>'),
end_span=HTML('</span>'),
) | n, js_escaped_string}" ) | n, js_escaped_string}"
); );
}); });
...@@ -240,20 +278,20 @@ from xmodule.tabs import CourseTabList ...@@ -240,20 +278,20 @@ from xmodule.tabs import CourseTabList
"${_('Details') + '*' | n, js_escaped_string}" "${_('Details') + '*' | n, js_escaped_string}"
); );
}); });
$("#feedback_form").submit(function() { $feedbackForm.submit(function() {
$("input[type='submit']", this).attr("disabled", "disabled"); $("input[type='submit']", this).attr("disabled", "disabled");
$('area,input,select,textarea,button').attr('tabindex', -1); $closeButton.focus();
$("#feedback_form_wrapper .close-modal").focus();
}); });
$("#feedback_form").on("ajax:complete", function() { $feedbackForm.on("ajax:complete", function() {
$("input[type='submit']", this).removeAttr("disabled"); $("input[type='submit']", this).removeAttr("disabled");
}); });
$("#feedback_form").on("ajax:success", function(event, data, status, xhr) { $feedbackForm.on("ajax:success", function(event, data, status, xhr) {
$("#feedback_form_wrapper").css("display", "none"); $("#feedback_form_wrapper").css("display", "none");
$("#feedback_success_wrapper").css("display", "block"); $("#feedback_success_wrapper").css("display", "block");
$("#feedback_success_wrapper").focus(); initializeTabKeyValues("#feedback_success_wrapper");
$closeButton.focus();
}); });
$("#feedback_form").on("ajax:error", function(event, xhr, status, error) { $feedbackForm.on("ajax:error", function(event, xhr, status, error) {
$(".field-error").removeClass("field-error").removeAttr("aria-invalid"); $(".field-error").removeClass("field-error").removeAttr("aria-invalid");
var responseData; var responseData;
try { try {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment