Angular scope nesting in ui bootsrap tab containing pagination - angularjs

I've come across an issue with angular and ui-bootstrap which I'm not sure I have an elegant solution for.
I have a UI Bootstrap tabset, with one tab containing a table with a UI Bootstrap pagination template.
Since each tab seems to have a scope of its own, the table row (with ng-repeat) must reference the $parent.test as a data source rather than the variable name directly.
As such, I'm not sure if this is the best solution, even if it seems to work perfectly fine? what if you have more nested scopes? Would you need to call $parent.$parent.$parent.test to access the data in your main controller scope?
Thanks
var testApp = angular.module('testApp', ['ui.bootstrap','testControllers']);
var testdata =
[{'number': '1', 'name': 'A' }
,{'number': '2', 'name': 'B' }
,{'number': '3', 'name': 'C' }
,{'number': '4', 'name': 'D' }
,{'number': '5', 'name': 'E' }
,{'number': '6', 'name': 'F' }];
var testControllers = angular.module('testControllers', []);
testControllers.controller('TestListController', function($scope){
$scope.totalItems = 6;
$scope.page = 1;
$scope.itemsPerPage = 2;
$scope.getData = function () {
$scope.test = [testdata[$scope.page*2-2],testdata[$scope.page*2-1]];
};
$scope.getData();
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.0/ui-bootstrap-tpls.min.js"></script>
<div ng-app="testApp">
<div ng-controller='TestListController'>
<tabset>
<tab><tab-heading>Tab 1</tab-heading>
<table>
<thead>
<tr>
<th>Item</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in $parent.test">
<td>
{{item.number}}
</td>
<td>
{{item.name}}
</td>
</tr>
</tbody>
</table>
<pagination
total-items="$parent.totalItems"
ng-model="$parent.page"
max-size="5"
boundary-links="true"
ng-change="$parent.getData()"
items-per-page="$parent.itemsPerPage"
previous-text="<" next-text=">"
first-text="<<" last-text=">>">
</pagination>
</tab>
</tabset>
</div>
</div>

The issue is that you are not keeping a reference to the original "this" context in the $scope.getData() function call. Have a look at the code below:
I also suggest you read through this article: Getting Out of Binding Situations in JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.0/ui-bootstrap-tpls.min.js"></script>
<meta charset="UTF-8">
<title>Document</title>
<script>
var testApp = angular.module('testApp', ['ui.bootstrap','testControllers']);
var testdata =
[{'number': '1', 'name': 'A' }
,{'number': '2', 'name': 'B' }
,{'number': '3', 'name': 'C' }
,{'number': '4', 'name': 'D' }
,{'number': '5', 'name': 'E' }
,{'number': '6', 'name': 'F' }];
var testControllers = angular.module('testControllers', []);
testControllers.controller('TestListController', function($scope){
// Pagination
$scope.total = testdata.length;
$scope.maxSize = 10;
$scope.currentPage = 1;
$scope.numPerPage = 2;
$scope.getData = function () {
// keep a reference to the current instance "this" as the context is changing
var self = this;
console.log(self.currentPage);
var itemsPerPage = self.numPerPage;
var offset = (self.currentPage-1) * itemsPerPage;
$scope.test = testdata.slice(offset, offset + itemsPerPage)
};
$scope.getData();
});
</script>
</head>
<body>
<div ng-app="testApp">
<div ng-controller='TestListController'>
<tabset>
<tab><tab-heading>Tab 1</tab-heading>
<table>
<thead>
<tr>
<th>Item</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in test">
<td>{{item.number}}</td><td>{{item.name}}</td>
</tr>
</tbody>
</table>
<pagination
total-items="total"
ng-model="currentPage"
max-size="maxSize"
boundary-links="true"
ng-change="getData()"
items-per-page="numPerPage"
previous-text="<" next-text=">"
first-text="<<" last-text=">>">
</pagination>
<div>
Page: {{currentPage}}<br/>
Total Items: {{total}}
</div>
</tab>
</tabset>
</div>
</div>
</body>
</html>

Related

angular, select with dynamic disabled

Now I am trying to select box with disabled.
When $shop.products.product[i].productId is changed by selectBox, I want that value disabled in select box option.
for example, if i select 3 in select box on second row, then select box option 1,3 are disabled.
so i register $watch witch resync isDisabled field when $shop.products[i].productId is changed.
i don't know why $watch is no calling. help me please.
// Code goes here
angular.module("myApp")
.controller("myCtrl", function($scope) {
$scope.shop = {
"id" : 'shop1',
"products" : [
{"productId" : 1,"desc" : 'product1'},
{"productId" : 2,"desc" : 'product2'}
]
};
$scope.allSelectBoxItems = [
{"id" : 1, "isDisabled" : true},
{"id" : 2, "isDisabled" : true},
{"id" : 3, "isDisabled" : false},
{"id" : 4, "isDisabled" : false}
];
$scope.addWatcher = function(index){
console.log('register watcher to ' + index);
$scope.$watch('shop.product[' + index + '].productId', function(newValue, oldValue){
console.log('watcher envoked');
if (newValue != oldValue) {
var selected = $scope.shop.products.map(function (product) {
return product.productId;
});
for (var i = 0; i < $scope.allSelectBoxItems.length; i++) {
var isSelected = selected.includes($scope.allSelectBoxItems[i].productId);
console.log(isSelected);
$scope.allSelectBoxItems.isDisabled = isSelected;
}
}
});
}
angular.forEach($scope.shop.products, function(value, index){
$scope.addWatcher(index);
});
$scope.addProduct = function(){
var nextProductId = $scope.shop.products.length + 1;
var newProduct = {"productId" : nextProductId ,"desc" : 'product' + nextProductId};
$scope.shop.products.push(newProduct);
$scope.addWatcher(nextProductId - 1);
}
});
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://code.angularjs.org/1.6.4/angular.min.js"></script>
<script src="https://code.angularjs.org/1.6.4/angular-animate.min.js"></script>
<script src="https://code.angularjs.org/1.6.4/angular-touch.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/2.5.0/ui-bootstrap-tpls.min.js"></script>
<script>
angular.module("myApp", ['ngAnimate', 'ui.bootstrap'])
</script>
<script src="script.js"></script>
</head>
<body ng-controller="myCtrl">
<div>
<table class="table">
<tbody>
<tr>
<th>id</th>
<td>{{shop.id}}</td>
</tr>
<tr>
<td>
products
<br>
<button type="button" class="btn btn-default" ng-click="addProduct()"><span class="glyphicon glyphicon-plus" aria-hidden="true" style="margin-right:4px"></span>add product</button>
</td>
<td>
<table class="table">
<tbody>
<tr>
<td>producId</td>
<td>desc</td>
</tr>
<tr ng-repeat="product in shop.products">
<td>
<select ng-model="product.productId" ng-options="selectBoxItem.id as selectBoxItem.id disable when selectBoxItem.isDisabled for selectBoxItem in allSelectBoxItems"></select>
</td>
<td>
{{product.desc}}
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>

Angular JS - dynamically add custom href link to value in table using ng-repeat based on a different value.

I am creating a table using ng-repeat that display some tickets info. Currently in the column "Ticket No" I am adding href link (when user click on the "Ticket No" it will open new tab and the URL will take the ticket no as parameter.
This a plunker I've created so it can show functionality as described above http://plnkr.co/edit/GB8WWz?p=preview
The problem I have now is that the href link might vary and it depends on the account column value. So if my "account = foo" set the href link of the Ticket No to "http://myfoopage.foo/ticketid...etc". If my "account = boo" set the href link for the Ticket No to "http://myboopage.boo/ticketid...etc".
Any idea on how to approach that ?
scriptang.js
angular.module('plunker', ['ui.bootstrap']);
function ListCtrl($scope, $dialog) {
$scope.items = [
{ticket: '123', description: 'foo desc',account:'foo'},
{ticket: '111', description: 'boo desc',account:'boo'},
{ticket: '222', description: 'eco desc',account:'eco'}
];
}
// the dialog is injected in the specified controller
function EditCtrl($scope, item, dialog){
$scope.item = item;
$scope.save = function() {
dialog.close($scope.item);
};
$scope.close = function(){
dialog.close(undefined);
};
}
index.html
<!doctype html>
<html ng-app="plunker">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
<script src="http://angular-ui.github.com/bootstrap/ui-bootstrap-tpls-0.1.0.js"></script>
<script src="scriptang.js"></script>
<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.0/css/bootstrap-combined.min.css" rel="stylesheet">
</head>
<body>
<div ng-controller="ListCtrl">
<table class="table table-bordered">
<thead>
<tr>
<th>Ticket No</th>
<th>Description</th>
<th>Account</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in items">
<td>{{item.ticket}}</td>
<td>{{item.description}}</td>
<td>{{item.account}}</td>
<td><button class="btn btn-primary" ng-click="edit(item)">Edit</button></td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
Updated plnkr here, I've made use of the ng-attr directive in combination with a function that creates an url.
$scope.getUrl = function (item) {
var url = '';
if(item.account === 'foo')
url = 'http://mywebpage.foo';
else
url = 'http://mwebpage.boo';
url += '/ticketid='+item.ticket
return url;
}

AngularJS Create a table from an array

I have an array of items and I want to render it as a table with 2 columns.
I did the basic implementation which is rendering with only one column. Any suggestions please?
<body ng-app="myApp" ng-controller="myCtrl">
<table>
<tr ng-repeat="i in items">
<td>{{i}}</td>
</tr>
</table>
</body>
var myApp = angular.module("myApp", []);
myApp.controller("myCtrl", function($scope) {
$scope.items = ["12", "22", "34", "657", "129"];
});
https://jsfiddle.net/nkarri/9czrqLny/1/
This is because your HTML only has a single <td> element, which you are not repeating. Since there's only 1, you get just 1 column. You'll need to have a nested ng-repeat in the <td> element in order to get more than 1 column, or explicitly have two <td> elements defined in your HTML.
You could try to write something more complicated to try to determine when a new column or row should be created, but I'd simplify things by creating your array into something that will be a little easier to consume: essentially a 2-dimensional array. Here's what I would do instead:
<body ng-app="myApp">
<div ng-controller="myCtrl">
<table>
<tr ng-repeat="row in items">
<td ng-repeat="column in row">{{column}}</td>
</tr>
</table>
</div>
</body>
var myApp = angular.module("myApp", []);
myApp.controller("myCtrl", function($scope) {
$scope.items = [];
$scope.items[0] = ["12", "22"];
$scope.items[1] = ["34", "657"];
$scope.items[2] = ["129", null];
});
https://jsfiddle.net/bntguybm/
Note that if any of these arrays were to contain more than 2 values, then you would also see extra columns for the rows that contains that data.
An alternative way would be like this, which would guarantee only 2 columns. You would need to create an array of objects for your items object. Like this:
<body ng-app="myApp">
<div ng-controller="myCtrl">
<table>
<tr ng-repeat="row in items">
<td>{{row.column1}}</td>
<td>{{row.column2}}</td>
</tr>
</table>
</div>
</body>
var myApp = angular.module("myApp", []);
myApp.controller("myCtrl", function($scope) {
$scope.items = [];
$scope.items[0] = {};
$scope.items[0].column1 = "12";
$scope.items[0].column2 = "22";
$scope.items[1] = {};
$scope.items[1].column1 = "34";
$scope.items[1].column2 = "657";
$scope.items[2] = {};
$scope.items[2].column1 = "129";
$scope.items[2].column2 = null;
});
https://jsfiddle.net/6v1701gx/1/
I googled and figured it out. This is what I came up with using index.
<body ng-app="myApp" ng-controller="myCtrl">
<table>
<tr ng-repeat="i in items" ng-if="$index%7 == 0">
<td>{{items[$index]}}</td>
<td>{{items[$index+1]}}</td>
<td>{{items[$index+2]}}</td>
<td>{{items[$index+3]}}</td>
<td>{{items[$index+4]}}</td>
<td>{{items[$index+5]}}</td>
<td>{{items[$index+6]}}</td>
</tr>
</table>
</body>
// the main (app) module
var myApp = angular.module("myApp", []);
// add a controller
myApp.controller("myCtrl", function($scope) {
$scope.items = ['12', '22', '34', '657', '129', '11', '23', '45', '65', '9', '76', '87', '90', '33', '51'];
});
https://jsfiddle.net/nkarri/9czrqLny/2/

Angular javascript array is empty?

Angular javascript array is empty?
I have an array of checkboxes databound to an angular scoped list
I need to check a few checkboxes and send bind them to a second angular scoped array:
My HTML:
<tr ng-repeat="item in pagedItems[currentPage]"
ng-controller="DealerComMaintainCtrl">
<td align="center">
<input type="checkbox" id="{{item.IdArticle}}"
ng-value="item.IdArticle"
ng-checked="selected.indexOf(item.IdArticle) > -1"
ng-click="toggleSelect(item.IdArticle)" />
</td>
<td>
<a href="/Dealercoms/ArticleDownload?FileName={{item.FileName}}&IdArticle={{item.IdArticle}}"
class="btn btn-xs btn-total-red btn-label"><i class="ti ti-download"></i></a>
</td>
<td>{{item.DateArticle | date:'dd/MM/yyyy'}}</td>
<td>{{item.Title}}</td>
<td>{{item.Archive}}</td>
</tr>
And JS in the controller:
$scope.selected = [];
$scope.toggleSelect = function toggleSelect(code) {
var index = $scope.selected.indexOf(code)
if (index == -1) {
$scope.selected.push(code)
} else {
$scope.selected.splice(index, 1)
}
}
$scope.ArchiveDocs = function() {
var selectedoptionz = $scope.selection;
console.log(selectedoptionz);
};
I tried your code and i fixed the problem, by adding the controller on an element above the ng-repeat. The alert is only to check, what is in the selected-array. This is the code, which works fine on my system:
<html>
<head>
</head>
<body ng-app="app">
<table ng-controller="DealerComMaintainCtrl">
<tbody>
<tr ng-repeat="item in pagedItems" >
<td align="center">
<input type="checkbox" id="{{item.IdArticle}}" ng-value="item.IdArticle" ng-checked="selected.indexOf(item.IdArticle) > -1" ng-click="toggleSelect(item.IdArticle)" />
</td>
<td><i class="ti ti-download"></i></td>
<td>{{item.DateArticle | date:'dd/MM/yyyy'}}</td>
<td>{{item.Title}}</td>
<td>{{item.Archive}}</td>
</tr>
</tbody>
</table>
<!-- jQuery -->
<script src="./jquery-2.1.4/jquery.min.js"></script>
<!-- AngularJS -->
<script src="./angular-1.4.5/angular.min.js"></script>
<script>
var app = angular.module("app", []);
app.controller("DealerComMaintainCtrl", function($scope){
$scope.pagedItems = [
{IdArticle: 1, Title: "test1", Archive: "archiv1"},
{IdArticle: 2, Title: "test2", Archive: "archiv1"},
{IdArticle: 3, Title: "test3", Archive: "archiv1"},
{IdArticle: 4, Title: "test4", Archive: "archiv1"}
];
$scope.selected = [];
$scope.toggleSelect = function toggleSelect(code) {
var index = $scope.selected.indexOf(code)
if (index == -1) {
$scope.selected.push(code)
}
else {
$scope.selected.splice(index, 1)
}
alert(JSON.stringify($scope.selected));
}
});
</script>
</body>
</html>
Just try it, i hope it solve your problem...

How to delete the row in which a ng-click is located?

In the following code, when I delete a customer, I want the TR row to disappear.
What is the best way to do this? Do I need to send the row as a parameter to deleteCustomer somehow? Do I have access to the TR DOM element within AngularJS somehow?
<html ng-app="mainModule">
<head>
<script src="http://code.jquery.com/jquery-1.11.2.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js"></script>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js"></script>
<link href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body ng-controller="mainController" style="padding: 20px">
<div class="col-lg-5">
<table style="width: 500px" class="table-striped table-bordered table table-hover">
<tr ng-repeat="customer in customers">
<td style="width:50px"><button ng-click="deleteCustomer(customer)">Delete</button></td>
<td style="text-align:right">{{customer.id}}</td>
<td>{{customer.firstName}}</td>
<td>{{customer.lastName}}</td>
</tr>
</table>
</div>
<div class="col-lg-7">
<div class="panel panel-info">
<div class="panel-heading">Logger</div>
<div class="panel-body" style="padding:0">
<table class="table table-bordered" style="margin:0">
<tr ng-repeat="loggedAction in loggedActions">
<td>{{loggedAction.action}}</td>
<td>{{loggedAction.description}}</td>
</tr>
</table>
</div>
</div>
</div>
<script>
var mainModule = angular.module('mainModule', []);
function mainController($scope) {
$scope.loggedActions = [];
$scope.customers = [
{id: 1, firstName: 'Joe', lastName: 'Thompson'},
{id: 2, firstName: 'Hank', lastName: 'Howards'},
{id: 3, firstName: 'Zoe', lastName: 'Frappe'}
];
$scope.deleteCustomer = function (customer) {
$scope.$emit('customerDeleted', customer);
};
$scope.$on('customerDeleted', function (event, customer) {
$scope.loggedActions.push({action: 'delete', description: 'Deleted customer ' + customer.firstName + ' ' + customer.lastName});
});
}
</script>
</body>
</html>
EDIT:
as pointed out by #K.Toress's comment, it's better to retrieve the index of the deleted customer via indexOf() from within the function, rather than passing $index from the ng-repeat.
passing $index will give unexpected results if using a filter or sorting the array.
deleteCustomer function:
$scope.deleteCustomer = function (customer) {
var index = $scope.customers.indexOf(customer);
$scope.customers.splice(index, 1);
$scope.$emit('customerDeleted', customer);
};
updated plnkr
you can use the $index provided by ng-repeat, and array.splice from within the delete function:
html:
<button ng-click="deleteCustomer($index, customer)">Delete</button>
js:
$scope.deleteCustomer = function ($index, customer) {
$scope.customers.splice($index, 1);
$scope.$emit('customerDeleted', customer);
};
plnkr
Working example:
http://plnkr.co/edit/7MOdokoohX0mv9uSWuAF?p=preview
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.data = ['a', 'b', 'c', 'd', 'e'];
$scope.delete = function(at) {
$scope.data.splice(at, 1);
}
});
Template:
<body ng-app="plunker" ng-controller="MainCtrl">
<p>Hello {{name}}!</p>
<div ng-repeat="elem in data">
{{elem}}
<button ng-click="delete($index)">delete {{elem}}</button>
</div>
</body>

Resources