Handlebars append dynamic content - mysql

How would I go ahead and append data to my existing list of data using Handlebars?
I can call ?page=1 to get the next 10 items presented, but how can I append them to the already existing list?
I already have a function, where I can call some javascript from when the user scrolls to the bottom.
app.get("/", function (req, res) {
let numRows;
const numPerPage = parseInt(req.query.npp, 10) || 10;
const page = parseInt(req.query.page, 10) || 0;
let numPages;
const skip = page * numPerPage;
// Here we compute the LIMIT parameter for MySQL query
const limit = skip + "," + numPerPage;
queryAsync("SELECT count(*) as numRows FROM cars")
.then(function (results) {
numRows = results[0].numRows;
numPages = Math.ceil(numRows / numPerPage);
})
.then(() => queryAsync("SELECT * FROM cars ORDER BY id ASC LIMIT " + limit))
.then(function (results) {
const responsePayload = {
results: results
};
const data = { cars: results };
const template = fs.readFileSync("./index.hbs", "utf8");
const html = handlebars.compile(template)(data);
if (page < numPages) {
responsePayload.pagination = {
current: page,
perPage: numPerPage,
totalPages: numPages,
previous: page > 0 ? page - 1 : undefined,
next: page < numPages - 1 ? page + 1 : undefined
}
}
else responsePayload.pagination = {
err: "queried page " + page + " is >= to maximum page number " + numPages
}
//res.send(responsePayload);
res.send(html);
})
.catch(function (err) {
console.error(err);
res.json({ err: err });
});
});
<div id="cars-wrapper">
<div class="row">
{{#each cars}}
<div class="col s12 m6 l4 xl3">
<div class="car z-depth-1">
<div class="car-header">
<div class="row no-margin">
<div class="col s12 m4 center-align">{{this.id}}</div>
</div>
</div>
</div>
{{/each}}
</div>
</div>

Related

Scraping <meta> content with Cheerio

Tanaike helped me with this amazing script using Cheerio. The original was for Letterboxd, but this one is to pull in a watchlist from Trakt.tv (sample watchlist).
As it is right now it pulls in the watched date and the title, but I'd also like to pull in the content from the meta tag for each item.
<meta content="8 Million Ways to Die (1986)" itemprop="name">
I tried using $('[itemprop="name"]').attr('content'); but it doesn't accept the .attr piece.
Here is the full script as it is now, returning the watched date in the Col1 and the title in Col2.
/**
* Returns Trakt watchlist by username
* #param pages enter the number of pages in the list. Default is 10
* #customfunction
*/
function TRAKT(pages=10) {
const username = `jerrylaslow`
const maxPage = pages;
const reqs = [...Array(maxPage)].map((_, i) => ({ url: `https://trakt.tv/users/`+ username +`/history/all/added?page=${i + 1}`, muteHttpExceptions: true }));
return UrlFetchApp.fetchAll(reqs).flatMap((r, i) => {
if (r.getResponseCode() != 200) {
return [["Values couldn't be retrieved.", reqs[i].url]];
}
const $ = Cheerio.load(r.getContentText());
const ar = $(`a.titles-link > h3.ellipsify, h4 > span.format-date`).toArray();
return [...Array(Math.ceil(ar.length / 2))].map((_) => {
const temp = ar.splice(0, 2);
const watchDate = Utilities.formatDate(new Date($(temp[1]).text().trim().replace(/T|Z/g, " ")),"GMT","yyyy-MM-dd");
const title = $(temp[0]).text().trim();
return [watchDate,title];
});
});
}
The values can be pulled with this, so I know there isn't any sort of blocking in play.
=IMPORTXML(
"https://trakt.tv/users/jerrylaslow/history",
"//meta[#itemprop='name']/#content")
Any help is appreciated.
In order to achieve your goal, in your script, how about the following modification?
Modified script:
function TRAKT(pages = 10) {
const username = `jerrylaslow`
const maxPage = pages;
const reqs = [...Array(maxPage)].map((_, i) => ({ url: `https://trakt.tv/users/` + username + `/history/all/added?page=${i + 1}`, muteHttpExceptions: true }));
return UrlFetchApp.fetchAll(reqs).flatMap((r, i) => {
if (r.getResponseCode() != 200) {
return [["Values couldn't be retrieved.", reqs[i].url]];
}
const $ = Cheerio.load(r.getContentText());
const ar = $(`a.titles-link > h3.ellipsify, h4 > span.format-date`).toArray();
return [...Array(Math.ceil(ar.length / 2))].map((_) => {
const temp = ar.splice(0, 2);
const c = $(temp[0]).parent('a').parent('div').parent('div').find('meta').toArray().find(ff => $(ff).attr("itemprop") == "name"); // Added
const watchDate = Utilities.formatDate(new Date($(temp[1]).text().trim().replace(/T|Z/g, " ")), "GMT", "yyyy-MM-dd");
const title = $(temp[0]).text().trim();
return [watchDate, title, c ? $(c).attr("content") : ""]; // Modified
});
});
}
When this modified script is run, the value of content is put to 3rd column. If you want to put it to other column, please modify return [watchDate, title, c ? $(c).attr("content") : ""];.

How to build an Input field with restrictions

Hello everyone I am a junior web dev. and I've been stuck on this task for weeks now :[]. I need an input field that will show default text coming from the backend. Restrictions I need for this input field are: The input line should break after reaching 30 characters and some parts of default text should be noneditable.
//Here's the closest solution that I came up
import React, { useEffect } from "react";
import styles from "./TextEditor.module.css";
const TextEditor = (props) => {
var limit = 80000; //height limit
let defaultvalueArr = props.data.variables.map((vari, index) => {
return vari.texts.map((p, index) => {
return p;
});
});
useEffect(() => {
const box = document.getElementById(props.title);
var charlimit = 30; // char limit per line
var space;
box.onkeyup = function () {
var lines = box.value.split("\n");
console.log(box.value, "box.value");
for (var i = 0; i < lines.length; i++) {
if (lines[i].length <= charlimit) continue;
var j = 0;
space = charlimit;
while (j++ <= charlimit) {
if (lines[i].charAt(j) === " ") space = j;
}
lines[i + 1] = lines[i].substring(space + 1) + (lines[i + 1] || "");
lines[i] = lines[i].substring(0, space);
}
box.value = lines.slice(0, 500).join("\n");
};
box.oninput = function() {
box.style.height = "";
box.style.height = Math.min(box.scrollHeight, limit) + "px";
};
box.value = defaultvalueArr.join("\n");
}, []);
return (
<div className={styles.TextEditor}>
<textarea
id={props.title}
className={styles.TextArea}
// rows={defaultvalueArr[0].length}
></textarea>
</div>
);
};
export default TextEditor;
output
the problem with this solution is that when the number of characters reaches 30 the cursor jumps at the end of text

How to display looped elements in html

Using the query string URLSearchparams and consolidating my fetch requests into the Promise.all method, my code is now able to print the elements that I want in the console.log. However, I'm having a hard time getting that information to display in html. It returns "undefined" on the screen but the console.log shows the correct data. What's missing ?
async function loadData() {
const queryStr_id = window.location.search;
console.log(queryStr_id);
const product = new URLSearchParams(queryStr_id);
console.log(product);
const id = product.get("_id");
console.log(id);
try {
const url_teddies = "http://localhost:3000/api/teddies/" + id;
const url_cameras = "http://localhost:3000/api/cameras/" + id;
const url_furniture = "http://localhost:3000/api/furniture/" + id;
const results = await Promise.all([fetch(url_teddies), fetch(url_cameras), fetch(url_furniture)]);
const dataPromises = results.map((result) => result.json());
const finalData = await Promise.all(dataPromises);
console.log(finalData);
for (i = 0; i < finalData.length; i++) {
const prodImg = finalData[i].imageUrl;
const prodName = finalData[i].name;
const prodPrice = finalData[i].price / 100 + " Eur";
const prodDesc = finalData[i].description;
const coloris = finalData[i].colors;
const lenses = finalData[i].lenses;
const varnish = finalData[i].varnish;
console.log(prodImg);
console.log(prodName);
console.log(prodPrice);
console.log(prodDesc);
console.log(coloris);
console.log(lenses);
console.log(varnish);
const productDetails = `
<div class="produit__card__wrapper">
<div class="produit__card__content">
<img src="${finalData[i].imageUrl}" alt="${finalData[i].name}" class="productImg"></img>
<div class="container_text">
<h1 class="name"><span>${finalData[i].name}</span></h1>
<p class="price"><strong>Price : </strong><span>${finalData[i].price / 100 + " €"}</span></p>
<p class="description"><strong>Description : </strong><span>${finalData[i].description}</span></p>
<form>
<label for="product_color"></label>
<select name="product_color" id="product_color">
</select>
</form>
<button id="addToCart" type="submit " name="addToCart">Ajouter au panier</button>
</div>
</div>
</div>
`;
const container = document.querySelector(".container");
container.innerHTML = productDetails;
}
return finalData;
} catch (err) {
console.log(err);
}
}
loadData()
I think you just forgot to flatten your three promised arrays before assigning them to finalData. Try
const finalData = (await Promise.all(dataPromises)).reduce((data, arr) => data.concat(arr), []);

Express REST API response methods are not recognized

I have this really simple get request that returns json that I am trying to implement. I have followed the tutorials for Express Web Framework REST API, but for some reason I keep getting the same error
ERROR:
TypeError: res.status is not a function
or
TypeError: res.json is not a function
index.js:
var express = require('express');
var router = express.Router();
var pg = require('pg');
var connectionString = 'pg://postgres:postgres#postgres/feed';
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
router.get('/api/leaderboard', function(resp, req){
var results = [];
pg.connect(connectionString, function(err, client, done){
if(err){
done();
console.log(err);
return res.status(500).json({ success: false, data: err});
}
var query = client.query("SELECT * FROM log WHERE (logged >= date_trunc('week', CURRENT_TIMESTAMP - interval '1 week') AND logged <= date_trunc('week', CURRENT_TIMESTAMP));");
var counter = 0;
var b1 = {};
var b2 = {};
var b3 = {};
var b4 = {};
b1.energy_sum_week = 0;
b2.energy_sum_week = 0;
b3.energy_sum_week = 0;
b4.energy_sum_week = 0;
b1.zne_sum_week = 30000;
b2.zne_sum_week = 30000;
b3.zne_sum_week = 30000;
b4.zne_sum_week = 30000;
query.on('row', function(row){
//results.push(row);
if(row['address'] == 215){
b1.energy_sum_week = row['kitchen'] + row['plugload'] + row['lights'] + row['ev'] + row['hvac'] + row['instahot'] - row['solar'];
}
else if (row['address'] == 1590) {
b2.energy_sum_week = row['kitchen'] + row['plugload'] + row['lights'] + row['ev'] + row['hvac'] + row['instahot'] - row['solar'];
} else if (row['address'] == 1605) {
console.log(row);
b3.energy_sum_week = row['kitchen'] + row['plugload'] + row['lights'] + row['ev'] + row['hvac'] + row['instahot'] - row['solar'];
} else if (row['address'] == 1715) {
b4.energy_sum_week = row['kitchen'] + row['plugload'] + row['lights'] + row['ev'] + row['hvac'] + row['instahot'] - row['solar'];
}
});
query.on('end', function(){
done();
//make zne lower than everything
results.push(b1);
results.push(b2);
results.push(b3);
results.push(b4);
resp.json(results);
});
});
});
module.exports = router;
It seems like it can't recognize the response object. Tried a bunch of different things like passing in the request and response's to the query callbacks, and using promises.
Getting kinda desperate here :/
The res variable doesn't exist in the current context, you probably expect that the line
router.get('/api/leaderboard', function(resp, req){
had this form
router.get('/api/leaderboard', function(req, res){
You are passing resp as the req object and the req as the resp object.
Try changing the order.
router.get('/api/leaderboard', function(req, resp){...}

Pagination in nodejs with mysql

In my project I need to query the db with pagination and provide user the functionality to query based on current search result. Something like limit, I am not able to find anything to use with nodejs. My backend is mysql and I am writing a rest api.
You could try something like that (assuming you use Express 4.x).
Use GET parameters (here page is the number of page results you want, and npp is the number of results per page).
In this example, query results are set in the results field of the response payload, while pagination metadata is set in the pagination field.
As for the possibility to query based on current search result, you would have to expand a little, because your question is a bit unclear.
var express = require('express');
var mysql = require('mysql');
var Promise = require('bluebird');
var bodyParser = require('body-parser');
var app = express();
var connection = mysql.createConnection({
host : 'localhost',
user : 'myuser',
password : 'mypassword',
database : 'wordpress_test'
});
var queryAsync = Promise.promisify(connection.query.bind(connection));
connection.connect();
// do something when app is closing
// see http://stackoverflow.com/questions/14031763/doing-a-cleanup-action-just-before-node-js-exits
process.stdin.resume()
process.on('exit', exitHandler.bind(null, { shutdownDb: true } ));
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', function (req, res) {
var numRows;
var queryPagination;
var numPerPage = parseInt(req.query.npp, 10) || 1;
var page = parseInt(req.query.page, 10) || 0;
var numPages;
var skip = page * numPerPage;
// Here we compute the LIMIT parameter for MySQL query
var limit = skip + ',' + numPerPage;
queryAsync('SELECT count(*) as numRows FROM wp_posts')
.then(function(results) {
numRows = results[0].numRows;
numPages = Math.ceil(numRows / numPerPage);
console.log('number of pages:', numPages);
})
.then(() => queryAsync('SELECT * FROM wp_posts ORDER BY ID DESC LIMIT ' + limit))
.then(function(results) {
var responsePayload = {
results: results
};
if (page < numPages) {
responsePayload.pagination = {
current: page,
perPage: numPerPage,
previous: page > 0 ? page - 1 : undefined,
next: page < numPages - 1 ? page + 1 : undefined
}
}
else responsePayload.pagination = {
err: 'queried page ' + page + ' is >= to maximum page number ' + numPages
}
res.json(responsePayload);
})
.catch(function(err) {
console.error(err);
res.json({ err: err });
});
});
app.listen(3000, function () {
console.log('Example app listening on port 3000!');
});
function exitHandler(options, err) {
if (options.shutdownDb) {
console.log('shutdown mysql connection');
connection.end();
}
if (err) console.log(err.stack);
if (options.exit) process.exit();
}
Here is the package.json file for this example:
{
"name": "stackoverflow-pagination",
"dependencies": {
"bluebird": "^3.3.3",
"body-parser": "^1.15.0",
"express": "^4.13.4",
"mysql": "^2.10.2"
}
}
I taked the solution of #Benito and I tried to make it more clear
var numPerPage = 20;
var skip = (page-1) * numPerPage;
var limit = skip + ',' + numPerPage; // Here we compute the LIMIT parameter for MySQL query
sql.query('SELECT count(*) as numRows FROM users',function (err, rows, fields) {
if(err) {
console.log("error: ", err);
result(err, null);
}else{
var numRows = rows[0].numRows;
var numPages = Math.ceil(numRows / numPerPage);
sql.query('SELECT * FROM users LIMIT ' + limit,function (err, rows, fields) {
if(err) {
console.log("error: ", err);
result(err, null);
}else{
console.log(rows)
result(null, rows,numPages);
}
});
}
});
Was looking for a quick solution. maybe would be useful for someone.
SELECT id FROM complexCoding LIMIT ? OFFSET ?
",req.query.perpage,((req.query.page-1) * req.query.perpage)
Do not forget to paginate according to the total count of id divided by perpage
I wrote a pagination class in order to use it on different pages, I used bootstrap to style the links, you can change it if you're not using bootstrap.
Items route
router.get('/items/:page',(req,res) => {
const db = require('mysql'),
Pagination = require('./pagination'),
// Get current page from url (request parameter)
page_id = parseInt(req.params.page),
currentPage = page_id > 0 ? page_id : currentPage,
//Change pageUri to your page url without the 'page' query string
pageUri = '/items/';
/*Get total items*/
db.query('SELECT COUNT(id) as totalCount FROM items',(err,result)=>{
// Display 10 items per page
const perPage = 10,
totalCount = result[0].totalCount;
// Instantiate Pagination class
const Paginate = new Pagination(totalCount,currentPage,pageUri,perPage);
/*Query items*/
db.query('SELECT * FROM items LIMIT '+Paginate.perPage+' OFFSET '+Paginate.start,(err,result)=>{
data = {
items : result,
pages : Paginate.links()
}
// Send data to view
res.render('items',data);
});
});
});
On items view, just print "pages" to generate pagination links
{{ pages }}
pagination.js >> Add this code to pagination.js and import it to any page you want to use pagination
class Pagination{
constructor(totalCount,currentPage,pageUri,perPage=2){
this.perPage = perPage;
this.totalCount =parseInt(totalCount);
this.currentPage = parseInt(currentPage);
this.previousPage = this.currentPage - 1;
this.nextPage = this.currentPage + 1;
this.pageCount = Math.ceil(this.totalCount / this.perPage);
this.pageUri = pageUri;
this.offset = this.currentPage > 1 ? this.previousPage * this.perPage : 0;
this.sidePages = 4;
this.pages = false;
}
links(){
this.pages='<ul class="pagination pagination-md">';
if(this.previousPage > 0)
this.pages+='<li class="page-item"><a class="page-link" href="'+this.pageUri + this.previousPage+'">Previous</a></li>';
/*Add back links*/
if(this.currentPage > 1){
for (var x = this.currentPage - this.sidePages; x < this.currentPage; x++) {
if(x > 0)
this.pages+='<li class="page-item"><a class="page-link" href="'+this.pageUri+x+'">'+x+'</a></li>';
}
}
/*Show current page*/
this.pages+='<li class="page-item active"><a class="page-link" href="'+this.pageUri+this.currentPage+'">'+this.currentPage+'</a></li>';
/*Add more links*/
for(x = this.nextPage; x <= this.pageCount; x++){
this.pages+='<li class="page-item"><a class="page-link" href="'+this.pageUri+x+'">'+x+' </a></li>';
if(x >= this.currentPage + this.sidePages)
break;
}
/*Display next buttton navigation*/
if(this.currentPage + 1 <= this.pageCount)
this.pages+='<li class="page-item"><a class="page-link" href="'+this.pageUri+this.nextPage+'">Next</a></li>';
this.pages+='</ul>';
return this.pages;
}
}
module.exports = Pagination;