Gih's Blog

只言片语

踩到 Bootstrap3 popover 的一个坑

2014-03-30 by gihnius, tagged as web

在实现显示投票用户的功能的时候(看这里(过期)) , 想把用户名直接用 bootstrap3 的 popover 展示. 需要在 popover 里面动态加载用户名列表.

开始时这么干的: 

html:

<a href="javascript:void(0);" class="voted-users-popover" ....>显示该选项用户</a>
<a href="javascript:void(0);" class="voted-users-popover" ....>显示该选项用户</a>
<a href="javascript:void(0);" class="voted-users-popover" ....>显示该选项用户</a>

js:

// setup popover
$("a.voted-users-popover").each(function() {
    var el = $(this);
    el.popover({
       html: true,
       content: function() {
           // call ajax and return the content html.
           // 这里 ajax call 应该是设置 async = false.
       },
    });
});

ok, 好像是工作了.
但是 Debug 时发现在每次点击 voted-users-popover 时应用的日志却显示 两个 requests.
查看浏览器的 console 也发现, 这里的函数被执行了两次:

       content: function() {
           console.log(">> getting voted users list...");
           // call ajax and return the content html.
           // 这里 ajax call 应该是设置 async = false.
       },

这是不该出现的结果.

然后是, 各种折腾... google..., 大半天后才想起 bootsrap3 的源代码!

找到 popover.js, 发现下面的代码:

  // Popover 是继承/扩展自 Tooltip 的, getTitle() 在 tooltip.js 里面定义的
  Popover.prototype.hasContent = function () {
    return this.getTitle() || this.getContent()
    // 如果提供 title 就不会执行 getContent()
  }

  Popover.prototype.getContent = function () {
    var $e = this.$element
    var o  = this.options

    return $e.attr('data-content')
      || (typeof o.content == 'function' ?
            o.content.call($e[0]) :
            o.content)

    // 如果 content 是一个函数, 这里就会调用该函数返回 content
    // 而在 popover/tooltip 执行 show 方法时, 是先检查有没有 content `hasContent()` 之后, 再调用 `setContent()` 来 render popover div 的. `setContent()` 当然也得执行 `getContent()`.

  }

好吧! 明显这样会调用两次 content 的函数!

所以, 只有给一个 title 吧:

<a href="javascript:void(0);" title="显示该选项用户" class="voted-users-popover" ....>显示该选项用户</a>
<a href="javascript:void(0);" title="显示该选项用户" class="voted-users-popover" ....>显示该选项用户</a>
<a href="javascript:void(0);" title="显示该选项用户" class="voted-users-popover" ....>显示该选项用户</a>

如果不设置 title, 能不能解决?
试试 content 不由函数提供! 那怎么动态加载 content ?
html 里删除 title,
js:

// setup popover
$("a.voted-users-popover").each(function() {
    var el = $(this);
    el.popover({
       html: true,
       content: '<center><i class="fa fa-spin fa-spinner"></i></center>',
    });
    // 调用 popover 的 shown 事件来加载内容
    el.on('shown.bs.popover', function(){
        //...
        $.ajax({
            // 这次不用 async = false 了!
            success: function(data) {
                if(good(data)) {
                    el.next().find("div.popover-content").html(html_from_data(data));
                    // popover div 在 el 的下面.
                } else {
                    el.next().find("div.popover-content").html("");
                }
            },
        })

    });
});

ok, 好像也工作了, 经测试没有两次 requests 了.

但也带来一个新的问题, 动态内容是在 popover 弹出(shown)后加载的, 也就是 popover div 的位置已经固定了, 渲染内容后整体的位置可能不是你所期望的!

不过最后还是采用的后面的方案! 因为可以方便地显示一个 loading... 菊花!

当然, 还有第三种方案:

自己去实现一个类似 popover 的东西...


补充:
总结就是:
通过 $(e).popover({}) 初始化 popover 的 content 时, 如果 没有初始化 e 的 title 且这个 content 是函数, 那么它会被执行两次.