Commit aab1be28 authored by Elijah Byrd's avatar Elijah Byrd
Browse files

Added lots of functionality and cleaned up code. Ready for cross-browser,...

Added lots of functionality and cleaned up code. Ready for cross-browser, cross-application testing.
parent e6e6a4e0
......@@ -63,63 +63,55 @@ div#zoom-base {
border: none;
width: 100%;
height: 100%;
background-color: #4E4E4E;
}
.accessible_button_wrapper.pan-up {
top: 10px;
left: 44px;
}
button#pan-up {
mask: url('icons/ic_keyboard_arrow_up_black_24px.svg') no-repeat 0px 0px/100% 100%;
-webkit-mask: url('icons/ic_keyboard_arrow_up_black_24px.svg') no-repeat 0px 0px/100% 100%;
background: url('icons/ic_keyboard_arrow_up_black_24px.svg') no-repeat 0px 0px/100% 100%;
}
.accessible_button_wrapper.pan-lt {
top: 44px;
left: 10px;
}
button#pan-lt {
mask: url('icons/ic_keyboard_arrow_left_black_24px.svg') no-repeat 0px 0px/100% 100%;
-webkit-mask: url('icons/ic_keyboard_arrow_left_black_24px.svg') no-repeat 0px 0px/100% 100%;
background: url('icons/ic_keyboard_arrow_left_black_24px.svg') no-repeat 0px 0px/100% 100%;
}
.accessible_button_wrapper.pan-ctr {
top: 44px;
left: 44px;
}
button#pan-ctr {
mask: url('icons/ic_all_out_black_24px.svg') no-repeat 0px 0px/100% 100%;
-webkit-mask: url('icons/ic_all_out_black_24px.svg') no-repeat 0px 0px/100% 100%;
background: url('icons/ic_all_out_black_24px.svg') no-repeat 0px 0px/100% 100%;
}
.accessible_button_wrapper.pan-rt {
top: 44px;
left: 79px;
}
button#pan-rt {
mask: url('icons/ic_keyboard_arrow_right_black_24px.svg') no-repeat 0px 0px/100% 100%;
-webkit-mask: url('icons/ic_keyboard_arrow_right_black_24px.svg') no-repeat 0px 0px/100% 100%;
background: url('icons/ic_keyboard_arrow_right_black_24px.svg') no-repeat 0px 0px/100% 100%;
}
.accessible_button_wrapper.pan-dn {
top: 79px;
left: 44px;
}
button#pan-dn {
mask: url('icons/ic_keyboard_arrow_down_black_24px.svg') no-repeat 0px 0px/100% 100%;
-webkit-mask: url('icons/ic_keyboard_arrow_down_black_24px.svg') no-repeat 0px 0px/100% 100%;
background: url('icons/ic_keyboard_arrow_down_black_24px.svg') no-repeat 0px 0px/100% 100%;
}
.accessible_button_wrapper.zoom-in {
top: 6px;
left: 6px;
}
button#zoom-in {
mask: url('icons/ic_keyboard_arrow_up_black_24px.svg') no-repeat 0px 0px/100% 100%;
-webkit-mask: url('icons/ic_add_black_24px.svg') no-repeat 0px 0px/100% 100%;
background: url('icons/ic_add_black_24px.svg') no-repeat 0px 0px/100% 100%;
}
.accessible_button_wrapper.zoom-out {
top: 64px;
left: 6px;
}
button#zoom-out {
mask: url('icons/ic_remove_black_24px.svg') no-repeat 0px 0px/100% 100%;
-webkit-mask: url('icons/ic_remove_black_24px.svg') no-repeat 0px 0px/100% 100%;
background: url('icons/ic_remove_black_24px.svg') no-repeat 0px 0px/100% 100%;
}
button#pan-up:hover,
button#pan-up:focus,
......@@ -134,9 +126,22 @@ button#pan-dn:focus,
button#zoom-in:hover,
button#zoom-in:focus,
button#zoom-out:hover,
button#zoom-out:focus {
button#zoom-out:focus,
span.accessibilityInfoClose:hover,
span.accessibilityInfoClose:focus {
background-color: #0f0;
}
button.active {
background-position: 0 -48px !important;
.gm-style-iw {
z-index: 200;
}
span.accessibilityInfoClose {
background: url('icons/ic_close_black_24px.svg') no-repeat 0px 0px/100% 100%;
width: 100%;
height: 100%;
text-indent:-100em;
position: absolute;
right: 0px;
top: 0px;
width: 22px;
height: 22px;
}
\ No newline at end of file
// TODO: properly isolate these global variables so they don't cause trouble when dropped into other sites
var markers = [];
var infowindowsEnabled;
var accessibilityMap;
var accessibilityInfowindow;
var mapIdentifier;
var initialCenter;
var accessibility_keys = { // Define values for keycodes
var accessible_gmaps_keys = { // Define values for keycodes
backspace: 8,
tab: 9,
enter: 13,
......@@ -25,28 +28,65 @@ var accessibility_keys = { // Define values for keycodes
del: 46
};
function accessibility_onload(map, mapIdentifier_given, options){
/**
* Call this function after all markers have been added using accessible_gmaps_addMarker.
*
* This master function builds the accessible map shim by:
* - adding keyboard-accessible accessible map controls
* - creating and positioning marker overlays (for keyboard tabbing through markers)
* - (if enabled) creating a keyboard-accessible modal infowindow
* - adding listeners necessary to keep created objects up-to-date
*
* I might change this function so that marker divs are created by accessible_gmaps_addMarker,
* and just positioned in this function. I'm not sure which use case is more viable.
*/
function accessible_gmaps_onload(map, mapIdentifier_given, options){
console.log("Accessibility shim loaded!");
// Collect data for shim global variables
mapIdentifier = mapIdentifier_given;
accessibilityMap = map;
initialCenter = accessibilityMap.getCenter();
if(options.infowindows){
infowindowsEnabled = true;
console.log("Infowindows enabled!");
}
initialCenter = map.getCenter();
console.log(initialCenter);
accessibility_init_controls(map);
// Add listeners to reposition marker overlays
map.addListener('center_changed', function() {
accessible_gmaps_resetAllMarkers();
});
map.addListener('zoom_changed', function() {
accessible_gmaps_resetAllMarkers();
});
// We need a timeout - I'm not sure why. But the map bounds aren't caught up with us if we don't wait.
// Create controls and other objects necessary for shim
accessible_gmaps_init_controls(map);
if(infowindowsEnabled){
accessibilityInfowindow = new google.maps.InfoWindow();
}
// Create marker overlays
// We need a timeout - I'm not sure why. But the map bounds aren't caught up with us if we don't wait, and we need the map bounds set before we can position markers.
setTimeout(function(){
for(var i = 0; i < markers.length; i++){
marker = markers[i];
accessibility_create_marker_div(marker, i);
accessible_gmaps_create_marker_div(marker, i);
}
}, 2000);
}
function accessibility_init_controls(map){
/**
* Adds keyboard-accessible map controls to the map,
* and binds handlers to them for actual map controlling.
* Most of the logic here is Keith's, but it's been modified pretty heavily.
*/
function accessible_gmaps_init_controls(map){
// Markup for keyboard-accessible map controls - there might be better ways to create this, but this is what we have.
var markup = '<div id="map-controls">';
markup += '<div id="pan-base">';
markup += '<div class="accessible_button_wrapper pan-up"><button id="pan-up" class="map-bn">Up</button></div>';
markup += '<div class="accessible_button_wrapper pan-lt"><button id="pan-lt" class="map-bn">Left</button></div>';
......@@ -60,55 +100,46 @@ function accessibility_init_controls(map){
markup += '</div>';
markup += '</div>';
// Add controls to map
jQuery(mapIdentifier).append(markup);
// Bind event handlers to controls. These pan values can be adjusted to taste.
jQuery('button#pan-up').bind('click', function(e) {
jQuery(this).focus();
map.panBy(0, -88);
accessibility_resetAllMarkers();
return false;
});
jQuery('button#pan-lt').bind('click', function(e) {
jQuery(this).focus();
map.panBy(-112, 0);
accessibility_resetAllMarkers();
return false;
});
jQuery('button#pan-rt').bind('click', function(e) {
jQuery(this).focus();
map.panBy(112, 0);
accessibility_resetAllMarkers();
return false;
});
jQuery('button#pan-dn').bind('click', function(e) {
jQuery(this).focus();
map.panBy(0, 88);
accessibility_resetAllMarkers();
return false;
});
jQuery('button#pan-ctr').bind('click', function(e) {
jQuery(this).focus();
map.setCenter(initialCenter);
accessibility_resetAllMarkers();
return false;
});
jQuery('button#zoom-in').bind('click', function(e) {
jQuery(this).focus();
var zoomLevel = map.getZoom();
map.setZoom(zoomLevel + 1);
accessibility_resetAllMarkers();
return false;
});
jQuery('button#zoom-out').bind('click', function(e) {
jQuery(this).focus();
var zoomLevel = map.getZoom();
map.setZoom(zoomLevel - 1);
accessibility_resetAllMarkers();
return false;
});
jQuery('button.map-bn').bind('mousedown', function(e) {
......@@ -116,7 +147,7 @@ function accessibility_init_controls(map){
return true;
});
jQuery('button.map-bn').bind('keydown', function(e) {
if (e.keyCode == accessibility_keys.space || e.keyCode == accessibility_keys.enter) {
if (e.keyCode == accessible_gmaps_keys.space || e.keyCode == accessible_gmaps_keys.enter) {
jQuery(this).addClass('active');
}
return true;
......@@ -125,30 +156,150 @@ function accessibility_init_controls(map){
jQuery(this).removeClass('active');
return true;
});
jQuery(mapIdentifier).bind('mouseup', function(e) {
accessibility_resetAllMarkers();
return true;
});
}
function accessibility_create_marker_div(marker, markerIndex){
/**
* Creates an overlay div for the given map marker.
* This div can receive keyboard focus and allows
* keyboard users to browse the map with the tab key.
*/
function accessible_gmaps_create_marker_div(marker, markerIndex){
// Position a focusable div over the maker
var markerID = "mkr-" + markerIndex;
var title = marker.getTitle();
jQuery('div#map-controls').after('<div id="' + markerID + '" title="' + title + '" class="map-marker" tabindex="0"><span>' + title + '</span></div>');
jQuery('div#' + markerID).data('markerID', markerIndex)
jQuery('div#' + markerID).focus(accessibility_zUp);
jQuery('div#' + markerID).blur(accessibility_zDown);
jQuery('div#map-controls').after('<div id="' + markerID + '" title="' + title + '" class="map-marker" tabindex="0"><span>' + title + '</span></div>');
jQuery('div#' + markerID).data('markerID', markerIndex) // Data attribute is added to the div so its associated marker can be identified.
// Bind events to the marker overlay to manipulate its marker's z index.
jQuery('div#' + markerID).focus(accessible_gmaps_zUp);
jQuery('div#' + markerID).blur(accessible_gmaps_zDown);
accessibility_resetMarker(markerIndex);
// If the accessible infowindows module is enabled, bind handlers to open the modal infowindow.
if(infowindowsEnabled){
jQuery('div#' + markerID).keyup(function (e) {
if (e.keyCode == accessible_gmaps_keys.enter) {
accessible_gmaps_infowindows_openInfoWindow(markerIndex);
}
});
jQuery('div#' + markerID).click(function (e) {
accessible_gmaps_infowindows_openInfoWindow(markerIndex);
});
}
// Reposition marker (requires map bounds to be set)
accessible_gmaps_resetMarker(markerIndex);
}
/**
* Helper function to open the modal infowindow.
* It's assumed this function will only be called
* if the accessible infowindow module is enabled.
*
* TODO: push all marker shims down in z index, so they aren't visible through the infowindow
* TODO: find a better way to grab our infowindow (right now we rely on a class created by Google)
*/
function accessible_gmaps_infowindows_openInfoWindow(markerIndex){
// Grab map marker; we need its title and windowContent.
marker = markers[markerIndex];
header = "<h2 id='markerHeader-"+markerIndex+"'>"+marker.title+"</h2>";
// windowContent should have been added to the marker object by the client if they wanted to use the accessible infowindow module.
accessibilityInfowindow.setContent(header + marker.windowContent);
accessibilityInfowindow.open(accessibilityMap, marker);
// Grab our infowindow, apply necessary attributes and give it focus
// We're assuming here that ours is the only infowindow open on the map - it's the only way I can find right now to grab our window for accessibility modifications.
var windowWithContent = jQuery('div.gm-style-iw');
windowWithContent.attr('tabindex', 0);
windowWithContent.attr('aria-labelledby', "markerHeader-"+markerIndex);
windowWithContent.focus();
// Remove the default close button and add our own
var windowCloseButton = windowWithContent.next().children('img');
windowCloseButton.parent().remove();
windowWithContent.append("<span role='button' tabindex='0' class='accessibilityInfoClose'>Close infowindow</button>");
//accessible_gmaps_zUp()
// Attach event handlers to close button.
jQuery('span.accessibilityInfoClose').keyup(function (e) {
if (e.keyCode == accessible_gmaps_keys.enter) {
jQuery('div#mkr-' + markerIndex).focus();
//accessible_gmaps_zDown()
windowWithContent.parent().unbind('keydown');
accessibilityInfowindow.close();
}
});
jQuery('span.accessibilityInfoClose').click(function(e) {
jQuery('div#mkr-' + markerIndex).focus();
//accessible_gmaps_zDown()
windowWithContent.parent().unbind('keydown');
accessibilityInfowindow.close();
});
// Attach event handler to infowindow to keep focus inside window (modal-ish dialog)
windowWithContent.parent().keydown(function (e) {
accessible_gmaps_infowindows_trapTabKey(windowWithContent.parent(), e, markerIndex);
});
}
/**
* Helper function that traps keyboard focus inside a modal dialog.
* Modified from example at https://accessibility.oit.ncsu.edu/blog/2013/09/13/the-incredible-accessible-modal-dialog/
*/
function accessible_gmaps_infowindows_trapTabKey(object,evt, markerIndex) {
var focusableElementsString ="a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]";
// if tab or shift-tab pressed
if( evt.which == accessible_gmaps_keys.esc) {
jQuery('div#mkr-' + markerIndex).focus();
//accessible_gmaps_zDown()
object.unbind('keydown');
accessibilityInfowindow.close();
}
else if ( evt.which == accessible_gmaps_keys.tab ) {
// get list of all children elements in given object
var o = object.find('*');
// get list of focusable items
var focusableItems = o.filter(focusableElementsString).filter(':visible')
// get currently focused item
var focusedItem = jQuery(':focus');
// get the number of focusable items
var numberOfFocusableItems = focusableItems.length
// get the index of the currently focused item
var focusedItemIndex = focusableItems.index(focusedItem);
//back tab
if (evt.shiftKey) {
// if focused on first item and user preses back-tab, go to the last focusable item
if(focusedItemIndex==0){
focusableItems.get(numberOfFocusableItems-1).focus();
evt.preventDefault();
}
}
//forward tab
else {
// if focused on the last item and user preses tab, go to the first focusable item
if(focusedItemIndex==numberOfFocusableItems-1){
focusableItems.get(0).focus();
evt.preventDefault();
}
}
}
}
function accessibility_resetMarker(markerIndex){
/**
* Repositions marker overlay to sit over the gMaps marker.
*
* TODO: allow offset to be calculated based on marker size data retrieved from user?
*/
function accessible_gmaps_resetMarker(markerIndex){
marker = markers[markerIndex];
var offset = getMarkerPos(marker);
var offset = accessible_gmaps_getMarkerPos(marker);
// adjust offset for size of div
// These values should be adjusted based on your marker size.
offset.x = offset.x - 10;
offset.y = offset.y - 40;
......@@ -165,34 +316,55 @@ function accessibility_resetMarker(markerIndex){
jQuery('div#' + markerID).css('top', offset.y + 'px');
}
else {
console.log("Offset not in bounds!");
jQuery('div#' + markerID).css('display', 'none');
}
}
function accessibility_resetAllMarkers(){
/**
* Wrapper function for accessible_gmaps_resetMarker
* that resets all markers in the markers array.
*/
function accessible_gmaps_resetAllMarkers(){
for(var i = 0; i < markers.length; i++){
accessibility_resetMarker(i);
accessible_gmaps_resetMarker(i);
}
}
function accessibility_zUp(){
console.log("Calling zUp!");
/**
* Helper function that modifies the z index of a
* gmaps marker.
* TODO: generalize so this can be called elsewhere
*/
function accessible_gmaps_zUp(){
marker = markers[jQuery(this).data('markerID')];
marker.setZIndex(1000);
}
function accessibility_zDown(){
console.log("Calling zDown!");
/**
* Helper function that modifies the z index of a
* gmaps marker.
* TODO: generalize so this can be called elsewhere
*/
function accessible_gmaps_zDown(){
marker = markers[jQuery(this).data('markerID')];
marker.setZIndex(1);
}
function accessibility_addMarker(marker){
/**
* Function called by client to register a gMaps marker
* with this shim.
*/
function accessible_gmaps_addMarker(marker){
markers.push(marker);
}
function getMarkerPos(marker) {
/**
* Helper function to calculate the position of a marker overlay.
* Written by Keith and almost entirely unmodified.
*/
function accessible_gmaps_getMarkerPos(marker) {
var gmap = marker.map;
var scale = Math.pow(2, gmap.getZoom());
var nw = new google.maps.LatLng(
......
<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>
\ No newline at end of file
Supports Markdown
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