Loading additional modals from server with Paginator.clientPager - javascript

I'm trying to load additional modals from the server after the initial fetch with Paginator.clientPager
This is my collection, pretty much copy pasted from the example code on github.
return new (Backbone.Paginator.clientPager.extend({
model: model,
paginator_core: {
type: 'GET',
dataType: 'json',
url: '/odata/LibraryFile'
},
paginator_ui: {
// the lowest page index your API allows to be accessed
firstPage: 1,
// which page should the paginator start from
// (also, the actual page the paginator is on)
currentPage: 1,
// how many items per page should be shown
perPage: 2,
// a default number of total pages to query in case the API or
// service you are using does not support providing the total
// number of pages for us.
// 10 as a default in case your service doesn't return the total
totalPages: 5
},
server_api: {
// number of items to return per request/page
'$skip': function () { return this.perPage * (this.currentPage - 1) },
'$top': function () { return this.perPage },
},
parse: function (response) {
console.log(response);
return response.value;
}
}))();
I'm calling the initial fetch like so
myCollection.fetch({
success: function(){
myCollection.pager();
},
silent:true
});
Then, after the user has browsed trough the local pages with the clientPager, he probably wants to load in more pages, without deleting the first pages.
I try to achieve this like this, but for some reason, after i call pager(); the 2 new records are removed.
myCollection.currentPage = 2;
myCollection.fetch({
success: function(){
console.log(myCollection.length) // 4 models, with correct data
myCollection.pager();
console.log(myCollection.length) // the 2 new records are removed
},
silent:true,
remove: false // don't remove old records
});
What am i doing wrong, how can i load it 2 more pages with the Paginator.clientPager ?
I don't want to use requestPager because then i can't do in memory pre-caching, at least, i think.

In my experience, this is caused by the pager() method of Backbone.Paginator.clientPager. You can take a look at the code here:
Backbone.Paginator.clientPager
Lines 292 through to 294 show that the Backbone.Paginator.clientPager.origModels is only assigned to the current models (the one whose length you correctly tested in your illustrations above) if it's undefined. The problem is that by the time the user probably wants to load more pages without deleting the first, the origModels property would already be set as a result of the initial fetch.
This means you'd have to explicitly make origModels undefined again before pager() would act as you want. Note what happens later on line 296 of the source code (models is assigned to a copy of origModels). That's why your two new records were removed. The following code should work as you intended:
myCollection.currentPage = 2;
myCollection.fetch({
success: function(){
delete myCollection.origModels; // to ensure that origModels is overridden in pager() call below
myCollection.pager();
},
silent:true,
remove: false // don't remove old records
});

Related

Cannot paginate with Angular Material pagination

I use Angular Material table pagination for server side data retrieval (page by page) and my API method is working and returns necessary parameters e.g. total records. However, although I pass the length parameter to the paginator, the prev and next buttons are not enabled (I think length is set the record size of per page that is 5, instead of 15 that is the real length). Here is the methods I use:
.html:
<mat-paginator
#paginator
[pageSizeOptions]="paginationSizes" // "[5,10,15,20]"
[pageSize]="defaultPageSize" // 5
showFirstLastButtons
[length] = "resultsLength" // it should be 16
[pageIndex]="0"
(page)="onPageFired($event)">
</mat-paginator>
I defined the necessary #Input and EventEmitter definitions and the necessary methods are called from base component (paginator).
ngOnInit() {
this.getPublishers();
}
getEmployee(): void {
this.service.getByPagination(1, 5, '1==1') // I iitially set pageNumber=1 and pageSize=5
.subscribe((data) => {
this.resultsLength = data.totalCount; // it returns 16
this.tableData = data.items; // data.items.length = 5, but setting resultsLength = 16 is enough I think
});
}
//I use a different method for paging, but I think I wiil use the getEmployee after making pagination work
//this method is triggered by onPageFired()
onPagingAction(event: any) {
const index = event.pageIndex + 1;
const size = event.pageSize;
this.service.getByPagination(index, size, '1==1')
.subscribe((data) => {
this.pageSizeTest = data.totalCount;
this.tableData = data.items;
});
}
The first page is loaded with 5 records after page refresh, but the next button is disabled. I think the problem may caused from setting length property as 5, but not sure.

Unknown amount of Ajax Request inside loop

So here is my problem (and I have tried several suggestions found here at stackOverflow):
Scenario:
I am using the Gitlab API and I want to list all the "issues" of the bug tracker present on the system for a given project.
Problem:
This is all fine and good, however there is a paging system to do this since the ajax requesst is limited to 100 entries per response.
So I have to do something like this:
$.ajax({
url: "https://mygitlabURL/api/v3/projects/97/issues",
type: "GET",
data: {
private_token: "mytoken",
per_page: 100,
page: 1
}
This will give me back 100 entries. What I need to do is add these entries to a list, and check: "was there fewer than 100 entries in the response?" if so I can stop doing requests.
I need however to get the complete list before I can move on with my code, so I tried using $.when() function and do my requests in a function of its own. In this function I have tried using:
closures like in this answer
recursion like suggested in another answer (don't have the link)
while loop since, oh well, why not
The problem with all the solutions is that, beeing asynchronous, I end up receiving a response and my $.when() function executes before I have any response from the server.
Is there a way to do this?
Here is the latest code (with recursion) I have tried:
$(function () {
$("button").on("click", function () {
$.when(func1(), func2()).then(finishedFunc);
});
});
var func1 = function (pageNr) {
pageNr = pageNr || 1;
megaList = [];
// Get server values
$.ajax({
url: "https://mygitlabURL/api/v3/projects/97/issues",
type: "GET",
data: {
private_token: "myToken",
per_page: 100,
page: pageNr
},
success: function (issuesList) {
console.log("Page number: " + pageNr);
megaList = [pageNr];
if (issuesList.length < 100) {
return megaList;
}
pageNr = pageNr +1 ;
var received = func1(pageNr);
megaList = $.merge(megaList, received);
return megaList;
}
});
}
var func2 = function () {
return 20;
}
var finishedFunc = function (resp1, resp2) {
console.log("Responses were resp1: " + resp1 + " and resp2: " + resp2);
}
And I always get something like:
"Responses were resp1: undefined and resp2: 20"
And I am expecting something like:
"Responses were resp1: [1, 2, 3, 4, 5, ..., 27] and resp2: 20"
As stated before, I can't find any solutions that resolve my problem here in the forums, but if I might have overlooked something, please point me in the right way.
While reading the documentation.I came across this.
Pagination
When listing resources you can pass the following parameters:
page (default: 1) - page number
per_page (default: 20, max: 100) - number of items to list per page
Link headers are send back with each response. These have rel prev/next/first/last and contain the relevant URL. Please use these instead of generating your own URLs.
It automatically says that the Response that came back will contain rel prev/next/first/last. So you can easily check that link headers contain next rel or not and If it contain then directly call that url for more issues and If not that means it does not contain more issues.
Once you start to think in async terms the solution become pretty simple:
var megaList = [];
function loadList(page) {
page = Math.max(1, page);
$.ajax({
url: "https://mygitlabURL/api/v3/projects/97/issues",
type: "GET",
data: {
private_token: "myToken",
per_page: 100,
page: page
},
success: function (issuesList) {
console.log("Page number: " + page);
megaList = megaList.concat(issuesList);
if (issuesList.length >= 100) loadList(page+1);
}
});
}
loadList();

Meteor, One to Many Relationship & add field only to client side collection in Publish?

Can anyone see what may be wrong in this code, basically I want to check if a post has been shared by the current logged in user AND add a temporary field to the client side collection: isCurrentUserShared.
This works the 1st time when loading a new page and populating from existing Shares, or when adding OR removing a record to the Shares collection ONLY the very 1st time once the page is loaded.
1) isSharedByMe only changes state 1 time, then the callbacks still get called as per console.log, but isSharedByMe doesn't get updated in Posts collection after the 1st time I add or remove a record. It works the 1st time.
2) Why do the callbacks get called twice in a row, i.e. adding 1 record to Sharescollection triggers 2 calls, as show by console.log.
Meteor.publish('posts', function() {
var self = this;
var mySharedHandle;
function checkSharedBy(IN_postId) {
mySharedHandle = Shares.find( { postId: IN_postId, userId: self.userId }).observeChanges({
added: function(id) {
console.log(" ...INSIDE checkSharedBy(); ADDED: IN_postId = " + IN_postId );
self.added('posts', IN_postId, { isSharedByMe: true });
},
removed: function(id) {
console.log(" ...INSIDE checkSharedBy(); REMOVED: IN_postId = " + IN_postId );
self.changed('posts', IN_postId, { isSharedByMe: false });
}
});
}
var handle = Posts.find().observeChanges({
added: function(id, fields) {
checkSharedBy(id);
self.added('posts', id, fields);
},
// This callback never gets run, even when checkSharedBy() changes field isSharedByMe.
changed: function(id, fields) {
self.changed('posts', id, fields);
},
removed: function(id) {
self.removed('posts', id);
}
});
// Stop observing cursor when client unsubscribes
self.onStop(function() {
handle.stop();
mySharedHandle.stop();
});
self.ready();
});
Personally, I'd go about this a very different way, by using the $in operator, and keeping an array of postIds or shareIds in the records.
http://docs.mongodb.org/manual/reference/operator/query/in/
I find publish functions work the best when they're kept simple, like the following.
Meteor.publish('posts', function() {
return Posts.find();
});
Meteor.publish('sharedPosts', function(postId) {
var postRecord = Posts.findOne({_id: postId});
return Shares.find{{_id: $in: postRecord.shares_array });
});
I am not sure how far this gets you towards solving your actual problems but I will start with a few oddities in your code and the questions you ask.
1) You ask about a Phrases collection but the publish function would never publish anything to that collection as all added calls send to minimongo collection named 'posts'.
2) You ask about a 'Reposts' collection but none of the code uses that name either so it is not clear what you are referring to. Each element added to the 'Posts' collection though will create a new observer on the 'Shares' collection since it calls checkSharedId(). Each observer will try to add and change docs in the client's 'posts' collection.
3) Related to point 2, mySharedHandle.stop() will only stop the last observer created by checkSharedId() because the handle is overwritten every time checkSharedId() is run.
4) If your observer of 'Shares' finds a doc with IN_postId it tries to send a doc with that _id to the minimongo 'posts' collection. IN_postId is passed from your find on the 'Posts' collection with its observer also trying to send a different doc to the client's 'posts' collection. Which doc do you want on the client with that _id? Some of the errors you are seeing may be caused by Meteor's attempts to ignore duplicate added requests.
From all this I think you might be better breaking this into two publish functions, one for 'Posts' and one for 'Shares', to take advantage of meteors default behaviour publishing cursors. Any join could then be done on the client when necessary. For example:
//on server
Meteor.publish('posts', function(){
return Posts.find();
});
Meteor.publish('shares', function(){
return Shares.find( {userId: this.userId }, {fields: {postId: 1}} );
});
//on client - uses _.pluck from underscore package
Meteor.subscribe( 'posts' );
Meteor.subscribe( 'shares');
Template.post.isSharedByMe = function(){ //create the field isSharedByMe for a template to use
var share = Shares.findOne( {postId: this._id} );
return share && true;
};
Alternate method joining in publish with observeChanges. Untested code and it is not clear to me that it has much advantage over the simpler method above. So until the above breaks or becomes a performance bottleneck I would do it as above.
Meteor.publish("posts", function(){
var self = this;
var sharesHandle;
var publishedPosts = [];
var initialising = true; //avoid starting and stopping Shares observer during initial publish
//observer to watch published posts for changes in the Shares userId field
var startSharesObserver = function(){
var handle = Shares.find( {postId: {$in: publishedPosts}, userId === self.userId }).observeChanges({
//other observer should have correctly set the initial value of isSharedByMe just before this observer starts.
//removing this will send changes to all posts found every time a new posts is added or removed in the Posts collection
//underscore in the name means this is undocumented and likely to break or be removed at some point
_suppress_initial: true,
//other observer manages which posts are on client so this observer is only managing changes in the isSharedByMe field
added: function( id ){
self.changed( "posts", id, {isSharedByMe: true} );
},
removed: function( id ){
self.changed( "posts", id, {isSharedByMe: false} );
}
});
return handle;
};
//observer to send initial data and always initiate new published post with the correct isSharedByMe field.
//observer also maintains publishedPosts array so Shares observer is always watching the correct set of posts.
//Shares observer starts and stops each time the publishedPosts array changes
var postsHandle = Posts.find({}).observeChanges({
added: function(id, doc){
if ( sharesHandle )
sharesHandle.stop();
var shared = Shares.findOne( {postId: id});
doc.isSharedByMe = shared && shared.userId === self.userId;
self.added( "posts", id, doc);
publishedPosts.push( id );
if (! initialising)
sharesHandle = startSharesObserver();
},
removed: function(id){
if ( sharesHandle )
sharesHandle.stop();
publishedPosts.splice( publishedPosts.indexOf( id ), 1);
self.removed( "posts", id );
if (! initialising)
sharesHandle = startSharesObserver();
},
changed: function(id, doc){
self.changed( "posts", id, doc);
}
});
if ( initialising )
sharesHandle = startSharesObserver();
initialising = false;
self.ready();
self.onStop( function(){
postsHandle.stop();
sharesHandle.stop();
});
});
myPosts is a cursor, so when you invoke forEach on it, it cycles through the results, adding the field that you want but ending up at the end of the results list. Thus, when you return myPosts, there's nothing left to cycle through, so fetch() would yield an empty array.
You should be able to correct this by just adding myPosts.cursor_pos = 0; before you return, thereby returning the cursor to the beginning of the results.

Interrupting an looping function

I trigger the following function when a tooltip is clicked. It is an ajax poll.
There can be many tooltips on the page, and more than one can need access to the data retrieved from the server.
What I want to achieve is to have this poll running as one instance - so if the user clicks a different tooltip the polling stops, rather than being duplicated.
Would be grateful if you could help.
Thanks
function doConversationsAjaxLongPoll(tablename){
clientSubmit = new Object;
// HERE WE'RE GOING TO GET A LIST OF THE ROWIDS THAT WE NEED TO POLL FOR, MAKE AN OBJECT OUT OF THEM. DO THIS BY LOOKING AT WHICH //TOOLIPS HAVE CLASS OPEN
var tooltips = [];
$('.tooltipOpen').each(function(index){
tooltips.push($(this).data('idrow'))
})
console.log("tooltips length: " + tooltips.length)
if(tooltips.length==0){
// console.log("tooltip length is 0 so we're returning false")
return false
}
clientSubmit.OpenConversations = tooltips
clientSubmit.tablename = tablename
clientSubmit.CurrentData = $('body').data('conversations')
console.log(clientSubmit)
$.ajax({
type: 'POST',
url: '/conversations.php?loadNew=1',
data: clientSubmit,
timeout: 25000,
success: function(data){
console.log('success')
data=JSON.parse(data)
console.log(data)
$('body').data('conversations', data)
},
complete: function(status, jqXHR){
if(tooltips.length==0){
// console.log("tooltip length is 0 so we're returning false")
return false
}
else
{
doConversationsAjaxLongPoll(tablename);
}
}
});
updateConversations()
}
I don't doubt that there are faaaar better ways of doing this but I have worked around the problem by having a random number generated by the click function, stored in $('body').data('random') which is then passed to the poll function. When the poll function loops it checks if the random number it was passed matches the one in data-random and returns false if it doesn't.

Javascript Data Layer Architecture Assistance

I'm making a fairly complex HTML 5 + Javascript game. The client is going to have to download images and data at different points of the game depending on the area they are at. I'm having a huge problem resolving some issues with the Data Layer portion of the Javascript architecture.
The problems I need to solve with the Data Layer:
Data used in the application that becomes outdated needs to be automatically updated whenever calls are made to the server that retrieve fresh data.
Data retrieved from the server should be stored locally to reduce any overhead that would come from requesting the same data twice.
Any portion of the code that needs access to data should be able to retrieve it easily and in a uniform way regardless of whether the data is available locally already.
What I've tried to do to accomplish this is build a data layer that has two main components:
1. The portion of the layer that gives access to the data (through get* methods)
2. The portion of the layer that stores and synchronizes local data with data from the server.
The workflow is as follows:
When the game needs access to some data it calls get* method in the data layer for that data, passing a callback function.
bs.data.getInventory({ teamId: this.refTeam.PartyId, callback: this.inventories.initialize.bind(this.inventories) });
The get* method determines whether the data is already available locally. If so it either returns the data directly (if no callback was specified) or calls the callback function passing it the data.
If the data is not available, it stores the callback method locally (setupListener) and makes a call to the communication object passing the originally requested information along.
getInventory: function (obj) {
if ((obj.teamId && !this.teamInventory[obj.teamId]) || obj.refresh) {
this.setupListener(this.inventoryNotifier, obj);
bs.com.getInventory({ teamId: obj.teamId });
}
else if (typeof (obj.callback) === "function") {
if (obj.teamId) {
obj.callback(this.team[obj.teamId].InventoryList);
}
}
else {
if (obj.teamId) {
return this.team[obj.teamId].InventoryList;
}
}
}
The communication object then makes an ajax call to the server and waits for the data to return.
When the data is returned a call is made to the data layer again asking it to publish the retrieved data.
getInventory: function (obj) {
if (obj.teamId) {
this.doAjaxCall({ orig: obj, url: "/Item/GetTeamEquipment/" + obj.teamId, event: "inventoryRefreshed" });
}
},
doAjaxCall: function (obj) {
var that = this;
if (!this.inprocess[obj.url + obj.data]) {
this.inprocess[obj.url + obj.data] = true;
$.ajax({
type: obj.type || "GET",
contentType: "application/json; charset=utf-8",
dataType: "json",
data: obj.data,
url: obj.url,
async: true,
success: function (data) {
try {
ig.fire(bs.com, obj.event, { data: data, orig: obj.orig });
}
catch (ex) {
// this enables ajaxComplete to fire
ig.log(ex.message + '\n' + ex.stack);
}
finally {
that.inprocess[obj.url + obj.data] = false;
}
},
error: function () { that.inprocess[obj.url + obj.data] = false; }
});
}
}
The data layer then stores all of the data in a local object and finally calls the original callback function, passing it the requested data.
publishInventory: function (data) {
if (!this.inventory) this.inventory = {};
for (var i = 0; i < data.data.length; i++) {
if (this.inventory[data.data[i].Id]) {
this.preservingUpdate(this.inventory[data.data[i].Id], data.data[i]);
}
else {
this.inventory[data.data[i].Id] = data.data[i];
}
}
// if we pulled this inventory for a team, update the team
// with the inventory
if (data.orig.teamId && this.team[data.orig.teamId]) {
this.teamInventory[data.orig.teamId] = true;
this.team[data.orig.teamId].InventoryList = [];
for (var i = 0; i < data.data.length; i++) {
this.team[data.orig.teamId].InventoryList.push(data.data[i]);
}
}
// set up the data we'll notify with
var notifyData = [];
for (var i = 0; i < data.data.length; i++) {
notifyData.push(this.inventory[data.data[i].Id]);
}
ig.fire(this.inventoryNotifier, "refresh", notifyData, null, true);
}
There are several problems with this that bother me constantly. I'll list them in order of most annoying :).
Anytime I have to add a call that goes through this process it takes too much time to do so. (at least an hour)
The amount of jumping and callback passing gets confusing and seems very prone to errors.
The hierarchical way in which I am storing the data is incredibly difficult to synchronize and manage. More on that next.
Regarding issue #3 above, if I have objects in the data layer that are being stored that have a structure that looks like this:
this.Account = {Battles[{ Teams: [{ TeamId: 392, Characters: [{}] }] }]}
this.Teams[392] = {Characters: [{}]}
Because I want to store Teams in a way where I can pass the TeamId to retrieve the data (e.g. return Teams[392];) but I also want to store the teams in relation to the Battles in which they exist (this.Account.Battles[0].Teams[0]); I have a nightmare of a time keeping each instance of the same team fresh and maintaining the same object identity (so I am not actually storing it twice and so that my data will automatically update wherever it is being used which is objective #1 of the data layer).
It just seems so messy and jumbled.
I really appreciate any help.
Thanks
You should consider using jquery's deferred objects.
Example:
var deferredObject = $.Deferred();
$.ajax({
...
success: function(data){
deferredObject.resolve(data);
}
});
return deferredObject;
Now with the deferredObject returned, you can attach callbacks to it like this:
var inventoryDfd = getInventory();
$.when(inventoryDfd).done(function(){
// code that needs data to continue
}
and you're probably less prone to errors. You can even nest deferred objects, or combine them so that a callback isn't called until multiple server calls are downloaded.
+1 for Backbone -- it does some great heavy lifting for you.
Also look at the Memoizer in Douglas Crockford's book Javascript the Good Parts. It's dense, but awesome. I hacked it up to make the memo data store optional, and added more things like the ability to set a value without having to query first -- e.g. to handle data freshness.

Categories

Resources