How to add multiple condition in a single class using Angular.js - javascript

I need one help. I need to add multiple condition with AND operator in ng-class using Angular.js. I am explaining my code below.
<div class="mainaccordion_title-width" ng-click="manageCollapseExpand(place, false)">
<i ng-class="{'fa fa-minus-circle': place.expanded, 'fa fa-plus-circle': !place.expanded }"></i>
{{place.root_des}}
</div>
Here I am adding one condition but I need to add another condition (i.e-place.cstatus==1 and place.cstatus==0) for both class. Please help.

Use && instead of and.
(i.e-place.cstatus==1 && place.cstatus==0)

As commented before, you can also create a function that takes arguments and return classnames.
Benefit of this approach is, it will keep your view clean and the logic will stay in JS. This will also make it more configurable as you can write a code and share it across multiple elements.
function myCtrl($scope) {
$scope.place = {
expanded: false,
cstatus: 1,
root_des: 'test'
}
$scope.manageCollapseExpand = function(place, seleccted) {
place.expanded = !place.expanded
}
// assuming place should be an array
$scope.getClasses = function(index) {
var className = 'fa ';
if ($scope.place.expanded && $scope.place.cstatus === 1) {
className += 'fa-minus-circle '
} else {
className += 'fa-plus-circle ';
}
return className
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app ng-controller='myCtrl'>
<div class="mainaccordion_title-width" ng-click="manageCollapseExpand(place, false)">
<i ng-class="getClasses(0)"></i>
{{place.root_des}}
<p>Classes: {{getClasses(0)}}</p>
</div>
</div>

Simply you can conbind condition using &&
<div class="mainaccordion_title-width" ng-click="manageCollapseExpand(place, false)">
<i ng-class="{'fa fa-minus-circle': place.expanded && place.cstatus==1, 'fa fa-plus-circle': !place.expanded && place.cstatus==0 }"></i>
{{place.root_des}}
</div>

Related

How can i access each element in a ngFor with a function?

I created a simple note app which for loops through an array of objects which holds note data. I have a button which opens up the note for edit by returning true when clicked returning false when clicked again.
The problem is when clicked all the notes open up in edit mode because the boolean variable is shared.
Question is: "How can i access that specific note where i clicked the edit button?"
HTML:
<div class="view-notes" *ngFor="let note of notes; index as i">
<div class="tools">
<i class="fa fa-edit" (click)="openNote(i)"></i>
<i (click)="deleteNote(i)" class="fa fa-trash"></i>
</div>
<input [disabled]="!onEdit()" type="text" [(ngModel)]="note.title" #title>
<p *ngIf="!onEdit()">{{note.date | noteDate}}</p>
<textarea type="text" *ngIf="onEdit()" name="body" id="" [(ngModel)]="note.note"></textarea>
</div>
<h1 class="error" *ngIf="noNotes()">No notes to be displayed.</h1>
The functions:
openNote(i: number) {
if (this.count == 0) {
// open
this.edit = true; this.count++;
} else {
//save
this._noteService.updateNote(i, this.notes[i].title, this.notes[i].note);
//close
this.count--;
this.edit = false;
}
}
onEdit() {
return this.edit;
}
In your title, and in your own words, you're asking:
How can i access each element in a ngFor with a function?
and
"How can i access that specific note where i clicked the edit button?"
To answer this question directly -- you can access the scoped variable that's created implicitly within the loop.
This is to say, the *ngFor="let note of notes" creates a note variable scoped to your to each iteration of the loop.
You're already doing this where your ngModel binding is in your <input> and <textarea> for the note title/text respectively.
You can also pass that variable to functions, just as you do with bindings.
So, you could use the note variable to pass to a function, which will be called using whichever note is clicked. e.g. openNote(note)
// HTML
<div class="view-notes" *ngFor="let note of notes">
<div class="tools">
<i class="fa fa-edit" (click)="openNote(note)"></i> // passes the current note you clicked
...
// TS
openNote(note: any) {
// do something with this note, here
}
That's your question answered (at least what your question is directly asking from the title).
However, your question appears to be asking more than one thing, namely to do with showing/hiding specific notes (i.e. the ones that were clicked). Please try and keep your questions focused, or at least asking the same question in your post as what the title says :)
I'll answer what I think you're asking, looking at the problem you've described in your question; which I think would be:
"How can I show just the note I wish to edit; and save/close it when I edit click again, or edit a different note?"
Regarding the show/hide of specific notes; as already pointed out, you're just showing/hiding all notes based on a single boolean (this.edit returned by onEdit()) variable, which will have the same effect on all your notes (showing/hiding them all at the same time).
Seeing as you have access to each note inside your notes array within your *ngFor loop, you could keep a record of which note is currently displayed, using a property on your component:
export class SomeComponent {
currentlyShownNote: any; // use this to store the reference of the note currently open
// rest of component code...
}
Then, you can simply check in your HTML if the currentlyShownNote is this particular one, and show/hide based on this check:
<textarea type="text" *ngIf="currentlyShownNote === note" ...>
Then, create a showHideNote function in your component to set which note is being shown when you click it:
// HTML
<div class="view-notes" *ngFor="let note of notes; index as i">
<div class="tools">
<i class="fa fa-edit" (click)="showHideNote(note)"></i>
...
// TS
showHideNote(note: any) {
// if there is a currently shown note, save it
if (this.currentlyShownNote) {
const currentNote = this.currentlyShownNote;
this._noteService.updateNote(this.notes.indexOf(currentNote), currentNote.title, currentNote.note);
}
if (this.currentlyShownNote == note) {
this.currentlyShownNote = null;
} else {
this.currentlyShownNote = note;
}
}
Or, rather than using the reference to each note variable, you could simply use the index (index as i) in the array to track which note is shown (similar to how you're deleting the notes):
// HTML
<div class="view-notes" *ngFor="let note of notes; index as i">
<div class="tools">
<i class="fa fa-edit" (click)="showHideNote(i)"></i>
<i (click)="deleteNote(i)" class="fa fa-trash"></i>
</div>
<input [disabled]="shownNoteIndex !== i" type="text" [(ngModel)]="note.title" #title>
<p *ngIf="shownNoteIndex !== i">{{note.date | noteDate}}</p>
<textarea type="text" *ngIf="shownNoteIndex === i" name="body" id="" [(ngModel)]="note.note"></textarea>
</div>
// TS
shownNoteIndex?: number; // property to hold the currently shown note index
showHideNote(noteIndex: number) {
// if there is a currently shown note, save it
if (this.shownNoteIndex >= 0) {
const i = this.shownNoteIndex;
this._noteService.updateNote(i, notes[i].title, notes[i].note);
}
if (this.shownNoteIndex == noteIndex) {
this.shownNoteIndex = null;
} else {
this.shownNoteIndex = noteIndex;
}
}
BONUS: For coming back round full circle, you can create another function in your component to make your shownNoteIndex === i and shownNoteIndex !== i (or even your currentlyShowNote === note) checks even more succinct:
// HTML
<div class="view-notes" *ngFor="let note of notes; index as i">
<div class="tools">
<i class="fa fa-edit" (click)="showHideNote(i)"></i>
<i (click)="deleteNote(i)" class="fa fa-trash"></i>
</div>
<input [disabled]="!isNoteShown(i)" type="text" [(ngModel)]="note.title" #title>
<p *ngIf="!isNoteShown(i)">{{note.date | noteDate}}</p>
<textarea type="text" *ngIf="isNoteShown(i)" name="body" id="" [(ngModel)]="note.note"></textarea>
</div>
// TS
// if you're using the note index
isNoteShown(noteIndex: number) {
return this.shownNoteIndex === noteIndex;
}
// or
// if you're using the note reference
isNoteShown(note: any) {
return this.currentlyShownNote === note;
}
Your edit flag should be a number representing which note should be edited. Then you can check if the item in the list matches the edit number and display edit for just that one.
try calling the function with doSomething($event) in the html
and define that function in ts file as doSomething(event){}

Vuejs- run #click when using v-html to return html string?

I have this html string:
errorText: '<a class="button" #click.prevent="enableExceedable(issue.mapping)">Enable exceedable limits</a>';
I want to add it inside this div:
<div v-if="hasIssue !== {} && issue.is_issue" v-for="(issue, key) in hasIssue" :key="key" class="issues" v-html="errorText"></div>
when I use v-html to return it, I see the button but #click doesn't run. I need a solution for that please.
You are using v-html in wrong way. You should add
<a class="button" #click.prevent="enableExceedable(issue.mapping)">Enable exceedable limits</a>
inside your div
<div v-if="hasIssue !== {} && issue.is_issue" v-for="(issue, key) in hasIssue" :key="key" class="issues">
<a class="button" #click.prevent="enableExceedable(issue.mapping)">Enable exceedable limits</a>
</div>
Maybe this will help:
<div
v-if="hasIssue !== {} && issue.is_issue"
v-for="(issue, key) in hasIssue"
:key="key"
class="issues"
v-html="errorText"
#click="processIssue(issue)"
>
</div>
data: {
hasIssue: [],
errorText: `
<button class="button">
Enable exceedable limits
</button>
`
},
methods: {
processIssue (issue) {
this.enableExceedable(issue.mapping)
},
enableExceedable (mapping) {
// implementation
}
}
PS: I'm using button instead of anchor. Use anchors only when they will really act as anchors, not just buttons. Better semantics. Recommended way.

ng-show working with multiple filters in ng-repeat

I have an ng-repeat list with multiple filters, but as I need the elements to be hidden and not removed from the DOM, I use ng-show for the filtering.
This is how my first filter look like:
<a href="" ng-click="myFilter = ''; toggle = toggle=0; togglesec=0; mySecondFilter = {}" ng-class="{'active' : toggle==0}" >
Tot
</a>
<a href="" ng-click="myFilter = 'pa'; toggle = toggle=1; togglesec=0; mySecondFilter = {}" ng-class="{'active' : toggle==1}">
Pa
</a>
<a href="" ng-click="myFilter = 'Reposteria'; toggle = toggle=2; togglesec=0; mySecondFilter = {}" ng-class="{'active' : toggle==2}">
Reposteria
</a>
And the second one:
<div ng-init="togglesec=0; mySecondFilter = {}">
<a ng-click="mySecondFilter = {}; togglesec = togglesec=0" ng-class="{'active' : togglesec==0}">
All
</a>
<a ng-click="mySecondFilter = {'vegan': true}; togglesec = togglesec=1" ng-class="{'active' : togglesec==1}">
Vegan
</a>
<a ng-click="mySecondFilter = {'gluten': true}; togglesec = togglesec=2" ng-class="{'active' : togglesec==2}">
Gluten Free
</a>
</div>
Now, I was able to filter the ng-repeat using ng-show and the first filter like so:
<div ng-repeat="pa in products" ng-show="pa.tipus.indexOf(myFilter) != -1">
Basically it compares the value of myFilter with the pa.tipus object property, and it works OK.
But it won't work with the second filter, because mySecondFilter is an object, not a string (it needs to filter the results containing vegan:true or gluten:true)
Here's an example of my object type:
pa {
tipus : 'pa',
gluten : false,
vegan : true
}
Any tips on how to combine both filters in the same ng-show?
EDIT
I've applied the answer by Naren, but I get the following error on click on any filter:
angular.min.js:122 TypeError: Cannot create property 'vegan' on string ''.
I've also tried to initialize myFilter by adding this, but no luck, same error appears:
$scope.myFilter = {
tipus : '',
vegan : '',
gluten : '',
lactosa : ''
};
Update:
Since the user wanted a generic version for the filtering. The following function should be the answer.
$scope.validate = function(row) {
for (var key in $scope.myFilter) {
if ($filter('filter')([row], {
[key]: $scope.myFilter[key]
}).length === 0) {
return false;
}
}
return true;
}
Where we loop through an object where all the filters are stored, then return false if any of the filters are not satisfied.
Please check the below example to get a understanding of how the filter works.
JSFiddle Demo
Old:
Here is my version of the fix. Angular's $filter will be great for identifying the object with the property, but this is not possible to have written in the HTML, hence I call a function which will return true or false to the ng-show.
$scope.validate = function(row) {
if (row.tipus.indexOf($scope.myFilter) != -1) {
if ($filter('filter')([row], $scope.mySecondFilter).length !== 0) {
return true;
} else {
return false;
}
} else {
return false;
}
}
Let me explain the function. First we receive the row of the ng-repeat through the parameter of validate, after which I apply the first if condition which is basically whatever you had written earlier, this code works great btw. Then I check the row using the $filter syntax for the object present in $scope.mySecondFilter, Refer here for more on filtering with $filter, this will basically check if the row passed contains the required object property and return true or false.
Here is a working example of your code for reference.
JSFiddle Demo
Please let me know if this fixes your issue completely :)

Have both JSX and normal className together

I have a css style class that needs to be in JSX form as it depends on the state, but I also need to use style class the ordinary way for styling. How do I combine these together?
I've tried
<span className={statusColor} className="uppercase">
Which only takes into account the last class one
<span className={statusColor uppercase}>
Which obviously looks for a variable called uppercase and errors out
<span className="uppercase {statusColor}">
Neither of them work
Below is a dummied-down version of my code. What is the correct way to do this?
const Component = React.createClass({
return {
completed: false
}
},
render () {
let statusColor;
this.state.completed === true ? statusColor = "green" : statusColor = "red";
return (
<div className="status-banner">
<span className="kata-text-status">Status: <span className={statusColor} className="uppercase">{this.state.completed.toString()}</span></span>
</div>
)
}
})
Try this:
<span className={`uppercase ${statusColor}`}>
This will use an ES6 template string (`uppercase ${statusColor}`) and interpolate statusColor with ${...}.
<span className={'uppercase' + ' ' + statusColor}>
You can do it in several ways.
Firstly you can use + to concat more than one string variables and generate dynamic strings
<span className={"uppercase " + statusColor}>
Or you can use npm modules classnames.
The brackets {} translate to normal JS. So you can just use + to concatenate.
<span className={"uppercase " + statusColor}>

AngularJS directive - ng-class in ng- repeat should it be a $watcher to toggle style?

I am currently implementing a spike to further my understanding on angular directives etc.
The premise is to create a FX watch list on a number of currency pairs.
My data feed is set up for my price updates via socket.io.
The stumbling block that i have is being able to change the css dependent on price change ie up arrow for up, down arrow for down.
I feel a watcher function is what i need but struggled on where to start so was looking for some sort of expression in ng-class to do the job ... but the method not only started to look like a $watcher it was also flawed as saving the previous price to scope on my directive meant there was only ever one old value not one for each price.
There for my question is : Is the solution with ng-class or in setting up a $watcher function ?
Heres my code ...
HTML template
<div ng-repeat="rate in rates" ng-click="symbolSelected(rate)">
<div class="col-1-4">
{{rate.symbol}}
</div>
<div class="col-1-4">
<span ng-class='bullBear(rate.bidPoint)' ></span> {{rate.bidBig}}<span class="point">{{rate.bidPoint}}</span>
</div>
<div class="col-1-4">
<span ng-class='bullBear(rate.offerPoint)' ></span> {{rate.offerBig}}<span class="point">{{rate.offerPoint}}</span>
</div>
<div class="col-1-4">
{{rate.timeStamp | date : 'hh:mm:ss'}}
</div>
</div>
My directive currently looks like this ... as noted this will not work and the bullBear method was starting to look like a $watcher function.
.directive('fxmarketWatch', function(fxMarketWatchPriceService){
return {
restrict:'E',
replace:'true',
scope: { },
templateUrl:'common/directives/fxMarketWatch/marketwatch.tpl.html',
controller : function($scope, SYMBOL_SELECTED_EVT,fxMarketWatchPriceService){
$scope.symbolSelected = function(currency){
$scope.$emit(SYMBOL_SELECTED_EVT,currency);
}
$scope.bullBear = function(newPrice){
if ($scope.oldPrice> newPrice ){
return ['glyphicon glyphicon-arrow-down','priceDown'];
}
else if ($scope.oldPrice > newPrice ){
return ['glyphicon glyphicon-arrow-up','priceUp'];
}
}
$scope.$on('socket:fxPriceUpdate', function(event, data) {
$scope.rates = data.payload;
});
}
}
})
You could modify the ng-class and move the logic into the view, because styling and placing classes shouldn't be done in code.
<div class="col-1-4">
<span class="glyphicon" ng-class="{'glyphicon-arrow-up priceUp': oldPrice > rate.bidPoint, 'glyphicon-arrow-down priceDown':oldPrice > rate.bidPoint}"></span> {{rate.bidBig}}<span class="point">{{rate.bidPoint}}</span>
</div>
Or like this:
<span class="glyphicon {{oldPrice > rate.bidPoint ? 'glyphicon-arrow-down priceDown':'glyphicon-arrow-up priceUp'}}></span> {{rate.bidBig}}<span class="point">{{rate.bidPoint}}</span>
I will recommend you to use both ng-class and $watcher. The two can actually compliment each other:
UPDATE: To make the code works with ng-repeat, we need to migrate all of CSS classes logic to another controller:
app.controller('PriceController', function($scope) {
// we first start off as neither up or down
$scope.cssBid = 'glyphicon';
$scope.cssOffer = 'glyphicon';
var cssSetter = function(newVal, oldVal, varName) {
if (angular.isDefined(oldVal) && angular.isDefined(newVal)) {
if (oldVal > newVal) {
$scope[varName] = 'glyphicon glyphicon-arrow-down priceDown';
} else if (newVal > oldVal) {
$scope[varName] = 'glyphicon glyphicon-arrow-up priceUp';
} else {
$scope[varName] = 'glyphicon';
}
}
};
// watch for change in 'rate.bidPoint'
$scope.$watch('rate.bidPoint', function(newVal, oldVal) {
cssSetter(newVal, oldVal, 'cssBid');
});
// watch for change in 'rate.offerPoint'
$scope.$watch('rate.offerPoint', function(newVal, oldVal) {
cssSetter(newVal, oldVal, 'cssOffer');
});
});
Next, we bind this PriceController onto ng-repeat div. By doing so, Angular will create one controller instance for each rate in rates. So this time rate.bidPoint and rate.offerPoint should be available for $watch-ing:
<div ng-repeat="rate in rates" ng-click="symbolSelected(rate)" ng-controller="PriceController">
<div class="col-1-4">
<span ng-class='cssBid'></span> {{rate.bidBig}}<span class="point">{{rate.bidPoint}}</span>
</div>
<div class="col-1-4">
<span ng-class='cssOffer'></span> {{rate.offerBig}}<span class="point">{{rate.offerPoint}}</span>
</div>
</div>
Now, directive's controller will be much shorter than before:
controller: function($scope, SYMBOL_SELECTED_EVT, fxMarketWatchPriceService){
$scope.symbolSelected = function(currency) {
$scope.$emit(SYMBOL_SELECTED_EVT, currency);
}
$scope.$on('socket:fxPriceUpdate', function(event, data) {
$scope.rates = data.payload;
});
}

Categories

Resources