Rotate Styles using Angular ng-if - javascript

Still learning angular, but having issues rotating styles. Ultimately, I want the first record to be blue, the second to be white, the third blue, the fourth white, etc...
Right now my order is (incorrect):
Record 1 Blue
Record 3 blue
Record 2 white
Record 4 white
Any ideas what could be the issues?
HTML
<div id="dashboard" style="width: 1100px;" ng-controller="controller4">
<div class="blue" ng-repeat="metrics in dashboard" ng-if="($index % 2 == 0)">
<div align="center">
<h3>{[{metrics.value}]}</h3>
<span class=dashboardtext>{[{metrics.name}]}</span>
</div>
</div>
<div class="white" ng-repeat="metrics in dashboard" ng-if="($index % 2 != 0)">
<div align="center">
<h4>{[{metrics.value}]}</h4>
<span class=dashboardtext>{[{metrics.name}]}</span>
</div>
</div>
</div>
JS
var dashboard = [
{
value: 15,
name: "Total Completed"
},
{
value: 1200,
name: "Additional Widgets Sold"
},
{
value: 16,
name: "Projects"
},
{
value: 5,
name: "Average Days"
}
];
myApp.controller('controller4', function($scope) {
$scope.dashboard = dashboard;
});

ng-class-even & ng-class-odd directive perfectly suits your need.
ng-class-even -> Add a class value to DOM classList object when $index
is even
ng-class-odd -> Add a class value to DOM classList object when
$index is odd
Markup
<div ng-class="'blue'" ng-class-odd="'white'" ng-repeat="metrics in dashboard">
<div align="center">
<h3>{[{metrics.value}]}</h3>
<span class=dashboardtext>{[{metrics.name}]}</span>
</div>
</div>

For simple even/odd, you can use the ng-class-even and ng-class-odd attributes. If you have a more complex situation with more values, use the below solution.
Create a filter and use it in the class attribute.
app.filter("classFilter", function () {
return function (input) {
var $class = "";
switch(input % 2){
case 0:
$class = "blue";
break;
case 1:
$class = "white";
break;
}
return $class;
}
});
Then in your HTML, you would have
<div class="$index | classFilter" ng-repeat="metrics in dashboard">
<div align="center">
<h3>{[{metrics.value}]}</h3>
<span class=dashboardtext>{[{metrics.name}]}</span>
</div>
</div>

Related

How to show and hide the data when the condition is true

In my angular application I have created the dashboard page. In that page I have created some conditions based on the condition it has to show on the view if the condition is false it will remove from the view page.
Dashboard.component.ts
var insidecirclespace1 = (lat,long) => {
for (var i=0; i< this.sensors.length;i++){
if (getDistanceFromLatLonInKm(this.sensors[i].latitude, this.sensors[i].longitude, lat,long) <= 5.0) {
circlemark[i].setStyle({ color: 'red', weight: 1, opacity: 7.0 });
"model": "1001",
"sensorname": "Sensor 1",
"rf": "1",
"Idref": "1"
});
}
}
outsidespace(latlngs[latlngidx1].lat,latlngs[latlngidx1].lon,latlngs02[latlngidx2].lat,latlngs02[latlngidx2].lon);
}
var insidecirclespace2 = (lat,long) => {
for (var i=0; i< this.sensors.length;i++){
if (getDistanceFromLatLonInKm(this.sensors[i].latitude, this.sensors[i].longitude, lat,long) <= 5.0) {
circlemark[i].setStyle({ color: 'red', weight: 1, opacity: 7.0 });
this.wifiDrones.push({
"model": "1002",
"sensorname": "Sensor 2",
"rf": "1",
"Idref": "1"
});
}
}
outsidespace(latlngs[latlngidx1].lat,latlngs[latlngidx1].lon,latlngs02[latlngidx2].lat,latlngs02[latlngidx2].lon);
}
var outsidespace = (lat1,long1,lat2,long2)=>{
for (var i=0; i< this.sensors.length;i++){
if (getDistanceFromLatLonInKm(this.sensors[i].latitude, this.sensors[i].longitude, lat1,long1) > 5.0 && getDistanceFromLatLonInKm(this.sensors[i].latitude, this.sensors[i].longitude, lat2,long2) > 5.0) {
circlemark[i].setStyle({ color: 'blue', weight: 1, opacity: 7.0 });
}
}
}
Based on the above condition the html page will show as
.component.html
<div class="col-sm-4">
<div class="card-body">
<h5 class="card-text " style="color: white;"> {{x.model}}
</h5>
<!--some code -->
</div>
</div>
Now my requirement is if the getdistanceInKm is less than 5 km It has to show the data on html page view if the distance is more than 5 km it has to remove (It will continuous for moving marker <5km or >5km)
It is coming for <5km for removing in >5km is not working can anyone please help me regarding this.
Using *ngIf will do the trick, you can use by providing your function inside the *ngIf or any property as follows:
using a propery:
in your ts file you could create a property for e.g. distance:
distance: number = 0;
and update your distance propery in your functions and using binding you could show and hide your HTML element as follows:
<div class="col-sm-4" *ngIf="distance < 5">
<div class="card-body">
<h5 class="card-text " style="color: white;"> {{x.model}}
</h5>
<!--some code -->
</div>
</div>
using a function, if the getdistanceInKm function will return a number this will do the job also:
<div class="col-sm-4" *ngIf="getdistanceInKm()< 5">
<div class="card-body">
<!--some code -->
</div>
</div>
Updated
Also you can use ViewChild as follows for changing the HTML element from ts file:
HTML file:
<div #distanceElement class="col-sm-4">
<div class="card-body">
<!--some code -->
</div>
</div>
ts file:
#ViewChild('distanceElement ') el:ElementRef;
and when changes occurs you can use:
this.el.nativeElement.style.display='none';
HTML
<div *ngIf="validation()">
<h5 class="card-text " style="color: white;"> {{x.model}}</h5>
</div>
.TS
validation() {
// logic if less than 5 km return
return true;
}
P.s This is just an overview, the syntax or the approach may not be perfect

Checking a substring from a programmatically added element in VueJs

I have a form that adds a new line on a button click. The new line must check for logic independently. In this case, it's chacking the first 2 digits of a barcode and associating it to a data set to see if it matches and return the appropriate value or "nothing found". I can't seem to get this right. First, it's not really evaluating at all. It only gives me "No Agency found" and second, it's doing it for all fields (because they all have the same v-model on a new line add). How can I achieve this so that it evaluates correctly and independently from each other?
Here's the relevant code in my template and script
<div id="q-app" class="q-pa-lg">
<div class="col-6">
<div v-for="(barcodefield, index) in barcodefields" :key="index">
<div class="flex q-pt-lg">
<div class="row flex-center">
<div class="col-3">
<div class="column">
<div class="row q-pr-lg items-center">
<label class="text-weight-medium">Starting Roll #:</label>
<q-input outlined square dense maxlength="24"
v-model.trim="barcodefield.start" ref="bcentry"></q-input>
</div>
</div>
</div>
<div class="col-3">
<div class="column">
<div class="row q-pr-lg items-center">
<label class="text-weight-medium">Ending Roll #:</label>
<q-input outlined square dense maxlength="24"
v-model.trim="barcodefield.end" #blur="showAgencyName" ref="bcentry"></q-input>
</div>
</div>
</div>
<div class="col-5">
<div class="column">
<label class="text-weight-medium">
Agency:
</label>
<div v-if="agencyName" style="min-height: 40px">
{{ agencyName }}
</div>
<div v-else style="min-height: 40px"></div>
</div>
</div>
<div class="col-1">
<div class="block float-right">
<q-btn v-if="index + 1 === barcodefields.length" #click="addLine" icon="add" color="primary" round />
<q-btn v-else style="min-width: 42px"/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
export default {
data() {
return {
barcodefields: [],
barcodeprefixes: {
"10": "Boston",
"11": "New York",
"13": "Houston",
"14": "Connecticut",
"16": "SIA",
"17": "Colorado",
"18": "Chicago",
"19": "Washington",
},
barcodefield: {
star: "",
end: ""
},
agencyName: "",
};
},
methods: {
addLine() {
this.barcodefields.push({
start: null,
end: null
});
},
showAgencyName() {
var str = this.barcodefield.end;
var res = str.substring(0, 2);
if (this.barcodeprefixes[res] == undefined) {
this.agencyName = "Agency not found";
} else {
this.agencyName = this.barcodeprefixes[res];
}
},
},
mounted() {
this.addLine();
}
}
Here is a codepen for you.
There are several things going on here:
First, as Simon points out, don't name loop variables the same thing as a top-level data element. Instead of <div v-for="(barcodefield, index) in barcodefields" :key="index">, do <div v-for="(item, index) in barcodefields" :key="index">. Then update all the barcodefield.start and barcodfield.end references to item.start and item.end.
Then, you need to get each item to have its own agencyName, instead of all of them referring to the same data.
Update showAgencyName to this:
showAgencyName(item) {
var str = item.end;
var res = str.substring(0, 2);
if (this.barcodeprefixes[res] == undefined) {
item.agencyName = "Agency not found";
} else {
item.agencyName = this.barcodeprefixes[res];
}
},
Then you can call it like this: #blur="showAgencyName(item)"
And use it in the html like so:
<div v-if="item.agencyName" style="min-height: 40px">
{{ item.agencyName }}
</div>
(And you can get rid of the top-level barcodefield in the data object, because it's not used anymore.)
Fiddle here:
https://jsfiddle.net/ebbishop/7r1pqx9f/
First you should change name of the for loop variable named "barcodefield", beacause you already have one in your data structure
Second, i would personnaly use a function {{ getAgencyName(b) }} instead of {{ agencyName }} otherwise you will have same agency name for all lines
There are a couple of problem with this.
First, you have a typo in the barcodefield data object. You have "star" instead of "start".
Secondly in the showAgency method you are referencing the this.barcodefield properties but that doesn't exist.
What you can do is pass the index of the barcodefield to the showAgencyName method, and use that inside the method to get the desired barcodefield from the barcodefields array.
In your html:
<q-input outlined square dense maxlength="24"
v-model.trim="barcodefield.end" #blur="showAgencyName(index)" ref="bcentry"></q-input>
and the showAgencyName method:
showAgencyName(index) {
const barcodefield = this.barcodefields[index]
var str = barcodefield.end;
var res = str.substring(0, 2);
if (this.barcodeprefixes[res] == undefined) {
this.agencyName = "Agency not found";
} else {
this.agencyName = this.barcodeprefixes[res];
}
}
UPDATE:
There is another problem that I didn't notice at first. The agencyName is overwritten every time you add a new barcodefield since it is kind of a global value.
I update the Codepen with the simplest solution I could think of. Return the name of the agency from the showAgencyName and use that to print it on the interface. There are many possible other solutions to this (for example add the name the the barcodefields object in the array).
Here is a working Codepen

VueJS Select only one element in a v-for

I'm using Vue v2
I'm trying to change only the properties of the selected element. See, when the response is marked after the click, it should change to a red color with a text that says 'Unmark'. And vice versa: if the button is clicked again (which now would say 'Unmark'), it should change to a green color and the text would be 'Mark'. Alas, it works.... Nevertheless, my code applies the change to every element inside the v-for; I only want that to happen to the selected element.
I've thought about using a Component to check if somethings changes, but first I'd like to see if there's a solutions for this. ANy help will be appreciated
Here's my code:
<div class="search-results">
<div class="activity-box-w" v-for="user in users">
<div class="box">
<div class="avatar" :style="{ 'background-image': 'url(' + user.customer.profile_picture + ')' }">
</div>
<div class="info">
<div class="role">
#{{ '#' + user.username }}
</div>
<div>
<div>
<p class="title">#{{ user.customer.name }}
#{{user.customer.lastname}}
</p>
</div>
</div>
</div>
<div class="time">
<input type="button" class="btn btn-sm btn-primary" v-on:click.prevent="markUser(user)" v-model="text"
v-bind:class="[{'green-border':notMarked}, {'red-border':marked}]" v-cloak v-if="!action"
:disabled="action"></input>
</div>
</div>
</div>
Now for the script:
new Vue({
el: '#engage-panel',
data: {
users: [],
mark: {'id' : '', 'marks' : ''},
text: 'Mark', //Migth change to Unmark later on
action: false,
marked: null,
notMarked: null,
},
methods:
{
markUser: function(user){
this.mark.id = user.id;
this.action= true;
Vue.http.headers.common['X-CSRF-TOKEN'] = $('meta[name="csrf-token"]').attr('content');
this.$http.put('/mark/user', this.mark).then(response => {
if(response.body === "marked")
{
this.mark.marks="true";
this.text = 'Unmark';
this.marked= true;
this.notMarked= false;
this.action= false;
}else{
this.mark.marks="false";
this.text = 'Mark';
this.marked= false;
this.notMarked= true;
this.action= false;
}
}).catch(e => {
this.action= false;
});
}
}
You can use $event.target on click if you just need to toggle css class.
fiddle here
But it's true that it's easier if a user has a status like marked = true/false for example, you just need to bind class like :
<input :class="{ 'green-border': user.marked, 'red-border': !user.marked }">
The issue my code applies the change to every element you met is caused by every user in v-for="user in users" uses one same object to indicates it is marked or not.
If your users data has one property like status to save current status (like unmark, mark etc), it is very simple, just change to next status when click mark button.
If your users data doesn't have that property, you need to create one dictionary, then save the users already clicked as key, the status for the user will be the value.
Below is one demo:
app = new Vue({
el: "#app",
data: {
users1: [{'name':'abc', 'status':'none'},
{'name':'xyz', 'status':'none'}],
users2: [{'name':'abc'}, {'name':'xyz'}],
selectedUsers: {}
},
methods: {
getNextStatusBaseOnRoute: function (status) {
if(status ==='marked') return 'marked'
let routes = {'none':'unmark', 'unmark':'marked'}
return routes[status]
},
markUser1: function (item) {
item.status = this.getNextStatusBaseOnRoute(item.status)
},
markUser2: function (item) {
let status = item.name in this.selectedUsers ? this.selectedUsers[item.name] : 'none'
// remember to use vue.$set when adding new property to one object
this.$set(this.selectedUsers, item.name, this.getNextStatusBaseOnRoute(status))
}
}
})
.marked {
background-color:green;
}
.unmark {
background-color:yellow;
}
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<h2>Case 1: </h2>
<div v-for="(item, index1) in users1" :key="'1'+index1">
<span>{{item.name}}:</span><span :class="[item.status]">{{item.status}}</span><button #click="markUser1(item)">Mark</button>
</div>
<h2>Case 2: </h2>
<div v-for="(item, index2) in users2" :key="'2'+index2">
<span>{{item.name}}:</span><span :class="[item.name in selectedUsers ? selectedUsers[item.name] : 'none']">{{item.name in selectedUsers ? selectedUsers[item.name] : 'none'}}</span><button #click="markUser2(item)">Mark</button>
</div>
</div>
For Vue3, you can also store the index of the selected element
<ul role="list" class="">
<li class="relative" v-for="(image, index) of images" :class="selectedImage == index? 'border-indigo-500 border-2': 'border-transparent'" >
<div #click="selectedImage = index" class="">
<img :src="image" alt="" class="object-cover pointer-events-none group-hover:opacity-75">
</div>
</li>
</ul>

Toggle other elements inside ng-repeat on ng-click

I'm trying to apply class by using ng-class and ng-click and this working fine for selected element, but how can I toggle this class in the other elements?
Improved description:
Current behaviour:
Click on elelement, class applied.
Clik on the other element, this element also class applied.
Desired behaviour:
Click on element, class applied.
Other element - class removed.
<div ng-repeat="element in ngModel | orderBy:'Field_Order'" class='elementForm' ng-hide="element.IsDeleted">
<div layout="row" style="width:100%" class="container" ng-mouseover="hovering=true" ng-mouseleave="hovering=false" flex ng-click="selected = !selected">
<div class="hover-space" ng-class="{'hoveredFormElement':hovering, 'selected':selected}" flex="2" ></div>
....
</div>
</div>
The problem is, selected is inside an isolated scope which is not shared by other items.
One easy solution using index is
var app = angular.module('my-app', [], function() {})
app.controller('AppController', function($scope) {
$scope.selected = -1;
$scope.ngModel = [{
i: 1
}, {
i: 2
}, {
i: 3
}, {
i: 4
}];
})
.hoveredFormElement {
color: green;
}
.selected {
background-color: grey;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="my-app">
<div ng-controller="AppController">
{{selected}}
<div ng-repeat="element in ngModel | orderBy:'Field_Order'" class='elementForm' ng-hide="element.IsDeleted">
<div layout="row" style="width:100%" class="container" ng-mouseover="hovering=true" ng-mouseleave="hovering=false" flex ng-click="$parent.selected = $parent.selected == $index ? -1 : $index">
<div class="hover-space" ng-class="{'hoveredFormElement':hovering, 'selected':selected == $index}" flex="2">{{element}}</div>
</div>
</div>
</div>
</div>
Maintain flag in controller scope and use it over ng-class
$scope.selected = { index: undefined };
Markup
<div ng-repeat="element in ngModel | orderBy:'Field_Order'" class='elementForm' ng-hide="element.IsDeleted">
<div layout="row" style="width:100%" class="container"
ng-mouseover="hovering = true"
ng-mouseleave="hovering = false"
flex ng-click="selected.index = !selected">
<div class="hover-space"
ng-class="{'hoveredFormElement':hovering , 'selected':selected.index }" flex="2" ></div>
....
</div>
</div>

Ng-repeat on integer value

I'm trying to use ng-repeat on a div which should contain a star image, each pie in the JSON has a rating property from 1-5, and I want to use this value to loop out x number of stars. I've got this working somewhat but it's flawed in the way that I can't re-sort the array and make the stars follow the correct item in the list since I'm using [$index] to track the iteration.
My solution is rather ugly as well since I'm creating arrays with as many index placeholders as the value of the rating property, and then pushing this into an array to loop out the appropriate number of images. I would like to have a more elegant solution.
How should I go about this problem without using [$index]?
Snippet of the JSON:
{"pies": [
...
{
"name": "Blueberry pie",
"imageUrl": "img/blueberrypie.png",
"id": "1",
"rating": "5", //Ng-repeat depending on this value
"description": "Blueberry pie is amazing."
},
...
]}
My controller:
pieShopApp.controller('shopCtrl', ['$scope', '$http', '$routeParams', function ($scope, $http, $routeParams) {
$scope.pieId = $routeParams.pieId,
$scope.sortingOptions = ['A-Z', 'Rating'],
$scope.sortingValues = ['name', 'rating'],
$scope.ratings = [],
$http.get('jsons/pies.json')
.success(function(data, status) {
$scope.pies = data;
for (i = 0; i < $scope.pies.pies.length; i++) {
switch ($scope.pies.pies[i].rating) {
case "1": $scope.ratings.push(["1"]); break;
case "2": $scope.ratings.push(["1", "2"]); break;
case "3": $scope.ratings.push(["1", "2", "3"]); break;
case "4": $scope.ratings.push(["1", "2", "3", "4"]); break;
case "5": $scope.ratings.push(["1", "2", "3", "4", "5"]); break;
}
}
console.log($scope.ratings);
})
.error(function(status) {
console.log(status);
})
}]);
The list which contains the pie items:
<div id="pie-list-wrapper">
<ul class="nav">
<a href="#/pies/pieid" ng-repeat="pie in pies.pies | filter:query | orderBy:orderProp">
<li class="list-item rounded-corners box-shadow">
<aside>
<img src="{{pie.imageUrl}}" no-repeat alt="Image of the pie">
</aside>
<header>
<h1 ng-bind="pie.name" id="item-name" class="bold-text"></h1>
</header>
<article>
<span ng-bind="pie.description" id="item-desc"></span>
</article>
<footer id="item-rating">
<div ng-repeat="rating in ratings[$index]" class="rating-box"></div> //Contains the stars
</footer>
</li>
</a>
</ul>
</div>
Outcome:
Checkout this
<div ng-app='myApp' ng-controller="Main">
<span ng-repeat="n in range('5')">Start{{$index}} </span>
</div>
$scope.range = function(count){
var ratings = [];
for (var i = 0; i < count; i++) {
ratings.push(i)
}
return ratings;
}
Change your html to following
<div id="pie-list-wrapper">
<ul class="nav">
<a href="#/pies/pieid" ng-repeat="pie in pies.pies | filter:query | orderBy:orderProp">
<li class="list-item rounded-corners box-shadow">
<aside>
<img src="{{pie.imageUrl}}" no-repeat alt="Image of the pie">
</aside>
<header>
<h1 ng-bind="pie.name" id="item-name" class="bold-text"></h1>
</header>
<article>
<span ng-bind="pie.description" id="item-desc"></span>
</article>
<footer id="item-rating">
<div ng-repeat="start in range(pie.rating)" class="rating-box"></div> //Contains the stars
</footer>
</li>
</a>
</ul>
</div>
I solved this in this way:
"items" is your array of objects in the $scope, accesing the property "rating", you can show the star if the value is less or the same comparing to the "rating" property.
In this example I'm using some icon fonts but for the case of an image is the same thing.
<div ng-repeat="item in items">
<div class="item-offers"">
<img ng-src="{{item.image}}">
<div class="item-not-rating">
<i class="icon ion-ios-star icon-rating" ng-if="item.rate >= 1"></i>
<i class="icon ion-ios-star icon-rating" ng-if="item.rate >= 2"></i>
<i class="icon ion-ios-star icon-rating" ng-if="item.rate >= 3"></i>
<i class="icon ion-ios-star icon-rating" ng-if="item.rate >= 4"></i>
<i class="icon ion-ios-star icon-rating" ng-if="item.rate >= 5"></i>
</div>
</div>
</div>
I've found a better solution that solves this requirement at all:
https://github.com/fraserxu/ionic-rating
It looks like you are iterating on the pies and that's where the $index gets its value from.
Instead of ng-repeat="rating in ratings[$index]"
you should use ng-repeat="rating in range(pie.rating)"
This way, the rating would follow your pie when ordering.
Then you could completely remove the loop in the controller.
Could you provide just a bit more HTML so that we could see where the $index comes from?
Regards,
Camusensei
EDIT:
You are indeed iterating over pies.pies in
ng-repeat="pie in pies.pies | filter:query | orderBy:orderProp"
So what I wrote earlier should work. See below for exhaustive changes.
Controller:
$http.get('jsons/pies.json')
.success(function(data, status) {
$scope.pies = data;
})
.error(function(status) {
console.log(status);
})
HTML:
<div ng-repeat="rating in range(pie.rating)" class="rating-box"></div>
EDIT2: Sorry, I forgot the range function (inspired from Ariya Hidayat):
$scope.range = function(count){
return Array.apply(0, Array(+count));
}
The problem is that ng-repeat only works with arrays or objects, so you can't say iterate x times (while x is a number)
A solution could be to write a function in JavaScript:
$scope.getNumber = function(num) {
return (new Array(num));
}
An then use this html to show the stars without $index:
<div ng-repeat="rating in getNumber(pie.rating)"></div>
In Angular 1.4+ you get the following error when using an empty array:
Error: [ngRepeat:dupes] Duplicates in a repeater are not allowed.
The following works:
$scope.range = function(count) {
return Array.apply(0, Array(+count)).map(function(value,index){
return index;
});
}
<div ng-app='myApp' ng-controller="Main">
<span ng-repeat="n in range(5)">Start{{$index}} </span>
</div>

Categories