using ng-click to update every cell in a table column - angularjs

I'm new to Angular and I was trying to figure out how to have the data in one of the columns in a table I made convert from one number format to another when the user clicks on the top of the table cell. I've create a filter already, but i don't know how to call it so it effects all the cells in the table.
<tr ng-repeat="x in data">
<td>{{x.id}}</td>
<td>{{x.name}} </td>
<td>{{x.desc}}></td>
<td>{{x.number}}</td> <--- THIS IS WHAT I WANT TO CONVERT
</tr>
I'm not even sure where to start with this. I basically have a ng-click directive call "convert" which I've defined in the controller. I know that if I define a variable in the $scope (such as $scope.foo = "1") and then call the convert() function I can replace the value like this:
$scope.convert = function(){
$scope.foo = 2;
}
And then my table updates with that value. But what if every table cell in that column has a different value, and I basically want to run that value through a filter I've made.
Any suggestions on how to approach this?

You said you already have a filter?
Then just give your filter an argument 'numberFormat':
angular.
module('yourModule').
filter('yourFilter', function() {
return function(input, numberFormat) {
// convert the input according to the numberFormat
return filteredValue;
};
});
Then you can update the format in your convert() scope-method:
$scope.convert = function(){
$scope.numberFormat = 'long';
}
and pass it to your filter:
<td>{{x.number | yourFilter:numberFormat}}</td>
BTW:
Read about controllerAs - IMHO it is a better practice to store values at the controller rrather than directly on the scope.

Your function simply needs to update number property of the each object in the data array. You could do it like this:
$scope.convert = function() {
$scope.data.forEach(function(item) {
item.number = item.number + 2; // Convert number somehow
});
};

I assume click handler on table header of column as below
<th ng-click="convert()">column of number</th>
in the controller write the function as below
$scope.convert = function() {
$scope.data.forEach(function(obj) {
obj.number += 1;
})
//$scope.$apply() use this is template is not refreshed
}

Related

Repeat an item in ng-repeat

I have an array of objects whereby there is a need to allow the user to add an item that is a duplicate of another. It is triggered when they increase the quantity where a property called 'HasPers' is true.
The HTML markup looks like:
<td width="10%"><input type="number" value="{{productdetails.Quantity}}" ng-model="productdetails.Quantity" min="1" ng-change="change(productdetails)" /></td>
and the function is:
$scope.change = function (item) {
if (item.HasPers) {
item.Quantity = 1;
$scope.items.push(item);
}
};
Initially this had a problem in the repeater wherby it knew it was a duplicat object but this was solved by adding 'track by $index' in the repeater
<tr ng-repeat="productdetails in items track by $index">
http://mutablethought.com/2013/04/25/angular-js-ng-repeat-no-longer-allowing-duplicates/
but the item is clearly still associated with the original since when I change some properties on the original one they change on the duplicate one too. I really want to avoid typing out all of the properties again as in
How to add new key in existing array in angular js?
is there any way to create a new item that is identical to the one being passed in but is a different object?
try
$scope.change = function (item) {
if (item.HasPers) {
var copyItem=angular.copy(item);
copyItem.Quantity = 1;
$scope.items.push(copyItem);
}
};
Try by using angular.copy
$scope.change = function (item) {
if (item.HasPers) {
var newItem = angular.copy(item);
newItem.Quantity = 1;
$scope.items.push(newItem);
}
};

How to get previous value and compare with new value before bind value in angularjs

I'm working with real time application to show current gold market rates. I'm getting service by ajax and convert the value and displaying into tables. Currently create dynamically this table and append into div. So i can compare with table row data with currently getting value and can set price up/ down or normal color value like bellow,
var tableid = document.getElementById("ratetable");
for(var i=1;i<tableid.rows.length;i++) {
selling_rate = data.Commodities.Commodity.selling_rate;
buying_rate = data.Commodities.Commodity.buying_rate;
if(tableid.rows[i].cells[1].innerHTML > selling_rate) {
tableid.rows[i].cells[1].style.color = "#FFFFFF"; //Red
tableid.rows[i].cells[1].style.background = "#FF0000";
} else if(tableid.rows[i].cells[1].innerHTML < selling_rate) {
tableid.rows[i].cells[1].style.color = "#ffffff"; //Green
tableid.rows[i].cells[1].style.background = "#2636f2";
} else {
tableid.rows[i].cells[1].style.color = "#000000"; //black
tableid.rows[i].cells[1].style.background = "";
}
tableid.rows[i].cells[1].innerHTML = selling_rate;
}
For this i used bellow code to create table in angular js
<table width="480" border="0" cellpadding="0" cellspacing="0" class="rate-table" id="ratetable">
<tr class="table-title">
<td width="297">DESCRIPTION</td>
<td width="183">PRICE</td>
</tr>
<tr ng-repeat="commodity in commodityrate.Commodity" ng-class-odd="'oddrow'" ng-class-even="'silver'">
<td width='297'>{{commodity.name}}</td>
<td width='183'>{{commodity.selling_rate}}</td>
</tr>
</table>
But I need to check value (compare new value with previous value) and update color code. for this i tried like
ng-class="{highcolor: $scope.text>commodity.selling_rate, lowcolor: $scope.text<commodity.selling_rate, normalcolor: $scope.text==commodity.selling_rate}"
But this didn't work, how to do this in angular js. Is there any way to do this dynamic created value to compare by angular $watch. Please any one help me.
Currently I don't come up with any idea about how to store or get previous value in brackets expressions, but there is a workaround: watch these elements' model change.
Instead of adding a $watch on every single element, you can watch your commodity list commodityrate.Commodity with setting objectEquality as true
scope.$watch('commodityrate.Commodity', function(newValue, oldValue) {
//iterating newValue and compare it the corresponding element in oldValue.
},
true);
BTW, you still need to add a new property like priceTrend to Commodity and use this to define your ngClass.
Since you always have to check every Commodity's price change when fetching new prices from backend, watch commodityrate.Commodity with objectEquality enabled won't lead you to worse performance.

angular filter name as variable

I'm designing universal table that reads data and columns from ajax.
In columns description is also filter name which angular should use for a specific column.
But in HTML templates I can't use variables for filter names:/
Is there a solution for that? Or should I code javascript loop with data source?
Here is code example:
<tr ng-repeat="item in data">
<td ng-repeat="col in cols">
{{item[col.source]}}
<span ng-if="col.ngFilter">
{{col.ngFilter}} // ex. "state" filter
{{item[col.source]|col.ngFilter}} //is not working - is looking for "col.ngFilter" not "state" filter.
</span>
</td>
</tr>
You cannot do it in your HTML. First, you need to apply the filter in your controller.
function MyCtrl($scope, $filter) {
$scope.applyFilter = function(model, filter) {
return $filter(filter)(model);
};
}
Then, in your HTML:
Instead of
{{item[col.source]|col.ngFilter}}
use
{{applyFilter(item[col.source], col.ngFilter)}}
For anyone looking to do something like
{{applyFliter(item[col.source], col.ngFilter)}}
where ngFilter might contains some colon separated parameters such as
currency:"USD$":0
I ended up writing this little helper
function applyFilter (model, filter){
if(filter){
var pieces = filter.split(':');
var filterName = pieces[0];
var params = [model];
if(pieces.length>1){
params = params.concat(pieces.slice(1));
}
return $filter(filterName).apply(this,params);
}else{
return model;
}
}

Table filter by predicate

I made a jsfiddle to show what is my problem.
The fisrt part is working in a partial way. See line number 15. I put the predicate in the filter (predicate is l_name) by hand and is working. The table is filtered by Last Name column.
<tr ng-repeat="item in items | filter:{l_name:myInput}">
The second part of the sample is not working when I use the select (model named mySelect2) to choose the predicate where I'm going to filter (see line number 36).
What I'm trying to do is use the select to choose the column by predicate and the input to filter in that column.
<tr ng-repeat="item in items | filter:{mySelect2:myInput2}">
Am I missing something or the binding of the select (mySelect2) must update the filter on the table?
Thanks for the help!
PS: type jo in the input.
Here's a fiddle with some options: http://jsfiddle.net/jgoemat/tgKkD/1/
Option 1 - Search on multiple fields
You can use an object on your model ('search' here) as your filter and separate input boxes for l_name and f_name. This allows you not only to filter on either, but filter on both:
any: <input ng-model="search.$"/><br/>
l_name: <input ng-model="search.l_name"/><br/>
f_name: <input ng-model="search.f_name"/><br/>
<!-- skipping code -->
<tr ng-repeat="item in items|filter:search">
Option 2 - Use a function on your controller
The built-in filter can take a function as an argument that should return true if the object should be included. This function takes the object to be filtered as its only argument and returns true if it should be included. Html:
<tr ng-repeat="item in items|filter:filterFunc">
controller function:
$scope.filterFunc = function(obj) {
// property not specified do we want to filter all instead of skipping filter?
if (!$scope.mySelect)
return obj;
if (obj[$scope.mySelect].toLowerCase().indexOf($scope.myInput.toLowerCase()) >= 0)
return obj;
return false;
};
Option 3 - Create a custom filter
This filter function will take the whole list as an argument and return the filtered list. This does require you to create an angular module and specify it in the ng-app tag like ng-app="MyApp"Html:
<tr ng-repeat="item in items|MyFilter:mySelect:myInput">
Code:
var app = angular.module('MyApp', []);
app.filter('MyFilter', function() {
return function(list, propertyName, value) {
console.log('MyFilter(list, ', propertyName, ', ', value, ')');
// property not specified do we want to filter all instead of skipping filter?
if (!propertyName)
return list;
var newList = [];
var lower = value.toLowerCase();
angular.forEach(list, function(v) {
if (v[propertyName].toLowerCase().indexOf(lower) >= 0)
newList.push(v);
});
return newList;
}
});
Option 4: ng-show
The built-in filter filter expressions don't let you use any expression, but ng-show does so you can just limit visible items like so:
<tr ng-show="item[mySelect].toLowerCase().indexOf(myInput.toLowerCase()) >= 0 || !mySelect" ng-repeat="item in items">
I think option 1 is easy and flexible. If you prefer your drop-down + field UI then I think option 3 is the most useful, and you can re-use it as a dependency in other apps like this:
var app = angular.module("NewApp", ["MyApp"]);
I would just name it something better like 'filterByNamedProperty'. Option 2 is easy but it is tied to your controller. Option 4 is messy and I wouldn't use it.
What about using a custom filter? Users concatenate the property with the criteria (e.g. last:jo). In the filter, split on the colon, and use the first part as the property name and the second part as the criteria.
You may pass scope variables to your filters:
<tr ng-repeat="item in items | filter:myScopeVariable">
This means that you may define your filter object in controller and it will be used by the filter:
$scope.$watch('mySelect2', function(val){
$scope.myScopeVariable = {};
$scope.myScopeVariable[val] = $scope.myInput2;
});
$scope.$watch('myInput2', function(val){
$scope.myScopeVariable = {};
$scope.myScopeVariable[$scope.mySelect2] = $scope.myInput2;
});
Demo Fiddle

Adding a computed field to every element of an array in an AngularJS model

I'm pulling an array of users into my AngularJS model from a JSON datasource. This data is being rendered in a table, and I'd like to create a column that is computed from two values of the existing user object, without modifying my underlying data service.
// My model
function UserListCtrl($scope,$http) {
$http.get('users').success(function(data) {
$scope.users = data;
});
};
In my partial template, I know I can do something like this:
<tr ng-repeat="for user in users">
<td>{{user.data / user.count | number:2}}</td>
</td>
But I'd rather add that field into the model, so I can use it like so:
<td>{{user.amplification}}</td>
How do I add the "amplification" field to every user in my model?
As an aside, is it possible to use the orderBy filter on something like this:
<td>{{user.data / user.count | number:2}}</td>
You can eather:
Just after loading user do:
$http.get('users').success(function(data) {
$scope.users = data;
$scope.user.amplification() = function() { return $scope.user.data / $scope.user.count; }
});
And use as {{user.amplification()}}
Anywhere at controller:
$scope.$watch('user', function() {
$scope.userAmplification = $scope.user.data / $scope.user.count;
}, true);
$http.get
Or if user.data/count do not change, do same as 1. but staticly calculate:
$http.get('users').success(function(data) {
$scope.users = data;
$scope.user.amplification = $scope.user.data / $scope.user.count;
});
And OrderBy could be used on any expression (uncluding result of other filter)
If you don't need your amplicification() function to update when the data and count properties on your user update, you can do something like this in your controller:
$scope.users.forEach(function(user) {
user.amplification = function() {
return user.data / user.count;
};
});
Adding a second answer as I feel it's appropriate as it's distinct from my first one.
After a little looking around, I found the method I originally posted falls over if you try to add new rows dynamically, or new elements to the array which depend on the computed value. This is because the $scope.array.forEach() will only run when the controller is created.
The best way to solve this problem is to create a properly defined object which contains the options you want. e.g.
function Task(id, name, prop1, prop2) {
this.id = id;
this.name = name;
this.prop1 = prop1;
this.prop2 = prop2;
this.computedProperty = function () {
return this.prop1 + this.prop2;
};
}
This is far more flexible as each new object created will have the new property.
The only downside is that in your ajax success callback, you'll need to pass each of your users into your 'Users()' constructor.
What worked for me was to add a loop and add the property to each item in that loop. I used a property of the controller but I am sure you can use scope the way you are approaching it in the question.
function(result) {
self.list = result;
angular.forEach(self.list, function(item) {
item.hasDate = function() {
return this.TestDate != null;
}.bind(item); // set this context
});
}
Then in my markup I just used it like this.
<div ng-repeat...>
<div ng-show="item.hasDate()">This item has a date.</div>
</div>

Resources