Support proper tooltips on undo redo buttons

This commit is contained in:
Tom MacWright
2012-12-05 16:00:49 -05:00
parent e027cf1709
commit 8126189e0a
5 changed files with 266 additions and 6 deletions

View File

@@ -349,3 +349,45 @@ div.typeahead a.active {
-webkit-tap-highlight-color:rgba(0,0,0,0);
-webkit-touch-callout:none;
}
.tooltip {
position: absolute;
z-index: 1030;
display: block;
padding: 5px;
font-size: 11px;
opacity: 0;
filter: alpha(opacity=0);
visibility: visible;
}
.tooltip.in {
opacity: 0.8;
filter: alpha(opacity=80);
}
.tooltip-inner {
max-width: 200px;
padding: 3px 8px;
color: #ffffff;
text-align: center;
text-decoration: none;
background-color: #000000;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.tooltip-arrow {
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
}
.tooltip.bottom .tooltip-arrow {
top: 0;
left: 50%;
margin-left: -5px;
border-bottom-color: #000000;
border-width: 0 5px 5px;
}

View File

@@ -21,6 +21,8 @@
<script src='js/lib/d3.geo.tile.js'></script>
<script src='js/lib/d3.size.js'></script>
<script src='js/lib/d3.keybinding.js'></script>
<script src='js/lib/d3-compat.js'></script>
<script src='js/lib/bootstrap-tooltip.js'></script>
<script src='js/id/id.js'></script>
<script src='js/id/util.js'></script>

View File

@@ -46,13 +46,17 @@ window.iD = function(container) {
.attr({ id: 'undo', 'class': 'mini' })
.property('disabled', true)
.html('&larr;<small></small>')
.on('click', history.undo);
.on('click', history.undo)
.call(bootstrap.tooltip()
.placement('bottom'));
undo_buttons.append('button')
.attr({ id: 'redo', 'class': 'mini' })
.property('disabled', true)
.html('&rarr;<small></small>')
.on('click', history.redo);
.on('click', history.redo)
.call(bootstrap.tooltip()
.placement('bottom'));
bar.append('input')
.attr({ type: 'text', placeholder: 'find a place', id: 'geocode-location' })
@@ -137,13 +141,11 @@ window.iD = function(container) {
bar.select('#undo')
.property('disabled', !undo)
.select('small')
.text(undo);
.attr('data-original-title', undo);
bar.select('#redo')
.property('disabled', !redo)
.select('small')
.text(redo);
.attr('data-original-title', redo);
});
window.onresize = function() {

166
js/lib/bootstrap-tooltip.js vendored Normal file
View File

@@ -0,0 +1,166 @@
(function(exports) {
var bootstrap = (typeof exports.bootstrap === "object") ?
exports.bootstrap :
(exports.bootstrap = {});
bootstrap.tooltip = function() {
var tooltip = function(selection) {
selection.each(setup);
},
animation = d3.functor(false),
html = d3.functor(false),
title = function() {
var title = this.getAttribute("data-original-title");
if (title) {
return title;
} else {
title = this.getAttribute("title");
this.removeAttribute("title");
this.setAttribute("data-original-title", title);
}
return title;
},
over = "mouseenter.tooltip",
out = "mouseleave.tooltip",
placements = "top left bottom right".split(" "),
placement = d3.functor("top");
tooltip.title = function(_) {
if (arguments.length) {
title = d3.functor(_);
return tooltip;
} else {
return title;
}
};
tooltip.html = function(_) {
if (arguments.length) {
html = d3.functor(_);
return tooltip;
} else {
return html;
}
};
tooltip.placement = function(_) {
if (arguments.length) {
placement = d3.functor(_);
return tooltip;
} else {
return placement;
}
};
tooltip.show = function(selection) {
selection.each(show);
};
tooltip.hide = function(selection) {
selection.each(hide);
};
tooltip.toggle = function(selection) {
selection.each(toggle);
};
tooltip.destroy = function(selection) {
selection
.on(over, null)
.on(out, null)
.attr("title", function() {
return this.getAttribute("data-original-title") || this.getAttribute("title");
})
.attr("data-original-title", null)
.select(".tooltip")
.remove();
};
function setup() {
var root = d3.select(this),
animate = animation.apply(this, arguments),
tip = root.append("div")
.attr("class", "tooltip");
if (animate) {
tip.classed("fade", true);
}
// TODO "inside" checks?
tip.append("div")
.attr("class", "tooltip-arrow");
tip.append("div")
.attr("class", "tooltip-inner");
var place = placement.apply(this, arguments);
tip.classed(place, true);
root.on(over, show);
root.on(out, hide);
}
function show() {
var root = d3.select(this),
content = title.apply(this, arguments),
tip = root.select(".tooltip")
.classed("in", true),
markup = html.apply(this, arguments),
innercontent = tip.select(".tooltip-inner")[markup ? "html" : "text"](content),
place = placement.apply(this, arguments),
outer = getPosition(root.node()),
inner = getPosition(tip.node()),
pos;
switch (place) {
case "top":
pos = {x: outer.x + (outer.w - inner.w) / 2, y: outer.y - inner.h};
break;
case "right":
pos = {x: outer.x + outer.w, y: outer.y + (outer.h - inner.h) / 2};
break;
case "left":
pos = {x: outer.x - inner.w, y: outer.y + (outer.h - inner.h) / 2};
break;
case "bottom":
pos = {x: outer.x + (outer.w - inner.w) / 2, y: outer.y + outer.h};
break;
}
tip.style(pos ?
{left: ~~pos.x + "px", top: ~~pos.y + "px"} :
{left: null, top: null});
this.tooltipVisible = true;
}
function hide() {
d3.select(this).select(".tooltip")
.classed("in", false);
this.tooltipVisible = false;
}
function toggle() {
if (this.tooltipVisible) {
hide.apply(this, arguments);
} else {
show.apply(this, arguments);
}
}
return tooltip;
};
function getPosition(node) {
return {
x: node.offsetLeft,
y: node.offsetTop,
w: node.offsetWidth,
h: node.offsetHeight
};
}
})(this);

48
js/lib/d3-compat.js vendored Normal file
View File

@@ -0,0 +1,48 @@
(function() {
// get a reference to the d3.selection prototype,
// and keep a reference to the old d3.selection.on
var d3_selectionPrototype = d3.selection.prototype,
d3_on = d3_selectionPrototype.on;
// our shims are organized by event:
// "desired-event": ["shimmed-event", wrapperFunction]
var shims = {
"mouseenter": ["mouseover", relatedTarget],
"mouseleave": ["mouseout", relatedTarget]
};
// rewrite the d3.selection.on function to shim the events with wrapped
// callbacks
d3_selectionPrototype.on = function(evt, callback, useCapture) {
var bits = evt.split("."),
type = bits.shift(),
shim = shims[type];
if (shim) {
evt = bits.length ? [shim[0], bits].join(".") : shim[0];
if (typeof callback === "function") {
callback = shim[1](callback);
}
return d3_on.call(this, evt, callback, useCapture);
} else {
return d3_on.apply(this, arguments);
}
};
function relatedTarget(callback) {
return function() {
var related = d3.event.relatedTarget;
if (this === related || childOf(this, related)) {
return undefined;
}
return callback.apply(this, arguments);
};
}
function childOf(p, c) {
if (p === c) return false;
while (c && c !== p) c = c.parentNode;
return c === p;
}
})();