From: John Firebaugh Date: Wed, 7 Aug 2013 19:18:33 +0000 (-0700) Subject: Tooltips for map controls X-Git-Tag: live~4802 X-Git-Url: https://git.openstreetmap.org/rails.git/commitdiff_plain/54ded37bf2e9967f6462795eebf0b5117399391a Tooltips for map controls --- diff --git a/Vendorfile b/Vendorfile index f9e705b2e..88f999a40 100644 --- a/Vendorfile +++ b/Vendorfile @@ -3,6 +3,10 @@ folder 'vendor/assets' do file 'jquery.throttle-debounce.js', 'https://raw.github.com/cowboy/jquery-throttle-debounce/v1.1/jquery.ba-throttle-debounce.js' end + folder 'bootstrap' do + file 'bootstrap.tooltip.js', 'https://raw.github.com/twbs/bootstrap/v2.3.2/js/bootstrap-tooltip.js' + end + folder 'leaflet' do file 'leaflet.js', 'http://cdn.leafletjs.com/leaflet-0.6.3/leaflet-src.js' file 'leaflet.css', 'http://cdn.leafletjs.com/leaflet-0.6.3/leaflet.css' diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 225ddaf44..a33e281ce 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -3,6 +3,7 @@ //= require jquery.timers //= require jquery.cookie //= require jquery.throttle-debounce +//= require bootstrap.tooltip //= require augment //= require osm //= require leaflet diff --git a/app/assets/javascripts/index.js b/app/assets/javascripts/index.js index 5d135696e..bd366b609 100644 --- a/app/assets/javascripts/index.js +++ b/app/assets/javascripts/index.js @@ -71,8 +71,10 @@ $(document).ready(function () { L.OSM.zoom({position: position}) .addTo(map); - L.control.locate({position: position}) - .addTo(map); + L.control.locate({ + position: position, + title: I18n.t('javascripts.map.locate.title') + }).addTo(map); var sidebar = L.OSM.sidebar('#map-ui') .addTo(map); @@ -102,6 +104,8 @@ $(document).ready(function () { L.control.scale() .addTo(map); + $('.leaflet-control .control-button').tooltip({placement: 'left', container: 'body'}); + map.on('moveend layeradd layerremove', updateLocation); var marker = L.marker([0, 0], {icon: getUserIcon()}); diff --git a/app/assets/javascripts/leaflet.key.js b/app/assets/javascripts/leaflet.key.js index d88ce5d6e..cc86736ef 100644 --- a/app/assets/javascripts/leaflet.key.js +++ b/app/assets/javascripts/leaflet.key.js @@ -8,7 +8,6 @@ L.OSM.key = function (options) { var button = $('') .attr('class', 'control-button') .attr('href', '#') - .attr('title', I18n.t('javascripts.key.tooltip')) .html('') .on('click', toggle) .appendTo($container); @@ -61,8 +60,12 @@ L.OSM.key = function (options) { } function updateButton() { - var layer = map.getMapBaseLayerId(); - button.toggleClass('disabled', layer !== 'mapnik'); + var disabled = map.getMapBaseLayerId() !== 'mapnik' + button + .toggleClass('disabled', disabled) + .attr('data-original-title', I18n.t(disabled ? + 'javascripts.key.tooltip_disabled' : + 'javascripts.key.tooltip')) } function update() { diff --git a/app/assets/javascripts/leaflet.note.js b/app/assets/javascripts/leaflet.note.js index a38e012de..c0f72af20 100644 --- a/app/assets/javascripts/leaflet.note.js +++ b/app/assets/javascripts/leaflet.note.js @@ -5,14 +5,25 @@ L.OSM.note = function (options) { var $container = $('
') .attr('class', 'control-note'); - $('') - .attr('id', 'createnoteanchor') - .attr('class', 'control-button geolink') - .attr('data-minzoom', 12) + var link = $('') + .attr('class', 'control-button') .attr('href', '#') .html('') .appendTo($container); + map.on('zoomend', update); + + update(); + + function update() { + var disabled = map.getZoom() < 12; + link + .toggleClass('disabled', disabled) + .attr('data-original-title', I18n.t(disabled ? + 'javascripts.site.createnote_disabled_tooltip' : + 'javascripts.site.createnote_tooltip')); + } + return $container[0]; }; diff --git a/app/assets/stylesheets/common.css.scss b/app/assets/stylesheets/common.css.scss index 418921dab..753f89671 100644 --- a/app/assets/stylesheets/common.css.scss +++ b/app/assets/stylesheets/common.css.scss @@ -525,6 +525,89 @@ a.donate { left: 15px; } +/* Rules for bootstrap tooltips */ + +.tooltip { + position: absolute; + display: none; + color:#333; + text-align: left; + font-size: 12px; +} + +.tooltip.in { + opacity: 0.8; + z-index: 1030; + height: auto; + display: block; +} + +.tooltip.top { + margin-top: -20px; + text-align: center; +} + +.tooltip.right { + margin-left: 20px; +} + +.tooltip.bottom { + margin-top: 20px; + text-align: center; +} + +.tooltip.left { + margin-left: -20px; + text-align: right; +} + +.tooltip-inner { + display: inline-block; + padding: 10px; + font-weight: normal; + background-color: white; +} + +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} + +.tooltip.top .tooltip-arrow { + bottom: -5px; + left: 50%; + margin-left: -5px; + border-top-color: white; + border-width: 5px 5px 0; +} + +.tooltip.right .tooltip-arrow { + top: 50%; + left: -5px; + margin-top: -5px; + border-right-color: white; + border-width: 5px 5px 5px 0; +} + +.tooltip.left .tooltip-arrow { + top: 50%; + right: -5px; + margin-top: -5px; + border-left-color: white; + border-width: 5px 0 5px 5px; +} + +.tooltip.bottom .tooltip-arrow { + top: -5px; + left: 50%; + margin-left: -5px; + border-bottom-color: white; + border-width: 0 5px 5px; +} + /* Rules for Leaflet maps */ .leaflet-control .control-button { diff --git a/config/locales/en.yml b/config/locales/en.yml index aabe978f5..62fcb8433 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2068,10 +2068,13 @@ en: key: title: "Map Key" tooltip: "Map Key" + tooltip_disabled: "Map Key available only for Standard layer" map: zoom: in: Zoom In out: Zoom Out + locate: + title: Show My Location base: standard: Standard cycle_map: Cycle Map diff --git a/vendor/assets/bootstrap/bootstrap.tooltip.js b/vendor/assets/bootstrap/bootstrap.tooltip.js new file mode 100644 index 000000000..acd6096e6 --- /dev/null +++ b/vendor/assets/bootstrap/bootstrap.tooltip.js @@ -0,0 +1,361 @@ +/* =========================================================== + * bootstrap-tooltip.js v2.3.2 + * http://getbootstrap.com/2.3.2/javascript.html#tooltips + * Inspired by the original jQuery.tipsy by Jason Frame + * =========================================================== + * Copyright 2013 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* TOOLTIP PUBLIC CLASS DEFINITION + * =============================== */ + + var Tooltip = function (element, options) { + this.init('tooltip', element, options) + } + + Tooltip.prototype = { + + constructor: Tooltip + + , init: function (type, element, options) { + var eventIn + , eventOut + , triggers + , trigger + , i + + this.type = type + this.$element = $(element) + this.options = this.getOptions(options) + this.enabled = true + + triggers = this.options.trigger.split(' ') + + for (i = triggers.length; i--;) { + trigger = triggers[i] + if (trigger == 'click') { + this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) + } else if (trigger != 'manual') { + eventIn = trigger == 'hover' ? 'mouseenter' : 'focus' + eventOut = trigger == 'hover' ? 'mouseleave' : 'blur' + this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) + this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) + } + } + + this.options.selector ? + (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : + this.fixTitle() + } + + , getOptions: function (options) { + options = $.extend({}, $.fn[this.type].defaults, this.$element.data(), options) + + if (options.delay && typeof options.delay == 'number') { + options.delay = { + show: options.delay + , hide: options.delay + } + } + + return options + } + + , enter: function (e) { + var defaults = $.fn[this.type].defaults + , options = {} + , self + + this._options && $.each(this._options, function (key, value) { + if (defaults[key] != value) options[key] = value + }, this) + + self = $(e.currentTarget)[this.type](options).data(this.type) + + if (!self.options.delay || !self.options.delay.show) return self.show() + + clearTimeout(this.timeout) + self.hoverState = 'in' + this.timeout = setTimeout(function() { + if (self.hoverState == 'in') self.show() + }, self.options.delay.show) + } + + , leave: function (e) { + var self = $(e.currentTarget)[this.type](this._options).data(this.type) + + if (this.timeout) clearTimeout(this.timeout) + if (!self.options.delay || !self.options.delay.hide) return self.hide() + + self.hoverState = 'out' + this.timeout = setTimeout(function() { + if (self.hoverState == 'out') self.hide() + }, self.options.delay.hide) + } + + , show: function () { + var $tip + , pos + , actualWidth + , actualHeight + , placement + , tp + , e = $.Event('show') + + if (this.hasContent() && this.enabled) { + this.$element.trigger(e) + if (e.isDefaultPrevented()) return + $tip = this.tip() + this.setContent() + + if (this.options.animation) { + $tip.addClass('fade') + } + + placement = typeof this.options.placement == 'function' ? + this.options.placement.call(this, $tip[0], this.$element[0]) : + this.options.placement + + $tip + .detach() + .css({ top: 0, left: 0, display: 'block' }) + + this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) + + pos = this.getPosition() + + actualWidth = $tip[0].offsetWidth + actualHeight = $tip[0].offsetHeight + + switch (placement) { + case 'bottom': + tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2} + break + case 'top': + tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2} + break + case 'left': + tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth} + break + case 'right': + tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width} + break + } + + this.applyPlacement(tp, placement) + this.$element.trigger('shown') + } + } + + , applyPlacement: function(offset, placement){ + var $tip = this.tip() + , width = $tip[0].offsetWidth + , height = $tip[0].offsetHeight + , actualWidth + , actualHeight + , delta + , replace + + $tip + .offset(offset) + .addClass(placement) + .addClass('in') + + actualWidth = $tip[0].offsetWidth + actualHeight = $tip[0].offsetHeight + + if (placement == 'top' && actualHeight != height) { + offset.top = offset.top + height - actualHeight + replace = true + } + + if (placement == 'bottom' || placement == 'top') { + delta = 0 + + if (offset.left < 0){ + delta = offset.left * -2 + offset.left = 0 + $tip.offset(offset) + actualWidth = $tip[0].offsetWidth + actualHeight = $tip[0].offsetHeight + } + + this.replaceArrow(delta - width + actualWidth, actualWidth, 'left') + } else { + this.replaceArrow(actualHeight - height, actualHeight, 'top') + } + + if (replace) $tip.offset(offset) + } + + , replaceArrow: function(delta, dimension, position){ + this + .arrow() + .css(position, delta ? (50 * (1 - delta / dimension) + "%") : '') + } + + , setContent: function () { + var $tip = this.tip() + , title = this.getTitle() + + $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title) + $tip.removeClass('fade in top bottom left right') + } + + , hide: function () { + var that = this + , $tip = this.tip() + , e = $.Event('hide') + + this.$element.trigger(e) + if (e.isDefaultPrevented()) return + + $tip.removeClass('in') + + function removeWithAnimation() { + var timeout = setTimeout(function () { + $tip.off($.support.transition.end).detach() + }, 500) + + $tip.one($.support.transition.end, function () { + clearTimeout(timeout) + $tip.detach() + }) + } + + $.support.transition && this.$tip.hasClass('fade') ? + removeWithAnimation() : + $tip.detach() + + this.$element.trigger('hidden') + + return this + } + + , fixTitle: function () { + var $e = this.$element + if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') { + $e.attr('data-original-title', $e.attr('title') || '').attr('title', '') + } + } + + , hasContent: function () { + return this.getTitle() + } + + , getPosition: function () { + var el = this.$element[0] + return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : { + width: el.offsetWidth + , height: el.offsetHeight + }, this.$element.offset()) + } + + , getTitle: function () { + var title + , $e = this.$element + , o = this.options + + title = $e.attr('data-original-title') + || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) + + return title + } + + , tip: function () { + return this.$tip = this.$tip || $(this.options.template) + } + + , arrow: function(){ + return this.$arrow = this.$arrow || this.tip().find(".tooltip-arrow") + } + + , validate: function () { + if (!this.$element[0].parentNode) { + this.hide() + this.$element = null + this.options = null + } + } + + , enable: function () { + this.enabled = true + } + + , disable: function () { + this.enabled = false + } + + , toggleEnabled: function () { + this.enabled = !this.enabled + } + + , toggle: function (e) { + var self = e ? $(e.currentTarget)[this.type](this._options).data(this.type) : this + self.tip().hasClass('in') ? self.hide() : self.show() + } + + , destroy: function () { + this.hide().$element.off('.' + this.type).removeData(this.type) + } + + } + + + /* TOOLTIP PLUGIN DEFINITION + * ========================= */ + + var old = $.fn.tooltip + + $.fn.tooltip = function ( option ) { + return this.each(function () { + var $this = $(this) + , data = $this.data('tooltip') + , options = typeof option == 'object' && option + if (!data) $this.data('tooltip', (data = new Tooltip(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.tooltip.Constructor = Tooltip + + $.fn.tooltip.defaults = { + animation: true + , placement: 'top' + , selector: false + , template: '
' + , trigger: 'hover focus' + , title: '' + , delay: 0 + , html: false + , container: false + } + + + /* TOOLTIP NO CONFLICT + * =================== */ + + $.fn.tooltip.noConflict = function () { + $.fn.tooltip = old + return this + } + +}(window.jQuery);