First post on Stackoverflow, be gentle.
Since I think my error lies somewhere in the logic and haven't found any similar questions I decided to go forward with it.
Situation as follows:
I have a global empty array called redirects.
var redirects = [];
redirects[] fills dynamically based on ajax response data
function ajaxFunction() {
within success call:
var prefix = "abc";
for (var key in data) {
var url = (prefix + key);
redirects[key.toLowerCase()] = url;
}
}
In my on-click method I bind the clicked class to it's url and call the function that creates a table; followed by the one that dynamically fills it based on our url.
/**
* Bind clicked class to url and call function to fill table.
*/
$(myid).on("click", "a", function () {
// Get the clicked class selector.
var clicked = $(this.getAttribute("class"));
var selectedClass = clicked.selector;
// Check for same value and break operation when found.
for(var key in redirects) {
if (key == selectedClass) {
// Set the url to redirect in fillTable function.
var url = redirects[key];
break;
}
}
// Returns values as we expect
// console.log(key, " | ", url);
// Construct a table based on url.
table();
fillTable(url);
});
FYI: table() displays a table form in html; fillTable() fills the th & td;
Both functions work fine with static url immediately defined but I want it dynamically based on the click.
Now, when I test this out, this only works when spamming the link quickly instead of just clicking it once.
This leads me to believe that the reason for it not showing up immediately, is because somehow the for-loop within my on-click is still running and requires the second click to be recorded (but I'm just guessing here).
Related
I am currently creating a script to handle Ajax page transitions using JQuery's Ajax request function. Inside the success callback of the Ajax function, I need to be able to access the current page's body classList, and the classList of the body in the callback's returned data. As this script is transitioning between pages of my (Wordpress) site, I need to update the body classes during the Ajax success function.
Here is a very oversimplified version of my code to give you a general idea of what I'm doing:
function loadPageData(event, elem, eventType) {
let _this = elem;
// use this to determine where the transition comes from (i.e. to differentiate between home->single-casestudy and single-casestudy->single-casestudy etc...)
let cameFromBodyClasses = elem.closest('body').classList;
console.info('console test 1:', cameFromBodyClasses, cameFromBodyClasses.contains('home'));
// Prevent the default behavior of clicking these links
event.preventDefault();
let th = eventType === 'menuLinkClick' ? $(_this) : $('.menu-main-container ul li.current-menu-item');
let url = eventType === 'menuLinkClick' ? th.attr('href') : window.location.href;
$.ajax({
type: 'POST',
url: url,
dataType: "html",
success: function (data) {
console.info('console test 2:', cameFromBodyClasses, cameFromBodyClasses.contains('home'));
let htmlObject = document.createElement('html');
htmlObject.innerHTML = data;
$(document).find('body').attr('class', $(htmlObject).find('body').attr('class'));
let newBody = $(htmlObject).find('body');
// this console.info is now different to the previous two...
console.info('console test 3:', cameFromBodyClasses, cameFromBodyClasses.contains('home'));
// the below if statement is being passed because cameFromBodyClasses.contains('home') is false at this point (but it shouldn't be)
if (cameFromBodyClasses.contains('home') && newBody.hasClass('single-casestudy')) {
// if coming from 'home' page and going to 'single-casestudy' page
// do unique transition animation here
}
}
});
}
$(document).on('click', 'a[data-ajax="true"]', function (e) {
loadPageData(e, this, 'menuLinkClick');
}
The Problem
As you can see from the simplified code, I have 3 times in which I console.info the cameFromBodyClasses variable to check its value.
When navigating from the homepage to a single case study page, the first and second console.info calls show that the variable cameFromBodyClasses contains the classList of the homepage body tag as it should (because we have come from the homepage). The 3rd console.info call, however, is different and prints the body classList of the page I am navigating to (the single case study page), but it should still be printing the homepage's body classList, as the variable has not been altered in any way between the two console.info calls.
So...
How can I store the body classList of the page the user is navigating away from and prevent it from ever being updated/changed at any point during the Ajax call?
I assume it has something to do with the fact that I am creating a new html element and updating its body classList before the 3rd console.info, but I still do not understand why this would change the value of the cameFromBodyClasses variable as it is only set at one point during the function (and before the ajax call)
The issue is that the variable cameFromBodyClasses points to the classList, which is a form of an array. If the contents of the array changes, cameFromBodyClasses will also reflect the changes because it just points to the array, not a unique array from what it originally pointed to.
Consider the following...
var x = [ 'a', 'b'];
var y = x;
console.log(x, y);
x.push('c');
console.log(x, y);
y = x.slice(0);
x.push('d');
console.log(x, y);
As you can see, x and y both point to the same array in memory. However, the last push is not reflected in y. This is because of the slice() performed, which caused y to point to a completely different array. So pushing to x would not affect y at that point.
I have a three dropdown with 1 submit button. Now, I'm trying to retain the value of each dropdown after the user clicked the input submit. But the Jquery I have is not working. I use PHP to displayed the output of the dropdown when the user clicked it.
Note: The page is refresh when the user clicked the input submit.
How to fix this? See in plunker
JS:
$(document).ready(function(){
$('#dropdown').change(function(){
var option = $(this).find('option:selected').val();
$('#dropdown').val(option);
});
});
Use local storage with all option;
$("#dropdown").change(function(){
var html=$("#dropdown").html();
localStorage.setItem("myapp-selectval",html);
localStorage.setItem("myapp-selectvalselected",$("#dropdown").val()); //to retain selected value
})
Now on document load;
window.onload=function()
{
if(localStorage.getItem("myapp-selectval")!="" && localStorage.getItem("myapp-selectval")!=undefined)
{
$("#dropdown").html(localStorage.getItem("myapp-selectval"));
$("#dropdown").val(localStorage.getItem("myapp-selectvalselected")); //to get previously selected value
}
}
Once again as I said in comment it's not a good solution.
You can get the values easily by making use of the model attribute present in the select element.
First add a onclick function like so
<input type="submit" name="submit" value="Submit" onclick="getValues()"/>
Then get the value on submit of the button(Entire code) Plunkr
I had a look at your code, the way your selectbox rendering is setup we have to explicitly call the updateSelect() function for the options to work well. This function makes your selectbox "dynamic".
var first = localStorage.getItem("firstDropDown");
var second = localStorage.getItem("secondDropDown");
var third = localStorage.getItem("thirdDropDown");
if(first !== null && second !== null && third !== null) {
setValues(); //this should come after getting the values above
}
function getValues() {
var first = document.getElementsByTagName("SELECT")[0].getAttribute("model");
var second = document.getElementsByTagName("SELECT")[1].getAttribute("model");
var third = document.getElementsByTagName("SELECT")[2].getAttribute("model");
localStorage.setItem("firstDropDown", first);
localStorage.setItem("secondDropDown", second);
localStorage.setItem("thirdDropDown", third);
}
//on load when this function is called globally, the values from the localStorage will be set to the dropdown values.
function setValues() {
//for first dropdown
document.getElementsByTagName("SELECT")[0].setAttribute("model", first);
document.getElementsByTagName("SELECT")[0].value = first;
updateSelect(document.getElementsByTagName("SELECT")[0]);
//for second dropdown
document.getElementsByTagName("SELECT")[1].setAttribute("model", second);
document.getElementsByTagName("SELECT")[1].value = second;
updateSelect(document.getElementsByTagName("SELECT")[1]);
//for third dropdown
document.getElementsByTagName("SELECT")[2].setAttribute("model", third);
document.getElementsByTagName("SELECT")[2].value = third;
updateSelect(document.getElementsByTagName("SELECT")[1]);
}
To retain the value you have no choice but to use a window.localStorage like so -
localStorage.setItem("firstDropDown", first);
localStorage.setItem("secondDropDown", second);
localStorage.setItem("thirdDropDown", third);
Then fetch the value
var first = localStorage.getItem("firstDropDown");
var second = localStorage.getItem("secondDropDown");
var third = localStorage.getItem("thirdDropDown");
If the user is going to refresh the page, your best bet is to have the server send down the value that the user just submitted with the new page load.
If that's impossible for some reason, you can use localStorage:
$(document).ready(function(){
var prevVal = localStorage.getItem('selectValue');
prevVal && $('#dropdown').val(prevVal);
$('#dropdown').change(function(){
var option = $(this).val();
localStorage.setItem('selectValue', option);
});
});
Keep in mind that not all browsers support this API yet.
EDIT: If you don't need the browser to refresh, you can use Ajax:
// ...
$('#myForm').submit(function (event) {
event.preventDefault();
var option = $('#dropdown').val();
var fd = new FormData();
fd.append('dropdown', option);
$.ajax('/path/to/form/target/', {
method: 'POST',
formData: fd
});
// ...
});
//...
Although I assume OP's question asks for a JS solution, I do want to bring something else to the table because I was also searching to solve the same problem. However, I ended up solving it in a manner that I considered to be satisfying.
In my case I'm using Flask as the backend, but I believe the solution should be at least similar for other use cases.
On the HTML where the dropdown is located, I pass a variable from the backend which is originally set to be some default value of my choice. Let's call this variable default_var_set_in_backend. This will ultimately be fed to the HTML and will look like as follows:
<select name="some_name_you_want" onchange="this.form.submit()">
<option selected="selected">{{ default_var_set_in_backend }}</option>
Once a change is made in the dropdown value by the user (in my case it's a GET request), I update the variable default_var_set_in_backend to be equal to this new choice. Now, the HTML will reflect the latest choice made by the user once the page refreshes.
I have a function that queries a database for info, when a button is clicked. This info gets written to innerHTML of a label. When this function returns, I read the innerHTML of this label. Problem is, it always returns the old value, not the new value that was pulled from the database. The label on the scree is displaying the correct value, though. When I click the button again, the value that I was expecting on the previous click, is now given. Seems like a timing issue but can't seem to figure it out.
example:
SQL Data - cost = 10
I expect to see 10 alerted to me when I click the button. I get a blank alerted to me, even though 10 is now in the label. When I click the button again, 10 is alerted, but 20 is now in the label.
function getInfo() {
var ctlMonthly = document.getElementById("cellMonthlyCost")
getSQLData(ctlMonthly);
alert(ctlMonthly.innerHTML);
}
function getSQLData(ctlCell){
...
var my_ctlCell = document.getElementById(ctlCell);
$.each(objData.items, function() {
my_ctlCell.innerHTML = this.Param1
});
...
}
Thanks.
you need to add the alert after the data is received from the database. I am assuming that you're sending an ajax request to fetch data. You will be able to get the new value in the callback of you're ajax request function.
Currently what is happening in your code is that
1. getSQLData(ctlMonthly);
// This sends a request to the data base to fetch data
2. alert(ctlMonthly.innerHTML);
// This shows the current value in an alert
3. data is received and shown in the label
This process happens so fast that you don't notice the difference between step 2 and 3.
Is this what you want?
I used a callback function
function getInfo() {
var ctlMonthly = document.getElementById("cellMonthlyCost")
getSQLData(ctlMonthly,alertInfo);
}
function alertInfo(info){
alert(info);
}
function getSQLDate(ctlCell,callbackFn){
...
var my_ctlCell = document.getElementById(ctlCell);
$.each(objData.items, function() {
my_ctlCell.innerHTML = this.Param1;
callbackFn(this.Param1);
});
...
}
to piggyback on Himanshu's answer, your request to your server is async. Meaning javascript will execute the GET request and continue on with the script, when the requests comes back from the server it will run whatever callback you give it. ( i.e. update label tag )
assuming getSQLData is a ajax call or something promised based, something like:
function getSQLData(ctlCell){
return $.get('/sql/data').done(function () {
var my_ctlCell = document.getElementById(ctlCell);
$.each(objData.items, function() {
my_ctlCell.innerHTML = this.Param1
});
});
}
you can change your code to:
function getInfo() {
var ctlMonthly = document.getElementById("cellMonthlyCost")
getSQLData(ctlMonthly)
.done(function () {
alert(ctlMonthly.innerHTML);
});
}
Basically the difference is your telling javascript to alert the innerHTML after the requests comes back from the server.
The more correct answer would be to alert the data straight from the response instead of reading from the DOM.
I'm using javascript but not jQuery.
Let's say I have 3 users in my database [Kim,Ted,Box] and 3 buttons as below:
<button class="user">Kim</button>
<button class="user">Ted</button>
<button class="user">Box</button>
<div id="displayArea"></div>
If a person clicks on any of the buttons it will display the user information in the div.
Assume I click on the Kim button and it uses ajax and displays the information of Kim. And now I click Ted it also calls a new ajax function to get the data. But when I click Kim again I call the new ajax function to get the data rather than get the data from cache or some place. How can I achieve it without getting the data from ajax function if the data is loaded before?
The reason why I need this is because I don't want the user to wait to get the data again that they loaded before.
Add one level of abstraction by creating a function that takes care of the caching and either returns the data from the cache or makes an Ajax request. For example:
var getDataForUser = (function() {
/**
* We use an object as cache. The user names will be keys.
* This variable can't be accessed outside of this function
*/
var cache = {};
/**
* The function that actually fetches the data
*/
return function getDataForUser(user, callback) {
if (cache.hasOwnProperty(user)) { // cache hit
callback(cache[user]);
} else {
// somehow build the URL including the user name
var url = ...;
makeAjaxRequest(url, function(data) { // cache miss
cache[user] = data; // store in cache
callback(data);
});
}
};
}());
Then you make the call
getDataForUser('John', function(data) { /*...*/ });
twice and the second time it will hit the cache.
Background
I'm writing an asynchronous comment system for my website, after reading plenty of tutorials on how to accomplish this I started building one from scratch. The comments are pulled using a JSON request and displayed using Javascript (jQuery). When the user adds a new comment it goes through the hoops and finally is sent via AJAX to the backend where it's added to the database. In the success section of the AJAX request I had the script empty the comments, then repull the new list (including the new post) and redisplay them.
Problem
While that was all nice, since it's making the page much shorter, then much longer it messes up where the user is viewing the page. I wanted to have it readjust the page back down to the end of the comment list (where the add comment form is). It also re-enables the add button, which was disabled when the clicked it to prevent impatient people from spamming.
$('#commentList').empty();
getComments('blog', $('input#blogId').val());
window.location = "#addComment";
$('#comAdd').removeAttr('disabled');
While this worked all well and good in theory, it seemed that the browser was getting ahead of itself and processing the window.location before the getComments function was done. So I read a little more and googled it and it seemed people were saying (for similar problems) to use callback functions, so I came up with this:
$('#commentList').empty();
getComments('blog', $('input#blogId').val(), function() {
window.location = "#addComment";
$('#comAdd').removeAttr('disabled');
});
This generates no javascript errors according to FireFox, but nothing within the callback function is working, it's not re-enabling the button nor changing the window.location.
Any ideas? Better ways to go about it? Do I have a glaring typo that I can't seem to see?
Thanks!
Update
I was under the impression the callback functions were a standard thing you could use.
function getComments(type, id)
{
$.getJSON("/ajax/"+type+"/comments?jsoncallback=&id="+id, function(data) {
for (var x = 0; x < data.length; x++)
{
var div = $("<div>").addClass("comment").appendTo("#commentList");
var fieldset = $("<fieldset>");
var legend = $("<legend>").addClass("commentHeader");
if ( data[x].url == "" )
{
legend.text((x+1) + ' - ' + data[x].name);
}
else
{
$("<a>").attr({href: data[x].url}).text((x+1) + ' - ' + data[x].name).appendTo(legend);
}
legend.appendTo(fieldset);
$("<div>").addClass("date").text(data[x].timestamp).appendTo(fieldset);
$("<p>").addClass("comment").text(data[x].content).appendTo(fieldset);
fieldset.appendTo(div);
}
});
}
This is called on document ready. Pulling all the comments and displaying them inside the #commentList div. When the user submits his/her comment it performs an AJAX request to a PHP script that adds the new comment to the database, upon success of this I have this:
$('#commentList').empty();
getComments('blog', $('input#blogId').val());
window.location = "#addComment";
$('#comAdd').removeAttr('disabled');
Deletes all the comments from the page.
Uses JSON to request the comments again (including the users new one).
Moves the page to the #addComment anchor, which is where their new comment would be displayed.
Re-enables the add comment button.
The problem is that the browser does the window.location line before the getComments function is done rendering all the comments, so as the page grows the user isn't looking anywhere near their new comment.
I expect here the problem is your getComments() function (for which more detail is required). You're supplying a third argument being a callback but does the function actually use a callback? What is it doing?
Certain jQuery functions provide callbacks but this isn't an automatic feature. If you're waiting for a user to type a comment you need to trigger the relevant event when they click "Done" or whatever they do.
Ok, try this:
function get_comments(type, id, callback) {
$.getJSON("/ajax/"+type+"/comments?jsoncallback=&id="+id, function(data) {
for (var x = 0; x < data.length; x++) {
var div = $("<div>").addClass("comment").appendTo("#commentList");
var fieldset = $("<fieldset>");
var legend = $("<legend>").addClass("commentHeader");
if ( data[x].url == "" ) {
legend.text((x+1) + ' - ' + data[x].name);
} else {
$("<a>").attr({href: data[x].url}).text((x+1) + ' - ' + data[x].name).appendTo(legend);
}
legend.appendTo(fieldset);
$("<div>").addClass("date").text(data[x].timestamp).appendTo(fieldset);
$("<p>").addClass("comment").text(data[x].content).appendTo(fieldset);
fieldset.appendTo(div);
if (typeof callback != 'undefined') {
callback();
}
}
});
}
Note: the difference here is that a third argument is supplied to get_comments() which is a callback that'll be called at the end of your $.getJSON() callback. That'll give you the proper ordering you want.
I might also suggest not constructing the HTML like that but including it in your page and hiding/unhiding it as necessary. It tends to be much more performant that dynamic HTML and have less issues (eg new HTML, unless you use $().live() will not have relevant event handlers).
Edit: Made the callback optional as per the comments. With the above code you can call the function without or without the callback.
Simple. Re-enable the button and go to the anchor after you receive the request and process the information. Like so:
function getComments(type, id)
{
// ADDED
$('#commentList').empty();
$.getJSON("/ajax/"+type+"/comments?jsoncallback=&id="+id, function(data) {
for (var x = 0; x < data.length; x++)
{
var div = $("<div>").addClass("comment").appendTo("#commentList");
var fieldset = $("<fieldset>");
var legend = $("<legend>").addClass("commentHeader");
if ( data[x].url == "" )
{
legend.text((x+1) + ' - ' + data[x].name);
}
else
{
$("<a>").attr({href: data[x].url}).text((x+1) + ' - ' + data[x].name).appendTo(legend);
}
legend.appendTo(fieldset);
$("<div>").addClass("date").text(data[x].timestamp).appendTo(fieldset);
$("<p>").addClass("comment").text(data[x].content).appendTo(fieldset);
fieldset.appendTo(div);
}
// ADDED
window.location = "#addComment";
$('#comAdd').removeAttr('disabled');
});
}
Personal opinion: rather than fetching all comments, why not fetch comments from a certain date? When you load the page, include a server time in the response. The Javascript uses this to query behind the scenes (to automatically check for new comments). The JSON response includes a new server time, which is used in the next response.
How would you handle deleted comments? Easy: have a deleted_on column in your database table, query it, and spit that out in the JSON response along with new posts.
Suggestion: instead of #addcomment, ID comments by timestamp.