Remove object from array when attribute 0 or below - Angular - javascript

I have a controller that lets a user edit an object. Part of this decrements an attribute of the object – the quantity of my model. When the model's quantity reaches 0 or below, ideally I'd like to delete the whole object.
HTML
<div ng-app='basket'>
<div ng-controller='BasketController as basket'>
<div class='product' ng-repeat='product in cart.products'>{{ product.name }} Count: {{ product.quantity }} <a ng-click='product.quantity = product.quantity - 1'>Remove</a></div>
</div>
</div>
JS
(function(){
var app = angular.module('basket', []);
var cart;
app.controller('BasketController', function($scope, $http){
$scope.getTimes=function(n){
return new Array(n);
};
$scope.cart = {};
$scope.cart.products = [{
'name':'item 1',
'quantity':3
},{
'name':'item 2',
'quantity':3
},{
'name':'item 3',
'quantity':3
}];
});
})();
Live demo
http://codepen.io/EightArmsHQ/pen/bNBmXm
So for instance, in the above if you click 'remove' again and again on the first object, when you get to 0 I'd like to just have an array like the following:
$scope.cart.products = [{
'name':'item 2',
'quantity':3
},{
'name':'item 3',
'quantity':3
}];

You could just write a remove method to check for quantity and remove the item from the list.
In your controller:-
$scope.remove = function(product){
var products = $scope.cart.products;
product.quantity -= 1;
if(!product.quantity){
/*Splice the object from the array based on the index*/
products.splice(products.indexOf(product), 1);
}
}
and on click just call it as:
<a ng-click='remove(product)'>Remove</a>
Demo

Change your link to:
<a ng-click='remove(product)'>Remove</a>
And add the following method to the controller:
$scope.remove = remove;
function remove(product) {
$scope.cart.products.splice($scope.cart.products.indexOf(product), 1);
}

Related

Duplicates in a repeater are not allowed while restore array from local storage

I'm working with angular-ui bootstrap accordion directive and did it sortable with help this topic. Now I'm working on functions storing and restoring accordion state and got some problem with restoring this state. When I apply saved state from local storage, I'm getting an error:
Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: group in groups, Duplicate key: string:"
My functions looks like :
$scope.storeState = function () {
localStorage.setItem("state", JSON.stringify($scope.groups))
};
$scope.restoreState = function () {
var retrieve = localStorage.getItem("state");
$scope.groups = retrieve
};
It is simple functions for set and get item from local storage. I have tried to set $scope.groups = [], inside restore state function but it didn't help, also I have tried to use track by $index, track by $id(group) or group in groups track by group.(some obj) but it doesn't work or just breaks the whole app. So, could anybody help me to find what I'm missing? Thanks in advance.
my code and plunker
angular.module('ui.bootstrap.demo', ['ui.bootstrap', 'ui.sortable'])
.config(['$provide', function ($provide){
$provide.decorator('accordionDirective', function($delegate) {
var directive = $delegate[0];
directive.replace = true;
return $delegate;
});
}])
.controller('AccordionDemoCtrl', function ($scope, $timeout) {
$scope.oneAtATime = false;
$scope.isOpen = false;
$scope.isClicked = false;
$scope.sortableOptions = {
handle: ' .handle',
update: function(event, ui) {
$timeout(function(){
$scope.order = ui.item.index();
});
}
}
$scope.groups = [
{
index: 0,
title: 'Dynamic Group Header - 1',
content: 'Dynamic Group Body - 1',
isOpen: false
},
{
index: 1,
title: 'Dynamic Group Header - 2',
content: 'Dynamic Group Body - 2',
isOpen: false
},
{
index: 2,
title: 'Dynamic Group Header - 3',
content: 'Dynamic Group Body - 3',
isOpen: false
}
];
$scope.$watch('isOpen', function(newval, oldval){
if(oldval !== newval) {
$scope.state = newval ? 'opening' : 'closing';
}
})
$scope.updateOpenStatus = function(obj){
$scope.isOpen = $scope.groups.some(function(item){
return item.isOpen;
});
}
$scope.updateClickStatus = function(group){
$scope.isClicked = true;
}
// functions store / restore state
$scope.storeState = function () {
localStorage.setItem("state", JSON.stringify($scope.groups))
};
$scope.restoreState = function () {
var retrieve = localStorage.getItem("state");
$scope.groups = []
console.log(retrieve)
$scope.groups = retrieve
};
});
html
<h2>Accordion</h2>
<ul accordion close-others="oneAtATime" ui-sortable="sortableOptions" ng-model="groups">
<li accordion-group ng-repeat="group in groups" is-open="group.isOpen" ng-click="updateOpenStatus(group)">
<accordion-heading>
<div class="handle btn btn-primary"><i class="glyphicon glyphicon-move"></i></div>
{{group.title}}
</accordion-heading>
<div ng-click="updateClickStatus(group)">
{{group.content}}
</div>
</li>
</ul>
<hr>
<h4>Panel Order: {{order}}</h4><br>
<h4>Panel Header Open: {{isOpen}} </h4><br>
<h4>Panel Content Click: {{isClicked}}</h4><br>
<h4>Panel state : {{state}}</h4><br>
<h4><b>Result:</b> <span ng-if ="isClicked && isOpen"> True</span></h4><br>
<!-- ******************** -->
<button class="btn btn-primary" ng-click="storeState()">store</button>
<button class="btn btn-danger" ng-click="restoreState()">restore</button>

AngularJS access further details by matching ids

This must be simple and Angular probably has an inbuilt directive to do this but I cant think of how to do without looping through the Array.
I have a array of options i.e.
$scope.colors=[
{id:"0",label:"blue"},
{id:"1",label:"red"},
{id:"2",label:"green"}
]
And then my data object that stores the id of a color option i.e.
$scope.data={
color:"1",
otherproperty:""
}
But when I display the data to the user I want to show the label rather than the id, so is there a easy(angular) way to do this?:
{{data.color.label}}
The Angular way would be using ng-repeat & filter, your still essentially looping over the Array but all options would require some sort of loop i.e.
<div ng-repeat="color in colors | filter:{ 'id': data.color}:true">
{{ color.label }}
</div>
Setting the Filter strict comparison to 'true' as above will only select the id with an exact match
https://jsfiddle.net/sjmcpherso/wztunyr5/
The following will return the object where the id matches $scope.data.color:
var pickedColor = $scope.colors.filter(function( obj ) {
return obj.id === $scope.data.color;
});
pickedColor.label will be the label string.
Look at other way, hope it will help you.
https://jsfiddle.net/kkdvvkxk/.
We can also use $filter under controller.
Controller :
var myApp = angular.module('myApp', []);
function MyCtrl($scope, $filter) {
$scope.colors = [{
id: "0",
label: "blue"
}, {
id: "1",
label: "red"
}, {
id: "2",
label: "green"
}]
$scope.data = {
color: "1",
otherproperty: ""
}
$scope.getLabel = function(colorId) {
return $filter('filter')($scope.colors, { id: colorId }[0].label;
}
}
HTML :
{{ getLabel(data.color)}}

reset ng-model from controller in ng-repeat

I am trying to create a list of editable inputs from a list of items. I want the user to be able to edit any of the items, but if they change there mind they can click a button and reset it back to the way it was.
So far I have everything working except the reset.
my html
<div ng-app ng-controller="TestController">
<div ng-repeat="item in list">
<label>Input {{$index+1}}:</label>
<input ng-model="item.value" type="text" ng-click="copy(item)"/>
<button ng-click="reset(item)">
x
</button>
</div>
{{list}}<br>
{{selected_item_backup}}
</div>
my controller
function TestController($scope) {
$scope.selected_item_backup = {};
$scope.list = [ { value: 'value 1' }, { value: 'value 2' }, { value: 'value 3' } ];
$scope.reset = function (item) {
// i know this wont work for many reasons, it is just an example of what I would like to do
item = $scope.selected_item_backup;
};
$scope.copy = function (item) {
angular.copy(item, $scope.selected_item_backup);
};
}
and here is a fiddle
http://jsfiddle.net/ryanmc/1ab24o4t/1/
Keep in mind that this is a simplified version of my real code. My objects will have many editable fields each. Also they are not indexed, and so the index cannot be relied on. I just want to be able to assign the original item on top of the new and have it replaced.
This is work solution jsfiddle
function TestController($scope) {
$scope.list = [{
value: 'value 1'
}, {
value: 'value 2'
}, {
value: 'value 3'
}];
var orgiinalList = [];
angular.forEach($scope.list, function(item) {
orgiinalList.push(angular.copy(item));
});
$scope.reset = function(index) {
$scope.list[index] = angular.copy(orgiinalList[index]);
};
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app ng-controller="TestController">
<div ng-repeat="item in list">
<label>Input {{$index+1}}:</label>
<input ng-model="item.value" type="text" />
<button ng-click="reset($index)">
x
</button>
</div>
{{list}}
<br>
</div>
Changing your reset function so that it looks like this:
$scope.reset = function(index) {
$scope.list[index].value = "value "+ (index+1);
};
will make it so that your 'reset' buttons restore what would be those original values...
Angular controller:
function TestController($scope) {
$scope.selected_item_backup = {};
$scope.list = [{
value: 'value 1'
}, {
value: 'value 2'
}, {
value: 'value 3'
}];
$scope.reset = function(index) {
$scope.list[index].value = "value " + (index+1);
};
$scope.copy = function(item) {
angular.copy(item, $scope.selected_item_backup);
};
}
HTML:
<div ng-app ng-controller="TestController">
<div ng-repeat="item in list">
<label>Input {{$index+1}}:</label>
<input ng-model="item.value" type="text" ng-click="copy(item)" />
<button ng-click="reset($index)">
x
</button>
</div>
</div>
The values of your array are directly modeled by those inputs - therefore as a user types changes into those inputs they are directly manipulating the $scope.list array so you cant really use it as a reference... Hope that makes sense.
You can't use global functions as controllers in newer versions of angular but you can register your controllers in your application:
<div ng-app ng-controller="TestController">
<div ng-repeat="item in list">
<label>Input {{$index+1}}:</label>
<input ng-model="item.value" type="text" ng-click="copy($index,item)"/>
<button ng-click="reset($index,item)">
x
</button>
</div>
{{list}}<br>
{{selected_item_backup}}
function TestController($scope) {
$scope.selected_item_backup = [];
$scope.list = [ { value: 'value 1' }, { value: 'value 2' }, { value: 'value 3' } ];
$scope.reset = function (index) {
$scope.list[index].value = $scope.selected_item_backup[index];
};
$scope.copy = function (index,item) {
// backup the old value
$scope.selected_item_backup[index] = item.value;
};
}

How to push data to array in factory/service (ANGULAR/IONIC)

I want to create an app that work like this : https://ionic-songhop.herokuapp.com
As you can see, when we click favorite button, the item will store in factory and we can invoke in another page (favorite page)
In my case : i use service to store the item data and create factory to store the pushed item.
Here's my code : (I store data in service)
.service('dataService',function(){
var service=this;
this.playerlist = [
{ name: 'Leonel Messi', ava:"https://i.scdn.co/image/d1f58701179fe768cff26a77a46c56f291343d68" },
{ name: 'Cristiano Ronaldo', ava:"https://i.scdn.co/image/d1f58701179fe768cff26a77a46c56f291343d68" },
{ name: 'Zlatan Ibrahimovic', ava:"https://i.scdn.co/image/d1f58701179fe768cff26a77a46c56f291343d68" },
{ name: 'Wayne Rooney', ava:"https://i.scdn.co/image/d1f58701179fe768cff26a77a46c56f291343d68" },
{ name: 'Michael Carrick', ava:"https://i.scdn.co/image/d1f58701179fe768cff26a77a46c56f291343d68" },
{ name: 'Phil Jones', ava:"https://pbs.twimg.com/profile_images/473469725981155329/E24vfxa3_400x400.jpeg" },
{ name: 'Angel di Maria', ava:"https://i.scdn.co/image/d1f58701179fe768cff26a77a46c56f291343d68" }
];
})
.factory('User', function() {
var play = { favorites: []}
play.addToFavorites = function(song) {
play.favorites.unshift(song);
}
play.removeFromFavorites = function(player, index) {
play.favorites.splice(index, 1);
}
return play;
})
Controller :
.controller('ChooseTabCtrl', function($scope, dataService, User) {
$scope.dataService=dataService;
$scope.addToFavorite = function (item) {
User.favorites.unshift(dataService.playerList.indexOf(), 1);
}
})
But when i click the favorite button on each item, the list dont show in favorite page.
Is it possible to do like this in Ionic app?
Here's my codepen : http://codepen.io/harked/pen/WvJQWp
There are a few issues with the code in your codepen...
In the controller you are referencing dataService.playerList.indexOf() when the player object is actually playerlist (all lowercase). Also, I assume you want to actually get the indexOf the player so that line needs to change to:
User.favorites.unshift(dataService.playerlist.indexOf(item));
// remove the `, 1` otherwise you'll be adding a `1` to the array everytime
and in your view, you need to change the following:
// wrong
ng-click="addToFavorite(item)"
// right
ng-click="addToFavorite(player)"
Next, in your ListTabCtrl change the following:
$scope.players=dataService;
// to
$scope.players=dataService.playerlist;
Then in the view:
<ion-item ng-repeat="player in favorites" class="item item-avatar" href="#">
<img ng-src="{{players[player].ava}}">
<h2>{{players[player].name}}</h2>
<p>Back off, man. I'm a scientist.</p>
<ion-option-button class="button-assertive" ng-click="removePlayer(player, $index)">
<i class="ion-minus-circled"></i>
</ion-option-button>
</ion-item>
I have posted a working example of your code on jsbin: http://jsbin.com/lukodukacu/edit?html,css,js,output

Deferring Angular until Firebase data received

Right now, I am having users input data on the a sign up page, which includes having the user input their "plan type". I store this user data on Firebase.
On the following page after the user has submitted the previous input page, I take the user to an output page that uses AngularJS to show all plans filtered by the user's "plan type" (in the code, it's the customFilter). So, as soon as the page loads, I want to be able to call the user's plan type from firebase and then make it the initial filter that customFilter uses.
How do I get the Angular filter to wait until I get the "plan type' from Firebase? Any examples would be much appreciated.
I've added the code below to make this easier to answer**
<body ng-app="tipOutput" ng-controller="Tips">
<div ng-controller="MainCtrl">
// Custom filter that I want to customize based on user data
<span class="select">
<select style="width:100%" ng-model="filterItem.plan" ng-options="item.name for item in filterOptions.plans"></select>
</span>
// Table using ng-repeat and above filter
<table>
<tbody>
<tr ng-repeat="tip in tips | filter:customFilter">
<td style="vertical-align:top"><span><strong>{{tip.planName}}</strong></span><span ng-show="tip.planDetail">Plan Detail: {{tip.planDetail}}</span></td>
</tr>
</tbody>
</table>
</div>
</body>
Angular app code here
angular.module('tipOutput', ['firebase', 'filters'])
.controller('Tips', ['$scope', 'angularFire',
function ($scope, angularFire) {
var ref = new Firebase('https://sitename.firebaseio.com/tips');
angularFire(ref, $scope, "tips");
}])
.controller('MainCtrl', function($scope) {
//Contains the filter options
$scope.filterOptions = {
plans: [
{id : 2, name : 'All Plans', type: 'all' },
{id : 3, name : 'Plan Type 1', type: 'plan-type-1' },
{id : 4, name : 'Plan Type 2', type: 'plan-type-2' },
{id : 5, name : 'Plan Type 3', type: 'plan-type-3' },
{id : 6, name : 'Plan Type 4', type: 'plan-type-4' },
{id : 7, name : 'Plan Type 5', type: 'plan-type-5' },
{id : 8, name : 'Plan Type 6', type: 'plan-type-6' }
]
};
// Here's where the initial value of the filter is set. Currently, it's not dynamic, but I
// want it to be based off a variable that comes in asynchronously (i.e. likely after this
// code would otherwise run)
$scope.filterItem = {
plan: $scope.filterOptions.plans[0]
}
//Custom filter - filter based on the plan type selected
$scope.customFilter = function (tip) {
if (tip.servicesReceived === $scope.filterItem.plan.type) {
return true;
} else if ($scope.filterItem.plan.type === 'all') {
return true;
} else {
return false;
}
};
})
I tried to simulate your call to your firebase.
DEMO: http://plnkr.co/edit/VDmTCmR82IyaKnfaT1CP?p=preview
html
<body ng-controller="MainCtrl">
<p>Hello {{name}}!</p>
<div>
<span class="select">
<select ng-model="filterItem.plan" ng-options="item.name for item in filterOptions.plans"></select>
</span>
<table border="1">
<tbody>
<tr ng-repeat="tip in (filtered = (tips | filter:customFilter))">
<td>
<span><strong>{{tip.planName}}</strong></span>
<span>Plan Detail: {{tip.planDetail}}</span>
</td>
</tr>
<tr ng-show="filtered.length==0">
<td>None</td>
</tr>
</tbody>
</table>
</div>
</body>
I keep the filtered list to be able to display a message if there is no items.
js
var app = angular.module('plunker', ['firebase']);
app.controller('MainCtrl', function($scope, $timeout, angularFire) {
$scope.name = 'World';
$scope.tips = [];
/*
// since we dont have access to your firebase, i used a $timeout
var ref = new Firebase('https://sitename.firebaseio.com/tips');
// we wait for the callback of angularFire
angularFire(ref, $scope, "tips").then(function(response) {
var index = 1; // find the good index in filterOptions
$scope.filterItem.plan = $scope.filterOptions.plans[index];
});*/
// simulate the response
$timeout(function() {
$scope.tips = [
{planName: '213', planDetail:'534',servicesReceived:'plan-type-1'},
{planName: '123', planDetail:'345',servicesReceived:'plan-type-2'},
{planName: '321', planDetail:'643'} // this one has no serviceReceived
];
// set it to the response receive from the server
var response = 1;
$scope.filterItem.plan = $scope.filterOptions.plans[response];
}, 1000);
$scope.filterOptions = {
plans: [
{id : 2, name : 'All Plans', type: 'all' },
{id : 3, name : 'Plan Type 1', type: 'plan-type-1' },
{id : 4, name : 'Plan Type 2', type: 'plan-type-2' },
{id : 5, name : 'Plan Type 3', type: 'plan-type-3' },
{id : 6, name : 'Plan Type 4', type: 'plan-type-4' },
{id : 7, name : 'Plan Type 5', type: 'plan-type-5' },
{id : 8, name : 'Plan Type 6', type: 'plan-type-6' }
]
};
// default value
$scope.filterItem = {
plan: $scope.filterOptions.plans[0] // Do something with response
}
$scope.customFilter = function (tip) {
return (tip.servicesReceived || 'all') === $scope.filterItem.plan.type;
};
});
Resolve the fireBase Data on the route. This will prohibit the controller from loading before the data is present. Then just inject the data into the controller and continue forward with your normal process.
I'm not going to write it using your variables, but an exmaple of such a config file would look like:
'use strict';
angular.module('someModule.dashboard')
.config(function ($stateProvider){
$stateProvider
.state('dashboard', {
url: '/dashboard',
templateUrl: '/app/dashboard/html/dashboard.html',
controller: 'dashBoardCtrl',
resolve: {
currentAuth: function(fireBaseAuth){
return fireBaseAuth.auth().$requireAuth();
},
currentUser: function($fireBaseUser, Session){
return $fireBaseUser.user(Session.id).$asObject();
},
userList: function($fireBaseUser){
return $fireBaseUser.userList().$asArray();
}
}
});
});
Then Your controller would look something like this:
'use strict';
angular.module('someModule.dashboard')
.controller('dashBoardCtrl', function($scope, currentUser, userList){
$scope.userList = userList;
});

Categories