Angular scope and ng-click / ng-show to set multiple divs
Asked Answered
B

3

5

I am looking for some help with my code I have so far.

The main objective is to be able to click on any Plus icon and have it place a cover over all other div blocks.

And when a plus icon is clicked it will also show a div block to the right.

As you will see when block 2 is clicked it does all that is intended.

I am looking for an efficient way to do this with Angular when any plus icon is clicked.

This is just a small sample I show here, in reality there would be 10 to 20 blocks to cover.

If someone could see a way to use less code here and make better use of the scope, this would be greatly appreciated.

I have looked at many options like in this post here.

Tried this but it doesn't want to work...

data-ng-class="{coverThisBlock: value2 == 'off',coverThisBlock: value == 'on'}"

If I had to use this type of option with even say 10 blocks, it would be a real mess.

The main Questions

Is there a better Angular way for this to work... when any plus icon is clicked it changes scope to then be used by ngclass and ng-show?

How to correctly wire up scope for this example?

Many Thanks.

I have set up a working FIDDLE HERE.

enter image description here

HERE IS THE FINAL WORKING EXAMPLE by Avijit Gupta.

enter image description here

<div class="container" ng-app="plusMinusApp"  ng-controller="plusMinusController">

<div class="row" ng-init="value1 = 'off'">
 <!--<div class="col-xs-4" data-ng-class="{coverThisBlock: value2 == 'off',coverThisBlock: value == 'on'}"> --> 
    <div class="col-sm-4 col-xs-6" data-ng-class="{coverThisBlock: value2 == 'off'}">    
        <div class="divClass" 
        data-ng-click="(selectBlock(1)) ; (status1 = !status1) ; (value1 = { 'on': 'off', 'off':'on'}[value1])" 
        data-ng-class="{'active-selection': status1 == activeClass}">
        1
        </div>
        <i ng-click="(selectBlock(1)) ; (status1 = !status1) ; (value1 = { 'on': 'off', 'off':'on'}[value1])" 
        class="btn btn-primary text-center fa" 
        ng-class="{'fa-minus': status1, 'fa-plus': !status1}"></i>
    </div>
    <div  ng-show="value1 == 'on'" class="col-xs-4 textdiv">Hello</div>
</div>

<div class="row" >
    <div class="col-sm-4 col-xs-6" ng-init="value2 = 'on'">    
        <div class="divClass" 
        data-ng-click="(value2 = { 'on': 'off', 'off':'on'}[value2])" 
        data-ng-class="{'active-selection': value2 == 'off'}">
        2
        </div>
        <i ng-click="(value2 = { 'on': 'off', 'off':'on'}[value2])" 
        class="btn btn-primary text-center fa" 
        ng-class="{'fa-minus': (value2 == 'off'), 'fa-plus': value2}"></i>
    </div>
    <div  ng-show="value2 == 'off'" class="col-xs-3 textdiv">Hello</div>
</div>

<div class="row">  
    <div class="col-sm-4 col-xs-6" data-ng-class="{'coverThisBlock': value2 == 'off'}">    
        <div class="divClass" 
        data-ng-click="(selectBlock(3)) ; (status3 = !status3)" 
        data-ng-class="{'active-selection': !status3 == activeClass}">
        3
        </div>
        <i ng-click="(selectBlock(3)) ; (status3 = !status3)" 
        class="btn btn-primary text-center fa" 
        ng-class="{'fa-minus': status3, 'fa-plus': !status3}"></i>
    </div>
</div>

<div class="row"> 
    <div class="col-sm-4 col-xs-6" data-ng-class="{'coverThisBlock': value2 == 'off'}">    
        <div class="divClass" 
        data-ng-click="(selectBlock(1)) ; (status4 = !status4)" 
        data-ng-class="{'active-selection': status4 == activeClass}">
        4
        </div>
        <i ng-click="(selectBlock(1)) ; (status4 = !status4)" 
        class="btn btn-primary text-center fa" 
        ng-class="{'fa-minus': status4, 'fa-plus': !status4}"></i>
    </div>
    <div  ng-show="status4" class="col-xs-4 textdiv">Hello</div>   
</div>

<div class="row" ng-init="value = 'off'">
    <div class="col-sm-4 col-xs-6" data-ng-class="{'coverThisBlock': value2 == 'off'}">    
        <div class="divClass" 
        data-ng-click="(selectBlock(1)) ; (status = !status) ; (value = { 'on': 'off', 'off':'on'}[value])" 
        data-ng-class="{'active-selection': status == activeClass}">
        5
        </div>
        <i ng-click="(selectBlock(1)) ; (status = !status) ; (value = { 'on': 'off', 'off':'on'}[value])" 
        class="btn btn-primary text-center fa" 
        ng-class="{'fa-minus': status, 'fa-plus': !status}"></i>
    </div>
    <div  ng-show="value == 'on'" class="col-xs-4 textdiv">Hello</div>
</div>
</div>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="js/plusMinusApp.j"></script>
<script>

var myModule = angular.module('plusMinusApp', []);
myModule.controller('plusMinusController', function ($scope) {

    $scope.status  = false;    
    $scope.status1 = false;
    $scope.status2 = false;
    $scope.status3 = false;
    $scope.status4 = false;    

$scope.blocks = [{
    id: '1',
    block: "1",
  }, {
    id: '2',
    block: "2",
  }, {
    id: '3',
    block: "3",
  }, {
    id: '4',
    block: "4",
  }, {
    id: '5',
    block: "5"
  }];
 // $scope.activeClass = 0;  
  $scope.selectBlock = function(id) {
    $scope.activeClass = id;
    console.log(id);
  };  
});    
</script>  

TO ANSWER THE QUESTION TO DO WITH NG-REPEAT

Can ng-repeat use multiple css classes for each different div

Apparently it can.

By using the scope id like this...

<div class="block-{{block.id}}">

and the css like this...

.block-1 {...

WORKING FIDDLE OF THIS HERE

Bouley answered 30/12, 2015 at 4:54 Comment(6)
I haven't really had time to evaluate your code thoroughly, but I think your best approach is the reverse of how you are attempting it here. One idea could be to have a Boolean for each block that says if the block is hidden or not, and a function that you pass the value that you don't want hidden to, which would then loop through your object tree and set all the Booleans.Subtenant
Hello Claies. Could you possibly fork my fiddle and demo what you suggest here please. I'm still very much learning Angular here. One thing to keep in mind here is this needs 3 states. 1 when nothing is selected. 1 that is active... ON. and 1 when all but 1 is blacked out... OFF . Thanks.Bouley
Just to confirm, if there is already an "active" one and another is selected, is the expected behavior to close the currently active one and select only the new one? In other words, is there only at most one active block at any one time, or can there be multiple selected blocks?Rune
Hello jasonszhao. Yes, this is what would be expected functionality wise. Only one can open at a time. Thanks.Bouley
@Bouley Updated my answer based on your comment.Salve
Hello again Avijit. Nice how doing that even reduces the code. About the may not be able to use ng-repeat... The design layout is that each div block is different. And no DB is used. Is it actually possible to use ng-repeat and have different css and images src for each block? I will take a look and review your code now too. Many thanks.Bouley
S
4

EDIT (Based on asker's comment):

UPDATED FIDDLE

  1. only one can be clicked at a time? Or if another is open/clicked the first opened is reset and closed

This simplifies your code by almost 2x.

Initial State:

  • None of the blocks are selected.
  • None of the blocks are covered.

Code:

$scope.setToInitialState = function() {
  $scope.blocks.forEach(function(block) {
    $scope.isSelected[block.id] = false;
    $scope.isCovered[block.id] =  false;
  });
};

Touched State:

  • Toggle the selected state of the clicked block. (toggle between select and deselect).
  • If selected, then cover and deselect all the other blocks.
  • If deselected, then bring the app to the initial state.

Code:

$scope.selectBlock = function(id) {
  $scope.isSelected[id] = !$scope.isSelected[id];
  $scope.isCovered[id] = false;
  if ($scope.isSelected[id]) {
    $scope.blocks.forEach(function(block) {
      if (block.id !== id) {
        $scope.isCovered[block.id] = true;
        $scope.isSelected[block.id] = false;
      }    
    });
  }
  else {
    $scope.setToInitialState();
  }
};
  1. The demo uses all the same set block sizes, but the actual use all div Blocks are different heights and widths. And each block is a different image

You should consider using ng-src.

I am assuming that you might be retreiving all this content from the DB. Then, if possible, you may place each of your image inside a div of fixed size, so that they all come out to be of the same size.

  1. Plus the display is divided into 2 vertical half screen sections

That can be set right tweaking a little bit of css.


ORIGINAL ANSWER:

WORKING FIDDLE

Let's say your app has 2 states:

  1. Initial state (untouched)
  2. Touched state

Initially, you are in the Initial state:

  • No right divs are shown.
  • All the icons are 'plus' (no 'minus')
  • None of the blocks are covered.

Code:

$scope.setToInitialState = function() {
  $scope.plusCount = 0;
  $scope.blocks.forEach(function(block) {
    $scope.isPlus[block.id] = true;
    $scope.isShowDiv[block.id] = false;
    $scope.isCoverBlock[block.id] = false;
    $scope.plusCount += 1;
  });
};

When you are in the Touched state:

  • All those blocks are covered, which have a 'plus' icon.
  • The right divs of only those blocks are shown, which are not covered.

Code:

// Run when user clicks on the 'plus' or 'minus' icon.
$scope.selectBlock = function(id) {
  $scope.isPlus[id] = !$scope.isPlus[id]; // toggle between 'plus' and 'minus' icons
  if ($scope.isPlus[id]) {
    $scope.plusCount += 1;
  }
  else {
    $scope.plusCount -= 1;
  }
  $scope.blocks.forEach(function(block) {

    if ($scope.isPlus[block.id]) {
      $scope.isCoverBlock[block.id] = true;
    }
    else {    
      $scope.isCoverBlock[block.id] = false;
    }
    $scope.isShowDiv[block.id] = !$scope.isCoverBlock[block.id];

  });
};

So, basically when the user interacts with the view and actually clicks on the icon, then he/she goes to the touched state (the above code is run).

Only when all the icons are 'plus', then the user must be sent to the initial state:

if ($scope.plusCount === $scope.blocks.length) {
  $scope.setToInitialState();
}

Changes in html:

  1. Add ng-init="setToInitialState()" to the outermost div, so that we are in the initial state initially.

Code:

<div class="container" ng-app="plusMinusApp"  ng-controller="plusMinusController" ng-init="setToInitialState()">
  1. Use ng-repeat instead of copy-pasting code for each block:

Code:

<div class="row" ng-repeat="block in blocks">
    <div class="col-sm-4 col-xs-6" data-ng-class="{ 'coverThisBlock': isCoverBlock[block.id]}">    
        <div class="divClass" 
        data-ng-class="{'active-selection': !isPlus[block.id]}">
        {{block.id}}
        </div>
        <i data-ng-click="selectBlock(block.id)" 
        class="btn btn-primary text-center fa" 
        data-ng-class="{'fa-minus': !isPlus[block.id], 'fa-plus': isPlus[block.id]}"></i>
    </div>
    <div data-ng-show="isShowDiv[block.id]" class="col-xs-3 textdiv">Hello</div>
</div>

Hope it helps you!

Salve answered 1/1, 2016 at 14:49 Comment(9)
Hello Avijit Gupta. Thanks for your suggestion here. This is very well laid out for how you have done this. With using 2 states are you able to tweak this so that only one can be clicked at a time? Or if another is open/clicked the first opened is reset and closed. Is this possible? The demo uses all the same set block sizes, but the actual use all div Blocks are different heights and widths. And each block is a different image. So not sure if ng-repeat will be an option. Plus the display is divided into 2 vertical half screen sections. Do appreciate your help with this. JRBouley
@Bouley Updated my answer.Salve
Hello Avijit. Thank you for the answer you provided and the update as well. The bounty goes to you. This will certainly help me with the project I am working on. The Angular code you used here did answer my question and I have learned a little more of Angular. Many thanks for your help it was greatly appreciated. JRBouley
Avijit. Did you see the update at the end of my main post to do about using different classes for each different div block in ng-repeat.Bouley
Yes, it seems the way to go.Salve
Yes, it looks like it will now help to use ng-repeat for the actual coding in my project. Hope I can help you sometime. Do ask if after looking at the sort of questions I answer. Thanks again.Bouley
Happy to help. Also, the bounty seems to be still open ?Salve
Yea it's awarded now. Thanks a lotSalve
Hi there Avijit I was hoping you might get this, and see if you could show a way to trigger this via a remote link/button. Meaning the button to select one of the plus icons to trigger the effect would be out side of the ng-repeat. Thanks.Bouley
R
3

UPDATE

Shoot, it's only one at a time. Well... Almost all of what I had written applies.

New Fiddle

So only difference is the JS. Instead of an array of selected blocks, we have a number that represents the selected block.

this.blocks = Array.apply(null, Array(10)).map(function (val, index) {return index;});

this.activeIndex = null;

this.isActive = function(index) {
    return that.activeIndex === index;
};

this.hasSelected = function() {
    return that.activeIndex !== null;
};

this.selectBlock = function(index) {

    if (that.activeIndex === index) {
        that.activeIndex = null;
    } else {
        that.activeIndex = index;
    }

};

See, good JS code is easily maintainable, even when the requirements change (or when I find out they do).

(We actually can get by without these helper functions, but we use them for the sake of prettier code and maybe encapsulation.)


Original Answer

Fiddle here

I hope I've explained why we should do the stuff in the fiddle, and gave credible references/further reading material.

First things first

  • Don't use $scope. I like controller as syntax. Basically, in JS, you use this.prop and HTML, you use myCtrlAs.prop. Docs
  • This is a CSS problem. Take advantage of :not()

Two principles: KISS and DRY

So we have a problem of same copy-pasted code blocks. Like @avijit said, you should use the ng-repeat directive.

<div class="row" ng-repeat="block in plusMinus.blocks track by $index">
    //This entire block will be repeated
</div>

For each row, we need to keep track of whether or not it is selected. Following the KISS principle, the only state we need to keep track of is this.

Otherwise, things get too complex.

  //each element represents the state of a row
  this.blocks = [0,0,0,0,0]; 

So this block array is used for the ng-repeat.


You ask, how will we keep track of what to turn dark and what is active?

The answer, is .... wait for it


You don't


Instead, we use functions located on the controller to get information about the single blocks variable. You could argue against my wording, but notice how I said "keep track"? Again, don't store duplicate state data. It becomes a nightmare, based off my prior experience.

(We actually can get by without these helper functions, but we use them for the sake of prettier code and maybe encapsulation.)

So the first function that I want to point out:

this.hasSelected = function() {
  return that.blocks.indexOf(true) !== -1;
};

What is this for? You guessed it! To determine if we should "cover up" the rows that aren't selected.

Use CSS–it's fun useful!

So we conditionally apply the .has-selected class to a wrapper.

So that the "cover" only applies when it has an ancestor with .has-selected, we have

has-selected :not(.active) > .col-sm-4 {
  width: 33%;
  height: inherit;
  background-color: rgba(00,00,00,0.8);
  z-index: 9;
}
.has-selected :not(.active) .col-sm-4 {
  @media (max-width:420px){
    width: 80%;
  }
}  

back to ng-repeat

Oh, and by now, you should know that $index is used to access the index of the current HTML in ng-repeat. (Docs and SO Thread

<div class="label-index" data-ng-class="{'active-selection': !plusMinus.isActive($index)}">
          {{$index}}
</div>

Concluding remarks

  • Read the docs
  • Don't try to do everything with one tool
  • If you think I didn't explain something particularly well, just ask.

Also, I think I screwed up some styling. I hope that you can fix it on your own.

Rune answered 2/1, 2016 at 1:30 Comment(5)
Hello jasonszhao. Thank you for another well laid out post detailing your answer here. Interesting re the css and the use of :not(). I will review this and get back to you if I have any questions. Many thanksBouley
Hello jasonszhao. Thank you very much for putting your answer forward here. Although there is really nothing wrong with your answer I will go with Avijit Gupta's answer. I am doing so because the question was really to do with Angular. You showed a great option also to the css side of this and I think both answers here in this post will probably help others when they come across this post. Many thanks for your help, I do appreciate it. JRBouley
May I ask how this answer does not effectively utilize Angular? I did it with minimal code. The CSS part could be easily translated to angular, as you have already written. I just wanted to point out the best way, which did not make my answer inapplicable.Rune
Also please note that you have 7 days before the bounty ends. You are not pressured to give the bounty early on, as this does not give all answerers a fair chanceRune
Hello jasonszhao. It was as you could guess by looking at both answers... very close between you both. Staying true the question as to why I posted this question is why I excepted Avijit's answer. Yes, I know I have 7 days , but there was no need to have other people also spend time on this when I was happy with the answers already. Again, thanks for your effort putting your answer together. I did appreciate the different approach you showed, I can say I learnt something from your post as well. Many thanks.Bouley
H
1

the best solution you can find in the snippet below

angular.module('plusMinusApp', [])
	.controller('plusMinusController', function ($scope) {
		$scope.blocks = [1, 2, 3, 4, 5, 6];
		var expandedBlock;
		
		$scope.isAnyBlockExpanded = function() {
			return expandedBlock !== undefined;
		};
		
		$scope.isBlockExpanded = function(blockId) {
			return expandedBlock === blockId;
		};
		
		$scope.toggleBlockExpandingStatus = function(blockId) {
			expandedBlock = $scope.isBlockExpanded(blockId) ? undefined : blockId;
		}; 
	});
body {
    padding-top: 15px;
}

.divClass{
    width: 35%;
    height:50px;
    text-align:center;
    line-height:50px;
    float: left;
    left:15px;
    margin-top: 25px;
    margin-bottom: 25px;
    margin-right: 15px;
    color: orange;
    font-size: 18px;
    border:2px solid #000; 
    background-color: rgba(00,00,00,0.6);
    cursor: pointer;
}

.textdiv {
    border:2px solid green; 
    background-color: rgba(100,100,100,0.1);
    height:50px;
    text-align:center;
    line-height:50px;
    margin-top: 25px;
	display: none;
}

.expanded-block  .textdiv {
	display: block;
}

i {
    color:#000;
    font-size: 40px;
    line-height:50px;
}

.btn {
    height: 50px;
    margin-top: 25px;
    margin-left: 15px;
}
 
.expanded-block  .divClass {
    background-color:rgba(100,100,100,0.1);
    width: 35%;
    font-size: 40px;
    text-align: center;
    border:2px solid green; 
}

.collapsed-block .block-item {
    width: 33%;
    height: inherit;
    background-color: rgba(00,00,00,0.8);
    z-index: 9;
}

@media (max-width:420px){
	.collapsed-block {
		width: 80%;
	}
}  
<html>
<head>
	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css"/>
	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"/>
	<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
</head>

<body ng-app="plusMinusApp">
	<div class="container"  ng-controller="plusMinusController">

		<div ng-repeat="block in blocks" class="row" ng-class="{'collapsed-block': isAnyBlockExpanded() && !isBlockExpanded(block), 'expanded-block': isBlockExpanded(block)}">
			<div class="col-sm-4 col-xs-6 block-item">    
				<div class="divClass" ng-click="toggleBlockExpandingStatus(block)">{{block}}</div>
				<i ng-click="toggleBlockExpandingStatus(block)" class="btn btn-primary text-center fa" ng-class="isBlockExpanded(block) ? 'fa-minus': 'fa-plus'"></i>
			</div>
			<div class="col-xs-4 textdiv">Hello</div>
		</div>
		
	</div>
</body>
</html>
Holsinger answered 2/1, 2016 at 10:40 Comment(4)
Hello Alexander. Thank you , but I had just this minute excepted Avijit's answer. Appreciate your help though. JRBouley
@Bouley if you really like it, you could at least up vote my answerHolsinger
Hi there, just did that for you.Bouley
@Bouley Thank you. I hope you find it useful. The algorithm is much simplier than thatt you granted the bounty.Holsinger

© 2022 - 2024 — McMap. All rights reserved.