How enable multiple row selection in angular js table - angularjs

I created html table and used ng-repeat to show items in table,
but i can't select multiple rows in table.
How can achieve this by using control key
Thank you!
<div class="table_bg">
<table datatable="ng" dt-options="dtOptions" dt-column-defs="dtColumnDefs" class="table table-striped table-bordered dt-responsive nowrap res_table" cellspacing="0" width="100%">
<thead>
<tr>
<th>#</th>
<th>Description</th>
<th>Ali</th>
<th> Extension</th>
<th> Ext/th>
<th>Comp</th>
</tr>
</thead>
<tbody ng-hide="loading">
<tr ng-class="{'selected':$index == selectedRow}" ng-click="setClickedRow($index)" ng-repeat="docType in DocTypes" ng-cloak ng-mouseenter="hover(docType)" ng-mouseleave="hover(docType)">
<td>{{$index}}</td>
<td>
{{docType.Desc}}
</td>
<td>{{docType.LI}}</td>
<td>{{docType.Ext}}</td>
<td>{{docType.EXT}}</td>
<td>{{docType.Comp}}</td>
</tr>
</tbody>
</table>

I hope this is not too late for you, I have you answer.
You can use the $event.ctrlKey parameter to check if the user has pressed control.
Even better, there is a $event.shiftKey parameter to check if shift was pressed.
You can use it this way, (I let all the logic in a simple controller so that it is easier to understand but I advice you to put it inside a service).
Also I have chosen to store only the rows index but it works the same with full rows.
HTML
<tr ng-repeat="row in rows track by $index" ng-click="selectRow($event, $index)" ng-class="{highlitedRow: isRowSelected($index)}">
AngularJS
var selectedRowsIndexes = [];
$scope.rows = [{name: 'Happy Butterfly'}, {name: 'Wonderful Bee'}];
$scope.selectRow = function(event, rowIndex) {
if(event.ctrlKey) {
changeSelectionStatus(rowIndex);
} else if(event.shiftKey) {
selectWithShift(rowIndex);
} else {
selectedRowsIndexes = [rowIndex];
}
console.log(selectedRowsIndexes);
console.log(getSelectedRows());
console.log(getFirstSelectedRow());
};
function selectWithShift(rowIndex) {
var lastSelectedRowIndexInSelectedRowsList = selectedRowsIndexes.length - 1;
var lastSelectedRowIndex = selectedRowsIndexes[lastSelectedRowIndexInSelectedRowsList];
var selectFromIndex = Math.min(rowIndex, lastSelectedRowIndex);
var selectToIndex = Math.max(rowIndex, lastSelectedRowIndex);
selectRows(selectFromIndex, selectToIndex);
}
function getSelectedRows() {
var selectedRows = [];
angular.forEach(selectedRowsIndexes, function(rowIndex) {
selectedRows.push($scope.rows[rowIndex]);
});
return selectedRows;
}
function getFirstSelectedRow() {
var firstSelectedRowIndex = selectedRowsIndexes[0];
return $scope.rows[firstSelectedRowIndex];
}
function selectRows(selectFromIndex, selectToIndex) {
for(var rowToSelect = selectFromIndex; rowToSelect <= selectToIndex; rowToSelect++) {
select(rowToSelect);
}
}
function changeSelectionStatus(rowIndex) {
if($scope.isRowSelected(rowIndex)) {
unselect(rowIndex);
} else {
select(rowIndex);
}
}
function select(rowIndex) {
if(!$scope.isRowSelected(rowIndex)) {
selectedRowsIndexes.push(rowIndex)
}
}
function unselect(rowIndex) {
var rowIndexInSelectedRowsList = selectedRowsIndexes.indexOf(rowIndex);
var unselectOnlyOneRow = 1;
selectedRowsIndexes.splice(rowIndexInSelectedRowsList, unselectOnlyOneRow);
}
function resetSelection() {
selectedRowsIndexes = [];
}
$scope.isRowSelected = function(rowIndex) {
return selectedRowsIndexes.indexOf(rowIndex) > -1;
};
});
Last thing, if you want to use powerfull tables, I recommend you ng-table.
If you use ng-table, be sure to add
$scope.$on('ngTableAfterReloadData', function() {
resetSelection();
});
and replace $scope.rows[rowIndex] with $scope.tableParams.data[rowIndex]

In this sample i try to detect which row is selected, so i add selected param to the each object which already selected, and then we can use $filter to detect the selected rows.
var app = angular.module("app", []);
app.controller("ctrl", function($scope, $filter) {
$scope.users = [{
name: "x"
}, {
name: "y"
}, {
name: "z"
}];
$scope.selectedRows = [];
$scope.select = function(item) {
item.selected ? item.selected = false : item.selected = true;
}
$scope.getAllSelectedRows = function() {
var selectedRows = $filter("filter")($scope.users, {
selected: true
}, true);
$scope.selectedRows = selectedRows;
}
});
body {
padding-top: 50px;
}
tr td {
cursor: pointer
}
tr.selected td {
background: #ccc!important;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0-rc.0/angular.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" />
<div ng-app="app" ng-controller="ctrl">
<div class="container">
<table class="table table-bordered">
<tr ng-repeat="user in users" ng-class="{'selected': user.selected}" ng-click="select(user)">
<td ng-bind="user.name" title="click to select a row"></td>
</tr>
</table>
<button class="btn btn-primary" ng-click="getAllSelectedRows()">Get All Selected Rows</button>
{{selectedRows | json}}
</div>
</div>

This demo uses Ctrl, Shift and combination of both for multiple selection of table rows in angularJs.
For plnkr demo.
http://plnkr.co/edit/IGBCkLpmK4ecJ9RUsALa?p=preview
Html goes like this
<!DOCTYPE html>
<html ng-app="app">
<head>
<script data-require="angular.js#*" data-semver="4.0.0" src="https://code.angularjs.org/latest/angular.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-controller="multiSelectController as vm">
{{vm.a}}
<table>
<thead>
</thead>
<tbody style="border:1px solid blue;">
<tr ng-repeat="item in vm.externalProductsTypes" ng-click="vm.selectUnselectMultiple($index,$event)" ng-class="{'selected': vm.isRowSelectedUnselect($index)}">
<td style="border:1px solid blue;">{{item.id}}</td>
<td style="border:1px solid blue;">
<div style="float:left; margin-right: 10px;">{{item.name}}</div>
</td>
</tr>
</tbody>
</table>
</body>
</html>
controller goes like this
var app = angular.module('app', []);
app.controller('multiSelectController', function() {
var vm = this;
/* Data loading */
vm.externalProductsTypes = [{
"id":1,
"name": "Access Winback"
}, {
"id":2,
"name": "ADSL",
}, {
"id":3,
"name": "Bigpond ADSL Activation",
}, {
"id":4,
"name": "Bigpond ADSL Recontracting",
}, {
"id":5,
"name": "Bigpond Cable Activation",
}, {
"id":6,
"name": "Bigpond Cable Recontracting",
}, {
"id":7,
"name": "Bigpond VAS",
}, {
"id":8,
"name": "Bigpond Wireless Activation",
}, {
"id":9,
"name": "Bigpond Wireless Recontracting",
}, {
"id":10,
"name": "Broadband Right Plan",
}];
/* click function */
vm.selectUnselectMultiple = function (idx, event) {
if (event.which != '1') {
return;
}
var row = vm.externalProductsTypes[idx];
row.rowIndex = idx;
if (!event.ctrlKey && !event.shiftKey) {
vm.clearAll();
vm.toggleRow(row);
vm.selectionPivot = row;
return;
}
if (event.ctrlKey && event.shiftKey) {
vm.selectRowsBetweenIndexes(vm.selectionPivot.rowIndex, row.rowIndex);
return;
}
if (event.ctrlKey) {
vm.toggleRow(row);
vm.selectionPivot = row;
}
if (event.shiftKey) {
vm.clearAll();
vm.selectRowsBetweenIndexes(vm.selectionPivot.rowIndex, row.rowIndex);
}
}
/* other supported functions */
vm.toggleRow = function (row) {
row.className = row.className == 's' ? '' : 's';
}
vm.selectRowsBetweenIndexes = function (ia, ib) {
var bot = Math.min(ia, ib);
var top = Math.max(ia, ib);
for (var i = bot; i <= top; i++) {
vm.externalProductsTypes[i].className = 's';
}
}
vm.clearAll = function () {
for (var i = 0; i < vm.externalProductsTypes.length; i++) {
vm.externalProductsTypes[i].className = '';
}
}
vm.isRowSelectedUnselect = function (index) {
if (vm.externalProductsTypes[index].className=='s') { // if found, then select the row.
return true;
}
}
});
finally the css for row selection
.selected {
background-color: steelblue !important;
color: white;
font-weight: bold;
}

Related

How can I assign a class to a report not on odd, even rows but on the change of a date column?

I have a very simple report in AngularJS:
<div class="gridHeader">
<div>User</div>
<div>Date</div>
<div>Count</div>
</div>
<div class="gridBody"
<div class="gridRow" ng-repeat="row in rps.reports">
<div>{{row.user}}</div>
<div>{{row.date}}</div>
<div>{{row.count}}</div>
</div>
</div>
The report works but it's difficult to notice when the date changes.
Is there some way that I could assign a class to the grid row so that one date grid row has one class and the next date the grid row has another class. I think this is already available for odd and even rows with Angular but here I need it to work on every date change.
I've done a different solution (PLUNKER) where whole work is inside the controller. Maybe is a little bit more of code, but you will gain a lot of performance if you have thousand records because you will avoid dirty checking of ng-class. Additionally if your report is static and it won't have any changes, you can disable the two data binding...
CONTROLLER
vm.reports = addReportCssClases();
function addReportCssClases() {
var reportsData = [...];
var classes = ['dateOdd', 'dateEven'];
var index = 0;
reportsData[0].cssClass = classes[index];
for (var i = 1; i < reportsData.length; i++) {
var row = reportsData[i];
index = (row.date !== reportsData[i-1].date) ? (1 - index) : index;
row.cssClass = classes[index] ;
}
return reportsData;
}
HTML
<div ng-repeat="row in vm.reports track by $index" class="gridRow {{::row.cssClass}}">
<div>{{::row.user}}</div>
<div>{{::row.date}}</div>
<div>{{::row.count}}</div>
</div>
You can use ng-class with a function defined in your controller. For example:
var currentColor = "color1";
$scope.getClass = function(index)
{
if (index !== 0 && $scope.data[index].date !== $scope.data[index - 1].date)
{
currentColor = currentColor == "color1" ? "color2" : "color1";
}
return currentColor;
}
And in your template:
<div class="gridRow" ng-repeat="(i, d) in data" data-ng-class="getClass(i)">
See the plunker: http://plnkr.co/edit/PPPJRJJ1jHuJOgwf9lNK
This can be done with a one line given all the dates are actually date with the same format and not date time
<div class="gridHeader">
<div>User</div>
<div>Date</div>
<div>Count</div>
</div>
<div class="gridBody">
<div ng-repeat="row in rps.reports" class="gridRow" ng-class="{'backblue':$index>0 && row.date!=rps.reports[$index-1].date}">
<div>{{row.user}}</div>
<div>{{row.date}}</div>
<div>{{row.count}}</div>
</div>
</div>
your gridRow class will have to contain the background-color
.gridRow{
//other styles
background-color:red;
}
and the class backblue will have to have only the background-color
.backblue{
background-color:blue !important;
}
IMPORTANT
This will only work if the date field is only date and does not have time. If in any case it does have time you will have to convert each datetime to date
I have created a very simple and working solution for this using angular-filter, only you need to add dynamic class on gridRow.
working jsfiddle
HTML
<div ng-repeat="row in rps.reports"
class="gridRow {{row.date | filterDate}}">
Styles
.even {
background-color: green;
color: #fff;
}
.odd {
background-color: red;
color: #000;
}
Angular-filter
myApp.filter('filterDate', function() {
var lastDate,
count = 0,
calssName = 'even';
return function(date) {
var newDate = new Date(date).toDateString();
!lastDate && (lastDate = newDate);
if (newDate != lastDate) {
if (calssName == 'even') {
calssName = 'odd';
} else {
calssName = 'even';
}
}
lastDate = newDate;
return calssName;
}
});
A modified version of #ssougnez answer by storing the current date also in addition to color:
if(!(currentDate && currentDate === data.date)){
currentColor = currentColor == "color1" ? "color2" : "color1";
currentDate = data.date;
}
Plunker: http://plnkr.co/edit/o3YVBB
This might have less impact on performance than his version.
var app = angular.module('sample', []);
app.controller('SampleController', function($scope)
{
$scope.data = [
{
user: "A",
date: "3/2/2017"
},
{
user: "B",
date: "3/4/2017"
},
{
user: "C",
date: "4/3/2017"
},
{
user: "D",
date: "4/3/2017"
},
{
user: "E",
date: "4/3/2017"
},
{
user: "F",
date: "4/2/2017"
}
];
});
.same{
background-color:#ddd;
}
.differ{
background-color:yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html ng-app="sample">
<head>
</head>
<body>
<div ng-controller="SampleController">
<table id="myTable">
<thead>
<tr>
<th>User</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in data">
<td>{{row.user}}</td>
<td class="same" ng-if="data[$index+1].date==row.date || data[$index-1].date==row.date">{{row.date}}</td>
<td class="differ" ng-if="data[$index+1].date!=row.date && data[$index-1].date!=row.date">{{row.date}}</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
You can add the class with an expression using the ngClass directive in the view:
(function() {
'use strict';
angular.module('app', []);
})();
(function() {
'use strict';
angular.module('app').controller('MainController', MainController);
MainController.$inject = ['$scope'];
function MainController($scope) {
var date = new Date();
$scope.rps = {
reports: [{
user: 'User A',
date: date.setDate(date.getDate() + 1),
count: 5
},
{
user: 'User B',
date: date.setDate(date.getDate() + 2),
count: 10
},
{
user: 'User C',
date: date.setDate(date.getDate()),
count: 8
},
{
user: 'User D',
date: date.setDate(date.getDate() + 2),
count: 6
},
{
user: 'User E',
date: date.setDate(date.getDate()),
count: 20
},
{
user: 'User F',
date: date.setDate(date.getDate() + 3),
count: 6
}
]
};
}
})();
.gridHeader,
.gridRow {
display: table-row;
}
.gridHeader>div,
.gridRow>div {
display: table-cell;
padding: 5px 10px;
}
.className {
background: #ff0000;
color: #fff;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.3/angular.min.js"></script>
<div ng-app="app" ng-controller="MainController as MainCtrl">
<div class="gridHeader">
<div>User</div>
<div>Date</div>
<div>Count</div>
</div>
<div class="gridBody">
<div class="gridRow" ng-repeat="row in ::rps.reports track by $index" ng-class="::{'className': $index === 0 || $index > 0 && rps.reports[$index - 1].date !== row.date}">
<div>{{::row.user}}</div>
<div>{{::row.date | date: 'dd-MM-yyyy'}}</div>
<div>{{::row.count}}</div>
</div>
</div>
</div>
Or add a boolean in the controller that you can use to trigger the className using the ngClass directive:
(function() {
'use strict';
angular.module('app', []);
})();
(function() {
'use strict';
angular.module('app').controller('MainController', MainController);
MainController.$inject = ['$scope'];
function MainController($scope) {
var date = new Date();
$scope.rps = {
reports: [{
user: 'User A',
date: date.setDate(date.getDate() + 1),
count: 5
},
{
user: 'User B',
date: date.setDate(date.getDate() + 2),
count: 10
},
{
user: 'User C',
date: date.setDate(date.getDate()),
count: 8
},
{
user: 'User D',
date: date.setDate(date.getDate() + 2),
count: 6
},
{
user: 'User E',
date: date.setDate(date.getDate()),
count: 20
},
{
user: 'User F',
date: date.setDate(date.getDate() + 3),
count: 6
}
]
};
// add the classes to the reports
addClasses($scope.rps.reports);
/*
* #name addClasses
* #type function
*
* #description
* Adds a class to a report if the date is different to the previous
*
* #param {array} reports The reports to add classes to
* #return nothing.
*/
function addClasses(reports) {
// loop through the reports to check the dates
for (var i = 0, len = reports.length; i < len; i++) {
// if the previous report a different date then the current report will have a class
reports[i].hasClass = (i === 0 || reports[i - 1].date !== reports[i].date);
}
}
}
})();
.gridHeader,
.gridRow {
display: table-row;
}
.gridHeader>div,
.gridRow>div {
display: table-cell;
padding: 5px 10px;
}
.className {
background: #ff0000;
color: #fff;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.3/angular.min.js"></script>
<div ng-app="app" ng-controller="MainController as MainCtrl">
<div class="gridHeader">
<div>User</div>
<div>Date</div>
<div>Count</div>
</div>
<div class="gridBody">
<div class="gridRow" ng-repeat="row in ::rps.reports track by $index" ng-class="::{'className': row.hasClass}">
<div>{{::row.user}}</div>
<div>{{::row.date | date: 'dd-MM-yyyy'}}</div>
<div>{{::row.count}}</div>
</div>
</div>
</div>

Show unique items with count of duplicate occurrences in ng-repeat

I have a below JSON:
[{"brand":"abc"},{"brand":"xyz"},{"brand":"abc"},{"brand":"abc"},{"brand":"por"},{"brand":"xyz"}]
Using ng-repeat, How can I display like -
Brand Occurances
abc (3)
xyz (2)
por (1)
i.e. brand name (number of duplicate occurrences of same brand name)?
You can create a custom function which will be returning the count from the existing array with the repeatvie values (occurances)
Along with the filter to show the unique values from the JSON:
$scope.getCount = function(i) {
var iCount = iCount || 0;
for (var j = 0; j < $scope.brands.length; j++) {
if ($scope.brands[j].brand == i) {
iCount++;
}
}
return iCount;
}
AND a filter will look like this:
app.filter('unique', function() {
return function (arr, field) {
var o = {}, i, l = arr.length, r = [];
for(i=0; i<l;i+=1) {
o[arr[i][field]] = arr[i];
}
for(i in o) {
r.push(o[i]);
}
return r;
};
})
Working Plunkr
To get unique items from an array you could write a custom filter, in AngularJS filters are used to modify the data to be displayed to the user and in order to get the count of the duplicate items from an array you can write a function on the controller's scope and call it in the view.
Check the below code snippet on how to achieve it.
angular
.module('demo', [])
.controller('DefaultController', DefaultController)
.filter('unique', unique);
function DefaultController() {
var vm = this;
vm.items = [
{
"brand":"abc"
},
{
"brand":"xyz"
},
{
"brand":"abc"
},
{
"brand":"abc"
},
{
"brand":"por"
},
{
"brand":"xyz"
}
];
vm.getBrandCount = getBrandCount;
function getBrandCount(brand) {
var count = 0;
if (brand !== undefined && brand !== null && brand.length > 0) {
for (var i = 0; i < vm.items.length; i++) {
if (vm.items[i].brand === brand) {
count++;
}
}
}
return count;
}
}
function unique() {
return function(array, key) {
if (angular.isArray(array) && array.length > 0 && key !== undefined && key !== null && key.length > 0) {
var arr = [], keys = [];
for (var i = 0; i < array.length; i++) {
if (keys.indexOf(array[i][key]) === -1) {
keys.push(array[i][key]);
arr.push(array[i]);
}
}
return arr;
}
return array;
}
}
.brandcount:before {
content: '('
}
.brandcount:after {
content: ')'
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="demo">
<div ng-controller="DefaultController as ctrl">
<table>
<thead>
<tr>
<th>Brand</th>
<th>Occurances</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in ctrl.items | unique: 'brand'">
<td>
<span ng-bind="item.brand"></span>
</td>
<td>
<span class="brandcount" ng-bind="ctrl.getBrandCount(item.brand)"></span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
You need to first process your data before passing to ng-repeat like this:
var app = angular.module("sa", []);
app.controller("FooController", function($scope) {
var data = [{
"brand": "abc"
}, {
"brand": "xyz"
}, {
"brand": "abc"
}, {
"brand": "abc"
}, {
"brand": "por"
}, {
"brand": "xyz"
}];
$scope.processedData = [];
// Group the raw data based on the brand name and store the count
function groupData() {
angular.forEach(data, function(item) {
// Check https://github.com/sagrawal14/angular-extras/blob/master/src/extras/array.js for this utility "find" method
var existingBrand = $scope.processedData.find("brand", item.brand);
if (!existingBrand) {
existingBrand = item;
existingBrand.count = 0;
$scope.processedData.push(existingBrand);
}
existingBrand.count++;
});
}
groupData();
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://rawgit.com/sagrawal14/angular-extras/master/src/extras/array.js"></script>
<div ng-app="sa" ng-controller="FooController">
<table>
<tr ng-repeat="data in processedData">
<td>{{data.brand}}</td>
<td>{{data.count}}</td>
</tr>
</table>
<br><br>Processed/grouped data: {{processedData | json}}
</div>

angularjs filter ng-repeat links outside of list

Building a simple app that filters results based on an input field. I'm adding a bunch of links that are outside of the repeated list and when clicked I want to be able to filter the list below.
I've searched online for various ways of tackling this problem but have yet to find a solution.
I'm already filtering by search, however I'd like to be able to click a link that is a popular search entry.
My code is:
tag one
tag two
tag three
<tr ng-repeat="item in items | orderBy:'date' | filter:itemsFilter" ng-click="clickedItem(item.id)">
<td><img ng-src="{{item.imageUrl}}" alt="{{item.title}}"></td>
<td>
{{item.title}}<br>
</td>
<td><i class="el el-time"></i> {d{item.date}}</td>
<td class="drop-me">{{item.description}}</td>
<td class="tag-me">{{item.tag}}</td>
</tr>
<tr ng-hide="item.length == 0"><td><p>There are no items!</p></td></tr>
I've tried custom filters, just can't figure a way to inject the items via an ng-click and update the list below.
I'd like to click one of the tag links and it filter the list below
Thanks
As proposed in the comments you can create an array to which you're adding your tags for filtering and in a custom filter you can filter your items array.
Also ngTagsInput is a nice directive that's helping to create a input field with tags.
Please have a look at the demo below or this jsfiddle.
angular.module('demoApp', ['ngTagsInput'])
// filter from here (with some modifications) http://stackoverflow.com/questions/23785592/apply-dynamic-filters-using-tags
.filter('filterByTags', function() {
return function(items, tags) {
var filtered = []; // Put here only items that match
(items || []).forEach(function(item) { // Check each item
var matches = tags.some(function(tag) { // If there is some tag
return item.tag == tag.text;
}); // we have a match
if (matches) { // If it matches
filtered.push(item); // put it into the `filtered` array
}
});
return filtered.length == 0 ? items : filtered; // Return the array with items that match any tag // return all if no tags
};
})
.controller('mainController', MainCtrl);
function MainCtrl() {
var vm = this;
function isTagInTags(tag) {
var seen = false;
//console.log('test', tag);
for (var i = 0; i < vm.tags.length; i++) {
//console.log(vm.tags[i].text, tag);
if (vm.tags[i].text == tag) {
seen = true;
return seen;
}
}
return seen;
}
vm.addTag = function(tag) {
//console.log(tag);
if (!isTagInTags(tag)) {
vm.tags.push({
text: tag
});
}
};
vm.data = [{
id: 0,
tag: 'JavaScript',
title: 'this is JS related'
}, {
id: 1,
tag: 'Java',
title: 'this is Java related'
}, {
id: 2,
tag: 'Python',
title: 'this is Python related'
}, {
id: 3,
tag: 'Python',
title: 'also Python stuff...'
}];
var unique = [];
vm.availTags = [];
for (i in vm.data) {
var item = vm.data[i];
//console.log(item);
if (unique.indexOf(item.tag) === -1) {
unique.push(item.tag);
vm.availTags.push(item.tag);
}
}
vm.loadItems = function(query) {
//console.log(query);
return vm.availTags.filter(function(tag) {
var testTag = tag.toLowerCase();
return testTag.indexOf(query.toLowerCase()) >= 0;
});
//return $http.get('/tags?query=' + query); // use this with a backend
}
//console.log(vm.availTags);
vm.tags = []; //vm.availTags[0];
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ng-tags-input/3.1.1/ng-tags-input.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/ng-tags-input/3.1.1/ng-tags-input.css" rel="stylesheet" />
<div ng-app="demoApp" ng-controller="mainController as ctrl">
<button ng-click="ctrl.addTag('JavaScript')">
JavaScript
</button>
<button ng-click="ctrl.addTag('Java')">
Java
</button>
<!--{{ctrl.tags}}-->
<tags-input ng-model="ctrl.tags">
<auto-complete source="ctrl.loadItems($query)"></auto-complete>
</tags-input>
<div ng-repeat="item in ctrl.data | filterByTags: ctrl.tags">
{{item.title}}
</div>
</div>
try addition ng-href
<a ng-href="">tag one</a>
<a ng-href="">tag two</a>
<a ng-href="">tag three</a>
<tr ng-repeat="item in items | orderBy:'date' | filter:itemsFilter" ng-click="clickedItem(item.id)">
<td><a ng-href="{{item.url}}"><img ng-src="{{item.imageUrl}}" alt="{{item.title}}"></a></td>
<td>
<a ng-href="{{item.url}}">{{item.title}}</a><br>
</td>
<td><i class="el el-time"></i> {d{item.date}}</td>
<td class="drop-me">{{item.description}}</td>
<td class="tag-me">{{item.tag}}</td>
</tr>
<tr ng-hide="item.length == 0"><td><p>There are no items!</p></td></tr>

How to Add a product to the cart and then update the quantity from both the product page and from the cart itself.

I am completely stumped on how to achieve something specific that the below website has achieved. Does anyone know how to update the quantity of a product from the product details page to the shopping cart, and have that quantity shared/bound between the cart and and the product details page for each an every product repeated from a collection. (I am not talking about simply having a global cart quantity total via a simple custom directive). Please see the link below. Add a product to the cart and then update the quantity from both the product page and from the cart itself. This is what I am trying to achieve. Thank you all in advance!
http://demo.shopnx.in/
Typically you'll get better responses if you post some code that you have tried and then ask to be guided on where you are going wrong. I've created a simple JSFiddle to demonstrate one method of doing this. It is extremely simple, contrived, not production worthy by any stretch of the imagination and doesn't really do much, but it should show you one construct that will allow you to accomplish the functionality you're after.
The key is to use some type of shared storage so that the same array of items is available to both your product listing and the cart. In the sample I have done this using a Value:
.value('cartStorage', {
items: []
})
This value is then injected in the main controller:
.controller('mainController', function(cartStorage) {
var _this = this;
_this.cartStorage = cartStorage;
_this.items = [{
name: 'Apple',
price: .5,
quantity: 0,
showAddToCart: false,
addedToCart: false
}, {
name: 'Orange',
price: .5,
quantity: 0,
showAddToCart: false,
addedToCart: false
}, {
name: 'Grapes',
price: 1,
quantity: 0,
showAddToCart: false,
addedToCart: false
}];
_this.addToCart = function(item) {
_this.cartStorage.items.push(item);
item.addedToCart = true;
}
_this.increaseItemAmount = function(item) {
item.quantity++;
item.showAddToCart = true;
}
_this.decreaseItemAmount = function(item) {
item.quantity--;
if (item.quantity <= 0) {
item.quantity = 0;
item.addedToCart = false;
item.showAddToCart = false;
var itemIndex = _this.cartStorage.items.indexOf(item);
if (itemIndex > -1) {
_this.cartStorage.items.splice(itemIndex, 1);
}
} else {
item.showAddToCart = true;
}
}
})
As well as the cart controller:
.controller('cartController', function(cartStorage) {
var _this = this;
_this.cartStorage = cartStorage;
_this.increaseItemAmount = function(item) {
item.quantity++;
}
_this.decreaseItemAmount = function(item) {
item.quantity--;
if (item.quantity <= 0) {
item.quantity = 0;
item.addedToCart = false;
item.showAddToCart = false;
var itemIndex = _this.cartStorage.items.indexOf(item);
if (itemIndex > -1) {
_this.cartStorage.items.splice(itemIndex, 1);
}
}
}
_this.removeFromCart = function(item) {
item.quantity = 0;
item.addedToCart = false;
item.showAddToCart = false;
var itemIndex = _this.cartStorage.items.indexOf(item);
if (itemIndex > -1) {
_this.cartStorage.items.splice(itemIndex, 1);
}
}
})
Now the cartStorage object is shared so any update made in one controller will automagically be reflected in the other controller. All that's left is the markup:
<div ng-app="app">
<div ng-controller="mainController as main">
<h2>Main Controller</h2>
<div>
<table>
<tr>
<td>Item</td>
<td>Price</td>
<td>Quantity</td>
<td></td>
</tr>
<tr ng-repeat="item in main.items">
<td>{{item.name}}</td>
<td>{{item.price | currency}}</td>
<td>{{item.quantity}}</td>
<td>
<button ng-click="main.increaseItemAmount(item)">+</button>
<button ng-click="main.decreaseItemAmount(item)">-</button>
<button ng-click="main.addToCart(item)" ng-show="item.showAddToCart && !item.addedToCart">Add to Cart</button>
</td>
</tr>
</table>
</div>
</div>
<div ng-controller="cartController as cart">
<h2>Cart Controller</h2>
<div>
<table>
<tr>
<td>Item</td>
<td>Price</td>
<td>Quantity</td>
<td></td>
</tr>
<tr ng-repeat="item in cart.cartStorage.items">
<td>{{item.name}}</td>
<td>{{item.price | currency}}</td>
<td>{{item.quantity}}</td>
<td>
<button ng-click="cart.increaseItemAmount(item)">+</button>
<button ng-click="cart.decreaseItemAmount(item)">-</button>
<button ng-click="cart.removeFromCart(item)">Remove from Cart</button>
</td>
</tr>
</table>
</div>
</div>
</div>
Update showing the usage of a Factory instead of Value
Instead of using a Value use this service:
.factory('cartStorage', function() {
var _cart = {
items: []
};
var service = {
get cart() {
return _cart;
}
}
return service;
})
Then modify the code in the controllers to use the .cart property of the service instead of the value. You only need to change one line of code in both controllers. Change:
_this.cartStorage = cartStorage;
to:
_this.cartStorage = cartStorage.cart;
Here is an updated JSFiddle.
I made this plunker as an example.
I've used events to achieve the desired behavior. (
This is just one way of doing this, should have a lot of possibilities)
ProductsController:
app.controller('ProductsCtrl', function($scope, $rootScope) {
$scope.products = [
{
'name': 'Product One',
'price': 10,
'qty': 0
},
{
'name': 'Product two',
'price': 20,
'qty': 0
}
];
// Fire event to add
$scope.add = function(product) {
product.qty++;
$rootScope.$broadcast('addProduct', product.price);
}
// Fire event to remove
$scope.remove = function(product) {
if(product.qty > 0) {
product.qty--;
$rootScope.$broadcast('removeProduct', product.price);
}
}
});
CartController:
app.controller('CartCtrl', function($scope) {
$scope.total = 0;
// Catch the event to add
$scope.$on('addProduct', function(event, data) {
$scope.total += data;
});
// Catch the event to remove
$scope.$on('removeProduct', function(event, data) {
$scope.total -= data;
});
});
View:
<div ng-controller="CartCtrl">Total: {{total}}</div>
<br>
<div ng-controller="ProductsCtrl">
<div ng-repeat="product in products">
<span>Name: {{product.name}}</span>
<br>
<span>Price:{{product.price}}</span>
<span>Quantity:{{product.qty}}</span>
<br>
<button type="button" ng-click="add(product);">Add</button>
<button type="button" ng-click="remove(product);">Remove</button>
<br><br><br>
</div>
</div>
You can have a shared service between your Product details and Cart detail controller which can have an array where you can push the the Product selected with its quantity and other details.

Angular object property value change not propagated into view using ng-repeat

I'm trying to generate a table using ng-repeat.
Use case
The data to generate the table from looks as follows:
$scope.data = [
{
name : 'foo1',
group : 1
},
{
name : 'foo2',
group : 1
},
{
name : 'foo3',
group : 1
},
{
name : 'foo4',
group : 1
},
{
name : 'foobar',
group : 2
},
{
name : 'foobarbar',
group : 3
}
];
The html generated should look like this:
<tr>
<th>Group</th>
<th>Name</th>
</tr>
<tr>
<td rowspan="4">1</td>
<td>foo1</td>
</tr>
<tr>
<td>foo2</td>
</tr>
<tr>
<td>foo3</td>
</tr>
<tr>
<td>foo4</td>
</tr>
<tr>
<td rowspan="1">2</td>
<td>foobar</td>
</tr>
<tr>
<td rowspan="1">2</td>
<td>foobarbar</td>
</tr>
Implementation
I know the easiest way would probably be to pre-process the data and group the items per group in a new array of arrays. However, I chose a different approach:
<td
ng-if = "isDifferentFromPrev(items, $index, groupingData)"
rowspan = "{{item._groupSize}}"
>
with
$scope.isDifferentFromPrev = function(array, index, groupingData){
if(index === 0){
groupingData.startI = 0;
groupingData.counter = 1;
array[0]._groupSize = 1;
return true;
}
var eq = equalsMethod(array[index], array[index-1]);
if(eq){
groupingData.counter++;
array[groupingData.startI]._groupSize = groupingData.counter;
}
else{
groupingData.startI = index;
groupingData.counter = 1;
array[index]._groupSize = 1;
}
return !eq;
};
Problem
For some reason the rendered value for rowspan is always 1.
The attribute is only set for the first td of the first tr of a group, as intended, but the value for it is 1.
If I put a breakpoint inside isDifferentFromPrev(), the values seem to be updated correctly. This does not reflect in the html though.
Solution?
It occured to me that maybe ng-repeat renders each step sequentially, without returning to it. So maybe the _groupSize values for the first item of each group do get properly updated, but since they are updated after that item has already been rendered by ng-repeat, the update isn't processed anymore.
I have no idea if this reasoning is correct, nor about how to solve it. Any suggestions please?
This solution, even if a bit orthodox, does work:
var app = angular.module("myApp", []);
app.controller("myController", function($scope) {
$scope.data = [{
name: 'foo1',
group: 1
}, {
name: 'foo2',
group: 1
}, {
name: 'foo3',
group: 1
}, {
name: 'foo4',
group: 1
}, {
name: 'foobar',
group: 2
}, {
name: 'foobarbar',
group: 3
}];
$scope.itemHasRowspan = function(item) {
return typeof item === "object" && item.hasOwnProperty("rowspan");
};
var groupData = {},
currentGroup = null,
addGroup = function(firstItem) {
currentGroup = firstItem.group;
groupData[firstItem.group] = {
"firstItem": firstItem,
"count": 1
};
};
angular.forEach($scope.data, function(item, index) {
if (item.group !== currentGroup) {
addGroup(item);
} else {
groupData[item.group].count++;
}
});
angular.forEach(groupData, function(group, index) {
group.firstItem["rowspan"] = group.count;
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="myController">
<table>
<thead>
<tr>
<th>Group</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in data">
<td ng-if="itemHasRowspan(item)" rowspan="{{ item.rowspan }}" valign="top">
{{ item.group }}
</td>
<td>
{{ item.name }}
</td>
</tr>
</tbody>
</table>
</div>
</div>

Resources