Function in angular expression not updating when parameter has changed - javascript

Here is a simplified example of what I'm trying to do.
(function(){
var app=angular.module('Tester',[]);
app.controller('TestController',function($scope){
$scope.days=[{name:'Sun'},{name:'Mon'},{name:'Teu'},{name:'Wed'},{name:'Thu'},{name:'Fri'},{name:'Sat'}];
//if I is use `days=['Sun','Mon'...]` it works
$scope.values={};
$scope.add=function(values){
var sum=0;
$scope.days.map(function(v){
v=values[v];
if(v){
sum+=(v-0);
}
});
return sum;
}
});
app.filter('sum',function(){
return function(obj){
var total=0;
for(var v in obj){
total+=(obj[v]-0)||0;
}
return total;
}
});
})()
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="Tester">
<div ng-controller="TestController as test">
<input ng-model="values[day.name]" value="{{values[day.name]||0}}" ng-repeat="day in days">
<div>{{add(values)}}</div>
<div>{{values | sum}}</div>
</div>
</div>
Basically I want to have a row of inputs and a total to show the sum. It works if I use days=['Sun','Mon'...] or a filter. But the filter seems semantically incorrect as well as not allowing me to add other properties to the values object (value={'Mon':2,'time':123456789} would give an incorrect sum) and days=[{name:'Sun'}...] allows me to store more information than days=['Sun','Mon'...] about each day.

You need to edit your add function a bit, changing v=values[v] to v=values[v.name] inside the inner map function.
This is because your days array now contains objects ({name: 'Mon'} etc), so...
$scope.days.map(function (v) {
// in here v = {name: 'Mon'}, not 'Mon' as previously,
// but the values object still has day names as keys:
// {'Sun': 2, 'Mon': 1} etc.
// --> values[v] === undefined, but values[v.name] matches
})
Below is the edit in place, and it works as I presume you wanted it to work. Oh, and I added the same (v-0)||0 trick to the sum calculations, so that entering invalid data (text for example) doesn't break it up.
(function(){
var app=angular.module('Tester',[]);
app.controller('TestController',function($scope){
$scope.days=[{name:'Sun'},{name:'Mon'},{name:'Teu'},{name:'Wed'},{name:'Thu'},{name:'Fri'},{name:'Sat'}];
//if I is use `days=['Sun','Mon'...]` it works
$scope.values={};
$scope.add=function(values){
var sum=0;
$scope.days.map(function(v){
v=values[v.name];
if(v){
sum+=(v-0)||0;
}
});
return sum;
}
});
app.filter('sum',function(){
return function(obj){
var total=0;
for(var v in obj){
total+=(obj[v]-0)||0;
}
return total;
}
});
})()
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="Tester">
<div ng-controller="TestController as test">
<input ng-model="values[day.name]" value="{{values[day.name]||0}}" ng-repeat="day in days">
<div>{{add(values)}}</div>
<div>{{values | sum}}</div>
</div>
</div>

Related

How can i plus a integer value after get sum result using jquery?

Actually, I want to get integer value this is working when i'm inserting value suppose i insert value 4+5 in input then I'm getting it in my result then I'm gettting it in another div with result like if I get 4+5 then it should be 9.
But i'm getting [object Object]
What is wrong in my code?
$(document).ready(function(){
$('button').click(function(){
var oldValue;
var getValue;
var boxValue;
oldValue = $('.box').text();
getValue = $('.getValue').val();
boxValue = $('.box').text(oldValue + getValue);
$('.finalResult').text(boxValue);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button>Button</button>
<input type="text" class="getValue">
<div class="box"></div>
<div class="finalResult"></div>
$(document).ready(function(){
$('button').click(function(){
var oldValue;
var getValue;
var boxValue;
oldValue = $('.box').text();
getValue = $('.getValue').val();
boxValue = $('.box').text(oldValue + getValue);
$('.finalResult').text(Number(oldValue || '0') + eval(getValue));
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button>Button</button>
<input type="text" class="getValue">
<div class="box"></div>
<div class="finalResult"></div>
$(document).ready(function(){
$('button').click(function(){
var oldValue;
var getValue;
var boxValue;
oldValue = $('.box').text();
getValue = $('.getValue').val();
if(!isNaN(oldValue) && !isNaN(getValue) )
{
boxValue = $('.box').text(oldValue + getValue);// in this line you are setting a value and assignig it to boxValue
$('.finalResult').text(boxValue.text());//I am replacing this line
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button>Button</button>
<input type="text" class="getValue">
<div class="box">0</div>
<div class="finalResult"></div>
see this fiddle
I'm not quite sure what your ultimate goal is, but the [Object Object] occurs because you are placing the jQuery representation of .box into the DOM, which is an object.
Try changing these lines ...
oldValue = +$('.box').text() || 0;
getValue = +$('.getValue').val();
$('.finalResult').text(boxValue.text());
The first two lines are one way to ensure you have a number; the last places the correct "text".
You are getting [objec object] because you are actually trying to set an object in the div, below see the problem line. What you are doing is that you calculate the some and set the result .finalResult div but you are actually assigning the div to a variable and jquery is treating that variable as an object
$(document).ready(function(){
$('button').click(function(){
var oldValue;
var getValue;
var boxValue;
oldValue = $('.box').text();
getValue = $('.getValue').val();
boxValue = $('.box').text(oldValue + getValue); //<-- problem
$('.finalResult').text(boxValue);
});
});
change the following line
$('.finalResult').text(boxValue);
with this one
$('.finalResult').text(boxValue.text());

angularjs filters unique filters

How to enable or disable the radio buttons according to available options in the array
// Code goes here
var app = angular.module('app', ['ui.directives','ui.filters']);
app.controller('ctrl', function($scope, $filter,$http) {
$scope.pageNumber = {};
$http.get('https://cdn.rawgit.com/rainnaren/20355aea389ba6f7bfb801ecb52774d6/raw/4482a3d9f5a0cc3e73720a11e0e2b60c085519c0/data.json').then(function(data){
$scope.data = data.data;
angular.forEach($scope.data.products,function(key,value){
angular.forEach(key.productAttributes,function(k,val){
// console.log(key.productAttributes)
// $scope.brandsGroup = uniqueItems(key.productAttributes, val);
// console.log($scope.brandsGroup)
})
})
});
// var uniqueItems = function (data, key) {
// var result = [];
// for (var i = 0; i < Object.keys(data).length; i++) {
// var value = data[i][key];
// if (result.indexOf(value) === -1) {
// result.push(value);
// }
// }
// return result;
// };
$scope.$watch(function() {
return $scope.pageNumber;
}, function() {
$scope.v = function(event,d) {
var count = Object.keys($scope.pageNumber.productAttributes).length;
console.log(count);
if ($scope.data.variantAttributeses.length === count) {
$scope.x = $filter('filter')($scope.data.products, $scope.pageNumber);
}
};
});
});
<!DOCTYPE html>
<html ng-app="app">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.6/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui/0.4.0/angular-ui.min.js"> </script>
<link rel="stylesheet" href="style.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-controller="ctrl">
{{pageNumber}}
<div ng-repeat="d in data.variantAttributeses | unique:d">
{{d.name}}
<div>
<form ng-repeat="dg in data.products | filter:pageNumber ">
<label><input ng-change="v()" type="checkbox" ng-model="pageNumber.productAttributes[d.name]" ng-true-value="'{{dg.productAttributes[d.name] }}'" ng-false-value="''">{{dg.productAttributes[d.name] }}</label>
</form>
</div>
</div>
<div>
<div ng-repeat="data in x | filter:pageNumber:true">
{{data.brandProductId}}
</div>
</div>
</body>
</html>
here the unique filter is not working what is the solution to do this i tried the angular ui filters but in my case it is not working anyone provide me the custom filter for that.
in this the field name is also dynamic . so how to filter the duplicates from the json
here is my working code
The logic for writing the function that determines what is disabled isn't trivial (I just tried for about 15 minutes) ultimately you can use ng-disabled and set a property on the "option" in the data so that it gets reflected in the view, something like
ng-disabled="d.disabled"
ng-change="checkDisabled()"
in the checkDisabled you'd have to run the logic to determine which options actually get the "disabled" attribute added to them for use by ng-disabled here.
Also to keep the filtered data on scope can use:
<div ng-repeat="data in data.products | filter:pageNumber as filtered">
then you will have a $scope.filtered with the filtered products. The trick is you need to go through all radio options and for each one you need to search all products and see if any product doesn't match the option value. If it matches any value then disabled for that option =false if it matches no values of any or the filtered products then it's disabled=true.

Knockout computed does not auto update

The debug console output shows that an option change does change the knockout data and the result of the change can be seen by forcing the computed to run by pressing a button on the last line on the html page but for some reason a ko.computed that is displayed on the second last line of the html does not auto update as I would expect.
Any help would be appreciated!
Code
JSFiddle Reference
function CustomBindingViewModel() {
var self = this;
self.range = [1, 2, 3, 4, 5];
self.opts = [
"Functionality, compatibility, pricing - all that boring stuff",
"How often it is mentioned on Hacker News",
"Number of gradients/dropshadows on project homepage",
"Totally believable testimonials on project homepage"
];
self.optionRange = 5;
self.options = ko.observableArray();
self.opts.forEach(function(option) {
self.options.push({
option: option,
importance: 2
});
});
self.points = function() {
var sum = 0;
var sumStr = "";
self.options().forEach(function(option) {
sum += option.importance;
sumStr += option.importance;
});
console.log(sumStr);
return 10 - sum;
};
}
ko.applyBindings(new CustomBindingViewModel());
.points {
font-weight: bold;
}
.select {
text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.1/knockout-min.js"></script>
<h1>Knockout Custom Bindings</h1>
<h2>Which factors affect your technology choices?</h2>
<p>Please distribute <span class="points">10</span> points between the following options.</p>
<table>
<thead>
<tr>
<td>Option</td>
<td>Importance</td>
</tr>
</thead>
<tbody data-bind="foreach:options">
<tr>
<td data-bind="text:option"></td>
<td class="select">
<select data-bind="options:$root.range, value:importance"></select>
</td>
</tr>
</tbody>
</table>
<p>You've got <span class="points" data-bind="text:points()"></span> points left to use.</p>
<button data-bind="click:$root.points()">Finished</button>
While the array is an observable array the values in the array are not observable.
Make the importance property of the items in the array observable as well since that is what you want to watch.
//...other code removed for brevity
self.options = ko.observableArray();
self.opts.forEach(function(option) {
self.options.push({option:option, importance:ko.observable(2)}); //<-- NOTE THIS HERE
});
//...other code removed for brevity
so that the event fires when importance values change.
You would then need to update how the points are calculated.
//...other code removed for brevity
self.points = function() {
var sum = 0;
var sumStr = "";
self.options().forEach(function (option) {
sum += option.importance(); //<-- NOTE THE BRACES
sumStr += option.importance(); //<-- NOTE THE BRACES
});
console.log(sumStr);
return 10 - sum;
};
//...other code removed for brevity
And Here is the completed js fiddle

Angular js function logic and repetition error

I'm having a logic error with my code using angular js. What I have done is made a function that loops through a json array and returns the strings of the weather condition, eg
'clear',
'cloudy', etc...
It then checks to see if the value of the string is equal to another string. If it is, it returns an image link associated with the weather condition. The problem is that html ng-repeat function is repeating that one image and not any other image.
Here is the js:
var app=angular.module('app');
app.controller('MainCtrl', function($scope, $http) {
$scope.currentSydney = null;
$scope.currentMelbourne = null;
$scope.currentAdelaide = null;
$scope.currentDarwin = null;
$scope.currentBrisbane = null;
$scope.currentMelbourne = null;
$scope.currentCairns = null;
$http.jsonp('http://api.wunderground.com/api/5ad0204df4bdbeff/conditions/q/Australia/Melbourne.json?callback=JSON_CALLBACK').success(function(data){
$scope.currentMelbourne=data;
});
$http.jsonp('http://api.wunderground.com/api/5ad0204df4bdbeff/conditions/q/Australia/Sydney.json?callback=JSON_CALLBACK').success(function(data){
$scope.currentSydney=data;
});
$http.jsonp('http://api.wunderground.com/api/5ad0204df4bdbeff/conditions/q/Australia/Adelaide.json?callback=JSON_CALLBACK').success(function(data){
$scope.currentAdelaide=data;
});
$http.jsonp('http://api.wunderground.com/api/5ad0204df4bdbeff/conditions/q/Australia/Darwin.json?callback=JSON_CALLBACK').success(function(data){
$scope.currentDarwin=data;
});
$http.jsonp('http://api.wunderground.com/api/5ad0204df4bdbeff/conditions/q/Australia/Perth.json?callback=JSON_CALLBACK').success(function(data){
$scope.currentPerth=data;
});
$http.jsonp('http://api.wunderground.com/api/5ad0204df4bdbeff/conditions/q/Australia/Cairns.json?callback=JSON_CALLBACK').success(function(data){
$scope.currentCairns=data;
});
$http.jsonp('http://api.wunderground.com/api/5ad0204df4bdbeff/conditions/q/Australia/Brisbane.json?callback=JSON_CALLBACK').success(function(data){
$scope.currentBrisbane=data;
$scope.cityData=[
{ name:'Brisbane',
temp:$scope.currentBrisbane.current_observation.temp_c,
image:$scope.currentBrisbane.current_observation.icon
},
{ name:'Melbourne',
temp:$scope.currentMelbourne.current_observation.temp_c,
image:$scope.currentMelbourne.current_observation.icon
},
{
name:'Adelaide',
temp:$scope.currentAdelaide.current_observation.temp_c ,
image:$scope.currentAdelaide.current_observation.icon
},
{ name:'Darwin',
temp:$scope.currentDarwin.current_observation.temp_c ,
image:$scope.currentDarwin.current_observation.icon
},
{ name:'Perth',
temp:$scope.currentPerth.current_observation.temp_c ,
image:$scope.currentPerth.current_observation.icon
},
{ name:'Cairns',
temp:$scope.currentCairns.current_observation.temp_c,
image:$scope.currentCairns.current_observation.icon
},
]
for(y = 0 ; y < 6; y++){
var string = $scope.cityData[y].image;
console.log(string[10]);
}
});
$scope.iconString = function() {
switch ($scope.currentSydney.current_observation.icon) {
case 'partlycloudy' :
return 'pics/partlycloudy.png';
case 'clear' :
return 'pics/partlycloudy.png';
}
}
$scope.repeat = function() {
for(y = 0 ; y < 1; y++){
var string = $scope.cityData[y].image;
if(string=='mostlycloudy'){
return 'pics/mostlycloudy.png';
}
}
}
});
And here is the html:
<div id="weather-container">
<div id="current-weather">
<!--Angular JSON pull -->
<div id="title"><span id="current-title">Current Weather</span></div>
<div id="current-condition">{{currentSydney.current_observation.weather}}</div>
<img ng-src="{{iconString()}}"></img>
<div id="current-temp"><span id="current-temp"> {{currentSydney.current_observation.temp_c}} </span></div>
<span id="current-city">{{currentSydney.current_observation.display_location.city}} </span>
</div>
<!--Angular JSON pull and iteration-->
<div id="other-city-container">
<div class="other-city-weather" ng-repeat="city in cityData" >
<!--Image-->
<img ng-src="{{repeat()}}"></img>
<div class="current-city-temp">
<span>{{city.temp}}</span>
</div>
<div class="current-city-lower">
<span>{{city.name}}</span>
</div>
</div>
</div>
</div>
Now I'm calling the repeat function in the html inside the img src tag.
`
I see. You are making 2 loops : ng-repeat in the view, and a for loop in the controller ( repeat() ).
But I think that right now, they are not related to each other (which is what you need I guess): getting the index of the ng-repeat loop in your repeat method.
Try something like that :
In the view :
<img ng-src="{{repeat($index)}}" /><!-- Getting the current $index of the ng-repeat loop and passing it to the $scope.repeat() method -->
In the controller :
$scope.repeat = function(index) { // there, index becomes the value we just put in the view ($index = the current index of the ng-repeat loop), e.g. : 0,1,2,3...
var string = $scope.cityData[index].image; // We go get the right city in the cityData array, according to the current ng-repeat index.
// then we do the job
if(string=='mostlycloudy'){
return 'pics/mostlycloudy.png';
}
}
Not sure that works as I didn't test it, but you may know what I mean ?

Calculate summary with AngularJs on selected checkboxes

I'm learning the AngularJs and I've created a dynamic list with ng-repeat, but now I cannot find the solution for the next step...
I have the following code:
HTML:
<div ng-app="app">
<div class="list" ng-controller="ListController">
<div class="row" ng-repeat="prod in prodList">
<div class="prod-name cell">
<input type="checkbox" id="prod-{{ prod.id }}" data-ng-model="orders.prods.prod[prod.id]" value="{{ prod.id }}" data-cb="" /><label for="prod-{{ prod.id }}">{{ prod.name }}</label>
</div>
<div class="prod-size cell">
<label for="prodColor-#{{ prod.id }}">
<select id="prodColor-{{ prod.id }}" data-ng-model="orders.prods.color[prod.id]" data-ng-options="color.id as color.name for color in prod.colors">
<option value="">Select one</option>
</select>
</label>
</div>
<div class="prod-price cell">
{{ prod.price }}
</div>
</div>
<div class="sum">
{{ sum }}
</div>
</div>
</div>
JS:
var app = angular.module("app", [], function () {
});
app.controller('ListController', function ($scope) {
init();
function init () {
$scope.prodList = [{"id":"1","name":"window","colors":[{"id":"9","name":"white"},{"id":"11","name":"black"}],"price":"100"},{"id":"2","name":"door","colors":[{"id":"22","name":"blue"},{"id":"23","name":"red"}],"price":"356"},{"id":"3","name":"table","colors":[{"id":"37","name":"white"},{"id":"51","name":"black"}],"price":"505"}];
$scope.orders = {};
$scope.orders.prods = {};
$scope.orders.prods.prod = {};
$scope.orders.prods.color = {};
$scope.sum = 0;
}
});
Working demo:
http://jsfiddle.net/HDrzR/
Question
How can I calculate the summary of selected product's price into the $scope.sum?
Edited:
So if you select the "window" and the "table" from the list, the sum should contains 606. But if you unselect the "table" then the sum should be 100.
Thank you in advance!
You could either $watch over $scope.orders.prod and re-calculate the sum or (if performance is not a major concern and the list of objects is not huge) you could use a sum() function and reference it in the view:
Total: {{sum()}}
$scope.sum = function () {
return $scope.prodList.filter(function (prod) {
return $scope.orders.prods.prod[prod.id];
}).reduce(function (subtotal, selectedProd) {
return subtotal + parseInt(selectedProd.price);
}, 0);
};
/* If the above looks complicated, it is the equivalent of: */
$scope.sum = function () {
var sum = 0;
for (var i = 0; i < $scope.prodList.length, i++) {
var prod = $scope.prodList[i];
if ($scope.orders.prods.prod[prod.id]) {
sum += parseInt(prod.price);
}
}
return sum;
}
If you decide to go down the watching path, this is how the code should look like:
Total: {{watchedSum}}
$scope.sum = ...; // same as above
$scope.$watchCollection('orders.prods.prod', function (newValue, oldValue) {
if (newValue !== undefined) {
$scope.watchedSum = $scope.sum();
}
});
UPDATE:
"Stole" Quad's $watchCollection as this is more "cheap" than $watch(..., true).
As long as $scope.order.prods.prod contains only rimitive values (i.e. no objects), $watchCollection is enough to detect the changes.
See, also, this short demo illustrating both approaches.
here is an updated fiddle.
http://jsfiddle.net/HRQ9u/
what I did is add a $scope.$watch on the orders:
$scope.$watch('orders', function (newValue, oldValue) {
var findProdById = function (products, id) {
for (var i = 0; i < products.length; i++) {
if (parseInt(products[i].id, 10) === parseInt(id, 10)) {
return products[i];
}
}
return null;
};
var orders = newValue;
var usedProducts = newValue.prods.prod;
var sum = 0;
for ( var prop in usedProducts ){
var prodList = $scope.prodList;
var id = prop;
if ( usedProducts[id] ){
var product = findProdById(prodList, id);
if ( product !== null ){
sum += parseInt(product.price, 10);
}
}
}
$scope.sum = sum;
}, true);
Edit:
With that said, you can probably simplify your model a lot. And I mean A LOT.
Also, I have used only regular js as opposed to lodash or underscore.
Register an ng-click with checkbox and pass $event & prod.id references to a custom function that will be called on checkbox click event.
<input type="checkbox" id="prod-{{ prod.id }}" data-ng-model="orders.prods.prod[prod.id]" value="{{ prod.price }}" ng-click="calcSum($event, prod.id)" data-cb="" />
Within controller define custom function as:
$scope.calcSum = function($event, id) {
var checkbox = $event.target;
if (checkbox.checked ) {
$scope.sum += parseInt(checkbox.value);
} else {
$scope.sum -= parseInt(checkbox.value);
}
}
Change the Checkbox value attribute to store {{ prod.price }} instead of {{ prod.id }}, it makes more sense.
From my POV it looks more appropriate to use is ngChange from the input[checkbox] itself. Like:
ng-change="addMe(prod.price);"
We need a way to differentiate checked from not checked and in order to change less your app we will use the existence of the object by it doing this way:
ng-change="addMe(orders.prods.prod[prod.id],prod.price);"
ng-true-value="{{ prod.id }}"
ng-false-value=""
The full HTML will look like this:
<input type="checkbox" id="prod-{{ prod.id }}" data-ng-model="orders.prods.prod[prod.id]" ng-change="addMe(orders.prods.prod[prod.id],prod.price);" ng-true-value="{{ prod.id }}" ng-false-value="" />
From your Controller all you need is to add or substract when necessary:
$scope.addMe=function(checked,val){
val = 1*val
if (!checked){ $scope.sum = $scope.sum-val;}
else { $scope.sum = $scope.sum +val; }
}
Here is the Online Demo
Updated your fiddle FIDDLE
You can $scope.$watchCollection your scope to calcuate new sum. I chaged your prodList prices to number instead of string, and used underscore.js for easier looping in your objects/arrays.
$scope.$watchCollection("orders.prods.prod",function(){
var sum = 0;
for(var i = 0;i<$scope.prodList.length;i++){
if($scope.orders.prods.prod[$scope.prodList[i].id]){
sum += $scope.prodList[i].price;
}
}
$scope.sum = sum;
});
watchCollection docs are here
edit: changed with regular js answer

Categories

Resources