I'm generating a list of links in Javascript that should open in a shadowbox. Initially, on any given page load (Ctrl-F5 for example) the link opens in the window rather than in the shadowbox. If I can some how get it to open in the shadowbox, through luck or random happenstance, it will work until the page is reloaded again.
Here's the markup in the page:
<div id="portAgreementList">
<ul id="blAgreements"></ul>
</div>
Here's the javascript that makes the links in blAgreements:
function (data, status)
{
if (status == 'success')
{
if (data == '')
{
alert('URL returned no data.\r\n' +
'URL: ' + url);
return;
}
var jsonObj = StringToJSON(data); // StringToJSON function defined in /js/utilities.js
if (!jsonObj) { return; }
var items = '';
if ( jsonObj.items.length > 0 ) {
for (var xx = 0; xx < jsonObj.items.length; xx++) {
items += '<li><a rel="shadowbox;width=750;height=450;" href="' + jsonObj.items[xx].Url +'">' +
jsonObj.items[xx].Text +'</a></li>';
}
}
else {
items = '<li>You have no port agreements on file for this company.</li>';
}
$('#blAgreements').html(items);
Shadowbox.init();
}
}
I'm calling to Shadowbox.init(); after I've added creating the list items and it works sometimes. What I'd like to understand is why is it inconsistent and how do I make it more reliable.
Update #1: This looks like it might be a race condition. If I load the page, in IE at least, and wait before clicking it will eventually work. With IE8 I have to wait about 3 seconds. FF doesn't seem to follow that behavior.
Update #2: With FF, if I click on the link after page load, it opens the URL like any other web page. Hit the back button and click the link again and the URL opens in the shadowbox.
More digging around and I found a solution, though I'd still like to know why the above had the issues it did.
function (data, status)
{
if (status == 'success')
{
if (data == '')
{
alert('URL returned no data.\r\n' +
'URL: ' + url);
return;
}
var jsonObj = StringToJSON(data); // StringToJSON function defined in /js/utilities.js
if (!jsonObj) { return; }
var items = '';
if ( jsonObj.items.length > 0 ) {
for (var xx = 0; xx < jsonObj.items.length; xx++) {
var li = $('<li></li>').appendTo('#blAgreements');
var anchor = $('<a rel="shadowbox;width=750;height=450;" href="' + jsonObj.items[xx].Url +'">' +
jsonObj.items[xx].Text +'</a>').appendTo(li);
Shadowbox.setup($(anchor), null);
}
}
else {
items = '<li>You have no port agreements on file for this company.</li>';
}
}
}
The key difference is that I am building out the DOM as elements
var li = $('<li></li>').appendTo('#blAgreements');
var anchor = $('<a rel="shadowbox;width=750;height=450;" href="' + jsonObj.items[xx].Url +'">' + jsonObj.items[xx].Text +'</a>').appendTo(li);
And then calling:
Shadowbox.setup($(anchor), null);
On the anchor.
Related
Disclaimer: I know my code is pretty bad. I'm not very experienced with JavaScript yet.
So I built a gallery with filtering using JavaScript and the WP Rest API. I finally have it working, but after a few clicks between the gallery sub-categories, the page starts slowing down and eventually crashes. I imagine I'm doing something really inefficient that is killing the page, but I'm not sure what the main culprit is.
Besides the other obvious issues with how I've written the code, what might be causing it and is there a good way to test performance issues like this?
(Here's a link to a working version of this: http://victorysurfaces.x10host.com/gallery/)
Edit: Updated code with fix for extra HTML DOM nodes being added by lightbox code. Didn't fix page crashing issue, unfortunately.
Update: I've noticed that sometimes when I click on a sub-category, it makes more network requests, but sometimes it doesn't. I feel like this might be important.
Update 2: I think it might have something to do with the event listeners I'm adding. Since I'm adding the sub-categories dynamically, I have to add the event listeners after they have been loaded, but the event listeners from the previous run seem to remain, so the number of event listeners just grows and grows. I don't know what to do about that.
<div class="gallery">
<div class="medium-wrapper">
<div class="gallery__filters text-center">
<div class="gallery__main-filters">
<button class="category-filter main-category active" data-category="residential">Residential</button>
<span>|</span>
<button class="category-filter main-category" data-category="commercial">Commercial</button>
</div>
<div class="gallery__category-filters"></div>
</div>
<div class="gallery__images"></div>
</div>
</div>
<script>
/* I'm so sorry for this monstrosity. This was way more complicated than I thought and in the end I just wanted it to work */
jQuery(document).ready(function($) {
$('.main-category').on('click', function() {
$('.main-category').removeClass('active');
$(this).addClass('active');
});
initLightbox();
});
jQuery( function( $ ) {
$.getJSON("/wp-json/wp/v2/gallery-categories", function( data ) {
var currentMainCategory = $('.main-category.active').data('category');
getSubCategories();
var currentSubCategory = '';
document.querySelectorAll('.main-category').forEach( function(trigger) {
trigger.addEventListener('click', function() {
resetCategories($(this).data('category')); }, false);
});
function getSubCategories() {
var categoriesArray = [];
var subCategories = data.map(function(category) {
if( category.acf.gallery_section.trim().toLowerCase() === currentMainCategory) {
var setCategory = "<button class='category-filter sub-category' data-category='" + category.acf.category_title + "'>" + category.acf.category_title + "</button>";
categoriesArray.push(setCategory);
}
});
$('.gallery__category-filters').html(categoriesArray);
getPhotos();
}
function resetCategories(mainCategoryTitle) {
currentMainCategory = '';
currentSubCategory = '';
$('.sub-category').removeClass('active');
$('.gallery__category-filters').empty();
currentMainCategory = mainCategoryTitle;
getSubCategories();
}
function setSubCategory() {
currentSubCategory = document.querySelector('.sub-category.active').dataset.category;
getPhotos();
}
var galleryPhotos;
function getPhotos(photos) {
$('.gallery__images').empty();
var mainCategory = currentMainCategory.trim().toLowerCase();
if( (currentSubCategory !== undefined) && (currentSubCategory !== '' ) ) {
var subCategory = currentSubCategory.trim().toLowerCase();
}
galleryPhotos = data.map(function(category) {
if( category.acf.gallery_section.toLowerCase() === mainCategory ) {
if( subCategory !== '' && subCategory !== undefined) {
var categoryTitle = category.acf.category_title.toLowerCase().trim();
if( categoryTitle === subCategory ) {
var galleryCategory = category.acf.gallery_items;
var categoryPhotos = galleryCategory.map(function(photo) {
var galleryPhoto = "<div class='gallery__item'><a class='lightbox-link' href=''><img class='full-width lightbox-target' src='" + photo.gallery_item_image.sizes.flexible + "' alt='" + photo.gallery_item_image.alt + "'></a></div>";
return galleryPhoto;
});
$('.gallery__images').append(categoryPhotos);
}
} else {
var galleryCategory = category.acf.gallery_items;
var categoryPhotos = galleryCategory.map(function(photo) {
var galleryPhoto = "<div class='gallery__item'><a class='lightbox-link' href=''><img class='full-width lightbox-target' src='" + photo.gallery_item_image.sizes.flexible + "' alt='" + photo.gallery_item_image.alt + "'></a></div>";
return galleryPhoto;
});
$('.gallery__images').append(categoryPhotos);
}
}
});
$('.sub-category').on('click', function() {
$('.sub-category').removeClass('active');
$(this).addClass('active');
setSubCategory();
});
checkOrientation();
handleLightboxUpdate();
}
});
});
function checkOrientation() {
document.querySelectorAll('.lightbox-target').forEach(function(item) {
var image = new Image();
image.src = item.src;
image.onload = function() {
if(image.naturalHeight >= image.naturalWidth) {
item.classList.add('portrait');
}
}
});
}
function initLightbox() {
var $overlay = jQuery('<div id="overlay"></div>');
var $container = jQuery('<div class="lightbox">×</div>');
var $image;
var $imageClone;
jQuery('body').append($overlay);
$overlay.click(function(){
$overlay.hide();
});
$overlay.append($container);
}
function handleLightboxUpdate() {
document.querySelectorAll('.lightbox-link').forEach( function(trigger) {
trigger.addEventListener('click', function() {
event.preventDefault();
jQuery('.lightbox-image').remove();
$image = jQuery(this).find('.lightbox-target');
$imageClone = $image.clone();
if($imageClone.hasClass('portrait')) {
$imageClone.addClass('resize-lightbox');
}
jQuery('#overlay').show();
//add image to overlay
$imageClone.addClass('lightbox-image').appendTo('#overlay .lightbox');
});
});
}
</script>
I'm not looking much into code, but I can tell you what's going on here. Page slows down with each 'subcategory' click, because you add more and more HTML nodes into the page until it's just too much. To be specific you add <div id="overlay">...</div> exponentially with every click.
is there a good way to test performance issues like this?
I suggest opening dev tools and see what's happening there. If adding more html wasn't the case, I'd look into potential problems with recursion or creating too many objects.
I figured it out! My setSubCategory() function was calling getPhotos() which was calling setSubCategory(), and so on and so forth.
Turns out it was a simple never-ending loop. Face-palm.
I am using Jquery Datatable in my code. I am loading the data via an ajax request. For each row created in my table there is one button called delete. The on-click event of this button reads an attribute called "r_id" and then applies a soft-deletion in my database. My code is like the following:
$(document).ready(function () {
var parity = 3;
var tr = "";
$.ajax({
url: "https://" + window.myLocalVar + "/api/rewards/findAllRewardsByStatusID/" + parity,
type: "GET",
dataType: 'json',
contentType: 'application/json',
success: function (rewards) {
if (rewards.length > 0) {
for (var i = 0; i < rewards.length; i++) {
var reward = rewards[i].Reward;
var reward_price = "$ " + rewards[i].Price;
var r_id = rewards[i].RewardID;
tr += '<tr r_id="' + r_id + '">\
<td>' + reward + '</td>\
<td>' + reward_price + '</td>\
<td>\
<button r_id="' + r_id + '" type="button" class="btn btn-danger delete">delete</button>\
</td>\
</tr>';
if (i % 10 == 0 || i == rewards.length - 1) {
$("#submitted_rewards > tbody").append(tr);
tr = "";
}
}
}
$('#submitted_rewards').on('init.dt', function () {
// click event for each tr
$("#submitted_rewards tbody tr td:not(:last-child)").on("click", function () {
var r_id = $(this).parent().attr("r_id");
window.location.href = "./reward-info.aspx?id=" + r_id;
});
$(".delete").click(function () {
var r = $(this).attr("r_id");
$("tr[r_id=" + r + "]").fadeOut();
});
}).DataTable({
"scrollX": true,
stateSave: true
});
},
error: function (err) {
if (err) {
console.log(err);
}
}
});
});
This code loads the data in my Jquery Datatable called "submitted_rewards" correctly. The page size of the table is set to '10' as a default (10 rows per page). Now, I tried to go to the second page and click on the row to redirect to another page OR if I tried to click on the delete button to delete a row and it did not work! Those events work only for the page that a user lands on the first time. I am saving the state of the table that mean if I switched from page 1 to page 2 then refreshed that page. The events of each row in page 2 ONLY will work.
I tried to use the event initComplete but it did not work. Any idea how to solve this problem.
Thanks!
If you attach handlers to DOM elements and those DOM elements are destroyed your handlers will be of no effect anymore and the related code will be garbage collected at some point.
When you change pages by doing window.location.href = ..., you are effectively replacing your DOM elements (and script) with a new set from the page that is replacing it. Consequently, your handlers will no longer apply.
You can address this in a couple ways.
1) You will need to add script to each page you are loading and have it execute when the DOM is ready, applying handlers to the DOM elements on that page.
2) Do another ajax request to get new data and replace certain elements on your page. Use jQuery's .delegate() to attach handlers automatically to certain elements even as you add them later.
The reason why it is not working because I am appending every 10 rows to the table.. I appended all the rows of the table at one shot and then have the clicking events then it is going to work.
If I want to keep loading every 10 rows. then I have to move the events inside the if statement like the following:
if (i % 10 == 0 || i == rewards.length - 1) {
$("#submitted_rewards > tbody").append(tr);
tr = "";
$("#submitted_rewards tbody tr td:not(:last-child)").on("click", function () {
var r_id = $(this).parent().attr("r_id");
window.location.href = "./reward-info.aspx?id=" + r_id;
});
$(".delete").click(function () {
var r = $(this).attr("r_id");
$("tr[r_id=" + r + "]").fadeOut();
});
}
I'm trying the print button and others when rendering a file using pdf.js. I tried using CSS and it works for Chrome but not Internet Explorer. For Internet Explorer I used javascript. JS works when I load one file but subsequent files are still showing the buttons.
viewer.html
<script type="text/javascript">
$(function () {
$('#print').hide();
$('#viewBookmark').hide();
$('#openFile').hide();
});
</script>
viewer.css
button#openFile, button#print, a#viewBookmark {
display: none;
}
default.cshtml
$('.file').on('click touchend', function (e) {
e.preventDefault();
if ($(this).hasClass('disabled'))
return;
var path = $(this).data('path').replace("\\\\", "http://").replace("#pdfFolder", "Uploads");
var name = $(this).data('name');
var lastname = $(this).data('lastname');
name = name.length > 8 ?
name.substring(0, 5) + '...' :
name.substring(0, 8);
lastname = lastname.length > 8 ?
lastname.substring(0, 5) + '...' :
lastname.substring(0, 8);
var tabCount = $('#tabs li').size();
var uuid = guid();
$(this).attr('data-position', uuid);
$('#content').css('display', 'block');
if (tabCount === 5) {
$('#maximumTabsModal').modal('show');
} else {
$(this).addClass('disabled')
$('<li role="presentation" data-position="' + uuid + '">' + name + '<span class="close"></span><br/>' + lastname + '</li>').appendTo('#tabs');
$('<div class="tab-pane" id="panel' + uuid + '"><div id="pdf' + uuid + '" class="pdf"></div></div>').appendTo('.tab-content');
$('#tabs a:last').tab('show');
var options = {
//pdfOpenParams: {
// view: "FitV"
//},
forcePDFJS: true,
PDFJS_URL: "pdfjs/web/viewer.html"
};
var pdf = PDFObject.embed(path, '#pdf' + uuid, options);
$('#print').hide();
$('#viewBookmark').hide();
$('#openFile').hide();
$('#exd-logo').hide();
}
});
Unfortunately, PDFObject is not capable of hiding the print button, it only provides a mechanism for specifying PDF Open Parameters, which do not include the ability to hide the print button.
I'm no expert on PDF.js, but since it's all JS-based, and hosted on your domain (i.e. you have full script access), you should be able to find a way to hack it to remove the print button. Good luck!
I was able to get the buttons to hide by handling the pagerendered event that PDF.js provides.
viewer.html
<script type="text/javascript">
$(function () {
document.addEventListener("pagerendered", function (e) {
$('#print').hide();
$('#viewBookmark').hide();
$('#openFile').hide();
});
});
</script>
I am shooting in the dark here because I do not know if the PDF viewer loads in an <iframe> or not, but the following code will scan over the page indefinitely and suppress the print button from showing if it finds it.
var $printSearch = setInterval(function() {
if ($('#print').length > 0 || $('#print').is(':visible')) {
hidePrint();
} else {
//doNothing
console.log('Searching...');
}
}, 150);
function hidePrint() {
$('div#print').css('display', 'none');
}
If it does load in an iframe we could use the .contents() and .filter() jQuery methods to target those elusive buttons.
Have you tried using print media queries ?
I'm using this jQuery script to show search results. Everything works fine, but when search results have more than one page and I'm browsing pages via paging then every page loading is gradually getting slower. Usually first cca 10 pages loads I get quickly, but next are getting avoiding loading delay. Whole website get frozen for a little while (also loader image), but browser is not yet. What should be the problem?
function editResults(def) {
$('.searchResults').html('<p class=\'loader\'><img src=\'images/loader.gif\' /></p>');
var url = def;
var url = url + "&categories=";
// Parse Categories
$('input[name=chCat[]]').each(function() {
if (this.checked == true) {
url = url + this.value + ",";
}
});
url = url + "&sizes=";
// Parse Sizes
$('input[name=chSize[]]').each(function() {
if (this.checked == true) {
url = url + this.value + ",";
}
});
url = url + "&prices=";
// Parse Prices
$('input[name=chPrice[]]').each(function() {
if (this.checked == true) {
url = url + this.value + ",";
}
});
$('.searchResults').load('results.php'+url);
$('.pageLinks').live("click", function() {
var page = this.title;
editResults("?page="+page);
});
}
$(document).ready(function(){
editResults("?page=1");
// Check All Categories
$('input[name=chCat[0]]').click(function() {
check_status = $('input[name=chCat[0]]').attr("checked");
$('input[name=chCat[]]').each(function() {
this.checked = check_status;
});
});
// Check All Sizes
$('input[name=chSize[0]]').click(function() {
check_status = $('input[name=chSize[0]]').attr("checked");
$('input[name=chSize[]]').each(function() {
this.checked = check_status;
});
});
// Edit Results
$('.checkbox').change(function() {
editResults("?page=1");
});
// Change Type
$(".sort").change(function() {
editResults("?page=1&sort="+$(this).val());
});
});
$('.pageLinks').live("click", function() {
var page = this.title;
editResults("?page="+page);
});
just a wild guess but... wouldn't this piece of code add a new event handler to the click event instead reaplacing the old one with a new one? causing the click to call all the once registered handlers.
you should make the event binding just once
var global_var = '1';
function editResults(def) {
// all your code
global_var = 2; // what ever page goes next
};
$(document).ready(function() {
// all your code ...
$('.pageLinks').live("click", function() {
var page = global_var;
editResults("?page="+page);
});
});
I'm working on a ajax/jquery quiz for my company and it runs without problem in Firefox, Chrome, and even Android. When running with IE8, though, it locks up to the effect of having to run the task manager to end the IE process. I have tried different browsers in different operating systems, and the only one that's giving me any trouble is specifically IE in Windows. Other browsers in Windows aren't having any problems. I have also tried on a few different computers.
The Ajax request itself is fine; I think it's the hide/show effects that are causing the issue, after a user chooses an answer. The other answers are supposed to go away, and some response text based on the user's answer appears. Pretty standard. In IE the response text appears, but the other answers don't go away, and then IE locks hard.
I tried using Firebug Lite, but there's no way to actually get any feedback from it since IE freezes to such a great extent.
The javascript is below, and the link to the live example IS HERE.
<script type='text/javascript'>
$(document).ready(function() {
var score=0;
$('#getStarted').click(function() {
$('#instructions').hide('slow');
$('#getStarted').hide('fast');
$.ajax({
url: "scripts/quizProgress.php",
data: "questionNumber=1",
success: function(xml) {
var question = $(xml).find("text:[type=question]").text();
$('#questionDisplay').append(question);
$('#questionDisplay').show('slow');
$(xml).find("answer").each(function() {
var id = $(this).attr('id');
var answerText = $(this).find("text").text();
var tbi = "<p correct='";
if ($(this).find("correct").text() == '0') {
tbi += "no";
}
else { tbi += "yes"; }
tbi += "' id='" + id + "'>" + answerText + "</p>";
$('#answerDisplay').append(tbi);
var responseText = $(this).find('response').text();
var responseInsert = "<p id='re"+id+"' class='hideResponse'>"+responseText+"</p>";
$('#responseDisplay').append(responseInsert);
});
$('#answerDisplay').show('slow');
}
});
});
$('#answerDisplay').hover(function() {
$(this).find('p').hover(function() {
$(this).addClass("answerHover");
}, function() {
$(this).removeClass('answerHover');
});
$(this).find('p').click(function() {
if ($(this).attr('correct')=='yes') {
score ++;
}
var answer = $(this).attr('id');
var allAnswers = $('#answerDisplay').find('p');
$(allAnswers).each(function() {
if (answer != $(this).attr('id')) {
$(this).hide('slow');
}
else {
$(this).addClass('selectedAnswer');
}
var responseID = "re"+answer;
$('#responseDisplay').find('p').each(function() {
if ($(this).attr('id')==responseID) {
$(this).removeClass('hideResponse');
$(this).show('slow');
$(this).addClass('showResponse');
}
});
});
});
});
});
Bear in mind, this is only one question, and does not have full funciontality yet. I'm hesitant to continue while it's causing this much of an issue in IE. A lot of our clients are...let's just say not the computer savvy demographic, and IE comprises a lot of their browsers.
Also: this is my first jQuery application, so it may be that I've done something absolutely horrible.
Thanks for any help.
I've cleaned up your code a little bit and changed the showing and hiding of the questions and answers. Hope this is of use to you.
var score = 0;
var $answerDisplay = $('#answerDisplay');
var $responseDisplay = $('#responseDisplay');
var $questionDisplay = $('#questionDisplay');
var $instructions = $('#instructions');
$('#getStarted').click(function() {
var $this = $(this);
$this.hide('fast');
$instructions.hide('slow');
$.ajax({
url: "scripts/quizProgress.php",
data: "questionNumber=1",
success: function(xml) {
var question = $(xml).find("text:[type=question]").text();
$questionDisplay.append(question);
$questionDisplay.show('slow');
$(xml).find("answer").each(function() {
var $this = $(this);
var id = $this.attr('id');
var answerText = $this.find("text").text();
var tbi = "<p correct='";
if ($this.find("correct").text() == '0') {
tbi += "no";
} else {
tbi += "yes";
}
tbi += "' id='" + id + "'>" + answerText + "</p>";
$answerDisplay.append(tbi);
var responseText = $this.find('response').text();
var responseInsert = "<p id='re" + id + "' class='hideResponse'>" + responseText + "</p>";
$responseDisplay.append(responseInsert);
});
$answerDisplay.show('slow');
}
});
});
$answerDisplay.find('p').hover(function() {
$(this).addClass("answerHover");
}, function() {
$(this).removeClass('answerHover');
}).click(function() {
var $p = $(this);
$p.addClass('selectedAnswer');
if ($p.attr('correct') == 'yes') {
score++;
}
$p.parent().find(':not(.selectedAnswer)').hide('slow');
$responseDisplay.find('p#re' + $p.attr('id')).removeClass('hideResponse').show('slow').addClass('showResponse');
});
I think it might be to do with:
$(this).hide("slow");
Your triggering a separate animation for each of your non-selected answers.
I'm not certain, but maybe if you triggered a single .hide() for all of the things you wanted to hide, then it may run faster.
Perhaps you could re-factor your code so that it marks the selected answer first and hides everything else.
Maybe, replace this:
var answer = $(this).attr('id');
var allAnswers = $('#answerDisplay').find('p');
$(allAnswers).each(function() {
if (answer != $(this).attr('id')) {
$(this).hide('slow');
} else {
$(this).addClass('selectedAnswer');
}
with this:
$(this).addClass('selectedAnswer');
$('#answerDisplay').find('p').not('.selectedAnswer').hide('slow');
But to be honest I don't know exactly how the animate works internally, so I'm not entirely sure if this will be faster.