Aurelia ui-virtualization infinite-scroll-next function not being hit after data refresh - aurelia

I'm using aurelia's ui-virtualization library to create a table using virtual-repeat and infinite-scroll-next. Html looks something like this:
<table>
<thead>
...
</thead>
<tbody>
<tr virtual-repeat.for="item of items" infinite-scroll-next="getMore">
...
</tr>
</tbody>
</table>
This works great, except I have certain ui components and interactions that update what is in my list array. Once that has be updated, the infinite-scroll-next function (getMore()) is never called. I update something like this:
update() {
let vm = this;
vm.apiService.getData(vm.filterOption)
.then(response => {
vm.items = response.content.items;
});
}
Where filterOptions are changed through the ui and apiService.getData() returns a promise from an http call. The data in the table updates correctly, but the infinite scroll is then broken.
Am I doing something wrong, or is this a bug in ui-virtualization?
Edit:
It appears there are some properties added to the array __array_observer__ and __observers__. Thinking overwriting the whole array and thus removing these properties might be causing the problem, I tried an approach where I just add or remove elements appropriately. This does not work either.
Edit:
It seems to fail if I leave fewer than 7 of the original elements in the array.

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.

vue.js: How can I create a dynamic key in v-for when key is a date?

I am struggling to figure out how to create a :key value for my v-for loop & I believe that is why the order of my list(s) are random.
This is what my template looks like:
<div v-for="(items, index) in documents">
<h1>{{ index }}</h1> // 2020-06-02
...
<table class="table">
<tr>
<th>Name</th>
</tr>
<tr v-for="(item, index) in items" :key="index">
<td>{{ item.name }}</td> // Foo
</tr>
</table>
</div>
My documents prop looks like this:
{
2020-06-01: ['name': 'Foo', ...],
2020-06-02: ['name': 'Bar', ...],
2020-06-03: ['name': 'Baz', ...],
}
My props are returning the documents in the correct order, but they are not shown on screen in the correct order. For example, I'll see 2020-06-03, 2020-06-01, 2020-06-02 or a different combination. I think it's because I don't have a :key so Vue doesn't know how to order things.
Since I am using the index in my outter loops, I'm not sure how to get a unique key.
What I think I need is something like:
<div v-for="(items, date, index) in documents" :key="index">...</div>
But, that's not really how it works.
I've also tried something like:
<div v-for="(items, index) in documents" :key="`i-${index}`">...</div>
No luck there either.
Thank you for any suggestions!
EDIT
Thank you for your feedback! Here's a few notes.
I'm getting documents from another source. I'm passing it is as props to my component. Currently, I'm using a computed property to try to alter the actual object; maybe add a key or something. No luck just yet...
Just as you've stated, it is coming in as an object. Each key of the object is a date. Each date, then has an array that I'm also looping through.
Here is what documents looks like (also added a screenshot). You can see that the dates are in order. The props are straight from my external service, so things look great so far.
{
2020-06-01: ['name': 'Foo', ...],
2020-06-02: ['name': 'Bar', ...],
2020-06-03: ['name': 'Baz', ...],
}
[
you can go with index2 or whatever else you would like
I tried making index be date just to help keep things straight and yeah.. I tried adding a third argument in the v-for just in case there was an unknown secret I could tap into (sadly no) :)
the format of your prop is messing me up mostly.
Same here, I'm just not able to understand why the order looks good in my props, but when looping through things, its pretty random.
show what you want the table to look like
This is the result I'm trying to accomplish.
<h2>2020-06-01</h2>. // this is the "missing" index (outer loop)
<table>
<tr>
<th>Name</th>
<th>Email</th>
</tr>
<tr> // this is the array that exists within each date
<td>Foo</td>
<td>foo#example.com</td>
</tr>
</table>
Edit/Solution
Thank you so much #Goofballtech! Your answer really helped me think about what I had and directions to try. Here's what I ended up with. sortedDocuments is a computed property; this.documents is a reference to the documents props that is getting passed in.
sortedDocuments() {
const data = this.documents;
return Object.keys(data)
.map((date) => {
return {
date: date,
data: data[date],
}
})
.sort(function (a, b) {
let dateA = new Date(a.date);
let dateB = new Date(b.date);
return dateA - dateB;
})
.reverse();
},
:key is used to watch for changes, not necessarily for order.
2 things.
if you want to maintain a specific order the general solution is to use an array. Order of object (although there is a bit of method to the madness) cannot necessarily be counted upon.
I believe you should used a computed property to modify these as you require then iterate over that array instead of using the object directly for what it seems you are trying to do. Will be back will an edit adding code in a moment.
Edit1 -
General information about indexes in v-for. The word index is not some magic keyword. If you have nested v-for you can do this:
<div v-for="(stuff, index) in stuffing">
display stuff here, index is your key
<div v-for="otherStuff, skdjdfhfyjsksj) in theOtherThings">
skdjdfhfyjsksj is your key value here
</div>
</div>
index is shown in examples to be clear but you can go with index2 or whatever else you would like there. It's getting passed as the second argument and that's where the magic happens.
Back to the base issue...So the format of your prop is messing me up mostly. Sorting the existing entries is straight forward but there really isn't a key:value array in javascript. Those are objects. So where are you getting the documents prop from originally? Is that something you are creating to pass down or is it coming that way from an external source?
sorting of the dates can be done like this in computed, this doesn't include the rest of the data yet as I'm not sure if you need to convert those on the fly or if we can change the prop before it gets here. I changed your document arrays to objects because the code would never show anything with that data formatted as an array as it complained about a missing ] since it did not expect the array key:value formatting.
https://jsfiddle.net/goofballtech/hmr6uw2a/65/
computed: {
sortedDates(){
return Object.keys(this.documents)
.sort((a,b) => a > b ? 1 : 0)
}
},
Final request for this edit, can you show what you want the table to look like? Having that context would help in finding the optimal data structure to achieve the results you need.
Edit2 -
Assuming the object can be an array of objects, and there is potential to have more than one name/email per date array. The below would work.
It all hinges on those funky arrays though.
Might be easier for you to copy the raw return from the API and just go in in to sterilize the data so i can see how the arrays are formatted.
https://jsfiddle.net/goofballtech/hmr6uw2a/141/
<div v-for="(docDate, index) in sortedDates" :key="index">
<h3>
{{docDate.date}}
</h3>
<table class="table">
<tr>
<th>Name</th>
<th>Email</th>
</tr>
<tr v-for="(item, index2) in docDate.data.length" :key="index2">
<td>{{documents[docDate.date][index2].name}}</td>
<td>{{documents[docDate.date][index2].email}}</td>
</tr>
</table>
</div>

Angular2 table is not refreshing data

I have an Angular2 application which is getting Json data and displaying it as a table. When I try to refresh / Update it with new data, table is showing old data only.
getting data through service like below:
#Input()
participants:Participant[];
testClasses: TestClass[] = [];
ngOnChanges() {
let codes="";
for (let item of this.participants)
{
codes = codes.concat(item.Id).concat(",");
}
this.buildTable(codes);
}
buildTable(codes){
let url = this.serverUrl + "api/test?codes=" + codes;
// passing input participants as parameter in url
this.testService.getData(url)
.subscribe(data => this.onDataUpdate(data));
}
onDataUpdate(data: any) {
this.testClasses = data;
// here I am getting new/refreshed data.
}
displying in table like below:
<table id="testGrid">
<tr *ngFor="let testClass of testClasses">
<td>{{testClass.name}}</td>
<td *ngFor="let t of (testClass.Score)">
{{ t }}
</td>
</tr>
</table>
When page loads data first time, table is showing data, but when I try to refresh data, I can see new refreshed data in onDataUpdate funcion, but html table is not refreshing view with new data.
Any idea or help would be much appreciated.
I tried below link:
https://github.com/swimlane/ngx-datatable/issues/255
Angular 2 - View not updating after model changes
I made my own example and I was able to reproduce the behaviour.
So what going on here it's that you use an array as input. (participants:Participant).
And after what you said about your checkboxes its seems that you mutate your array. It has for effect that the reference of the array is still the same therefor angular don't detect a change. (by the way I don't know if it's an expected behaviour).
A solution to your problem could be to create a new array of participants at each change on checkboxes.
Something like that:
this.participants = this.participants.slice(0)
You could also look at this answer: Angular2 change detection: ngOnChanges not firing for nested object

Detecting Selected Row in html table Knockout/ASP.NET MVC

I've loaded an ASP.NET MVC viewModel into KnockoutJS using ko.mapping.fromJS(Model).
My viewModel looks something like this:
public IEnumerable<FunkyThing>funkyThings;
public FunkyThing selectedFunkyThing;
I've then got in my HTML View a table which looks something like this
<tbody data-bind="foreach: funkyThings">
<tr>
<td data-bind="text:name"></td>
<td data-bind="text:funkiness"></td>
<td>
</td>
</tr>
</tbody>
and all is good with this table. Clicking the select funky thing link happily calls the selectFunkyThing function:
model.selectFunkyThing= function (funkyThing) {
window.location = '#Url.Action(MVC.FunkyThingController.Index())' + "?funkyThingID=" + funkyThing.funkyThingID();
};
which in turn refreshes the page. The MVC viewmodels is reloaded and selectedFunkyThing is populated with the selected FunkyThing and the knockout view models are then re-read from the MVC viewmodel. So far so good.
I then wanted to update the table to highlight the selected entry.
So I updated the tr line as follows:
<tr data-bind="css:{'selected': $root.isSelected()}">
and created the new knockout function:
model.isSelected = function (funkyThing) {
return funkyThing.funkyThingID== model.selectedFunkyThing.funkyThingID;
};
but... it's not working.
Chrome throws a javascript exception stating that the FunkyThing parameter is undefined.
Technically I figure I could solve it by changing the MVC viewModel to actually set a isSelected on each FunkyThing within the array. However I figure there's got to be away of doing this from Knockout?
You were close! I added the ko.utils.unwrapObservable call because I bet the funkyThingID is an observable and not just a straight property - you did this yourself in your selectFunkyThing function. You could just execute them as well. I like the verbosity of unwrap though.
model.isSelected = function (funkyThing) {
var thisId = ko.utils.unwrapObservable(model.selectedFunkyThing.funkyThingID);
return ko.utils.unwrapObservable(funkyThing.funkyThingID) == thisId;
};
and then in your document you actually have to execute this function when ko parses the binding
<tr data-bind="css:{'selected': $root.isSelected($data)}">
Are those properties not both observables, so you need to reference them as functions? You also need to make your function a computed observable, I think:
model.isSelected = ko.computed(function (funkyThing) {
return funkyThing().funkyThingID() == model.selectedFunkyThing().funkyThingID();
});

How to pass extra variables and parameters to jQote2?

I have a couple Javascript functions which concatenates a URL which I set to the variable c. I then try to pass that variable into jQote2:
$.get("emptyReq.tpl", function(tmpl,c) {
$("#reqs").jqoteapp(tmpl, result, c);
});
In emptyReq.tmpl, I'm doing the following:
<tr id="row">
<td name="id" class="id"><%= this.FormattedID %></td>
<td name="name" class="name"><%= this._refObjectName %></td>
<td name="state" class="state"><%= this.ScheduleState %></td>
<td name="owner" class="owner"></td>
</tr>
I've tried a couple of variations (this.c and c) and I've also tried different variables, but I'm not able to get the URL to display correctly.
c is labeled as undefined in the console, and the URL ends up being something like: http://127.0.0.1/xampp/py2/undefined instead of the actual c which is something like https://rally1.rallydev.com/slm/rally.sp#/2735190513d/detail/userstory/4599269614
Is there a way to pass the parameters properly? Or am I supposed to do the concatenation in the .tmpl file itself?
Here is what I've been using as a reference: jQote Reference.
rishimaharaj,
the jqoteapp method's third paramter is used to change the template tag (<% ... %> by default) on a per call basis. If you need to pass additional data to your template you have two options:
Make c a global variable (I wouldn't recommend that, though)
Copy c's value to the data parameter (recommended):
Please be aware that the copying needs to take into account of what type your
templating data is, i.e. a single object is handled different from an array of
objects!
$.get("emptyReq.tpl", function(tmpl,c) {
var data;
// 'result' seems to be a global var, thus making a copy is a good idea.
// Copying needs to take into account the type of 'result'
if ( Object.prototype.toString(result) === '[object Array]' ) {
data = result.slice(0);
} else {
data = [$.extend({}, result)];
}
// Now it is safe to add 'c' to the wrapping array. This way
// we won't override any homonymous property
data.c = c;
// Call the processing with our local copy
$("#reqs").jqoteapp(tmpl, data);
});
Once you've changed this, you will be able to access c through the templating
lamda's inherent property data:
<tr id="row">
... <a href="<%= data.c %>"><%= this.FormattedID ...
</tr>
Regards,
aefxx