I am hiding and fading in different content on the same page using jQuery to hide()
and fadeIn()
the content depending on which link is clicked.
It works how I want it to, but the way I've written the jQuery looks like it could be simplified.
$("#navItem1").on('click', function(){
$('#content-wrap1').hide();
$('#content-wrap2').hide();
$('#content-wrap3').hide();
$('#content-wrap4').hide();
$('#content-wrap5').hide();
$('#content-wrap1').fadeIn(1000);
});
$("#navItem2").on('click', function(){
$('#content-wrap1').hide();
$('#content-wrap2').hide();
$('#content-wrap3').hide();
$('#content-wrap4').hide();
$('#content-wrap5').hide();
$('#content-wrap2').fadeIn(1000);
});
$("#navItem3").on('click', function(){
$('#content-wrap1').hide();
$('#content-wrap2').hide();
$('#content-wrap3').hide();
$('#content-wrap4').hide();
$('#content-wrap5').hide();
$('#content-wrap3').fadeIn(1000);
});
$("#navItem4").on('click', function(){
$('#content-wrap1').hide();
$('#content-wrap2').hide();
$('#content-wrap3').hide();
$('#content-wrap4').hide();
$('#content-wrap5').hide();
$('#content-wrap4').fadeIn(1000);
});
$("#navItem5").on('click', function(){
$('#content-wrap1').hide();
$('#content-wrap2').hide();
$('#content-wrap3').hide();
$('#content-wrap4').hide();
$('#content-wrap5').hide();
$('#content-wrap5').fadeIn(1000);
});
function fadeInFirstContent(){
$('#content-wrap1').fadeIn(1000);
}
fadeInFirstContent();
How can I rewrite the jQuery so it does exactly the same thing but in much less code?
9 Answers 9
Perhaps change the HTML:
Navitem:
<span class="navItem" data-content-id="1"></span>
The content
<div id="content-wrap1" class="content-wrap"></div>
jQuery
$('.navItem').on('click',function(){
$('.content-wrap').hide();
$('#content-wrap'+$(this).data('content-id')).fadeIn(1000);
});
-
1\$\begingroup\$ This is much better than using a substring of the ID. Also, you’re missing a
.
in front ofnavItem
... \$\endgroup\$Ry-– Ry-2013年05月16日 20:03:06 +00:00Commented May 16, 2013 at 20:03 -
\$\begingroup\$ This is better than the accepted answer, though I feel it would be cleaner with data attributes on the content-wrap elements instead of an ID. A selector like
.content-wrap[data-wrapid=VAR]
wouldn't be noticeably slower. Alternatively, simply useeq()
to select the nth element. \$\endgroup\$DisgruntledGoat– DisgruntledGoat2013年05月16日 22:51:20 +00:00Commented May 16, 2013 at 22:51
You don't have to change the HTML markup at all. You can make use of the starts-with CSS selector (^=
):
// All elements with an ID starting with navItem...
$('[id^="navItem"]').on('click', function() {
var
// Get the ID as a string
id = this.id,
// Get the last character from the ID
num = id.charAt(id.length-1)
;
// Hide all elements with an ID starting with content-wrap...
$('[id^="content-wrap"]').hide();
// Fade in the relevant ID
$('#content-wrap'+num).fadeIn(1000);
});
If we assume "navItem5" is used, here is what the variables would contain:
id == "navItem5"
num == "5"
$('#content-wrap'+num) == "#content-wrap5"
This is very basic jQuery. You'd benefit from going through the official jQuery interactive tutorial: http://try.jquery.com.
-
2\$\begingroup\$ Why not just do:
var id = this.id;
? No need for$(this).attr('id')
... It's also massively faster. \$\endgroup\$RobH– RobH2013年05月16日 12:59:16 +00:00Commented May 16, 2013 at 12:59 -
2\$\begingroup\$ That would be a simple oversight, I've modified the answer. :) \$\endgroup\$James Donnelly– James Donnelly2013年05月16日 13:01:28 +00:00Commented May 16, 2013 at 13:01
-
\$\begingroup\$ Possibly include
.toggle()
if OP is going to use the same click to show/fadeout. \$\endgroup\$Jonny Sooter– Jonny Sooter2013年05月16日 16:15:46 +00:00Commented May 16, 2013 at 16:15 -
2\$\begingroup\$ While this answer works, it's very hacky to extract numbers out of ID attributes. \$\endgroup\$DisgruntledGoat– DisgruntledGoat2013年05月16日 22:43:53 +00:00Commented May 16, 2013 at 22:43
-
2\$\begingroup\$ I’ll comment on my downvote from a few hours ago: this is incredibly fragile, and wanton abuse of IDs. What if the numbers go past
9
, for example? Why not just use a clean and very efficient class? Why don't you cache that collection, or put it in an array, or something nice? \$\endgroup\$Ry-– Ry-2013年05月16日 23:37:36 +00:00Commented May 16, 2013 at 23:37
I think this should do the trick
for (var j = 1; j <= 5; j++) {
(function(i){
$("#navItem" + i).on('click', function(){
$('#content-wrap1').hide();
$('#content-wrap2').hide();
$('#content-wrap3').hide();
$('#content-wrap4').hide();
$('#content-wrap5').hide();
$('#content-wrap' + i).fadeIn(1000);
});
})(j);
}
-
\$\begingroup\$ Hi, could you explain what is happening in that for loop? \$\endgroup\$crmepham– crmepham2013年05月16日 12:37:36 +00:00Commented May 16, 2013 at 12:37
-
\$\begingroup\$ @crm: inside the loop I'm defining a function that gets a number as parameter. Inmediatly after defining the function, I'm calling it trough this statement
(j)
. This is a closure. \$\endgroup\$Claudio Redi– Claudio Redi2013年05月16日 12:41:31 +00:00Commented May 16, 2013 at 12:41 -
\$\begingroup\$ How does
i
hold the value? Shouldn't you be callingj
within the loop? also why isj
in the parenthesis at the end like that? \$\endgroup\$crmepham– crmepham2013年05月16日 12:44:49 +00:00Commented May 16, 2013 at 12:44 -
2\$\begingroup\$ @crm: it's the magic of closures :) Here you have a SO popular question that may clarify this topic stackoverflow.com/questions/111102/….
j
in the parenthesis at the end is the way to call the function I just defined with parameterj
\$\endgroup\$Claudio Redi– Claudio Redi2013年05月16日 12:54:28 +00:00Commented May 16, 2013 at 12:54
yes there is...there are many ways to simplify this.. i am doing it by using data
attribute to your <a>
tag...and adding same class to all the contents <div>
.(i am using content here)...
HTML
<nav>
<!-- Start of side menu -->
<ul id="mainNav">
<li class="mainNavItem"><a href="#" id="navItem1" data-content="content-wrap1">Home</a>
</li>
<li class="mainNavItem"><a href="#" id="navItem2" data-content="content-wrap2">Prices</a>
</li>
<li class="mainNavItem"><a href="#" id="navItem3" data-content="content-wrap3">Find us</a>
</li>
<li class="mainNavItem"><a href="#" id="navItem4" data-content="content-wrap4">Reviews</a>
</li>
<li class="mainNavItem"><a href="#" id="navItem5" data-content="content-wrap5">Gallery</a>
</li>
</ul>
</nav>
<!-- closes mainNav -->
<div id="content-wrap1" class="content">Home</div>
<div id="content-wrap2" class="content">Prices contents</div>
<div id="content-wrap3" class="content">find us contents</div>
<div id="content-wrap4" class="content">review contents</div>
<div id="content-wrap5" class="content">gallery contents</div>
jquery
$('ul#mainNav li a').click(function () {
$('.content').hide();
$('#' + $(this).data('content')).fadeIn(1000);
});
function fadeInFirstContent() {
$('#content-wrap1').fadeIn(1000);
}
fadeInFirstContent();
or without adding classes to content..
$(function(){
$('ul#mainNav li a').click(function () {
$('div[id^="content-wrap"]').hide();
$('#' + $(this).data('content')).fadeIn(1000);
});
$('#content-wrap1').fadeIn(1000);
});
It could be done much shorter: http://jsfiddle.net/2ADyp/
function hideAll(){
$('#content-wrap1').hide();
$('#content-wrap2').hide();
$('#content-wrap3').hide();
$('#content-wrap4').hide();
$('#content-wrap5').hide();
}
function showSelected(e){
hideAll();
$(e.target.hash).fadeIn(100);
}
$("#mainNav").find("a").each(function(i,o){
$(o).click(showSelected);
});
-
1\$\begingroup\$
hideAll()
can be minimized to$('#content-wrap1,#content-wrap2,#content-wrap3,#content-wrap4,#content-wrap5').hide()
\$\endgroup\$Muhammad Omer Aslam– Muhammad Omer Aslam2019年04月26日 18:13:11 +00:00Commented Apr 26, 2019 at 18:13
Assuming you're using an expressive structure such as:
<nav>
<a href="#content-wrap1" id="navItem1" class="navItem">One</a>
<a href="#content-wrap2" id="navItem2" class="navItem">Two</a>
<a href="#content-wrap3" id="navItem3" class="navItem">Three</a>
</nav>
<div id="content-wrap1" class="content-wrap">...</div>
<div id="content-wrap2" class="content-wrap">...</div>
<div id="content-wrap3" class="content-wrap">...</div>
You can then toggle the display of the content-wrap
items in a short, expressive manner.
$('.navItem').on('click', function (e) {
$('.content-wrap').hide();
$($(this).prop('hash')).fadeIn(1000);
e.preventDefault();
});
This does suggest changing the HTML, but only in ways that make the content semantic.
-
\$\begingroup\$ You should make use of the
data-*
attribute to store data, not thehref
attribute (<a data-id="#content-wrap1" .../>
then you can pull it using$(this).data('id')
. \$\endgroup\$James Donnelly– James Donnelly2013年05月16日 16:45:03 +00:00Commented May 16, 2013 at 16:45 -
1\$\begingroup\$ @JamesDonnelly, the
href
is more appropriate if the item being clicked on is a link. The fragment identifier is designed to point the user to a particular ID in the DOM, so even if JavaScript is disabled, the same section will be highlighted. If the item is not a link, it certainly makes sense to use a[data-*]
attribute, although I'd probably usedata-target
ordata-content-element
becausedata-id
seems a little too ambiguous. \$\endgroup\$zzzzBov– zzzzBov2013年05月16日 16:53:59 +00:00Commented May 16, 2013 at 16:53 -
\$\begingroup\$ if JavaScript was disabled the element wouldn't be visible anyway, so it wouldn't matter in this case. \$\endgroup\$James Donnelly– James Donnelly2013年05月16日 17:58:22 +00:00Commented May 16, 2013 at 17:58
-
1\$\begingroup\$ @JamesDonnelly, "the element wouldn't be visible anyway" no, if you set up your styles and scripts correctly, all the elements will be shown with javascript disabled. While it may not be what OP has set up, you have to remember that this is Code Review. \$\endgroup\$zzzzBov– zzzzBov2013年05月16日 18:15:55 +00:00Commented May 16, 2013 at 18:15
Here's one with minimal changes to the HTML (only a common container for the contents):
$( '.mainNavItem a' ).on( 'click', function() {
$( '#content > div' ).hide() // hide all content divs
// (immediate div children of the main container)
.eq( $( this ).parent().index() ) // match the index of the clicked link
// with the index of the corresponding content
.fadeIn( 1000 ); // display that div
});
why not skip the javascript completely?
EDIT: sorry that's completely unhelpful seeing as this is a javascript question. Here's a solution with some modifications to the HTML:
$('.link').click(function(e) {
e.preventDefault();
var $con = $('#main-content'),
tar = $(this).data('show');
$con.find('.contents').fadeOut();
$con.find(tar).fadeIn();
});
-
\$\begingroup\$ It is ok to post a working demo on JSFiddle. Nevertheless, add the relevant code to your answer. \$\endgroup\$nibra– nibra2013年05月18日 10:44:04 +00:00Commented May 18, 2013 at 10:44
I've noted some details, let me explain:
CSS Selectors. jQuery allows you to use them too:
$("[id^='content-wrap']")
--> will select all elements which id starts with 'content-wrap'
- Substring is an available method of JavaScript:
var number = "navItem15".substring(7);
// will return what is after position 7..
// resulting the number "15"
- To start your menu with the first position. This is the best way:
(function(){
$("#navItem1").trigger("click");
})();
// using this approach, you won't need copy your code into a function as you did
// this approach triggers the "click" event in the first menu automatically
Well, hope you got it!
Nice coding!
[id]
s. It makes the HTML more expressive of the fact that these elements obviously share a similar class. \$\endgroup\$