The task is to fetch some data from pokemon api and append it to the list. Api request results have links on previous and next pages.
HTML:
<ul class="poke-list"></ul>
<div class="pagination">
<button class="prev">Prev</button>
<button class="next">Next</button>
</div>
here's a function that makes an api call (async await is necessary):
var getData = async function (url, pokemonName) {
var response;
if(!pokemonName) {
response = await $.get(url);
} else {
response = await $.get(url + pokemonName);
}
return response;
};
A function to append content and handle click events:
var appendContent = function (data) {
var list = $('.poke-list');
list.empty();
var res = data;
var pokemons = res.results;
pokemons.forEach(function (item) {
list.append($(`<li>${item.name}</li>`));
$('.prev').on('click', function (event) {
res = data;
var url2 = res.previous;
if (url2 === undefined || url2 === null) {
alert('Limit reached!');
} else {
getData(url2)
.then(appendContent);
}
});
$('.next').on('click', function (event) {
res = data;
var url = res.next;
if (url === undefined || url ===null) {
alert('Limit reached!');
} else {
getData(url)
.then(appendContent)
});
});
I call it on page load (yes it is necessary):
$(function {
getData()
.then(appendcontent)
here is a fiddle: https://jsfiddle.net/aikast/4rgcvd7z/
What happens is that every time append function is called it creates new click events (I know that's how it should work), and the code stops working properly (it does not see current ajax call results, so every value is null).
Stopping event propagation did not help.
Is there a way to organise it differently? I can't find a way for click event handlers to see ajax call results outside of appendContent function scope
The question was marked duplicate, but buttons are static, they are not dynamically added!
you should try something like below.
now only once click event fired.
$(next).unbind( "click" );
so every time when your function call, first it will unbind previous event.
you can check live demo as well.
https://jsfiddle.net/zhna7ksu/
.unbind() is deprecated and you should use the .off() method instead. Simply call .off() right before you call .on()
$(next).off().on("click", .......);
store data/res (why double naming the same variable?) somewhere outside appendData (window.pokemonResponse for example) function context and declare previous and next logic outside appendData. Also, use let instead of var, even IE 11 supports let
Related
I have a CZML datasource declared:
public geometryDataPromise: Cesium.CzmlDataSource;
I am loading it with an endpoint above when a component is loaded:
if(!this.store.geometryDataPromise) {
this.store.geometryDataPromise = Cesium.CzmlDataSource.load(environment.apiBaseURL + `/protectedareas/geometry/all`);
}
all the rendered objects are displayed to terrain but trying to follow the instructions by doing:
this.store.geometryDataPromise.show = false;
the objects are not being hidden
The problem here is that Cesium.CzmlDataSource.load does not return a Cesium.CzmlDataSource. It returns a Promise to asynchronously go get a CzmlDataSource, and that's not the same thing at all. Your code is trying to show or hide the promise, that's not a thing that gets displayed.
var dataSourcePromise = Cesium.CzmlDataSource.load( ... );
var dataSource = null;
dataSourcePromise.then(function(d) { dataSource = d; });
Note that after the above code runs, dataSource will be null for some time while the browser waits for the server's response to finish downloading. Once the callback function fires, the dataSource is ready.
function onClick() {
if (dataSource !== null) {
dataSource.show = !dataSource.show;
}
}
You can wire up a click handler for a toggle button like this. But the toggle won't do anything until after the dataSource is downloaded and ready.
First I have to take the result of the Cesium.CzmlDataSource.load promise
Cesium.when(Cesium.CzmlDataSource.load(environment.apiBaseURL + `/protectedareas/geometry/all`), result => {
this.sources = result;
this.viewer.dataSources.add(this.sources);
});
and then just change it's fiend show when the visibility changed
this.store.sourceVisibility.subscribe(visibility=>this.sources.show=visibility);
I'm trying to do a couple of things in the IndexedDB database inside the 'fetch' event of a service worker, when the aplication asks the server for a new page. Here's what I'm going for:
Create a new object store (they need to be created dynamically, according to the data that 'fetch' picks up);
Store an element on the store.
Or, if the store already exists:
Get an element from the store;
Update the element and store it back on the store.
The problem is that the callbacks (onupgradeneeded, onsuccess, etc) never get executed.
I've been trying with the callbacks inside of each other, though I know that may not be the best approach. I've also tried placing an event.waitUntil() on 'fetch' but it didn't help.
The 'fetch' event, where the function registerPageAccess is called:
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request)
.then(function (response) {
event.waitUntil(function () {
const nextPageURL = new URL(event.request.url);
if (event.request.destination == 'document') {
if (currentURL) {
registerPageAccess(currentURL, nextPageURL);
}
currentURL = nextPageURL;
}
}());
/*
* some other operations
*/
return response || fetch(event.request);
})
);
});
registerPageAccess, the function with the callbacks.
I know it's plenty of code, but just look at secondRequest.onupgradeneeded in the 5th line. It is never executed, let alone the following ones.
function registerPageAccess(currentPageURL, nextPageURL) {
var newVersion = parseInt(db.version) + 1;
var secondRequest = indexedDB.open(DB_NAME, newVersion);
secondRequest.onupgradeneeded = function (e) {
db = e.target.result;
db.createObjectStore(currentPageURL, { keyPath: "pageURL" });
var transaction = request.result.transaction([currentPageURL], 'readwrite');
var store = transaction.objectStore(currentPageURL);
var getRequest = store.get(nextPageURL);
getRequest.onsuccess = function (event) {
var obj = getRequest.result;
if (!obj) {
// Insert element into the database
console.debug('ServiceWorker: No matching object in the database');
const addRes = putInObjectStore(nextPageURL, 1, store);
addRes.onsuccess = function (event) {
console.debug('ServiceWorker: Element was successfully added in the Object Store');
}
addRes.onerror = function (event) {
console.error('ServiceWorker error adding element to the Object Store: ' + addRes.error);
}
}
else {
// Updating database element
const updRes = putInObjectStore(obj.pageURL, obj.nVisits + 1, store);
updRes.onsuccess = function (event) {
console.debug('ServiceWorker: Element was successfully updated in the Object Store');
}
updRes.onerror = function (event) {
console.error('ServiceWorker error updating element of the Object Store: ' + putRes.error);
}
}
};
};
secondRequest.onsuccess = function (e) {
console.log('ServiceWorker: secondRequest onsuccess');
};
secondRequest.onerror = function (e) {
console.error('ServiceWorker: error on the secondRequest.open: ' + secondRequest.error);
};
}
I need a way to perform the operations in registerPageAccess, which involve executing a couple of callbacks, but the browser seems to kill the Service Worker before they get to occur.
All asynchronous logic inside of a service worker needs to be promise-based. Because IndexedDB is callback-based, you're going to find yourself needing to wrap the relevant callbacks in a promise.
I'd strongly recommend not attempting to do this on your own, and instead using one of the following libraries, which are well-tested, efficient, and lightweight:
idb-keyval, if you're okay with a simple key-value store.
idb if you're need the full IndexedDB API.
I'd also recommend that you consider using the async/await syntax inside of your service worker's fetch handler, as it tends to make promise-based code more readable.
Put together, this would look roughly like:
self.addEventListener('fetch', (event) => {
event.waitUntil((async () => {
// Your IDB cleanup logic here.
// Basically, anything that can execute separately
// from response generation.
})());
event.respondWith((async () => {
// Your response generation logic here.
// Return a Response object at the end of the function.
})());
});
I have list and each clicked item triggers different API request. Each request have different duration. On success I'm displaying some data.
Issue is that when I click on item#1 which takes approx 6000 to load, and just after on item#2 which takes 2000 to load, I will have the last clicked item displayed - which is item#2 because it has already loaded and once item#1 has received data my data will change to that. This is wrong as I want to display data from the latest click.
This is how I handle event:
newList.on('click', 'li', (e) => {
let id = $(e.currentTarget).data("id");
store.getCharacterDetails(id).then(docs => {
this.clearDetails();
this.charDetails = docs;
this.displayDetails(this.charDetails);
})
My API is a simulation from store object.
I suppose this works as expected but I do want the last triggered request to be valid.
A crude and simple method can be creating an array and pushing the IDs and after the asynchronous operations you can just check if it is the latest click or not. But pitfall is that if clear and displayDetails takes much time and if someone click while it was clearing and displaying it will not register the latest click.
Anyway, here is the code maybe you can make something better out of it.
var latestClick = [];
newList.on('click', 'li', (e) => {
let id = $(e.currentTarget).data("id");
latestClick.push(id);
store.getCharacterDetails(id).then(docs => {
if(id === latestClick[latestClick.length - 1]){
this.clearDetails();
this.charDetails = docs;
this.displayDetails(this.charDetails);
latestClick = [];
}
})
})
Make charDetails an object that keeps all of the results, keyed by the ids. Keep track of the last clicked id.
// in constructor
this.charDetails = {};
this.lastId = null;
newList.on('click', 'li', (e) => {
let id = $(e.currentTarget).data("id");
this.lastId = id;
if (this.charDetails[id] === id) { // don't cancel requests, cache them!
this.displayDetails(this.charDetails[id])
} else {
store.getCharacterDetails(id).then(docs => {
// this runs later, cache the result
this.charDetails[id] = docs;
if (id === lastId) { // only update UI if the id was last clicked
this.displayDetails(docs)
}
});
}
});
I have two main loops for posts and comments. However the comments don't display, presumably because the post ID is not on the DOM yet (?).
$.getJSON('fresh_posts.php',function(data){
//posts
$.each(data.freshposts, function(id, post) {
// set variables and append divs to document
var id = post.id;
...
});
// comments attached to each post
$.each(data.freshcomments, function(id, commentList) {
$.each(commentList, function(index, c) {
// set variables and append comments to each post div
var postid = c.postid; // this is the same as post.id (linked)
...
var full = "<div> ... </div>";
$('#comment-block'+postid).append(full); // comment-block+postid is attached with each post div, so it tells the comment which div it should be appended to.
})
});
});
Does not display comments ^
If I wrap the $.each loop for the comments in a setTimeOut(function(){},1), the comments are able to be displayed - I suppose it needs to wait 1 millisecond before the loop can commence? However this doesn't seem like a good/fool-proof way to ensure this.
setTimeOut(function(){
$.each(data.freshcomments, function(id, commentList) {
...
})
},1)
Displays comments ^
I managed to solve this after a few hours of work.
I made two functions, getPosts() and appendComments(). I used the promise method:
function getPosts(){
var deferred = new $.Deferred(); // new deferred
$.getJSON('fresh_posts.php',function(data){
global_save_json = data.freshcomments;
var howManyPosts = Object.keys(data.freshposts).length; // how many posts there are (object length)
var arrayCount = 0;
$.each(data.freshposts, function(id, post) {
// closing tags for above
return deferred.promise();
}
I had an async method (just learned what that was) to check if the image was designed for retina displays within getPosts.
if (retina === "true") {
var img = new Image();
img.onload = function() {
var retina_width = this.width/2;
var post = '...';
$('.main').append(post);
arrayCount++; // for each post add +1 to arrayCount
if (arrayCount == howManyPosts) { // if last post
deferred.resolve();
}
}
img.src = image;
} else {
...
I then did
getPosts().then(appendComments);
after closing tag of the function getPosts. So basically even though the function finishes, it still waits for the async method to also finish within that function before the function getComments is called. That way the post id's will exist on the document for the comments to be appended to.
appendComments looks like this:
function appendComments(){
if (global_save_json != null){
$.each(global_save_json, function(id, commentList) {
$.each(commentList, function(index, c) {
I have a page where the user can click a button to retrieve data via an xhr get request. While the data is loading and being parsed, I want a loading message to be displayed, which will be replaced with the data once it is ready. I'm using dojo libraries, so would rather not include jQuery or other libraries.
This is a simplified version of the set up I'm using:
HTML
<div id = "clickMe"> Click Me! </div>
<div id = "results" class = "hidden">
Please wait while we retrieve the results
</div>
CSS
.hidden {display: none;}
Javascript
// Bind function to click me div
var clickMe = document.getElementById('clickMe');
clickMe.addEventListener('click', getResults, false);
function getResults () {
// Display the loading message while results are retrieved
var resultsDiv = document.getElementById('results');
resultsDiv.classList.remove('hidden');
// Get the data and parse it here using a standard dojo.xhrGet method
var displayResults = getData();
// Output data to resultsDiv, overwriting loading message
resultsDiv.innerHTML = displayResults;
}
The problem I'm having is that the getResults function always waits until the getData function has completed before removing the 'hidden' class and showing the results div. This means that the user never sees the loading message, only the retrieved data, even if there's a delay while the data is processed. However, if I put an alert in the middle the function is forced to pause, the loading message is displayed:
function getResults () {
// Display the loading message while results are retrieved
var resultsDiv = document.getElementById('results');
resultsDiv.classList.remove('hidden');
// The loading message will be displayed if this alert is included
alert ("Hello world!");
// Get the data and parse it here using a standard dojo.xhrGet method
var displayResults = getData();
// Output data to resultsDiv, overwriting loading message
resultsDiv.innerHTML = displayResults;
}
I have tried replacing the alert with console.log, but it reverts to not showing the loading message. I've also tried setting up getting the data as a callback function inside displaying the loading message, but again it doesn't show anything. I have also tried with the get request set to sync: true as well as sync: false, but again no luck.
How can I make sure the loading message is displayed while waiting for getData?
Edit:
This is the getData function. I have tried both with and without syncing.
function getData() {
var targetUrl = //some url;
var restResponse;
dojo.xhrGet({
url: targetUrl,
sync: true; // no difference when this is omitted
load: function(result) {
restResponse = result;
}
});
// Parse the rest response - fairly long function so I won't paste it here
var parsedResponse = parseResult(restResponse);
return parsedResponse;
}
My recommendation is to learn how to write asynchronous code and dojo/Deferred.
Instead of getData, rename the method to loadData and
loadData: function() {
return xhr('', {...}); // this returns a deferred
}
function getResults () {
var resultsDiv = dom.byId('results');
domClass.remove(resultsDiv, 'hidden');
loadData().then(function(displayResults) {
resultsDiv.innerHTML = displayResults;
});
}
http://dojotoolkit.org/reference-guide/1.9/dojo/Deferred.html
You can use deffereds and promises in jQuery
http://www.bitstorm.org/weblog/2012-1/Deferred_and_promise_in_jQuery.html. If you work with ajax request you can chain like this (since jQuery 1.8).
var promise1 = $.ajax("/myServerScript1");
function getStuff() {
return $.ajax("/myServerScript2");
}
promise1.then(getStuff).then(function(myServerScript2Data){
// Both promises are resolved
});