diff --git a/examples/accessible-panel.html b/examples/accessible-panel.html new file mode 100644 index 0000000000..d46d4fbfee --- /dev/null +++ b/examples/accessible-panel.html @@ -0,0 +1,130 @@ + + + + + + + Custom and accessible panel + + + + + + + +

Custom and accessible panel

+
+ panels, CSS, style, accessibility, button +
+

+ Create a custom and accessible panel, styled entirely with + CSS. +

+
+
+ +
+ +

An accessible panel: + +

+

+ +

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"); }