I'm using a Bootstrap theme and I wanted the image gallery on the theme's image display page to load via AJAX.
Photos come as JSON with AJAX but I couldn't get them to show on the page.
The gallery related part of this theme from the original JS file:
var productGallery = function () {
var gallery = document.querySelectorAll('.product-gallery');
if (gallery.length) {
var _loop8 = function _loop8(i) {
var thumbnails = gallery[i].querySelectorAll('.product-gallery-thumblist-item'),
previews = gallery[i].querySelectorAll('.product-gallery-preview-item');
for (var n = 0; n < thumbnails.length; n++) {
thumbnails[n].addEventListener('click', changePreview);
} // Changer preview function
function changePreview(e) {
e.preventDefault();
for (var _i3 = 0; _i3 < thumbnails.length; _i3++) {
previews[_i3].classList.remove('active');
thumbnails[_i3].classList.remove('active');
}
this.classList.add('active');
gallery[i].querySelector(this.getAttribute('href')).classList.add('active');
}
};
for (var i = 0; i < gallery.length; i++) {
_loop8(i);
}
}
}();
Data from JSON file with Ajax:
some AJAX code..
if (slidePhotos.photos) {
for (let x= 0; x< slidePhotos.photos.length; x++) {
document.getElementById('gallery_photos_div').innerHTML += '<div class="product-gallery-preview-item" id="' + x+ '"><img src="' + slidePhotos.photos[x].url + '" alt=""></div>';
document.getElementById('gallery_thumbs_div').innerHTML += '<a class="product-gallery-thumblist-item" href="#' + x+ '"><img src="' + slidePhotos.photos[x].url + '"></a>';
}
}
The HTML Code is generated but unfortunately the images do not change when I click on it.
Sample JSON:
[
{
"url":"https://example.com/image1.jpg",
"url":"https://example.com/image2.jpg"
}
]
Can you tell me where I made a mistake?
I see a couple of problems here:
It looks like the code that you're using to populate your slideshow with new images from that JSON file appends a div.product-gallery-preview-item to your slideshow container, but doesn't append a corresponding .product-gallery-thumblist-item. In your productGallery function, your click handler is being bound to the latter, not the former. You'll want to make sure those target thumbnail elements are added as well as the preview ones.
Presumably, your productGallery function is fired when the page/DOM is first loaded to initialize the slideshow. The click event handlers that control the functionality of your slideshow are bound only to the elements that are present when the function runs. If you're not running this function repeatedly when appending content via AJAX (I hope you're not, as this would bind duplicate event handlers to the elements already in the slideshow), you'll need to ensure that your new elements are primed to respond to click in the same way that your existing ones are. You have a couple of options here:
Refactor productGallery so that it can be invoked repeatedly with the same slideshow, e.g: adding a :not(.slideshow-processed) to the the end of your .product-gallery-thumblist-item and .product-gallery-preview-item query selectors, and then adding the slideshow-processed class to these elements after binding your event handlers so they won't be processed again during subsequent invocations of productGallery.
Refactor productGallery to use event delegation (where a parent element listens for an event that occurs on one of its child elements). This would allow you to bind your event handler to the .product-gallery container just once, and have it fire for any preview/thumbnail pair that gets appended to the slideshow, without having to re-invoke productGallery. You can read more about event delegation at https://javascript.info/event-delegation.
Hopefully this points you in the right direction. Happy coding!
Related
I've read similar questions and tried several suggestions nothing had worked - hope you can help
Scenario:
loading my website with initial (static) index.html -- looks great css takes fine
clicking on "next page" button --> retrieves successfully the data from DB and dynamically generates the element's innerHTML section.
Problem: the audio player is the only element which doesn't look the same
Initial state: when loading the site first time
and how it looks after generating the HTML
All other tags like header image and text do apply the css rules.
The audio player has rules in the css file and has also the following script at the end of index.html and I'm suspecting thats the root of my problem not being "fired/called" again for the new generated elements
<script>
document.addEventListener('DOMContentLoaded', function() {
var mediaElements = document.querySelectorAll('video, audio'), total = mediaElements.length;
for (var i = 0; i < total; i++) {
new MediaElementPlayer(mediaElements[i], {
pluginPath: 'https://cdn.jsdelivr.net/npm/mediaelement#4.2.7/build/',
shimScriptAccess: 'always',
success: function () {
var target = document.body.querySelectorAll('.player'), targetTotal = target.length;
for (var j = 0; j < targetTotal; j++) {
target[j].style.visibility = 'visible';
}
}
});
}
});
</script>
If that's the reason how can I invoke this event on demand and how can I do it from the javascript file?
Apologies if my problem sounds trivial to JS experts.
I've created an image slider (carousel) and, while loading thumbnails, I'm trying to create a reference to a full-size image, so that when a thumbnail is clicked - the image opens in another div.
The relevant code within window.onload handler is:
for (var i = 0; i < numImages; ++i) {
var image = images[i],
frame = document.createElement('div');
frame.className = 'pictureFrame';
/* some styling skipped */
carousel.insertBefore(frame, image);
frame.appendChild(image);
} /* for */
My first attempt was to add "onclick" at the end of the for loop:
frame.onclick= function () {
var largeImage = document.getElementById('largeImage');
largeImage.style.display = 'block';
largeImage.style.width=200+"px";
largeImage.style.height=200+"px";
var url=largeImage.getAttribute('src');
document.getElementById('slides').style.display = 'block';
document.getElementById("slides").innerHTML="<img src='url' />";
}
However, this may only work with hard-coded ids (e.g. 'largeImage').
Ideally, I need to pass image.src as a parameter but this (frame.onclick= function (image.src)) will not work.
My next thought was to put all logic of getting image.src to a separate function and displaying it with frame.onclick= myFunction;
However, I came over an example:
<input type="button" value="Click me" id="elem">
<script>
elem.onclick = function(event) {
// show event type, element and coordinates of the click
alert(event.type + " at " + event.currentTarget);
alert("Coordinates: " + event.clientX + ":" + event.clientY);
};
</script>
And here it is above me to understand why in this example a handler can accept a parameter.
What would be a correct way of assigning an image to the onclick event? Or is there a better way of turning a thumbnail into href?
While it might not be the best way, you could place the full size image path as a data-attribute on your thumbnail.
<img id="thumbnail" src="thumbnailpath" data-fullSizeImage="fullSizePath">
Then on your onclick, you could access the thumbnail element and get it's data- attribute.
function onClick(event){
var fullSizePath = event.currentTarget.getAttribute("data-fullSizeImage");
//Do whatever you want with fullsizepath
}
Code is untested; but, something like that should work based on my experience.
data attributes are a very flexible custom attribute for developers to use. Essentially you start with "data-" and then append a name to represent the attribute. Here is the documententation link.
You can create a closure (that is, an anonymous function) and register it as an event handler to access variables from outside the event handler inside the event handler itself:
for (var i = 0; i < numImages; ++i) {
/* create element */
element.addEventListener('click', function (event) {
console.log('The index is ' + i);
});
}
However, this will not quite work, since the variable i is changed every time the loop increases it, and closures don't "capture" the current value, only the reference itself, so at the end i will be equal to numImages for each event listener.
If you're using ES6 you can overcome this by using let (or const) to prevent this behavior:
for (let i = 0; i < numImages; ++i) {
/* create element */
element.addEventListener('click', function (event) {
console.log('The index is ' + i);
});
}
If using ES6 is not an option, you can still accomplish this in ES5 and earlier by wrapping the inside of your loop in a function that takes i as a parameter, which makes sure each event handler references different variables, since these parameters are different variables for each iteration:
for (var i = 0; i < numImages; ++i) {
(function (index) {
/* create element */
element.addEventListener('click', function (event) {
console.log('The index is ' + index);
});
})(i); /* pass in i here, which will be assigned to the index parameter */
}
I am working on a dialog script in Vanilla JS. I ran into a problem with the click event on the video image. Even tough the image is surrounded with an anchor tag it shows the image as the event.target on the "trigger-dialog-open" event.
Here is the HMTL:
<a class="trigger-dialog--open thumbnail" data-dialog-id="dialog-video" href="javascript:;">
<figure>
<img src="http://i.ytimg.com/vi/id/sddefault.jpg" alt="" />
</figure>
</a>
And this is the event in JS:
var openTriggers = document.getElementsByClassName('trigger-dialog--open');
for (var i = 0; i < openTriggers.length; i++) {
openTriggers[i].addEventListener("click", function (event) {
this.openDialog(event.target.getAttribute('data-dialog-id'));
}.bind(this), false);
}
The event handler wants to know the dialog-id from the anchors data attribute. It can't be found because it thinks the image is the event.target, not the actual anchor. How can I correct this? Thanks!
Use event.currentTarget. The event.target is supposed to be the img element since that is what the user has clicked on. The click then bubbles up through the image's containers. event.currentTarget gives you the element that the click handler was actually bound to.
(Or if you didn't bind this to some other object you could use this within the click handler and it should also be the current target.)
I have a few questions is the var openTriggers supposed to be a part of a module hash? Because if it's global then you don't use a this, you only add a this, if it's referencing a variable that the function is also contained in. For example:
var aThing = {
openTriggers: document.getElementsByClassName('trigger-dialog--open'),
openModal: null,
openDialog: function(clickedThingAttr){
if(this.openModal !== null){
this.openModal.style.display = 'none';
}else{
this.openModal = document.getElementById(clickedThingAttr);
}
this.openModal = document.getElementById(clickedThingAttr);
this.openModal.style.display = 'block';
},
setEventListenersNStuff: function(){
for (var i = 0, n = this.openTriggers.length;i < n; i++) {
this.openTriggers[i].addEventListener("click", function (event) {
this.openDialog(event.target.getAttribute('data-dialog-id'));
});
};
}
};//end of aThing hash object
aThing.setEventListenersNStuff();
There are a few issues here:
1. why are you using .bind I think that is a jQuery thing, you want to pass a string to another function when an object is clicked, there no need for binding at all.
2. Also make sure that if you want to do something like open a modal, there is no need to call another method unless it's kinda complex.
3. What about other potential dialogs, it seems that when a .trigger-dialog--open is clicked you're just showing that one one modal with the embedded id, but what about others? Make sure all modals are closed before you open a new one, unless you want to have like 10 modals are open.
A thing to note: I added the line var i = 0, n = openTriggers.length;i < n; i++, now in this case it's silly optimization, and I heard for modern browsers this doesn't apply, but to explain why I added it, is because i < openTriggers.length would count and integrate the array N times. (This may be an outdated optmiziation).
If you meant global
Below I added a different set of code, just in case you meant that var openTriggers is global, kinda like you wrote above. Also I used querySelectorAll for this which is like jQuery's $('.thing') selector.
anyhoo, I also added
var openTriggers = document.querySelectorAll('.trigger-dialog--open');
var n = openTriggers.length;
function openDialog(ddId){
for (var i = 0;i < n; i++) {
openTriggers[i].style.display = 'none';
};
document.getElementById(ddId).style.display = 'block';
};
for (var i = 0;i < n; i++) {
openTriggers[i].addEventListener("click", function (event) {
openDialog(event.target.getAttribute('data-dialog-id'));
});
}
}
So for the question of hiding already open modals I would suggest you could either cache the open Dialog within a module, or you could toggle a class, which would be less efficient since it would require an extra DOM search. Additionally you could add a if this.openModal.id === clickedThingAttr to hide if open, that way you got a toggle feature.
Anyways I suggest you read up on this stuff, if you want to use plain JS but would like the features of jQuery: http://blog.romanliutikov.com/post/63383858003/how-to-forget-about-jquery-and-start-using-native
Thank you for your time.
You can use a closure
var openTriggers = document.getElementsByClassName('trigger-dialog--open');
for (var i = 0; i < this.openTriggers.length; i++) {
(function(element) {
element.addEventListener("click", function (event) {
element.openDialog(event.target.getAttribute('data-dialog-id'));
}, false)
})(openTriggers[i]);
}
I have a SUBMIT and SAVE button for a form that are by default listening for Onclick events.
When the form is SUBMITTED OR SAVED - the page resets the scroll position to the TOP of the page.
Recently the users of the applications have requested that the page stay at the bottom of the page where the buttons are located for only a subset of forms.
(These buttons are used across hundreds of other forms so I cannot change the reset of the scrolling globally.)
So the solution that I am trying to implement involves a couple hidden input fields and a few event listeners.
I have added an onmousedown event for these buttons, like so -
// Submit and Save button listeners
var globalButtons;
if (v_doc.getElementsByClassName) {
globalButtons = v_doc.getElementsByClassName('globalbuttons');
}
// Internet Explorer does not support getElementsByClassName - therefore implement our own version of it here
else {
globalButtons = [];
var myclass = new RegExp('\\b'+'globalbuttons'+'\\b');
var elem = v_doc.body.getElementsByTagName("input");
for (var i = 0; i < elem.length; i++) {
var classes = elem[i].className;
if (myclass.test(classes)) {
globalButtons.push(elem[i]);
}
}
}
for (var gb = 0; gb < globalButtons.length; gb++) {
if (globalButtons[gb].name == 'methodToCall.route' ||
globalButtons[gb].name == 'methodToCall.save') {
if(globalButtons[gb].addEventListener) { //all browsers except IE before version 9
globalButtons[gb].addEventListener("mousedown", function(){flagSpecialScrollOnRefresh()},false);
}
else {
if(globalButtons[gb].attachEvent) { //IE before version 9
globalButtons[gb].attachEvent("onmousedown",function(){flagSpecialScrollOnRefresh()});
}
}
}
else { continue; }
}
This code is located in a function called attachButtonListeners
Next, I defined my handler like so and placed it into another function that gets called each time my page is being loaded -
function checkSpecialScrollCase() {
var spfrm = getPortlet();
var sp_doc = spfrm.contentDocument ? spfrm.contentDocument: spfrm.contentWindow.document;
var specialScrollExists = sp_doc.getElementById(docTypeButton).value;
if (specialScrollExists == "YES") {
sp_doc.getElementById(docTypeButton).value = 'NO';
}
// else - nothing to do in this case
}
docTypeButton = REQS_BUTTONS
And it references the following element at the bottom of my JSP page -
<input type="hidden" id="REQS_BUTTONS" value="NO"/>
<a name="anchorREQS"></a>
Notice the anchor tag. Eventually, I need to add the location.hash call into my handler so that I scroll to this location. That part is irrelevant at this point and here is why.
Problem -
My flagSpecialScrollOnRefresh function is NOT setting the value to YES when it should be.
I believe my onClick event is happening too fast for my onmousedown event from happening.
Evidence -
If I place an alert statement like so -
function flagSpecialScrollOnRefresh() {
var scfrm = getPortlet();
var sc_doc = scfrm.contentDocument ? scfrm.contentDocument: scfrm.contentWindow.document;
alert("BLAH!");
sc_doc.getElementById(docTypeButton).value = "YES";
}
And then I examine the element using Firebug - the value is getting SET!
Once I take out the alert - no go!
How do I ensure that my mousedown event gets executed first? Or is this even the problem here????
mousedown is part of a click event.
Whatever you are doing with click events now should be moved to the submit event on the form. That way you can use mousedown, mouseover, or even click on the buttons to do whatever you want.
I'm having a problem generating a list of feed items (using Google Feeds API) triggered by a tap event on a list item. I can call the function on page load, and it works great. However, when I try to call the function on a "tap" event if generates a blank page.
Here is the list item I want to trigger the event on (on which I'm calling the 'tap' event):
<li id="welstech" class="listFeeds">
<a href="#">
<img src="_images/welstech-logo-db.jpg" alt="WELSTech" />
<h2>WELSTech Podcast</h2>
<p>Discussions about Tech & Ministry</p>
</a>
</li>
Here is the script I have placed at the bottom of the html page:
<script type="text/javascript>
$(document).on('tap', '.listFeeds', function() {
listAudioPosts("http://feeds.feedburner.com/welstech");
});
</script>
Here is the function that it calls:
function listAudioPosts(feedurl){
google.load("feeds", "1");
console.log("I'm still going 1");
function initialize() {
console.log("I'm still going 2");
var feed = new google.feeds.Feed(feedurl);
feed.setNumEntries(10)
var output = '<ul data-role="listview" data-split-icon="info">';
var name = "welstech";
feed.load(function(result) {
if (!result.error) {
for (var i = 0; i < result.feed.entries.length; i++) {
var entry = result.feed.entries[i];
var mediaGroups = result.feed.entries[i].mediaGroups[0].contents[0];
var stripContentSnippet = entry.contentSnippet.replace(/[^a-zA-Z 0-9.:-]+/g,'');
var stripaContentSnippet = stripContentSnippet.replace('lt--pagingfilter--gt','');
output += '<li>';
output += '<a href="#">';
output += '<h2>' + entry.title + '</h2>';
output += '<p>' + stripaContentSnippet + '</p>';
output += '</a>';
output += '</li>';
} // loop through all the feeds and create li elements
} // end of if statement
output += '</ul>';
$("#testtouchoutput").html(output).trigger('refresh');
$.mobile.changePage("#test");
}); // end feed.load (function(result)
} // end initialize function
google.setOnLoadCallback(initialize);
}
I know the function works, because I can call it on page load with this script at the bottom of my html page see below. I also know the function gets as far as the sub function initialize(), as I can see the console.log output before that sub function, but not the one after it:
<script type="text/javascript">
listAudioPosts("http://feeds.feedburner.com/welstech");
</script>
So the second the home page loads, the function runs and the page refreshes to the list I want to generate...except I want to generate it on a "tap" event.
I'm a fairly new jquery programmer, so I'm sure it is something obvious. What am I missing?
Thanks.
You didn't close the .on, try something like this:
<script type="text/javascript>
$(document).ready(function() {
var timeTouched;
$('.listFeeds').bind('touchstart', function() {
timeTouched = new Date().getTime();
}
$('.listFeeds').bind('touchend', function() {
if (new Date().getTime() - timeTouched < 200) { // Ended touch within 200 milliseconds, counts as a tap
listAudioPosts("http://feeds.feedburner.com/welstech");
}
});
});
</script>
Make sure if your script is above wherever the .listFeeds element is defined, you include the $(document).ready() part, else it will be unable to bind the event to that element.
UPDATE: tap doesn't seem to be an event, updated with a potential solution.
UPDATE 2: Trying with .bind instead of .on.