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.
Related
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).
I have this old "closed" system where it runs IE in its own container, meaning I have to code like a caveman in many cases because I can't use any browser developer tools/console to x-ray the objects being returned from the remote system.
Now, the specific function I'm looking at is a callback from a third party (it gets complicated) that returns what I am willing to bet is a standard JSON object.
function core_OnUeDeltaItemsUpdate(dataType, oData)
{
if (dataType == "Units")
{
// Bail if dispatch report xml hasn't been loaded yet.
if (oXml == null)
return;
..... does lots of stuff
// Reload the content in case any of our displayed units changed
processUeDelta.click();
}
}
... at the bottom of the page
<button style="display:none;" type="button" id="processUeDelta"/>
and the attached javascript file that I was hoping would use jQuery
$(function(){
$("#processUeDelta").click(function(){
var i = 0;
alert(this.ueDelta);
for(var propertyName in this.ueDelta)
{
i++;
alert("property " + i + ": " + oData[propertyName]);
}
});
});
Now, currently the last function that binds itself to the hidden button cannot parse oData. I'm stuck on two things here.
I'm not sure how to pass the oData object to the attached eventhandler
I'm not too keen on this design, perhaps there is another way were I can take out the intermediary button so I can then process the JSON data object oData.
Points of note:
This is based on a data pump, so this callback is being called on an average of 5s.
I am limited to using jQuery 1.7.1
I cannot see the object, my browser cannot act as a test harness, there are too many moving parts for me to be able to test it from outside the application.
You can replace the core_OnUeDeltaItemsUpdate function with your own and then call the original core_OnUeDeltaItemsUpdate function. In your jQuery file do something like this
$(document).ready(function(){
window._core_OnUeDeltaItemsUpdate = core_OnUeDeltaItemsUpdate;
window.core_OnUeDeltaItemsUpdate = function(dataType, oData){
// pass the parameters into the original function
_core_OnUeDeltaItemsUpdate(dataType, oData);
// do whatever you need to do with oData
var i = 0;
alert(this.ueDelta);
for(var propertyName in this.ueDelta)
{
i++;
alert("property " + i + ": " + oData[propertyName]);
}
}
});
I'm trying to put together a script that will add to the concept of load, and extend it outward in a fashion that load can be called to multiple pages, in search of instances of a div.
For example, consider the code below:
<div class="loadStuffHere">
</div>
$('.loadStuffHere').load('/wiki.htm .posts');
So, the code above works just fine, but I'm not sure how to get it to search more than one page at a time.
The second page I would need to search in this case would be: /wiki.htm?b=1&pageindex=2. After the script searches the first page, then it would need to search the second. After the second page is searched, then the script would need to first auto increment the number 2, and change it to a three, and search that page, and then continue to do so, until it hits a page that doesn't exist, and then return false and end it's execution. So the search and load process would consist of this:
/wiki.htm
/wiki.htm?b=1&pageindex=2
/wiki.htm?b=1&pageindex=3
/wiki.htm?b=1&pageindex=4
/wiki.htm?b=1&pageindex=5
/wiki.htm?b=1&pageindex=6
And it would continue in that manor, until it hits a page that doesn't exist yet, as this is a pagination system.
Is this something that can be done, and if so, what would it look like?
Any help is very appreciated!
P.s. If you are thinking I'm likely attempting something that would be easier achieved with some server side coding, you are right. I don't have access to it however, regretfully.
You could do something like:
function loadWikiPosts(page_index) {
if (page_index != null) { url += '?b=1&pageIndex='+page_index; }
else { page_index = 1; /* for next time if this one fails */ }
url += ' .posts'; // to grab just the posts
// Send the AJAX request
$('.loadStuffHere').load(url, function(response, status, request) {
if (status == "error") { console.log(response); throw "Load returned an error"; }
if ($('.loadStuffHere').find('.posts').length == 0) {
page_index++;
loadWikiPosts(page_index);
}
});
}
try {
loadWikiPosts();
} catch (exception) {
// do something w/the exception message
}
Create an array with all your results, using the following code:
JavaScript/jQuery
var linkArray=["/wiki.htm",
"/wiki.htm?b=1&pageindex=2",
"/wiki.htm?b=1&pageindex=3",
"/wiki.htm?b=1&pageindex=4",
"/wiki.htm?b=1&pageindex=5",
"/wiki.htm?b=1&pageindex=6"];
for (var i in linkArray)
{
var content = "";
$('.loadStuffHere').append($(content).load(linkArray[i]+' .posts'));
}
The code above was not tested.
I have two HTML pages that work in a parent-child relationship in this way:
The first one has a button which does two things: First it requests data from the database via an AJAX call. Second it directs the user to the next page with the requested data, which will be handled by JavaScript to populate the second page.
I can already obtain the data via an ajax call and put it in a JSON array:
$.ajax({
type: "POST",
url: get_data_from_database_url,
async:false,
data: params,
success: function(json)
{
json_send_my_data(json);
}
});
function json_send_my_data(json)
{
//pass the json object to the other page and load it
}
I assume that on the second page, a "document ready" JavaScript function can easily handle the capture of the passed JSON object with all the data. The best way to test that it works is for me to use alert("My data: " + json.my_data.first_name); within the document ready function to see if the JSON object has been properly passed.
I simply don't know a trusted true way to do this. I have read the forums and I know the basics of using window.location.url to load the second page, but passing the data is another story altogether.
session cookie may solve your problem.
On the second page you can print directly within the cookies with Server-Script tag or site document.cookie
And in the following section converting Cookies in Json again
How about?
Warning: This will only work for single-page-templates, where each pseudo-page has it's own HTML document.
You can pass data between pages by using the $.mobile.changePage() function manually instead of letting jQuery Mobile call it for your links:
$(document).delegate('.ui-page', 'pageinit', function () {
$(this).find('a').bind('click', function () {
$.mobile.changePage(this.href, {
reloadPage : true,
type : 'post',
data : { myKey : 'myVal' }
});
return false;
});
});
Here is the documentation for this: http://jquerymobile.com/demos/1.1.1/docs/api/methods.html
You can simply store your data in a variable for the next page as well. This is possible because jQuery Mobile pages exist in the same DOM since they are brought into the DOM via AJAX. Here is an answer I posted about this not too long ago: jQuery Moblie: passing parameters and dynamically load the content of a page
Disclaimer: This is terrible, but here goes:
First, you will need this function (I coded this a while back). Details here: http://refactor.blog.com/2012/07/13/porting-javas-getparametermap-functionality-to-pure-javascript/
It converts request parameters to a json representation.
function getParameterMap () {
if (window.location.href.indexOf('?') === (-1)) {
return {};
}
var qparts = window.location.href.split('?')[1].split('&'),
qmap = {};
qparts.map(function (part) {
var kvPair = part.split('='),
key = decodeURIComponent(kvPair[0]),
value = kvPair[1];
//handle params that lack a value: e.g. &delayed=
qmap[key] = (!value) ? '' : decodeURIComponent(value);
});
return qmap;
}
Next, inside your success handler function:
success: function(json) {
//please really convert the server response to a json
//I don't see you instructing jQuery to do that yet!
//handleAs: 'json'
var qstring = '?';
for(key in json) {
qstring += '&' + key + '=' + json[key];
qstring = qstring.substr(1); //removing the first redundant &
}
var urlTarget = 'abc.html';
var urlTargetWithParams = urlTarget + qstring;
//will go to abc.html?key1=value1&key2=value2&key2=value2...
window.location.href = urlTargetWithParams;
}
On the next page, call getParameterMap.
var jsonRebuilt = getParameterMap();
//use jsonRebuilt
Hope this helps (some extra statements are there to make things very obvious). (And remember, this is most likely a wrong way of doing it, as people have pointed out).
Here is my post about communicating between two html pages, it is pure javascript and it uses cookies:
Javascript communication between browser tabs/windows
you could reuse the code there to send messages from one page to another.
The code uses polling to get the data, you could set the polling time for your needs.
You have two options I think.
1) Use cookies - But they have size limitations.
2) Use HTML5 web storage.
The next most secure, reliable and feasible way is to use server side code.
http://rndnext.blogspot.com/2009/02/jquery-ajax-tooltip.html
I want to implement something like the link above. Now this pops up the box's fetching data from some page, using PageID and what not. I want the content of that popup box to have simple HTML stuff in it and it will be bound later. The one above has got Ajax that I am not familiar with.
What do I need to change in the code? All I want is a simple pop up box that looks exactly like the one above, opens up the same way and all, BUT has got simple HTMl stuff in it. What and where do I make changes?
Although you haven't posted any of your attempts at doing this yourself, I will try to help you out.
If I understand correctly, you want to get rid of the AJAX and just add normal HTML right?
Well, I will at least tell you where to put your HTML to get you started.
You see on their website, at line 51 it says:
$('#personPopupContent').html(' ');
You can change the nbsp bit to any HTML you want.
For example:
$('#personPopupContent').html('<strong>My strong text</strong>');
You can also delete from lines 53 to 74 where it says:
$.ajax({
type: 'GET',
url: 'personajax.aspx',
data: 'page=' + pageID + '&guid=' + currentID,
success: function(data)
{
// Verify that we're pointed to a page that returned the expected results.
if (data.indexOf('personPopupResult') < 0)
{
$('#personPopupContent').html('<span >Page ' + pageID + ' did not return a valid result for person ' + currentID + '.Please have your administrator check the error log.</span>');
}
// Verify requested person is this person since we could have multiple ajax
// requests out if the server is taking a while.
if (data.indexOf(currentID) > 0)
{
var text = $(data).find('.personPopupResult').html();
$('#personPopupContent').html(text);
}
}
});
Since you won't be using it.
I hope that helped you.