Why is my JavaScript code executing only on second click? - javascript

I have been toying with this forever now. I can't seem to find out why the lightbox is only executing after I've clicked it twice. The first click, it just pops the image up in a new tab.
I've already tried using e.preventDefault (which did nothing except keep the image from popping up in a new tab after the first click).
$(document).ready(function() {
$('a[class^="fomod-"]').on('click', function() {
var fomodClass = $(this).attr('class');
var fomodGallery = $('.' + fomodClass).simpleLightbox({
loop: false
});
});
});
What I'm ultimate trying to do is watch the DOM for any clicks on links that have the "fomod-*" class and, if clicked, get the exact class of the element that was clicked. With that, the lightbox pops up and only shows the other images with that same exact class as a gallery.

The Issue
.simpleLightbox() initializes the lightbox. That means your first click adds simpleLightbox to your page, allowing all following clicks to actually trigger it.
You need to do the initialization when the page loads. Now you could do something like...
$('a[class^="fomod-"]').each(function() { ... })
But that has a few drawbacks.
It won't find elements where fomod isn't the first class, ie class="other-class fomod-one".
If you had class="fomod-one other-class", your internal selector won't work because the concatenation would result in $(".fomod-one other-class").
You'd be continuously re-initializing simpleLightbox on the same elements repeatedly, which I'm not sure if the plugin is setup to handle or not.
Solution 1 - Data Attributes
data attributes allow us a bit more flexibility on how we select our elements. Also, fetching data attributes in JavaScript is supported in both vanilla JS (using dataset) and jQuery (using .data()).
<a data-fomod="Gallery1"></a>
<a data-fomod="Gallery1"></a>
<a data-fomod="Gallery2"></a>
$(document).ready(function() {
const galleries = $("[data-fomod]")
//Get array from elements
.get()
//Attach lightbox to each unique gallery on the page
.reduce((output, {dataset}) => {
let galleryName = dataset.fomod;
return output[galleryName]
? output
: {...output, [galleryName]: $(`[data-fomod="${galleryName}"]`).simpleLightbox({loop: false})}
}, {});
});
This approach gives us three things over the initial approach:
It doesn't restrict the use of classes.
It attaches simpleLightbox to each gallery just once.
It stores the galleries individually by name in the galleries object. For example, if you wanted to tell Gallery1 to go to the next slide, you could do galleries["Gallery1"].next().
Solution 2 - Using Classes More Appropriately
As you'd mentioned in the comments, your environment doesn't provide great support for data- attributes. Instead, we can use classes, we just have to be a bit more considerate. I'll use two classes here - one to flag this as a lightbox element ("fomod"), and another to associate the gallery ("fomod-GalleryName").
You may be wondering why that "flag" fomod class is necessary. Why not just use fomod- and use the ^= selector? As mentioned above, what if fomod- is the second class behind my-other-class? The selector won't find the element.
(There are ways around this but that's opening a can of worms.)
This approach, although just slightly more involved, achieves all the same benefits that the data attribute solution does.
<a class="fomod fomod-Gallery1"></a>
<a class="fomod fomod-Gallery1"></a>
<a class="fomod fomod-Gallery2"></a>
Without comments
$(document).ready(function() {
const galleries = $(".fomod")
.get()
.reduce((output, elem) => {
let galleryName = [...elem.classList].find(c => c.startsWith('fomod-'));
if (!galleryName) return;
galleryName = galleryName.split("-")[1];
return output[galleryName]
? output
: { ...output, [galleryName]: $(`.fomod-${galleryName}`).simpleLightbox({loop: false})}
}, {});
});
With comments
$(document).ready(function() {
const galleries = $(".fomod")
//Get array from elements
.get()
//For each fomod element...
.reduce((output, elem) => {
//Get the classes on this element, and find one that starts with "fomod-"
let galleryName = [...elem.classList].find(c => c.startsWith('fomod-'));
//Didn't find one. Skip this element
if (!galleryName) return;
//Remove the "fomod-" part so we're left with just the gallery name
galleryName = galleryName.split("-")[1];
//Have we already initialized this gallery?
return output[galleryName]
//Yup. Do nothing.
? output
//Nope. Initialize it now.
: { ...output, [galleryName]: $(`.fomod-${galleryName}`).simpleLightbox({loop: false})}
}, {});
});

Related

An AJAX Search Plugin is removing my Event Listeners from an 'Accordion' style list

I'm using Search & Filter pro WP plugin for the ease of a client using it.
I've created a results page and filter on a demo site (for testing) that works fine but I know the categories will get large on the real site. So I turned the plugins' filters into an Accordion style list.
It works fine until certain searches reload all those filter results with AJAX and they remove my event listeners (which are sitting on elements for the moment, I know it's not ideal but for now I just want to see if it could work).
I imagine because my script has already been parsed when the DOM loaded, the AJAX from the plugin is just redefining those elements and they are then missing the Event Listeners or something.
Any help would be appreciated.
Here's my script:
<?php
add_action( 'wp_footer', function () { ?>
<script>
const clicker = document.querySelectorAll('#search-filter-form-4346 > ul > li > h4');
// looping through the <h4> elements and adding an event listener onto each, the class toggle just adds an animation to a pseudo-element spinner
for (let i = 0; i < clicker.length; i++) {
clicker[i].addEventListener("click", function() {
this.classList.toggle("open-filter-dropdown");
console.log('EL was created');
// declaring the <ul> as a variable
const openFilterPanel = this.nextElementSibling;
// animating the <ul> elements max-height
if (openFilterPanel.style.maxHeight) {
openFilterPanel.style.maxHeight = null;
} else {
openFilterPanel.style.maxHeight = openFilterPanel.scrollHeight + "px";
}
console.log('openFilterPanel style is changed');
});
}
</script>
<?php } );
I'm pretty new to javascript, I get the basic concepts but this kind of an interference is above my head. I tried refactoring my code, forcing the page to refresh and other such measures. None of these work very well. I also thought I could use a 'loadend' event on the document to re-add my ELs but that didn't work either.
Hoping there is a workaround here, otherwise I might have to find another solution or plugin.
Thanks in advance!

Button onClick Event Listener not working in Electron app

Background:
I am using ElectronJS to make a game and I have a class for a shop which I named Shop.
It has a method that I call 'createItemElement' which takes an object named 'item' and creates an li element to be later added to a ul element. The code for this can be found below.
class Shop {
// ...
createItemElement(item) {
// Item Container
const li = document.createElement("li");
// Title
const title = document.createElement("h3");
title.textContent = item.title;
// Info
const info = document.createElement("p");
info.textContent = `Type: ${item.type}`;
// Add to Cart
const addButton = document.createElement("button");
addButton.textContent = "Add to Cart";
addButton.onclick = () => console.log("Adding to cart!");
li.appendChild(title);
li.appendChild(info);
li.appendChild(addButton);
return li;
}
// ...
}
Problem:
Interestingly, all of the HTML is correctly rendered and everything looks as it should, but the 'onclick' event just plain does not work.
Since all of these elements are in fact being rendered, it should be safe to assume that the code is indeed being run in the renderer process.
For some reason, however, the event is not being carried over to the app.
When I click the button in the app, nothing happens.
I checked the devTools and looked at the elements individually.
As expected, all of the elements were listed out correctly in element inspector, but when I inspected that 'addButton' button, there was no 'onclick' property to be seen.
Also, there are no errors in the console whatsoever, so that's nice (sarcasm).
And I am aware that there are many seemingly similar questions asked on StackOverflow, but none of the answers that I found have helped or applied to my situation.
It is extremely confusing as to why the elements are being rendered perfectly but the event listener is not working.
The full project can be found here and the file being excerpted below can be found here.
I looked at your shop.js page, and you incorrectly camel-case the onclick event in one place, but not in another. Using the camel-cased version of onclick will yield no error and do nothing.
Won't work:
empty_cart_button.onClick = (evt) => this.emptyCart();
Works:
addButton.onclick = () => this.addToCart(item);
As I can see from you're code, you're using el.innerHTML in the shop instance .getHTML() method. this will return the inner HTML as a raw string without any event listeners, this is why you see the rendered content as expected but the click listener doesn't work.
In the sketch.js file, the toggleShop function should use appendChild so instead of:
document.querySelector("#shop-container").innerHTML = shop.getHTML();
you should do:
document.querySelector("#shop-container").appendChild(shop.getElement())
and in the Shop class add the getElement method:
getElement() {
return this.el;
}
Be sure to toggle the shop and remove the #shop-container innerHTML when you want to toggle it off.
Also, as #Andy Hoffman answered, you should set the onclick property and not the onClick.

jQuery slideToggle for multiple divs

What I am trying to do is have four links that each will display and hide a certain div when clicked. I am using slideToggle and I was able to get it to work with really sloppy and repetitive code. A friend of mine gave me a script he used and I tried it out and finally was able to get something to happen. However, all it does is hide the div and wont redisplay. Also it hides all the divs instead of just the specific one. Here is a jsfiddle I made. Hopefully you guys can understand what I am trying to do and help! Thanks alot.
Here is the script I'm using.
$(document).ready(function () {
$(".click_me").on('click', function () {
var $faq = $(this).next(".hide_div");
$faq.slideToggle();
$(".hide_div").not($faq).slideUp();
});
});
http://jsfiddle.net/uo15brz1/
Here's a link to a fiddle. http://jsfiddle.net/uo15brz1/7/
I changed your markup a little, adding id attributes to your divs. The jquery, gets the name attribute from the link that's clicked, adds a # to the front, hides the visible div, then toggles the respective div. I also added e.preventDefault to stop the browser from navigating due to the hash change. As an aside, javascript don't require the $ prefix.
$(document).ready(function () {
$(".click_me").on('click', function (e) {
e.preventDefault();
var name = $(this).attr('name');
var target = $("#" + name);
if(target.is(':visible')){
return false; //ignore the click if div is visible
}
target.insertBefore('.hide_div:eq(0)'); //put this item above other .hide_div elments, makes the animation prettier imo
$('.hide_div').slideUp(); //hide all divs on link click
target.slideDown(); // show the clicked one
});
});
Welcome to Stack Overflow!
Here's a fiddle:
http://jsfiddle.net/uo15brz1/2/
Basically, you need a way to point to the relevant content <div> based on the link that's clicked. It would be tricky to do that in a robust way with your current markup, so I've edited it. The examples in the jquery documentation are pretty good. Spend some time studying them, they are a great way to start out.

Adding a class while looping to an array

I have a list composed by some divs, all of them have a info link with the class .lnkInfo. When clicked it should trigger a function that adds the class show to another div (like some sort of PopUp) so it is visible and when clicked again it should hide it.
I am quite certain this must be a very basic thing and most likely I will get some scoffs...but hey! Once I have this down that's one thing less I will ever have to ask again. Anyway I am starting to leave the safety of html and css to start learning JS, PHP and the like and I came to a bit of a problem.
When testing it before it was working, that was until I added another div, it only worked with the first one, reading a bit and with some suggestion I realized it must be something related to a array, the problem is that I am not quite certain of the syntax for accomplishing what I am visualizing.
Any help would be deeply appreciated.
This is my JS code and below I will attack a Fiddle of how the html looks just in case.
var infoLab = document.getElementsByClassName('lnkInfo'),
closeInfo = document.getElementById('btnCerrar');
infoLab.addEventListener('click', function () {
for (var i = 0 ; i < infoLab.length; i++) {
var links = infoLab[i];
displayPopUp('popUpCorrecto1', 'infoLab[i]');
};
});
function displayPopUp(pIdDiv, infoLab[i]){
var display = document.getElementById(pIdDiv),
for (var i = 0 ; i < infoLab.length; i++) {
infoLab[i]
newClass ='';
newClass = display.className.replace('hide','');
display.className = newClass + ' show';
};
}
JSFiddle.
Thanks a lot in advance and sorry for any facepalms!
EDIT:
This a jQuery function (in another file) that I need to call using the link because it fetches the data that will be inside the div, thus why I wanted to just add a hide/show.
$(".lnkInfo").click(function() {
var id = $('#txtId').val();
var request = $.ajax({
url: "includes/functionsLabs.php",
type: "post",
data: {
'call': 'displayInfoLabs',
'pId':id},
dataType: 'html',
success: function(response){
$('#info').html(response);
}
});
});
EDIT 2:
To a future reader of this question,
If you managed to find this answer throughout space and time, know that this is how the solution ended being, may it help you in your quest to stop being a noob.
SOLUTION
Here is a rudimentary working example of how to make a popup appear after clicking on a specific element given your current code. Note that I added an id to your link element.
// Select the element.
var infoLink1 = document.getElementById('infoLink1');
// Add an event listener to that element.
infoLink1.addEventListener('click', function () {
displayPopUp('popUpCorrecto1');
});
// Display a the popup by removing it's default "hide"
// class and adding a "show" class.
function displayPopUp(pIdDiv) {
var display = document.getElementById(pIdDiv);
var newClass = display.className.replace('hide', '');
display.className = newClass + ' show';
}
Fiddle.
There are various ways to generalize this to work for all links/popups. You could add a data-link-number=1, data-link-number=2, etc to each link element (more on data-). Select an element containing all of your links. Bind to that element an event listener that, when clicked, detects the link element that was clicked (see event delegation / "bubbling"). You can determine which link was clicked based on the value of your data-link-number attribute. Then show the appropriate popup.
You may also want to use jQuery for this. Changing an element's class by setting it's className property makes for brittle DOM code. There is an addClass and a removeClass method available. jQuery's events also work cross-browser; element.addEventListener() will not work in IE8 which still has a significant market share.

How can these generic classes be distinct between different divs?

I'm creating a notification system for a game to work similar to how notifications might work in a phone.
The notifications are all created initially, hidden, and later the game is supposed to "activate" certain ones from in-game triggers.
I'm running into problems when trying to keep the notifications separate in terms of their classes. Each notification starts off as a small rectangular box with only the title visible. Upon clicking, the notification expands and the description becomes visible.
Right now, clicking a notification does expand that notification and display its notification, but any other notifications also show their descriptions as well.
Example code:
var NotificationItems = new Array();
scope.registerNotification = function(title, description)
{
//add it to the array
NotificationItems.push(new scope.Application(title, description));
var $NotificationContainer = $("#NotificationContainer");
$NotificationContainer.append('<div class="Notification" title="'+title+'"></div>');
var $thisNotification = $NotificationContainer.children('.Notification[title='+title+']');
$thisNotification.append('<div class="NotificationTitle">'+title+'</div>');
$thisNotification.append('<div class="NotificationDescription">'+description+'</div>');
$(".NotificationDescription").hide();
$thisNotification.click(function()
{
$(this).toggleClass('expanded');
$('.NotificationDescription').slideToggle('slow');
});
}
How can I get the .NotificationDescription to be uniquely recognized for each notification?
You could try the .children() method: jQuery docs for children method
$thisNotification.click(function()
{
$(this).toggleClass('expanded').children('.NotificationDescription').slideToggle('slow');
});
Just find the right one for the clicked element:
$thisNotification.click(function()
{
$(this).toggleClass('expanded');
$(this).find('.NotificationDescription').slideToggle('slow');
});
You can chain the calls if you like:
$thisNotification.click(function()
{
$(this).toggleClass('expanded').find('.NotificationDescription').slideToggle('slow');
});
You might want to try out event delegations.
$('#NotificationContainer > div.Notification').live('click',function()
{
$(this).toggleClass('expanded').find('div.NotificationDescription').slideToggle('slow');
});
This way you only need to attach the event once (on init), and a single event handles all the notifications.
You also should add all your html at one time:
var $NotificationContainer = $("#NotificationContainer");
var $Notification = $('<div class="Notification" title="'+title+'"></div>');
$Notification.append('<div class="NotificationTitle">'+title+'</div>');
$Notification.append('<div class="NotificationDescription">'+description+'</div>');
$NotificationContainer.append($Notification);
notice the subtle difference, we're building the elements in jquery rather than the dom, and append them all at once.

Categories