I'm trying to get update the total as the checkboxes are selected and unselected (incase the user changes their mind and no longer wants the item). But I'm not sure how to get the actual value I have assigned to each toy. For example: If the user selects toy 1 and 3 they should see: You ordered Toy1, Toy3 and your total is $6.00. For now I assigned the values with the names themselves which isn't what I want but I just put that to show what I'm trying to do. Is their something else I should be using actually perform an operation to get the total?
<script>
let toylist = [];
let toynames = [
'Toy1 5.00',
'Toy2 5.00',
'Toy3 1.00',
];
function join(toylist) {
return toylist.join(', ');
}
</script>
{#each toynames as toy}
<label>
<input type=checkbox bind:group={toylist} value={toy}> {toy}
</label>
{/each}
{#if toylist.length === 0}
<p>Pick at least one toy</p>
{:else}
<p>
You ordered {toylist.join(', ')} and your total is
</p>
{/if}
Ok, first you should separate the toynames array into an array of names and values. Then you should bind to the checked property of the input.
In order to display the current state to the user, we need a reactive declaration. Let's call it total. It contains two functions. The first one gives back an array of the selected names, the second the sum of the selected values.
Both work by looking at the selected property of the object in the toylist array. This updates due to our binding of the checked attribute. I created a repl for you to toy ;-) around with
<script>
let toylist = [
{name: "Toy", value: 5, selected: false},
{name: "Elephant", value: 6, selected: false},
{name: "Choo-Choo", value: 1, selected: false}
]
$: total = {
names: toylist.reduce((acc, cV) => {
if (cV && cV.selected) {
return [...acc, cV.name];
} else return [...acc];
}, []),
values: toylist.reduce((acc, cV) => {
if (cV && cV.selected) {
return parseInt(acc) + parseInt(cV.value);
} else return parseInt(acc);
}, 0)
};
</script>
{#each toylist as {name,value, selected}}
<label>
<input type=checkbox bind:checked={selected} bind:value={value}> {name}
</label>
{/each}
{#if toylist.length === 0}
<p>Pick at least one toy</p>
{:else}
<p>
You ordered {total.names.length < 1 ? "nothing": total.names} and your total is {total.values}
</p>
{/if}
EDIT:
Here is the total function with a more classic syntax:
$: total = {
names: toylist.reduce(function(acc, cV) {
if (cV && cV.selected) {
return [...acc, cV.name];
} else return [...acc];
}, []),
values: toylist.reduce(function(acc, cV) {
if (cV && cV.selected) {
return parseInt(acc) + parseInt(cV.value);
} else return parseInt(acc);
}, 0)
};
And here without the ? operator:
<script>
function renderNames() {
if (total.names.length < 1) {
return "nothing";
} else {
return total.names;
}
}
</script>
<p>You ordered {renderNames()} and your total is {total.values}</p>
The best way to isolate the price of each toy would be to make your array of toys into an array of objects where 'name' is one key value pair and price another. For manipulating the data it would be helpful if each toy had an id, and I've added a 'selected' boolean value to each toy that is updated if they are added or removed from the "toylist". I've also added a 'total' variable to hold the total of selected toys prices.
I have played with your code a bit to make this work. I have used buttons instead of checkboxes but you could do it in any way you like. So give this code a go and it should be doing what you want.
<script>
let toylist = [];
let toys = [
{id: 1, name: 'Toy 1', price: 5.00, selected: false},
{id: 2, name: 'Toy 2', price: 5.00, selected: false},
{id: 3, name: 'Toy 3', price: 1.00, selected: false}
];
let total = 0.00
function join(toy) {
if(toy.selected === false) {
toylist = [...toylist, toy.name]
total = total+toy.price
let i = toys.findIndex(t => t.id === toy.id)
toys[i].selected = true
toys = toys
} else {
total = total-toy.price
let i = toys.findIndex(t => t.id === toy.id)
let i2 = toylist.findIndex(t => t === toy.name)
toylist.splice(i2, 1)
toylist = toylist
toys[i].selected = false
toys = toys
}
}
</script>
{#each toys as toy}
<label>
{toy.name}: ${toy.price} <button on:click="{() => join(toy)}" value={toy.name}>{toy.selected ? 'remove' : 'add'}</button>
</label>
{/each}
{#if toylist.length === 0}
<p>Pick at least one toy</p>
{:else}
<p>
You ordered {toylist} and your total is ${total}
</p>
{/if}
Related
I have multiple checkboxes in my angular application. When user checked and unchecked checkboxes I want to pass those true/false values into an array. It's happening from below code.
But my problem is as you can see the below console.log, it has duplicate checkbox values(index 0 and 3 have same thing) and push it to the array.
I want to know how to check duplicate objects and avoid pushing object to the array.
.ts file
layerChange(e:any){
var isChecked = e.target.checked;
var id = e.target.attributes.id.nodeValue;
const layer = {
isChecked: isChecked,
id: id,
}
this.layers.push(layer);
console.log(this.layers);
}
.html file
<input id="population" (change)="layerChange($event)" type="checkbox">
<input id="gender" (change)="layerChange($event)" type="checkbox">
<input id="householdIncome" (change)="layerChange($event)" type="checkbox">
console.log(this.layers)
**0: {isChecked: true, id: 'population'}**
1: {isChecked: true, id: 'age'}
2: {isChecked: false, id: 'population'}
**3: {isChecked: true, id: 'population'}**
You can check if an entry exists first using either :
var id = e.target.attributes.id.nodeValue;
var attr = this.layer.find(x => x.id === id);
if(!attr)
this.layers.push(layer);
or
this.layer.filter(x => x.id === id)
Edit
in your scenario is better to construct the array only one time in page load.
ngOnInit(): void {
this.layer = [{id: 'age', isChecked: false}, {id:'population',isChecked: false}]
}
and then alter the check state when user check/uncheck :-
layerChange(e:any){
var isChecked = e.target.checked;
var id = e.target.attributes.id.nodeValue;
this.layer.find(x => x.id === id).isChecked = isChecked;
console.log(this.layers);
}
You can create a string array which contains only ids and you can insert or remove elements from the array as per the selection
layerChange(e:any) {
const id = e.target.attributes.id.nodeValue;
const index = this.layers.findIndex(el => el === id)
if(index === -1) {
this.layers.push(id);
} else {
this.layers.splice(index, 1)
}
console.log(this.layers);
}
my GOD!
Kalana (and others) we need re-thinking the problem using "variables". Yes, Angular philosophy is binding variables. Variables in .ts makes the values are showed in the .html
So, some simple like declare an array of object in the .ts
layers:any[]=[{isChecked: true, id: 'population'},
{isChecked: true, id: 'age'},
{isChecked: false, id: 'population'}
]
Allow us write in .html
<ng-container *ngFor="let layer in layers">
<input type="checkbox" [(ngModel)]="layer.isChecked"
(change)="onChange()">
</ng-container>
In .ts we has a function:
onChange(){
console.log(this.layers)
}
Finally , I was able to find a solution. Thank you.
layerChange(e:any){
var isChecked = e.target.checked;
var id = e.target.attributes.id.nodeValue;
const index = this.layers.findIndex(el => el.id === id);
const layer = {
isChecked: isChecked,
id: id,
}
if(index > -1){
this.layers[index].isChecked = isChecked;
}else{
this.layers.push(layer);
}
console.log(this.layers);
}
I am having trouble with checkbox filtering an array field inside a JSON object/string in javascript.
Here is my HTML:
<div ng-app="fruit">
<div ng-controller="FruitCtrl">
<input type="checkbox" ng-click="includeColour('Red')"/> Red</br/>
<input type="checkbox" ng-click="includeColour('Orange')"/> Orange</br/>
<input type="checkbox" ng-click="includeColour('Yellow')"/> Yellow</br/>
<ul>
<li ng-repeat="f in fruit | filter:colourFilter">
{{f.name}}
</li>
</ul>
Filter dump: {{colourIncludes}}
</div>
</div>
Here is my data:
$scope.fruit = [
{'name': 'Apple', 'colour': ['Red']},
{'name': 'Orange', 'colour': ['Red','Orange']},
{'name': 'Banana', 'colour': ['Yellow','Orange']}];
The original jsfiddle was based off of single item colours field, not an array of colours.
original:
{'name':'Apple','colour':'Red'}
my version:
{'name':'Apple','colour':['Red','Yellow']}
And this is how it WAS filtered:
$scope.colourFilter = function(fruit) {
if ($scope.colourIncludes.length > 0) {
var data = fruit.colour;
if ($.inArray(fruit.colour, $scope.colourIncludes) < 0)
return;
}
return fruit;
}
I am trying to filter like so:
$scope.colourFilter = function(fruit) {
if ($scope.colourIncludes.length > 0) {
var data = fruit.colour;
for(var i in data){
if ($.inArray(data[i], $scope.colourIncludes) < 0)
return;
}
return fruit;
}
Any ideas as to why I cannot find a match? I guess I am not scanning the arrays properly?
for (var i in fruit){
fruit[i].colour.forEach(function(color){
console.log(color);
});
}
This loop returns the items in the colour array one by one.
Basically, fruit.color is undefined b/c you're not specifying which index in the array you want to reference.
In other words, fruit[0].colour, fruit[1].colour ...etc are valid.
Your code didn't quite work for me, but this at least allows me to print the colors in each fruit:
$scope.colourFilter = function(f) {
angular.forEach(f.colour, function(color){
console.log("Color is: "+color);
});
}
I need to show object only if one of it's properties equals to array.
I have a controller in app.js:
app.controller('checkBoxController', function ($scope) {
$scope.ingredients= [
{label: 'Egg', value: 1},
{label: 'Milk', value: 2},
$scope.selection=[];
$scope.toggleSelection = function toggleSelection(ingredientLabel) {
var idx = $scope.selection.indexOf(ingredientLabel);
if (idx > -1) {
$scope.selection.splice(idx, 1);
}
else {
$scope.selection.push(ingredientLabel);
}
};
});
and an html code for it:
<span style="color:black;" class="selected-item">Selected Items:<span>
<div ng-repeat="label in selection" class="selected-item">
</div>
<div class="list-group">
<div class="list-group-item" ng-repeat="product in meals.products" ng-show="product.contents==selection">
<h1>{{product.name}}</h1>
<meal-gallery></meal-gallery>
<meal-tabs></meal-tabs>
</div>
</div>
And I Have { name: 'Scrambled Egg', contents: "Egg"} in array of products. So I need to show product if it's contents equals to selected ingredients.
I do not have problems when it is only one ingredient like "Egg", but if I need contents of two equal to selected?
It would be best to use a custom filter.
If you add lodash to your application, you can create a filter that will preform a following operation:
angular.module('common', [])
.filter('canBeMadeFrom', function() {
return function(product, ingredients) {
return _.intersection(product.contents, ingredients).length == product.contents.length';
};
});
this will return true if all of products contents are contained in ingredients
use it like this
ng-repeat='product in products | canBeMadeFrom:ingredients'
Use a filter.
<div ng-repeat=product in products |filter: product.a === a && product.b === b>
You can also use a function for filter. It's a function that takes in an item and returns Boolean
I would like to use a check list and show the user the boxes she has checked.
I am using this framework: http://vitalets.github.io/angular-xeditable/#checklist . See his example 'Checklist' versus his example 'Select multiple'. However, I do not want to display a link with a comma separated string, i.e., join(', '). I would like each selection to appear beneath the previous, in an ordered list or similar.
Pretty much copied from his examples, here are the guts of my controller:
$scope.userFeeds = {
feeds: {}
};
$scope.feedSource = [
{ id: 1, value: 'All MD' },
{ id: 2, value: 'All DE' },
{ id: 3, value: 'All DC' }
];
$scope.updateFeed = function (feedSource, option) {
$scope.userFeeds.feeds = [];
angular.forEach(option, function (v) {
var feedObj = $filter('filter')($scope.feedSource, { id: v });
$scope.userFeeds.feeds.push(feedObj[0]);
});
return $scope.userFeeds.feeds.length ? '' : 'Not set';
};
And here is my html:
<div ng-show="eventsForm.$visible"><h4>Select one or more feeds</h4>
<span editable-select="feedSource"
e-multiple
e-ng-options="feed.id as feed.value for feed in feedSource"
onbeforesave="updateFeed(feedSource, $data)">
</span>
</div>
<div ng-show="!eventsForm.$visible"><h4>Selected Source Feed(s)</h4>
<ul>
<li ng-repeat="feed in userFeeds.feeds">
{{ feed.value || 'empty' }}
</li>
<div ng-hide="userFeeds.feeds.length">No items found</div>
</ul>
</div>
My problem is - display works with editable-select and e-multiple, but not with editable-checklist. Swap it out and nothing is returned.
To workaround, I have tried dynamic html as in here With ng-bind-html-unsafe removed, how do I inject HTML? but I have considerable difficulties getting the page to react to a changed scope.
My goal is to allow a user to select from a checklist and then to display the checked items.
Try this fiddle: http://jsfiddle.net/mr0rotnv/15/
Your onbeforesave will need to return false, instead of empty string, to stop conflict with the model update from xEditable. (Example has onbeforesave and model binding working on the same variable)
return $scope.userFeeds.feeds.length ? false : 'Not set';
If you require to start in edit mode add the attribute shown="true" to the surrounding form element.
Code for completeness:
Controller:
$scope.userFeeds = {
feeds: []
};
$scope.feedSource = [
{ id: 1, value: 'All MD' },
{ id: 2, value: 'All DE' },
{ id: 3, value: 'All DC' }
];
$scope.updateFeed = function (feedSource, option) {
$scope.userFeeds.feeds = [];
angular.forEach(option, function (v) {
var feedObj = $filter('filter')($scope.feedSource, { id: v });
if (feedObj.length) { // stop nulls being added.
$scope.userFeeds.feeds.push(feedObj[0]);
}
});
return $scope.userFeeds.feeds.length ? false : 'Not set';
};
Html:
<div ng-show="editableForm.$visible">
<h4>Select one or more feeds</h4>
<span editable-checklist="feedSource"
e-ng-options="feed.id as feed.value for feed in feedSource"
onbeforesave="updateFeed(feedSource, $data)">
</span>
</div>
<div ng-show="!editableForm.$visible">
<h4>Selected Source Feed(s)</h4>
<ul>
<li ng-repeat="feed in userFeeds.feeds">{{ feed.value || 'empty' }}</li>
<div ng-hide="userFeeds.feeds.length">No items found</div>
</ul>
</div>
Css:
(Used to give the "edit view" a list appearance)
.editable-input label {display:block;}
Also there is the option of using a filter if you do not need to do any validation or start in edit mode.
Controller:
$scope.user = { status: [2, 3] };
$scope.statuses = [
{ value: 1, text: 'status1' },
{ value: 2, text: 'status2' },
{ value: 3, text: 'status3' }
];
$scope.filterStatus = function (obj) {
return $scope.user.status.indexOf(obj.value) > -1;
};
HTML:
<a href="#" editable-checklist="user.status" e-ng-options="s.value as s.text for s in statuses">
<ol>
<li ng-repeat="s in statuses | filter: filterStatus">{{ s.text }}</li>
</ol>
</a>
I have a AngularJS directive that allows users to select a values from a list to filter on. Pretty simple concept which is represented here:
Problem is when I click one of the checkboxes they all select unintended. My directive is pretty simple so I'm not sure why this is happening. The code around the selection and checkboxes is as follows:
$scope.tempFilter = {
id: ObjectId(),
fieldId: $scope.available[0].id,
filterType: 'contains'
};
$scope.toggleCheck = function (id) {
var values = $scope.tempFilter.value;
if (!values || !values.length) {
values = $scope.tempFilter.value = [];
}
var idx = values.indexOf(id);
if (idx === -1) {
values.push(id);
} else {
values.splice(idx, 1);
}
};
$scope.valuesListValues = function (id) {
return $scope.available.find(function (f) {
return f.id === id;
}).values;
};
and the data resembles:
$scope.available = [{
id: 23,
name: 'Store'
values: [
{ id: 124, name: "Kansas" },
{ id: 122, name: "Florida" }, ... ]
}, ... ]
the view logic is as follows:
<ul class="list-box">
<li ng-repeat="val in valuesListValues(tempFilter.fieldId)">
<div class="checkbox">
<label ng-click="toggleCheck(val.id)">
<input ng-checked="tempFilter.value.indexOf(val.id) === -1"
type="checkbox"> {{val.name}}
</label>
</div>
</li>
</ul>
First off, it toggleCheck fires twice but populates the correct data ( second time given my code it removes it though ).
After the second fire, it checks all boxes... Any ideas?
Perhaps its that the local variable doesn't get reassigned to the property of the scope property used in the view. Since your values are then non-existent and not found, the box is checked.
$scope.tempFilter.value = values
I took the interface concept you were after and created a simpler solution. It uses a checked property, found in each item of available[0].values, as the checkbox model. At the top of the list is a button that clears the selected items.
JavaScript:
function DataMock($scope) {
$scope.available = [{
id: 23,
name: 'Store',
values: [{
id: 124,
name: "Kansas"
}, {
id: 122,
name: "Florida"
}]
}];
$scope.clearSelection = function() {
var values = $scope.available[0].values;
for (var i = 0; i < values.length; i++) {
values[i].checked = false;
}
};
}
HTML:
<body ng-controller="DataMock">
<ul class="list-box">
<li>
<button ng-click="clearSelection()">Clear Selection</button>
</li>
<li ng-repeat="val in available[0].values">
<div class="checkbox">
<label>
<input ng-model="val.checked"
type="checkbox" /> {{val.name}}
</label>
</div>
</li>
</ul>
</body>
Demo on Plunker
The repeat that I used to grab the values based on the id, was the problem area.
<li ng-repeat="val in valuesListValues(tempFilter.fieldId)">
removing that and simple listening and setting a static variable resolved the problem.
$scope.$watch('tempFilter.fieldId', function () {
var fId = $scope.tempFilter.fieldId;
if ($scope.isFieldType(fId, 'valuesList')) {
$scope.valuesListValues = $scope.valuesListValues(fId);
}
}, true);
});
and then in the view:
ng-repeat="value in valuesListValues"