ng-click to affect only the element it is inside - javascript

I am using ng-repeat to generate some elements...
<div class="form-block" ng-repeat="form in formblock | filter:dateFilter">
<div ng-click="showResults()" ng-if="repeat == true" class="drop">{{ form.form_name }} <span class="caret"></span></div>
<div ng-show="results" class="formURL">{{ form.url }}</div>
<div ng-show="results" class="formCount">{{ form.count }}</div>
<div ng-show="results" class="formSubmit">{{ form.submit }}</div>
</div>
As you can see, ng-click="showResults()" toggles the display of the other elements. The problem is, I only want the ng-click to toggle the elements inside the same container, not toggle all elements.
In short, I only want the click event to affect the elements in the same container that the function is called, how can I do this?
this is showResults in my controller...
$scope.showResults = function(){
return ($scope.results ? $scope.results=false : $scope.results=true)
}

ng-repeat provides you with a special variable (unless you already have an identfier): $index.
Using this, you can store (instead of a single boolean value) an object $index => toggleState in your angular code:
$scope.hiddenHeroes = {};
$scope.toggleHero = function (idx) {
$scope.hiddenHeroes[idx] = !$scope.hiddenHeroes[idx];
}
And in your HTML:
<div ng-repeat="hero in heroes">
<div class="hero" ng-hide="hiddenHeroes[$index]">
<h1>
{{hero}}
</h1>
All you want to know about {{hero}}!
<br />
</div>
<a ng-click="toggleHero($index)">Toggle {{hero}}</a>
</div>
See it live on jsfiddle.net!

You can use $index to index item/containers and show the corresponding results:
<div ng-click="showResults($index)" ng-if="repeat == true" class="drop">{{ form.form_name }} <span class="caret"></span></div>
<div ng-show="results[$index]" class="formURL">{{ form.url }}</div>
<div ng-show="results[$index]" class="formCount">{{ form.count }}</div>
<div ng-show="results[$index]" class="formSubmit">{{ form.submit }}</div>
And your function
$scope.showResults = function(index){
return ($scope.results[index] ? $scope.results[index]=false : $scope.results[index]=true)
}

Related

Conditionally hide the nth element of a v-for loop without modifying the array. vue 3 composition api search function

I have a ref variable (foxArticles ), which holds a list that contains 100 items. In a v-for loop i loop over each value. As a result, i have 100 values rendered on the page.
<template>
<div class="news_container">
<div
v-for="article in foxArticles"
v-bind:key="article"
class="article_single_cell"
>
<div
class="news_box shadow hover:bg-red-100 "
v-if="containsKeyword(article, keywordInput)"
>
<div class="news_box_right">
<div class="news_headline text-red-500">
<a :href="article.url" target="_blank">
{{ article.title }}
</a>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
const foxArticles = ref([]);
</script>
I also have a search function, which returns the value, if it includes the passed in keyword. The function is used in the child of the v-for loop.
<div class="search_input_container">
<input
type="text"
class="search_input"
v-model="keywordInput"
/>
</div>
<script>
const keywordInput = ref("");
function containsKeyword(article, keywordInput) {
if (article.title.toLowerCase().includes(keywordInput.toLowerCase())) {
return article;
}
}
</script>
The problem is, i can't use .slice() on the foxArticles array in the v-for loop, because that screws up the search functionality, as it returns only the values from the sliced range.
How can i have the access the all of the values of the array, while not rendering all 100 of returned articles on the initial load?
Any suggestions?
I think your approach will make it incredibly complex to achieve. It would be simpler to always iterate over some set, this set is either filtered based on a search-term, or it will be the first 100 items.
I'm not very familiar yet with the Vue 3 composition api so I'll demonstrate with a regular (vue 2) component.
<template>
<div class="news_container">
<div
v-for="article in matchingArticles"
v-bind:key="article"
class="article_single_cell"
>
... news_box ...
</div>
</div>
</template>
<script>
export default {
...
computed: {
matchingArticles() {
var articles = this.foxArticles;
if (this.keywordInput) {
articles = articles.filter(article => {
return this.containsKeyword(article, this.keywordInput)
})
} else {
// we will limit the result to 100
articles = articles.slice(0, 100);
}
// you may want to always limit results to 100
// but i'll leave that up to you.
return articles;
}
},
....
}
</script>
Another benefit is that the template does not need to worry about filtering results.
ok, so i kind of came up with another solution, for which you don't have to change the script part...
instead of having one v-for loop , you can make two of them, where each one is wrapped in a v-if statement div
The first v-if statement says, If the client has not used the search (keywordInput == ''), display articles in the range of (index, index)
The second one says = If the user has written something (keywordInput != ''), return those articles.
<template>
<div class="news_container">
<!-- if no search has been done -->
<div v-if="keywordInput == ''">
<div
v-for="article in foxArticles.slice(0, 4)"
v-bind:key="article"
class="article_single_cell"
>
<div class="news_box shadow hover:bg-red-100 ">
<div class="news_box_right">
<div class="news_headline text-red-500">
<a :href="article.url" target="_blank">
{{ article.title }}
</a>
</div>
</div>
</div>
</div>
</div>
<!-- if searched something -->
<div v-else-if="keywordInput != ''">
<div
v-for="article in foxArticles"
v-bind:key="article"
class="article_single_cell"
>
<div
class="news_box shadow hover:bg-red-100 "
v-if="containsKeyword(article, keywordInput) && keywordInput != ''"
>
<div class="news_box_right">
<div class="news_headline text-red-500">
<a :href="article.url" target="_blank">
{{ article.title }}
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
im not sure how this impacts performance tho, but that's a problem for another day

Target only 1 button in Angular

Im getting the values in a div from the DB and displaying using ng-repeat:
<div ng-controller = "myTest">
<div ng-repeat="name in names">
<h4>{{name.name}}</h4>
<button ng-class="{'active': isActive}" ng-click="test()" >me</button>
</div>
</div>
In my controller I have:
$scope.test= function(){
$scope.isActive = !$scope.isActive;
}
I have defined a class isActive in my css and that is applied/removed to the button on click. There are 5 results so 5 divs are created cause of ng-repeat and 5 buttons(1 for each respective div). The problem is that every button (all 5 of them) is getting that class. I want the class to be applied/removed only to the button clicked. How can I achieve this?
You can try something like this :
<div ng-controller="myTest">
<div ng-repeat="name in names">
<h4>{{name.name}}</h4>
<button ng-class="{ active : name.isActive }"
ng-click="name.isActive = !name.isActive">me</button>
</div>
</div>
Hope this will help.
You need to keep track of each button status.
One way will be to passing the name or anything that uniquely identify the button to your function:
<div ng-controller = "myTest">
<div ng-repeat="name in names">
<h4>{{name.name}}</h4>
<button ng-class="{'active': buttons[name].isActive}" ng-click="test(name)" >me</button>
</div>
</div>
$scope.buttons = {};
$scope.test= function(name){
$scope.buttons[name].isActive = !$scope.buttons[name].isActive;
}
I created a plunk that answers this question without modifying your source array.
Your function becomes
vm.test = function(buttonIndex) {
//Clear the class if you press the same button again
if (vm.buttonIndex === buttonIndex) {
vm.buttonIndex = undefined;
} else {
vm.buttonIndex = buttonIndex;
}
};
And your HTML is
<div ng-repeat="name in main.names track by $index">
<h4>{{name.name}}</h4>
<button ng-class="{'active': main.buttonIndex===$index}" ng-click="main.test($index)">me</button>
</div>

Create a new scope outside ng-repeat

The idea is how a list of texts can be modified by pressing the button next to the text. We can also apply it to the title text which is outside the list.
HTML:
<div ng-controller="TextController">
<div class="title">
<span>{{ text }}</span>
<button ng-click="edit()">Edit</button>
</div>
<ul>
<li ng-repeat="text in list">
<span>{{ text }}</span>
<button ng-click="edit()">Edit</button>
</li>
</ul>
</div>
JavaScript:
angular.module("app").
controller("TextController", function($scope) {
$scope.text = "hello";
$scope.list = [....]; // list of texts;
$scope.edit = function() {
this.text += " world";
};
});
I'm not sure if I wrote it the right way. However, everything works fine except the edit button in the title which is when I'm trying to edit the title only, it accidentally edits all text which is in its children scope.
What I'm trying to do is to give the title a new scope so that the button doesn't affect other texts because it isn't a parent of any scope.
Yeah What you are trying to do is right:
The {{text}} variable is bound to the same controller scope. so the edit button just updates that value which makes it to change everywhere
You should try to update $scope.list value:
When you updated the scope, the html will render.
$scope.edit = function() {
$scope.list[this.$index] += " world";
};
Please check the demo:
http://jsfiddle.net/Lvc0u55v/7050/
Why don't you use another variable name for ng-repeat(i have made second text => txt). It is better to have separate functions for updating list and text, but if you want, you can try
<div ng-controller="TextController">
<div class="title">
<span>{{ text }}</span>
<button ng-click="edit()">Edit</button>
</div>
<ul>
<li ng-repeat="txt in list">
<span>{{ txt }}</span>
<button ng-click="edit($index)">Edit</button>
</li>
</ul>
</div>
The view is passing $index of the array element:
$scope.edit = function(listIndex) {
if(listIndex) $scope.list[listIndex] += " world"
else $scope.text += " world";
};
You could create an "include" combined with an ng-template so that you get a new scope.
http://jsfiddle.net/pfeq0mwe/3/
<div ng-app="myApp" ng-controller="myCtrl">
<div ng-include src="'myTemplate.htm'">
</div>
<ul>
<li ng-repeat="text in list">
<span>{{ text }}</span>
<button ng-click="edit()">Edit</button>
</li>
</ul>
<script type = "text/ng-template" id = "myTemplate.htm">
<div class="title">
<span>{{ text }}</span>
<button ng-click="edit()">Edit</button>
</div>
</script>
</div>
<div >
</div>

Assign value to dynamically created scope variables

I'm trying to create a means to toggle dynamically created rows of information. I've tried using ng-init, and then passing it to a function, but I'm screwing up somewhere and I can't seem to wrap my head around how or if this is possible. The gap, I believe, is in getting the concatenated scope variable to be referenced elsewhere. I'm using Bootstrap 3 and AngularJS 1.5.
The HTML:
<div class="row" data-ng-repeat="equipment in task.equipment">
<div class="col-md-12">
<h4 class="green-text">
{{ equipment.equipId }}
<small class="green-text">
<i class="glyphicon"
data-ng-class="{'glyphicon-triangle-bottom': field{{ $index }}, 'glyphicon-triangle-right': !field{{ $index }}}"
data-ng-init="equipment['field' + $index] = true"
data-ng-click="toggleTaskEquip('field{{ $index }}')">
field{{ $index }}: I WANT THIS TO WORK</i>
</small>
</h4>
</div>
<div data-ng-show="field{{ $index }}">
...stuff here...
</div>
</div>
The JS:
$scope.toggleTaskEquip = function(toggleBool)
{
if (toggleBool === true)
$scope.isTaskEquipOpen = false;
else if (toggleBool === false)
$scope.isTaskEquipOpen = true;
};
If I understand the problem correctly, you want to be able to toggle the boolean created in the ng-init with a click.
I think you need this:
<div class="container-fluid">
<div ng-controller="MyCtrl">
<div class="row" data-ng-repeat="equipment in task.equipment">
<div class="col-md-12">
<h4 class="green-text">
{{equipment.equipId}}
<small class="green-text">
<i class="glyphicon"
data-ng-class="{'glyphicon-triangle-bottom': isVisible, 'glyphicon-triangle-right': !isVisible}"
data-ng-init="isVisible = true"
data-ng-click="isVisible = !isVisible">I WANT THIS TO WORK</i>
</small>
</h4>
</div>
<div data-ng-show="isVisible">
...stuff here...
</div>
</div>
</div>
</div>
You don't even need the function toggleTaskEquip on the $scope.
JSFiddle here.
ng-repeat creates a new scope for each template instance, so you can just create a separate isVisible for each equipment with isVisible = true in the ng-init.

How can I give each ng-model in an ng-repeat a unique identifier?

I have a page with multiple AngularUI sliders, and a span displaying the value of each slider with an ng-bind. It looks something like this:
<div class="formitem" ng-repeat="food in foodlist" >
<div ui-slider="{range: 'min'}" min="0" max="{{ unit.max }}" ng-model="demoVals.slider" class="food-slider" >
<div class="begin">1</div>
<div class="end"> {{ unit.max }} {{ food.uid }} </div>
</div>
<span ng-bind="demoVals.slider"></span>
</div>
I want the ng-model to be unique for each food item, so something like demoVals.slider57, where 57 is the output of {{ food.uid }}. I can get {{ food.uid }} to print out in the template just fine, but if I try to add it to the ng-model or ng-bind, I just get this:
ng-model="demoVals.slider[food.uid]"
How can I add the food.uid to the ng-model of each food item?
It does not seem to be a good architectural choice to use individual elements instead of using an array. You should really try to switch to put your slider values into an array.
However, you can use some controller function to achieve what you want, although it is rather ugly. Within your ng-repeat you can bind to a function
<div ng-bind="test(food.uid)"></div>
with the function being
$scope.test = function test(v) {
var t = $scope.$eval("demoVals.slider" + v);
return t;
};
You want to add a food.uid property to demoVals for each iteration. This brings the need to execute js code on each iteration. That can be only be done (IMHO)in a directive.
So here is a working plunker
Here is how you should modify your html ( notice the repeat-directive ... directive :)
<div class="formitem" ng-repeat="food in foodlist" repeat-directive>
<div ui-slider="{range: 'min'}" min="0" max="{{ unit.max }}" ng-model="demoVals.slider" class="food-slider" >
<div class="begin">1</div>
<div class="end"> {{ unit.max }} {{ food.uid }} </div>
</div>
<span ng-bind="demoVals.slider"></span>
here is the code for the direective that will be executed on every iteration:
var app = angular.module('plunker', [])
.directive ('repeatDirective',function () {
return {
restrict: 'A',
link: function (scope, element, attr) {
console.log('linking directive ',scope.demoVals)
if (scope.food){
// you have a value for food
// you can add it as an attribute to demoVals
scope.demoVals['prop'+scope.food.uid] = scope.food.name;
}}
}
});

Categories

Resources