How should i design javascript pagination component by async?
I want to synchronized http request/respone.
Because the pagination component must need total data count to figure out page count when component was created and initialized(like in constructer).
So the total data count is in my web server(or db) so i http requested total data count. But it was async api, so the code will not blocked when i request total data count to server. It must be blocked and dispose with synchronize.(because if component can't know total data count, then it can't initialized.
Below is my component code.
function ComponentPagination(paginationAreaID, pageViewDataCount ,
getTotalDataCountApiUrl)
{
this.OnUpdatePagingCallBack = null;
//div
var paginationArea = document.getElementById(paginationAreaID);
paginationArea.classList.add("component-pagination-area");
//prev button
var prevBtn = document.createElement("a");
prevBtn.textContent = "Prev";
paginationArea.appendChild(prevBtn);
//page buttons
var totalDataCount = getTotalDataCount();
if (totalDataCount == null) //If error occur
totalDataCount = 0;
var pageCount = (totalDataCount / pageViewDataCount);
for (var i = 0; i < pageCount; ++i) {
var pageBtn = document.createElement("a");
pageBtn.textContent = (i + 1) + "";
pageBtn.addEventListener("click", function (event) {
if (this.OnUpdatePagingCallBack == null) {
console.log("[Component Pagination Error] You must register
update paging callback function!");
return;
}
var curClickedPage = event.target.textContent
axios.get(getTotalDataCountApiUrl)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log("[Component Pagination Error] " + error);
});
}.bind(this));
paginationArea.appendChild(pageBtn);
}
//Next Button
var nextBtn = document.createElement("a");
nextBtn.textContent = "Next"
paginationArea.appendChild(nextBtn);
/* Private Function */
function getTotalDataCount()
{
this.totalDataCount = 0;
axios.get('/API/TestAPI/GetTotalFileCount') //hard coded for test
.then(function (response) {
this.totalDataCount = response.data;
}.bind(this))
.catch(function (error) {
console.log("[Component Pagination Error] " + error);
});
return this.totalDataCount;
}
/* Call Back */
this.registerUpdatePagingCallBack = function(onUpdatePagingMethod)
{
if (typeof onUpdatePagingMethod != "function")
{
console.log("[Component Pagination Error] You must pass only function
to registerUpdatePagingCallBack method's parameter");
return;
}
this.OnUpdatePagingCallBack = onUpdatePagingMethod;
}
}
And then i use this like below in window.onload = function().
var fileListPaginationer = new ComponentPagination("blocked-file-view-
pagination-area", 10, "/API/TestAPI/GetTotalFileCount");
fileListPaginationer.registerUpdatePagingCallBack(function ()
{
/* This function will call when paging button clicked or updated.
Parameters will passed like (clickedPageIndex)
User dispose real pagination data like file list table...(etc)
*/
});
JavaScript developers usually say that java script event or request must be run with async, but i have no idea that i can't know total data count in component initializing time.
Well... maybe show loading... message until request/response total data count will be the way of this problem...
If Possible i want to initialize pagination component in initialize time.
(like window.onload)
(ps: I think the loading message is a little bit burdensome.)
May i have some suggestion about design this pagination component?
I had never designed library or component with async.
Related
my goal is to render some user data from API https://reqres.in/api/users?page=( this can be 1,2 or more if pages are available) and output it to a html table using JS / Promises. So initially I have managed to get the first page's data to the table and now I need it to be modified when I click "Load More" button it should delete the current data on the table and shows the page 2's data. This is my code so far
let userDataTable = document.getElementById("userData");
var tot_pages;
let pagesCount = 1;
console.log(tot_pages);
let getUSerInfo = function () {
fetch(`https://reqres.in/api/users?page=${pagesCount}`)
.then((response) => response.json())
.then((people) => {
let users = people.data;
tot_pages = people.total_pages;
console.log(users);
console.log(tot_pages);
for (let i = 0; i < users.length; i++) {
let htmlContent = `<tr><td>${users[i].id}</td><td><img src="${users[i].avatar}"/></td><td>${users[i].first_name}</td><td>${users[i].last_name}</td><td>${users[i].email}</td></tr>`;
userDataTable.insertAdjacentHTML("beforeend", htmlContent);
}
})
.catch(function (error) {
console.log(error);
});
};
getUSerInfo();
console.log(tot_pages);
document
.getElementById("load_btn")
.addEventListener("click", () => getUSerInfo());
also when there are no pages left to load (ex: when the last page's data is showing in the table then the "Load More" button should not be visible)
I'll explain what my idea was achieving this task : I was trying to create a global variable(tot_pages) initializing it to 1. Then inside the promise I was trying to assign the total pages from the object which I render via reqres.in and return it to the button as a counter or something. So as an ex : When I click the button the counter will increase(in this case the tot_pages variable will increase). Anyway after hours of trying on my own could not get this done yet. I do really appreciate if someone can help me out. Thank you for you time and consideration!
I think your code will work fine with you updating pagesCount variable on each successful API fetch, check this updated code. I've changed some variable names
let totalPages,
currentPage = 0,
loadMoreBtn = document.getElementById("load_btn");
// bind load more button
loadMoreBtn.addEventListener("click",getUSerInfo);
// fetch people
function getUSerInfo() {
// ignore if all data has been loaded
if(currentPage >= totalPages) return
const nextPage = currentPage + 1;
fetch(`https://reqres.in/api/users?page=${nextPage}`)
.then((response) => response.json())
.then((people) => {
const users = people.data,
userDataTable = document.getElementById("userData");
totalPages = people.total_pages;
// hide load more button
if(totalPages == nextPage) loadMoreBtn.style.visibility = 'hidden';
// udate table data
for (let i = 0; i < users.length; i++) {
let htmlContent = `<tr><td>${users[i].id}</td><td><img src="${users[i].avatar}"/></td><td>${users[i].first_name}</td><td>${users[i].last_name}< /td><td>${users[i].email}</td></tr>`;
userDataTable.insertAdjacentHTML("beforeend", htmlContent);
}
currentPage = nextPage;
})
.catch(function (error) {
console.log(error);
});
};
// fetch initial page
getUSerInfo();
EDIT: ANSWER BELOW
I'm making my first JavaScript project and decided to make a simple weather app. It fetches weather data of a city you put in from the openweathermap.org api and displays it in a table. I firstly made it using fetch() and .then. I then learned about async functions and the await keyword. After converting the script to an asynchronous function, I came across a problem. If the first city you enter isn't a real city (an error is catched while fetching the api), the warning message appears, BUT the table also appears because the rest of the function still executes.
So my question is: how can I stop the async function if any errors are catched?
Here's the website: https://lorenzo3117.github.io/weather-app/
Here's the code:
// Launch weather() function and catch any errors with the api request and display the warning message if there are any errors
function main() {
weather().catch(error => {
document.querySelector("#warningMessage").style.display = "block";
console.log(error);
});
}
// Main function
async function weather() {
// Take city from input and reset input field
var city = document.querySelector("#cityInput").value;
document.querySelector("#cityInput").value = "";
// Get api response and make it into a Json
const apiResponse = await fetch("https://api.openweathermap.org/data/2.5/weather?q=" + city + "&appid=<apiKey>&units=metric");
const jsonData = await apiResponse.json();
// Removes warning message
document.querySelector("#warningMessage").style.display = "none";
// Puts the Json into an array and launches createTable function
var arrayJson = [jsonData];
createTable(document.querySelector("#table"), arrayJson);
// Function to create the table
function createTable(table, data) {
// Makes the table visible
document.querySelector("#table").style.display = "block";
// Goes through the array and makes the rows for the table
for (let i = 0; i < data.length; i++) {
let rowData = data[i];
var row = table.insertRow(table.rows.length);
// This var exists to make the first letter capitalized without making a gigantic line (see insertCell(3), line 53)
// Could be made into a function if needed
var weatherDescription = rowData.weather[0].description;
// Take latitude and longitude for google maps link
var lat = rowData.coord.lat;
var long = rowData.coord.lon;
// Make an a-tag for link to google maps
var mapLink = document.createElement("a");
mapLink.innerHTML = "Link";
mapLink.target = "_blank";
mapLink.href = "https://www.google.com/maps/search/?api=1&query=" + lat + "," + long;
// Making rows in table
row.insertCell(0).innerHTML = rowData.name + ", " + rowData.sys.country;
row.insertCell(1).innerHTML = rowData.main.temp + " °C";
row.insertCell(2).innerHTML = rowData.main.humidity + "%";
row.insertCell(3).innerHTML = weatherDescription.charAt(0).toUpperCase() + weatherDescription.slice(1);
row.insertCell(4).appendChild(mapLink); // appendChild for anchor tag because innerHTML only works with text
}
}
And the repo: https://github.com/lorenzo3117/weather-app
Thank you
you can do this :
async function weather() {
try {
const apiResponse = await fetch("https://api.openweathermap.org/data/2.5/weather?q=" + city + "&appid=02587cc48685af80ea225c1601e4f792&units=metric");
} catch(err) {
alert(err); // TypeError: failed to fetch
return;
}
}
weather();
Actually, the error catched isn't an error with the api itself because the api still sends a json, but the error is catched while trying to read a certain object from the json (which doesn't exist because the json isn't a normal one with weather data). Therefore the function stops far later than expected, after the table was made visible.
I just put the line that made the table visible after the function that creates the table (after where the real error occurs). Also thanks #Dadboz for the try catch method which made the code even more compact. I also added an if else to check if the json file is the correct one so unnecessary code doesn't get executed. Thanks #James for pointing this out to me.
Here's the final code:
// Main function
async function weather() {
try {
// Take city from input and reset input field
var city = document.querySelector("#cityInput").value;
document.querySelector("#cityInput").value = "";
// Get api response and make it into a Json
const apiResponse = await fetch("https://api.openweathermap.org/data/2.5/weather?q=" + city + "&appid=<apiKey>&units=metric");
const jsonData = await apiResponse.json();
if (jsonData.message == "city not found") {
document.querySelector("#warningMessage").style.display = "block";
} else {
// Removes warning message
document.querySelector("#warningMessage").style.display = "none";
// Puts the Json into an array and launches updateTable function
var arrayJson = [jsonData];
updateTable(document.querySelector("#table"), arrayJson);
}
}
catch (error) {
console.log(error);
}
}
// Function to update the table
function updateTable(table, data) {
// Goes through the array and makes the rows for the table
for (let i = 0; i < data.length; i++) {
let rowData = data[i];
var row = table.insertRow(table.rows.length);
// This var exists to make the first letter capitalized without making a gigantic line (see insertCell(3), line 53)
// Could be made into a function if needed
var weatherDescription = rowData.weather[0].description;
// Take latitude and longitude for google maps link
var lat = rowData.coord.lat;
var long = rowData.coord.lon;
// Make an a-tag for link to google maps
var mapLink = document.createElement("a");
mapLink.innerHTML = "Link";
mapLink.target = "_blank";
mapLink.href = "https://www.google.com/maps/search/?api=1&query=" + lat + "," + long;
// Making rows in table
row.insertCell(0).innerHTML = rowData.name + ", " + rowData.sys.country;
row.insertCell(1).innerHTML = rowData.main.temp + " °C";
row.insertCell(2).innerHTML = rowData.main.humidity + "%";
row.insertCell(3).innerHTML = weatherDescription.charAt(0).toUpperCase() + weatherDescription.slice(1);
row.insertCell(4).appendChild(mapLink); // appendChild for anchor tag because innerHTML only works with text
}
// Makes the table visible
document.querySelector("#table").style.display = "block";
}
Thanks everyone for your answers, have a good day!
Lorenzo
Im trying to make a webscraper(educational puposes), and I got really far, but this little issue is bugging me.
I made a request callback function, and im trying to get lines 75-78 to work. However to get this to work, I need PDF_LISTS and PDF_LINKS to initilaze to the right values.
I've already tried to make them global variables, and what not, for some reason that doesnt work. So my question is: How do I make a callback function that will call that for loop (75-78) and succesfully initilaze PDF_LISTS and PDF_LINKS to the correct values ?
(Dont worry I use this on educational content, with the prof's permission). First time posting here!
// URL_LINKS has the pdf links of the pages
PDF_LINKS = [];
// URL_LIST has the names of the pdf links
PDF_LIST = [];
function fillPDF(callback) {
request(url, function(err, res, body) {
$ = cheerio.load(body);
links = $('a'); //jquery get all hyperlinks
$(links).each(function(i, link) {
var value = $(link).attr('href');
// creates objects to hold the file
if (value.substring(value.length - 3, value.length) == "pdf") {
PDF_LINKS[i] = $(link).attr('href');
PDF_LIST[i] = $(link).text();
}
})
});
}
// must decleare fillPDF variable or else you wont initilze teh variables
fillPDF() {
//HERE I WANT PDF_LINKS and PDF_LIST to be intialized to 33.....
}
for (j = 0; j < PDF_LIST.length; j++) {
request(PDF_LINKS[j]).pipe(fs.createWriteStream(PDF_LIST[j]));
}
You may push your values into arrays using array's push method, avoiding array's element to be undefined.
You can put your final for loop into a function, and then use fillPDF();
You also need to call fillPDF's callback once the request is over.
PDF_LINKS = [];
PDF_LIST = [];
function fillPDF(callback) {
request(url, function(err, res, body) {
$ = cheerio.load(body);
links = $('a');
$(links).each(function(i, link) {
var value = $(link).attr('href');
if (value.slice(-3) == "pdf") {
PDF_LINKS.push(value);
PDF_LIST.push($(link).text());
}
})
callback();
});
}
function writePDF() {
for (j = 0; j < PDF_LIST.length; j++) {
request(PDF_LINKS[j]).pipe(fs.createWriteStream(PDF_LIST[j]));
}
}
fillPDF(writePDF);
I have an issue related to database. I am currently working with Gupshup bot programming. There are two different data persistence modes which can be read here and here. In the advanced data persistence, the following code is documented to put data into data base:
function MessageHandler(context, event) {
if(event.message=='update bug - 1452') {
jiraUpdate(context);
}
}
function jiraUpdate(context){
//connect to Jira and check for latest update and values
if(true){
context.simpledb.doPut("1452" ,"{\"status\":\"QA pending\",\"lastUpdated\":\"06\/05\/2016\",\"userName\":\"John\",\"comment\":\"Dependent on builds team to provide right build\"}");
} else{
context.sendResponse('No new updates');
}
}
function DbPutHandler(context, event) {
context.sendResponse("New update in the bug, type in the bug id to see the update");
}
If I want to change only one of column (say status or last Updated) in the table for the row with key value 1452, I am unable to do that. How can that be done?
I used the following code:
function MessageHandler(context, event) {
// var nlpToken = "xxxxxxxxxxxxxxxxxxxxxxx";//Your API.ai token
// context.sendResponse(JSON.stringify(event));
if(event.message=='deposit') {
context.sendResponse("Enter the amount to be deposited");
}
if(event.message=="1000") {
jiraUpdate(context);
}
if(event.message== "show"){
context.simpledb.doGet("1452");
}
}
function HttpResponseHandler(context, event) {
var dateJson = JSON.parse(event.getresp);
var date = dateJson.date;
context.sendResponse("Today's date is : "+date+":-)");
}
function jiraUpdate(context){
//connect to Jira and check for latest update and values
if(true){
context.simpledb.doPut("aaa" ,"{\"account_number\":\"90400\",\"balance\":\"5800\"}");
} else{
context.sendResponse('No new updates');
}
}
/** Functions declared below are required **/
function EventHandler(context, event) {
if (!context.simpledb.botleveldata.numinstance)
context.simpledb.botleveldata.numinstance = 0;
numinstances = parseInt(context.simpledb.botleveldata.numinstance) + 1;
context.simpledb.botleveldata.numinstance = numinstances;
context.sendResponse("Thanks for adding me. You are:" + numinstances);
}
function DbGetHandler(context, event) {
var bugObj = JSON.parse(event.dbval);
var bal = bugObj.balance;
var acc = bugObj.account_number;
context.sendResponse(bal);
var a = parseInt (bal,10);
var b = a +1000;
var num = b.toString();
context.simpledb.doPut.aaa.balance = num;
}
function DbPutHandler(context, event) {
context.sendResponse("testdbput keyword was last put by:" + event.dbval);
}
Since the hosted DB that is provided by Gupshup is the DynamoDB of AWS. Hence you can enter something as a key, value pair.
Hence you will have to set the right key while using doPut method to store data into the database and use the same key to get the data from the database using the doGet method.
To update the data you should first call doGet method and then update the JSON with right data and then call doPut method to update the database with the latest data.
I have also added something which is not present in the documentation, You can now make DB calls and choose which function the response goes to.
I am refactoring your example as using 3 keywords and hard coding few things just for example -
have - this will update the database with these values
{"account_number":"90400","balance":"5800"}
deposit - on this, the code will add 1000 to the balance
show - on this, the code show the balance to the user.
Code -
function MessageHandler(context, event) {
if(event.message=='have') {
var data = {"account_number":"90400","balance":"5800"};
context.simpledb.doPut(event.sender,JSON.stringify(data),insertData); //using event.sender to keep the key unique
return;
}
if(event.message=="deposit") {
context.simpledb.doGet(event.sender, updateData);
return;
}
if(event.message== "show"){
context.simpledb.doGet(event.sender);
return;
}
}
function insertData(context){
context.sendResponse("I have your data now. To update just say \"deposit\"");
}
function updateData(context,event){
var bugObj = JSON.parse(event.dbval);
var bal = bugObj.balance;
var a = parseInt(bal,10);
var b = a + 1000;
var num = b.toString();
bugObj.balance = num;
context.simpledb.doPut(event.sender,bugObj);
}
function EventHandler(context, event) {
if (!context.simpledb.botleveldata.numinstance)
context.simpledb.botleveldata.numinstance = 0;
numinstances = parseInt(context.simpledb.botleveldata.numinstance) + 1;
context.simpledb.botleveldata.numinstance = numinstances;
context.sendResponse("Thanks for adding me. You are:" + numinstances);
}
function DbGetHandler(context, event) {
var accountObj = JSON.parse(event.dbval);
context.sendResponse(accountObj);
}
function DbPutHandler(context, event) {
context.sendResponse("I have updated your data. Just say \"show\" to view the data.");
}
QUESTION:
How to implement efficient infinite scrolling in Firebase using javascript (and node.js) ?
WHAT I CHECKED:
Implementing Infinite Scrolling with Firebase?
Problem: older firebase ^
Infinite scroll with AngularJs and Firebase
CODE FROM:
Infinite scroll with AngularJs and Firebase
"First, I recommend to create an Index in your Firebase. For this answer, I create this one:
{
"rules": {
".read": true,
".write": false,
"messages": {
".indexOn": "id"
}
}
}
Then, let's make some magic with Firebase:
// #fb: your Firebase.
// #data: messages, users, products... the dataset you want to do something with.
// #_start: min ID where you want to start fetching your data.
// #_end: max ID where you want to start fetching your data.
// #_n: Step size. In other words, how much data you want to fetch from Firebase.
var fb = new Firebase('https://<YOUR-FIREBASE-APP>.firebaseio.com/');
var data = [];
var _start = 0;
var _end = 9;
var _n = 10;
var getDataset = function() {
fb.orderByChild('id').startAt(_start).endAt(_end).limitToLast(_n).on("child_added", function(dataSnapshot) {
data.push(dataSnapshot.val());
});
_start = _start + _n;
_end = _end + _n;
}
Finally, a better Infinite Scrolling (without jQuery):
window.addEventListener('scroll', function() {
if (window.scrollY === document.body.scrollHeight - window.innerHeight) {
getDataset();
}
});
I'm using this approach with React and it's blazing fast no matter how big your data is."
(answered Oct 26 '15 at 15:02)
(by Jobsamuel)
PROBLEM
In that solution, n posts will be loaded each time the scroll reaches the end of the height of screen.
Depending on screen sizes, this means a lot more posts than needed will be loaded at some point (the screen height only contains 2 posts, which means 3 more posts than necessary will be loaded each time we reach the end of the screen height when n = 5 for example).
Which means 3*NumberOfTimesScrollHeightHasBeenPassed more posts than needed will be loaded each time we reach the end of the scrollheight.
MY CURRENT CODE (loads all posts at once, no infinite scrolling):
var express = require("express");
var router = express.Router();
var firebase = require("firebase");
router.get('/index', function(req, res, next) {
var pageRef = firebase.database().ref("posts/page");
pageRef.once('value', function(snapshot){
var page = [];
global.page_name = "page";
snapshot.forEach(function(childSnapshot){
var key = childSnapshot.key;
var childData = childSnapshot.val();
page.push({
id: key,
title: childData.title,
image: childData.image
});
});
res.render('page/index',{page: page});
});
});
Here is full code for infinite paging.
The function createPromiseCallback is for supporting both promises and callbacks.
function createPromiseCallback() {
var cb;
var promise = new Promise(function (resolve, reject) {
cb = function (err, data) {
if (err) return reject(err);
return resolve(data);
};
});
cb.promise = promise;
return cb;
}
The function getPaginatedFeed implements actual paging
function getPaginatedFeed(endpoint, pageSize, earliestEntryId, cb) {
cb = cb || createPromiseCallback();
var ref = database.ref(endpoint);
if (earliestEntryId) {
ref = ref.orderByKey().endAt(earliestEntryId);
}
ref.limitToLast(pageSize + 1).once('value').then(data => {
var entries = data.val() || {};
var nextPage = null;
const entryIds = Object.keys(entries);
if (entryIds.length > pageSize) {
delete entries[entryIds[0]];
const nextPageStartingId = entryIds.shift();
nextPage = function (cb) {
return getPaginatedFeed(endpoint, pageSize, nextPageStartingId, cb);
};
}
var res = { entries: entries, nextPage: nextPage };
cb(null, res);
});
return cb.promise;
}
And here is how to use getPaginatedFeed function
var endpoint = '/posts';
var pageSize = 2;
var nextPage = null;
var dataChunk = null;
getPaginatedFeed(endpoint, pageSize).then(function (data) {
dataChunk = data.entries;
nextPage = data.nextPage;
//if nexPage is null means there are no more pages left
if (!nextPage) return;
//Getting second page
nextPage().then(function (secondpageData) {
dataChunk = data.entries;
nextPage = data.nextPage;
//Getting third page
if (!nextPage) return;
nextPage().then(function (secondpageData) {
dataChunk = data.entries;
nextPage = data.nextPage;
//You can call as long as your nextPage is not null, which means as long as you have data left
});
});
});
What about the question how many items to put on the screen, you can give a solution like this, for each post give fixed x height and if it requires more space put "read more" link on the bottom of the post which will reveal missing part when user clicks. In that case you can keep fixed count of items on your screen. Additionally you can check screen resolution in order to put more or less items.