I have this error when running the snippet below.
The code is actualy working, but there seem to be a problem.
angular.js:9037 Error: [$rootScope:infdig] http://errors.angularjs.org/undefined/$rootScope/infdig?p0=10&p1=%5B%5B%22f…5Ct%5C%5CtMi%5C%5Cn%20%20%20%20%5C%5Ct%5C%5Ct%5C%5Ct%5C%5Ct%5C%22%22%5D%5D
at Error (native)
at https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js:6:453
at g.$get.g.$digest (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js:99:110)
at g.$get.g.$apply (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js:101:12)
at https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js:17:415
at Object.d [as invoke] (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js:30:328)
at c (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js:17:323)
at Wb (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js:18:30)
at Oc (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js:17:99)
at HTMLDocument.<anonymous> (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js:198:494)
MINERR_ASSET:22 Uncaught Error: [$rootScope:infdig] http://errors.angularjs.org/undefined/$rootScope/infdig?p0=10&p1=%5B%5B%22f…5Ct%5C%5CtMi%5C%5Cn%20%20%20%20%5C%5Ct%5C%5Ct%5C%5Ct%5C%5Ct%5C%22%22%5D%5D
Please run snippet below for further understanding.
I get this error because of the day names im looping trough. When deleting following:
this.firstDay++;
if (this.firstDay == 6) {
this.firstDay = 0;
}
There is no error, but Only Monday is displayed
(function() {
var app = angular.module('myApp', []);
app.controller('CalenderController', function() {
this.days = dayNames;
this.date = date;
this.firstDay = 0;
this.getDays = function(num) {
return new Array(num);
};
this.getDayName = function() {
this.firstDay++;
if (this.firstDay == 6) {
this.firstDay = 0;
}
return dayNames[this.firstDay];
};
});
//Variables
var dayNames = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'];
var date = {
'2015': [{
'month': 'Januar',
'day': 31
}, {
'month': 'Februar',
'day': 28
}, {
'month': 'März',
'day': 31
}, {
'month': 'April',
'day': 30
}, {
'month': 'Mai',
'day': 31
}, {
'month': 'Juni',
'day': 30
}, {
'month': 'Juli',
'day': 31
}, {
'month': 'August',
'day': 31
}, {
'month': 'September',
'day': 30
}, {
'month': 'Oktober',
'day': 31
}, {
'month': 'November',
'day': 30
}, {
'month': 'Dezember',
'day': 31
}]
};
})();
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.min.js"></script>
<div class="container" ng-app="myApp" ng-controller="CalenderController as calender">
<table ng-repeat="date in calender.date.2015" class="table table-striped table-bordered">
<thead>
<tr class="info">
<td colspan="{{date.day}}"> {{date.month}} </td>
</tr>
</thead>
<tbody>
<tr>
<td ng-repeat="_ in calender.getDays(date.day) track by $index">
{{calender.getDayName()}}
</td>
</tr>
<tr>
<td ng-repeat="_ in calender.getDays(date.day) track by $index">
{{$index + 1 }}
</td>
</tr>
</tbody>
</table>
</div>
Change this.firstDay to var firstDay i.e. javascript variable. The issue is while it is scope variable this.firstDay, angular internally place watch on it, and whenever any scope update occurs it run it digest cycle instantly, in you case this.firstDay is update exact 4015 times, that leads to an indefinite loop of digest cycle, and i don't think you are using this scope variable anywhere inside the controller. So better make it javascript var
Controller
app.controller('CalenderController', function(){
this.days = dayNames;
this.date = date;
var firstDay = 0;
this.getDays = function(num){
return new Array(num);
};
this.getDayName = function(){
firstDay++;
if(firstDay == 6){
firstDay = 0;
}
return dayNames[this.firstDay];
};
});
Working Plunkr
Hope this could help you. Thanks.
Related
I'm using angularjs order by filter for sorting a json. As per the order of preference, lower case followed by the upper case but it is not working for single character. Can any one help in this to find out?
$scope.friends = [
{name: 'A'},
{name: 'a'},
{name: 'B'},
{name: 'b'},
{name: 'C'},
{name: 'c'}
];
<table class="friends">
<tr>
<th>Name</th>
</tr>
<tr ng-repeat="friend in friends | orderBy:'name'">
<td>{{friend.name}}</td>
</tr>
</table>
The result :
Name
====
A
a
B
b
C
c
Try this solution only for single characters:
angular.module('app', []).controller('MyController', ['$scope', function($scope) {
$scope.friends = [
{name: 'A'},
{name: 'a'},
{name: 'B'},
{name: 'b'},
{name: 'C'},
{name: 'c'}
];
}]).filter('customOrderBy', function(){
return function(input, name)
{
var result = []
for(var item of input)
{
var prop = item[name];
var toUpper = prop.toUpperCase().charCodeAt(0);
result.push({item, code: toUpper + (prop.toLowerCase() == prop ? 0 : 1)});
}
return result.sort(function(a, b){return a.code > b.code ? 1 : (a.code == b.code ? 0 : -1);}).map(function(x) { return x.item; });
}
})
<script src="//code.angularjs.org/snapshot/angular.min.js"></script>
<body ng-app="app">
<div ng-controller="MyController">
<table class="friends">
<tr>
<th>Name</th>
</tr>
<tr ng-repeat="friend in friends | customOrderBy: 'name'">
<td>{{friend.name}}</td>
</tr>
</table>
</div>
</body>
can you try this custom filter as like below?
<body ng-app="MyApp">
<div ng-controller="MyCtrl">
<table class="friends">
<tr>
<th>Name</th>
</tr>
<tr ng-repeat="friend in friends | localeOrderBy:'name'">
<td>{{friend.name}}</td>
</tr>
</table>
</div>
</body>
app.filter("localeOrderBy", [function () {
return function (array, sortPredicate, reverseOrder) {
if (!Array.isArray(array)) return array;
if (!sortPredicate) return array;
var isString = function (value) {
return (typeof value === "string");
};
var isNumber = function (value) {
return (typeof value === "number");
};
var isBoolean = function (value) {
return (typeof value === "boolean");
};
var arrayCopy = [];
angular.forEach(array, function (item) {
arrayCopy.push(item);
});
arrayCopy.sort(function (a, b) {
var valueA = a[sortPredicate];
var valueB = b[sortPredicate];
if (isString(valueA))
return !reverseOrder ? valueA.localeCompare(valueB) : valueB.localeCompare(valueA);
if (isNumber(valueA) || isBoolean(valueA))
return !reverseOrder ? valueA - valueB : valueB - valueA;
return 0;
});
return arrayCopy;
}
}]);
what I'm I doing wrong here? I'm trying to render a table using angular but the table shows empty, with {{ place.id}} , {{ place.name}} and {{ place.social}} where the data should be.
<head>
<title>Angular JS </title>
<script src="http://code.angularjs.org/1.4.8/angular.js"></script>
<script src="http://code.angularjs.org/1.4.8/angular-resource.js"></script>
<script src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.11.0.js"></script>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="css/main.css">
<script>
var app = angular.module('MyForm', ['ui.bootstrap', 'ngResource']);
app.controller('myCtrl', function ($scope, $http) {
$http.get('http://passarola.pt/api/places').success(function(data) {
$scope.predicate = 'id';
$scope.reverse = true;
$scope.currentPage = 1;
};
$scope.order = function (predicate) {
$scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false;
$scope.predicate = predicate;
};
$scope.totalItems = $scope.places.length;
$scope.numPerPage = 5;
$scope.paginate = function (value) {
var begin, end, index;
begin = ($scope.currentPage - 1) * $scope.numPerPage;
end = begin + $scope.numPerPage;
index = $scope.places.indexOf(value);
return (begin <= index && index < end);
};
});
</script>
</head>
<body ng-app="MyForm">
<div ng-controller="myCtrl">
<div class="container-fluid">
<pre>Passarola Beer</pre>
<hr />
<table class="table table-striped">
<thead>
<tr>
<th class="media_gone">
ID
</th>
<th> Name </th>
<th>Social Media </th>
</tr>
</thead>
<tbody>
<tr>
<td class="media_gone2"> <input type="text" ng-model="search.id" /></td>
<td> <input type="text" ng-model="search.name" /> </td>
</tr>
<tr ng-repeat="place in places | orderBy:predicate:reverse | filter:paginate| filter:search" ng-class-odd="'odd'">
<td>{{ place.id}}</td>
<td>{{ place.name}}</td>
<td class="gender_gone">{{ place.social}}</td>
</tr>
</tbody>
</table>
<pagination total-items="totalItems" ng-model="currentPage"
max-size="5" boundary-links="true"
items-per-page="numPerPage" class="pagination-sm">
</pagination>
</div>
</div>
</body>
</html>
Example code:
var app = angular.module('MyForm', ['ui.bootstrap', 'ngResource']);
app.controller('myCtrl', function ($scope, $timeout) {
$scope.predicate = 'id';
$scope.reverse = true;
$scope.currentPage = 1;
$scope.order = function (predicate) {
$scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false;
$scope.predicate = predicate;
};
$scope.places = [
{ id: 'Kevin', name: 25, social: 'boy' },
{ id: 'John', name: 30, social: 'girl' },
{ id: 'Laura', name: 28, social: 'girl' },
{ id: 'Joy', name: 15, social: 'girl' },
{ id: 'Mary', name: 28, social: 'girl' },
{ id: 'Peter', name: 95, social: 'boy' },
{ id: 'Bob', name: 50, social: 'boy' },
{ id: 'Erika', name: 27, social: 'girl' },
{ id: 'Patrick', name: 40, social: 'boy' },
{ id: 'Tery', name: 60, social: 'girl' }
];
$scope.totalItems = $scope.places.length;
$scope.numPerPage = 5;
$scope.paginate = function (value) {
var begin, end, index;
begin = ($scope.currentPage - 1) * $scope.numPerPage;
end = begin + $scope.numPerPage;
index = $scope.places.indexOf(value);
return (begin <= index && index < end);
};
Use this controller code.
app.controller('myCtrl', function ($scope, $http) {
$scope.places = [];
$http.get('http://passarola.pt/api/places').success(function(data) {
$scope.places = data.data;
$scope.totalItems = $scope.places.length;
$scope.predicate = 'id';
$scope.reverse = true;
$scope.currentPage = 1;
};
$scope.order = function (predicate) {
$scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false;
$scope.predicate = predicate;
};
$scope.numPerPage = 5;
$scope.paginate = function (value) {
var begin, end, index;
begin = ($scope.currentPage - 1) * $scope.numPerPage;
end = begin + $scope.numPerPage;
index = $scope.places.indexOf(value);
return (begin <= index && index < end);
};
});
I am using ControllerAs in angular with ui.router, I have an API in PHP when I call the API and set the scope variable by vm approach for templates then it works accordingly and when I want to delete some record set
and update the vm.servers variable again then template not change according to the newly updated object.
function serverController( server, $state, $rootScope, $scope)
{
var vm = this;
vm.delete = function(server_id) {
vm.loader = false;
server.delete('server/' + server_id)
.then(
function(response){
if(response.status === 200 && !response.data.status) {
alert(response.data.message);
} else if(response.status === 200 && response.data.status){
server.setRootScope().then(
function(){
vm.servers = $rootScope.servers;
$state.go($state.current, {}, {reload: true});
}
);
}
}, function(response) {
if(response.status === 401) {
$state.go('login');
}
}
);
};
if($rootScope.servers == undefined) {
server.get('server')
.then(
function (response) {
if (response.status === 200) {
vm.servers = response.data;
$rootScope.servers = {};
angular.forEach(response.data, function (val) {
if('running' === val.status) {
val['serverState'] = true;
} else {
val['serverState'] = false;
}
$rootScope.servers[val.id] = val;
});
}
},
function (response) {
if (response.status === 401) {
$state.go('login');
}
});
} else {
vm.servers = $rootScope.servers;
}
}
Template File.
<table class="table movietable" width="70%" border="1">
<tr ng-repeat="server in serverModel.servers">
<td width="85%">
<table>
<tr>
<td><b>Server Label: </b> {{server.label}}</td>
</tr>
<tr>
<td>Status: {{server.status}}</td>
</tr>
<tr>
<td>Created At: {{server.created_at}}</td>
</tr>
</table>
</td>
</tr>
</table>
I look your code. I found a problem that,
You are not updating the vm.servers with
the response data. As the values in $rootScope.servers might
be older. So with every delete function call you will have to either update the $rootScope.servers or vm.servers with new data.
I have create a small demo from your code, hope it will help you
identify the problem. In this demo I have first load the data in table
after this, on a button click deleting the record by id and updating
the vm.servers.
My Controller
.controller('Controller',['$rootScope', function($rootScope) {
var vm =this;
vm.customer = {
name: 'Naomi',
address: '1600 Amphitheatre'
};
vm.delete = function(server_id) {
vm.loader = false;
// added some value to $rootScope.servers or you can update it with response data. This is where you will need to update your logic.
$rootScope.servers = [
{ id: 1,
name: 'Naomi1',
address: '1600 Amphitheatre1'
},
{
id: 2,
name: 'Naomi2',
address: '1600 Amphitheatre2'
}
];
angular.forEach($rootScope.servers, function(value,key) {
if(value.id == server_id) {
$rootScope.servers.splice(key,1);
}
});
console.log($rootScope.servers);
//Here I have assign new $rootScope.servers.
vm.servers = $rootScope.servers;
};
var val = {};
vm.init = function() {
vm.servers = [{
id: 1,
name: 'Naomi1',
address: '1600 Amphitheatre1'
},{
id: 2,
name: 'Naomi2',
address: '1600 Amphitheatre2'
}];
$rootScope.servers = {};
val['serverState'] = true;
$rootScope.servers[val.id] = val;
}
vm.init();
}])
index.html
<div ng-controller="Controller as vm">
<table class="table movietable" width="70%" border="1">
<tr>
<td width="85%">
<table>
<tr ng-repeat="server in vm.servers">
<td><b>Server Label: </b> {{server.name}}</td>
<td>Status: {{server.address}}</td>
<td><button ng-click="vm.delete(server.id);">Delete</button></td>
</tr>
</table>
</td>
</tr>
</table>
</div>
Hope this will help you !
Cheers,
Jimmy
In my angular js project factory is not providing values to the controller as needed. I always get empty result in view. When i logged in browser using console.log() all i can see in console is :
[object Object],[object Object],[object Object]. I am stuck at this. Tried many things but nothing worked.
This is my controller code:
var controllers = {};
controllers.ProductController = function ($scope, $route, $routeParams, $location, ProductFactory) {
$scope.products = [];
var init = function () {
$scope.products = ProductFactory.getProducts();
console.log('got products in controller');
console.log($scope.products)
};
var initProductEdit = function () {
var code = $routeParams.code;
if (code = undefined) {
$scope.currentProduct = {};
}
else
{
$scope.currentProduct = ProductFactory.loadProductByCode(code);
}
};
$scope.$on('$viewContentLoaded', function () {
var templateUrl = $route.current.templateUrl;
if (templateUrl == '/Partials/ProductEdit.html') {
initProductEdit();
}
else if (templateUrl == '/Partials/ProductList.html')
{
var code = $routeParams.code;
if(code!=undefined)
{
$scope.deleteProduct(code);
}
}
});
init();
$scope.saveProduct = function () {
ProductFactory.saveProduct($scope.currentProduct);
$location.search('code', null);
$location.path('/');
};
$scope.deleteProduct = function (code) {
ProductFactory.deleteProduct(code);
$location.search('code', null);
$location.path('/');
};
};
angSPA.controller(controllers);
This is my factory code:
angSPA.factory('ProductFactory', function () {
var products = [
{ code: 1, name: 'Game of Thrones', description: 'Series' }
{ code: 2, name: 'DmC', description: 'Game' },
{ code: 3, name: 'Matrix', description: 'Movie' },
{ code: 4, name: 'Linkin Park', description: 'Music Band' }];
var factory = {};
console.log('initializing factory');
factory.getProducts = function () {
console.log('factory now providing products');
return products;
};
factory.loadProductByCode = function (code) {
var product;
for (var i = 0; i < products.length; i++) {
if (products[i].code == code) {
product = products[i];
return product;
}
}
};
factory.saveProduct = function (product) {
products.push(product);
console.log('factory saved product');
};
factory.deleteProduct = function (code) {
var product = factory.loadProductByCode(code);
if (product != null) {
products.remove(product);
console.log('factory deleted product');
}
};
console.log('returning factory');
return factory;
});
This is my view:
<div class="container">
<h2 class="page-title">Product Listing</h2>
<div class="searchbar">
<ul class="entity-tabular-fields">
<li>
<label>Search: </label>
<span class="field-control">
<input type="text" data-ng-model="filter.productName" />
</span>
<label></label>
</li>
</ul>
</div>
<h2>Add New Product</h2>
<table class="items-listing">
<thead>
<tr>
<th>Code</th>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td data-ng-repeat="product in products|filter:filter.productName"></td>
<td>{{product.code}}</td>
<td>{{product.name}}</td>
<td>{{product.description}}</td>
<td>Delete</td>
</tr>
</tbody>
</table>
</div>
My routing function:
angSPA.config(function ($routeProvider) {
$routeProvider
.when(
'/',
{
controller: 'ProductController',
templateUrl: 'Partials/ProductList.html'
})
.when(
'/ProductEdit',
{
controller: 'ProductController',
templateUrl: 'Partials/ProductEdit.html'
})
.otherwise({
redirectTo: '/'
});
console.log('routing done');
});
Change your htmt given
var angSPA = angular.module('angSPA', []);
angSPA.controller("ProductController", function($scope, ProductFactory) {
$scope.products = [];
var init = function() {
$scope.products = ProductFactory.getProducts();
console.log('got products in controller');
console.log($scope.products + "")
};
init();
});
angSPA.factory('ProductFactory', function() {
var products = [
{code: 1, name: 'Game of Thrones', description: 'Series'},
{code: 2, name: 'DmC', description: 'Game'},
{code: 3, name: 'Matrix', description: 'Movie'},
{code: 4, name: 'Linkin Park', description: 'Music Band'}];
var factory = {};
console.log('initializing factory');
factory.getProducts = function() {
console.log('factory now providing products');
return products;
};
factory.loadProductByCode = function(code) {
var product;
for (var i = 0; i < products.length; i++) {
if (products[i].code == code) {
product = products[i];
return product;
}
}
};
factory.saveProduct = function(product) {
products.push(product);
console.log('factory saved product');
};
factory.deleteProduct = function(code) {
var product = factory.loadProductByCode(code);
if (product != null) {
products.remove(product);
console.log('factory deleted product');
}
};
console.log('returning factory');
return factory;
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js"></script>
<body ng-app="angSPA" ng-controller="ProductController">
<div class="container">
<h2 class="page-title">Product Listing</h2>
<div class="searchbar">
<ul class="entity-tabular-fields">
<li>
<label>Search: </label>
<span class="field-control">
<input type="text" data-ng-model="filter.productName" />
</span>
<label></label>
</li>
</ul>
</div>
<h2>Add New Product</h2>
<table class="items-listing">
<thead>
<tr>
<th>Code</th>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="prod in products|filter:filter.productName">
<td ></td>
<td>{{prod.code}}</td>
<td>{{prod.name}}</td>
<td>{{prod.description}}</td>
<td>Delete</td>
</tr>
</tbody>
</table>
</div>
Your ng-repeat directive should be on the tr element and not the td.
<tr data-ng-repeat="product in products|filter:filter.productName">
Not the cause of your problem, but to log in a service or controller, you can use the $log service and stringify to serialize your objects.
$log.debug(JSON.stringify($scope.products));
Looking at your code, you do $scope.products = [] right at the beginning. This will make angular watch the empty array.
In your init function you assign the products array to $scope.products. But as angular is still watching the initial array it will not be aware of the change.
The solution is to delete the initial assignment $scope.products = [] and make sure to alter the original array but never set it to a new array.
BTW: you could do console.log(JSON.stringify($scope.products)) to get better log information.
I have a grid element that generates the following HTML:
<div id="grid">
<table class="grid test" ng-controller="gridController" width="900">
<thead>
<tr><td class="th-break" colspan="8"> </td></tr>
<tr>
<th>Id</th>
<th>Title</th>
<th>Potential Customer</th>
<th>Est. Close Date</th>
<th>Est. Revenue</th>
<th>Probability</th>
<th>Rating</th>
<th></th>
</tr>
<tr><td class="th-break" colspan="8"> </td></tr>
</thead>
<tbody ui-sortable="sortableOptions" ng-model="opps">
<tr ng-repeat="obj in opps|orderBy:orderByField:reverseSort" style="cursor:move;">
<td>{{obj.id}}</td>
<td>{{obj.title}}</td>
<td>{{obj.customer}}</td>
<td>{{obj.closeDate}}</td>
<td>{{obj.revenue}}</td>
<td>{{obj.probability}}</td>
<td>{{obj.rating}}</td>
<td><i class="fa fa-times" ng-click="delete(obj)"></i></td>
</tr>
</tbody>
</table>
</div>
I created angular code that bound data to the grid and it worked perfectly. However, it was all manual binding and I'm tidying up my code. I want to create a jQuery extension so that I can pass in a ton of options and bind the grid using:
$('div#grid').bindGrid();
Here's my jQuery:
(function ($) {
'use strict';
$.fn.bindGrid = function (options) {
var settings = $.extend({
}, options);
var grid = this;
var gridMod = angular.module('grid', ['ui.sortable']);
gridMod.controller('gridController', function ($scope) {
$scope.orderBy = 'title';
$scope.reverseSort = false;
var list = [
{ 'id': 1, 'title': 'Interested in Product Designer', 'customer': '', 'closeDate': '4/2/2013', 'revenue': '$349,383.00', 'probability': '70', 'rating': 'Hot' },
{ 'id': 2, 'title': 'Interested in Product Designer', 'customer': 'Bold Sales Accessories', 'closeDate': '6/11/2013', 'revenue': '$234,382.00', 'probability': '20', 'rating': 'Cold' },
{ 'id': 3, 'title': 'Interested in Product Designer', 'customer': 'Coho Winery', 'closeDate': '6/18/2013', 'revenue': '$182,796.00', 'probability': '50', 'rating': 'Warm' },
{ 'id': 4, 'title': 'Interested in Plotters', 'customer': 'Daring Farm', 'closeDate': '7/28/2013', 'revenue': '$685,780.00', 'probability': '50', 'rating': 'Warm' }
];
$scope.opps = list;
$scope.delete = function (item) {
var index = $scope.opps.indexOf(item);
$scope.opps.splice(index, 1);
}
$scope.sortableOptions = {
stop: function (e, ui) {
var newOrder = list.map(function (i) {
return i.id;
}).join(', ');
console.log(newOrder);
}
}
});
angular.element(document).ready(function () {
angular.bootstrap(grid, ['grid']);
});
};
}(jQuery));
When I run this, I get "'gridController' not a function, got undefined." I'm sure it has something to do with scope, but not sure what's going on. Any suggestions?
And I don't want to use a directive as I need this to be highly configurable via options passed in through jQuery.
Thanks.
Nevermind, I had a typo. grrhhh, too many late nights. I've edited the code above.