Problem
I want to load different food products on a page which users then can scroll through.
For this I am using pagination in sqlalchemy which works fine.
The problem though is that I have discovered that some products are being repeated and I have trouble figuring out why. I.e when I scroll down the page and the next 10 products are being loaded one or two of them are the same as one of the first 10 that was loaded.
I have included a distinct on the ids but it doesnt seem to help. What can I do to make sure I don't get duplicate products when I scroll down the page?
Backend code (Flask/Python)
from sqlalchemy_paginator import Paginator
def all_products():
user_id = request.args.get("userid")
main_category_id = request.args.get("category_id")
page_nr = request.args.get("page_nr")
category_product_query1 = Session.query(enjordfoodProducts.id).distinct(enjordfoodProducts.id).group_by(enjordfoodProducts.id).subquery()
category_product_query = Session.query(enjordfoodProducts,enjordfoodCompanies).filter(enjordfoodProductsToCompanies.product_id == enjordfoodProducts.id,
enjordfoodProductsToCompanies.company_id == enjordfoodCompanies.id).join(
category_product_query1, category_product_query1.c.id == enjordfoodProducts.id).order_by(desc(enjordfoodProducts.ranking_value),enjordfoodProducts.id)
paginate = Paginator(category_product_query,8)
pg = paginate.page(page_nr)
products_list= pg.object_list
total_pages = paginate.total_pages
all_product_dictionary = []
all_product_list = []
for product, company in products_list:
all_product_dictionary = {
'id' :product.id,
'product_name':product.product_name,
'product_image':product.product_image,
'env_value':product.product_env_value,
'env_value_color':product.env_value_color,
'created_by_userid':product.created_by_userid,
'is_category':product.is_category,
'company': company.company_name,
}
all_product_list.append(all_product_dictionary)
result_dict = {}
result_dict["all_products_list"] =all_product_list
result_dict["current_page_nr"] = page_nr
result_dict["total_page_nr"] = total_pages
result = json.dumps(result_dict)
return result
Frontend code Vue.js
<div class="flow" >
<div class="flowpost" v-for="(post,idx) in all_products_list" :key="idx" #click="to_product(post.id)" >
<img class="product_image" :src="get_image(post.created_by_userid,post.product_image)" />
<div :style="{marginLeft:'12%',marginTop:'40%', height:'23px', position:'absolute',color:'white',paddingTop:'0%',fontSize:'13px', paddingLeft:'0%',paddingRight:'0%',width:'50px', borderRadius:'12px', backgroundColor:post.env_value_color}">
<img :style="{height:'19px', width:'19px'}" :src= "`${publicPath}`+ 'loggaenjord.png'" alt=""/>
<div :style="{marginTop:'-20px',marginLeft:'54%', color:'white'}">{{post.env_value}}</div>
</div>
<div :style="{marginTop:'47%',position:'absolute',lineHeight: 0.9, marginLeft:'3%',width:'120px', textAlign:'center',color:'black'}">
<span :style="{fontSize:'11px', fontWeight:600}">{{post.product_name}}</span> <br>
<span :style="{fontSize:'9.3px'}">{{post.company_name}}</span>
</div>
</div>
</div>
methods:{
load_all_products(page_nr){
var self = this
axios({
method:'get',
url: API_URL+ '/all_products' + '?user_id='+this.user_id+'&page_nr='+page_nr ,
})
.then(function(response){
self.all_products_list.push.apply(self.all_products_list,response.data["all_products_list"])
self.current_page_nr = response.data["current_page_nr"]
self.total_page_nr = response.data["total_page_nr"]
})
},
addmore(){
if ( this.current_page_nr <this.total_page_nr ){
var page_nr = Number(this.current_page_nr) +1
this.load_all_products(page_nr)
}
}
},
mounted(){
this.load_all_products(1)
window.onscroll = () => {
let bottomOfWindow = document.documentElement.scrollTop + window.innerHeight === document.documentElement.offsetHeight;
if (bottomOfWindow ) {
this.addmore()
}
}
}
Related
I'm working on building a quiz using Jquery, Loops, and Arrays. I started with looping through the answers (seen in matrix). Now I'd like to do the same thing for questions so that each of the three options appears with a question/prompt as well. The matrix matches the dict object I have above, so I'm not sure how to replicate for questions...
Secondly, I'm having trouble getting a final score to show once we've reached the end of the arrays in the matrix. I've tried setting it so that the final score button appears once the matrix counter has reached an index beyond number of arrays, but it seems to be causing some issues. I also am having trouble returning the actual final score on the screen.
Any advice is helpful!
<body>
<div id="Start">
<div id="welcome" class="question">
<p>
Choose the right response. Don't get fired.
</p>
</div>
<button type="button" class="startBtn" id="startBtn">Start</button>
</div>
<div id="wordZone">
</div>
<ul id="choices">
<li></li>
<li></li>
<li></li>
</ul>
Next Word
Final Score
<div id="finalScore" class="hide">
<h3 id="scoreMessage"></h3>
<p id="playerScore"></p>
</div>
</body>
</html>
// setup
let score = 0;
let matrixcounter = 0;
const dict = {
officeSpeak: ["Hi there!", "Regards", "Per my last email"],
counter: 0,
};
const matrix = [
["Hi there!", "Sup dude", "Salutations"],
["Regards", "Hasta luego", "Byebye"],
[
"Per my last email",
"oopsie!",
"some other option here, you're writing this game",
],
];
const wordZone = $("#wordZone");
const choiceButtons = $("#choices li");
$("#choices").hide();
$("#end").hide();
$("#startBtn").click(function () {
$("#choices").show();
});
function buildOptions(wordCounter) {
let turnChoices = matrix[wordCounter];
$("#next").hide();
for (let i = 0, ii = turnChoices.length; i < ii; i++) {
let choiceWord = turnChoices[i];
let choiceButton = $(choiceButtons[i]);
let btnClass = "incorrect";
choiceButton.text(choiceWord);
if (dict.officeSpeak.indexOf(choiceWord) != -1) {
btnClass = "correct";
}
choiceButton.addClass(btnClass);
}
}
buildOptions(matrixcounter);
function onClickWord(e) {
console.log($(this));
$("#choices li").addClass("active");
$(".correct").css("color", "green");
$(".incorrect").css("color", "red");
if ($(this).hasClass("correct")) {
score++;
console.log(score);
}
$("#next").show();
let turnChoices = matrix[+1];
}
$("#choices li").click(onClickWord);
$("#next").click(function () {
matrixcounter++;
buildOptions(matrixcounter);
$(".correct").css("color", "black");
$(".incorrect").css("color", "black");
});
function finalScore() {
if (matrixcounter >= buildOptions(turnChoices.length)) {
$("#end").show();
}
$("#end").click(function () {
return score;
});
// let finalScore = score;
// $("#wordZone").show(finalScore);
// if (finalScore >= 2) {
// $("#wordZone").addClass("win");
// $("#win").show();
// } else {
// $("#wordZone").addClass("lose");
// $("#lose").show();
// }
}
finalScore();
I tried setting up questions as such:
const questions = [{ question: "Did you get the email I sent 5 minutes ago? Havent heard from u.", choices: ["I'm busy", "You just sent it", "Havent had a chance to look!",], correctAnswer: "Havent had a chance to look!" }, {
however it broke the loop I had previously set up to have matrix read dict as an answer key.
I am creating a services cart at client side
where services are grouped inside 3 level of groupings like
product[SECTION] [SERVICE] [ITEM]
product['carpet']['cleaning']['room'] = {'qty':2,'price':15};
product['carpet']['cleaning']['hall'] = {'qty':1,'price':10};
product['carpet']['protecting']['hall'] = {'qty':1,'price':10};
product['carpet']['deodorize']['hall'] = {'qty':1,'price':10};
product['leather']['cleaning']['sofa'] = {'qty':1,'price':10};
want to generate above structure of json.
my text boxes looks like below notice data-section data-service data-tem and data-price
<input type="text" class="form-control input-number"
data-price="15" data-section="carpet" data-item="room" data-service="protect" />
My JS code is as below, but it adds only current item while overwriting all other services and sections.
$(function(){
$('.input-number').change(function(){
var section = $(this).attr('data-section');
var item = $(this).attr('data-item');
var service = $(this).attr('data-service');
var qty = $(this).val();
var unitprice = $(this).attr('data-unitprice');
addProduct(section,item,service,qty,unitprice);
});
});
function addProduct(section,item,service,qty,unitprice){
let products = {};
if(localStorage.getItem('products')){
products = JSON.parse(localStorage.getItem('products'));
}
products['"'+section+'"'] =
{
['"'+service+'"'] : {
['"'+item+'"'] : {
'unitprice' : unitprice, 'qty': qty
}
}
};
localStorage.setItem('products', JSON.stringify(products));
}
How can I append only instead of overwriting nested items?
EDITED
I have edited my add product function as below but still not getting desired result
function addProduct(section,item,service,qty,unitprice){
let products = {};
if(localStorage.getItem('products')){
products = JSON.parse(localStorage.getItem('products'));
}
var v = {
[service] : {
[item] :{
"qty":qty,'unitprice':unitprice
}
}
};
products.push( section, v );
localStorage.setItem('products', JSON.stringify(products));
}
Object.prototype.push = function( key, value ){
if(typeof value === 'object'){
var k = Object.keys(value)[0];
value.push( k, value[k] );
}
this[ key ] = value;
return this;
}
First of all that is not a good way to store your data.
Here's a better way to store your data.
It's much more easy to understand if you see it like an object cause in javascript it's the same thing
//example this 2 data is store the same way in javascript
var product['carpet']['cleaning']['room'] = {'qty':2,'price':15};
var product = [{
carpet: {
cleaning: {
room: {'qty':2,'price':15}
}
}
}]
// my solution would be just save section, service and item in the object, example:
var product = [];
var v = {
section: 'carpet',
service: 'cleaning',
item: 'room'
qty:2,
price:15
}
product.push(v)
Currently, this codepen I forked displays one tv monitor on the webpage as shown below where the channel button allows the user to toggle different gif's. This gif data is stored as an array in the js file. I want to create multiple tv sets, so I am thinking it may be better to create a TV class and instantiate the TV object n-times through a loop. I am new to OOP in a web dev context, so I haven't yet figured out how to rearchitect the code to accomplish this. Since id's only allow for one HTML element, duplicating the chunk of code below would visually create another tv but without any dynamic features. What then becomes of the tv-body display elements? Would they be enveloped with a show() fx nested with the script's TV class? Thank you so much in advance!
[Cropped Output Displayed Here][1]
HTML
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>CodePen - Vintage Analog TV</title>
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"><link rel="stylesheet" href="./style.css">
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://code.jquery.com/jquery-3.5.0.js"></script>
</head>
<body>
<!-- partial:indexe.partial.html -->
<main>
<div class="tv-set">
<div class="tv-body">
<div class="screen-container">
<canvas class="static" width="380" height="280"></canvas>
<img class="displayed" src="" alt="Nothing" width="380" height="280">
<div class="screen">
<div class="screen-frame"></div>
<div class="screen-inset"></div>
</div>
</div>
<div class="logo-badge">
<div class="logo-text">Bush</div>
</div>
<div class="controls">
<div class="panel">
<div class="screw"></div>
<div class="dial">
<button class="channel dial-label pristine">Channel</button>
</div>
</div>
<div class="vents">
<div class="vent"></div>
<div class="vent"></div>
<div class="vent"></div>
<div class="vent"></div>
<div class="vent"></div>
<div class="vent"></div>
</div>
<div class="panel">
<div class="screw"></div>
<div class="dial">
<button class="dial-label" disabled>Volume</button>
</div>
</div>
</div>
</div>
<div class="legs">
<div class="leg"></div>
<div class="leg"></div>
</div>
</div>
</main>
<!-- partial -->
<script src="./script.js"></script>
</body>
</html>
JS
document.addEventListener("DOMContentLoaded", tv);
// Helper Functions
//returns tagname
jQuery.fn.tagName = function () {
return this.prop("tagName").toLowerCase;
};
// returns nth parent from target element
$.fn.nthParent = function (n) {
var p = this;
for (var i = 0; i < n; i++)
p = p.parent();
return p;
}
phases = [{
channels: ["red", "blue"]
},
{
channels: ["green", "yellow"]
},
{
channels: ["red", "green"]
},
{
channels: ["blue", "green"]
}
]
const container = document.getElementsByTagName("main")[0];
const template = document.getElementsByClassName("tv-set")
for (let i = 0; i < phases.length; i++) {
const clone = template[i].cloneNode(true);
clone.setAttribute("id", "tv-" + (i + 1))
console.log("clone id: ", clone.getAttribute("id"))
clone.setAttribute("data-channel", 0)
clone.setAttribute("name", "tv-" + i)
// clone.style.backgroundColor = phases[i].channels[0]
container.appendChild(clone)
}
function tv() {
let cnvs = document.querySelectorAll(".static");
//Gather all static elements
// let scrns = $(".static").getContext
// console.log("Screen 01: ", scrns)
// var cnv = document.getElementById("static"),
// var cnv = document.querySelector(".static"), //works in place of line above
// Need to establish a boolean array for the isStatic
let c = []
let isStatic_arr = []
// Need to establish a boolean array for the isStatic
cnvs.forEach((cnv) => {
isStatic_arr.push(false)
var c = cnv.getContext("2d"),
cw = cnv.offsetWidth,
ch = cnv.offsetHeight,
staticScrn = c.createImageData(cw, ch),
staticFPS = 30,
// isStatic_arr.push(false),
// isStatic = false,
staticTO,
gifData = [{
// file: "https://i.ibb.co/chSK1Zt/willie.gif",
file: "./media/back-to-school-chacha.gif",
desc: "Stephen Chow Fight Back to School"
// <video controls autoplay>
// <source src="fbts_chacha_sound.mp4" type="video/mp4">
// <source src="movie.ogg" type="video/ogg">
// Your browser does not support the video tag.
// </video>
},
{
file: "https://i.ibb.co/chSK1Zt/willie.gif",
desc: "Steamboat Willie (Mickey Mouse) steering a ship"
},
{
file: "https://i.ibb.co/0FqQVrj/skeletons.gif",
desc: "Spooky scary skeletons sending shivers down your spine"
},
{
file: "https://i.ibb.co/Hpnwgq2/kingkong.gif",
desc: "King Kong waving on Empire State Building",
},
{
file: "https://i.ibb.co/fp0PSjv/tracks.gif",
desc: "Looking at train tracks from behind a train",
},
{
file: "https://i.ibb.co/5FM7BtH/nuke.gif",
desc: "Nuclear explosion at sea",
}
],
gifs = [],
channel = 0;
for (g in gifData) {
gifs.push(new Image());
gifs[g].src = gifData[g].file;
gifs[g].alt = gifData[g].desc;
}
/* Static */
var runStatic = function () {
isStatic = true;
c.clearRect(0, 0, cw, ch);
for (var i = 0; i < staticScrn.data.length; i += 4) {
let shade = 127 + Math.round(Math.random() * 128);
staticScrn.data[0 + i] = shade;
staticScrn.data[1 + i] = shade;
staticScrn.data[2 + i] = shade;
staticScrn.data[3 + i] = 255;
}
c.putImageData(staticScrn, 0, 0);
staticTO = setTimeout(runStatic, 1e3 / staticFPS);
};
runStatic();
/* Channels */
var changeChannel = function (button, idx) {
console.log("Tv-set: ", idx)
console.log("Tv-set- " + idx + "button: " + button)
// var displayed = document.getElementById("displayed");
var displayed = document.querySelectorAll(".displayed")[idx];
var display_parent = $(".displayed")[1]
console.log("Display: ", displayed)
console.log("Display's parent: ", display_parent)
++channel;
if (channel > gifData.length)
channel = 1;
// this.classList.remove("pristine");
button.classList.remove("pristine");
// this.style.transform = `rotate(${channel * 360/(gifData.length + 1)}deg)`;
button.style.transform = `rotate(${channel * 360/(gifData.length + 1)}deg)`;
theCanvas = document.querySelectorAll(".static")[idx]
// cnv.classList.remove("hide");
theCanvas.classList.remove("hide");
displayed.classList.add("hide"); //CAUSING PROBLEMS
if (!isStatic[idx])
runStatic();
setTimeout(function () {
// cnv.classList.add("hide");
theCanvas.classList.add("hide");
displayed.classList.remove("hide");
displayed.src = gifs[channel - 1].src;
displayed.alt = gifs[channel - 1].alt;
isStatic = false;
clearTimeout(staticTO);
}, 300);
};
function iterate(item, index) {
console.log(`${item} has index ${index}`);
}
// const buttons = document.getElementsByClassName("channel dial-label pristine");
// const btns_arr = Array.from(document.querySelectorAll(".channel"))
const buttons = document.querySelectorAll(".channel")
buttons.forEach((btn, i) => {
btn.addEventListener('click', () => changeChannel(btn, i));
});
});
}
[1]: https://i.stack.imgur.com/INtzP.png
[2]: https://i.stack.imgur.com/aOxoQ.png
(11/14/20) #ggirodda, thank you so much for the example. Unfortunately, I am still a bit stuck. Why is it when I use const template = document.getElementsByClassName("tv-body").children[0], I get the error: script_001.js:154 Uncaught TypeError: Cannot read property '0' of undefined
at HTMLDocument.tv (script_001.js:154) Shouldn't the tv-body class have children based on the code snippet below?
(11/14/20) Addressed error above by removing .children[0] but unsure as to why that works and why it was undefined.
(11/19/20) Resolved! Sort of, that is. All tv clones can will run as intended, meaning the static display will remain active on all tv's whose channel button has not been pressed, and the channels can be changed independently. Here were the main changes I made on the original code:
All id's replaced with classes so that they can be accessed and wrapped the "tv-body" and "legs" in a separate div so that they can be cloned as a set.
Gathered all the "tv-set" class elements outside of the tv function() and then performed the setup functions forEach()
Converted a few of the variables e.g canvas, isStatic into arrays so that their states and displays could be toggled independently. I am sure there is more work to be done here as some of the variables may still be shared among the clones.
You can take some inspiration from the code below, the example is less complex but the idea is the same
const tvs = [
{ channels: ["red", "blue"] },
{ channels: ["green", "yellow"] }
]
function changeChannel(idx) {
const tv = tvs[idx]
const tvNode = document.getElementById("tv-" + idx)
const currentChannelIdx = parseInt(tvNode.getAttribute("data-channel"))
const nextChannelIdx = tv.channels[currentChannelIdx + 1] ? currentChannelIdx + 1 : 0;
tvNode.style.backgroundColor = tv.channels[nextChannelIdx]
tvNode.setAttribute("data-channel", nextChannelIdx)
}
const container = document.getElementById("container")
const template = document.getElementById("tv-template").children[0]
for (let i = 0; i < tvs.length; i++) {
const clone = template.cloneNode(true);
clone.setAttribute("id", "tv-" + i)
clone.setAttribute("data-channel", 0)
clone.style.backgroundColor = tvs[i].channels[0]
container.appendChild(clone)
}
const buttons = document.getElementsByClassName("channel-btn")
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', () => changeChannel(i), false);
}
<div id="container"></div>
<div id="tv-template" style="display: none;">
<div class="tv" style="width: 70px; height: 70px; margin-bottom: 20px;">
<button class="channel-btn">next</button>
</div>
</div>
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>
I made a sort of to-do list app but for movies using a movie database API.
Adding movies works fine but I'm trying to add a delete button to each element that will delete said html element and also erase it from the array in local storage. I've tried it a few different ways from threads I've found on here like appending the button as a child to the movie but that didn't work for me. Thanks in advance for any help offered.
let addMovies = document.querySelector('.add-movies');
let moviesList = document.querySelector('.films');
let movies = JSON.parse(localStorage.getItem('movies')) || [];
let removeFilm = document.querySelectorAll('.delete');
function addMovie(e) {
e.preventDefault();
let text = (this.querySelector('[name=movie]')).value;
let api = "https://www.omdbapi.com/?apikey=636d7ecb&t=" + text;
$.getJSON(api, function (data) {
var year = data.Year;
var director = data.Director;
var plot = data.Plot;
var title = data.Title;
var actors = data.Actors;
var poster = data.Poster;
var genre = data.Genre;
var rating = data.imdbRating;
var movie = {
text
, director
, year
, title
, actors
, plot
, poster
, genre
, rating
}
movies.push(movie);
populateList(movies, moviesList);
localStorage.setItem('movies', JSON.stringify(movies));
});
};
function populateList(films = [], filmsList) {
filmsList.innerHTML = movies.map((film, i) => {
return `
<li>
<img src="${film.poster}"/>
<label for="movie${i}"><h1>${film.title}</h1><h2 id="genre">
${film.genre}</h2><p id="year"> (${film.year})</p></label>
<label for="movie${i}"><p style="font-weight:bold;">Starring:</p>
<p> ${film.actors}</p></label>
<label for="movie${i}">
<img id="rt" src="https://www.digitaltrends.com/wp-content/uploads/2011/05/Rotten_Tomatoes_logo.png"/>
<h2>${film.rating}</h2></label>
<button id="remove" class="delete">Delete</button>
</li>
`;
} ).join('');
}
function deleteMovie(e){
var el = e.target
movies.splice(el.movie);
localStorage.removeItem("movies", movie);
}
addMovies.addEventListener('submit', addMovie);
removeFilm.addEventListener('click', deleteMovie);
populateList(movies, moviesList);