Angular UI-Bootstrap typeahead in table - html

Simple question really, but how can I setup the typeahead to work in a table that works off of a different table than my typeahead?
For Example, I have a foreign key in a table and I want to let the users select this key based on the respective NAME value in the foreign key's primary table.
Code Example:
<table class="table table-bordered table-hover table-striped rwd-table" id="page-wrap">
<thead>
<tr>
<th>Primary ID</th>
<th>Foreign Key (As Name)</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="p in PrimaryTable" id="display">
<td data-th="ID">{{p.PrimaryID}}</td>
<td>
<input id="ForeignKeyName"
type="text"
ng-model="p.ForeignKeyID"
uib-typeahead="f.ForeignKeyID as f.ForeignKeyName for f in ForeignKeyTable | filter:$viewValue"
class="form-control">
</td>
</tr>
</tbody>
With this example, I would want the users to see the "Foreign Key (As Name)" As the Name value instead of the ID value. The trick is that I also want the underlying value to be the ID and have it mapped to match the original value, as notified by the ng-model.
UPDATE: Another question I had that was in line with the previous one is how do I setup my ng-model to show the ForeignKeyTable.ForeignKeyName in place of the PrimaryTable.ForeignKeyID?
This would be similar ( I imagine) to how the uib-typeahead matches the ForeignKeyTable.ForeignKeyID and ForeignKeyTable.ForeignKeyName but with the two seperate tables?
What I would desire is to be able to put ng-model: PrimaryTable.ForeignKeyID as ForeignKeyTable.ForeignKeyName

First thing would be updating PrimaryKeyTable rows every time user selects value in typeahead. You'll have to catch selected item and manually assign its ForeignKeyId value to the row of PrimaryTable.
The way to do it is to add typeahead-on-select directive to your typeahead and bind it to the function that assigns the values for you.
It would look like this:
HTML
<tr ng-repeat="p in PrimaryTable" id="display">
<td data-th="ID">{{p.PrimaryID}}</td>
<td>
<input id="ForeignKeyName"
type="text"
ng-model="selectedItem" <!-- could be anything really for we will not use it here -->
uib-typeahead="f.ForeignKeyID as f.ForeignKeyName for f in ForeignKeyTable | filter:$viewValue"
typeahead-on-select="onSelect($item, $model, $label, p)"
class="form-control">
</td>
</tr>
Inside your controller
$scope.onSelect = function($item, $model, $label, primaryKeyTableRow) {
primaryKeyTableRow.ForeignKeyId = $item.ForeignKeyId;
}
Next step is to display name property value of ForeignKeyTable row that corresponds to ForeignKeyId from PrimaryKeyTable in each row. Since we have to filter ForeignKeyTable to find suitable item it would be a good idea to put that logic inside the controller. For there are multiple rows in which we want to display corresponding name, we'll have to filter the ForeignKeyTable for each row separately. This is where ng-controller for ng-repeat comes in handy. What we'll do is bind new controller for each table row generated by ng-repeat and put some logic inside that controller. In HTML it would look like this:
<tr ng-repeat="p in primaryKeyTable" ng-controller="itemController">
<!-- ... -->
</tr>
And we'll have to define new controller in JS file:
app.controller('itemController', function() {
// ...
});
Now we can have separate logic for each row in the table. So that's a place to filter ForeignKeyTable to find corresponding item and display it's name.
Since whole code is kind of big I've put it in the plunker:
http://plnkr.co/edit/xccgnpxoPHg6vhXWPwZn?p=preview
See what you can do with it.

Related

Remove last array element and return array and also return removed element separately

I have an array of objects. I have two tables on the screen. I want n-1 elements in top table and the last element to be displayed in bottom column.
HTML top table:
<th>name</th>
<th>age</th>
<tr *ngFor="let key of array">
<td> {{key.name}}</td>
<td> {{key.age}}</td>
HTML bottom table:
<th></th>
<th>age</th>
<tr *ngFor="let key of array">
<td></td>
<td> {{key.age}}</td>
suppose if there are 5 elements, 4 should be displayed at top and last at bottom. In api response I am getting a single array of objects and last object should always be in the bottom table. Also, last table does not have a name column. It returns a string "Final Name". Can this be used to detect that if name === Final Name then remove it from array and show it else where?
What should be the for loop condition for it? Should I slice last element from array and store it in temp array in .ts file?
You could create a variable to store the last array object in your ts file then pop the array to this variable. E.g:
component.ts:
export class YourComponent {
lastItem: YourType;
...
lastItem = myArray.pop();
This will remove the last item and assign it to lastItem variable which can be used to populate your bottom table.
HTML bottom table:
<th></th>
<th>age</th>
<tr>
<td></td>
<td> {{lastItem.age}}</td>
Ok first of all use the *ngFor like this
*ngFor="let item of items; let i = index"
And you can apply condition like
*ngIf="i!=array.length"
And by using index you can print those last array elements separately

Vue v-bind - access array object from another array loop

I'm working on a project where I created a table component which is used on multiple pages with different configuration. Every table has it's configuration in a separate file where I store keys, titles and size classes for each column.
Data for each table body come from REST calls and they are loaded dynamically, paginated and then displayed.
<template slot="thead">
<tr>
<th v-for="item in headers" :key="item.id" :class="item.classes">{{item.title}}</th>
</tr>
</template>
<template slot="tbody">
<tr v-for="skill in paginatedSkills"
:key="skill.id"
v-on:click="selectRow(skill)"
v-bind:class="{selectedRow: selectedSkill === skill}"
>
<td class="cell-l">{{skill.name}}</td>
<td class="cell-m">{{skill.owner}}</td>
<td class="cell-s">{{skill.complexity}}</td>
<td class="cell-full">{{skill.description}}</td>
</tr>
</template>
What I want to do is to avoid writing size class for every single cell in the tbody loop. I was hoping to get index of looped object and use it to retrieve the class from config object which is used to populate cells in thead.
<tr v-for="(skill, index) in paginatedSkills" ...>
<td class="{headers[index].classes}">{{skill.name}}</td>
Using index on headers will return the correct item but as a string so obviously classes are not accessible. Any idea how to tweak it?
This options are no go, failing on compile
<td :class="{JSON.parse(headers[index]).classes}">{{skill.name}}</td>
<td :class="{JSON.parse(headers)[index].classes}">{{skill.name}}</td>
<td :class="{{JSON.parse(headers[index]).classes}}">{{skill.name}}</td>
To set class from a variable/property you have two options:
<td v-bind:class="headers[index].classes">{{skill.name}}</td>
<td :class="headers[index].classes">{{skill.name}}</td>
No need for curly braces here since v-bind already expects JS expression.
Update:
What you can also do, is to associate keys of skill object (name, owner, complexity, description) with their header, so each item of headers array will also have for example key property used to access value from skill object:
headers: [
{ id: 1, classes: 'cell-l', title: 'title', key: 'name' },
{ id: 2, classes: 'cell-s', title: 'title', key: 'owner' },
...
]
Thus, your code can be simplified the following way:
<tr v-for="skill in paginatedSkills" ...>
<td v-for="header in headers" v-bind:class="header.classes">{{skill[header.key]}}</td>
</tr>

Angular checkbox gets updated even though ngModel doesn't

I'm working on a shift arrangement app. In it I'm trying to create two tables that show which possible shifts each user has selected.
Both tables display the same data, but arrange it differently. Each table cell has a number of check-boxes that display the possible shifts for each person (in table 1) or the possible people for a shift (in table 2). A checkbox from table 1 that displays shift A option for person X will have the same data-bind as its equivalent checkbox in table 2, which displays person X option for shift A.
The purpose of this is to update the equivalent data in both tables simultaneously when the user couples a person with a shift. The problem: when a checkbox in table 1 is checked/unchecked, all of the check-boxes in table 2 gets checked/unchecked, as shown below:
Here is my template:
<div class="table-container" dir="ltr">
<h3>People</h3>
<table>
<thead>
<th>Name</th>
<th>Options</th>
</thead>
<tbody>
<tr *ngFor="let user of userPreferences">
<td>{{user.name}}</td>
<td>
<div *ngFor="let selection of userYesses[user.name]">
<mat-checkbox class="option-checkbox" dir="ltr" [(ngModel)]="selection.isSelected" name="usc">{{selection.option}}</mat-checkbox>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="table-container" dir="ltr">
<h3>Shifts</h3>
<table>
<thead>
<th>Time</th>
<th>Options</th>
</thead>
<tbody>
<tr *ngFor="let shift of totalShifts">
<td dir="ltr">{{shift.time}}</td>
<td>
<div *ngFor="let selection of shiftYesses[shift.time]">
<mat-checkbox class="option-checkbox" [(ngModel)]="selection.isSelected" name="syc">{{selection.name}}</mat-checkbox>
</div>
</td>
</tr>
</tbody>
</table>
</div>
And here is relevant component code:
this.userPreferences.forEach(u => {
this.userYesses[u.name] = [];
u.preferences.shifts.forEach(week => {
week.forEach(day => {
if (!day.shifts) return;
day.shifts.forEach(shift => {
if (!this.shiftYesses[`${day.date} ${shift.time}`]) this.shiftYesses[`${day.date} ${shift.time}`] = [];
if (shift.isSelected) {
let selection = new Selection(`${day.date} ${shift.time}`, u.name);
this.userYesses[u.name].push(selection);
this.shiftYesses[`${day.date} ${shift.time}`].push(selection);
}
});
});
});
});
The code seems alright to me, am I missing anything? Maybe it's a bug in Angular?
Thanks in advance!
In case anyone else experiences this issue -
After a few days of struggling with this, I stumbled upon this issue from Angular's git - https://github.com/angular/angular/issues/9230
I've read the following in kara's answer:
In the case that you don't want to register a form control, you currently have a few options:
1 - Use ngModel outside the context of a form tag. This will never throw.
<input [(ngModel)]="person.food">
After reading this, I switched the <form> tag into a <div> and everything works as expected now.

ASP How to get a value from HTML column

I have an ASP page which displays rows of data in an HTML table, after a query to an SQL database. The number of returned rows can vary. I want to add a function so that when i click on a button, I fire off a query to the SQL server database to update a column in that particular row. To do that i will use the primary key from my result set.
The part I am having difficulty with is getting the proper rows ID. What I have wrote so far returns the same ID every time regardless of which row i click on. Sample code below.
<table>
<tr>
<td class="bold" colspan="9"><%=vHeading%></td>
</tr>
<tr>
<td class="tablehead">Id</td>
<td class="tablehead">WhenPosted</td>
<td class="tablehead">WhenCreated</td>
<td class="tablehead">WhenUpdated</td>
<td class="tablehead">Source</td>
<td></td>
</tr>
<%
DO WHILE NOT stRs.eof
i = i + 1
%>
<tr>
<td id="pId<%=i%>" class="tablecell"><%=stRs.fields("Id")%></td>
<td class="tablecell"><%=stRs.fields("WhenPosted")%></td>
<td class="tablecell"><%=stRs.fields("WhenCreated")%></td>
<td class="tablecell"><%=stRs.fields("WhenUpdated")%></td>
<td class="tablecell"><%=stRs.fields("Source")%></td>
<td id=<%=i%>><input type="button" value="Post" onclick="post();" /></td>
</tr>
<%
stRs.MoveNext
LOOP
%>
</table>
<script text="text/javascript">
function post() {
var pId = document.getElementById("pId").innerHTML;
alert(pId);
}
</script>
So i loop through my result set creating rows. For examples-sake, row one will contain ID 1. Row 2 will have ID 2 etc.
If I click on my button which fires off the post method, the alert shows ID 1 every time, no matter the row i click on. I guessed its because i was originally assigning the same ID to the column for each row. I now use a counter variable and assign it to the ID which is creating unique ID's for the columns now, but I'm not sure how to call the function and use the correct ID. Any pointers are much appreciated!
You have to pass I to the function:
<input type="button" value="Post" onclick="post(<%=i%>);" />
then
<script text="text/javascript">
function post(i) {
var pId = document.getElementById("pId"+i).innerHTML;
alert(pId);
}
</script>

How to use xpath to select a value in a drop down list located in a particular row?

I am testing a page using selenium web driver. I have rows of data that represent 'requests', and in the last column of each of those rows the user can click a drop down list (with the option to either approve or reject) element that allows them to 'approve' or 'reject' the request.
I need to be able to select the approve option on the drop down list of a row whose 'Name' column is equal to a variable (in this instance say the variable is 'John').
In this test the user will be approving 'John's' request by selecting approve. How do I use xpath to ensure I am selecting the correct drop down element for the right person (right row)? Will I need to include a select element within an xpath somehow?
An example of the select element method to select a drop down element:
new SelectElement(this.Driver.FindElement(By.Name("orm")).FindElement(By.Name("Tutors"))).SelectByText(tutorName);
<form name="RequestsForm" action="SubmitRequest.aspx" method="POST">
<h2 class="blacktext" align="center">Course approvals</h2>
<table class="cooltable" width="90%" border="0" cellspacing="1" cellpadding="1">
<tbody>
<tr>
<td class="heading">
<b>Name</b>
</td>
<td class="heading">
<b>Request Date</b>
</td>
<td class="heading">
<b>Approved</b>
</td>
</tr>
<tr>
<td>
John
<input id="T1" type="text" value="888" name="T1">
</td>
<td>1/3/2015</td>
<td>
<select id="D1" class="selecttext" size="1" name="D1">
<option>?</option>
<option value="Approved">Approved</option>
<option>Rejected</option>
</select>
</td>
</tr>
</tbody>
</table>
Using XPath, this gets the position where the Name column is in your table:
count(//table[#class='cooltable']/tbody/tr[1]/td[b = 'Name']/preceding-sibling::td)+1
You can use that position to get the corresponding table cell in the other columns. This selects the corresponding td in the second row (where the ... represent the expression above):
//table[#class='cooltable']/tbody/tr[2]/td[count( ... )+1]
Appending /text() will extract the text (with spaces). Using normalize-space() will trim the text so you can compare it with John:
normalize-space(//table[#class='cooltable']/tbody/tr[2]/td[count( ... )+1]/text()) = 'John'
To select only the tr which contains John in the Name column, you leave only the td in the predicate. Now it returns a node-set of all tr which match the predicate text = John:
//table[#class='cooltable']/tbody/tr[normalize-space(td[count( ... )+1]/text()) = 'John']
Finally, if you append //select/option[#value='Approved'] to that expression, you will select the option with the Approved attribute in the context of that tr. Here is the full XPath expression:
//table[#class='cooltable']/tbody/tr[normalize-space(td[count(//table[#class='cooltable']/tbody/tr[1]/td[b = 'Name']/preceding-sibling::td)+1]/text()) = 'John']//select/option[#value='Approved']