悬停时如何保持Bootstrap弹出窗口的生命?


114

我正在使用Bootstrap弹出窗口来创建显示用户信息的悬停卡,并在将鼠标悬停在按钮上时触发它。我想在悬停弹出窗口时保持该弹出窗口的活动,但是一旦用户停止将鼠标悬停在按钮上,它就会消失。我怎样才能做到这一点?

$('#example').popover({
    html : true,
    trigger : 'manual',
    content : function() {
        return '<div class="box">Popover</div>';
    }
});

$(document).on('mouseover', '#example', function(){
    $('#example').popover('show');
});

$(document).on('mouseleave', '#example', function(){
    $('#example').popover('hide');
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.js"></script>
<script src="https://unpkg.com/@popperjs/core@2"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"/>

<a href="#" id="example" class="btn btn-danger" rel="popover" >hover for popover</a>


你想保持活着吗?我将鼠标悬停在按钮上并保持打开状态?
大卫·蔡斯

阅读最后一行问题
vikas devde 2013年

Answers:


172

使用以下代码段进行测试:

进行细微修改(来自vikas提供的解决方案)以适合我的用例。

  1. 在悬停事件上为弹出按钮打开弹出窗口
  2. 将鼠标悬停在弹出框上方时,保持弹出框处于打开状态
  3. 在弹出框按钮或弹出框上关闭mouseleave上的弹出框。

$(".pop").popover({
    trigger: "manual",
    html: true,
    animation: false
  })
  .on("mouseenter", function() {
    var _this = this;
    $(this).popover("show");
    $(".popover").on("mouseleave", function() {
      $(_this).popover('hide');
    });
  }).on("mouseleave", function() {
    var _this = this;
    setTimeout(function() {
      if (!$(".popover:hover").length) {
        $(_this).popover("hide");
      }
    }, 300);
  });
<!DOCTYPE html>
<html>

<head>
  <link data-require="bootstrap-css@*" data-semver="3.2.0" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" />
  <script data-require="jquery@*" data-semver="2.1.1" src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
  <script data-require="bootstrap@*" data-semver="3.2.0" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.js"></script>

  <link rel="stylesheet" href="style.css" />

</head>

<body>
  <h2 class='text-primary'>Another Great "KISS" Bootstrap Popover example!</h2>
  <p class='text-muted'>KISS = Keep It Simple S....</p>

  <p class='text-primary'>Goal:</p>
  <ul>
    <li>Open popover on hover event for the popover button</li>
    <li>Keep popover open when hovering over the popover box</li>
    <li>Close popover on mouseleave for either the popover button, or the popover box.</li>
  </ul>

  <button type="button" class="btn btn-danger pop" data-container="body" data-toggle="popover" data-placement="right" data-content="Optional parameter: Skip if this was not requested<br>                                    A placement group is a logical grouping of instances within a single Availability                                     Zone. Using placement groups enables applications to get the full-bisection bandwidth                                     and low-latency network performance required for tightly coupled, node-to-node                                     communication typical of HPC applications.<br>                                    This only applies to cluster compute instances: cc2.8xlarge, cg1.4xlarge, cr1.8xlarge, hi1.4xlarge and hs1.8xlarge.<br>                                    More info: <a href=&quot;http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/placement-groups.html&quot; target=&quot;_blank&quot;>Click here...</a>"
    data-original-title="" title="">
    HOVER OVER ME
    </button>
  <br><br>
  <button type="button" class="btn btn-info pop" data-container="body" data-toggle="popover" data-placement="right" data-content="Optional parameter: Skip if this was not requested<br>                                    A placement group is a logical grouping of instances within a single Availability                                     Zone. Using placement groups enables applications to get the full-bisection bandwidth                                     and low-latency network performance required for tightly coupled, node-to-node                                     communication typical of HPC applications.<br>                                    This only applies to cluster compute instances: cc2.8xlarge, cg1.4xlarge, cr1.8xlarge, hi1.4xlarge and hs1.8xlarge.<br>                                    More info: <a href=&quot;http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/placement-groups.html&quot; target=&quot;_blank&quot;>Click here...</a>"
    data-original-title="" title="">
    HOVER OVER ME... Again!
    </button><br><br>
  <button type="button" class="btn btn-success pop" data-container="body" data-toggle="popover" data-placement="right" data-content="Optional parameter: Skip if this was not requested<br>                                    A placement group is a logical grouping of instances within a single Availability                                     Zone. Using placement groups enables applications to get the full-bisection bandwidth                                     and low-latency network performance required for tightly coupled, node-to-node                                     communication typical of HPC applications.<br>                                    This only applies to cluster compute instances: cc2.8xlarge, cg1.4xlarge, cr1.8xlarge, hi1.4xlarge and hs1.8xlarge.<br>                                    More info: <a href=&quot;http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/placement-groups.html&quot; target=&quot;_blank&quot;>Click here...</a>"
    data-original-title="" title="">
    Okay one more time... !
    </button>
  <br><br>
  <p class='text-info'>Hope that helps you... Drove me crazy for a while</p>
  <script src="script.js"></script>
</body>

</html>


这很完美,我确实注意到;您第二次失踪了$(_this).popover("hide")。但是,谢谢你,它是如此简单和干净!
scapegoat17 2014年

3
这个答案是惊人的。截至2015
退化

1
我在表中使用了它,并添加container: 'body'了选项,因为它使单元格移动。好答案!
Alexander Derck '16

如果您输入弹出窗口,则会将其隐藏起来,然后在300ms之前全部返回触发元素。要解决此问题,请先将弹出框及其触发器都设为:hover,然后再将其隐藏在setTimeout中。我还将在鼠标离开弹出窗口本身时使用setTimeout和相同的方法来修复闪烁。
rzb

确保设置animation:false以解决闪烁-检查上面的Plunker链接。它非常适合我。
OkezieE

84

我追求了另一种解决方案...这是代码

    $('.selector').popover({
        html: true,
        trigger: 'manual',
        container: $(this).attr('id'),
        placement: 'top',
        content: function () {
            $return = '<div class="hover-hovercard"></div>';
        }
    }).on("mouseenter", function () {
        var _this = this;
        $(this).popover("show");
        $(this).siblings(".popover").on("mouseleave", function () {
            $(_this).popover('hide');
        });
    }).on("mouseleave", function () {
        var _this = this;
        setTimeout(function () {
            if (!$(".popover:hover").length) {
                $(_this).popover("hide")
            }
        }, 100);
    });

6
重要的是要添加,animation: false否则反复将鼠标移到链接上会导致其无法正常工作
jasop 2013年

5
我对您的代码@vikas(gist.github.com/Nitrodist/7913848)进行了少量修改。它会在50毫秒后重新检查条件,以使其不会保持打开状态。也就是说,它每隔50ms不断地对其进行检查。
Nitrodist

2
如何对此进行调整,使其适用于刚添加到文档中的实时元素?
williamsowen 2014年

28

这是我的看法:http : //jsfiddle.net/WojtekKruszewski/Zf3m7/22/

有时,当将鼠标从弹出窗口触发器移动到对角线的实际弹出窗口内容时,将鼠标悬停在下面的元素上。我想处理这样的情况–只要您在超时触发前到达弹出窗口内容,就可以安全了(弹出窗口不会消失)。这个需要delay选项。

该hack基本上会覆盖Popover leave函数,但会调用原始功能(启动计时器以隐藏弹出窗口)。然后,它将一次性侦听器附加到mouseenter弹出式内容元素的侦听器。

如果鼠标进入弹出窗口,则清除计时器。然后,它使它mouseleave在弹出窗口上侦听,如果被触发,它将调用原始的离开函数,以便可以启动隐藏计时器。

var originalLeave = $.fn.popover.Constructor.prototype.leave;
$.fn.popover.Constructor.prototype.leave = function(obj){
  var self = obj instanceof this.constructor ?
    obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type)
  var container, timeout;

  originalLeave.call(this, obj);

  if(obj.currentTarget) {
    container = $(obj.currentTarget).siblings('.popover')
    timeout = self.timeout;
    container.one('mouseenter', function(){
      //We entered the actual popover – call off the dogs
      clearTimeout(timeout);
      //Let's monitor popover content instead
      container.one('mouseleave', function(){
        $.fn.popover.Constructor.prototype.leave.call(self, self);
      });
    })
  }
};

5
使用container = self.$tip;此方法可以改进查找容器的方法container。设置属性后,甚至可以找到弹出窗口。这里有个小提琴:jsfiddle.net/dennis_c/xJc65
dbroeks 2013年

3
@pferrel我已经在我的@Wojtek_Kruszewski的小提琴的叉子中解决了这个问题:jsfiddle.net/HugeHugh/pN26d看到了if (!thisTip.is(':visible'))在致电之前检查的部分originalShow()
H Dog

1
如果使用选项初始化弹出窗口,则container: 'body',此解决方案将无法正常工作。该变量container需要替换为self.$tip。检查我的答案以获取更多详细信息:stackoverflow.com/a/28731847/439427
Rubens Mariuzzo 2015年

1
辉煌。与其他答案不同,这在使用“选择器”参数时适用。
jetlej 2015年

1
这里是离开并重新输入尖端仍然墙根它时,它修复了一个错误的改进版本,并且还解决方案时,尖端附着在身体jsfiddle.net/Zf3m7/1499
佐尔坦陶马希

14

我认为一个简单的方法是这样的:

$('.popover').each(function () {
                    var $this = $(this);
                    $this.popover({
                        trigger: 'hover',
                        content: 'Content Here',
                        container: $this
                    })
                });

这样,将在目标元素本身内部创建弹出窗口。因此,当您将鼠标移到弹出框上时,它仍在元素上。Bootstrap 3.3.2与此配合良好。旧版本的动画可能会出现一些问题,因此您可能要禁用“ animation:false”


我知道这个线程很旧,但是我认为这是最好,最干净的解决方案,应该排名更高。唯一的警告是,如果将弹出窗口(以怪异的方式)放置在距触发元素“远离”的位置,这将会中断。但是只要两者之间的距离为零(例如,它们重叠),就可以很好地工作,并且不需要任何自定义JS。谢谢!
JohnGalt

到目前为止,这是最好,干净,最简单的解决方案。应该排名更高!我添加delay: { "hide": 400 }了一些隐藏时间,效果很好!👍
coorasse

14

我使用的触发设置为hover,给容器设置到#element最后加入的位置boxright

这应该是您的设置:

$('#example').popover({
    html: true,
    trigger: 'hover',
    container: '#example',
    placement: 'right',
    content: function () {
        return '<div class="box"></div>';
    }
});

#exampleCSS需要position:relative;检查以下jsfiddle:

https://jsfiddle.net/9qn6pw4p/1/

已编辑

这个小提琴具有两个链接,都可以正常使用 http://jsfiddle.net/davidchase03/FQE57/4/


嗯,行得通,我可以在content选项中使用jquery ajax 从服务器端获取内容吗?是否行得通,或者我需要为此做一些额外的工作
vikas devde 2013年

@vikasdevde是你可以使用ajax的内容,但是你需要设置了该工作......请标明正确的答案,如果它解决了你OP..以便其他人可以受益
大卫追逐

但是,如果我们将链接本身用作容器,则整个弹出窗口都将成为链接....尝试一下
vikas devde

如果您将链接放在框内,它仍然会链接出去。
大卫·蔡斯

2
jsfiddle都不适合我工作。Chrome 2014
。– pferrel

7

这就是我在网络上其他位的帮助下使用bootstrap popover的方式。从现场显示的各种产品中动态获取标题和内容。每个产品或弹出窗口都有唯一的ID。退出产品($ this .pop)或弹出框时,弹出框将消失。超时用于显示弹出窗口,直到通过产品而不是弹出窗口退出为止。

$(".pop").each(function () {
        var $pElem = $(this);
        $pElem.popover(
            {
                html: true,
                trigger: "manual",
                title: getPopoverTitle($pElem.attr("id")),
                content: getPopoverContent($pElem.attr("id")),
                container: 'body',
                animation:false
            }
        );
    }).on("mouseenter", function () {
        var _this = this;
        $(this).popover("show");
        console.log("mouse entered");
        $(".popover").on("mouseleave", function () {
            $(_this).popover('hide');
        });
    }).on("mouseleave", function () {
        var _this = this;
        setTimeout(function () {
            if (!$(".popover:hover").length) {
                $(_this).popover("hide");
            }
        }, 100);
    });
    function getPopoverTitle(target) {
        return $("#" + target + "_content > h3.popover-title").html();
    };

    function getPopoverContent(target) {
        return $("#" + target + "_content > div.popover-content").html();
    };

如果弹出框不是目标元素的子元素,这也将起作用。+1
塔哈·帕克苏

6

这是我设计的一种解决方案,看起来效果很好,同时还允许您使用常规的Bootstrap实现打开所有弹出窗口。

原始提琴:https : //jsfiddle.net/eXpressive/hfear592/

移植到这个问题:

<a href="#" id="example" class="btn btn-danger" rel="popover" >hover for popover</a>

$('#example').popover({
    html : true,
    trigger : 'hover',
    content : function() {
        return '<div class="box"></div>';
    }
}).on('hide.bs.popover', function () {
    if ($(".popover:hover").length) {
      return false;
    }                
}); 

$('body').on('mouseleave', '.popover', function(){
    $('.popover').popover('hide');
});

2

我同意,最好的方法是使用David Chase,Cu Ly等人提供的方法,而其他人则最简单的方法是使用以下container: $(this)属性:

$(selectorString).each(
  var $this = $(this);
  $this.popover({
    html: true,
    placement: "top",
    container: $this,
    trigger: "hover",
    title: "Popover",
    content: "Hey, you hovered on element"
  });
);

我想在这里指出,在这种情况下,弹出框将继承当前元素的所有属性。因此,例如,如果对.btn元素(引导程序)执行此操作,则将无法在popover中选择文本。只是想记录一下,因为我花了很多时间对此进行研究。


1

维卡斯的答案对我来说非常完美,在这里我还添加了对延迟的支持(显示/隐藏)。

var popover = $('#example');
var options = {
    animation : true,
    html: true,
    trigger: 'manual',
    placement: 'right',
    delay: {show: 500, hide: 100}
};   
popover
    .popover(options)
    .on("mouseenter", function () {

        var t = this;
        var popover = $(this);    
        setTimeout(function () {

            if (popover.is(":hover")) {

                popover.popover("show");
                popover.siblings(".popover").on("mouseleave", function () {
                    $(t).popover('hide');
                });
            }
        }, options.delay.show);
    })
    .on("mouseleave", function () {
        var t = this;
        var popover = $(this);

        setTimeout(function () {
            if (popover.siblings(".popover").length && !popover.siblings(".popover").is(":hover")) {
                $(t).popover("hide")
            }
        }, options.delay.hide);
    });     

还请注意我改了:

if (!$(".popover:hover").length) {

与:

if (popover.siblings(".popover").length && !popover.siblings(".popover").is(":hover")) {

因此它完全引用打开的弹出窗口,而不引用其他窗口(由于延迟,从现在开始可以同时打开多个窗口)


我在结尾处所做的评论实际上在使用container:body时是不正确的,如果还是要使用Vikas的解决方案的那一行
user1993198 2014年

1

选定的答案有效,但如果使用body容器初始化弹出窗口,则会失败。

$('a').popover({ container: 'body' });

基于所选答案的解决方案是使用弹出框之前需要放置以下代码。

var originalLeave = $.fn.popover.Constructor.prototype.leave;
$.fn.popover.Constructor.prototype.leave = function(obj) {
    var self = obj instanceof this.constructor ? obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type);
    originalLeave.call(this, obj);

    if (obj.currentTarget) {
        self.$tip.one('mouseenter', function() {
            clearTimeout(self.timeout);
            self.$tip.one('mouseleave', function() {
                $.fn.popover.Constructor.prototype.leave.call(self, self);
            });
        })
    }
};

使用self.$tip而不是遍历DOM(使弹出窗口始终是该元素的同级元素)而不是遍历DOM所做的更改很小。


0

工具提示也一样:

对我而言,以下解决方案有效,因为它不会在每个“ mouseenter”上添加事件侦听器,并且可以将鼠标悬停在工具提示元素上,从而使工具提示保持活动状态。

$ ->

  $('.element').tooltip({
    html: true,
    trigger: 'manual'
  }).
  on 'mouseenter', ->
    clearTimeout window.tooltipTimeout
    $(this).tooltip('show') unless $('.tooltip:visible').length > 0
  .
  on 'mouseleave', ->
    _this = this
    window.tooltipTimeout = setTimeout ->
      $(_this).tooltip('hide')
    , 100

$(document).on 'mouseenter', '.tooltip', ->
  clearTimeout window.tooltipTimeout

$(document).on 'mouseleave', '.tooltip', ->
  trigger = $($(this).siblings('.element')[0])
  window.tooltipTimeout = setTimeout ->
    trigger.tooltip('hide')
  , 100

0

这个解决方案对我来说很好:(现在是防弹);-)

function enableThumbPopover() {
    var counter;

    $('.thumbcontainer').popover({
        trigger: 'manual',
        animation: false,
        html: true,
        title: function () {
            return $(this).parent().find('.thumbPopover > .title').html();
        },
        content: function () {
            return $(this).parent().find('.thumbPopover > .body').html();
        },
        container: 'body',
        placement: 'auto'
    }).on("mouseenter",function () {
        var _this = this; // thumbcontainer

        console.log('thumbcontainer mouseenter')
        // clear the counter
        clearTimeout(counter);
        // Close all other Popovers
        $('.thumbcontainer').not(_this).popover('hide');

        // start new timeout to show popover
        counter = setTimeout(function(){
            if($(_this).is(':hover'))
            {
                $(_this).popover("show");
            }
            $(".popover").on("mouseleave", function () {
                $('.thumbcontainer').popover('hide');
            });
        }, 400);

    }).on("mouseleave", function () {
        var _this = this;

        setTimeout(function () {
            if (!$(".popover:hover").length) {
                if(!$(this).is(':hover'))
                {
                    $(_this).popover('hide');
                }
            }
        }, 200);
    });
}

0
        $(function() {
            $("[data-toggle = 'popover']").popover({
                placement: 'left',
                html: true,
                trigger: "  focus",
            }).on("mouseenter", function() {
                var _this = this;
                $(this).popover("show");
                $(this).siblings(".popover").on("mouseleave", function() {
                    $(_this).popover('hide');
                });
            }).on("mouseleave", function() {
                var _this = this;
                setTimeout(function() {
                    if (!$(".popover:hover").length) {
                        $(_this).popover("hide")
                    }
                }, 100);
            });
        }); 

0

我发现mouseleave当发生奇怪的事情时,不会触发,例如窗口焦点突然改变,然后用户返回浏览器。那样的话mouseleave将永远不会触发,直到光标经过并再次离开该元素。

我想出的解决方案依赖于mouseenterwindow对象,因此当鼠标移动到页面上的其他任何地方时,该解决方案便会消失。

它旨在与页面上具有多个触发它的元素一起工作(例如在表中)。

var allMenus = $(".menus");
allMenus.popover({
    html: true,
    trigger: "manual",
    placement: "bottom",
    content: $("#menuContent")[0].outerHTML
}).on("mouseenter", (e) => {
    allMenus.not(e.target).popover("hide");
    $(e.target).popover("show");
    e.stopPropagation();
}).on("shown.bs.popover", () => {
    $(window).on("mouseenter.hidepopover", (e) => {
        if ($(e.target).parents(".popover").length === 0) {
            allMenus.popover("hide");
            $(window).off("mouseenter.hidepopover");
        }
    });
});

0

这将是更加灵活hover()

$(".my-popover").hover(
    function() {  // mouse in event
        $this = $(this);
        $this.popover({
            html: true,
            content: "Your content",
            trigger: "manual",
            animation: false
            });
        $this.popover("show");
        $(".popover").on("mouseleave", function() {
            $this.popover("hide");
        });
    },
    function() {  // mouse out event
        setTimeout(function() {
            if (!$(".popover:hover").length) {
                $this.popover("hide");
            }
        }, 100);
    } 
)

0

简单:)

$('[data-toggle="popover"]').popover( { "container":"body", "trigger":"focus", "html":true });
$('[data-toggle="popover"]').mouseenter(function(){
    $(this).trigger('focus');
});

0

我最近需要与KO一起使用,并且在延迟显示和隐藏时上述解决方案效果不佳。下面应解决此问题。基于引导工具提示的工作方式。希望这对某人有帮助。

var options = {
                delay: { show: 1000, hide: 50 },
                trigger: 'manual',                      
                html: true
            };
var $popover = $(element).popover(options);

$popover.on('mouseenter', function () { // This is entering the triggering element
    var self = this;

    clearTimeout(self.timeout);
    self.hoverState = 'in';

    self.timeout = setTimeout(function () {
        if (self.hoverState == 'in') {
            $(self).popover("show");

            $(".popover, .popover *").on('mouseover', function () { // This is moving over the popover
                clearTimeout(self.timeout);
            });                                                                 

            $(".popover").on('mouseleave', function () { // This is leaving the popover
                self.timeout = setTimeout(function () {
                    if (self.hoverState == 'out') {
                        $(self).popover('hide');
                    }
                }, options.delay.hide);
            });
        }
    }, options.delay.show);
}).on('mouseleave', function (event) { // This is leaving the triggering element
    var self = this;

    clearTimeout(self.timeout);
    self.hoverState = 'out';

    self.timeout = setTimeout(function () {                             
        if (self.hoverState == 'out') {
            $(self).popover('hide');
        }

    }, options.delay.hide);
});

-1

这是我的带有延迟并由ajax加载的显示动态工具提示的代码。

$(window).on('load', function () {
    generatePopovers();
    
    $.fn.dataTable.tables({ visible: true, api: true }).on('draw.dt', function () {
        generatePopovers();
    });
});

$(document).ajaxStop(function () {
    generatePopovers();
});

function generatePopovers() {
var popover = $('a[href*="../Something.aspx"]'); //locate the elements to popover

popover.each(function (index) {
    var poplink = $(this);
    if (poplink.attr("data-toggle") == null) {
        console.log("RENDER POPOVER: " + poplink.attr('href'));
        poplink.attr("data-toggle", "popover");
        poplink.attr("data-html", "true");
        poplink.attr("data-placement", "top");
        poplink.attr("data-content", "Loading...");
        poplink.popover({
            animation: false,
            html: true,
            trigger: 'manual',
            container: 'body',
            placement: 'top'
        }).on("mouseenter", function () {
            var thispoplink = poplink;
            setTimeout(function () {
                if (thispoplink.is(":hover")) {
                    thispoplink.popover("show");
                    loadDynamicData(thispoplink); //load data by ajax if you want
                    $('body .popover').on("mouseleave", function () {
                        thispoplink.popover('hide');
                    });
                }
            }, 1000);
        }).on("mouseleave", function () {
            var thispoplink = poplink;
            setTimeout(function () {
                if (!$("body").find(".popover:hover").length) {
                    thispoplink.popover("hide");
                }
            }, 100);
        });
    }
});

function loadDynamicData(popover) {
    var params = new Object();
    params.somedata = popover.attr("href").split("somedata=")[1]; //obtain a parameter to send
    params = JSON.stringify(params);
    //check if the content is not seted
    if (popover.attr("data-content") == "Loading...") {
        $.ajax({
            type: "POST",
            url: "../Default.aspx/ObtainData",
            data: params,
            contentType: "application/json; charset=utf-8",
            dataType: 'json',
            success: function (data) {
                console.log(JSON.parse(data.d));
                var dato = JSON.parse(data.d);
                if (dato != null) {
                    popover.attr("data-content",dato.something); // here you can set the data returned
                    if (popover.is(":hover")) {
                        popover.popover("show"); //use this for reload the view
                    }
                }
            },

            failure: function (data) {
                itShowError("- Error AJAX.<br>");
            }
        });
    }
}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.