<div id='a'>A</div>
<div id='b'>B</div>
<div id='c'>C</div>
<div id='d'>D</div>
This is the javascript:
var first = [$('#a'), $('#b')];
var second = [$('#c'), $('#d')]
var everything = [first, second];
for (var i=0; i<everything.length; ++i) {
var current = [];
for (var j=0; j<everything[i].length; ++j) {
current.push(everything[i][j]);
}
$.each(current, function(i,d) {
d.hover(function() {
$(current).each(function() { this.css('background-color', '#00FF00') });
}, function() { // Hover out
$(current).each(function() { this.css('background-color', 'white') });
});
});
}
This code is a little strange because I took my original code to nerf it down to something presentable here.
Why is C,D highlighted when I hover over A,B?
Fiddle: http://jsfiddle.net/C4rSj/4/
EDIT
Desired Behavior: I would like A,B to highlight when I hover over A or B and C,D to highlight when I hover over C or D
It’s because current holds references to #c and #d when the hover handler function is executed.
Use a variable inside of your each loop to create a scope that preserves the current value, like this:
$.each(current, function (i, d) {
var _cur = current; // local variable to preserve value of current within the scope of this function
d.hover(function () {
$(_cur).each(function () {
this.css('background-color', '#00FF00')
});
}, function () { // Hover out
$(_cur).each(function () {
this.css('background-color', 'white')
});
});
});
http://jsfiddle.net/C4rSj/5/
though you have already an answer, I would probably change the code to make it better for reuse:
HTML:
wrap all group elements in a div and use data-group to set the group
<div class="grouping">
<div id='a' data-group="1">A</div>
<div id='b' data-group="1">B</div>
<div id='c' data-group="2">C</div>
<div id='d' data-group="2">D</div>
</div>
then use a simplify version of your code, where you add/remove css classes instead of hardcode the values:
function setGroups() {
var groups = [];
$(".grouping > div").each(function(i, elm) {
var g = $(this).attr("data-group");
if(!_.contains(groups, g)) {
$(elm).parent().find("[data-group=" + g + "]")
.hover(function() {
$(elm).removeClass("out").addClass("in");
}, function() {
$(elm).removeClass("in").addClass("out");
});
} // if
}); // each
}
and test for your self:
Demo on JsBin: http://jsbin.com/wewir/1/edit?html,css,js,output
This way it's easier to reuse the feature in the same page or in different pages through your website.
Related
I'm trying to do the following:
Fade out a div
Change its text
Fade it in again
The problem is, step 2 is happening before step 1. Why is that happening?
Here's the code:
<p id="p">
hi!
</p>
<button onclick="foo()">
wefew
</button>
<script>
$("button").click(function (){
var item = $("#p");
item.hide("slow");
item.text("text");
item.show("slow");
})
</script>
https://jsfiddle.net/pq35yd5t/
edit:
I found that the problem is that I'm using a for loop and that the callback function only work on ht elast loop... why, again
code:
for (var i = 0; i < ob_prop.length; i++) {
if (ob_prop[i]=="tag") {
continue;
}
var item = $("#"+ob_prop[i]);
item.hide("slow", function() {
item.text(work[pointer][ob_prop[i]]).show("slow");
});
}
Because fading is an asynchronous operation.
To do what you're doing, use the callback on hide:
$("button").click(function (){
var item = $("#p");
item.hide("slow", function() {
item.text("text");
item.show("slow");
});
})
In a comment you've said:
ok i have tried it but in the original code there's a for loop and function work only at the end of the loop
The callback will have this set to the element related to the callback, so use that rather than item:
$("button").click(function (){
var item = $("#p");
item.hide("slow", function() {
$(this).text("text").show("slow");
});
})
Your latest edit has the closures in loops problem. See that question's answers for details, but one of the solutions is to use $.each (or Array.prototype.forEach if you don't have to worry about obsolete browsers like IE8):
$.each(function(index, ob) {
if (ob != "tag") {
$(ob).hide("slow", function() {
$(this).text(work[pointer][ob]).show("slow");
});
}
});
I'm just learning JS and jQuery, so I can not reduce the normal code is shown below:
var menuBtn = '#menu',
classMenuOpen = 'side_menu_open',
aboutBtn = '#about',
classAboutOpen = 'side_about_open',
dateBtn = '#date',
classDateOpen = 'side_date_open',
closeBtn = '.header__menu a, .close';
// Menu Side
$(menuBtn).on('click', function() {
$('html').toggleClass(classMenuOpen);
});
$(closeBtn).not(menuBtn).on('click', function() {
$('html').removeClass(classMenuOpen);
});
// About Side
$(aboutBtn).on('click', function() {
$('html').toggleClass(classAboutOpen);
});
$(closeBtn).not(aboutBtn).on('click', function() {
$('html').removeClass(classAboutOpen);
});
// Date Side
$(dateBtn).on('click', function() {
$('html').toggleClass(classDateOpen);
});
$(closeBtn).not(dateBtn).on('click', function() {
$('html').removeClass(classDateOpen);
});
I would like to write a loop (example below) , but knowledge is not enough. I hope someone can help, thanks in advance ;)
['menu', 'about', 'date'].forEach((selector) => {
$('.' + selector + ' .scrollbar-inner').scrollbar({
onScroll: function(y, x){
$('.' + selector + ' .scrollbar-inner').toggleClass('scroll-shadow', y.scroll >= 5);
}
});
});
maybe something like this:
// wrap in IIFE for scope containment
(function($) {
// Setup button keys
const buttons = ['menu', 'about', 'date'];
// click handler
const createClickHandler = value => {
// set loop variables
let selector = `#${value}`
, className = `side_${value}_open`;
// create event triggers
$(selector).on('click', e => $('html').toggleClass(className));
$('.header__menu a, .close').not(selector).on('click', e => $('html').removeClass(className))
};
// iterate keys and apply handler method
buttons.forEach(createClickHandler);
})(jQuery);
Here is the loop you are looking for!
In the forEach() loop you are looping through the array of strings, component holds the string element (so here 'menu', 'about', etc...). Then inside the loop's body set two variables:
selector is the selector string
classOpen is the class name of an element you have associated with the component
Then you basically write the same code using only those two variables instead of writing the code three times with set strings.
let closeBtn = '.header__menu a, .close'
['menu', 'about', 'date'].forEach(function(component) {
let selector = '#' + component;
let classOpen = '.side_' + component + '_open';
$(selector).on('click', function() {
$('html').toggleClass(classOpen);
});
$(closeBtn).not(selector).on('click', function() {
$('html').removeClass(selector);
});
});
I have a requirement to change the text on hover of multiple menu items at once but I cannot use CSS and nor can I give each individual item its own CSS class. What I would like to do is when the mouse hovers anywhere over the .menu-wrapper the Javascript replaces each of the <li> item texts with the relevant replacement text.
I have a script which works perfectly for a single item:
<div class="menu-wrapper">
<ul>
<li>WORD1</li>
</ul>
</div>
Javascript:
var originalText = $('.menu-wrapper > ul > li').text();
$('.menu-wrapper').hover(function () {
var $p = $(this).find('li');
$p.fadeOut(300, function () {
$(this).text('replacement word 1').fadeIn(300);
});
}, function () {
// set original text
$(this).find('li').fadeOut(300, function () {
$(this).text(originalText).fadeIn(300);
});
});
But obviously if you add multiple <li> items it breaks because it is only storing a single .text() variable and concatenates all the entries after the first mouseout event.
I tried using a switch statement to look for the value of the .text() and change the text value accordingly but it didn't work (my Javascript is weak...).
I'd appreciate any help with this. I only have four items to replace the text of so repeating any script as necessary is not a problem. Normally I would give each one it's own class identity and use what I already have but unfortunately I can't.
Please don't suggest using CSS as I already know how to do that but for this I need to use Javascript.
I could not find this question elsewhere.
Thanks!
Main issue is first line:
var originalText = $('.menu-wrapper > ul > li').text();
This will get all text from all elements in the collection:
What you could do is store that text on each element using jQuery data() by looping over the elements and dealing with instances:
$('.menu-wrapper > ul > li').each(function(){
$(this).data('original', $(this).text());
});
Then in mouseout part of hover read the previously stored text using data() again
$(this).find('li').fadeOut(300, function () {
var originalText = $(this).data('original');
$(this).text(originalText).fadeIn(300);
});
Several options for the new text:
Put it in markup as data attribute
<li data-alt_text="alternate word">
Then within mousenter callback of hover:
$p.fadeOut(300, function () {
$(this).text($(this).data('alt_text')).fadeIn(300);
});
Or put in array and use first loop to add the array data to element
var words=['W1','W2','W3'];
// first argument of "each" is "index"
$('.menu-wrapper > ul > li').each(function(index){
$(this).data(
{
'alt_text': words[index],
'original', $(this).text()
}
);
});
You can make use of javascripts ability to assign any property to an object (element) to store the original text instead of storing it in a single variable (or use jquery data functionality to do the same)
$('.menu-wrapper li').hover(function () {
$(this).fadeOut(300, function () {
this.originalText = $(this).text();
$(this).text('replacement word 1').fadeIn(300);
});
}, function () {
// set original text
$(this).fadeOut(300, function () {
$(this).text(this.originalText).fadeIn(300);
});
});
fiddle
For this to work, instead of binding to the .menu-wrapper div directly, you can use .menu-wrapper li to bind to the individual li elements inside the div. Afterwards the orignal text can be stored before changing it. The same can be done beforehand, storing all values, the advantage of this way is that you always store the latest value, in case the text is dynamically altered after startup.
To couple the replacement texts to the li elements, without altering the html safest would be to couple the replacement to the text. Easiest is an indexed based solution:
var replacements = ['replacement Word1', 'for word2' , 'third time\'s a charm'];
$('.menu-wrapper li').hover(function () {
var $this= $(this);
$this.fadeOut(300, function () {
$this.data('originalText', $this.text()).
text(replacements[$this.index()]).fadeIn(300);
});
}, function () {
// set original text
$(this).fadeOut(300, function () {
$(this).text($(this).data('originalText')).fadeIn(300);
});
});
fiddle
For completeness sake, this would be an alternative while using the li text (provided the text can be used as a property):
var replacements ={
WORD1 : 'replacement Word1',
WORD2 : 'for word2',
WORD3: 'third time\'s a charm'
};
$('.menu-wrapper li').hover(function () {
var $this= $(this);
$this.fadeOut(300, function () {
$this.data('originalText', $this.text()).
text(replacements[$this.text()]).fadeIn(300);
});
}, function () {
// set original text
$(this).fadeOut(300, function () {
$(this).text($(this).data('originalText')).fadeIn(300);
});
});
fiddle
Here's a short and simple solution to your problem:
var originalText;
$('.menu-wrapper').hover(function () {
var $p = $(this).find('li');
$p.fadeOut(300, function () {
this.originalText = $(this).text(); // STORES VALUE BEFORE REPLACEMENT
$(this).text('replacement word 1').fadeIn(300);
});
}, function () {
$(this).find('li').fadeOut(300, function () {
$(this).text(this.originalText).fadeIn(300);
});
});
Just store the value of that element in originalText before replacing it.
We can use two arrays to store Original text and New text. And then use $.each to loop through each of the lis and use their index to replace the text.
HTML :
<div class="menu-wrapper">
<ul>
<li>WORD1</li>
<li>WORD2</li>
<li>WORD3</li>
</ul>
</div>
jQuery :
var originaltext = ['Word1','Word2','Word3'];
var newText = ['New text1','New text2','New text3'];
$('.menu-wrapper').hover(function () {
$('.menu-wrapper li').each(function(i){
$this = $(this);
$this.html(newText[i])
});
}, function(){
$('.menu-wrapper li').each(function(i){
$this = $(this);
$this.html(originaltext[i])
});
});
jsfiddle
Since all of the other answers here use jQuery, I'll add one done with vanilla js.
To do this, we're going to need to use a javascript closure. This is used so that on completion of the fade-out, we have (a) the element just faded and (b) which is far more important, an index into the originalStrings array. (B) is the more important here, because the target element is something the animate code already has - we could easily pass the original element to the callback function. However, we really need the index or the string that corresponds to each element. The closure gives a means to do so.
The following code will fade-out all/any matching elements and then perform a fade-in after changing the text.
Using the equations found here: Math: Ease In, ease Out a displacement using Hermite curve with time constraint we can then set about making some code that will perform a smooth fade/move/scale pitch/volume slide etc, etc. I did this an ended up a few functions that facilitate simple animations. I've included minified versions of them below, for an all-in-one complete solution that relies on no other resources.
<!DOCTYPE html>
<html>
<head>
<script>
"use strict";
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded()
{
document.getElementById('goBtn').addEventListener('click', onButtonClick, false);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// animation stuff
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function cubicHermite(a,b,d,e,c){var g=a*a,f=g*a;return(2*f-3*g+1)*b+(f-2*g+a)*e+(-2*f+3*g)*d+(f-g)*c}
function interp(a,b,d,e,c){var g,f;f=e/(a/2+b+d/2);g=f*a/2;f*=b;return c<=a?cubicHermite(c/a,0,g,0,f/b*a):c<=a+b?g+f*(c-a)/b:cubicHermite((c-a-b)/d,g+f,e,f/b*d,0)}
function linear(a){return a}
function cubic(a){return interp(0.35,0.3,0.35,1,a)}
function doAnimStep(a,b,d,e,c){a<=b?(setTimeout(function(){doAnimStep(a,b,d,e,c)},d),e(a/b),a++):void 0!=c&&null!=c&&c()}
function doAnim3(totalMs, stepCallbackFunc, doneCallbackFunc)
{
var stepDelay = 1000 / 60.0; // set anim to 60 fps
var numSteps = (totalMs / stepDelay)>>0;
setTimeout( doAnim3TimeoutCallback, stepDelay );
function doAnim3TimeoutCallback()
{
doAnimStep(0, numSteps, stepDelay, stepCallbackFunc, doneCallbackFunc);
};
}
function animFadeOut(elem, callback){ doAnim3(500,function(raw){elem.style.opacity=1-cubic(raw)},callback); }
function animFadeIn(elem, callback) { doAnim3(500,function(raw){elem.style.opacity=cubic(raw)},callback); }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
var replacementStrings = [ "replacement 1", "I'm next", "mee too", "fourth item" ];
function onButtonClick(evt)
{
var originalStrings = [];
var targetLiElems = document.querySelectorAll('.menu-wrapper > ul > li');
for (var i=0,n=targetLiElems.length;i<n;i++)
{
var curElem = targetLiElems[i];
originalStrings.push(curElem.innerText);
animFadeOut(curElem, createFunc(i) );
}
function createFunc(i)
{
return function(){ var curElem = targetLiElems[i]; curElem.innerText = replacementStrings[i]; animFadeIn(curElem); };
}
}
</script>
<style>
</style>
</head>
<body>
<button id='goBtn'>Change the text</button>
<div class="menu-wrapper">
<ul>
<li>WORD1</li>
<li>WORD2</li>
<li>WORD3</li>
<li>WORD4</li>
</ul>
</div>
</body>
</html>
I have over 100 videos and I use a function to highlight the links clicked. The code thought is very long and I feel like there must be a way to simplify it into a for loop or something. Any idea?
var vid_all0 = $('#vid_link0, #vidtop_link0, .vidtop_link0, #vidmob_link0, #link0'); //cache selector
vid_all0.click(function () {
$('[id^=vid_link],[id^=vidtop_link],[id^=vidmob_link], .vidtop_link0').css('background-color', 'inherit');
vid_all0.css('background-color', '#A9CDEB'); //change color of all elements
$('.vidtop_link0').css('background-color', 'inherit');
});
var vid_all1 = $('#vid_link1, #vidtop_link1, #vidmob_link1,#link10'); //cache selector
vid_all1.click(function () {
$('[id^=vid_link],[id^=vidtop_link],[id^=vidmob_link]').css('background-color', 'inherit');
vid_all1.css('background-color', '#A9CDEB'); //change color of all elements
});
var vid_all2 = $('#vid_link2, #vidtop_link2, #vidmob_link2,#link19'); //cache selector
vid_all2.click(function () {
$('[id^=vid_link],[id^=vidtop_link],[id^=vidmob_link]').css('background-color', 'inherit');
vid_all2.css('background-color', '#A9CDEB'); //change color of all elements
});
...
it goes up to 15
Give all those elements the same class, then use all elements with that class like
$(".vidtop").on("click", function()
{
// Do something with their CSS
});
If you can't modify your HTML with a class, something like this should work:
for (var i=1; i=99; i==;) {
$(vid_all + i).click(function () {
$(this).find('[id^=vid_link],[id^=vidtop_link],[id^=vidmob_link]').css('background-color', 'inherit');
});
}
I'm not sure if I understood your code correctly, but this would seem to be a simpler version:
function doStuff(links, additional) {
links.click(function() {
$('[id^=vid_link],[id^=vidtop_link],[id^=vidmob_link]' + (additional ? "," + additional : "")).css('background-color', 'inherit');
links.css('background-color', '#A9CDEB');
if (additional) {
additional.css('background-color', 'inherit');
}
});
}
// vid_all0
doStuff($('#vid_link0, #vidtop_link0, .vidtop_link0, #vidmob_link0, #link0'), $('.vidtop_link0'));
// vid_all1
doStuff($('#vid_link1, #vidtop_link1, #vidmob_link1,#link10'));
// vid_all2
doStuff($('#vid_link2, #vidtop_link2, #vidmob_link2,#link19'));
// etc.
I've already posted a question about jQuery toggle method here
But the problem is that even with the migrate plugin it does not work.
I want to write a script that will switch between five classes (0 -> 1 -> 2 -> 3 -> 4 -> 5).
Here is the part of the JS code I use:
$('div.priority#priority'+id).on('click', function() {
$(this).removeClass('priority').addClass('priority-low');
});
$('div.priority-low#priority'+id).on('click' ,function() {
$(this).removeClass('priority-low').addClass('priority-medium');
});
$('div.priority-medium#priority'+id).on('click', function() {
$(this).removeClass('priority-medium').addClass('priority-normal');
});
$('div.priority-normal#priority'+id).on('click', function() {
$(this).removeClass('priority-normal').addClass('priority-high');
});
$('div.priority-high'+id).on('click', function() {
$(this).removeClass('priority-high').addClass('priority-emergency');
});
$('div.priority-emergency'+id).on('click', function() {
$(this).removeClass('priority-emergency').addClass('priority-low');
});
This is not the first version of the code - I already tried some other things, like:
$('div.priority#priority'+id).toggle(function() {
$(this).attr('class', 'priority-low');
}, function() {
$(this).attr('class', 'priority-medium');
}, function() {
...)
But this time it only toggles between the first one and the last one elements.
This is where my project is: strasbourgmeetings.org/todo
The thing is that your code will hook your handlers to the elements with those classes when your code runs. The same handlers remain attached when you change the classes on the elements.
You can use a single handler and then check which class the element has when the click occurs:
$('div#priority'+id).on('click', function() {
var $this = $(this);
if ($this.hasClass('priority')) {
$this.removeClass('priority').addClass('priority-low');
}
else if (this.hasClass('priority-low')) {
$this.removeClass('priority-low').addClass('priority-medium');
}
else /* ...and so on... */
});
You can also do it with a map:
var nextPriorities = {
"priority": "priority-low",
"priority-low": "priority-medium",
//...and so on...
"priority-emergency": "priority"
};
$('div#priority'+id).on('click', function() {
var $this = $(this),
match = /\bpriority(?:-\w+)?\b/.exec(this.className),
current = match && match[0],
next = nextPriorities[current];
if (current) {
$this.removeClass(current).addClass(next || 'priority');
}
});
[edit: working demo]
Assuming you have 'priority' as the default class already on the element at the initialization phase, this will cycle through the others:
$('div#priority' + id)
.data('classes.cycle', [
'priority',
'priority-low',
'priority-medium',
'priority-normal',
'priority-high',
'priority-emergency'
])
.data('classes.current', 0)
.on('click', function () {
var $this = $(this),
cycle = $this.data('classes.cycle'),
current = $this.data('classes.current');
$this
.removeClass(cycle[current % cycle.length])
.data('classes.current', ++current)
.addClass(cycle[current % cycle.length]);
});
I have tried myself to do this with the sole help of toggleClass() and didn't succeeded.
Try my method that declares an array with your five classes and toggles dynamically through
them.Do adapt to your own names.
//variable for the classes array
var classes=["one","two","three","four","five"];
//add a counter data to your divs to have a counter for the array
$('div#priority').data("counter",0);
$(document).on('click','div#priority',function(){
var $this=$(this);
//the current counter that is stored
var count=$this.data("counter");
//remove the previous class if is there
if(($this).hasClass(classes[count-1])){
$(this).removeClass(classes[count-1]));
}
//check if we've reached the end of the array so to restart from the first class.
//Note:remove the comment on return statement if you want to see the default class applied.
if(count===classes.length){
$this.data("counter",0);
//return;//with return the next line is out of reach so class[0] wont be added
}
$(this).addClass(classes[count++]);
//udpate the counter data
$this.data("counter",count);
});
//If you use toggleClass() instead of addClass() you will toggle off your other classes.Hope is a good answer.