+ Create a custom and accessible panel, styled entirely with
+ CSS.
+
+
+
+
+
+
+
An accessible panel:
+
+
+
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.
+
The buttons include text and titles (displayed when a button
+ is hovered).
+
If you remove colors from the page (for example using FireFox's No
+ Color extension) the buttons are still visible, and
+ accessible using the keyboard.
+
+
+
+
By default a panel creates buttons as divs. In this example the
+ createControlMarkup panel function is overridden to create
+ a more accessible markup for the buttons. See the accessible-panel.js
+ source to see how this is done.
+
+
Note: in IE 8, when a button is pressed its content shifts by 1 pixel.
+ This is a known
+ IE8 bug, with known workarounds. No workaround is applied in this
+ example though.
+
+
+
+
+
diff --git a/examples/accessible-panel.js b/examples/accessible-panel.js
new file mode 100644
index 0000000000..f982fc624b
--- /dev/null
+++ b/examples/accessible-panel.js
@@ -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);
+}
diff --git a/lib/OpenLayers/Control/Panel.js b/lib/OpenLayers/Control/Panel.js
index b04f6c3863..f141671445 100644
--- a/lib/OpenLayers/Control/Panel.js
+++ b/lib/OpenLayers/Control/Panel.js
@@ -245,26 +245,58 @@ OpenLayers.Control.Panel = OpenLayers.Class(OpenLayers.Control, {
controls = [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} The control to create the HTML
+ * markup for.
+ *
+ * Returns:
+ * {DOMElement} The markup.
+ */
+ createControlMarkup: function(control) {
+ return document.createElement("div");
+ },
/**
* Method: addControlsToMap
diff --git a/lib/OpenLayers/Events.js b/lib/OpenLayers/Events.js
index 907ced8109..ea46bf3cba 100644
--- a/lib/OpenLayers/Events.js
+++ b/lib/OpenLayers/Events.js
@@ -20,6 +20,12 @@ OpenLayers.Event = {
* element._eventCacheID
*/
observers: false,
+
+ /**
+ * Constant: KEY_SPACE
+ * {int}
+ */
+ KEY_SPACE: 32,
/**
* Constant: KEY_BACKSPACE
@@ -388,7 +394,8 @@ OpenLayers.Events = OpenLayers.Class({
"mousedown", "mouseup", "mousemove",
"click", "dblclick", "rightclick", "dblrightclick",
"resize", "focus", "blur",
- "touchstart", "touchmove", "touchend"
+ "touchstart", "touchmove", "touchend",
+ "keydown"
],
/**
diff --git a/lib/OpenLayers/Events/buttonclick.js b/lib/OpenLayers/Events/buttonclick.js
index 351248bf0c..6520b83a0d 100644
--- a/lib/OpenLayers/Events/buttonclick.js
+++ b/lib/OpenLayers/Events/buttonclick.js
@@ -39,7 +39,7 @@ OpenLayers.Events.buttonclick = OpenLayers.Class({
*/
events: [
'mousedown', 'mouseup', 'click', 'dblclick',
- 'touchstart', 'touchmove', 'touchend'
+ 'touchstart', 'touchmove', 'touchend', 'keydown'
],
/**
@@ -97,6 +97,31 @@ OpenLayers.Events.buttonclick = OpenLayers.Class({
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
* Check if a button was clicked, and fire the buttonclick event
@@ -108,15 +133,25 @@ OpenLayers.Events.buttonclick = OpenLayers.Class({
var propagate = true,
element = OpenLayers.Event.element(evt);
if (element && (OpenLayers.Event.isLeftClick(evt) || !~evt.type.indexOf("mouse"))) {
- if (element.nodeType === 3 || OpenLayers.Element.hasClass(element, "olAlphaImg")) {
- element = element.parentNode;
- }
- if (OpenLayers.Element.hasClass(element, "olButton")) {
- if (this.startEvt) {
- if (this.completeRegEx.test(evt.type)) {
- var pos = OpenLayers.Util.pagePosition(element);
+ // was a button pressed?
+ var button = this.getPressedButton(element);
+ if (button) {
+ if (evt.type === "keydown") {
+ switch (evt.keyCode) {
+ case OpenLayers.Event.KEY_RETURN:
+ case OpenLayers.Event.KEY_SPACE:
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: {
x: this.startEvt.clientX - pos[0],
y: this.startEvt.clientY - pos[1]
diff --git a/tests/Events/buttonclick.html b/tests/Events/buttonclick.html
index 9aff9b89b8..35ebb81f8a 100644
--- a/tests/Events/buttonclick.html
+++ b/tests/Events/buttonclick.html
@@ -27,9 +27,44 @@
buttonClick.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) {
- t.plan(23);
+ t.plan(27);
events = new OpenLayers.Events({}, element);
events.on({
"buttonclick": logEvent,
@@ -38,7 +73,8 @@
"click": logEvent,
"dblclick": logEvent,
"touchstart": logEvent,
- "touchend": logEvent
+ "touchend": logEvent,
+ "keydown": logEvent
});
buttonClick = events.extensions["buttonclick"];
@@ -111,12 +147,26 @@
t.eq(log[1].type, "buttonclick", "buttonclick for 2nd click IE");
// rightclick
- log = []
+ log = [];
trigger({type: "mousedown", button: 2});
trigger({type: "mouseup", button: 2});
t.eq(log.length, 2, "two events fired for rightclick");
t.eq(log[0].type, "mousedown", "mousedown 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");
}