Merge pull request #223 from camptocamp/access_panelbutton
make it possible to create accessible panel buttons
This commit is contained in:
130
examples/accessible-panel.html
Normal file
130
examples/accessible-panel.html
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<title>Custom and accessible panel</title>
|
||||||
|
<link rel="stylesheet" href="../theme/default/style.css" type="text/css">
|
||||||
|
<link rel="stylesheet" href="style.css" type="text/css">
|
||||||
|
<style type="text/css">
|
||||||
|
|
||||||
|
.olControlPanel button {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
margin: 2px;
|
||||||
|
border: 1px solid;
|
||||||
|
padding: 0 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
height: 35px;
|
||||||
|
background-color: white;
|
||||||
|
float: left;
|
||||||
|
overflow: visible; /* needed to remove padding from buttons in IE */
|
||||||
|
}
|
||||||
|
.olControlPanel button span {
|
||||||
|
padding-left: 25px;
|
||||||
|
}
|
||||||
|
.olControlPanel button span:first-child {
|
||||||
|
padding-left: 0;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: 2px;
|
||||||
|
}
|
||||||
|
.olControlPanel .olControlDrawFeatureItemActive span:first-child {
|
||||||
|
background-image: url("../theme/default/img/draw_line_on.png");
|
||||||
|
height: 22px;
|
||||||
|
width: 24px;
|
||||||
|
top: 5px;
|
||||||
|
}
|
||||||
|
.olControlPanel .olControlDrawFeatureItemInactive span:first-child {
|
||||||
|
background-image: url("../theme/default/img/draw_line_off.png");
|
||||||
|
height: 22px;
|
||||||
|
width: 24px;
|
||||||
|
top: 5px;
|
||||||
|
}
|
||||||
|
.olControlPanel .olControlZoomBoxItemInactive span:first-child {
|
||||||
|
background-image: url("../img/drag-rectangle-off.png");
|
||||||
|
height: 29px;
|
||||||
|
width: 29px;
|
||||||
|
top: 2px;
|
||||||
|
}
|
||||||
|
.olControlPanel .olControlZoomBoxItemActive span:first-child {
|
||||||
|
background-image: url("../img/drag-rectangle-on.png");
|
||||||
|
height: 29px;
|
||||||
|
width: 29px;
|
||||||
|
top: 2px;
|
||||||
|
}
|
||||||
|
.olControlPanel .olControlZoomToMaxExtentItemInactive span:first-child {
|
||||||
|
background-image: url("../img/zoom-world-mini.png");
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
top: 8px;
|
||||||
|
}
|
||||||
|
.olControlPanel .navHistory span:first-child {
|
||||||
|
background-image: url("../theme/default/img/navigation_history.png");
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
top: 4px;
|
||||||
|
}
|
||||||
|
.olControlPanel .navHistoryPreviousItemActive span:first-child {
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
.olControlPanel .navHistoryPreviousItemInactive span:first-child {
|
||||||
|
background-position: 0 -24px;
|
||||||
|
}
|
||||||
|
.olControlPanel .navHistoryNextItemActive span:first-child {
|
||||||
|
background-position: -24px 0;
|
||||||
|
}
|
||||||
|
.olControlPanel .navHistoryNextItemInactive span:first-child {
|
||||||
|
background-position: -24px -24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<script src="../lib/OpenLayers.js"></script>
|
||||||
|
<script src="accessible-panel.js"></script>
|
||||||
|
</head>
|
||||||
|
<body onload="init()">
|
||||||
|
<h1 id="title">Custom and accessible panel</h1>
|
||||||
|
<div id="tags">
|
||||||
|
panels, CSS, style, accessibility, button
|
||||||
|
</div>
|
||||||
|
<p id="shortdesc">
|
||||||
|
Create a custom and accessible panel, styled entirely with
|
||||||
|
CSS.
|
||||||
|
</p>
|
||||||
|
<div id="panel"></div>
|
||||||
|
<div id="map" class="smallmap"></div>
|
||||||
|
|
||||||
|
<div id="docs">
|
||||||
|
|
||||||
|
<p>An accessible panel:
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>The buttons are actual HTML buttons. You can therefore
|
||||||
|
use the TAB key to give the focus to the panel's buttons, and the "ENTER"
|
||||||
|
key to activate or trigger the corresponding control.</li>
|
||||||
|
<li>The buttons include text and titles (displayed when a button
|
||||||
|
is hovered).</li>
|
||||||
|
<li>If you remove colors from the page (for example using FireFox's <a
|
||||||
|
href="https://addons.mozilla.org/en-US/firefox/addon/no-color/">No
|
||||||
|
Color extension</a>) the buttons are still visible, and
|
||||||
|
accessible using the keyboard.</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>By default a panel creates buttons as divs. In this example the
|
||||||
|
<code>createControlMarkup</code> panel function is overridden to create
|
||||||
|
a more accessible markup for the buttons. See the <a
|
||||||
|
href="accessible-panel.js" target="_blank"> accessible-panel.js
|
||||||
|
source</a> to see how this is done.</p>
|
||||||
|
|
||||||
|
<p>Note: in IE 8, when a button is pressed its content shifts by 1 pixel.
|
||||||
|
This is a <a
|
||||||
|
href="http://labs.findsubstance.com/2009/05/21/ie8-form-button-with-background-image-on-click-css-bug/">known
|
||||||
|
IE8 bug</a>, with known workarounds. No workaround is applied in this
|
||||||
|
example though.</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
64
examples/accessible-panel.js
Normal file
64
examples/accessible-panel.js
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
var lon = 5;
|
||||||
|
var lat = 40;
|
||||||
|
var zoom = 5;
|
||||||
|
var map, layer;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
map = new OpenLayers.Map( 'map', { controls: [] } );
|
||||||
|
layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
|
||||||
|
"http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
|
||||||
|
map.addLayer(layer);
|
||||||
|
|
||||||
|
vlayer = new OpenLayers.Layer.Vector( "Editable" );
|
||||||
|
map.addLayer(vlayer);
|
||||||
|
|
||||||
|
zb = new OpenLayers.Control.ZoomBox({
|
||||||
|
title: "Zoom box: zoom clicking and dragging",
|
||||||
|
text: "Zoom"
|
||||||
|
});
|
||||||
|
|
||||||
|
var panel = new OpenLayers.Control.Panel({
|
||||||
|
defaultControl: zb,
|
||||||
|
createControlMarkup: function(control) {
|
||||||
|
var button = document.createElement('button'),
|
||||||
|
iconSpan = document.createElement('span'),
|
||||||
|
textSpan = document.createElement('span');
|
||||||
|
iconSpan.innerHTML = ' ';
|
||||||
|
button.appendChild(iconSpan);
|
||||||
|
if (control.text) {
|
||||||
|
textSpan.innerHTML = control.text;
|
||||||
|
}
|
||||||
|
button.appendChild(textSpan);
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
panel.addControls([
|
||||||
|
zb,
|
||||||
|
new OpenLayers.Control.DrawFeature(vlayer, OpenLayers.Handler.Path,
|
||||||
|
{title:'Draw a feature', text: 'Draw'}),
|
||||||
|
new OpenLayers.Control.ZoomToMaxExtent({
|
||||||
|
title:"Zoom to the max extent",
|
||||||
|
text: "World"
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
nav = new OpenLayers.Control.NavigationHistory({
|
||||||
|
previousOptions: {
|
||||||
|
title: "Go to previous map position",
|
||||||
|
text: "Prev"
|
||||||
|
},
|
||||||
|
nextOptions: {
|
||||||
|
title: "Go to next map position",
|
||||||
|
text: "Next"
|
||||||
|
},
|
||||||
|
displayClass: "navHistory"
|
||||||
|
});
|
||||||
|
// parent control must be added to the map
|
||||||
|
map.addControl(nav);
|
||||||
|
panel.addControls([nav.next, nav.previous]);
|
||||||
|
|
||||||
|
map.addControl(panel);
|
||||||
|
|
||||||
|
map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
|
||||||
|
}
|
||||||
@@ -246,18 +246,16 @@ OpenLayers.Control.Panel = OpenLayers.Class(OpenLayers.Control, {
|
|||||||
}
|
}
|
||||||
this.controls = this.controls.concat(controls);
|
this.controls = this.controls.concat(controls);
|
||||||
|
|
||||||
// Give each control a panel_div which will be used later.
|
|
||||||
// Access to this div is via the panel_div attribute of the
|
|
||||||
// control added to the panel.
|
|
||||||
// Also, stop mousedowns and clicks, but don't stop mouseup,
|
|
||||||
// since they need to pass through.
|
|
||||||
for (var i=0, len=controls.length; i<len; i++) {
|
for (var i=0, len=controls.length; i<len; i++) {
|
||||||
var element = document.createElement("div");
|
var control = controls[i],
|
||||||
element.className = controls[i].displayClass + "ItemInactive olButton";
|
element = this.createControlMarkup(control);
|
||||||
controls[i].panel_div = element;
|
OpenLayers.Element.addClass(element,
|
||||||
if (controls[i].title != "") {
|
control.displayClass + "ItemInactive");
|
||||||
controls[i].panel_div.title = controls[i].title;
|
OpenLayers.Element.addClass(element, "olButton");
|
||||||
|
if (control.title != "" && !element.title) {
|
||||||
|
element.title = control.title;
|
||||||
}
|
}
|
||||||
|
control.panel_div = element;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.map) { // map.addControl() has already been called on the panel
|
if (this.map) { // map.addControl() has already been called on the panel
|
||||||
@@ -266,6 +264,40 @@ OpenLayers.Control.Panel = OpenLayers.Class(OpenLayers.Control, {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APIMethod: createControlMarkup
|
||||||
|
* This function just creates a div for the control. If specific HTML
|
||||||
|
* markup is needed this function can be overridden in specific classes,
|
||||||
|
* or at panel instantiation time:
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* (code)
|
||||||
|
* var panel = new OpenLayers.Control.Panel({
|
||||||
|
* defaultControl: control,
|
||||||
|
* // ovverride createControlMarkup to create actual buttons
|
||||||
|
* // including texts wrapped into span elements.
|
||||||
|
* createControlMarkup: function(control) {
|
||||||
|
* var button = document.createElement('button'),
|
||||||
|
* span = document.createElement('span');
|
||||||
|
* if (control.text) {
|
||||||
|
* span.innerHTML = control.text;
|
||||||
|
* }
|
||||||
|
* return button;
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
* (end)
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* control - {<OpenLayers.Control>} The control to create the HTML
|
||||||
|
* markup for.
|
||||||
|
*
|
||||||
|
* Returns:
|
||||||
|
* {DOMElement} The markup.
|
||||||
|
*/
|
||||||
|
createControlMarkup: function(control) {
|
||||||
|
return document.createElement("div");
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method: addControlsToMap
|
* Method: addControlsToMap
|
||||||
* Only for internal use in draw() and addControls() methods.
|
* Only for internal use in draw() and addControls() methods.
|
||||||
|
|||||||
@@ -21,6 +21,12 @@ OpenLayers.Event = {
|
|||||||
*/
|
*/
|
||||||
observers: false,
|
observers: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constant: KEY_SPACE
|
||||||
|
* {int}
|
||||||
|
*/
|
||||||
|
KEY_SPACE: 32,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constant: KEY_BACKSPACE
|
* Constant: KEY_BACKSPACE
|
||||||
* {int}
|
* {int}
|
||||||
@@ -388,7 +394,8 @@ OpenLayers.Events = OpenLayers.Class({
|
|||||||
"mousedown", "mouseup", "mousemove",
|
"mousedown", "mouseup", "mousemove",
|
||||||
"click", "dblclick", "rightclick", "dblrightclick",
|
"click", "dblclick", "rightclick", "dblrightclick",
|
||||||
"resize", "focus", "blur",
|
"resize", "focus", "blur",
|
||||||
"touchstart", "touchmove", "touchend"
|
"touchstart", "touchmove", "touchend",
|
||||||
|
"keydown"
|
||||||
],
|
],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ OpenLayers.Events.buttonclick = OpenLayers.Class({
|
|||||||
*/
|
*/
|
||||||
events: [
|
events: [
|
||||||
'mousedown', 'mouseup', 'click', 'dblclick',
|
'mousedown', 'mouseup', 'click', 'dblclick',
|
||||||
'touchstart', 'touchmove', 'touchend'
|
'touchstart', 'touchmove', 'touchend', 'keydown'
|
||||||
],
|
],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -97,6 +97,31 @@ OpenLayers.Events.buttonclick = OpenLayers.Class({
|
|||||||
delete this.target;
|
delete this.target;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method: getPressedButton
|
||||||
|
* Get the pressed button, if any. Returns undefined if no button
|
||||||
|
* was pressed.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* element - {DOMElement} The event target.
|
||||||
|
*
|
||||||
|
* Returns:
|
||||||
|
* {DOMElement} The button element, or undefined.
|
||||||
|
*/
|
||||||
|
getPressedButton: function(element) {
|
||||||
|
var depth = 3, // limit the search depth
|
||||||
|
button;
|
||||||
|
do {
|
||||||
|
if(OpenLayers.Element.hasClass(element, "olButton")) {
|
||||||
|
// hit!
|
||||||
|
button = element;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
element = element.parentNode;
|
||||||
|
} while(--depth > 0 && element);
|
||||||
|
return button;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method: buttonClick
|
* Method: buttonClick
|
||||||
* Check if a button was clicked, and fire the buttonclick event
|
* Check if a button was clicked, and fire the buttonclick event
|
||||||
@@ -108,15 +133,25 @@ OpenLayers.Events.buttonclick = OpenLayers.Class({
|
|||||||
var propagate = true,
|
var propagate = true,
|
||||||
element = OpenLayers.Event.element(evt);
|
element = OpenLayers.Event.element(evt);
|
||||||
if (element && (OpenLayers.Event.isLeftClick(evt) || !~evt.type.indexOf("mouse"))) {
|
if (element && (OpenLayers.Event.isLeftClick(evt) || !~evt.type.indexOf("mouse"))) {
|
||||||
if (element.nodeType === 3 || OpenLayers.Element.hasClass(element, "olAlphaImg")) {
|
// was a button pressed?
|
||||||
element = element.parentNode;
|
var button = this.getPressedButton(element);
|
||||||
}
|
if (button) {
|
||||||
if (OpenLayers.Element.hasClass(element, "olButton")) {
|
if (evt.type === "keydown") {
|
||||||
if (this.startEvt) {
|
switch (evt.keyCode) {
|
||||||
if (this.completeRegEx.test(evt.type)) {
|
case OpenLayers.Event.KEY_RETURN:
|
||||||
var pos = OpenLayers.Util.pagePosition(element);
|
case OpenLayers.Event.KEY_SPACE:
|
||||||
this.target.triggerEvent("buttonclick", {
|
this.target.triggerEvent("buttonclick", {
|
||||||
buttonElement: element,
|
buttonElement: button
|
||||||
|
});
|
||||||
|
OpenLayers.Event.stop(evt);
|
||||||
|
propagate = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (this.startEvt) {
|
||||||
|
if (this.completeRegEx.test(evt.type)) {
|
||||||
|
var pos = OpenLayers.Util.pagePosition(button);
|
||||||
|
this.target.triggerEvent("buttonclick", {
|
||||||
|
buttonElement: button,
|
||||||
buttonXY: {
|
buttonXY: {
|
||||||
x: this.startEvt.clientX - pos[0],
|
x: this.startEvt.clientX - pos[0],
|
||||||
y: this.startEvt.clientY - pos[1]
|
y: this.startEvt.clientY - pos[1]
|
||||||
|
|||||||
@@ -28,8 +28,43 @@
|
|||||||
events.destroy();
|
events.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function test_getPressedButton(t) {
|
||||||
|
t.plan(4);
|
||||||
|
|
||||||
|
// set up
|
||||||
|
|
||||||
|
events = new OpenLayers.Events({}, element);
|
||||||
|
buttonClick = new OpenLayers.Events.buttonclick(events);
|
||||||
|
|
||||||
|
var button = document.createElement('button'),
|
||||||
|
span1 = document.createElement('span'),
|
||||||
|
span2 = document.createElement('span'),
|
||||||
|
span3 = document.createElement('span');
|
||||||
|
button.className = 'olButton';
|
||||||
|
button.appendChild(span1);
|
||||||
|
span1.appendChild(span2);
|
||||||
|
span2.appendChild(span3);
|
||||||
|
|
||||||
|
t.ok(buttonClick.getPressedButton(button) === button,
|
||||||
|
'getPressedButton returns button when element is button');
|
||||||
|
t.ok(buttonClick.getPressedButton(span1) === button,
|
||||||
|
'getPressedButton returns button when element is button descendant level 1');
|
||||||
|
t.ok(buttonClick.getPressedButton(span2) === button,
|
||||||
|
'getPressedButton returns button when element is button descendant level 2');
|
||||||
|
t.eq(buttonClick.getPressedButton(span3), undefined,
|
||||||
|
'getPressedButton returns undefined when element is button descendant level 3');
|
||||||
|
|
||||||
|
// test
|
||||||
|
|
||||||
|
|
||||||
|
// tear down
|
||||||
|
|
||||||
|
buttonClick.destroy();
|
||||||
|
events.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
function test_ButtonClick_buttonClick(t) {
|
function test_ButtonClick_buttonClick(t) {
|
||||||
t.plan(23);
|
t.plan(27);
|
||||||
events = new OpenLayers.Events({}, element);
|
events = new OpenLayers.Events({}, element);
|
||||||
events.on({
|
events.on({
|
||||||
"buttonclick": logEvent,
|
"buttonclick": logEvent,
|
||||||
@@ -38,7 +73,8 @@
|
|||||||
"click": logEvent,
|
"click": logEvent,
|
||||||
"dblclick": logEvent,
|
"dblclick": logEvent,
|
||||||
"touchstart": logEvent,
|
"touchstart": logEvent,
|
||||||
"touchend": logEvent
|
"touchend": logEvent,
|
||||||
|
"keydown": logEvent
|
||||||
});
|
});
|
||||||
buttonClick = events.extensions["buttonclick"];
|
buttonClick = events.extensions["buttonclick"];
|
||||||
|
|
||||||
@@ -111,12 +147,26 @@
|
|||||||
t.eq(log[1].type, "buttonclick", "buttonclick for 2nd click IE");
|
t.eq(log[1].type, "buttonclick", "buttonclick for 2nd click IE");
|
||||||
|
|
||||||
// rightclick
|
// rightclick
|
||||||
log = []
|
log = [];
|
||||||
trigger({type: "mousedown", button: 2});
|
trigger({type: "mousedown", button: 2});
|
||||||
trigger({type: "mouseup", button: 2});
|
trigger({type: "mouseup", button: 2});
|
||||||
t.eq(log.length, 2, "two events fired for rightclick");
|
t.eq(log.length, 2, "two events fired for rightclick");
|
||||||
t.eq(log[0].type, "mousedown", "mousedown from rightclick goes through");
|
t.eq(log[0].type, "mousedown", "mousedown from rightclick goes through");
|
||||||
t.eq(log[1].type, "mouseup", "mouseup from rightclick goes through");
|
t.eq(log[1].type, "mouseup", "mouseup from rightclick goes through");
|
||||||
|
|
||||||
|
// keydown RETURN
|
||||||
|
log = [];
|
||||||
|
trigger({type: "keydown", keyCode: OpenLayers.Event.KEY_RETURN});
|
||||||
|
trigger({type: "click"});
|
||||||
|
t.eq(log.length, 1, "one event fired for RETURN keydown");
|
||||||
|
t.eq(log[0].type, "buttonclick", "buttonclick for RETURN keydown");
|
||||||
|
|
||||||
|
// keydown SPACE
|
||||||
|
log = [];
|
||||||
|
trigger({type: "keydown", keyCode: OpenLayers.Event.KEY_SPACE});
|
||||||
|
trigger({type: "click"});
|
||||||
|
t.eq(log.length, 1, "one event fired for SPACE keydown");
|
||||||
|
t.eq(log[0].type, "buttonclick", "buttonclick for SPACE keydown");
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
Reference in New Issue
Block a user