*ngFor and *ngIf on a <tr> element in Angular 5 - angular5

In my Angular 5 app I'm looping to create tr elements:
<tr *ngFor="let element of elements">
but I only want to show the row if certain conditions are met. I know I can't use *ngFor and *ngIf together, but I'm not seeing how to get around this.
I saw a post saying to add a template inside the tr with an [ngIf] construct but that doesn't seem to be valid syntax any longer. If I try it like so:
<tr *ngFor="let element of elements">
<template [ngIf]="element.checked || showUnchecked">
then I get runtime errors.
AppComponent.html:14 ERROR Error: StaticInjectorError(AppModule)[NgIf -> TemplateRef]:
StaticInjectorError(Platform: core)[NgIf -> TemplateRef]:
NullInjectorError: No provider for TemplateRef!

Create a getter that return the property elements already filtered. Something like
get filteredElements() {
if( this.showUnchecked ) {
return this.elements;
} else {
return this.elements.filter( () => { return this.checked; } );
}
}

ng-container would work instead of template
<table><tr *ngFor="let element of elements">
<ng-container *ngIf="element.checked || showUnchecked">
<td></td>
<td></td>
</ng-container>
</tr>
<table>

Related

Svelte {#if variable} block does not react to variable updates within the block

I would like to populate a table with visible rows in Svelte.
My current attempt relies on a {#if variable} test, where the rendered row updates the variable. Unfortunately, the test does not appear to react to changes to the variable. Perhaps this is as designed but the documentation does not appear to address this. Essentially:
<table>
<tbody>
{#each rows as row}
{#if renderIt==true}
<tr use:updateRenderIt>
<td>cell</td>
</tr>
{/if}
{/each}
</tbody>
</table>
I think my understanding of the timing is lacking :(. Perhaps the {#if} block cannot react to each renderIt change. There are quite a few examples of {#if} blocks, but none appear to rely on a variable which is changed within the block.
There is a running example in the Svelte playground. The console divider can be moved vertically to change the viewport dimensions.
If someone knows of a way to achieve this it would be appreciated! I can do it in traditional Javascript, but my Svelte expertise is limited :).
What I'm assuming you want is to have a state on each row when it is visible.
To do so you will need to store some data with your row, so instead of your row being a list of numbers and a single boolean to say if all rows are visible or not, it will be a list of objets that have a property visible:
let rows = [];
for (let i = 0; i < 100; i++) {
rows.push({
index: i,
visible: false,
});
};
Next, to capture when visibility changes on those rows, use Intersection Observer API:
let observer = new IntersectionObserver(
(entries, observer) => {
console.log(entries);
}
);
And use a svelte action to add that observer to elements:
<script>
...
let intersect = (element) => {
observer.observe(element);
};
</script>
<table>
<tbody>
{#each rows as row (row.index)}
<tr
use:intersect>
<td>{row.visible}</td>
</tr>
{/each}
</tbody>
</table>
To pass the intersecting state back to the element throw a custom event on it:
let observer = new IntersectionObserver(
(entries, observer) => {
entries.forEach((entry) => {
entry.target.dispatchEvent(
new CustomEvent("intersect", { detail: entry.isIntersecting })
);
});
}
);
And finally capture that event and modify the state:
<tr use:intersect
on:intersect={(event) => (row.visible = event.detail)} >
<td>{row.visible}</td>
</tr>
To render rows up to how many can fit on screen you could make the defaut state visible: true, and then wrap the element with an {#if row.visible}<tr .... </tr>{/if}. After the first event you would then remove the observer from the element using observer.unobserve by either updating the svelte action or in the observer hook.

VeeValidate not working with inline editing. I am using v-for and v-if for inline editing

I have user list inline editing table structure. Each row is represents a user. I am editing user and using vee validate for validation
validateState(ref) {
if (
this.veeFields[ref] &&
(this.veeFields[ref].dirty || this.veeFields[ref].validated)
) {
return !this.veeErrors.has(ref)
}
return null
},
saveUserUpdate: function(user) {
this.$validator.validateAll().then((result) => {
debugger
if (!result) {
return
} else {
//code to save user api
}
})
}
<tr v-for="(user, userIndex) in userList" :key="userIndex">
<td>
<span v-show="user.isEditing">
<b-form-input size="sm" v-model="user.fullName" :name="'tbnewFullName' + user.unique_id" :state="validateState('tbnewFullName' + userIndex)" :aria-describedby="'tbnewFullName-feedback' + userIndex"></b-form-input>
<b-form-invalid-feedback :id="'tbnewFullName-feedback' + userIndex">
Required.
</b-form-invalid-feedback>
</span>
<div v-show="!user.isEditing">
{{user.fullName}}
</div>
</td>
</tr>
I don't know what wrong I am doing here but validations are not fired in saveUserUpdate. veeFields list in validateState are not showing my user's name field. Also veeFields is showing list of fields from previous form.
You have to use v-model="userList[userIndex].fullName. See here for details. The summary is, due to how vee-validate works internally, they have trouble tracking the limited-in-scope user variable.

Vue.js Update Array not working using this.$set

I do a request to a service and fill an object that have multiple arrays inside and it's an array itself. ex: this.Jprojs: [{name : 'test', ListItem1 : [], ListItem2 : [] }]
I put that object in a v-for:
<div id="app">
<table class="table table-striped">
<tr>
<th width="15%">Proj</th>
<th>Detail</th>
</tr>
<tr v-for="proj in Jprojs" :key="proj.name">
<td style="vertical-align:middle;"><strong>{{proj.name}}</strong><br/><a v-on:click="list(proj)"> <font-awesome-icon icon="tasks" /></a></td>
<td>{{proj.ListItem1.length}}</td>
</tr>
</table>
I have the method list:
list : function(proj){
axios.get(url).then(
response => {
this.$set(proj.ListItem1,0,response.data.value);
//Vue.set(proj.ListItem1,0,response.data.value);
this.nextTick;
console.log(proj)
},
error => {
},
err => { }
);
}
The console shows the update but the html is not updated.
Make sure to update the Jprojs value, instead of proj. You coud pass an index instead of the proj object.
Get the index with v-for="(proj, index) in Jprojs" and pass it as list(index). Then just edit the Jprojs array with the given index; Jprojs[index].ListItem1 = ...
I found the problem, Actually the property ListItem1 was not in the original Json so vue was not recognizing. what I did was use vue.$set correctly, was using Wrong
both work
this.$set(proj,"ListItem1",response.data.value);
Vue.set(proj,"ListItem1",response.data.value);

TYPO3 - Adding extbase viewhelper via javascript to partial

I have a partial ... and if I add a link via link.action ... the link is working perfectly fine:
MyPartial.html:
<f:link.action action="show" pageUid="43" pluginName="abc" controller="Abc" extensionName="abc" arguments="{record:1}">ActionLink</f:link.action>
But if I want to add the link.page viewhelper via Javascript to the partial ...
MyPartial.html
<table id="lei_all" width="100%">
<thead>
<tr>
<th>Column01</th>
<th>Column02</th>
</tr>
</thead>
</table>
JS:
var table_all = $('#table_all').DataTable( {
dom: "Blrtip",
ajax: {
url: "/source.php",
type: "POST"
},
serverSide: true,
processing: true,
columns: [
{ data: "column_01",
"render": function ( data, type, row ) {
return '<f:link.action action="show" pageUid="43" pluginName="abc" controller="Abc" extensionName="abc" arguments="{record:1}">'+data+'</f:link.action>';
} },
{ data: "column_02" }
],
...
} );
I'm getting the raw view helper like this:
<tbody>
<tr id=row_1 class="odd" role="row">
<td tabindex="0"><f:link.action action="show" pageUid="43" pluginName="abc" controller="Abc" extensionName="abc" arguments="{record:1}">ActionLink</f:link.action></td>
...
</tr>
</tbody>
How can I add the viewhelper in the above case?
ViewHelpers simply do not work that way. What you are trying is equivalent to calling a PHP function in JavaScript to build a string. The expected result of this is that you will see the Fluid tag output, exactly as you describe.
The only way is to construct your links in PHP and (somehow) transfer them to the JavaScript. Valid ways of doing that includes:
Using data-something properties to carry URLs from HTML to JS
Using an XHR request to create links for a certain purpose
Generating a list of all links indexed by whichever identifier it has in the table, as a JSON or other file in for example typo3temp, then reading this list and using the appropriate link when rendering your table in JS.
Depending on what you need, pick one and implement it so you do not have Fluid code in your JavaScript.

Using jQuery DataTables with Aurelia

I need some advice regarding the usage of jQuery DataTables with Aurelia. Basically I'm running into two problems.
I can't determine the best way to initialize it AFTER the repeat.for binding loop has completed. Apparently that loop is still working even after the attached() lifecycle is fired.
If I use $(myRef).DataTables(data: myArray) method to populate the table, and insert links (<a href-route=... click.delegate=...>) into that table, Aurelia doesn't seem to recognize the links or activate the router.
Problem 1: Here's my attempt to populate the table using Aurelia's binding. Aurelia correctly makes the table, and I can just wait 2-3 seconds and then load DataTables, but that's not the right way. I don't have a definitive event to trigger the loading of the DataTables class because I don't know when repeat.for is completed.
<div class="table-responsive">
<table ref="tblUserList" class="table table-condensed table-hover table-striped" width="100%">
<thead>
<tr>
<th><span t="Username"></span></th>
<th><span t="First_name"></span></th>
<th><span t="Last_name"></span></th>
</tr>
</thead>
<tbody>
<tr repeat.for="record of records">
<td><a route-href="route: user; params.bind: {id: record.user_id}" click.delegate="$parent.select(record)">${record.user_username}</a></td>
<td>${record.p_fname}</td>
<td>${record.p_lname}</td>
</tr>
</tbody>
</table>
</div>
Problem 2: Here's my attempt to populate the table using the jQuery method. DataTables successfully loads the table, but Aurelia doesn't recognize the links or trigger action.
$(this.tblUserList).dataTable({
"paginate": true,
"pageLength": 10,
data: this.records,
columns: [
{ data: 'user_username',
fnCreatedCell: function (nTd, sData, oData, iRow, iCol) {
$(nTd).html("<a route-href='route: user; params.bind: {id:" + oData.user_id + "}' click.delegate='$parent.select(" + oData.user_id + ")'>" + oData.user_username + "</a>");
}
},
{ data: 'p_fname' },
{ data: 'p_lname' }
]
});
Can anyone help me solve any one of the above problems? Or... am I approaching this whole issue the wrong way? Is it better to use the jQuery method to populate, or the Aurelia repeat.for binding loop?
I know this is old, but just in case, if it can help.
When you add DOM elements after binding, they are not aureliazed. You should use the enhance method of TemplatingEngine:
import the TemplatingEngine and inject it:
import {inject, TemplatingEngine} from 'aurelia-framework';
#inject(TemplatingEngine)
In the constructor, initialize the template engine:
constructor(templatingEngine) {
this.templatingEngine = templatingEngine;
}
in Aurelia's attached() method, do your datatable init stuff, and add a class to be able to retrieve your newly created DOM elements:
$(nTd).html("<a class='myclass' ...
Enhance your elements:
$('.myclass').each(function (index, value) {
let el = $(this);
if (!el.hasClass('au-target')) { //can't enhance already aureliazed elm
this.templatingEngine.enhance({element: el[0], bindingContext: this});
//el[0] is important to have DOM and not jQuery object
}
});
Then your binding should work.
Using the first approach (aurelia binding), remove data from the config object and load your data in the activate lifecycle hook:
import 'datatables';
export class UserList {
activate() {
this.records = [...];
}
attached() {
$(this.tblUserList).dataTable();
}
}