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

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>

Related

Angular + firebase - can't get the key value in html

I want to navigate to an URL based on the key of an object in my database, but when i acces the p.$key value is always undefined.
service:
getAll(){
return this.db.list('/products').valueChanges();
}
component:
products$;
constructor(private productService: ProductService) {
this.products$ = this.productService.getAll();
}
html:
<table class="table">
<thead>
<tr>
<th>Title</th>
<th>Price</th>
<th></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let p of products$ | async">
<td>{{p.title}}</td>
<td>{{p.price}}</td>
<td>
<a [routerLink]="['/admin/products/', p.$key]" >Edit</a>
</td>
</tr>
</tbody>
</table>
I am getting the right values for p.title and p.price, but p.$key is always undefined. I also tried p.key , same thing.
The valueChanges() stream doesn't include the key of the nodes. According to the AngularFire documentation on [valueChanges](When would you not use it? - When you need a more complex data structure than an array or you need the key of each snapshot for data manipulation methods. This method assumes you either are saving the key for the snapshot data or using a "readonly" approach.):
When would you not use it? - When you need a more complex data structure than an array or you need the key of each snapshot for data manipulation methods. This method assumes you either are saving the key for the snapshot data or using a "readonly" approach.
I think you may want to use the snapshotChanges stream instead, which gives you the entire DataSnapshot for each object from which you can get the key and the val().
Service:
Pipe is not necessary. I used it so you can console.log your object to view.
getAll(){
return this.db.list('/products').snapshotChanges()
.pipe(
map(object => {
console.log(object);
return object;
})
);
}
Component
Same as yours
html
<span *ngFor="let item of items$ | async">
<!-- print out the key values -->
{{item.key}}
</span>

Angular UI-Bootstrap typeahead in table

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.

Thymeleaf, get first element list of an object that is already iterating

<tr th:each="current : ${object.list}" >
<td th:text="${current.currentList...???}"></td>
...
I have an object that has a list.
"current" has also a list inside it called currentList.
currentList has only one element called objectTwo. I want to access to the attributes of objectTwo.
It's possible?
Hope this will work for you.
<tr th:each="current : ${object.list}" >
<td th:text="${current.currentList[0].objectTwo...}"></td>
...

create dynamic html table with dynamic tr ,td using angularjs

how can I create an html table from a json file, which I do not know the number of columns or the number of rows (the number of row ng-repeat enough),
this json file is editable and the number of column and row change
You would need to load your JSON file into your app with either the $http or $resource service. This is best done in a service, which you would inject whereever you need your data.
this.getJson = function() { // real json get
$http.get('/api/GetJson').then(function(data) {
return data;
});
};
I created a small plunker with a service that holds my fake getJson function which returns the json data with the rows. Then I injected the service into my HomeController and put the rows on the $scope.
$scope.rows = loadJsonService.getFakeJson().rows;
When the rows are on the $scope, you only have to use the ngRepeat directive to create your table structure.
<table class="table table-striped">
<tr class="row" ng-repeat="row in rows">
<th>row {{$index + 1}}</th>
<td class="column" ng-repeat="column in row.column">{{column}}</td>
</tr>
</table>

Inserting HTML code when passing values to templates for given conditions

I have a table I am creating that looks like this:
<table>
<thead>
<tr>
<th>Value1</th>
<th>Value2</th>
<th>Value3</th>
<th>Value4</th>
</tr>
</thead>
<tbody>
{{#each []}}
<tr>
<td>{{this.val1}}</td>
<td>{{this.val2}}</td>
<td>{{this.val3}}</td>
<td>{{this.val4}}</td>
</tr>
{{/each}}
</tbody>
I want it to be the case that if val1, for instance, is greater than some value X, it will appear red.
How can I pass HTML into the template once some pre-defined condition - like the above example - is satisfied?
Ideally you should be driving this functionality using your models.
You could achieve the desired functionality using Marionette CollectionView. Each model in the collection should look something like:
var Model = Backbone.Model.extend({
initialize: function(){
/* On initialize, we call our method to set additional computed properties */
this.setProperty();
}
setProperty: function() {
if (this.get("someProperty") > x) {
this.set("className", "css-class")
}
}
});
Then from within your ItemView template you should be able to set the class on your table row item
<tr class="{{this.className}}">
<td>{{this.val1}}</td>
<td>{{this.val2}}</td>
<td>{{this.val3}}</td>
<td>{{this.val4}}</td>
</tr>