Pagination by token in sever side and ember.js - javascript

I have in the frontend an ember.js application with ember-data to abstract the models and in server side I have a REST Api. When I need a list of the items in the server I do a GET request for /items in the case that the list have more than 10 items I receive a json just like that:
{
nextPageToken:"token",
items:[
...
]
}
The nextPageToken is a token to be used to get the items in the next page, in this particular case making another GET request with the page token included: /items?pageToken=token.
I don't know the best way that I can use this tokens to create a page showing the items with a link to the next and the previous pages (storing the token from the previous page) with ember and ember-data.
UPDATE
After some research I found a solution to navigate to next page extending the extractMeta function in the RESTSerializer to store the nextPageToken as metadata.
App.ApplicationSerializer = DS.RESTSerializer.extend({
//...
// Extract the pageToken and store it as metadata from the desired type.
extractMeta: function(store, type, payload) {
store.metaForType(type, { nextPageToken: payload.nextPageToken });
}
}
And then in the controller from my page create a property based in the stored page token.
App.ItemsController = Ember.ArrayController.extend({
//...
// Get the page token store as metadata from the item model.
nextPageToken: function(){
var meta = this.store.metadataFor("item");
return meta.nextPageToken;
}.property('item')
}
Now it's possible to use the page token in your template to create a link to the next page.
With that, part of the problem is solved, but I still need to find a solution to navigate to previous pages.

Related

Nextjs creating a url that only processes code with no view

I am creating a new site using NextJS, the issue i am having is in regards to a password reset verification endpoint.
When a user triggers a password reset, it goes to the API, does all the processing and then returns them to the NextJS frontend at /verifyreset, which saves a code into localstorage, does a small bit of processing and then forwards them onto another page.
The issue is that there is a Default layout wrapping the component in my _app.js like so;
function MyApp({ Component, pageProps }) {
return (
<DefaultLayout><Component {...pageProps} /></DefaultLayout>
);
}
which means that the layout shows on the /verifyreset endpoint, and I only want that endpoint to process data.
Is there any way around this? to have an endpoint that can access localstorage but not be a 'page' so to speak
I did partially understand your question, it would have been clear if you had attached more code snippets in the question.
Anyway, from your statement below:
When a user triggers a password reset, it goes to the API, does all
the processing and then returns them to the NextJS frontend at
/verifyreset, which saves a code into localstorage, does a small bit
of processing and then forwards them onto another page.
what I understood is:
User triggers a password reset [lets say from PageA]
API is invoked; some processing happen
API then, redirects user to /verifyreset page [lets say it PageB]
Navigating to the page, information is saved into localstorage
Once that is completed, user is redirected to another page [lets say it PageC]
Correct me if I am wrong, so your question is, how could you actually skip users to view /verifyreset page but do the things like save to localstorage and other background operations.
Answer 1: The api is being invoked from PageA (see 1). Instead of the api redirecting user to /verifyreset page on the frontend, send some data (JSON or XML) to the calling function (in PageA components..). Based on that data, do the processing and once every thing is complete, redirect the user to PageC. [no need to worry about PageB i.e. /verifyreset page]. Please find the code snippet below:
**API End Point**
async resetPassword(req, res) {
try {
const model = model.body || {};
let data = await PasswordBusiness.reset(model);
//data needs to have information that you require on frontend
return res.json({success: true, data: data});
} catch (error) {
return res.json({success: false, error: error});
}
}
** Frontend - pageA **
import Router from 'next/router';
const resetPassword = (model) => {
callApiEndPoint(model).then(data) {
// do what you want to do with data
//finally navigate to page c
Router.push('url-to-page-c');
});
};
return <button onClick={resetPassword}> Reset </button>
Answer 2: If you require redirecting to the page any how from the API [I think you don't necessary require this], once operation/processing is completed on API end, redirect the user directly to the pageC with some query params with data (if they are not security vulnerable data). e.g. /pagec?token=sometokens&otherinfos=otherinfos and do things on pageC itself. Once completed, remove the query string from the page without refreshing the page.
You have to put /verifyreset at the api folder.
This is what Next.js said in their documentation :
Any file inside the folder pages/api is mapped to /api/* and will be treated as an API endpoint instead of a page.
Reference : https://nextjs.org/docs/api-routes/introduction

Emberjs: Save persistent data from remote API to local storage

I am still a little confused about the way Ember fetches data from remote API and save them in the browser.
So I have created a REST Adapter to get sets of records with an Ajax call, a serializer, and the corresponding model. Suppose they are posts, and I have successfully made a posts index page where the user can click on any post to get into the post detail page.
The Ajax call happens on the index page, and using the Ember inspector, it is clear that all the records are stored in the local store.
But when I click the "back link" which is available on every post detail page, it does redirect to '/posts/' but it seems to make the ajax call again. So all the posts are fetched from the API once again making the page much less responsive.
Here's my questions:
How does that part of Ember work and how do I make Ember simply get the records from the local store without making Ajax call again and again? (unless the user refresh the browser)
If I make a GET request to 'post/1' , no data will be available since in this route no Ajax call should be made. But how do I let the data show? Should I set up another REST adapter for individual post or is it possible to get the record from the local store if an Ajax call has been made?
Hope that makes sense and thanks in advance!
Update:
My post adapter:
App.PostAdapter = DS.RESTAdapter.extend({
findAll: function(store, type, sinceToken) {
var url = 'THE URL TO GET JSON BACK';
return $.getJSON(url).then(function(data) {
return posts;
})
}
});
My Post and Posts routes:
App.PostRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('post', params.postId);
}
})
App.PostsRoute = Ember.Route.extend({
model: function() {
return this.store.find('post');
}
})
Regarding your first question: It depends on the model callback of your route. If you use the all method of the store, the Ajax Request won't be made again. (But: You'd be responsible to get the data the first time around. You way want to sideload it somewhere or may want to call find if all didn't return anything. It depends on your application.
Regarding your second question: The RESTAdapter should be available for single data items as well as for lists. You can implement a model hook in the route using find. If you link-to this route with an object (instead of an ID), this hook won't be called. So the hook would only be called when needed.

How to make sure to add response from server to ember store

I am using ember.js for UI side development of application. For every action we are showing a modal popup and we have forms in that pop ups and then on form submission we have some respective actions which have controllers. Its working fine upto here, if we are making a web service call it is taking the server response and updating respective model js file in store.
So new requirement came that in that form if we give some value in a particular text field then a server call should go and fetch some information. Now the problem is, this value is not updating in store. The html(template) is like any html form and mouseout has some action calling on a particular field. That action is
App.MainFormSubmitActionController = App.ModalController.extend({
needs : 'application',
actions : {
actionToBeCalled : function() {
this.store.find('xyzModel');
//the above line should normally find xyzModal in store if not found then hit server and then update the store too
},
mainFormSubmitAction : function() {
//some task done here
}
}
});
the json object I am getting back from server is :
{"payload":{"xyzModel":{"gmp":25.0,"type":"someType","id":1}},"status":"SUCCESS"}
and the js file is
App.XyzModel = DS.Model.extend({
"type" : DS.attr(),
"gmp" : DS.attr()
});
The server call is happening fine, my problem is why ember store is not getting updated when I am getting a response and model not found error is not there. I google a lot but no one seems to have faced the same problem. What cud have I possibly done wrong.
Ember doesn't expect the model data to be wrapped in anything, what you can do is either change the api behaviour or you can customise your Application or Model serializer like so to extract the model and make it the top level object:
App.XyzModelSerializer = DS.RESTSerializer.extend({
extractSingle: function(store, type, payload, id) {
delete payload.payload.status;
payload = {xyzModel: payload.payload.xyzModel };
return this._super(store, type, payload, id);
}
});
there are other methods for when mutiple records are returned, see http://emberjs.com/api/data/classes/DS.RESTSerializer.html

How do I know my collection already has data using Backbone.JS?

I am developing a site using javascript framework BACKBONE.JS. In my site, There is one Category Selection drop down. Using Backbone collection fetch, I have rendered my category drop down successfully. In my header i have three horizontal menu[image shown below]. User click of the menu(Page navigation is done using backbone routers). My main content of the day will change. The user can filter the content based on the category. My category filter drop down option will not change frequently.
ALL = http://www.Site1.com
MOBILE = http://www.Site1.com/#all/mobile
DESKTOP = http://www.Site1.com/#all/desktop
My Router:
dealapp.AppRouter = Backbone.Router.extend({
routes: {
"": "home",
"all/mobile": "mobile",
"all/descktop": "displayAllVoucher"
},
home: function () {},
mobile: function () {},
desktop: function () {}
});
Success Case
I am loading my site Using "http://www.Site1.com/". The function home will get a call and do the listed action. If i am navigating to some other tab(mobile/desktop), my category drop down displaying.[ Note : i am fetching my category from the server in the home function]
scenario
I am loading my site using "http://www.Site1.com/#all/deal" directly. In this case my category drop down is not rendering , i am getting an empty drop down. I know that i haven't added my category fetch in the other two functions mobile and desktop. If i include the category fetch in mobile and desktop function each time then my category fetch call goes to server and fetches data from server.
My doubt
How do i know if my collection already has data? I want to reuse the already downloaded data. If data not available in the local storage then i need to fetch it from the server.
You can override fetch on the collection. Fetch returns a deferred object, you can store this on the collection itself. If the deferred is null you will call the prototype fetch. The advantage is that in your code you always call fetch, and if the collection has data you return the cached data.
fetch : function(options) {
if(this.deferred){
return this.deferred;
}
this.deferred = Backbone.Collection.prototype.fetch.call(this, options);
return this.deferred;
}
This specific problem was dealt with by others and a few plugins can be found.
The one I am currently using with success is the Thorax framework that adds a few things over Backbone.
For Model and Collection they added isPopulated() and isEmpty() method as can be seen here in their documentation.
They will tell you if there is data in the collection or not. If you don't want to use the entire framework, just copying the code from their Git repository here, would do.
In a few words they solve the problem by overriding fetch to set a property called _fetched to true when the data are fetched.
Another way would be to cache the data. Most of the time this is a good idea, but this depends. In your scenario it could be a good idea to cache it in a localStorage.
A plugin I found that seems to do it's job is Backbone fetch cache.
Description:
This plugin intercepts calls to fetch and stores the results in a
cache object (Backbone.fetchCache._cache). If fetch is called with {
cache: true } in the options and the URL has already been cached the
AJAX call will be skipped.
Yet another version is mentioned in this answer: Caching collections in backbone.js?
As the answerer there said, you could do it similar to this:
var manager = (function(){
var constructors = {
'example': ExampleCollection
};
var collections = {};
return {
getCollection: function(name) {
if(!collections[name]) {
var collection = new constructors[name]();
collection.fetch();
collections[name] = collection;
}
return collections[name];
}
}
})();
Here the manager is responsible for instantiating collections and
fetching them. When you call:
var exampleCollection = manager.getCollection('example');
you get an instance of example collection with data being already
fetched. Whenever you need this collection again you can call the
method again. You will then get the exact same instance with no need
to fetch it again.

BackboneJS: Load more items into a collection

In Backbone JS when I fetch a collection should I be fetching the entire collection or a small portion of it?
For example I have news feed collection in mongoDB that could have potentially 1000s of items. When the user hits the page I only want to show them the latest 10 items with the option to 'Load More'. But if they visit a specific item via URL http://site.com/#/feed/:itemID I want to be able to pull up that item's record.
1. How many document should I be fetching initially?
2. How would I got about fetching any item by id?
I ended up using the {add: true} statement when calling fetch on my collection. This prevents the collection from being replaced by the result of the fetch and but instead appends the result to the collection. I then also passed the 'skip' amount using the {data: {skip: amountOfItemsInCollectionAlready }, this is used on the server-side to get the correct batch of items from the database.
My final fetch method looks like this:
loadMore: function(e){
this.collection.fetch({
add: true,// this adds to collection instead of replacing
data:{// this is optional params to be sent with request
skip: this.collection.length// skip the number of items already in the collection
}
});
}
You probably don't want to just use Collection.fetch(), because you won't get the benefit of client-side caching - it'll drop the items you've already loaded from the server and reset the collection. You will probably need to extend Backbone.Collection with a custom function to retrieve more items. I used the following code in a recent project:
Backbone.Collection.extend({
// fetch list without overwriting existing objects (copied from fetch())
fetchNew: function(options) {
options = options || {};
var collection = this,
success = options.success;
options.success = function(resp, status, xhr) {
_(collection.parse(resp, xhr)).each(function(item) {
if (!collection.get(item.id)) {
collection.add(item, {silent:true});
}
});
if (!options.silent) collection.trigger('reset', collection, options);
if (success) success(collection, resp);
};
return (this.sync || Backbone.sync).call(this, 'read', this, options);
}
});
This is mostly copied from the default fetch() code, but instead of dropping existing items it will add new ones. You'd probably want to implement something server-side, using the options object as Julien suggests to pass in the parameters of what items you want to load, probably either a page number (if you want to control page size on the server) or a start-stop pair (if you want to control it on the client).
1 - You should be fetching 10
Add a page argument to your collection and have the backend code return the page matching (10/page). /my_objects?page=2 to get records 10-20 etc.
You do this like this (untested):
collection.fetch({data: {page:2}})
Or you alter the URL directly
2 - To fetch an item by ID you create the model
object = new Model({id: 1})
and fetch it
object.fetch()

Categories

Resources