Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds ability to wrap geometry subtypes with span and a elements + a element functionality within coordinates #371

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@
/bower_components/
/node_modules/
.idea/
*.iml
*.iml
test.html
51 changes: 44 additions & 7 deletions demo/canada.mapml

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@
<body>
<mapml-viewer projection="CBMTILE" zoom="2" lat="45" lon="-90" controls>
<layer- label="CBMT" src="https://geogratis.gc.ca/mapml/en/cbmtile/cbmt/" checked></layer->
<layer- label="Restaurants" src="demo/restaurants.mapml" checked></layer->
<!-- <layer- label="Restaurants" src="demo/restaurants.mapml" checked></layer->-->
<layer- label="Links Testing" src="demo/canada.mapml" checked></layer->
</mapml-viewer>
</body>
</html>
</html>
106 changes: 69 additions & 37 deletions src/mapml/features/feature.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@
* ];
*/
export var Feature = L.Path.extend({
options: {
accessibleTitle: "Feature",
},

/**
* Initializes the M.Feature
Expand All @@ -32,17 +29,20 @@ export var Feature = L.Path.extend({
this.type = markup.tagName.toUpperCase();

if(this.type === "POINT" || this.type === "MULTIPOINT") options.fillOpacity = 1;

if(options.wrappers.length > 0)
options = Object.assign(this._convertWrappers(options.wrappers), options);
L.setOptions(this, options);

this._createGroup(); // creates the <g> element for the feature, or sets the one passed in options as the <g>
this.group = this.options.group;

this._parts = [];
this._markup = markup;
this.options.zoom = markup.getAttribute('zoom') || this.options.nativeZoom;

this._convertMarkup();

if(markup.querySelector('span') || markup.querySelector('a')){
if(markup.querySelector('span') || markup.querySelector('map-a')){
this._generateOutlinePoints();
}

Expand All @@ -53,37 +53,39 @@ export var Feature = L.Path.extend({
* Removes the focus handler, and calls the leaflet L.Path.onRemove
*/
onRemove: function () {
L.DomEvent.off(this.group, "keyup keydown mousedown", this._handleFocus, this);
L.Path.prototype.onRemove.call(this);
},

/**
* Creates the <g> conditionally and also applies event handlers
* @private
*/
_createGroup: function(){
if(this.options.multiGroup){
this.group = this.options.multiGroup;
} else {
this.group = L.SVG.create('g');
if(this.options.interactive) this.group.setAttribute("aria-expanded", "false");
this.group.setAttribute('aria-label', this.options.accessibleTitle);
if(this.options.featureID) this.group.setAttribute("data-fid", this.options.featureID);
L.DomEvent.on(this.group, "keyup keydown mousedown", this._handleFocus, this);
if(this.options.link) {
this.off({
click: this._handleLinkClick,
keypress: this._handleLinkKeypress,
});
}

if(this.options.interactive) this.off('keypress', this._handleSpaceDown);

L.Path.prototype.onRemove.call(this);
},

/**
* Handler for focus events
* @param {L.DOMEvent} e - Event that occured
* @private
* Attaches link handler to the sub parts' paths
* @param path
* @param link
* @param linkTarget
* @param linkType
* @param leafletLayer
*/
_handleFocus: function(e) {
if((e.keyCode === 9 || e.keyCode === 16 || e.keyCode === 13) && e.type === "keyup" && e.target.tagName === "g"){
this.openTooltip();
} else {
this.closeTooltip();
}
attachLinkHandler: function (path, link, linkTarget, linkType, leafletLayer) {
let drag = false; //prevents click from happening on drags
L.DomEvent.on(path, 'mousedown', () =>{ drag = false;}, this);
L.DomEvent.on(path, 'mousemove', () =>{ drag = true;}, this);
L.DomEvent.on(path, "mouseup", (e) => {
L.DomEvent.stop(e);
if(!drag) M.handleLink(link, linkTarget, linkType, leafletLayer);
}, this);
L.DomEvent.on(path, "keypress", (e) => {
L.DomEvent.stop(e);
if(e.keyCode === 13 || e.keyCode === 32)
M.handleLink(link, linkTarget, linkType, leafletLayer);
}, this);
},

/**
Expand Down Expand Up @@ -140,6 +142,32 @@ export var Feature = L.Path.extend({
this._renderer._updateFeature(this);
},

/**
* Converts the spans, a and divs around a geometry subtype into options for the feature
* @private
*/
_convertWrappers: function (elems) {
if(!elems || elems.length === 0) return;
let classList = '', output = {};
for(let elem of elems){
if(elem.tagName.toUpperCase() !== "MAP-A" && elem.className){
// Useful if getting other attributes off spans and divs is useful
/* let attr = elem.attributes;
for(let i = 0; i < attr.length; i++){
if(attr[i].name === "class" || attributes[attr[i].name]) continue;
attributes[attr[i].name] = attr[i].value;
}*/
classList +=`${elem.className} `;
} else if(!output.link && elem.getAttribute("href")) {
output.link = elem.getAttribute("href");
if(elem.hasAttribute("target")) output.linkTarget = elem.getAttribute("target");
if(elem.hasAttribute("type")) output.linkType = elem.getAttribute("type");
}
}
output.className = `${classList} ${this.options.className}`.trim();
return output;
},

/**
* Converts this._markup to the internal structure of features
* @private
Expand All @@ -149,6 +177,8 @@ export var Feature = L.Path.extend({

let attr = this._markup.attributes;
this.featureAttributes = {};
if(this.options.link && this._markup.parentElement.tagName.toUpperCase() === "MAP-A" && this._markup.parentElement.parentElement.tagName.toUpperCase() !== "GEOMETRY")
this.featureAttributes.tabindex = "0";
for(let i = 0; i < attr.length; i++){
this.featureAttributes[attr[i].name] = attr[i].value;
}
Expand All @@ -163,10 +193,10 @@ export var Feature = L.Path.extend({
this._parts[0].subrings = this._parts[0].subrings.concat(subrings);
} else if (this.type === "MULTIPOINT") {
for (let point of ring[0].points.concat(subrings)) {
this._parts.push({ rings: [{ points: [point] }], subrings: [], cls: point.cls || this.options.className });
this._parts.push({ rings: [{ points: [point] }], subrings: [], cls:`${point.cls || ""} ${this.options.className || ""}`.trim() });
}
} else {
this._parts.push({ rings: ring, subrings: subrings, cls: this.featureAttributes.class || this.options.className });
this._parts.push({ rings: ring, subrings: subrings, cls: `${this.featureAttributes.class || ""} ${this.options.className || ""}`.trim() });
}
first = false;
}
Expand Down Expand Up @@ -212,11 +242,12 @@ export var Feature = L.Path.extend({
* @param {Object[]} subParts - An empty array representing the sub parts
* @param {boolean} isFirst - A true | false representing if the current HTML element is the parent coordinates element or not
* @param {string} cls - The class of the coordinate/span
* @param parents
* @private
*/
_coordinateToArrays: function (coords, main, subParts, isFirst = true, cls = undefined) {
_coordinateToArrays: function (coords, main, subParts, isFirst = true, cls = undefined, parents = []) {
for (let span of coords.children) {
this._coordinateToArrays(span, main, subParts, false, span.getAttribute("class"));
this._coordinateToArrays(span, main, subParts, false, span.getAttribute("class"), parents.concat([span]));
}
let noSpan = coords.textContent.replace(/(<([^>]+)>)/ig, ''),
pairs = noSpan.match(/(\S+\s+\S+)/gim), local = [];
Expand All @@ -230,12 +261,13 @@ export var Feature = L.Path.extend({
if (isFirst) {
main.push({ points: local });
} else {
let attrMap = {}, attr = coords.attributes;
let attrMap = {}, attr = coords.attributes, wrapperAttr = this._convertWrappers(parents);
if(wrapperAttr.link) attrMap.tabindex = "0";
for(let i = 0; i < attr.length; i++){
if(attr[i].name === "class") continue;
attrMap[attr[i].name] = attr[i].value;
}
subParts.unshift({ points: local, cls: cls || this.options.className, attr: attrMap});
subParts.unshift({ points: local, cls: `${cls || ""} ${wrapperAttr.className || ""}`.trim(), attr: attrMap, link: wrapperAttr.link, linkTarget: wrapperAttr.linkTarget, linkType: wrapperAttr.linkType});
}
},

Expand Down
60 changes: 56 additions & 4 deletions src/mapml/features/featureGroup.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,63 @@
export var FeatureGroup = L.FeatureGroup.extend({

/**
* Adds layer to feature group
* @param {M.Feature} layer - The layer to be added
* Initialize the feature group
* @param {M.Feature[]} layers
* @param {Object} options
*/
initialize: function (layers, options) {
if(options.wrappers && options.wrappers.length > 0)
options = Object.assign(M.Feature.prototype._convertWrappers(options.wrappers), options);

L.LayerGroup.prototype.initialize.call(this, layers, options);

if(this.options.onEachFeature || this.options.link) {
this.options.group.setAttribute("aria-expanded", "false");
this.options.group.setAttribute('tabindex', '0');
L.DomUtil.addClass(this.options.group, "leaflet-interactive");
L.DomEvent.on(this.options.group, "keyup keydown mousedown", this._handleFocus, this);
let firstLayer = layers[Object.keys(layers)[0]];
if(layers.length === 1 && firstLayer.options.link){ //if it's the only layer and it has a link, take it's link
this.options.link = firstLayer.options.link;
this.options.linkTarget = firstLayer.options.linkTarget;
this.options.linkType = firstLayer.options.linkType;
}
if(this.options.link){
M.Feature.prototype.attachLinkHandler.call(this, this.options.group, this.options.link, this.options.linkTarget, this.options.linkType, this.options._leafletLayer);
} else {
this.options.onEachFeature(this.options.properties, this);
this.off("click", this._openPopup);
}
}

this.options.group.setAttribute('aria-label', this.options.accessibleTitle);
if(this.options.featureID) this.options.group.setAttribute("data-fid", this.options.featureID);
},

/**
* Handler for focus events
* @param {L.DOMEvent} e - Event that occured
* @private
*/
_handleFocus: function(e) {
if(e.target.tagName.toUpperCase() !== "G") return;
if((e.keyCode === 9 || e.keyCode === 16 || e.keyCode === 13) && e.type === "keyup") {
this.openTooltip();
} else if (e.keyCode === 13 || e.keyCode === 32){
this.closeTooltip();
if(!this.options.link && this.options.onEachFeature){
L.DomEvent.stop(e);
this.openPopup();
}
} else {
this.closeTooltip();
}
},

addLayer: function (layer) {
layer.openTooltip = () => { this.openTooltip(); }; // needed to open tooltip of child features
layer.closeTooltip = () => { this.closeTooltip(); }; // needed to close tooltip of child features
if(!layer.options.link && this.options.onEachFeature) {
this.options.onEachFeature(this.options.properties, layer);
}
L.FeatureGroup.prototype.addLayer.call(this, layer);
},

Expand Down
22 changes: 18 additions & 4 deletions src/mapml/features/featureRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export var FeatureRenderer = L.SVG.extend({
}
if (p.subrings) {
for (let r of p.subrings) {
this._createPath(r, layer.options.className, r.attr['aria-label'], false, r.attr);
this._createPath(r, layer.options.className, r.attr['aria-label'], (r.link !== undefined), r.attr);
if(r.attr && r.attr.tabindex){
p.path.setAttribute('tabindex', r.attr.tabindex || '0');
}
Expand All @@ -40,8 +40,6 @@ export var FeatureRenderer = L.SVG.extend({
if(stampLayer){
let stamp = L.stamp(layer);
this._layers[stamp] = layer;
layer.group.setAttribute('tabindex', '0');
L.DomUtil.addClass(layer.group, "leaflet-interactive");
}
},

Expand Down Expand Up @@ -89,15 +87,24 @@ export var FeatureRenderer = L.SVG.extend({
for (let p of layer._parts) {
if (p.path)
layer.group.appendChild(p.path);
if (interactive){
if(layer.options.link) layer.attachLinkHandler(p.path, layer.options.link, layer.options.linkTarget, layer.options.linkType, layer.options._leafletLayer);
layer.addInteractiveTarget(p.path);
}

if(!outlineAdded && layer.pixelOutline) {
layer.group.appendChild(layer.outlinePath);
outlineAdded = true;
}

for (let subP of p.subrings) {
if (subP.path)
if (subP.path) {
if (subP.link){
layer.attachLinkHandler(subP.path, subP.link, subP.linkTarget, subP.linkType, layer.options._leafletLayer);
layer.addInteractiveTarget(subP.path);
}
layer.group.appendChild(subP.path);
}
}
}
c.appendChild(layer.group);
Expand Down Expand Up @@ -180,6 +187,13 @@ export var FeatureRenderer = L.SVG.extend({
if (!path || !layer) { return; }
let options = layer.options, isClosed = layer.isClosed;
if ((options.stroke && (!isClosed || isOutline)) || (isMain && !layer.outlinePath)) {
if (options.link){
path.style.stroke = "#0000EE";
path.style.strokeOpacity = "1";
path.style.strokeWidth = "1px";
path.style.strokeDasharray = "none";

}
path.setAttribute('stroke', options.color);
path.setAttribute('stroke-opacity', options.opacity);
path.setAttribute('stroke-width', options.weight);
Expand Down
1 change: 1 addition & 0 deletions src/mapml/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,7 @@ window.M = M;
});
}());

M.handleLink = Util.handleLink;
M.convertPCRSBounds = Util.convertPCRSBounds;
M.axisToXY = Util.axisToXY;
M.csToAxes = Util.csToAxes;
Expand Down
12 changes: 7 additions & 5 deletions src/mapml/layers/Crosshair.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,15 @@ export var Crosshair = L.Layer.extend({

_isMapFocused: function (e) {
//set this._map.isFocused = true if arrow buttons are used
if (this._map._container.parentNode.activeElement.classList.contains("leaflet-container") && ["keydown"].includes(e.type) && (e.shiftKey && e.keyCode === 9)) {
this._map.isFocused = false;
} else if (this._map._container.parentNode.activeElement.classList.contains("leaflet-container") && ["keyup", "keydown"].includes(e.type)) {
this._map.isFocused = true;
} else {
if(!this._map._container.parentNode.activeElement){
this._map.isFocused = false;
return;
}
let isLeafletContainer = this._map._container.parentNode.activeElement.classList.contains("leaflet-container");
if (isLeafletContainer && ["keydown"].includes(e.type) && (e.shiftKey && e.keyCode === 9)) {
this._map.isFocused = false;
} else this._map.isFocused = isLeafletContainer && ["keyup", "keydown"].includes(e.type);

this._addOrRemoveMapOutline();
this._addOrRemoveCrosshair();
},
Expand Down
Loading