Skip to content

Commit

Permalink
Fix oppia#4936: The dropdown menus work similar to what the about and…
Browse files Browse the repository at this point in the history
… profile dropdown behave with the keyboard. (oppia#5974)

* oppia#4936 adds tab and shift-tab navigation feature

* section now closes when opened with mouseclick

* Removed unwanted functions

* Fixed function issues - added necessary functions

* Added Service

* Added service to top-navigation-bar

* Added NavigationService to aboutMenu

* fixed linting errors

* removed spaces

* removed comments

* made rootScope accessible in html

* removed use of rootScope

* resolved merge conflict

* fixed pattern errors

* changed fileoverview
  • Loading branch information
geetchoudhary authored and bansalnitish committed Dec 28, 2018
1 parent ff94f82 commit 618344f
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ oppia.directive('topNavigationBar', [
controller: [
'$scope', '$http', '$window', '$timeout', '$translate',
'SidebarStatusService', 'LABEL_FOR_CLEARING_FOCUS', 'UserService',
'SiteAnalyticsService', 'WindowDimensionsService', 'DebouncerService',
'DeviceInfoService',
'SiteAnalyticsService', 'NavigationService', 'WindowDimensionsService',
'DebouncerService', 'DeviceInfoService',
function(
$scope, $http, $window, $timeout, $translate,
SidebarStatusService, LABEL_FOR_CLEARING_FOCUS, UserService,
SiteAnalyticsService, WindowDimensionsService, DebouncerService,
DeviceInfoService) {
SiteAnalyticsService, NavigationService, WindowDimensionsService,
DebouncerService, DeviceInfoService) {
$scope.isModerator = null;
$scope.isAdmin = null;
$scope.isSuperAdmin = null;
Expand Down Expand Up @@ -84,24 +84,7 @@ oppia.directive('topNavigationBar', [
$scope.LABEL_FOR_CLEARING_FOCUS = LABEL_FOR_CLEARING_FOCUS;
$scope.newStructuresEnabled = constants.ENABLE_NEW_STRUCTURE_EDITORS;
$scope.getStaticImageUrl = UrlInterpolationService.getStaticImageUrl;
$scope.activeMenuName = '';
$scope.logoutUrl = GLOBALS.logoutUrl;
$scope.ACTION_OPEN = 'open';
$scope.ACTION_CLOSE = 'close';
$scope.KEYBOARD_EVENT_TO_KEY_CODES = {
enter: {
shiftKeyIsPressed: false,
keyCode: 13
},
tab: {
shiftKeyIsPressed: false,
keyCode: 9
},
shiftTab: {
shiftKeyIsPressed: true,
keyCode: 9
}
};
$scope.userMenuIsShown = ($scope.currentUrl !== NAV_MODE_SIGNUP);
$scope.standardNavIsShown = (
NAV_MODES_WITH_CUSTOM_LOCAL_NAV.indexOf($scope.currentUrl) === -1);
Expand All @@ -119,7 +102,10 @@ oppia.directive('topNavigationBar', [
$scope.onLogoutButtonClicked = function() {
$window.localStorage.removeItem('last_uploaded_audio_lang');
};

$scope.ACTION_OPEN = NavigationService.ACTION_OPEN;
$scope.ACTION_CLOSE = NavigationService.ACTION_CLOSE;
$scope.KEYBOARD_EVENT_TO_KEY_CODES =
NavigationService.KEYBOARD_EVENT_TO_KEY_CODES;
/**
* Opens the submenu.
* @param {object} evt
Expand All @@ -128,8 +114,7 @@ oppia.directive('topNavigationBar', [
*/
$scope.openSubmenu = function(evt, menuName) {
// Focus on the current target before opening its submenu.
angular.element(evt.currentTarget).focus();
$scope.activeMenuName = menuName;
NavigationService.openSubmenu(evt, menuName);
};
$scope.blurNavigationLinks = function(evt) {
// This is required because if about submenu is in open state
Expand All @@ -139,9 +124,7 @@ oppia.directive('topNavigationBar', [
$('nav a').blur();
};
$scope.closeSubmenu = function(evt) {
$scope.activeMenuName = '';
angular.element(evt.currentTarget).closest('li')
.find('a').blur();
NavigationService.closeSubmenu(evt);
};
$scope.closeSubmenuIfNotMobile = function(evt) {
if (DeviceInfoService.isMobileDevice()) {
Expand All @@ -161,23 +144,10 @@ oppia.directive('topNavigationBar', [
* onMenuKeypress($event, 'aboutMenu', {enter: 'open'})
*/
$scope.onMenuKeypress = function(evt, menuName, eventsTobeHandled) {
var targetEvents = Object.keys(eventsTobeHandled);
for (var i = 0; i < targetEvents.length; i++) {
var keyCodeSpec =
$scope.KEYBOARD_EVENT_TO_KEY_CODES[targetEvents[i]];
if (keyCodeSpec.keyCode === evt.keyCode &&
evt.shiftKey === keyCodeSpec.shiftKeyIsPressed) {
if (eventsTobeHandled[targetEvents[i]] === $scope.ACTION_OPEN) {
$scope.openSubmenu(evt, menuName);
} else if (eventsTobeHandled[targetEvents[i]] ===
$scope.ACTION_CLOSE) {
$scope.closeSubmenu(evt);
} else {
throw Error('Invalid action type.');
}
}
}
NavigationService.onMenuKeypress(evt, menuName, eventsTobeHandled);
$scope.activeMenuName = NavigationService.activeMenuName;
};

// Close the submenu if focus or click occurs anywhere outside of
// the menu or outside of its parent (which opens submenu on hover).
angular.element(document).on('click', function(evt) {
Expand Down
1 change: 1 addition & 0 deletions core/templates/dev/head/pages/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ <h2>

<script src="/templates/dev/head/services/AlertsService.js"></script>
<script src="/templates/dev/head/services/ContextService.js"></script>
<script src="/templates/dev/head/services/NavigationService.js"></script>
<script src="/templates/dev/head/services/UtilsService.js"></script>
<script src="/templates/dev/head/services/DebouncerService.js"></script>
<script src="/templates/dev/head/services/DateTimeFormatService.js"></script>
Expand Down
35 changes: 33 additions & 2 deletions core/templates/dev/head/pages/library/SearchBarDirective.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ oppia.directive('searchBar', [
'search_bar_directive.html'),
controller: [
'$scope', '$rootScope', '$timeout', '$window', '$location',
'$translate', 'SearchService', 'DebouncerService', 'HtmlEscaperService',
'$translate', 'SearchService', 'NavigationService',
'DebouncerService', 'HtmlEscaperService',
'UrlService', 'ConstructTranslationIdsService',
function(
$scope, $rootScope, $timeout, $window, $location,
$translate, SearchService, DebouncerService, HtmlEscaperService,
$translate, SearchService, NavigationService,
DebouncerService, HtmlEscaperService,
UrlService, ConstructTranslationIdsService) {
$scope.isSearchInProgress = SearchService.isSearchInProgress;
$scope.SEARCH_DROPDOWN_CATEGORIES = (
Expand All @@ -43,6 +45,35 @@ oppia.directive('searchBar', [
}
)
);
$scope.ACTION_OPEN = NavigationService.ACTION_OPEN;
$scope.ACTION_CLOSE = NavigationService.ACTION_CLOSE;
$scope.KEYBOARD_EVENT_TO_KEY_CODES =
NavigationService.KEYBOARD_EVENT_TO_KEY_CODES;
/**
* Opens the submenu.
* @param {object} evt
* @param {String} menuName - name of menu, on which
* open/close action to be performed (category,language).
*/
$scope.openSubmenu = function(evt, menuName) {
NavigationService.openSubmenu(evt, menuName);
};
/**
* Handles keydown events on menus.
* @param {object} evt
* @param {String} menuName - name of menu to perform action
* on(category/language)
* @param {object} eventsTobeHandled - Map keyboard events('Enter') to
* corresponding actions to be performed(open/close).
*
* @example
* onMenuKeypress($event, 'category', {enter: 'open'})
*/

$scope.onMenuKeypress = function(evt, menuName, eventsTobeHandled){
NavigationService.onMenuKeypress(evt, menuName, eventsTobeHandled);
$scope.activeMenuName = NavigationService.activeMenuName;
};
// TODO(sll): Remove the filter once the App Engine Search API
// supports 3-letter language codes.
$scope.ALL_LANGUAGE_CODES = GLOBALS.LANGUAGE_CODES_AND_NAMES.filter(
Expand Down
1 change: 1 addition & 0 deletions core/templates/dev/head/pages/library/library.html
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ <h2 ng-class="{'active': activeGroupIndex === $index}" class="oppia-library-grou

{% block footer_js %}
{{ super() }}
<script src="/templates/dev/head/services/NavigationService.js"></script>
<script src="/templates/dev/head/services/SearchService.js"></script>
<script src="/templates/dev/head/services/SiteAnalyticsService.js"></script>
<script src="/templates/dev/head/components/RatingComputationService.js"></script>
Expand Down
48 changes: 40 additions & 8 deletions core/templates/dev/head/pages/library/search_bar_directive.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
</div>

<div class="oppia-same-row-container">
<div uib-dropdown class="navbar-left oppia-navbar-button-container oppia-search-bar-category-selector protractor-test-search-bar-category-selector">
<button uib-dropdown-toggle type="button" class="btn protractor-test-search-bar-dropdown-toggle oppia-search-bar-input ng-cloak"
<div ng-class="{'open' : activeMenuName === 'category'}" uib-dropdown class="navbar-left oppia-navbar-button-container oppia-search-bar-category-selector protractor-test-search-bar-category-selector">
<button ng-click="openSubmenu($event, 'category')" ng-keydown="onMenuKeypress($event, 'category', {shiftTab: ACTION_CLOSE, enter: ACTION_OPEN})" uib-dropdown-toggle type="button" class="btn protractor-test-search-bar-dropdown-toggle oppia-search-bar-input ng-cloak"
title="<[selectionDetails.categories.description | translate]>"
style="max-width: 130px;">
<[categoryButtonText|truncate:14]>
Expand All @@ -28,7 +28,15 @@
<ul uib-dropdown-menu class="protractor-test-search-bar-dropdown-menu" role="menu" style="max-height: 400px; overflow: auto;" ng-click="$event.stopPropagation()">
<li ng-repeat="item in selectionDetails.categories.masterList track by $index"
ng-if="selectionDetails.categories.selections[item.id]">
<a href="" ng-click="toggleSelection('categories', item.id)">
<a href="" ng-click="toggleSelection('categories', item.id)" ng-if="!$first && !$last">
<span translate="<[item.text]>" class="protractor-test-selected"></span>
<i ng-if="selectionDetails.categories.selections[item.id]" class="material-icons md-18 pull-right oppia-search-bar-category-selection-symbol">&#xE876;</i>
</a>
<a href="" ng-click="toggleSelection('categories', item.id)" ng-if="$first" ng-keydown="onMenuKeypress($event, 'category', {shiftTab: ACTION_CLOSE})">
<span translate="<[item.text]>" class="protractor-test-selected"></span>
<i ng-if="selectionDetails.categories.selections[item.id]" class="material-icons md-18 pull-right oppia-search-bar-category-selection-symbol">&#xE876;</i>
</a>
<a href="" ng-click="toggleSelection('categories', item.id)" ng-if="$last" ng-keydown="onMenuKeypress($event, 'category', {tab: ACTION_CLOSE})">
<span translate="<[item.text]>" class="protractor-test-selected"></span>
<i ng-if="selectionDetails.categories.selections[item.id]" class="material-icons md-18 pull-right oppia-search-bar-category-selection-symbol">&#xE876;</i>
</a>
Expand All @@ -39,16 +47,24 @@
<hr ng-if="selectionDetails.categories.numSelections > 0" style="margin: 2px;">
<li ng-repeat="item in selectionDetails.categories.masterList track by $index"
ng-if="!selectionDetails.categories.selections[item.id]">
<a href="" ng-click="toggleSelection('categories', item.id)">
<a href="" ng-click="toggleSelection('categories', item.id)" ng-if="!$first && !$last">
<span translate="<[item.text]>" class="protractor-test-deselected"></span>
<i ng-if="selectionDetails.categories.selections[item.id]" class="material-icons md-18 pull-right oppia-search-bar-category-selection-symbol">&#xE876;</i>
</a>
<a href="" ng-click="toggleSelection('categories', item.id)" ng-if="$first" ng-keydown="onMenuKeypress($event, 'category', {shiftTab: ACTION_CLOSE})">
<span translate="<[item.text]>" class="protractor-test-deselected"></span>
<i ng-if="selectionDetails.categories.selections[item.id]" class="material-icons md-18 pull-right oppia-search-bar-category-selection-symbol">&#xE876;</i>
</a>
<a href="" ng-click="toggleSelection('categories', item.id)" ng-if="$last" ng-keydown="onMenuKeypress($event, 'category', {tab: ACTION_CLOSE})">
<span translate="<[item.text]>" class="protractor-test-deselected"></span>
<i ng-if="selectionDetails.categories.selections[item.id]" class="material-icons md-18 pull-right oppia-search-bar-category-selection-symbol">&#xE876;</i>
</a>
</li>
</ul>
</div>
</div>
<div uib-dropdown class="navbar-left oppia-navbar-button-container oppia-search-bar-language-selector protractor-test-search-bar-language-selector">
<button uib-dropdown-toggle type="button" class="btn protractor-test-search-bar-dropdown-toggle oppia-search-bar-input ng-cloak"
<div ng-class="{'open' : activeMenuName === 'language'}" uib-dropdown class="navbar-left oppia-navbar-button-container oppia-search-bar-language-selector protractor-test-search-bar-language-selector">
<button ng-click="openSubmenu($event, 'language')" ng-keydown="onMenuKeypress($event, 'language', {shiftTab: ACTION_CLOSE, enter: ACTION_OPEN})" uib-dropdown-toggle type="button" class="btn protractor-test-search-bar-dropdown-toggle oppia-search-bar-input ng-cloak"
style="border-bottom-right-radius: 4px; border-top-right-radius: 4px; max-width: 130px;"
title="<[selectionDetails.languageCodes.description | translate]>">
<[languageButtonText|truncate:14]>
Expand All @@ -58,7 +74,15 @@
<ul uib-dropdown-menu class="protractor-test-search-bar-dropdown-menu" role="menu" style="max-height: 400px; overflow: auto;" ng-click="$event.stopPropagation()">
<li ng-repeat="item in selectionDetails.languageCodes.masterList track by $index"
ng-if="selectionDetails.languageCodes.selections[item.id]">
<a href="" ng-click="toggleSelection('languageCodes', item.id)">
<a href="" ng-click="toggleSelection('languageCodes', item.id)" ng-if="!$first && !$last">
<span translate="<[item.text]>" class="protractor-test-selected"></span>
<i ng-if="selectionDetails.languageCodes.selections[item.id]" class="material-icons md-18 pull-right oppia-search-bar-category-selection-symbol">&#xE876;</i>
</a>
<a href="" ng-click="toggleSelection('languageCodes', item.id)" ng-if="$first" ng-keydown="onMenuKeypress($event, 'language', {shiftTab: ACTION_CLOSE})">
<span translate="<[item.text]>" class="protractor-test-selected"></span>
<i ng-if="selectionDetails.languageCodes.selections[item.id]" class="material-icons md-18 pull-right oppia-search-bar-category-selection-symbol">&#xE876;</i>
</a>
<a href="" ng-click="toggleSelection('languageCodes', item.id)" ng-if="$last" ng-keydown="onMenuKeypress($event, 'language', {tab: ACTION_CLOSE})">
<span translate="<[item.text]>" class="protractor-test-selected"></span>
<i ng-if="selectionDetails.languageCodes.selections[item.id]" class="material-icons md-18 pull-right oppia-search-bar-category-selection-symbol">&#xE876;</i>
</a>
Expand All @@ -69,7 +93,15 @@
<hr ng-if="selectionDetails.languageCodes.numSelections > 0" style="margin: 2px;">
<li ng-repeat="item in selectionDetails.languageCodes.masterList track by $index"
ng-if="!selectionDetails.languageCodes.selections[item.id]">
<a href="" ng-click="toggleSelection('languageCodes', item.id)">
<a href="" ng-click="toggleSelection('languageCodes', item.id)" ng-if="!$first && !$last">
<span translate="<[item.text]>" class="protractor-test-deselected"></span>
<i ng-if="selectionDetails.languageCodes.selections[item.id]" class="material-icons md-18 pull-right oppia-search-bar-category-selection-symbol">&#xE876;</i>
</a>
<a href="" ng-click="toggleSelection('languageCodes', item.id)" ng-if="$first" ng-keydown="onMenuKeypress($event, 'language', {shiftTab: ACTION_CLOSE})">
<span translate="<[item.text]>" class="protractor-test-deselected"></span>
<i ng-if="selectionDetails.languageCodes.selections[item.id]" class="material-icons md-18 pull-right oppia-search-bar-category-selection-symbol">&#xE876;</i>
</a>
<a href="" ng-click="toggleSelection('languageCodes', item.id)" ng-if="$last" ng-keydown="onMenuKeypress($event, 'language', {tab: ACTION_CLOSE})">
<span translate="<[item.text]>" class="protractor-test-deselected"></span>
<i ng-if="selectionDetails.languageCodes.selections[item.id]" class="material-icons md-18 pull-right oppia-search-bar-category-selection-symbol">&#xE876;</i>
</a>
Expand Down
84 changes: 84 additions & 0 deletions core/templates/dev/head/services/NavigationService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright 2018 The Oppia Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @fileoverview Factory for navigating the top navigation bar with
* tab and shift-tab.
*/
oppia.factory('NavigationService', [function(){
var navigation = {};
navigation.activeMenuName = '';
navigation.ACTION_OPEN = 'open';
navigation.ACTION_CLOSE = 'close';
navigation.KEYBOARD_EVENT_TO_KEY_CODES = {
enter: {
shiftKeyIsPressed: false,
keyCode: 13
},
tab: {
shiftKeyIsPressed: false,
keyCode: 9
},
shiftTab: {
shiftKeyIsPressed: true,
keyCode: 9
}
};
/**
* Opens the submenu.
* @param {object} evt
* @param {String} menuName - name of menu, on which
* open/close action to be performed (category,language).
*/
navigation.openSubmenu = function(evt, menuName) {
// Focus on the current target before opening its submenu.
navigation.activeMenuName = menuName;
angular.element(evt.currentTarget).focus();
};
navigation.closeSubmenu = function(evt) {
navigation.activeMenuName = '';
angular.element(evt.currentTarget).closest('li')
.find('a').blur();
};
/**
* Handles keydown events on menus.
* @param {object} evt
* @param {String} menuName - name of menu to perform action
* on(category/language)
* @param {object} eventsTobeHandled - Map keyboard events('Enter') to
* corresponding actions to be performed(open/close).
*
* @example
* onMenuKeypress($event, 'category', {enter: 'open'})
*/
navigation.onMenuKeypress = function(evt, menuName, eventsTobeHandled) {
var targetEvents = Object.keys(eventsTobeHandled);
for (var i = 0; i < targetEvents.length; i++) {
var keyCodeSpec =
navigation.KEYBOARD_EVENT_TO_KEY_CODES[targetEvents[i]];
if (keyCodeSpec.keyCode === evt.keyCode &&
evt.shiftKey === keyCodeSpec.shiftKeyIsPressed) {
if (eventsTobeHandled[targetEvents[i]] === navigation.ACTION_OPEN) {
navigation.openSubmenu(evt, menuName);
} else if (eventsTobeHandled[targetEvents[i]] ===
navigation.ACTION_CLOSE) {
navigation.closeSubmenu(evt);
} else {
throw Error('Invalid action type.');
}
}
}
};
return navigation;
}]);

0 comments on commit 618344f

Please sign in to comment.