I have a table of users that is populated dynamically from a Firebase database:
<table id="users">
<thead>
...
</thead>
<tbody>
<tr ng-repeat="user in users">
<td data-user-id="{{user.$id}}">{{user.$id}}</td>
</tr>
</tbody>
</table>
function loadUsers($scope, DatabaseFactory) {
DatabaseFactory.users.on('value', function(snapshot) {
var users = snapshot.val();
for(var id in users) {
var user = users[id];
// Find cell with user ID
var element = angular.element('#users tr td[data-user-id="' + user.id + '"]'); // 1
console.log(element); // 2
}
});
};
In loadUsers(), I'm trying to find a cell which contains a particular user ID. The log statement above returns an array with length 0. Why does this happen? When I try the statements 1 and 2 in the Chrome console, it works fine.
Put your loop inside viewContentLoaded event.Its look like things are asycnhronous.
$scope.$on('$viewContentLoaded', function () {
for (var id in users) {
var user = users[id];
// Find cell with user ID
var element = angular.element('#users tr td[data-user-id="' + user.id + '"]'); // 1
console.log(element); // 2
}
});
What worked for me was turning loadUsers() into a directive. It is called after generating the DOM elements with ngRepeat.
HTML:
<tr ng-repeat="user in users" load-users>
Angular:
var app = angular
.module('app', ['firebase'])
.directive('loadUsers', loadUsers);
function loadUsers($scope, DatabaseFactory) {
return function(scope, element, attrs) {
if (scope.$last) {
...
}
}
};
Related
I an trying to determine how to remove one or more specific rows from an array of objects.
In the page, I have a text box for user to enter a user ID; click "Find" button and I do a search in AD and add a row to a table with user's display name and user ID. In addition to this, for later processing, I store in an array of objects some info on user in addition to display name and user ID.
Also, I have a checkbox column in above table that user can select and remove one or more of the added users; I can correctly rebuild the table displaying data but don't know how, at this point, remove one or more rows from the array (i.e. the deleted users).
This is what I have:
HTML:
<table class="adminList" style="width:100%">
<thead>
<tr>
<th></th>
<th>User Name</th>
<th>User ID</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<button type="button" class="btn-primary btn-sm delete-admin-row">Delete User(s)</button>
JS/jQuery:
var admins = [];
<Somehow, using ajax, get user's data from AD>
var fn = userData.FirstName;
var mi = userData.Initial;
var ln = userData.LastName;
var name = userData.DisplayName;
var email = userData.Email;
var userid = userData.UserID;
//push the object onto the array
admins.push({
"fn" : fn,
"mi" : mi,
"ln": ln,
"email": email,
"userid": userid
});
// Display in admins list
var markup = "<tr><td><input type='checkbox' name='record'></td><td>" + name + "</td><td>" + userid + "</td></tr>";
$(".adminList tbody").append(markup);
FURTHER DOW IN THE CODE:
$(document).ready(function(){
$(".delete-admin-row").click(function(){
$(".adminList tbody").find('input[name="record"]').each(function(){
if($(this).is(":checked")){
$(this).parents("tr").remove();
// HERE I NEED TO POP THE ARRAY
}
});
});
});
Not sure if removing the checkbox column and having instead an 'x' image next to each row, associate some property of user data with this 'x' image, say User ID, as 'data-xxx' and accessing it in delete function would be easier; but I still wouldn't know how to access and delete the row from array based on 'userid'.
Update
Following the suggestions made, I made the changes but noticed that IE does not understand => nor findIndex. So, I made the following changes but still get -1 as returned index.
$(".delete-admin-row").click(function(){
$(".adminList tbody").find('input[name="record"]').each(function(){
if($(this).is(":checked")){debugger
$(this).parents("tr").remove();
// Both of these return the userid
const useridToFind1 = this.dataset.userid;
const useridToFind = $(this).closest("tr").find("td:eq(2)").text();
//const index = admins.findIndex(admin => admin.userid === useridToFind);
const index = admins.indexOf(function (e) { return e.userid === useridToFind });
if (index !== -1) { // Always -1
admins.splice(index, 1);
}
}
});
});
I'd add the UserID to a data attribute of the input in a TR, so that when iterating over checked inputs, you can take that value and use .findIndex to see if a matching UserID object exists in the array. Then you can splice it:
var markup = "<tr><td><input data-userid='" + userid + "' type='checkbox' name='record'></td><td>" + name + "</td><td>" + userid + "</td></tr>";
and
$(".delete-admin-row").click(function(){
$(".adminList tbody").find('input[name="record"]:checked').each(function(){
$(this).parents("tr").remove();
const useridToFind = Number(this.dataset.userid);
const index = admins.findIndex(admin => admin.userid === useridToFind);
if (index !== -1) {
admins.splice(index, 1);
}
});
});
I'm assuming the userid is a number, thus the need for a type cast in the .find (since a dataset element will always be a string).
If you have to use an obsolete 7-year-old browser, dumb down the syntax and use a different iteration method:
"use strict";
$(".delete-admin-row").click(function() {
$(".adminList tbody")
.find('input[name="record"]:checked')
.each(function() {
$(this)
.parents("tr")
.remove();
var useridToFind = Number(this.dataset.userid);
var foundIndex = -1;
admins.forEach(function(admin, index) {
if (admin.userid === useridToFind) foundIndex = index;
});
if (index !== -1) {
admins.splice(index, 1);
}
});
});
Just use splice method, what you need to know - index of array (first element - 0)
var a = [1, 2, 3, 4, 5];
a.splice(1, 2); // delete 2 elements starting from second element
// (second, because first element in array has index - 0,
// so index 1 - it's second element)
// a will contain -> [1, 4, 5]
Get the user ID from the third <td> in the current row. Find the index of the array element with that userid, and remove it with splice().
$(document).ready(function() {
$(".delete-admin-row").click(function() {
$(".adminList tbody").find('input[name="record"]').each(function() {
if ($(this).is(":checked")) {
let userid = $(this).closest("tr").find("td:eq(2)").text();
let index = admins.findIndex(el => el.userid == userid);
if (index >= 0) {
admins.splice(index, 1);
}
$(this).parents("tr").remove();
}
});
});
});
I have a page object that looks like this:
<table border>
<th>Email</th>
<th>action</th>
<tr current-page="adminUsers.meta.page">
<td>admin#example.com</td>
<td>Delete permanently</td>
</tr>
<tr current-page="adminUsers.meta.page">
<td>matilda#snape.com</td>
<td>Delete permamently</td>
</tr>
</table>
I want to create a method that will enable me to delete a user based on his email address.
This is what I came up with, basing on How to find and click a table element by text using Protractor?:
describe('Admin panel', function() {
it('admin deletes a user', function() {
var re = new RegExp("matilda#snape.com");
var users_list = element.all(by.xpath("//tr[#current-page='adminUsers.meta.page']"));
var delete_btn = element(by.xpath("//a[contains(text(), 'Delete permamently')]"));
users_list.filter(function(user_row, index) {
return user_row.getText().then(function(text) {
return re.test(text);
});
}).then(function(users) {
users[0].delete_btn.click();
});
// some assertion, not relevant right now
});
});
First I'm trying to filter the row in which there's a user I want delete (array with all rows fitting my filter, then selecting the first row - should be one row anyway) and then click corresponding Delete button.
However, from my debugging I know that the method ignores the filtering and clicks the first Delete button available in the table and not the first from filtered elements.
What am I doing wrong?
In this particular case, I would use an XPath and its following-sibling axis:
function deleteUser(email) {
element(by.xpath("//td[. = '" + email + "']/following-sibling::td/a")).click();
}
I agree with #alexce's short & elegant answer but #anks, why don't you delete inside your filter??
describe('Admin panel', function() {
it('admin deletes a user', function() {
var re = new RegExp("matilda#snape.com");
var users_list = element.all(by.xpath("//tr[#current-page='adminUsers.meta.page']"));
var delete_btn = element(by.xpath("//a[contains(text(), 'Delete permamently')]"));
users_list.filter(function(user_row, index) {
return user_row.getText().then(function(text) {
return if(re.test(text)) { //assuming this checks the match with email id
user_row.delete_btn.click();
}
});
})
// some assertion, not relevant right now
});
});
I need to compare values of variable name by id. But I do not know ho to do it.
It must be something like:
if (this.name == self.copy().indexOf(this.id).name) {
alert('equals');
} else {
alert('not equals');
}
function MyViewModel() {
var self = this;
self.items = ko.observableArray();
self.copy = self.items;
self.items.push({ id: 1, name: 'Jhon' });
self.items.push({ id: 2, name: 'Smith' });
self.alarm = function () {
alert(this.name);
}
}
ko.applyBindings(new MyViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/2.0.0/knockout-min.js"></script>
<table>
<thead>
<tr>
<th>Passenger name</th>
</tr>
</thead>
<tbody data-bind="foreach: items">
<tr>
<td><input class="target" data-bind="value: name, event: { change: $root.alarm}" /></td>
</tr>
</tbody>
</table>
You're looking for the newer Array functions, e.g. filter. Use it like this:
self.alarm = function(data) {
var itemsWithSameName = self.copy().filter(function(item) {
return item.name() === data.name();
});
if (itemsWithSameName.length > 0) {
alert('At least one item with same name exists.');
} else {
alert('New name is unique, for now.');
}
}
However, for this to work you need to change name on your items observable (which is needed anyways for the value binding to work).
Also note that I've replaced this with data as passed to the custom event handler you bound in the view (the "current" item inside the foreach is passed as the first arg to alarm).
As a final note, beware that copy is not actually a copy, but a reference to the original array.
PS. If you want to filter by id you can do this:
self.alarm = function(data) {
var itemsWithSameId = self.copy().filter(function(item) {
return item.id === data.id;
});
// If `id` is guaranteed to be unique, you can probably get away
// with just assuming/grabbing the only item found:
var theItem = itemsWithSameId[0];
// Note that you've just essentially retrieved `data`. That is:
// data === theItem
if (!!theItem) {
alert('At least one item with same id exists.');
} else {
alert('Id of edited item cuold not be found.');
}
}
As I've put in comments, this makes less sense, because data passed to the event handler already is a reference to the item from your observableArray.
I'm really new to Angular and i'm trying to create a list of user transactions that presents the time of the action and the user's name. In my audit API I have an action ID and the User FK which associates with my User API and i'm displaying it as follows:
HTML
<table>
<thead>
<tr>
<th>
Date/Time
</th>
<th>
User
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="audit in audit.data>
<td>{{audit.audit_date_time}}</td>
<td>**{{audit.audit_user_fk}}**</td> **<--I need the name not the ID here**
</tr>
</tbody>
</table>
My Apis are as follows:
AUDIT
[
{
"audit_id": "1",
"audit_date_time": "2016-01-28 12:46:20",
"audit_user_fk": "97"
}
]
USER
[
{
"user_id": "97",
"user_full_name": "Mr.User",
}
]
Controller, which is working fine GETting the data from each API:
app.controller('auditControl', ['$scope','auditService', 'userService', function ($scope, auditService, userService) {
var auditLogs = auditService.query(function () {
$scope.audit.data = auditLogs;
});
var user = userService.query(function () {
$scope.auditUser = user;
});
}]);
So my main issue i'm having is getting the user name in the table instead of the foreign key value. I've stripped out a lot of this just so we can focus on the main problem. Getting the user name from the user API, based on the FK in the Audit API and repeated based on the items in the Audit API.
Any help greatly appreciated and apologies for the noob question!
Create a custom filter.
app.filter("lookupUser", function() {
function lookup (idNum, userList) {
var userName = "UNKNOWN";
angular.forEach(userList, function(user) {
if ( user.user_id == idNum ) {
userName = user.user_full_name;
};
});
return userName;
};
return lookup;
});
Then in your template:
<tr ng-repeat="audit in audit.data>
<td>{{audit.audit_date_time}}</td>
<td>{{audit.audit_user_fk | lookupUser : auditUser }}</td>
</tr>
You could do something like this:
Controller:
app.controller('auditControl', ['$scope','auditService', 'userService', function ($scope, auditService, userService) {
var auditLogs = auditService.query(function () {
$scope.audit.data = auditLogs;
});
var user = userService.query(function () {
$scope.auditUser = user;
});
$scope.getUserName = function (id) {
var result = $scope.users.filter(function( user ) {
return user.user_id == id;
});
if (angular.isDefined(result) && result.length > 0) {
return result[0].user_full_name;
} else {
return "--";
}
}
}]);
HTML
<table>
<thead>
<tr>
<th>
Date/Time
</th>
<th>
User
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="audit in audit.data">
<td>{{audit.audit_date_time}}</td>
<td>**{{getUserName(audit.audit_user_fk)}}**</td> **<--I need the name not the ID here**
</tr>
</tbody>
</table>
I don't know where the users array are, so I called $scope.users.
I am attempting to use a custom orderBy function. Initially, I want the data to appear in the order it was added to $scope.rows, and only after clicking on a column heading should it order by a specific property. Here's my fiddle:
http://jsfiddle.net/S8M4c/
Here's my view:
<table ng-app ng-controller="ctrl">
<tr>
<th><a ng-click="orderBy = 'id'">ID</a></th>
<th><a ng-click="orderBy = 'name'">Name</a></th>
</tr>
<tr ng-repeat="row in rows | orderBy:mySort">
<td>{{row.object.id}}</td>
<td>{{row.object.name}}</td>
</tr>
</table>
Here's my controller:
function ctrl($scope)
{
// Initially, we don't sort by anything
$scope.orderBy = "";
$scope.rows = [];
// Add some rows
for(var i = 10;i < 30;i++)
{
$scope.rows.push({settings: {foo: true}, object: {id: i, name: "Name " + i}})
};
$scope.mySort = function(row)
{
if($scope.orderBy != "")
{
return row.object[$scope.orderBy];
}
// What do I return here??
return "";
}
}
In the case that $scope.orderBy isn't set and I want to return $scope.rows in it's original order, what do I return in $scope.mySort? I cannot return row.object.id because the rows are not guaranteed to be added in order of their ID. Running my code as is on Chrome 32, the first row that appears has an ID of 20, which is the halfway row.
return $scope.rows.indexOf(row);
(Fiddle.)
You can also do this with out-of-the-box orderBy by providing a function returning that as the default predicate:
Controller:
$scope.mySort = $scope.unsorted = function(row)
{
return $scope.rows.indexOf(row);
}
View:
<div ng-app ng-controller="ctrl">
<table>
<tr>
<th><a ng-click="mySort = 'object.id'">ID</a></th>
<th><a ng-click="mySort = 'object.name'">Name</a></th>
</tr>
<tr ng-repeat="row in rows | orderBy:mySort">
<td>{{row.object.id}}</td>
<td>{{row.object.name}}</td>
</tr>
</table>
<button ng-click="mySort = unsorted;">Original Sort</button>
</div>
Fiddle here. (I've changed the numbers used in the objects so that sort by id, sort by name, and the original sort aren't all the same.)
I think you have to write your own sortby function. The original angulars orderBy is a regular filter that returns the sorted array. Your filter may look something like this:
.filter('mySort', function(){
return function(values, param){
if(param===''){
return values;
}else{
// very important! create a copy of the array - otherwise
// the $wtachCollection function will fire to often!
var arrayCopy = [];
for ( var i = 0; i < values.length; i++) { arrayCopy.push(values[i]); }
return arrayCopy.sort(function(a,b){
var v1 = a.object[param];
var v2 = b.object[param];
// you know best how to sort it!
if (v1 === v2) return 0;
return v1 < v2 ? -1 : 1;
});
}
}
})
You can use this filter in this way:
<table ng-app="myApp" ng-controller="ctrl">
<tr>
<th><a ng-click="orderBy = 'id'">ID</a></th>
<th><a ng-click="orderBy = 'name'">Name</a></th>
</tr>
<tr ng-repeat="row in rows | mySort:orderBy">
<td>{{row.object.id}}</td>
<td>{{row.object.name}}</td>
</tr>
</table>
here is your modified fiddle: http://jsfiddle.net/spRf6/ I have changed the names a little bit so you may see that the sorting works.
Create a copy of the objects array and the ordering then becomes trivial:
Controller:
$scope.objects = [];
angular.forEach($scope.rows, function(row){
$scope.objects.push(row.object);
});
View:
<tr ng-repeat="object in objects | orderBy:orderBy">
<td>{{object.id}}</td>
<td>{{object.name}}</td>
</tr>
No need for the mySort function.