Our team is developing in ServiceNow and have a requirement to add rows from "Sending" table to "Receiving" table and delete rows from the "Receiving" table and return it back to the "Sending":
We've nailed down the adding portion of this exercise, but am having trouble getting the returning part to work correctly. Sometimes it will work completely fine. Other times, when we delete, it would uncheck the correct entry from the "Sending" table, but the entry would remain on the "Receiving" table.
On our "Sending" table, it is receiving the rootScope like this:
<tbody>
<tr ng-repeat="item in data.list track by item.sys_id">
<td ng-if="options.show_checkboxes">
<input type="checkbox"
ng-checked="item.isRowSelected"
ng-click="toggleSelection(item);" >
</td>
<td role="cell" class="pointer sp-list-cell"
ng-class="{selected: item.selected}"
ng-click="go(item.targetTable, item)"
ng-repeat="field in ::data.fields_array" data-field="{{::field}}"
data-th="{{::data.column_labels[field]}}">
<span ng-if="$first"
aria-label="${Open record}: {{::item[field].display_value}}"
role="link" tabindex="0">
{{::item[field].display_value | limitTo : item[field].limit}}
{{::item[field].display_value.length > item[field].limit ? '...' : ''}}
</span>
<span ng-if="!$first">
{{::item[field].display_value | limitTo : item[field].limit}}
{{::item[field].display_value.length > item[field].limit ? '...' : ''}}
</span>
</td>
</tr>
</tbody>
$scope.selectedItems = [];
$scope.toggleSelection = function(item){
item.isRowSelected = !item.isRowSelected;
if(item.isRowSelected==false){
$scope.allSelected=false;
if($scope.selectedItems.indexOf(item.sys_id)!==-1){
var add = $scope.selectedItems.indexOf(item.sys_id);
$scope.selectedItems.splice(add,1);
}
} else {
if($scope.selectedItems.indexOf(item.sys_id)==-1){
$scope.selectedItems.push(
item
);
$rootScope.$broadcast('moveItem', $scope.selectedItems);
}
}
}
$rootScope.$on('deleteItem', function(event, data) {
$scope.selectedItems = data;
});
In our "Receiving" table widget, our code looks like this:
<tbody>
<tr ng-repeat="item in data.list2 track by item.sys_id">
<td role="cell" class="pointer sp-list-cell"
ng-class="{selected: item.selected}"
ng-click="go(item.targetTable, item)"
ng-repeat="field in ::data.fields_array"
data-field="{{::field}}" data-th="{{::data.column_labels[field]}}">
<span ng-if="$first"
aria-label="${Open record}: {{::item[field].display_value}}"
role="link" tabindex="0">
{{::item[field].display_value | limitTo : item[field].limit}}
{{::item[field].display_value.length > item[field].limit ? '...' : ''}}
</span>
<span ng-if="!$first">
{{::item[field].display_value | limitTo : item[field].limit}}
{{::item[field].display_value.length > item[field].limit ? '...' : ''}}
</span>
</td>
<td>
<a href="javascript:void(0)" ng-click="deleteSelection(item);">
Remove
</a>
</td>
</tr>
</tbody>
$scope.deleteSelection = function(item){
item.isRowSelected =false;
var minus = $scope.data.list2.indexOf(item.sys_id);
$scope.data.list2.splice(minus, 1);
$rootScope.$broadcast('deleteItem', $scope.data.list2);
}
$rootScope.$on('moveItem', function(event, data) {
$scope.data.list2 = data;
$scope.data.row_count = data.length;
});
Avoid $rootScope.$on — it risks memory leaks
In the course of its operation, AngularJS adds and removes DOM with their attached directives and controllers. The listener functions added by $rootScope.$on are not automatically removed when a directive or controller is removed. This can result in memory leaks and undesired behavior.
To avoid memory leaks, add event listeners to the $scope of the controller, not $rootScope:
̶$̶r̶o̶o̶t̶S̶c̶o̶p̶e̶.̶$̶o̶n̶(̶'̶d̶e̶l̶e̶t̶e̶I̶t̶e̶m̶'̶,̶ ̶f̶u̶n̶c̶t̶i̶o̶n̶(̶e̶v̶e̶n̶t̶,̶ ̶d̶a̶t̶a̶)̶ ̶{̶
$scope.$on('deleteItem', function(event, data) {
$scope.selectedItems = data;
});
To avoid injecting $rootScope, use $scope.$root:
̶$̶r̶o̶o̶t̶S̶c̶o̶p̶e̶.̶$̶b̶r̶o̶a̶d̶c̶a̶s̶t̶(̶'̶m̶o̶v̶e̶I̶t̶e̶m̶'̶,̶ ̶$̶s̶c̶o̶p̶e̶.̶s̶e̶l̶e̶c̶t̶e̶d̶I̶t̶e̶m̶s̶)̶;̶
$scope.$root.$broadcast('moveItem', $scope.selectedItems);
I've created a simple app in AngularJS.
Please have a look: https://stackblitz.com/edit/angularjs-mcqdzw?embed=1&file=home/home.html&view=preview
Hope this helps.
Related
I have AngularJS table with CRUD operations and sortable header. When I add the new row to table the lines are jumping and the table is getting sorted while in edit mode.
I want to stop sorting while in the edit mode and should sort only after saving the new row. I searched online for fixing this issue but nothing helped yet.
Here is the HTML file:
<thead>
<tr>
<td><input type="text" ng-model="main.search.Data1" id="myInput" placeholder="search for Data1..."/></td>
<td><input type="text" ng-model="main.search.Data2" id="myInput" placeholder="search for Data2..."/></td>
</tr>
<tr class="table_header" style="background-color: blue;">
<th><a href="#" ng-click="orderByField='data1'; reverseSort = !reverseSort"> Data1 <span ng-show ="!reverseSort">^</span><span ng-show="reverseSort">v</span></th>
<th><a href="#" ng-click="orderByField='data2'; reverseSort = !reverseSort"> Data2</th>
</tr>
</thead>
<tbody>
<tr ng-repeat=" code in main.Table | orderBy: Predicate | orderByField:reverseSort | filter:main.search"></tr>
<script type="text/ng-template" id="dispay">
<td>{{code.data1}}</td>
<td>{{code.data2}}</td>
</script>
<script type="text/ng-template" id="edit">
<td><input type="text" ng-model="code.data1" class"form-control input-sm"/></td>
<td><input type="text" ng-model="code.data2" class"form-control input-sm"/></td>
</script>
</tbody>
Here is the JS code:
//add new row
$scope.addNew = function(data) {
$scope.Table.unshift({
data1: "",
data2: ""
});
console.log($scope.table);
};
//Edit the row
$scope.edit = function(data){
$scope.selected = angular.copy(data);
$scope.backuplist = angular.copy($scope.table);
};
The behaviour is correct because on addNew you add 1 empty record in apply cycle which then will reorder again the list.
You have 2 ways to go :
1) When Adding new Item, create sperate object and upon submit add it in the list:
$scope.addNew = function(data) {
$scope.newItem ={
data1: "",
data2: ""
};
};
$scope.save = function() {
$scope.table.push($scope.newItem);
};
Like this there is speration of concerns and not to mingle the main list.
Or if you still want to add it in main list directly which i dont recommend you can implement your custom filter which keeps always the empty data at the top - In your predicate Method.
<tr class="table_header" style="background-color: blue;">
<th><a href="#" ng-click="orderByField='data1'; reverseSort = !reverseSort" ng-disabled="setTrueFalse"> Data1 <span ng-show ="!reverseSort">^</span><span ng-show="reverseSort">v</span></th>
<th><a href="#" ng-click="orderByField='data2'; reverseSort = !reverseSort" ng-disabled="setTrueFalse"> Data2</th>
</tr>
$scope.setTrueFalse = true/false
MAke use of ng-disabled.. Set it to true when you do not want to sort the values and vice versa
I am displaying some data in a web app that I am getting from a SQL database using a http service. I want to be able to modify the information of that data on the same table where the data is shown. I am using angularjs and I am using the directives ng-show and ng-hide on the table. Here I show the part of the code I am talking about.
<table>
<tr>
<td><font size = 4>Servicio: </font> </td>
<td> </td>
<td>
<select class="col-sm-12" name="repeatSelect" id="repeatSelect" ng-model="currentItem.ServicioId">
<option ng-repeat="option in items.Servicio" value="{{option.Id}}"> {{option.Nombre}}</option>
</select>
</td>
</tr>
</table>
<h4>Cliente: {{getNameC(currentItem.ServicioId)}}</h4>
<br />
<button class="btn btn-primary" ng-click="editOrCreate()">Nuevo</button>
<button class="btn btn-primary" ng-click="list()">Actualizar</button>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Cantidad</th>
<th>Tipo Concepto</th>
<th>Descripción</th>
<th>Precio Unitario</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in itemsSC">
<td>
<div ng-hide="editingData[item.Id]">{{item.Cantidad}}</div>
<div ng-show="editingData[item.Id]"><input ng-model="currentItem.Cantidad" /></div>
</td>
<td>
<div ng-hide="editingData[item.Id]">{{getName(item.ServicioConceptoTipoId)}}</div>
<div ng-show="editingData[item.Id]">
<select name="repeatSelect" id="repeatSelect" ng-model="currentItem.ServicioConceptoTipoId">
<option ng-repeat="option in items.ServicioConceptoTipo" value="{{option.Id}}"> {{option.Nombre}}</option>
</select>
</div>
</td>
<td>
<div ng-hide="editingData[item.Id]">{{item.Descripcion}}</div>
<div ng-show="editingData[item.Id]"><input type="text" ng-model="currentItem.Descripcion" /></div>
</td>
<td>
<div ng-hide="editingData[item.Id]">{{item.PrecioUnitario}}</div>
<div ng-show="editingData[item.Id]"><input ng-model="currentItem.PrecioUnitario" /></div>
</td>
<td>
<button class="btn btn-xs btn-primary" ng-hide="editingData[item.Id]" ng-click="editOrCreate(item)">Modificar</button>
<button class="btn btn-xs btn-primary" ng-show="editingData[item.Id]" ng-click="saveEdit(currentItem,0)">Actualizar</button>
<button class="btn btn-xs btn-primary" ng-hide="viewField" ng-click="delete(item)">Eliminar</button>
</td>
</tr>
</tbody>
<tfoot id="total">
<tr>
<td colspan="3" class="text-right">Total:</td>
<td class="text-right">
{{total(currentItem.ServicioId) | currency}}
</td>
</tr>
</tfoot>
</table>
On my controller I am using object arrays to get the information from the databases and I am using a mirror array to keep the boolean values of the data shown on the table, so depending on the boolean value the tables shows the data or get an input for the user to modify the data.
I get the mirror array once I know the service and client related to the data I want to show and which is possible to be modified.
However, when I click on the button to modify the status of the ng-hide or ng-show object named editingData, the controllers modifies it, but the $digest run all the other functions related to the bindings of the view and returns the bool object array to its inital values.
I haven´t been able to find away to go around this normal working way of the $digest and $watches so my $editingData object is not modified to its initial values. Here I show the code of the controller related to this.
angular.module("App")
.controller("ConceptoCtrl", function ($scope, $http, $resource, serviciosConceptoFactory, serviciosConceptoTipoFactory, serviciosFactory, clientesFactory) {
$scope.list = function () {
$scope.items = serviciosConceptoFactory.query();
$scope.itemsT = serviciosConceptoTipoFactory.query();
$scope.items.ServicioConceptoTipo = $scope.itemsT;
$scope.itemsS = serviciosFactory.query();
$scope.items.Servicio = $scope.itemsS;
$scope.itemsC = clientesFactory.query();
$scope.itemsS.Cliente = $scope.itemsC;
}
//some other functions
$scope.editingData = {};
$scope.getitemsSC = function (item) {
$scope.itemsSC = [];
for (var j = 0; j < $scope.items.length; j++) {
if ($scope.items[j].ServicioId == item) {
newitem = $scope.items[j];
$scope.itemsSC.push(newitem);
}
}
for (var i = 0; i < $scope.itemsSC.length; i++) {
$scope.editingData[$scope.itemsSC[i].Id] = false;
}
}
$scope.editOrCreate = function (item) {
$scope.currentItem = item;
$scope.editingData[item.Id] = true;
}
$scope.list();
});
The {{getNameC(currentItem.ServicioId}} function shown on the html file is the one that calls the $scope.getItemsSC(item) function once it knows the services and client related to the data that will be shown and it is this function the one that initializes the boolean values for the $scope.editingData mirror array depending on the Id of the item.
The $scope.editOrCreate(item) function is the one that changes the boolean object of the specified item so that the view shows the input element for the user instead of the data. However, $digest re-runs the $scope.getItemsSC(item) function because the $watches were modified, and that´s what I want to avoid, because in this case the input elements are never shown.
I thank in advance any help provided
Instead of using the HTML to call functions when data is a available, use the $promise property attached to the resources.
$scope.list = function () {
$scope.items = serviciosConceptoFactory.query();
$scope.itemsT = serviciosConceptoTipoFactory.query();
$scope.items.ServicioConceptoTipo = $scope.itemsT;
$scope.itemsS = serviciosFactory.query();
$scope.items.Servicio = $scope.itemsS;
$scope.itemsC = clientesFactory.query();
$scope.itemsS.Cliente = $scope.itemsC;
var promiseArray = [$scope.items.$promise,
$scope.itemsT.$promise,
$scope.itemsS.$promise,
$scope.itemsC.$promise
];
$q.all(promiseArray).then(function(resultsArray) {
var items = resultsArray[0];
var itemsT = resultsArray[1];
var itemsS = resultsArray[2];
var itemsC = resultsAttay[3];
//create mirror array here
//create editingData array here
});
};
By using the .then method of the promises, the processing of the data can be delayed until it arrives from the server. This way there is no need for the HTML to call functions.
I am currently working on a project in which, I am fetching the data from server and adding(pushing) in the array for further process. And its working great.
I face problem when I add an external data to the array with the data coming from server.
var quantity= ItemsValue[1];
$scope.product = [];
$http.get(___).success(function(data) {
$scope.greeting = data ;
$scope.product.push($scope.greeting);
}
I want to push "quantity" with the "$scope.greeting". I already tried different thing such as concatenation but failed.
I want data of array to be like this. For Example
$scope.product=[{ "greeting.name": "abc", "greeting.price": "50",
"quantity":"1" }]
Name and Price came from server and quantity in added as an extra data to Array.
<tbody >
<tr ng-repeat="greeting in product" ><!-- -->
<td class="cart_product">
<img src={{greeting.Image}} alt="book image" ></td>
<td class="cart_description">
<h4>{{greeting.Name}}</h4></td>
<td class="cart_price">
<p>Rs. {{greeting.Cost}}</p>
</td>
<td class="cart_quantity">
<div class="cart_quantity_button">
<a class="cart_quantity_up" href=""> + </a>
<input class="cart_quantity_input" type="text" name="quantity" value="Itemsquantity" autocomplete="off" size="2">
<a class="cart_quantity_down" href=""> - </a>
</div>
</td>
<td class="cart_total">
<p class="cart_total_price">Rs. {{greeting.Cost}}</p>
</td>
<td class="cart_delete">
<a class="cart_quantity_delete" ng-click="removeItem($index)"><i class="fa fa-times"></i></a>
</td>
</tr>
</tbody>
This is the code of client end.
Any way to push these things together...
You can just add it into the object by creating a new property
var quantity = ItemsValue[1];
$scope.product = [];
$http.get(___).success(function(data) {
$scope.greeting = data;
$scope.greeting.quantity = quantity;
$scope.product.push($scope.greeting);
}
I have an ng-repeat that isn't updating upon changing the data in the array that it is using. I've researched for quite a while but nothing seems to be working. Initially, when the page loads, the ng-repeat displays the first page of a dataset, upon getting new data (the next page) and setting that array with this data, the ng-repeat isn't noticing the change and never populates with the updated array. It would be greatly appreciated if someone could steer me in the right direction on this.
gatekeeper.controller('businessController', ['$scope', 'siteService', function($scope, siteService) {
$scope.page = 1;
$scope.resultsPerPage = 50;
$scope.maxPaginationSite = 10;
$scope.pageCount = 0;
$scope.resultCount = 0;
$scope.getBusinessSites = [];
function getBusinessSites()
{
siteService.getBusinessSites($scope.page, $scope.resultsPerPage).then(function(response) {
$scope.getBusinessSites = response.data;
console.log($scope.getBusinessSites);
$scope.resultCount = response.data[0].QueryCount;
$scope.page = response.data[0].Page;
$scope.pageCount = Math.ceil($scope.resultCount / 50);
});
}
getBusinessSites();
$scope.pageChanged = function () {
$scope.page = this.page;
getBusinessSites($scope.page, $scope.resultsPerPage);
};
}]);
<tbody ng-controller="businessController">
<tr ng-repeat="site in getBusinessSites">
<td>{{ site.SiteName }}</td>
<td class="tableButton">
<button ng-controller="ModalCtrl" type="button" class="btn btn-default" ng-click="open('lg')">
{{ site.ClientName }}
</button>
<br />
<b>Facilities:</b>
No Data Yet
</td>
<td>{{ site.Subdomain }}</td>
<td>
<a href={{ site.URL}}> {{ site.URL}} </a>
<br />
<b>Go-live Date: </b> No Data Yet
</td>
<td>No Data Yet</td>
<td>{{site.ChannelPartner}}</td>
<td>No Data Yet</td>
<td>No Data Yet</td>
<td>No Data Yet</td>
<td>No Data Yet</td>
</tr>
<div >
<uib-pagination class="pull-right" boundary-link-numbers="true" max-size="maxPaginationSite" boundary-links="true" total-items="resultCount" ng-model="page" ng-change="pageChanged()"></uib-pagination>
</div>
</tbody>
The problem is that ng-repeat has referenced on this initial empty array [], when you change $scope.getBusinessSites, you change this variable's reference, but ng-repeat still reference on that empty array in memory.
So, solution is write data directly to array your ng-repeat reference. You can do it with angular.extend function:
Change this line:
$scope.getBusinessSites = response.data;
On this one:
angular.extend($scope.getBusinessSites, response.data);
UPD:
Also, if you use loading data not once, you'll need to clear previously loaded data in that array:
// empties an array
$scope.getBusinessSites.length = 0;
Try wrapping the tbody inside of a div and but the controller in the div:
<div ng-controller="BusinessController">
<tbody>
<tr ng-repeat="site in getBusinessSites">
.
.
.
</tbody>
</div>
and I suggest naming the $scope.getBusinessSites to $scope.businessSites for avoiding confusion :)
The ng-repeat create its own scope. Try adding $parent to reference your variable on your current controller scope
I've simulated my problem here.
Looking into this html, you can see that I am doing two ng-repeats with the same array as input, but different filters to each one:
<div ng-app='Lists'>
<div ng-controller='listsController'>
<table>
<tbody>
<tr ng-repeat='item in listValues | filter : xxx track by $index' ng-click="update($index)">
<td>{{item.ref}}</td>
<td>{{item.others}}</td>
</tr>
</tbody>
</table><hr/>
<table>
<tbody>
<tr ng-repeat='item in listValues | filter : yyy track by $index' ng-click="update($index)">
<td>{{item.ref}}</td>
<td>{{item.others}}</td>
</tr>
</tbody>
</table><hr/>
<div>{{updateIndex}}</div>
</div>
</div>
And my js code:
var appModule = angular.module('Lists', []);
appModule.controller('listsController', function($scope) {
$scope.listValues = [
{'ref' : '1', 'others' : 'abc..'},
{'ref' : '2', 'others' : 'def..'},
{'ref' : '1', 'others' : 'ghi..'},
{'ref' : '2', 'others' : 'jkl..'}
];
$scope.xxx = function(a){
return a.ref == 1;
};
$scope.yyy = function(a){
return a.ref == 2;
};
$scope.update = function(i) {
$scope.updateIndex = i;
};
$scope.updateIndex = "none";
});
The problem I'm stuck is that the update(index) function needs to change the object in the correct index of the listValues array. But as you can see clicking in the object of the second table gives me the $index of the first table.
How to work around this situation? Thanks in advance.
Using the $index is doomed to fail, even if you iterate once. $index is the index of the current item in the filtered array. And that index is different from the index of the same element in the original, non-filtered array.
If you want to modify an item on click, don't pass its index as argument. Pass the item itself:
ng-click="update(item)"
Instead of filters use ng-if which allows you to track items by index.Index will give exact click even list has duplicate items
<body>
<div ng-app='Lists'>
<div ng-controller='listsController'>
<table>
<tbody>
<tr ng-repeat="item in listValues track by $index" ng-click="update($index)" ng-if="xxx(item)=='1'">
<td>{{item.ref}}</td>
<td>{{item.others}}</td>
</tr>
</tbody>
</table><hr/>
<table>
<tbody>
<tr ng-repeat='item in listValues track by $index' ng-click="update($index)" ng-if="item.ref=='2'">
<td>{{item.ref}}</td>
<td>{{item.others}}</td>
</tr>
</tbody>
</table><hr/>
<div>{{updateIndex}}</div>
</div>
</div>
</body>