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 want to save an html element inside a variable and than append that element with childelements. I can append that element with the usual jquery selector but not with the variable. Why?
Here is my snippet:
var elements, UserTableWidget = {
elements: {
pagination: $("#pageNumbers")
},
init: function() {
elements = this.elements;
elements.pagination.append("<li>test</li>") // works not
$("#pageNumbers").append("<li>test</li>") // works
},
}
Why is everything so complicated? I simplified some of it.
$(function(){
var UserTableWidget = {
elements: {
pagination: $("#pageNumbers")
},
init: function() {
this.elements.pagination.append("<li>test</li>");
},
}
UserTableWidget.init()
// same as: UserTableWidget.elements.pagination.append("<li>test</li>");
});
As mentioned in the comments, this.elements.pagination is initialized to ("#pageNumbers") probably before the dom is ready. When you call init() later, it uses the value calculated before (empty jQuery collection), so nothing is appended.
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.
I've seen other similar questions to this on SO but the answers weren't quite what I'm looking for. My problem with the code below is about the variable hide.
In it's current form hide won't be visible to the first hover function, but I don't want to declare it at a higher scope because it has no use there. Plus declaring it at a higher scope would require making a different variable for every li.
What's the solution for keeping this variable containing a timeout between these two functions?
$('li').hover(function() {
clearTimeout(hide);
$('.menu', this).show();
}, function() {
var menu = $('.menu', this);
var hide = setTimeout(function() {
menu.hide();
}, 500);
});
You can store the value with .data()
$('li').hover(function() {
clearTimeout($(this).data('hide'));
$('.menu', this).show();
}, function() {
var menu = $('.menu', this);
$(this).data('hide', setTimeout(function() {
menu.hide();
}, 500));
});
I have a function which loops through rows in a table so that only one is shown at any given time.
I want to expand on this so that when I hover over the table, it shows all the rows, and then when I move away, it resumes showing one row at a time.
The Problem I have is that on hovering, the first function keeps going, is there a way to 'pause' the function. I've looked at various examples using ClearInterval(),but can't match them to my script.
//Calling The function that loops through the rows
function hideShow(time)
{
setInterval('showRows()',time);
};
//Set the time between each 'loop' and start looping
$(document).ready(function()
{
hideShow(2000);
}
);
//The hover function to show / hide all the rows
$(document).ready(function()
{
$('#dbTable1 tr').hover(function()
{
$('.Group td').removeClass('RoundBottom');
$('.Group').show();
},
function()
{
$('.Group td').addClass('RoundBottom');
$('.Group').hide();
}
);
}
);
Can anyone show me please how I can combine the two?
You need to keep track of the timer ID when you call setInterval:
var timerID;
function hideShow(time){
timerID = setInterval(showRows, time);
}
Then later on when you want to stop the repetition, call clearInterval and pass in that ID:
// ...
$('.Group td').removeClass('RoundBottom');
$('.Group').show();
clearInterval(timerID);
},
function()
{
hideShow(2000);
$('.Group td').addClass('RoundBottom');
// ...
You could just check the hovering state before doing anything else, like this:
function showRows() {
if (isHovering) {
return;
}
// ...
}
The isHovering variable is just a boolean with current hovering state, that could be set by your callback function.
With the above approach, you can set your timer only once and forget about it.