Switch to bootstrap for dropdowns and tooltips

Due to the javascript involved, it was more straightforward to combine
the two together. Moving to bootstrap v4 for dropdowns required some
redesign of the dropdown menu html, and the opportunity was taken to
move to using standard buttons for those menus.
This commit is contained in:
Andy Allan 2020-01-29 16:54:29 +01:00
parent f7f73322b3
commit e5c33c119a
11 changed files with 88 additions and 1047 deletions

View file

@ -3,8 +3,8 @@
//= require jquery.timers //= require jquery.timers
//= require jquery.cookie //= require jquery.cookie
//= require jquery.throttle-debounce //= require jquery.throttle-debounce
//= require bootstrap.tooltip //= require popper
//= require bootstrap.dropdown //= require bootstrap-sprockets
//= require osm //= require osm
//= require leaflet //= require leaflet
//= require leaflet.osm //= require leaflet.osm

View file

@ -16,14 +16,14 @@
@import "bootstrap/grid"; @import "bootstrap/grid";
// @import "bootstrap/tables"; // @import "bootstrap/tables";
// @import "bootstrap/forms"; // @import "bootstrap/forms";
// @import "bootstrap/buttons"; @import "bootstrap/buttons";
// @import "bootstrap/transitions"; // @import "bootstrap/transitions";
// @import "bootstrap/dropdown"; @import "bootstrap/dropdown";
// @import "bootstrap/button-group"; @import "bootstrap/button-group";
// @import "bootstrap/input-group"; // @import "bootstrap/input-group";
// @import "bootstrap/custom-forms"; // @import "bootstrap/custom-forms";
// @import "bootstrap/nav"; @import "bootstrap/nav";
// @import "bootstrap/navbar"; @import "bootstrap/navbar";
@import "bootstrap/card"; @import "bootstrap/card";
// @import "bootstrap/breadcrumb"; // @import "bootstrap/breadcrumb";
// @import "bootstrap/pagination"; // @import "bootstrap/pagination";
@ -36,7 +36,7 @@
// @import "bootstrap/close"; // @import "bootstrap/close";
// @import "bootstrap/toasts"; // @import "bootstrap/toasts";
// @import "bootstrap/modal"; // @import "bootstrap/modal";
// @import "bootstrap/tooltip"; @import "bootstrap/tooltip";
// @import "bootstrap/popover"; // @import "bootstrap/popover";
// @import "bootstrap/carousel"; // @import "bootstrap/carousel";
// @import "bootstrap/spinners"; // @import "bootstrap/spinners";

View file

@ -1,83 +0,0 @@
/* Rules for bootstrap tooltips */
.tooltip {
position: absolute;
display: none;
color: #333;
text-align: left;
font-size: 12px;
max-width: 250px;
}
.tooltip.in {
opacity: 0.8;
z-index: 1030;
height: auto;
display: block;
}
.tooltip.top {
margin-top: -10px;
text-align: center;
}
.tooltip.right {
margin-left: 10px;
}
.tooltip.bottom {
margin-top: 10px;
text-align: center;
}
.tooltip.left {
margin-left: -10px;
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;
}

View file

@ -140,7 +140,7 @@ header {
z-index: 1001; z-index: 1001;
font-size: 14px; font-size: 14px;
h1, nav, nav > ul, nav > ul > li, .dropdown { h1, nav, nav > ul, nav > ul > li {
display: inline-block; display: inline-block;
} }
@ -179,68 +179,16 @@ header {
color: #000; color: #000;
} }
} }
.btn {
font-size: 14px;
}
} }
nav.primary { nav.primary {
> ul { .btn-outline-primary {
padding: 0; @include button-outline-variant($green, $white);
$border: 1px solid $green;
border: $border;
border-radius: $border-radius;
> li {
border-right: $border;
float: left;
&:last-child {
border-right: 0;
}
> a:hover { background: lighten($green, 30%); }
&.current > a:hover { background: $green; }
&.disabled > a:hover { background: lighten($green, 38%); }
&.dropdown {
> a.tab { border-right: 1px solid lighten($green, 30%); }
&.current > a.tab { border-right: 1px solid lighten($green, 10%); }
}
}
}
a.tab,
.dropdown-toggle {
display: inline-block;
font-weight: 500;
color: $green;
padding: 5px 15px;
}
.dropdown-toggle {
padding: 5px 6px;
}
.caret {
border-top-color: $green;
margin-top: 10px;
}
.disabled a {
color: $grey;
cursor: default;
.caret {
border-top-color: $grey;
}
}
> ul li.current {
background-color: $green;
.tab {
color: #fff;
}
.caret {
border-top-color: #fff;
}
} }
} }
@ -248,74 +196,32 @@ nav.secondary {
position: absolute; position: absolute;
right: 0; right: 0;
> ul { .nav-link {
vertical-align: middle; padding: 0.2rem;
margin: 0; color: $darkgrey;
padding: 0;
a, .dropdown-toggle {
display: inline-block;
text-decoration: none;
color: $darkgrey;
padding: 5px;
&:hover { color: darken($darkgrey, 25%); }
}
} }
> ul li.current a { > ul li.current a {
color: darken($darkgrey, 25%); color: darken($darkgrey, 25%);
} }
.user-menu { .login-menu {
$border: 1px solid $grey; .btn-outline-secondary {
border: $border; @include button-outline-variant($darkgrey);
border-radius: $border-radius;
margin-left: 10px;
padding: 0;
> li {
border-right: $border;
float: left;
&:last-child {
border-right: 0;
> a {
border-radius: 0 $border-radius $border-radius 0;
}
}
&:first-child > a { border-radius: $border-radius 0 0 $border-radius; }
&:hover a { background: lighten($darkgrey, 30%); }
}
a {
padding: 5px 15px;
}
&.logged-in > a {
padding: 0;
> .user-button {
line-height: 1.8;
padding: 5px 10px 3px 6px;
display: inline-block;
color: $darkgrey;
}
&:hover > .user-button { color: darken($darkgrey, 5%); }
} }
} }
.caret { .user-menu {
border-top-color: $grey; .btn-outline-secondary {
margin-top: 9px; @include button-outline-variant($darkgrey, $darkgrey, white, $darkgrey);
// @include button-outline-variant($grey, $grey, white, $grey);
border-color: $grey;
}
} }
img.user_thumbnail_tiny { img.user_thumbnail_tiny {
border: 0; border: 0;
vertical-align: top; border-radius: 3px;
margin-top: 0px;
margin: 4px 0 0 4px;
border-radius: 2px;
} }
#inboxanchor { #inboxanchor {
@ -329,25 +235,14 @@ nav.secondary {
} }
.dropdown-menu { .dropdown-menu {
left: auto;
right: -1px;
border-radius: 3px 0 3px 3px;
.count-number { .count-number {
float: right; font-size: 14px;
padding: 0 5px;
margin: 0;
} }
} }
} }
#compact-secondary-nav { #compact-secondary-nav {
display: none; display: none;
ul li a {
width: 100%;
color: #333;
&:hover { color: #fff; }
}
} }
body.compact { body.compact {
@ -2272,131 +2167,6 @@ input.richtext_title[type="text"] {
height: 100%; height: 100%;
} }
/* Rules for dropdown menus */
.dropdown {
position: relative;
}
.dropdown-toggle {
*margin-bottom: -3px;
}
.dropdown-toggle:active,
.open .dropdown-toggle {
outline: 0;
}
.caret {
display: inline-block;
width: 0;
height: 0;
vertical-align: top;
border-top: 4px solid #000000;
border-right: 4px solid transparent;
border-left: 4px solid transparent;
content: "";
}
.dropdown .caret {
margin-top: 8px;
margin-left: 2px;
}
.dropdown-menu {
position: absolute;
top: 100%;
left: -1px;
z-index: 1000;
display: none;
float: left;
min-width: 160px;
padding: 5px 0;
margin: 0;
list-style: none;
background-color: #ffffff;
border: 1px solid $grey;
border-radius: 0 3px 3px;
*border-right-width: 2px;
*border-bottom-width: 2px;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
background-clip: padding-box;
}
.dropdown-menu.pull-right {
right: 0;
left: auto;
}
.dropdown-menu .divider {
*width: 100%;
height: 1px;
margin: 9px 1px;
*margin: -5px 0 5px;
overflow: hidden;
background-color: $lightgrey;
border-bottom: 1px solid #ffffff;
}
.dropdown-menu > li > a {
display: block;
padding: 3px 10px;
clear: both;
font-weight: normal;
line-height: 20px;
color: #333333;
white-space: nowrap;
}
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus,
.dropdown-submenu:hover > a,
.dropdown-submenu:focus > a {
color: #ffffff;
text-decoration: none;
background-color: $green;
}
.dropdown-menu > .active > a,
.dropdown-menu > .active > a:hover,
.dropdown-menu > .active > a:focus {
color: #ffffff;
text-decoration: none;
background-color: $green;
outline: 0;
}
.dropdown-menu > .disabled > a,
.dropdown-menu > .disabled > a:hover,
.dropdown-menu > .disabled > a:focus {
color: $darkgrey;
}
.dropdown-menu > .disabled > a:hover,
.dropdown-menu > .disabled > a:focus {
text-decoration: none;
cursor: default;
background-color: transparent;
background-image: none;
}
.open {
*z-index: 1000;
}
.open > .dropdown-menu {
display: block;
}
.dropdown-backdrop {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 990;
}
/* Rules for the "Welcome" page */ /* Rules for the "Welcome" page */
.site-welcome, .site-fixthemap { .site-welcome, .site-fixthemap {
.center { .center {

View file

@ -1,5 +1,4 @@
/* /*
*= require ltr/common *= require ltr/common
*= require bootstrap-tooltips
*= require ltr/small *= require ltr/small
*/ */

View file

@ -1,5 +1,4 @@
/* /*
*= require rtl/common *= require rtl/common
*= require bootstrap-tooltips
*= require rtl/small *= require rtl/small
*/ */

View file

@ -71,22 +71,16 @@ body.small {
} }
} }
} }
.btn-group {
width: 100%;
padding: 10px;
}
} }
nav.secondary { nav.secondary {
border-bottom: 1px solid #eee;
.user-menu { .user-menu {
display: block;
width: 100%; width: 100%;
margin-left: 0;
> li {
width: 49%;
> a {
width: 100%;
text-align: center;
}
}
} }
} }

View file

@ -11,105 +11,100 @@
<a href="#" id="menu-icon"></a> <a href="#" id="menu-icon"></a>
<nav class='primary'> <nav class='primary'>
<%= content_for :header %> <%= content_for :header %>
<ul> <div class="btn-group">
<li id="edit_tab" class="dropdown <%= current_page_class(edit_path) %>"> <div id="edit_tab" class="btn-group <%= current_page_class(edit_path) %>">
<%= link_to t("layouts.edit"), <%= link_to t("layouts.edit"),
edit_path, edit_path,
:class => "tab geolink editlink", :class => "btn btn-outline-primary geolink editlink",
:id => "editanchor", :id => "editanchor",
:data => { :editor => preferred_editor } %> :data => { :editor => preferred_editor } %>
<a class='dropdown-toggle' data-toggle='dropdown' href='#'><b class="caret"></b></a> <a class='btn btn-outline-primary dropdown-toggle dropdown-toggle-split' data-toggle='dropdown' href='#'></a>
<ul class='dropdown-menu'> <ul class='dropdown-menu'>
<% Editors::RECOMMENDED_EDITORS.each do |editor| %> <% Editors::RECOMMENDED_EDITORS.each do |editor| %>
<li> <li>
<%= link_to t("layouts.edit_with", :editor => t("editor.#{editor}.description")), <%= link_to t("layouts.edit_with", :editor => t("editor.#{editor}.description")),
edit_path(:editor => editor), edit_path(:editor => editor),
:data => { :editor => editor }, :data => { :editor => editor },
:class => "geolink editlink" %> :class => "geolink editlink dropdown-item" %>
</li> </li>
<% end %> <% end %>
</ul> </ul>
</li> </div>
<li id="history_tab" class="<%= current_page_class(history_path) %>"> <%= link_to t("layouts.history"), history_path, :class => "btn btn-outline-primary geolink flex-grow-1 current_page_class(history_path)", :id => "history_tab" %>
<%= link_to t("layouts.history"), history_path, :class => "tab geolink" %> <%= link_to t("layouts.export"), export_path, :class => "btn btn-outline-primary geolink current_page_class(export_path)", :id => "export_tab" %>
</li> </div>
<li id="export_tab" class="<%= current_page_class(export_path) %>">
<%= link_to t("layouts.export"), export_path, :class => "tab geolink" %>
</li>
</ul>
</nav> </nav>
<nav class='secondary'> <nav class='secondary'>
<ul> <ul class='mx-1 px-0'>
<% if can? :index, Issue %> <% if can? :index, Issue %>
<li class="compact-hide <%= current_page_class(issues_path) %>"> <li class="compact-hide nav-item <%= current_page_class(issues_path) %>">
<%= link_to issues_path(:status => "open") do %> <%= link_to issues_path(:status => "open"), :class => "nav-link" do %>
<%= t("layouts.issues") %> <%= t("layouts.issues") %>
<%= open_issues_count %> <%= open_issues_count %>
<% end -%> <% end -%>
</li> </li>
<% end %> <% end %>
<li class="compact-hide <%= current_page_class(traces_path) %>"><%= link_to t("layouts.gps_traces"), traces_path %></li> <li class="compact-hide nav-item <%= current_page_class(traces_path) %>">
<li class="compact-hide <%= current_page_class(diary_entries_path) %>"><%= link_to t("layouts.user_diaries"), diary_entries_path %></li> <%= link_to t("layouts.gps_traces"), traces_path, :class => "nav-link" %>
<li class="compact-hide <%= current_page_class(copyright_path) %>"><%= link_to t("layouts.copyright"), copyright_path %></li> </li>
<li class="compact-hide <%= current_page_class(help_path) %>"><%= link_to t("layouts.help"), help_path %></li> <li class="compact-hide nav-item <%= current_page_class(diary_entries_path) %>">
<li class="compact-hide <%= current_page_class(about_path) %>"><%= link_to t("layouts.about"), about_path %></li> <%= link_to t("layouts.user_diaries"), diary_entries_path, :class => "nav-link" %>
<li id="compact-secondary-nav" class="dropdown"> </li>
<a class="dropdown-toggle" data-toggle="dropdown" href="#"><%= t "layouts.more" %> <b class="caret"></b></a> <li class="compact-hide nav-item <%= current_page_class(copyright_path) %>">
<%= link_to t("layouts.copyright"), copyright_path, :class => "nav-link" %>
</li>
<li class="compact-hide nav-item <%= current_page_class(help_path) %>">
<%= link_to t("layouts.help"), help_path, :class => "nav-link" %>
</li>
<li class="compact-hide nav-item <%= current_page_class(about_path) %>">
<%= link_to t("layouts.about"), about_path, :class => "nav-link" %>
</li>
<li id="compact-secondary-nav" class="dropdown nav-item">
<a class="dropdown-toggle nav-link" data-toggle="dropdown" href="#"><%= t "layouts.more" %></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<% if Settings.status != "database_offline" && can?(:index, Issue) %> <% if Settings.status != "database_offline" && can?(:index, Issue) %>
<li class="<%= current_page_class(issues_path) %>"> <li class="<%= current_page_class(issues_path) %>">
<%= link_to issues_path(:status => "open") do %> <%= link_to issues_path(:status => "open"), :class => "dropdown-item" do %>
<%= open_issues_count %>
<%= t("layouts.issues") %> <%= t("layouts.issues") %>
<%= open_issues_count %>
<% end -%> <% end -%>
</li> </li>
<% end %> <% end %>
<li class="<%= current_page_class(traces_path) %>"><%= link_to t("layouts.gps_traces"), traces_path %></li> <li class="<%= current_page_class(traces_path) %>"><%= link_to t("layouts.gps_traces"), traces_path, :class => "dropdown-item" %></li>
<li class="<%= current_page_class(diary_entries_path) %>"><%= link_to t("layouts.user_diaries"), diary_entries_path %></li> <li class="<%= current_page_class(diary_entries_path) %>"><%= link_to t("layouts.user_diaries"), diary_entries_path, :class => "dropdown-item" %></li>
<li class="<%= current_page_class(copyright_path) %>"><%= link_to t("layouts.copyright"), copyright_path %></li> <li class="<%= current_page_class(copyright_path) %>"><%= link_to t("layouts.copyright"), copyright_path, :class => "dropdown-item" %></li>
<li class="<%= current_page_class(help_path) %>"><%= link_to t("layouts.help"), help_path %></li> <li class="<%= current_page_class(help_path) %>"><%= link_to t("layouts.help"), help_path, :class => "dropdown-item" %></li>
<li class="<%= current_page_class(about_path) %>"><%= link_to t("layouts.about"), about_path %></li> <li class="<%= current_page_class(about_path) %>"><%= link_to t("layouts.about"), about_path, :class => "dropdown-item" %></li>
</ul> </ul>
</li> </li>
</ul> </ul>
<% if current_user && current_user.id %> <% if current_user && current_user.id %>
<div class='dropdown user-menu logged-in'> <div class='d-inline-flex dropdown user-menu logged-in clearfix'>
<a class='dropdown-toggle' data-toggle='dropdown' href="#"> <a class='dropdown-toggle btn btn-outline-secondary pl-2 py-1 flex-grow-1' data-toggle='dropdown' href="#">
<%= user_thumbnail_tiny(current_user, :width => 25, :height => 25) %> <%= user_thumbnail_tiny(current_user, :width => 25, :height => 25) %>
<%= render :partial => "layouts/inbox" %> <%= render :partial => "layouts/inbox" %>
<span class="user-button"> <span class="user-button">
<span class='username'> <span class='username'>
<%= current_user.display_name %> <%= current_user.display_name %>
</span> </span>
<b class="caret"></b>
</span> </span>
</a> </a>
<ul class='dropdown-menu'> <div class='dropdown-menu dropdown-menu-right'>
<li> <%= link_to inbox_messages_path, :class => "dropdown-item" do %>
<%= link_to inbox_messages_path do %> <%= t("users.show.my messages") %>
<span class='count-number'><%= number_with_delimiter(current_user.new_messages.size) %></span> <span class='count-number'><%= number_with_delimiter(current_user.new_messages.size) %></span>
<%= t("users.show.my messages") %> <% end %>
<% end %> <%= link_to t("users.show.my profile"), user_path(current_user), :class => "dropdown-item" %>
</li> <%= link_to t("users.show.my settings"), { :controller => "users", :action => "account", :display_name => current_user.display_name }, { :class => "dropdown-item" } %>
<li> <div class="dropdown-divider"></div>
<%= link_to t("users.show.my profile"), user_path(current_user) %> <%= yield :greeting %>
</li> <%= link_to t("layouts.logout"), logout_path(:referer => request.fullpath), :method => "post", :class => "geolink dropdown-item" %>
<li> </div>
<%= link_to t("users.show.my settings"), :controller => "users", :action => "account", :display_name => current_user.display_name %>
</li>
<li class="divider"></li>
<li>
<%= yield :greeting %>
</li>
<li>
<%= link_to t("layouts.logout"), logout_path(:referer => request.fullpath), :method => "post", :class => "geolink" %>
</li>
</ul>
</div> </div>
<% else %> <% else %>
<ul class="user-menu clearfix"> <div class="d-inline-flex btn-group login-menu" role="">
<li><%= link_to t("layouts.log_in"), login_path(:referer => request.fullpath), :class => "geolink" %></li> <%= link_to t("layouts.log_in"), login_path(:referer => request.fullpath), :class => "geolink btn btn-outline-secondary" %>
<li><%= link_to t("layouts.sign_up"), user_new_path %></li> <%= link_to t("layouts.sign_up"), user_new_path, :class => "btn btn-outline-secondary" %>
</ul> </ul>
<% end %> <% end %>
</nav> </nav>

View file

@ -9,7 +9,7 @@
<%= link_to t("layouts.home"), <%= link_to t("layouts.home"),
"#", "#",
:id => "homeanchor", :id => "homeanchor",
:class => "set_position", :class => "set_position dropdown-item",
:data => { :lat => current_user.home_lat, :data => { :lat => current_user.home_lat,
:lon => current_user.home_lon, :lon => current_user.home_lon,
:zoom => 15 } %> :zoom => 15 } %>

View file

@ -1,161 +0,0 @@
/* ========================================================================
* Bootstrap: dropdown.js v3.3.2
* http://getbootstrap.com/javascript/#dropdowns
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// DROPDOWN CLASS DEFINITION
// =========================
var backdrop = '.dropdown-backdrop'
var toggle = '[data-toggle="dropdown"]'
var Dropdown = function (element) {
$(element).on('click.bs.dropdown', this.toggle)
}
Dropdown.VERSION = '3.3.2'
Dropdown.prototype.toggle = function (e) {
var $this = $(this)
if ($this.is('.disabled, :disabled')) return
var $parent = getParent($this)
var isActive = $parent.hasClass('open')
clearMenus()
if (!isActive) {
if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
// if mobile we use a backdrop because click events don't delegate
$('<div class="dropdown-backdrop"/>').insertAfter($(this)).on('click', clearMenus)
}
var relatedTarget = { relatedTarget: this }
$parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))
if (e.isDefaultPrevented()) return
$this
.trigger('focus')
.attr('aria-expanded', 'true')
$parent
.toggleClass('open')
.trigger('shown.bs.dropdown', relatedTarget)
}
return false
}
Dropdown.prototype.keydown = function (e) {
if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return
var $this = $(this)
e.preventDefault()
e.stopPropagation()
if ($this.is('.disabled, :disabled')) return
var $parent = getParent($this)
var isActive = $parent.hasClass('open')
if ((!isActive && e.which != 27) || (isActive && e.which == 27)) {
if (e.which == 27) $parent.find(toggle).trigger('focus')
return $this.trigger('click')
}
var desc = ' li:not(.divider):visible a'
var $items = $parent.find('[role="menu"]' + desc + ', [role="listbox"]' + desc)
if (!$items.length) return
var index = $items.index(e.target)
if (e.which == 38 && index > 0) index-- // up
if (e.which == 40 && index < $items.length - 1) index++ // down
if (!~index) index = 0
$items.eq(index).trigger('focus')
}
function clearMenus(e) {
if (e && e.which === 3) return
$(backdrop).remove()
$(toggle).each(function () {
var $this = $(this)
var $parent = getParent($this)
var relatedTarget = { relatedTarget: this }
if (!$parent.hasClass('open')) return
$parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
if (e.isDefaultPrevented()) return
$this.attr('aria-expanded', 'false')
$parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)
})
}
function getParent($this) {
var selector = $this.attr('data-target')
if (!selector) {
selector = $this.attr('href')
selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
}
var $parent = selector && $(selector)
return $parent && $parent.length ? $parent : $this.parent()
}
// DROPDOWN PLUGIN DEFINITION
// ==========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.dropdown')
if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
if (typeof option == 'string') data[option].call($this)
})
}
var old = $.fn.dropdown
$.fn.dropdown = Plugin
$.fn.dropdown.Constructor = Dropdown
// DROPDOWN NO CONFLICT
// ====================
$.fn.dropdown.noConflict = function () {
$.fn.dropdown = old
return this
}
// APPLY TO STANDARD DROPDOWN ELEMENTS
// ===================================
$(document)
.on('click.bs.dropdown.data-api', clearMenus)
.on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
.on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
.on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
.on('keydown.bs.dropdown.data-api', '[role="menu"]', Dropdown.prototype.keydown)
.on('keydown.bs.dropdown.data-api', '[role="listbox"]', Dropdown.prototype.keydown)
}(jQuery);

View file

@ -1,472 +0,0 @@
/* ========================================================================
* Bootstrap: tooltip.js v3.3.2
* http://getbootstrap.com/javascript/#tooltip
* Inspired by the original jQuery.tipsy by Jason Frame
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// TOOLTIP PUBLIC CLASS DEFINITION
// ===============================
var Tooltip = function (element, options) {
this.type =
this.options =
this.enabled =
this.timeout =
this.hoverState =
this.$element = null
this.init('tooltip', element, options)
}
Tooltip.VERSION = '3.3.2'
Tooltip.TRANSITION_DURATION = 150
Tooltip.DEFAULTS = {
animation: true,
placement: 'top',
selector: false,
template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
trigger: 'hover focus',
title: '',
delay: 0,
html: false,
container: false,
viewport: {
selector: 'body',
padding: 0
}
}
Tooltip.prototype.init = function (type, element, options) {
this.enabled = true
this.type = type
this.$element = $(element)
this.options = this.getOptions(options)
this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport)
var triggers = this.options.trigger.split(' ')
for (var i = triggers.length; i--;) {
var trigger = triggers[i]
if (trigger == 'click') {
this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
} else if (trigger != 'manual') {
var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin'
var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'
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()
}
Tooltip.prototype.getDefaults = function () {
return Tooltip.DEFAULTS
}
Tooltip.prototype.getOptions = function (options) {
options = $.extend({}, this.getDefaults(), this.$element.data(), options)
if (options.delay && typeof options.delay == 'number') {
options.delay = {
show: options.delay,
hide: options.delay
}
}
return options
}
Tooltip.prototype.getDelegateOptions = function () {
var options = {}
var defaults = this.getDefaults()
this._options && $.each(this._options, function (key, value) {
if (defaults[key] != value) options[key] = value
})
return options
}
Tooltip.prototype.enter = function (obj) {
var self = obj instanceof this.constructor ?
obj : $(obj.currentTarget).data('bs.' + this.type)
if (self && self.$tip && self.$tip.is(':visible')) {
self.hoverState = 'in'
return
}
if (!self) {
self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
$(obj.currentTarget).data('bs.' + this.type, self)
}
clearTimeout(self.timeout)
self.hoverState = 'in'
if (!self.options.delay || !self.options.delay.show) return self.show()
self.timeout = setTimeout(function () {
if (self.hoverState == 'in') self.show()
}, self.options.delay.show)
}
Tooltip.prototype.leave = function (obj) {
var self = obj instanceof this.constructor ?
obj : $(obj.currentTarget).data('bs.' + this.type)
if (!self) {
self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
$(obj.currentTarget).data('bs.' + this.type, self)
}
clearTimeout(self.timeout)
self.hoverState = 'out'
if (!self.options.delay || !self.options.delay.hide) return self.hide()
self.timeout = setTimeout(function () {
if (self.hoverState == 'out') self.hide()
}, self.options.delay.hide)
}
Tooltip.prototype.show = function () {
var e = $.Event('show.bs.' + this.type)
if (this.hasContent() && this.enabled) {
this.$element.trigger(e)
var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])
if (e.isDefaultPrevented() || !inDom) return
var that = this
var $tip = this.tip()
var tipId = this.getUID(this.type)
this.setContent()
$tip.attr('id', tipId)
this.$element.attr('aria-describedby', tipId)
if (this.options.animation) $tip.addClass('fade')
var placement = typeof this.options.placement == 'function' ?
this.options.placement.call(this, $tip[0], this.$element[0]) :
this.options.placement
var autoToken = /\s?auto?\s?/i
var autoPlace = autoToken.test(placement)
if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
$tip
.detach()
.css({ top: 0, left: 0, display: 'block' })
.addClass(placement)
.data('bs.' + this.type, this)
this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
var pos = this.getPosition()
var actualWidth = $tip[0].offsetWidth
var actualHeight = $tip[0].offsetHeight
if (autoPlace) {
var orgPlacement = placement
var $container = this.options.container ? $(this.options.container) : this.$element.parent()
var containerDim = this.getPosition($container)
placement = placement == 'bottom' && pos.bottom + actualHeight > containerDim.bottom ? 'top' :
placement == 'top' && pos.top - actualHeight < containerDim.top ? 'bottom' :
placement == 'right' && pos.right + actualWidth > containerDim.width ? 'left' :
placement == 'left' && pos.left - actualWidth < containerDim.left ? 'right' :
placement
$tip
.removeClass(orgPlacement)
.addClass(placement)
}
var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
this.applyPlacement(calculatedOffset, placement)
var complete = function () {
var prevHoverState = that.hoverState
that.$element.trigger('shown.bs.' + that.type)
that.hoverState = null
if (prevHoverState == 'out') that.leave(that)
}
$.support.transition && this.$tip.hasClass('fade') ?
$tip
.one('bsTransitionEnd', complete)
.emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
complete()
}
}
Tooltip.prototype.applyPlacement = function (offset, placement) {
var $tip = this.tip()
var width = $tip[0].offsetWidth
var height = $tip[0].offsetHeight
// manually read margins because getBoundingClientRect includes difference
var marginTop = parseInt($tip.css('margin-top'), 10)
var marginLeft = parseInt($tip.css('margin-left'), 10)
// we must check for NaN for ie 8/9
if (isNaN(marginTop)) marginTop = 0
if (isNaN(marginLeft)) marginLeft = 0
offset.top = offset.top + marginTop
offset.left = offset.left + marginLeft
// $.fn.offset doesn't round pixel values
// so we use setOffset directly with our own function B-0
$.offset.setOffset($tip[0], $.extend({
using: function (props) {
$tip.css({
top: Math.round(props.top),
left: Math.round(props.left)
})
}
}, offset), 0)
$tip.addClass('in')
// check to see if placing tip in new offset caused the tip to resize itself
var actualWidth = $tip[0].offsetWidth
var actualHeight = $tip[0].offsetHeight
if (placement == 'top' && actualHeight != height) {
offset.top = offset.top + height - actualHeight
}
var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
if (delta.left) offset.left += delta.left
else offset.top += delta.top
var isVertical = /top|bottom/.test(placement)
var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'
$tip.offset(offset)
this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
}
Tooltip.prototype.replaceArrow = function (delta, dimension, isHorizontal) {
this.arrow()
.css(isHorizontal ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
.css(isHorizontal ? 'top' : 'left', '')
}
Tooltip.prototype.setContent = function () {
var $tip = this.tip()
var title = this.getTitle()
$tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
$tip.removeClass('fade in top bottom left right')
}
Tooltip.prototype.hide = function (callback) {
var that = this
var $tip = this.tip()
var e = $.Event('hide.bs.' + this.type)
function complete() {
if (that.hoverState != 'in') $tip.detach()
that.$element
.removeAttr('aria-describedby')
.trigger('hidden.bs.' + that.type)
callback && callback()
}
this.$element.trigger(e)
if (e.isDefaultPrevented()) return
$tip.removeClass('in')
$.support.transition && this.$tip.hasClass('fade') ?
$tip
.one('bsTransitionEnd', complete)
.emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
complete()
this.hoverState = null
return this
}
Tooltip.prototype.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', '')
}
}
Tooltip.prototype.hasContent = function () {
return this.getTitle()
}
Tooltip.prototype.getPosition = function ($element) {
$element = $element || this.$element
var el = $element[0]
var isBody = el.tagName == 'BODY'
var elRect = el.getBoundingClientRect()
if (elRect.width == null) {
// width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
}
var elOffset = isBody ? { top: 0, left: 0 } : $element.offset()
var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null
return $.extend({}, elRect, scroll, outerDims, elOffset)
}
Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } :
placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
/* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
}
Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
var delta = { top: 0, left: 0 }
if (!this.$viewport) return delta
var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
var viewportDimensions = this.getPosition(this.$viewport)
if (/right|left/.test(placement)) {
var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll
var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
if (topEdgeOffset < viewportDimensions.top) { // top overflow
delta.top = viewportDimensions.top - topEdgeOffset
} else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
}
} else {
var leftEdgeOffset = pos.left - viewportPadding
var rightEdgeOffset = pos.left + viewportPadding + actualWidth
if (leftEdgeOffset < viewportDimensions.left) { // left overflow
delta.left = viewportDimensions.left - leftEdgeOffset
} else if (rightEdgeOffset > viewportDimensions.width) { // right overflow
delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
}
}
return delta
}
Tooltip.prototype.getTitle = function () {
var title
var $e = this.$element
var o = this.options
title = $e.attr('data-original-title')
|| (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
return title
}
Tooltip.prototype.getUID = function (prefix) {
do prefix += ~~(Math.random() * 1000000)
while (document.getElementById(prefix))
return prefix
}
Tooltip.prototype.tip = function () {
return (this.$tip = this.$tip || $(this.options.template))
}
Tooltip.prototype.arrow = function () {
return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
}
Tooltip.prototype.enable = function () {
this.enabled = true
}
Tooltip.prototype.disable = function () {
this.enabled = false
}
Tooltip.prototype.toggleEnabled = function () {
this.enabled = !this.enabled
}
Tooltip.prototype.toggle = function (e) {
var self = this
if (e) {
self = $(e.currentTarget).data('bs.' + this.type)
if (!self) {
self = new this.constructor(e.currentTarget, this.getDelegateOptions())
$(e.currentTarget).data('bs.' + this.type, self)
}
}
self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
}
Tooltip.prototype.destroy = function () {
var that = this
clearTimeout(this.timeout)
this.hide(function () {
that.$element.off('.' + that.type).removeData('bs.' + that.type)
})
}
// TOOLTIP PLUGIN DEFINITION
// =========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.tooltip')
var options = typeof option == 'object' && option
if (!data && option == 'destroy') return
if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
if (typeof option == 'string') data[option]()
})
}
var old = $.fn.tooltip
$.fn.tooltip = Plugin
$.fn.tooltip.Constructor = Tooltip
// TOOLTIP NO CONFLICT
// ===================
$.fn.tooltip.noConflict = function () {
$.fn.tooltip = old
return this
}
}(jQuery);