I'm using the jQuery tooltip to show content that is dynamically loaded (javascript below). In some cases when the mouse moves away from the element the tooltip doesn't go away. My theory is that the loading of the dynamic content introduces a slight delay so the mouse moves away from the element just before the tooltip function completes and so it doesn't consume the mouseleave event. Any way to resolve this?
element.tooltip(
{
items: "table.orderlist label",
open: function (event, ui)
{
ui.tooltip.css("max-width", "600px");
ui.tooltip.css("max-height", "300px");
ui.tooltip.css("overflow", "hidden");
},
content: function (callback)
{
//Get the contents as the tooltip is popping up
MyAjaxCall(iID,
function (result)
{
try
{
//success
callback(result) //returns the result
}
catch (e)
{
DisplayError(e);
}
},
function (result)
{
//Error
DisplayError(result);
});
}
});
Your content function should be a bit more complicated, the possible solution is:
content: function (callback)
{
var $this = $(this), isMouseOn = true;
// check if tooltip ajax-request is in progress
// really needed for slow scripts only
if($this.data('tt-busy')) return;
// check if el is hovered (in jquery <= 1.8 you can simly use .is(':hover')
$this
.data('tt-busy', true)
.on('mouseenter.xxx', function() { isMouseOn = true; })
.on('mouseleave.xxx', function() { isMouseOn = false; });
//
$.get('slow.script', function(d) {
if(isMouseOn) callback(d);
}).always(function() {
// even if result fails set loading var to false and unbind hover events
$this
.data('tt-busy', false)
.off('.xxx');
});
}
Related
The issue I'm having is every time you resize the browser a function is called, that function will make a side panel into an accordion if the screen width is a certain number or below or on a larger screen it's just displaying like an open side panel with no interaction.
In the resize event I call the sidepanel function. Unfortunately every time I resize the browser my side panel function is duplicated. I've been seeing stuff on unbinding but nothing that seems to make sense for how I'm calling the side panel function.
Is there a way in the resize.js to unbind the sidepanel function and rebind to the window so it's only called once every time the window is resized?
Resize.js
$(document).ready(function() {
var resizeTimer;
$(window).on('resize', function() {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function() {
sidePanelAccordion();
}, 250);
});
});
Side-panel.js
function sidePanelAccordion() {
var panelAccordion = $('.side-panel-accordion');
var panelHeader = $('.side-panel-header');
var panelBody = $('.side-panel-body');
var panelHeaderActive = $('.mobile-header-active');
if (userScreen.type === 'mobile') {
panelAccordion.find(panelBody).hide();
panelAccordion.find(panelHeader).addClass('mobile-header-active');
} else if (userScreen.type === 'desktop') {
panelAccordion.find(panelBody).show().removeClass('open');
panelHeader.removeClass('mobile-header-active');
}
panelHeaderActive.on('click', function(e) {
console.log('clicked');
if (panelBody.hasClass('open')) {
panelBody.removeClass('open').stop(true, true).slideUp().clearQueue();
//console.log('panel had class open');
e.stopPropagation();
return false;
} else {
panelBody.addClass('open').stop(true, true).slideDown().clearQueue();
//console.log('panel now has class open');
e.stopPropagation();
return false;
}
});
}
Try this code:
panelHeaderActive.unbind('click').on('click', function(e){
console.log('clicked');
if (panelBody.hasClass('open')) {
panelBody.removeClass('open').stop(true,true).slideUp().clearQueue();
//console.log('panel had class open');
e.stopPropagation();
return false;
} else {
panelBody.addClass('open').stop(true,true).slideDown().clearQueue();
//console.log('panel now has class open');
e.stopPropagation();
return false;
}
});
I am currently working with PhantomJS and CasperJS to scrape for links in a website. The site uses javascript to dynamically load results. The below snippet however is not getting me all the results the page contains. What I need is to scroll down to the bottom of the page, see if the spinner shows up (meaning there’s more content still to come), wait until the new content had loaded and then keep scrolling until no more new content was shown. Then store the links with class name .title in an array. Link to the webpage for scraping.
var casper = require('casper').create();
var urls = [];
function tryAndScroll(casper) {
casper.waitFor(function() {
this.page.scrollPosition = { top: this.page.scrollPosition["top"] + 4000, left: 0 };
return true;
}, function() {
var info = this.getElementInfo('.badge-post-grid-load-more');
if (info["visible"] == true) {
this.waitWhileVisible('.badge-post-grid-load-more', function () {
this.emit('results.loaded');
}, function () {
this.echo('next results not loaded');
}, 5000);
}
}, function() {
this.echo("Scrolling failed. Sorry.").exit();
}, 500);
}
casper.on('results.loaded', function () {
tryAndScroll(this);
});
casper.start('http://example.com/', function() {
this.waitUntilVisible('.title', function() {
tryAndScroll(this);
});
});
casper.then(function() {
casper.each(this.getElementsInfo('.title'), function(casper, element, j) {
var url = element["attributes"]["href"];
urls.push(url);
});
});
casper.run(function() {
this.echo(urls.length + ' links found:');
this.echo(urls.join('\n')).exit();
});
I've looked at the page. Your misconception is probably that you think the .badge-post-grid-load-more element vanishes as soon as the next elements are loaded. This is not the case. It doesn't change at all. You have to find another way to test whether new elements were put into the DOM.
You could for example retrieve the current number of elements and use waitFor to detect when the number changes.
function getNumberOfItems(casper) {
return casper.getElementsInfo(".listview .badge-grid-item").length;
}
function tryAndScroll(casper) {
casper.page.scrollPosition = { top: casper.page.scrollPosition["top"] + 4000, left: 0 };
var info = casper.getElementInfo('.badge-post-grid-load-more');
if (info.visible) {
var curItems = getNumberOfItems(casper);
casper.waitFor(function check(){
return curItems != getNumberOfItems(casper);
}, function then(){
tryAndScroll(this);
}, function onTimeout(){
this.echo("Timout reached");
}, 20000);
} else {
casper.echo("no more items");
}
}
I've also streamlined tryAndScroll a little. There were completely unnecessary functions: the first casper.waitFor wasn't waiting at all and because of that the onTimeout callback could never be invoked.
I need to write some code which is supposed to wait until a predefined div is no longer visible in order to process the next line. I plan on using jQuery( ":visible" ) for this, and was thinking I could have some type of while loop. Does anyone have a good suggestion on how to accomplish this task?
$( document ).ready(function() {
$(".scroller-right" ).mouseup(function( event ) {
alert('right');
pollVisibility();
});
});
function pollVisibility() {
if ($(".mstrWaitBox").attr("visibility")!== 'undefined') || $(".mstrWaitBox").attr("visibility") !== false) {
alert('inside else');
microstrategy.getViewerBone().commands.exec('refresh');
} else {
setTimeout(pollVisibility, 100);
}
}
$( document ).ready(function() {
$(".scroller-right" ).mouseup(function( event ) {
alert('right');
pollVisibility();
});
});
function pollVisibility() {
if (!$(".mstrWaitBox").is(":visible")) {
alert('inside if');
microstrategy.getViewerBone().commands.exec('refresh');
} else {
setTimeout(pollVisibility, 100);
}
}
div when not visible:
<div class="mstrWaitBox" id="divWaitBox" scriptclass="mstrDialogImpl" dg="1" ty="edt">
</div>
div when visible:
<div class="mstrWaitBox" id="divWaitBox" scriptclass="mstrDialogImpl" dg="1" ty="edt" visibility="visible">
</div>
You can use the setTimeout function to poll the display status of the div. This implementation checks to see if the div is invisible every 1/2 second, once the div is no longer visible, execute some code. In my example we show another div, but you could easily call a function or do whatever.
http://jsfiddle.net/vHmq6/1/
Script
$(function() {
setTimeout(function() {
$("#hideThis").hide();
}, 3000);
pollVisibility();
function pollVisibility() {
if (!$("#hideThis").is(":visible")) {
// call a function here, or do whatever now that the div is not visible
$("#thenShowThis").show();
} else {
setTimeout(pollVisibility, 500);
}
}
}
Html
<div id='hideThis' style="display:block">
The other thing happens when this is no longer visible in about 3s</div>
<div id='thenShowThis' style="display:none">Hi There</div>
If your code is running in a modern browser you could always use the MutationObserver object and fallback on polling with setInterval or setTimeout when it's not supported.
There seems to be a polyfill as well, however I have never tried it and it's the first time I have a look at the project.
FIDDLE
var div = document.getElementById('test'),
divDisplay = div.style.display,
observer = new MutationObserver(function () {
var currentDisplay = div.style.display;
if (divDisplay !== currentDisplay) {
console.log('new display is ' + (divDisplay = currentDisplay));
}
});
//observe changes
observer.observe(div, { attributes: true });
div.style.display = 'none';
setTimeout(function () {
div.style.display = 'block';
}, 500);
However an even better alternative in my opinion would be to add an interceptor to third-party function that's hiding the div, if possible.
E.g
var hideImportantElement = function () {
//hide logic
};
//intercept
hideImportantElement = (function (fn) {
return function () {
fn.apply(this, arguments);
console.log('element was hidden');
};
})(hideImportantElement);
I used this approach to wait for an element to disappear so I can execute the other functions after that.
Let's say doTheRestOfTheStuff(parameters) function should only be called after the element with ID the_Element_ID disappears, we can use,
var existCondition = setInterval(function() {
if ($('#the_Element_ID').length <= 0) {
console.log("Exists!");
clearInterval(existCondition);
doTheRestOfTheStuff(parameters);
}
}, 100); // check every 100ms
I'm using backbone and lazy loading views in a single page application as I need them. However, it appears doing this seems to be confusing the way backbone knows what my 'el' is when setting up events. Using the view definition below, I'm trying to get the code that fires on the submit button click or the input fields changing but right now, neither appear to work.
$(document).ready(function () {
editaddressView = Backbone.View.extend({
elementReady: false,
initialize: function () {
this.model = window.AccountData;
this.listenTo(this.model, "change", this.render);
if ($('#section-editaddress').length == 0) {
// Load UI
$('#ajax-sections').prepend('<div class="section" id="section-editaddress" style="display: none;"></div>');
}
this.el = $('#section-editaddress');
},
events: {
"click #edit-address-submit": "beginSaving",
"change input": "updateModel",
"change select": "updateModel"
},
render: function () {
$(this.el).find("[name=address]").val(this.model.get('owner_address1'));
// ...
return this;
},
switchTo: function () {
// Set menu state
$('.js-NavItem').removeClass('active');
$('#sN-li-personal').addClass('active');
if (this.options.isPreLoaded)
this.elementReady = true;
if (this.elementReady) {
this.renderSwitch();
}
else {
var model = this;
$('#section-editaddress').load('/ajax/ui/editaddress', function (response, status, xhr) {
if (status == "error") {
$('#page-progress-container').fadeOut('fast', function () {
$('#page-load-error').fadeIn('fast');
});
} else {
$('#section-editaddress').find('.routedLink').click(function (e) {
window.Router.navigate($(this).attr('href'), true);
return false;
});
model.delegateEvents();
model.elementReady = true;
model.render(); // First render
model.renderSwitch();
}
});
}
},
renderSwitch: function () {
// Abort showing loading progress if possible
if (window.firstRunComplete) {
clearTimeout(window.pageHide);
// Change screen - Fade progress if needed
$('#page-progress-container').fadeOut('fast', function () {
$('#page-load-error').fadeOut('fast');
var sections = $(".section");
var numSections = sections.length;
var i = 0;
sections.hide('drop', { easing: 'easeInCubic', direction: 'left' }, 350, function () {
i++;
if (i == numSections) {
$('#section-editaddress').show('drop', { easing: 'easeInExpo', direction: 'right' }, 350).removeClass('hidden');
$.scrollTo($('#contentRegion'), 250, { margin: true });
}
});
});
}
// Switch complete
window.changingPage = false;
},
updateModel: function () {
var changedItems = {};
if (this.model.get('csrf') != $(this.el).find("[name=csrf]").val())
changedItems.csrf = $(this.el).find("[name=csrf]").val();
// ...
},
beginSaving: function () {
alert('test');
}
});
});
Can anyone see what I've missed?
Whenever you need to change or modify the DOM element of a BackboneJS view manually, you should use setElement rather than setting the property directly. It moves all of the event handlers to the newly attached DOM element and also sets the $el property. In addition, the function also detaches any existing event handlers.
So, in the code you pasted, you'd just change it to:
this.setElement($('#section-editaddress'));
I have a query plugin I'm working on and I certain functions after ajax content has loaded. The problem is that let's say I re-initiate it 15 times, a click event will then fire 15 times when it's only clicked once.
Is there a way so it doesn't keep piling up? I'm calling addToCart onload and also from itemDetail after the ajax return
thanks!
function addToCart()
{
$(options.add_to_cart).click(function ()
{
event.preventDefault();
var id = $(this).attr('id');
store_item_id_val = id.replace('store-item-id-', '');
var quantity = $('.quantity-' + store_item_id_val);
if (quantity.val() < 1)
{
showError('Please enter a quantity of 1 or more.');
$(quantity).val(1);
return this;
}
$.post($(this).attr('href'),
{ store_item_id: store_item_id_val, quantity: quantity.val() },
function (data)
{
var result = jQuery.parseJSON(data);
renderCart(result);
});
});
return this;
}
function itemDetails()
{
$('.item-details').click(function ()
{
event.preventDefault();
var url = $(this).attr('href');
$.getJSON(url, function (result)
{
$('.modal-title').empty().html(result.title);
$('.modal-content').empty().html(result.html);
$('#modal').slideDown(100);
$('.ui-button').button();
addToCart();
$('.modal-close').click(function ()
{
event.preventDefault();
$('#modal').hide();
});
});
});
Based on the code you provided, I would probably say that you have some other code calling itemDetails(). Each time itemDetails() is called, it ADDS another event handler for click to your .item-details. You may want to instead do:
$(document).ready(function()
{
$('.item-details').click(function ()
{
event.preventDefault();
var url = $(this).attr('href');
$.getJSON(url, function (result)
{
$('.modal-title').empty().html(result.title);
$('.modal-content').empty().html(result.html);
$('#modal').slideDown(100);
$('.ui-button').button();
addToCart();
$('.modal-close').click(function ()
{
event.preventDefault();
$('#modal').hide();
});
});
});
});
This would put the event handler on your .item-details classed items, and only fire the events once. If you have dynamic .item-details added and removed you probably should use:
$('.item-details').live('click', function() ...