新手问题 踩到 Bootstrap3 popover 的一个坑

gihnius · 2014年03月30日 · 最后由 gihnius 回复于 2014年03月31日 · 18651 次阅读

原文

在实现显示投票用户的功能的时候 (看这里) , 想把用户名直接用 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 是函数, 那么它会被执行两次.

<%= link_to "More", "javascript::void()", id: "more-info-#{item.id}”, class: “more-info" %> 

<div id="more-info-<%= item.id %>-content" style="display:none”>
   <%= item.id %> Hello world
</div>
_.each($('.more-info'), (ele)->
  content = $("#" + $(ele).attr('id') + -content").html()
  $(ele).popover
    html: true
    placement: 'right'
    content: content
)

自己实现吧

@leekelby 你这个不是动态的,和楼主环境不一样。

@gihnius 我碰到过类似情况,也用的类似处理办法,也有动态加载后尺寸不一的问题。我的解决方法是 popover 里面的 html 用一个 wrapper 包起来,然后强行规定 wrap 的尺寸。

匿名 #6 2014年03月30日
function content() {
   if (content._cache) return content._cache;
   var html=$.ajax("xxxx");
   return content._cache=html;
};
 el.popover({
       html: true,
       content: content,
});

其实它的设计模式太弱了,应该考虑用 Deferred 或双向的 Data Binding,最好考虑用 async 的请求

#4 楼 @billy 也想过加个 wrap, 确实也加了,只限定 max-height 和 scroll, 固定大小的话不适合, 通常动态加载的内容不是很多,所以固定大了,太多空白,固定小了,内容一多很难看。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号