I need to know how to add pagination with vue.js? - node.js

I Have html+javascript that requests from mongodb database some games(game1,2,3,4,5,6)just simple database with alot of games.
I want to know how via vue.js i can do pagination that per page show 4games.?
const SEARCH = new Vue({
el: '#search',
data: {
query: {
name: '',
max_price:0,
game_category:'',
game_publisher:'',
},
games: [] // current list of games. we re-fill this array after search
},
methods: {
btn_search: function () {
// now we know that this.query is our search critearia object
// so we can do fetch, and will do.
fetch('/search?json=' + JSON.stringify(this.query))
.then((response) => { //as you remember - res is a buffer.
return response.text();
})
.then((text_response) => {
console.log('got response!');
let games_from_server = JSON.parse(text_response);
this.games.splice(0, this.games.length); //it will remove all elemtns from array remove all elemtns from array
// and add games from server one by one.
for (let i = 0; i < games_from_server.length; i++) {
this.games.push(games_from_server[i]);
}
});
console.log(this.query);
}
}
});
console.log('pew?');

If you want to do a client-side pagination you can do it this way:
In your data add currentPage: 1 and gamesPerPage:
data() {
return {
currentPage: 1,
gamesPerPage: 4,
games: []
}
}
then add a computed property paginatedGames which is your games property split into pages, a currentPageGames property which filters games in current page and changePage method which changes your page:
computed: {
paginatedGames() {
let page = 1;
return [].concat.apply(
[],
this.games.map( (game, index) =>
index % this.gamesPerPage ?
[] :
{ page: page++, games: this.games.slice(index, index + this.gamesPerPage)}
)
);
},
currentPageGames() {
let currentPageGames = this.paginatedGames.find(pages => pages.page == this.currentPage);
return currentPageGames ? currentPageGames.games : [];
}
},
methods {
changePage(pageNumber) {
if(pageNumber !== this.currentPage)
this.currentPage = pageNumber;
}
}
Complete example: http://jsfiddle.net/eywraw8t/217989/
However, if your database has lots of games, it might be a better idea to implement a server-side pagination and fetch games only for requested page.

Related

Multiple REACT API calls vs handling it all in NodeJS

I am currently using AWS DynamoDB for the backend and REACT for the frontend with a NodeJS/AWS Lambda frame.
What I'm trying to do:
Get the information of the teammates in a dataset from a teams table that contains a list of the partition keys of the profiles table, and the name of the team set as the sort key, to get their specific information for that team. I then need to get each of the teammate's universal information that is true across all teams, namely their name and profile picture.
I have 2 options:
Either loop through the list of partition keys in the team dataset that was fetched in REACT, and make a couple of API calls to retrieve their specific information, then a couple more for their universal information.
Or, I was wondering if I can instead then implement the same logic, but instead of the API calls, it will be DynamoDB querying in the Lambda function, then collect them all into a JSON object for it to be fetched by the REACT in one swoop.
Would it be a better idea? Or am I simply doing the REACT logic wrong? I am relatively new to REACT so it's definitely possible and am open to tips.
For reference, here's the code:
useEffect(() => {
let theTeammates : (userProfileResponse | undefined)[] = teammateObjects;
(async () => {
let teammatesPromises : any[] = [];
// teammates = the list of partition keys
for (let i = 0; i < teammates.length; i++) {
if(teammates[i] !== '') {
teammatesPromises.push(getSpecificUser(teammates[i], teamName))
}
}
await Promise.all(teammatesPromises)
.then((resolved) => {
if(resolved) {
theTeammates = resolved.map( (theTeammate) => {
if(theTeammate) {
let user: userProfileResponse = {
userKey: theTeammate.userKey,
teamKey: theTeammate.teamKey,
description: theTeammate.description,
userName: theTeammate.userName,
profilePic: defaultProfile,
isAdmin: theTeammate.isAdmin,
role: theTeammate.role,
}
return user as userProfileResponse;
}
})
}
})
setTeammateObjects(theTeammates as userProfileResponse[]);
}) ();
}, [teamData]);
useEffect(() => {
if(teammateObjects) {
(async () => {
let Teammates : any[] = teammateObjects.filter(x => x !== undefined)
Teammates = Teammates.map( (aTeammate) => {
getProfilePicAndName(aTeammate.userKey).then((profile) => {
if(profile) {
aTeammate.userName = profile.userName;
if(profile.profilePic !== undefined && profile.profilePic !== 'none') {
getProfilePic(profile.profilePic).then((picture) => {
if(picture) {
aTeammate.profilePic = picture
return aTeammate
}
})
} else
return aTeammate
}
})
})
}) ();
}
}, [teammateObjects])
I am currently trying to do it through the react. It works for the most part, but I have noticed that sometimes some of the API calls fail, and some of the teammates don't get fetched successfully and never get displayed to the user until the page is refreshed, which is not acceptable.

How to loop in useEffect and setState

I have categories and every category has subcategories, and i want to setState a dictionary like
{category1.name: [/*list of all subcategories*/], category2.name: [...]}
and i have
useEffect(()=>{
Axios.get("http://localhost:3001/categories/get").then((p) => { /* return a list of categories */
setCategories(p.data)
let actual = {}
for (let i = 0; i < p.data.length; i++) {
Axios.get("http://localhost:3001/category/subcategories", { /* return all the subcategories of p.data[i], this is working! */
params : {category : p.data[i].category}}).then((p) => {
actual = {...actual, [p.data[i].category]: p.data}
console.log(actual) /* 1 */
})
}
console.log(actual) /* 2 */
})
}, [])
I already have the backend working, but when i console.log(actual) /* 2 */ its just a empty dictionary {}; and when i console.log(actual) /* 1 */ its not empty (has just 1 element), and i don't know when can i setState(actual), someone know how to do what i want?
You can just use async/await to works like what you need.
EDIT (mentioned by #skyboyer):
When you do a network call all requests will go sequentially and loading will take NĂ—time_to_load_one
Try this out.
useEffect(()=>{
Axios.get("http://localhost:3001/categories/get").then(async (p) => { /* return a list of categories */
setCategories(p.data)
let actual = {}
for (let i = 0; i < p.data.length; i++) {
await Axios.get("http://localhost:3001/category/subcategories", { /* return all the subcategories of p.data[i], this is working! */
params : {category : p.data[i].category}}).then((p) => {
actual = {...actual, [p.data[i].category]: p.data}
console.log(actual) /* 1 */
})
}
console.log(actual) /* 2 */
})
}, [])
This should probably get you on the right track
useEffect(() => {
(async() => {
const p = await Axios.get("http://localhost:3001/categories/get")
setCategories(p.data)
let actual = {}
for (let i = 0; i < p.data.length; i++) {
const x = await Axios.get("http://localhost:3001/category/subcategories", { /* return all the subcategories of p.data[i], this is working! */
params: {
category: p.data[i].category
}
})
actual = {...actual, [x.data[i].category]: x.data
}
console.log(actual) /* 1 */
}
console.log(actual) /* 2 */
})();
}, [])
Also avoid reusing the same variable like you did with p it makes reading the code harder.
It seems to me that you need to spend time learning about promise or async/await in JS. Forgive me that I cannot spend time writing a whole article to teach everything, please google these keywords.
To complement #HarshPatel's answer which uses async/await, below is a solution using promise. His solution makes requests sequentially, while mine does it concurrently.
useEffect(() => {
Axios.get("http://localhost:3001/categories/get").then((p) => {
/* return a list of categories */
setCategories(p.data)
const tasks = p.data.map((category) => {
return Axios.get(
"http://localhost:3001/category/subcategories",
{ params: { category } }
).then((p) => {
return { [category]: p.data }
})
})
Promise.all(tasks).then(results => {
const actual = results.reduce((acc, item) => {
return { ...acc, ...item }
}, {})
})
setState(actual)
})
}, [])

Elasticsearch node js point in time search_phase_execution_exception

const body = {
query: {
geo_shape: {
geometry: {
relation: 'within',
shape: {
type: 'polygon',
coordinates: [$polygon],
},
},
},
},
pit: {
id: "t_yxAwEPZXNyaS1wYzYtMjAxN3IxFjZxU2RBTzNyUXhTUV9XbzhHSk9IZ3cAFjhlclRmRGFLUU5TVHZKNXZReUc3SWcAAAAAAAALmpMWQkNwYmVSeGVRaHU2aDFZZExFRjZXZwEWNnFTZEFPM3JReFNRX1dvOEdKT0hndwAA",
keep_alive: "1m",
},
};
Query fails with search_phase_execution_exception at onBody
Without pit query works fine but it's needed to retrieve more than 10000 hits
Well, using PIT in NodeJS ElasticSearch's client is not clear, or at least is not well documented. You can create a PIT using the client like:
const pitRes = await elastic.openPointInTime({
index: index,
keep_alive: "1m"
});
pit_id = pitRes.body.id;
But there is no way to use that pit_id in the search method, and it's not documented properly :S
BUT, you can use the scroll API as follows:
const scrollSearch = await elastic.helpers.scrollSearch({
index: index,
body: {
"size": 10000,
"query": {
"query_string": {
"fields": [ "vm_ref", "org", "vm" ],
"query": organization + moreQuery
},
"sort": [
{ "utc_date": "desc" }
]
}
}});
And then read the results as follows:
let res = [];
try {
for await (const result of scrollSearch) {
res.push(...result.body.hits.hits);
}
} catch (e) {
console.log(e);
}
I know that's not the exact answer to your question, but I hope it helps ;)
The usage of point-in-time for pagination of search results is now documented in ElasticSearch. You can find more or less detailed explanations here: Paginate search results
I prepared an example that may give an idea about how to implement the workflow, described in the documentation:
async function searchWithPointInTime(cluster, index, chunkSize, keepAlive) {
if (!chunkSize) {
chunkSize = 5000;
}
if (!keepAlive) {
keepAlive = "1m";
}
const client = new Client({ node: cluster });
let pointInTimeId = null;
let searchAfter = null;
try {
// Open point in time
pointInTimeId = (await client.openPointInTime({ index, keep_alive: keepAlive })).body.id;
// Query next chunk of data
while (true) {
const size = remained === null ? chunkSize : Math.min(remained, chunkSize);
const response = await client.search({
// Pay attention: no index here (because it will come from the point-in-time)
body: {
size: chunkSize,
track_total_hits: false, // This will make query faster
query: {
// (1) TODO: put any filter you need here (instead of match_all)
match_all: {},
},
pit: {
id: pointInTimeId,
keep_alive: keepAlive,
},
// Sorting should be by _shard_doc or at least include _shard_doc
sort: [{ _shard_doc: "desc" }],
// The next parameter is very important - it tells Elastic to bring us next portion
...(searchAfter !== null && { search_after: [searchAfter] }),
},
});
const { hits } = response.body.hits;
if (!hits || !hits.length) {
break; // No more data
}
for (hit of hits) {
// (2) TODO: Do whatever you need with results
}
// Check if we done reading the data
if (hits.length < size) {
break; // We finished reading all data
}
// Get next value for the 'search after' position
// by extracting the _shard_doc from the sort key of the last hit
searchAfter = hits[hits.length - 1].sort[0];
}
} catch (ex) {
console.error(ex);
} finally {
// Close point in time
if (pointInTime) {
await client.closePointInTime({ body: { id: pointInTime } });
}
}
}

How can I retrieve a SharePoint list items attachments using spfx?

I've managed to figure out how to submit multiple attachments to a sharepoint list item.
I now need to retrieve the item and display these items in the same form that was submitted.
Here's the submit code:
private _onSubmit() {
this.setState({
FormStatus: 'Submitted',
SubmittedLblVis: true,
}, () => {
pnp.sp.web.lists.getByTitle("My List").items.add({
State: this.state.State,
State1: this.state.State1,
}).then((iar: ItemAddResult) => {
var attachments: AttachmentFileInfo[] = [];
attachments.push({
name: this.state.FileUpload[0].name,
content: this.state.FileUpload[0]
});
attachments.push({
name: this.state.FileUpload2[0].name,
content: this.state.FileUpload2[0]
});
attachments.push({
name: this.state.FileUpload3[0].name,
content: this.state.FileUpload3[0]
});
iar.item.attachmentFiles.addMultiple(attachments);
This works great.
I have a button the form that allows the user to read an item and populate all the fields in the form. This works fine. But it's not working for the attachments. First thing is I don't know what the Attachments column is called!
Here's the retrieval function:
private _editItem = (ev: React.MouseEvent<HTMLElement>) => {
const sid = Number(ev.currentTarget.id);
let _item = this.state.Items.filter((item) => { return item.Id === sid; });
if (_item && _item.length > 0) {
this._getListItems();
this.setState({
State etc...with a few examples
FormStatus: _item[0].FormStatus,
showModal: true
//The below callback function
}, () => {
if (_item[0].PanelMember) {
this.PanelMemberGetPeoplePicker(Number(_item[0].PanelMemberId));
}
});
}
}
And the _getListItems() function within the above:
public _getListItems() {
sp.web.lists.getByTitle("MyList").items.get().then((items: any[]) => {
let returnedItems: MyDataModel[] = items.map((item) => { return new MyDataModel(item); });
this.setState({ Items: returnedItems });
});
}
I understand that I'll have to update the MyDataModel interface with whatever the attachment column is but what is the attachment column? And how would I implement it within the above to retrieve all 3 attached documents?
Get the item first, then get item attachment files.
let item=sp.web.lists.getByTitle("TestList").items.getById(13);
item.attachmentFiles.get().then((files)=>{
console.log(files);
})

populating menus from multiple collections

I'm new to backbone.js and express and I have been adapting Christophe Coenraets Wine Cellar REST API example application for my own project.
I am building a form that has several menus needing to be populated from multiple unrelated collections in mongodb.
I am able to populate one menu with one collection, but I have no idea how to get more than one collection to my form View.
Here are the files I am using to populate one menu. How do I expand this to populate two menus?
I suppose I could make a new View for every menu I want to populate - but that seems like overkill.
Can I combine two mongodb find() collections into one object, and list them separately on a page? If so, how?
thanks in advance!
/routes/modules.js contains:
exports.findAllModules = function(req, res) {
db.collection('modules', function(err, collection) {
collection.find().toArray(function(err, items) {
res.send(items);
});
});
};
/server.js contains:
app.get('/modules', module.findAllModules);
/public/js/main.js contains:
routes: {
"modules" : "list" }
...
list: function(page) {
var p = page ? parseInt(page, 10) : 1;
var moduleList = new ModuleCollection();
moduleList.fetch({success: function(){
console.log('in list function');
$("#content").html(new ModuleListView({model: moduleList, page: p}).el);
}});
this.headerView.selectMenuItem('home-menu');
},
...
utils.loadTemplate([
'ModuleListItemView' ], function() {
app = new AppRouter();
Backbone.history.start(); });
/public/models/models.js contains:
window.Module = Backbone.Model.extend({
urlRoot: "/modules",
idAttribute: "_id",
initialize: function () {
this.validators = {};
this.validators.name = function (value) {
return value.length > 0 ? {isValid: true} : {isValid: false, message: "You must enter a name"};
};
validateItem: function (key) {
return (this.validators[key]) ? this.validators[key](this.get(key)) : {isValid: true};
},
validateAll: function () {
var messages = {};
for (var key in this.validators) {
if(this.validators.hasOwnProperty(key)) {
var check = this.validators[key](this.get(key));
if (check.isValid === false) {
messages[key] = check.message;
}
}
}
return _.size(messages) > 0 ? {isValid: false, messages: messages} : {isValid: true};
},
defaults: {
_id: null,
name: ""
} });
window.ModuleCollection = Backbone.Collection.extend({
model: Module,
url: "/modules"
});
/public/js/views/modulelist.js contains:
window.ModuleListView = Backbone.View.extend({
initialize: function () {
this.render();
},
render: function () {
var modules = this.model.models;
$(this.el).html('<ul class="thumbnails"></ul>');
for (var i = 0; i < modules.length; i++) {
$('.thumbnails', this.el).append(new ModuleListItemView({model: modules[i]}).render().el);
}
return this;
} });
window.ModuleListItemView = Backbone.View.extend({
tagName: "li",
initialize: function () {
this.model.bind("change", this.render, this);
this.model.bind("destroy", this.close, this);
},
render: function () {
$(this.el).html(this.template(this.model.toJSON()));
return this;
} });
/public/tpl/ModuleListView.html contains:
Not entirely sure how your code works, but here are a few backbone tips.
If you wanna build a menu from a collection don't pass the collection as a model.
Instead of:
$("#content").html(new ModuleListView({model: moduleList, page: p}).el);
Use:
$("#content").empty().append(new ModuleListView({collection: moduleList, page: p}).el);
Instead of:
render: function () {
var modules = this.model.models;
$(this.el).html('<ul class="thumbnails"></ul>');
for (var i = 0; i < modules.length; i++) {
$('.thumbnails', this.el).append(new ModuleListItemView({model: modules[i]}).render().el);
}
return this;
}
Use:
render: function () {
this.$el.html('<ul class="thumbnails">');
this.collection.each(function(model) {
this.$('.thumbnails').append(new ModuleListItemView({model: model}).render().el);
}, this);
return this;
}
If you have no need in updating or deleting your models, it's enough to add the url path /modules only to the collection, for reading the initial modules.

Resources