3

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!

5 Answers 5

2

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()
           }
      );
});
Sign up to request clarification or add additional context in comments.

4 Comments

Thank you very much for the helpful answer - it's the looping through the data that I needed. However while this works perfectly for reverting everything to the original, how can I get it to replace each <li> item with the correct text? For example the second item would be 'replacement word 2' and the third 'replacement word 3' etc. Thanks again.
You could use data- attributes in the html...then use data() method to read those also <li data-alt_text="alternate word">. Or add them to elements from array within the first loop. each has index you can use
Thank you - unfortunately it isn't possible for me to add the alternative words to the HTML anywhere as the menu is generated by the ecommerce system I'm using. I was hoping for a 'pure' Javascript solution if possible? Appreciate it might not be!
Need to tell us where the data comes from then. I also gave you an array option. If it is related to existing text in anyway and can be parsed from that. You haven't defined any source for it
2

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

4 Comments

This works well and I actually like the way it changes the individual <li> items. Unfortunately it has the same problem as the above answer in that it always replaces with the same text whereas I need to specify the text for each replacement. It isn't possible for me to add the alternative words to the HTML as the menu is generated by the ecommerce system I'm using and without delving into the deep dark depths of those files it isn't really possible.
What options do you have to identify which text you need? An index based array, or replacement texts based on the text of the li elements?
The order is fixed and the text of the items isn't going to change (if it does I can update the Javascript) so I think either option would work. Many thanks.
Yes perfect! Thank you
0

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.

2 Comments

can't use one variable for multiple elements, will get all set the same. Won't work
Yeah, sorry. It's better if we assign that variable to the object itself using this.originalText. Fixed.
0

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

Comments

0

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>

1 Comment

I voted this up as it's helpful to provide alternatives - as soon as I have enough rep the vote will show. Thanks for the answer!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.