nickruggiero
nickruggiero

Reputation: 161

Relating click events and multiple items, without multiple functions

The problem I am having is that I feel my Javascript is not very efficient and that there is a much better way to accomplish what I am trying to do. To show my problem, I have created a smaller version of my project in JSFiddle.

JSFiddle

The ultimate goal is to have the blocks on the left show info in the large grey box when clicked. This is achieved in the JSFiddle! The problem is (I think its a problem) is that I have a separate javascript function for each block. (this is a small scale example, my project has 70+ "blocks")

In each function, all of the previous clicked blocks are hidden and their "clicked" class is removed. (Clicked class is basically a flag that tells the function whether to hide/show the info)

Here is example of one of the functions...is this the correct way to go about this? Remember, my project is this times 70 so I go through and hide each info block every time.

$('.block1').on('click', function (e) {
    if ($(this).hasClass('block1-clicked')) {
       $('.block1-info').fadeOut();
    }
    else {
        $('.block2-info').fadeOut();
        $('.block2').removeClass('block2-clicked');
        $('.block3-info').fadeOut();
        $('.block3').removeClass('block3-clicked');
        $('.block4-info').fadeOut();
        $('.block4').removeClass('block4-clicked');
        $('.block1-info').fadeIn();

    }
   $(this).toggleClass('block1-clicked') ;
});

Thank you!!

Upvotes: 0

Views: 130

Answers (10)

myfunkyside
myfunkyside

Reputation: 3950

Okay I went all out:D

Fiddle: http://jsfiddle.net/3A5M6/


Your code had so much redundancy that I kinda went all out and removed as much as I could everywhere:

HTML

<div id="blocksAndInfo">
    <div class="blockContainer" onmousedown="return false">
        <div class="block" id="b1">Block 1</div>
        <div class="block" id="b2">Block 2</div>
        <div class="block" id="b3">Block 3</div>
        <div class="block" id="b4">Block 4</div>
    </div>

    <div class="infoContainer">
        <div class="info b1">Info about Block 1</div>
        <div class="info b2">Info about Block 2</div>
        <div class="info b3">Info about Block 3</div>
        <div class="info b4">Info about Block 4</div>
    </div>
</div>

JS

$('.block').on('click',function(){
    if ($(this).hasClass('active')) {
        //deactivate the clicked block, and hide its info
        $(this).removeClass('active');
        $('.info.'+$(this).attr('id')).fadeOut();
    } else {
        //first deactivate any possible active block, and hide its info
        $('.block').removeClass('active');
        $('.info').fadeOut();
        //then activate the clicked block, and show its info
        $(this).addClass('active');
        $('.info.'+$(this).attr('id')).fadeIn();
    }
});

CSS

#blocksAndInfo {
    height: 190px;
    margin: 20px 0px 0px 20px;
}

.blockContainer {
    float: left;
    width: 140px;
    height: 100%;
}
.block {
    width: 100%;
    margin: 0px 0px 10px 0px;
    padding: 5px 0px;
    border: 2px solid transparent;
    cursor: pointer;
}
.block:last-child {margin-bottom:0px;}
.block.active {border:2px solid black;}
.block#b1 {background-color:red;}
.block#b2 {background-color:blue;}
.block#b3 {background-color:green;}
.block#b4 {background-color:orange;}

.infoContainer {
    position: relative;
    float: left;
    width: 140px;
    height: 100%;
    margin: 0px 0px 0px 20px;
    padding 5px;
    background-color: grey;
}
.info {
    display: none;
    position: absolute;
    width: 100%;
    height: 100%;
}

The CSS in the jsFiddle contains some more code though, some extra styling touches.

Upvotes: 0

CodingWithSpike
CodingWithSpike

Reputation: 43748

(This is my 2nd answer, and I think is more robust than my 1st one...)

This solution doesn't need any special numbered classes or IDs, so it is easy to just insert 70 sections without having to specify a class on every one.

Here is a working jsFiddle of what I think is a cleaner solution. It only uses 1 click handler for all the 'buttons' on the left, and uses CSS3 transitions to fade out/in the info boxes. It also ditches the data-target and boxN-info attributes and classes, and instead just uses the indexes, so it assumes the buttons and info blocks have the same number and are in the same order:

http://jsfiddle.net/5dQUk/11/

Html:

<ul class="block-buttons">
  <li class="active">Block 1</div>
  <li>Block 2</div>
  <li>Block 3</div>
  <li>Block 4</div>
</ul>

<div class="info-blocks">
  <div class="active">Info about Block 1</div>
  <div>Info about Block 2</div>
  <div>Info about Block 3</div>
  <div>Info about Block 4</div>
</div>

CSS

.block-buttons {
    list-style: none;
    margin: 0;
    padding: 0;
    float: left;
}

.block-buttons li {
    border: 1px solid #777;
    background-color: #eee;
    cursor: pointer;
    margin-bottom: 2px;
    padding: 2px;
}

.block-buttons li.active {
    background-color: #aaa;    
}

.info-blocks {
    position: relative;
    min-width: 100px;
    min-height: 100px;
    border: 1px solid #777;
    margin-left: 10px;
    float: left;
}

.info-blocks > div {
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    opacity: 0;
    transition: opacity 0.4s ease-in 0s;
    -ms-transition: opacity 0.4s ease-in 0s;
    -moz-transition: opacity 0.4s ease-in 0s;
    -webkit-transition: opacity 0.4s ease-in 0s;
}

.info-blocks > div.active {
    opacity: 1;
    transition: opacity 0.4s ease-out 0.4s;
    -ms-transition: opacity 0.4s ease-out 0.4s;
    -moz-transition: opacity 0.4s ease-out 0.4s;
    -webkit-transition: opacity 0.4s ease-out 0.4s;
}

JS:

$(".block-buttons").on("click", "li", function (clickEvent) {
    var $clickedElement = $(clickEvent.target);
    var index = $clickedElement.index();

    // set class on selected button
    $(".block-buttons > li").removeClass("active");
    $clickedElement.addClass("active");

    // set class on selected panel
    $(".info-blocks > div")
        .removeClass("active")
        .eq(index)
        .addClass("active");
});

Upvotes: 0

CodingWithSpike
CodingWithSpike

Reputation: 43748

One possibility is to wrap all the buttons and info blocks into wrapper elements, and use those to add a single click handler, and then hide all the child info block elements and show just the 1 you want.

I also used a data-* attribute here to specify what info block each button should show.

<div id="block-buttons">
  <div class="block1" data-target="block1-info">Block 1</div>
  <div class="block2" data-target="block2-info">Block 2</div>
  <div class="block3" data-target="block3-info">Block 3</div>
  <div class="block4" data-target="block4-info">Block 4</div>
</div>


<div class="info-blocks">
  <div class="block1-info">Info about Block 1</div>
  <div class="block2-info">Info about Block 2</div>
  <div class="block3-info">Info about Block 3</div>
  <div class="block4-info">Info about Block 4</div>
</div>

Javascript:

$("#block-buttons").on("click", "div", function (clickEvent) {
    var target = $(clickEvent.target).data("target");
    $(".info-blocks > div").fadeOut();
    $("." + target).fadeIn();
});

This could be cleaned up a lot more if you didn't have CSS styling per-element. If all the buttons were the same color you could eliminate all the block1 though block4 classes completely.

Also if you don't need to support IE9 and older, you should use a CSS transition instead of a jQuery fadein/out, because the CSS transitions can be hardware rendered by the browser.

Upvotes: 2

Jay Blanchard
Jay Blanchard

Reputation: 34426

For things like this we tend to make heavy use of custom data attributes (http://html5doctor.com/html5-custom-data-attributes/) -

<div class="selector" data-block="d1">Block 1</div>
<div class="selector" data-block="d2">Block 2</div>
<div class="selector" data-block="d3">Block 3</div>
<div class="selector" data-block="d4">Block 4</div>


<div class="info active"></div>
<div class="info" data-info="d1">Info about Block 1</div>
<div class="info" data-info="d2">Info about Block 2</div>
<div class="info" data-info="d3">Info about Block 3</div>
<div class="info" data-info="d4">Info about Block 4</div>

Which we can then use to dramatically shorten the code -

$('.selector').click(function() {
    var currentSelection = $(this).data('block');
    $('.info').removeClass('active');
    $('.info[data-info=' + currentSelection + ']').addClass('active');    
});

I find this to be far less elegant than what Moby's Stunt Double does with his though. He doesn't have to have extra lines of markup for each block.

Upvotes: 1

Tanvir
Tanvir

Reputation: 1695

Hope that it will be helpful to you.. fiddle

html

<div class="block1 block" value="Info about Block 1">Block 1</div>
<div class="block2 block" value="Info about Block 2">Block 2</div>
<div class="block3 block" value="Info about Block 3">Block 3</div>
<div class="block4 block" value="Info about Block 4">Block 4</div>


<div class="info"></div>

js

$(".block").click(function() {
    var value = $(this).attr('value');
    $(".info").html(value);
});

Upvotes: -1

Moby&#39;s Stunt Double
Moby&#39;s Stunt Double

Reputation: 2550

I've boiled everything down, removing the animation, to show you the basics of how to structure it and created a new fiddle for you here: http://jsfiddle.net/7E3ch/

Markup:

<div id="blocks">
    <div id="block1" class="block" data-info="Info about Block 1">Block 1</div>
    <div id="block2" class="block" data-info="Info about Block 2">Block 2</div>
    <div id="block3" class="block" data-info="Info about Block 3">Block 3</div>
    <div id="block4" class="block" data-info="Info about Block 4">Block 4</div>
</div>
<div id="info"></div>

Javascript:

$(".block").on("click", function (e) {
    $("#info span").hide();
    $("#info").append("<span>" + $(this).data("info") + "</span>");
});

CSS:

#blocks{
    float:left;
}

.block {
    margin-left:20px;
    margin-top:10px;
    height:40px;
    width:140px;
    display:block;
}

#block1 {
    background-color:red;
}

#block2 {
    background-color:blue;
}

#block3 {
    background-color:green;
}

#block4 {
    background-color:orange;
}

#info {
    margin-left:180px;
    margin-top:10px;
    height:190px;
    width:140px;
    display:block;
    position:absolute;
    background-color:grey;
}

The following things are the big items:

  • Use generic classes on elements, because you can peg reusable animations and styles against them.
  • Look into the acronym: DRY (Don't Repeat Yourself)
  • Use data attributes to your advantage.

I hope this helps. Good luck with your app!

Upvotes: 2

charlietfl
charlietfl

Reputation: 171698

Here's a simple approach using common classes and indexing:

HTML:

<div class="tab block1">Block 1</div>
<div class="tab block2">Block 2</div>

<div class="content block1-info">Info about Block 1</div>
<div class="content block2-info">Info about Block 2</div>

JS:

var $tabs=$('.tab'), $content=$('.content').hide();
$tabs.click(function(){
   var index=$tabs.removeClass('clicked').index(this);
    $(this).addClass('clicked')
    $content.filter(':visible').fadeOut()
    $content.eq(index).fadeIn();
});

DEMO

Upvotes: 0

user2600677
user2600677

Reputation:

Instead of having multiple divs within the gray div, and checking whether a certain one is clicked or not, and then removing the clicked class, you could just use jQuery's .html() or .text() to set up the corresponding text within your gray div. Read about them more here:

http://api.jquery.com/html/

http://api.jquery.com/text/

Upvotes: 0

NoDataDumpNoContribution
NoDataDumpNoContribution

Reputation: 10864

Only one block has to be hidden at most (the one that was active) and only one block has to be shown whenever a change occurs. Therefore you just have to store which block was active before and then fade this one out and remove the class of it before fading the new block in.

Upvotes: 0

Diodeus - James MacFarlane
Diodeus - James MacFarlane

Reputation: 114437

There are a few ways to accomplish this. If you use the following pattern you can use the ID of the clicked unit to set the state of the related DIV. By using two classes you can use one to clear all of the items, and one to set an individual item

<div id="block1" class="block">Block 1</div>
. . .

<div class="info"></div>
<div class="blockinfo block1">Info about Block 1</div>
... etc

JS:

$('.block').on('click', function() {
     var myId = $(this).attr('id');
     $('.block').removeClass('clicked');
     $(this).addClass('clicked');
     $('.blockinfo').fadeout();
     $('.' + myId).fadeIn();
})

Upvotes: 5

Related Questions