Tooltipster does not work in a generated content

2019-05-07 18:18发布

I have installed Tooltipster on my website, but it does not work for content added dynamically.

You can see it in the "SORT GAMES BY YOUR CHOICE (NEWEST GAMES BY DEFAULT)" box, when I select sort by "newest first", "most popular," etc... when the content is generated, Tooltipster does not see that content. Somehow I must tell Tooltipster about that content.

Here is the Tooltipster code:

<head></head>
    <link rel="stylesheet" type="text/css" href="http://www.heroplaysonline.com/css/tooltipster.css" />
    <script type="text/javascript" src="http://www.heroplaysonline.com/js/jquery.tooltipster.min.js"></script>
<script>
    $(document).ready(function() {
        $('.tooltip').tooltipster({
            position: 'bottom-left',
            contentAsHTML: 'true',
            animation: 'grow',
            theme: '.my-custom-theme'
        });
    });
</script>

Here is the script which generates the content:

jQuery(document).ready(function($){
  $.fn.vAlign = function() {
    return this.each(function(i){
      var ah = $(this).height();
      var ph = $(this).parent().height();
      var mh = Math.ceil((ph-ah) / 2);
      $(this).css('margin-top', mh);
    });
  };

  $('#sortable-game-box-order select').change(function() {
    var sorturl = $(this).attr("value");
    var loaderheight = $('#sortable-game-box-inner-content').height();
    $('#sortable-game-box-loader').css('height', loaderheight+13);
    $('#sortable-game-box-loader-content').css('height', loaderheight+13);
    $('#sortable-game-box-loader-content img').vAlign();
    $('#sortable-game-box-list').hide();
    $('#sortable-game-box-loader').show();
    $('#sortable-game-box-inner').load(sorturl + ' #sortable-game-box-inner-content', function() {
      $('#sortable-game-box-loader').hide();
      $('#sortable-game-box-list').show();      
    });
  });
});

Here is the code of js, where content is generated dynamically and tooltipster works, but I could not transform it for mine.

(function($){'use strict';Date.now=Date.now||function(){return+new Date();};$.ias=function(options)
{var opts=$.extend({},$.ias.defaults,options);var util=new $.ias.util();var paging=new $.ias.paging(opts.scrollContainer);var hist=(opts.history?new $.ias.history():false);var _self=this;function call_tooltip()
{$('.tooltip_cls').tooltipster({animation:'grow',theme:'.my-custom-theme'});}
function init()
{call_tooltip();var pageNum;paging.onChangePage(function(pageNum,scrollOffset,pageUrl){if(hist){hist.setPage(pageNum,pageUrl);}
opts.onPageChange.call(this,pageNum,pageUrl,scrollOffset);});reset();if(hist&&hist.havePage()){stop_scroll();pageNum=hist.getPage();util.forceScrollTop(function(){var curTreshold;if(pageNum>1){paginateToPage(pageNum);curTreshold=get_scroll_treshold(true);$('html, body').scrollTop(curTreshold);}
else{reset();}});}
return _self;}
init();function reset()
{hide_pagination();opts.scrollContainer.scroll(scroll_handler);call_tooltip();}
function scroll_handler()
{var curScrOffset,scrTreshold;curScrOffset=util.getCurrentScrollOffset(opts.scrollContainer);scrTreshold=get_scroll_treshold();if(curScrOffset>=scrTreshold){if(get_current_page()>=opts.triggerPageTreshold){stop_scroll();show_trigger(function(){paginate(curScrOffset);});}
else{paginate(curScrOffset);}}}
function stop_scroll()
{opts.scrollContainer.unbind('scroll',scroll_handler);}
function hide_pagination()
{$(opts.pagination).hide();}
function get_scroll_treshold(pure)
{var el,treshold;el=$(opts.container).find(opts.item).last();if(el.size()===0){return 0;}
treshold=el.offset().top+ el.height();if(!pure){treshold+=opts.tresholdMargin;}
return treshold;}
function paginate(curScrOffset,onCompleteHandler)
{var urlNextPage;urlNextPage=$(opts.next).attr('href');if(!urlNextPage){if(opts.noneleft){$(opts.container).find(opts.item).last().after(opts.noneleft);}
return stop_scroll();}
if(opts.beforePageChange&&$.isFunction(opts.beforePageChange)){if(opts.beforePageChange(curScrOffset,urlNextPage)===false){return;}}
paging.pushPages(curScrOffset,urlNextPage);stop_scroll();show_loader();loadItems(urlNextPage,function(data,items){var result=opts.onLoadItems.call(this,items),curLastItem;if(result!==false){$(items).hide();curLastItem=$(opts.container).find(opts.item).last();curLastItem.after(items);$(items).fadeIn();}
urlNextPage=$(opts.next,data).attr('href');$(opts.pagination).replaceWith($(opts.pagination,data));remove_loader();hide_pagination();if(urlNextPage){reset();}
else{stop_scroll();}
opts.onRenderComplete.call(this,items);if(onCompleteHandler){onCompleteHandler.call(this);}});}
function loadItems(url,onCompleteHandler,delay)
{var items=[],container,startTime=Date.now(),diffTime,self;delay=delay||opts.loaderDelay;$.get(url,null,function(data){container=$(opts.container,data).eq(0);if(0===container.length){container=$(data).filter(opts.container).eq(0);}
if(container){container.find(opts.item).each(function(){items.push(this);});}
if(onCompleteHandler){self=this;diffTime=Date.now()- startTime;if(diffTime<delay){setTimeout(function(){onCompleteHandler.call(self,data,items);},delay- diffTime);}else{onCompleteHandler.call(self,data,items);}}},'html');}
function paginateToPage(pageNum)
{var curTreshold=get_scroll_treshold(true);if(curTreshold>0){paginate(curTreshold,function(){stop_scroll();if((paging.getCurPageNum(curTreshold)+ 1)<pageNum){paginateToPage(pageNum);$('html,body').animate({'scrollTop':curTreshold},400,'swing');}
else{$('html,body').animate({'scrollTop':curTreshold},1000,'swing');reset();}});}}
function get_current_page()
{var curScrOffset=util.getCurrentScrollOffset(opts.scrollContainer);return paging.getCurPageNum(curScrOffset);}
function get_loader()
{var loader=$('.ias_loader');if(loader.size()===0){loader=$('<div class="ias_loader">'+ opts.loader+'</div>');loader.hide();}
return loader;}
function show_loader()
{var loader=get_loader(),el;if(opts.customLoaderProc!==false){opts.customLoaderProc(loader);}else{el=$(opts.container).find(opts.item).last();el.after(loader);loader.fadeIn();}}
function remove_loader()
{var loader=get_loader();loader.remove();}
function get_trigger(callback)
{var trigger=$('.ias_trigger');if(trigger.size()===0){trigger=$('<div class="ias_trigger"><a href="all.html">'+ opts.trigger+'</a></div>');trigger.hide();}
return trigger;}
function show_trigger(callback)
{var trigger=get_trigger(callback),el;el=$(opts.container).find(opts.item).last();el.after(trigger);trigger.fadeIn();}
function remove_trigger()
{var trigger=get_trigger();trigger.remove();}};$.ias.defaults={container:'#container',scrollContainer:$(window),item:'.item',pagination:'#pagination',next:'.next',noneleft:false,loader:'<img src="images/loader.gif"/>',loaderDelay:600,triggerPageTreshold:4,trigger:'Load more items',tresholdMargin:0,history:true,onPageChange:function(){},beforePageChange:function(){},onLoadItems:function(){},onRenderComplete:function(){},customLoaderProc:false};$.ias.util=function()
{var wndIsLoaded=false;var forceScrollTopIsCompleted=false;var self=this;function init()
{$(window).load(function(){wndIsLoaded=true;});}
init();this.forceScrollTop=function(onCompleteHandler)
{$('html,body').scrollTop(0);if(!forceScrollTopIsCompleted){if(!wndIsLoaded){setTimeout(function(){self.forceScrollTop(onCompleteHandler);},1);}else{onCompleteHandler.call();forceScrollTopIsCompleted=true;}}};this.getCurrentScrollOffset=function(container)
{var scrTop,wndHeight;if(container.get(0)===window){scrTop=container.scrollTop();}else{scrTop=container.offset().top;}
wndHeight=container.height();return scrTop+ wndHeight;};};$.ias.paging=function()
{var pagebreaks=[[0,document.location.toString()]];var changePageHandler=function(){};var lastPageNum=1;var util=new $.ias.util();function init()
{$(window).scroll(scroll_handler);}
init();function scroll_handler()
{var curScrOffset,curPageNum,curPagebreak,scrOffset,urlPage;curScrOffset=util.getCurrentScrollOffset($(window));curPageNum=getCurPageNum(curScrOffset);curPagebreak=getCurPagebreak(curScrOffset);if(lastPageNum!==curPageNum){scrOffset=curPagebreak[0];urlPage=curPagebreak[1];changePageHandler.call({},curPageNum,scrOffset,urlPage);}
lastPageNum=curPageNum;}
function getCurPageNum(scrollOffset)
{for(var i=(pagebreaks.length- 1);i>0;i--){if(scrollOffset>pagebreaks[i][0]){return i+ 1;}}
return 1;}
this.getCurPageNum=function(scrollOffset)
{scrollOffset=scrollOffset||util.getCurrentScrollOffset($(window));return getCurPageNum(scrollOffset);};function getCurPagebreak(scrollOffset)
{for(var i=(pagebreaks.length- 1);i>=0;i--){if(scrollOffset>pagebreaks[i][0]){return pagebreaks[i];}}
return null;}
this.onChangePage=function(fn)
{changePageHandler=fn;};this.pushPages=function(scrollOffset,urlNextPage)
{pagebreaks.push([scrollOffset,urlNextPage]);};};$.ias.history=function()
{var isPushed=false;var isHtml5=false;function init()
{isHtml5=!!(window.history&&history.pushState&&history.replaceState);isHtml5=false;}
init();this.setPage=function(pageNum,pageUrl)
{this.updateState({page:pageNum},'',pageUrl);};this.havePage=function()
{return(this.getState()!==false);};this.getPage=function()
{var stateObj;if(this.havePage()){stateObj=this.getState();return stateObj.page;}
return 1;};this.getState=function()
{var haveState,stateObj,pageNum;if(isHtml5){stateObj=history.state;if(stateObj&&stateObj.ias){return stateObj.ias;}}
else{}
return false;};this.updateState=function(stateObj,title,url)
{if(isPushed){this.replaceState(stateObj,title,url);}
else{this.pushState(stateObj,title,url);}};this.pushState=function(stateObj,title,url)
{var hash;if(isHtml5){history.pushState({ias:stateObj},title,url);}
else{}
isPushed=true;};this.replaceState=function(stateObj,title,url)
{if(isHtml5){history.replaceState({ias:stateObj},title,url);}
else{this.pushState(stateObj,title,url);}};};})(jQuery);

I have been working on this for two days, but could not fix it, because I know nothing in coding. I could only experiment, remove some code etc, but I think that the solution is beyond that.

Any help will be appreciated.

2条回答
我想做一个坏孩纸
2楼-- · 2019-05-07 18:52

The problem you are experiencing can be resolved at least a couple of ways. The best solution is one that reduces the amount of code required to write, and can even improve the performance and maintainability of the application.

This is a problem that will bite you once, but rarely again. It is very common for people new to JavaScript, so remember this.

Tooltipster is fired when the document is "ready." A document is ready when it is fully constructed and ready to have its nodes targeted [1]. Ask yourself, "how often will a document be ready?" The answer, surely, is once every page refresh. If the document can only be ready once per page refresh, that means the anonymous function attached to the event (being a page reresh, or "ready") will only be fired once. The anonymous tooltipster function will only be fired that one time, with all the known and currently available matching nodes. Nodes added to the document after it is ready are not known to tooltipster.

Knowing this, the problem can be approached from at least a couple of angles:

  1. Fire the anonymous tooltipster function every time a change to the document is made. All of the nodes in the section of the document that had the tooltipster function attached to them are most likely already removed, because rearranging nodes in the document tends to be destructive, unless care is taken. This approach is okay, but far from ideal, and certainly not performant or the most maintainable.

  2. Let the parent node delegate the event triggering to its children nodes. This is what should be done. This means that the parent will do the listening for events that previously each of the individual children would do. The parent will likely never change or be manipulated, but its content (children) will. Attaching to the parent is a more robust and maintainable approach; in addition, it performs much faster in the browser, which means end users will appreciate the speedier implementation. A single event attached to the parent node can handle everything that happens to the children nodes.

jQuery makes event delegation very straightforward. Then again, so does plain old JavaScript. Nevertheless, you are using the former. Read about event delegation on jQuery. Do not replace the code on your site until you read it. This is roughly what needs to be written; it has not been tested, so adjustments may be necessary:

$(document).ready(function() {
    $('#sortable-game-box-list').on('mouseover mouseout', '.tooltip', function(e) {
        $(e.target).tooltipster({
            position: 'bottom-left',
            contentAsHTML: 'true',
            animation: 'grow',
            theme: '.my-custom-theme'
        });
    });
});

As mentioned earlier, remember this. It is going to be a problem encountered regularly in JavaScript development, at least until experience takes the lead.


[1] As an aside, this is different from when the document is loaded, which means that in addition to all the nodes being live, all of the assets pulled in by the document are also loaded; assets are external scripts, images, and whatever else gets pulled in synchronously.

查看更多
▲ chillily
3楼-- · 2019-05-07 19:06

danemacmillan, you were right. I have added the following code and now tooltipster works on dynamically generated content too.

<script type="text/javascript">
    $('body').on('mouseover mouseenter', '.tooltip', function(){
         $(this).tooltipster({
            contentAsHTML: 'true',
            animation: 'grow',
            theme: '.my-custom-theme',
            position: 'top-left'
         });
         $(this).tooltipster('show');
    });
</script>

I still don't believe in it, but it really works in fact! :D

查看更多
登录 后发表回答