I have a div that can display 3 images (in the background) each indicating the 'state' of some variable: i.e., partial, full and none. For each of these states I have images: partial.gif, full.gif and none.gif (i.e., these are background images of that div)
Need: Circular queue like toggling effect for changing the images in this order partial -> full -> none -> partial
So if the current image is 'partial.gif' and the user clicks the div the background image changes to the next one in the sequence i.e., full.gif (and if it is currently full.gif it changes to none.gif and that to partial.gif and so on).
Naive solution: have a bunch of if/else's or switch-case and check the current one (image) and then decide based on array look up which is the next one. Is this the best way of doing it? Can I leverage jQuery's toggle function somehow?
(PS: It need not be restricted to images, but could also be for different background color sequences etc., I'd like to know what it is a good 'generic' way of doing it i.e., The example may be specific for background images but if I changed part of that code for background-color or font it should still work. I don't want it to be purely generic, but just enough so it is easy to modify for other attributes. If not, that's fine too. Just a thought :)
http://api.jquery.com/toggle-event/
To be precise http://api.jquery.com/toggle-event/#example-0
does exactly what you wanted...
$("#div1").toggle(
function() {
$(this).css("background-image","url(full.png)")
},
function() {
$(this).css("background-image","url()")
},
function() {
$(this).css("background-image","url(partial.png)")
}
});
UPDATE fn.toggle was removed from jQuery
Here are relevant posts
Where has fn.toggle( handler(eventObject), handler(eventObject)...) gone?
Toggle stopped working after jquery update
As long as it's a CSS-based solution (where you can just switch classes), you could do something like this (untested code):
$('#element').click(function() {
// get current css class value.
var class = $(this).attr('class');
// determine/increment number.
var nextNumber = parseInt(class.charAt(class.length - 1)) + 1;
// if too high, reset to first one.
if (nextNumber > 3) {
nextNumber = 1;
}
// remove old class and add new class.
$(this).removeClass(class).addClass('my_class' + nextNumber);
});
Assumption being made here that you only have one CSS class applied to the element at a time. But if that's not the case, I'm sure you can find a workaround/tweak for this.
And this is just generic enough where you can swap out your CSS class definitions without impacting the script functionality.
Related
I'm using two simple addEventListener mouseenter and mouseleave functions respectively to play and stop animations (Bodymovin/SVG animations, though I suspect that fact is irrelevant).
So, the following works fine:
document.getElementById('animationDiv').addEventListener('mouseenter', function(){
animation.play();
})
(The HTML couldn't be simpler: The relevant part is just an empty div placeholder filled by script - i.e., <div id="animationDiv"></div>.
I can place that in the same file as the one that operationalizes the animation code, or I can place it in a separate "trigger" file, with both files (and other others necessary to processing) loaded in the site footer.
The problem arises when I need to be able to set triggers for any of multiple similar animations that may or may not appear on a given page.
If only one of two animatable elements are present on a page, then one of two sets of triggers will throw an error. If the first of two such triggers is not present, then the second one will not be processed, meaning that the animation will fail. Or at least that's what it looks like to me is happening.
So, just to be clear, if I add the following two triggers for the same page, and the first of the following two elements is present, then the animation will play on mouseenter. If only the second is present, its animation won't be triggered, apparently because of the error thrown on the first.
document.getElementById('firstAnimationDiv').addEventListener('mouseenter', function(){
firstAnimation.play();
})
document.getElementById('secondAnimationDiv').addEventListener('mouseenter', function(){
secondAnimation.play();
})
At present I can work around the problem by creating multiple trigger files, one for each animation, and setting them to load only when I know that the animatable element will be present, but this approach would get increasingly inefficient when I am using multiple animations per page, on pages whose content may be altered.
I've looked at try/catch approaches and also at event delegation approaches, but so far they seem a bit complicated for handling this simple problem, if appropriate at all.
Is there an efficient and flexible standard method for preventing or properly handling an error for an element not found, in such a way that subsequent functions can still be processed? Or am I missing something else or somehow misreading the error and the function failure I've been encountering?
WHY I PICKED THE ANSWER THAT I DID (PLUS WORKING CODE)
I was easily able to make the simple, directly responsive answer by Baoo work.
I was unable to make the answers below by Patrick Roberts and Crazy Train work, though no doubt my undeveloped js skills are entirely at fault. When I have the time, or when the issue next comes up for me in a more complex implementation (possibly soon!), I'll take another look at their solutions, and see if I can either make them work or if I can formulate a better question with fully fledged coding examples to be worked through.
Finally, just to make things clear for people who might be looking for an answer on Bodymovin animations, and whose js is even weaker than mine, the following is working code, all added to the same single file in which a larger set of Bodymovin animations are constructed, relieving me of any need to create separate trigger files, and preventing TypeErrors and impaired functionality.
//There are three "lets_talk" animations that can play - "home," "snug," and "fixed"
//and three types of buttons needing enter and leave play and stop triggers
let home = document.getElementById('myBtn_bm_home');
if (home) home.addEventListener('mouseenter', function() {
lets_talk_home.play();
});
if (home) home.addEventListener('mouseleave', function() {
lets_talk_home.stop();
});
let snug = document.getElementById('myBtn_bm_snug');
if (snug) snug.addEventListener('mouseenter', function() {
lets_talk_snug.play();
});
if (snug) snug.addEventListener('mouseleave', function() {
lets_talk_snug.stop();
});
let fixed = document.getElementById('myBtn_bm_fixed');
if (fixed) fixed.addEventListener('mouseenter', function() {
lets_talk_fixed.play();
});
if (fixed) fixed.addEventListener('mouseleave', function() {
lets_talk_fixed.stop();
});
At typical piece of underlying HTML (it's generated by a PHP function taking into account other conditions, so not identical for each button), looks like this at the moment - although I'll be paring away the data-attribute and class, since I'm not currently using either. I provide it on the off-chance that someone sees something significant or useful there.
<div id="letsTalk" class="lets-talk">
<a id="myBtn" href="#"><!-- a default-prevented link to a pop-up modal -->
<div class="bm-button" id="myBtn_bm_snug" data-animation="snug"></div><!-- "snug" (vs "fixed" or "home" is in both instances added by PHP -->
</a>
</div>
Obviously, a more parsimonious and flexible answer could be - and probably should be - written. On that note, correctly combining both the play and stop listeners within a single conditional would be an obvious first step, but I'm too much of a js plodder even to get that right on a first or second try. Maybe later/next time!
Thanks again to everyone who provided an answer. I won't ask you to try to squeeze the working solution into your suggested framework - but I won't ask you not to either...
Just write your code so that it won't throw an error if the element isn't present, by simply checking if the element exists.
let first = document.getElementById('firstAnimationDiv');
if (first) first.addEventListener('mouseenter', function() {firstAnimation.play();});
You could approach this slightly differently using delegated event handling. mouseover, unlike mouseenter, bubbles to its ancestor elements, so you could add a single event listener to an ancestor element where every #animationDiv is contained, and switch on event.target.id to call the correct play() method:
document.getElementById('animationDivContainer').addEventListener('mouseover', function (event) {
switch (event.target.id) {
case 'firstAnimationDiv':
return firstAnimation.play();
case 'secondAnimationDiv':
return secondAnimation.play();
// and so on
}
});
You could also avoid using id and use a more semantically correct attribute like data-animation as a compromise between this approach and #CrazyTrain's:
document.getElementById('animationDivContainer').addEventListener('mouseover', function (event) {
// assuming <div data-animation="...">
// instead of <div id="...">
switch (event.target.dataset.animation) {
case 'first':
return firstAnimation.play();
case 'second':
return secondAnimation.play();
// and so on
}
});
First, refactor your HTML to add a common class to all of the placeholder divs instead of using unique IDs. Also add a data-animation attribute to reference the desired animation.
<div class="animation" data-animation="first"></div>
<div class="animation" data-animation="second"></div>
The data- attribute should have a value that targets the appropriate animation.
(As #PatrickRobers noted, the DOM selection can be based on the data-animation attribute, so the class isn't really needed.)
Since your animations are held as global variables, you can use the value of data-animation to look up that variable. However, it would be better if they weren't global, but were rather in a common object.
const animations = {
first: null, // your first animation
second: null, // your second animation
};
Then select the placeholder elements by class, and use the data attribute to see if the animation exists, and if so, play it.
const divs = document.querySelectorAll("div.animation");
divs.forEach(div => {
const anim = animations[div.dataset.animation];
if (anim) {
anim.play(); // Found the animation for this div, so play it
}
});
This way you're guaranteed only to work with placeholder divs that exist and animations that exist.
(And as noted above, selection using the data attribute can be done const divs = document.querySelectorAll("div[data-animation]"); so the class becomes unnecessary.)
I have a slide in menu using vanilla javascript for use on phones, but so far all my tests have resulted in the mobile browsers ignoring the first tap (have tried both touchstart & click as events). Starting with the second tap it works beautifully with each and every subsequent tap.
As opening & closing the menu is the only javascript function on the pages, I don't want to load a huge library, I want to keep it simple and small. My code is below:
var b = document.getElementById('menubtn');
b.addEventListener('touchstart', function (e) {
var n = document.getElementById('nav');
var ns = n.style.left;
if (ns == "-600px") {
n.style.left = "0px";
} else {
n.style.left = "-600px";
}
e.preventDefault();
});
Any ways to easily eliminate this need for double clicking the first time?
In the fwiw dept, it is a responsive design, with the nav menu in a column on big screens and a slide in on phones.
Edit: Solved the issue
Following through on Matt Styles comment, I tried using classList.toggle and it solved the issue. The final version is below:
var b = document.getElementById('menubtn');
var n = document.getElementById('nav');
b.addEventListener('touchstart', function () {
n.classList.toggle('shwmenu');
setTimeout(function () {
b.classList.toggle('shwmenu');
}, 500);
});
I added the delayed menubtn code to toggle the icon between closed and open states.
The behaviour you describe could be caused by the following:
In your JS you try to implement some kind of On-Off toggle for your nav element, differentiated on the left CSS property value, with -600 representing the off value and 0 representing the on value.
As you said, toggling between those states seems to work fine, but what happens when your element is NOT initialized on exactly -600? Then on your first tap you will always run into your else clause and set it to -600 first, showing effectively no visual effect on your first tap, just as you describe.
For an instant test, just swap the if-else clause around to turn it on when style.left is not -600 and then maybe work up towards a more dynamic differentiation between your states ;)
I think this is because element.style.left is empty even if you have set left property in your stylesheet. You can use element.offsetLeft instead. Please see here to how it works.
HTMLElement.style - MDN
The style property is not useful for learning about the element's style in general, since it represents only the CSS declarations set in the element's inline style attribute, not those that come from style rules elsewhere, such as style rules in the <head> section, or external style sheets. To get the values of all CSS properties for an element you should use window.getComputedStyle() instead.
I'm using a jquery code to display an image slideshow on my website, with a counter.
When clicking on the image, img swicth between show and hide.
here is the jquery code i'm using :
$(document).ready(function () {
var count = $('.image_news').length;
$("#total").text(count);
// set display:none for all members of ".pic" class except the first
$('.image_news:gt(0)').hide();
// stores all matches for class="pic"
var $slides = $('.image_news');
$slides.click(function () {
// stores the currently-visible slide
var $current = $(this);
if ($current.is($slides.last())) {
$("#current").text("1");
$current.hide();
$slides.first().show();
}
// else, hide current slide and show the next one
else {
$("#current").text($current.next().index()+1);
$current.hide().next().show();
}
});
});
it works fine with one slideshow, but I would like to have several sildeshow on the same page, and I don't know how many so I can't add a unique ID to my images ('.image_news')...
is there a way of doing it using $this ? I need also to have unique counter for each slideshow, and the slideshow to be fully independant... when clicking on first slideshow to navigate, only the first slideshow should slide.
hope someone can help me with this, or maybe there's another way of doing it...
here is a jsfiddle to see it in action :
http://jsfiddle.net/XRpeA/19/
thanks for your help
I can't write the codes exactly but I can give ideas to design it. I think you should think object oriented way. Because you want to use more than one instance of sliders. If I were you, I'd do these to get the results you want.
Make a slider class whose constructor takes 'class name' as a parameter. For example in your situation, you have used "image_news" for class name. For each of sliders, use different class names. With using this, there will be no longer problem for separation between different sliders in the page.
Define the click method exactly as above.
I may have forgotten sth but with these design there is no more problem for multiple slider in one page.
I'm working on designing an interactive university campus map and need some direction with what I am looking to do.
Link to page: http://www.torontoclassfind.com/startpage.html
I want to be able to click on the links in the top menu (only one link is active so far and it loads and Ajax page in lower left div) and have it swap the building image with a different image to show that it's been selected.
I could do that with the following:
$("#buildinglink1").click(function () {
$("#buildingimg1").attr("src","highlightedimage.gif")
})
Problem is I need to change back the image to it's default image once another menu link is clicked and a new building is selected.
The building images are located at www.torontoclassdfind.com/building/ and the highlighted images are located at www.torontoclassdfind.com/buildingc/ and the names for the buildings are the same in both locations.
I am thinking of using JQuery's .replace element to do this (ex: jquery remove part of url) which would remove or add the 'c' to the url, but I'm kind of lost from here.
Any tips? I think I need to make a function that would indicated a link is selected and somehow merge it with the .replace element.
Just a note: .replace is a JavaScript string (and others) method, not a jQuery method.
I think you're asking to do something like this:
$(".any-building").click(function () {
//replace all building sources with the unhighlighted version
$(".any-building").attr('src', function () {
return $(this).attr('src').replace('buildingc', 'building');
});
//replace clicked image with highlighted one
$(this).attr('src', $(this).attr('src').replace('building', 'buildingc'));
});
A possible downside is that with a lot of images this swap may take a long time or cause some flicker. If that's the case, then you may want to add a class .active to the highlighted image or something like that and only do the swap for that image and the newly clicked one.
A common learning mistake in jQuery is to focus on ID's for all types of selectors. They work great for very small number of elements however become very unwieldy fast for large groups of elements that can easily be managed by simpler code methods.
You want to be able to write far more universal code where one handler would cover all of your links that share the same functionality in the page .
Example:
var $mainImage=$('#mainImage')
var $buildingLinks=$('.buildingliststyle a').click(function(){
/* "this" is the link clicked*/
/* get index of this link in the whole collection of links*/
var index=$buildingLinks.index(this);
/* perhaps all the image urls are stored in an array*/
var imgUrl= imagesArray( index);
/* perhaps the image urls are stored in data attribute of link( easy to manage when link created server side)*/
var imgUrl=$(this).data('image');
/* store the current image on display (not clear how page is supposed to work)*/
var currImage=$mainImage.attr('src');
$mainImage.data('lastImage', currImage);/* can use this to reset in other parts of code*/
/* nw update main image*/
$mainImage.attr('src', imgUrl);
/* load ajax content for this link*/
$('#ajaxContainer').load( $(this).attr('href') );
/* avoid browser following link*/
return false;
});
/* button to reset main image from data we already stored*/
$('#imageResetButton').click(function(){
$mainImage.attr('src', $mainImage.data('lastImage') );
})
Can see that by working with groups of elements in one handler can do a lot with very little code and not needing to focus on ID
Code above mentions potentially storing image url in data attribute such as:
Building name
I have a search page that is used in multiple places with multiple 'themes' throughout my site. I have a few divs that can have their background color changed based on a radio button selection (whether they are enabled or not). I can do this just fine by changing the css class of the div on the fly with javascript.
However, these themes could potentially change, and the background color is grabbed from a database when the page is created. Right now I do this in the C# codebehind:
string bgStyle = "background-color:" +theme.searchTextHeaderColor +";";
OwnerSearchHeader.Attributes.Add("style", bgStyle);
In the Javascript I need to change this color to make it look disabled, and when the user clicks back to this div I need to re-enable it by changing it back to its original color. But since I only knew this color in the code-behind, I don't know what it was in the Javascript.
So my thought was to create a css class in the resulting HTML page when the page is loaded with the background color I need. Then I could simply switch from the divEnabled and divDisabled class in the javascript. But I'm not exactly sure how to do that.
Alternatively I could create a hidden element, assign it the 'enabled' style, and use that as a reference in the JavaScript when enabling my div. This seems like a hack but maybe its the easiest way. I'm still new to a lot of this, so I'd appreciate any suggestions. Thanks for the input!
So my thought was to create a css class in the resulting HTML page when the page is loaded with the background color I need. Then I could simply switch from the divEnabled and divDisabled class in the javascript. But I'm not exactly sure how to do that.
Yes, this is the anser; do this. In the <head> of your document add a <style> and put your CSS in there like so: (my Asp.NET is a little rusty so forgive me if it has some hicups ;) )
<style>
<!--
.divEnabled {
background-color:<%=theme.searchTextHeaderColor%>;
}
.divDisabled {
background-color:gray; /* or wtv */
}
-->
</style>
You could also put it in an external CSS file, which may be a good idea.
Then write some JavaScript to add/remove the class attribute (I'm going to ask that you don't call is the "CSS Class" ;) )
var ownersearchheader = document.getElementById("<%=OwnerSearchHeader.ClientId%>");
// changing the class attribute to `divDisabled`
var newClassAttribute = ownersearchheader.getAttribute("class").replace(/\bdivEnabled\b/, "divDisabled")
ownersearchheader.setAttribute("class", newClassAttribute);
// ... or,
// changing the class attribute to `divEnabled`
var newClassAttribute = ownersearchheader.getAttribute("class").replace(/\bdivDisabled\b/, "divEnabled")
ownersearchheader.setAttribute("class", newClassAttribute);
This is indeed a mouthfull, so, like #Haydar says, you might want to use jQuery, which offers easy-as-pie addClass(), removeClass() and toggleClass() methods.
You can use the jquery .toggleClass method.
Description: Add or remove one or more classes from each element in the set of matched elements, depending on either the class's presence or the value of the switch argument.
Here is the link to the api doc.
Jquery API