I'm playing with mobile app creation with jQuery and an api I built using Slim.
My overall goal is to create a list of buttons that when clicked will call a function with the appropriate parameters.
I am successfully sending my request to slim, getting a list of users and storing the results in a global array of User objects.
Next I am iterating through the array appending html to the document to show the buttons. At the same time I am adding a click listener. The listener gets added, but not like I expect.
Users is an array of user objects with global scope. A user object can be boiled down to [{"name":"User's name"}].
Also relevant might be the fact that I'm doing this in a call back.
$.getJSON(url, function(data){
for(i=0;i<data.length;i++)
{
var u = new User();
u.name = data[i]['name'];
}
})
//The success handler DOES get called.
.success(function() {
for(var i=0; i<users.length; i++)
{
//this part works. users[i] is accessed as expected.
var html_string = '<li>'+users[i].name+'</li>';
$("#myUL").append(html_string);
$("#myUL li:last").click(function(){
//logs undefined
console.log(users[i].name);
//Handle the event
eventHandleFunction()
});
}
})
I'm well enough versed in programming in general to know that what I'm doing does not fall into best practices, but I am so illiterate in javascript that I don't know the right way to fix it. In addition to the howto answer I'd really appreciate anyone who took some time to point me to useful resources.
Update: After reading an answer about using delegate to assign handlers I have updated my code a bit. Now it looks like this. Note: I've only not updated the code above in order to get an answer to the 'why' part of the original question.
$("#myUL").delegate("li","click",function(){
//sets the attribute name of the global var user
user.name = $(this).text();
//lets see what user.name is now. Oh, look. It's what i expect
alert(user.name);
//do whatever else i want to do here.
});
//This json request is now (i believe) entirely unnecessary to answer the question.
$.getJSON(url, function(data){
for(i=0;i<data.length;i++)
{
var u = new User();
u.name = data[i]['name'];
}
})
//The success handler DOES get called.
.success(function() {
//No longer relevant
})
you can use delegate
$("#myUL").delegate("li:last","click",function(){
//event handler code here
});
this way you dont have to explicitly attach click event handler to every dynamically added li in the DOM
if you are using jquery version 1.7+ then you can use .on method like
$("#myUL").on("click","li:last",function(){
//event handler code here
});
the why part
the reason i see is the scope
for(var i=0; i<users.length; i++)
{
var html_string = '<li>'+users[i].name+'</li>';
$("#myUL").append(html_string);
$("#myUL li:last").click(function(){
//logs undefined BECAUSE i is undefined inside the click handler
console.log(users[i].name);
//Handle the event
eventHandleFunction()
});
}
for(var i=0; i<users.length; i++) {
var numb = i,
html_string = '<li>'+users[numb].name+'</li>';
$("#myUL").append(html_string);
$("#myUL li:last").click(function(){
console.log(users[numb].name);
eventHandleFunction()
});
}
Related
I am trying to access specific albums via their location from a Facebook page via a graph API call and ultimately display them on a webpage.
Essentially I think I need to loop through the object that Facebook sends back (response in this case) and only store objects which contain the substring "Germany" in the location description to the variable albums. Currently nothing happens in the console when I trigger the method.
I'm kind of stuck at this point, I can make API calls and have them returned from the specific page via other functions, so my permission seems to be fine, I'm just structuring this function incorrectly (I think, I'm relatively new to JS and Jquery).
model.getPhoto = (function(data){
var albums;
FB.api(
'/--USER ID GOES HERE--/albums',
'GET',
{"fields":"link,location"},
function(response){
for (var i=0; i<response.length; i++){
if (response.albums.location[i] = "Germany"){
albums = response.albums.location[i];
i++;
}
console.log(albums);
}
}
);
});
You are setting model.getPhoto as a new function. If your code is complete, you never call this function. I added some lines then you can try to fix and see some results... Also changed some lines, created albums as an Array and put i++ outside of if to execute for loop correctly.
Last line calls your function.
model.getPhoto = (function(data){
FB.api('/--USER ID GOES HERE--/albums',
'GET',
{"fields":"link,location"},
function(response){
var albums = new Array();
for (var i=0; i<response.length; i++){
if (response.albums.location[i] == "Germany"){
albums.push(response.albums.location[i]);
}
i++;
console.log(albums);
}
});
});
model.getPhoto({var1: "value1"});
I suggest you play more with javascript and logics, take a look inside some libraries like jQuery itself, as you are not using it here, just pure JS, and have fun!
EDIT
When comparing, you must use == instead of =
Best,
Eder
I have a simple JS file that uses Jquery to apply rules to the loaded page.
I starts with the traditional $(document).ready(function(){
Nevertheless, when I load more posts (load more button) or submit a new post, those rules don't apply. I think I understand why...though it is not clear.
Is there a way to apply the same rules to each new added post? Is the only way defining events directly on the html code like e.g onclick....?
I may be a very simple question. I'll appreciate any answers :)
Thanks
JS Code
$(document).ready(function(){
(...)
$('button#cancel').on('click',function () {
$(this).parents('.footer').hide();
$(this).parents('.footer').siblings('.small-textarea-main-feed').removeClass('set-large');
$(this).parents('.footer').siblings('.small-textarea-main-feed').val('');
});
(...)
}); closes all
I am using the following code in load_questions.js to load a new post:
$('form.ajax').submit(function() {
//
var that = $(this),
url = that.attr('action'),
type = that.attr('method'),
data = {};
that.find('[name]').each(function(index, value) {
var that = $(this),
name = that.attr('name'),
value = that.val();
data[name] = value;
});
//event.preventDefault();
$.ajax({
url: url,
type: type,
data: data,
cache: false, // it will force requested pages not to be cached by the browse
success: function(html){
console.log(html);
$("ol#list-feed").prepend(html);
$("ol#list-feed li:first").slideDown(600);
document.getElementById('set-width1').value='';
document.getElementById('tags').value='';
if ($("ol#list-feed > li").size() <= 3) {
$('#loadmorebutton').hide();
} else {
$("ol#list-feed > li:last").remove();
$('#loadmorebutton').show();
}
}
});
//event.preventDefault();
return false;
});
I want that this type of rules apply to new posts I submit.
The DOMDocumentReady event fires exactly once on the page, when the entire HTML document has been loaded and parsed, so any DOM element you should be able to expect be on the page, will be.
Any DOM elements you add to the page from this point on, need to be decorated again. This can be as simple as calling the DOMDocumentReady handler again, in which case you'd want to make it a named function, and pass that named function to $(document).ready(...). Something like this:
var onReadyHandler = function() { };
$(document).ready(onReadyHandler);
$.ajax({
success: function(html) {
$("ol#list-feed").prepend(html);
onReadyHandler();
}
});
Now, it's likely that a better way of handling this (it's really unclear to me what precisely you're trying to accomplish, but that's not a real problem), is to not bind anything to your new posts at all. If you're concerned about events, bind the events to the container you know will be on the page, using 'event delegation' (jQuery link: http://api.jquery.com/delegate/). This pattern takes advantage of the fact that events 'bubble' in the DOM, meaning you can listen higher in the DOM then the elements you actually want to respond to, and just check that the click event happened on the event you do care about ($.delegate does this check automatically). The end result? You bind far fewer event handlers, since you're not decorating each post individually.
Alright, I know questions like this have probably been asked dozens of times, but I can't seem to find a working solution for my project. Recently, while using jQuery for a lot of AJAX calls, I've found myself in a form of callback hell. Whether or not jQuery is too powerful for this project is beyond the scope of this question. So basically here's some code that shows what's going on:
function check_form(table)
{
var file = "/models/"+table+".json";
var errs = {};
var xhr = $.getJSON(file, function(json)
{
for (key in json)
{
var k = key;
var r = json[k];
$.extend(errs, check_item("#"+k,r));
}
});
return errs;
}
And... as you can probably guess, I get an empty object returned. My original idea was to use some sort of onReadyStateChange idea that would return whenever the readyState had finally hit 4. This causes my application to hang indefinitely, though. I need these errors to decide whether or not the form is allowed to submit or not (as well as to tell the user where the errors are in the application. Any ideas?
Edit. It's not the prettiest solution, but I've managed to get it to work. Basically, check_form has the json passed to it from another function, instead of loading it. I was already loading it there, too, so it's probably best that I don't continue to load the same file over and over again anyways. I was just worried about overloading memory. These files aren't absolutely huge, though, so I guess it's probably okay.
The inline-function in your $.getJSON call will be run when the Ajax call have finished. Do your work inside that (callback) function.
Your check_form function should accept a callback to execute when the information becomes available later.
function check_form(table, callback)
{
var file = "/models/"+table+".json";
var xhr = $.getJSON(file, function(json)
{
var errs = {};
for (key in json)
{
var k = key;
var r = json[k];
$.extend(errs, check_item("#"+k,r));
}
callback(errs);
});
}
Then, have callers of check_form provide a callback:
var table = getSomeTable();
check_form(table, function(errs)
{
for (var key in errs)
{
var value = errs[k];
// do something with key/value pair
}
});
I'm unsure of the best practice for modifying the DOM based on an ajax response. I'll try to let the code do the talking because it's hard to explain.
// page has multiple checkboxes
$("input[type='checkbox']").live('click', function {
var cb = $(this); // for the sake of discussion i need this variable to be in scope
$("form").ajaxSubmit({ dataType: "script" });
}
The server sends a response back, and the js gets eval'd and that means "cb" is out of scope.
What I've done so far is create a couple of helper functions:
var target = undefined;
function setTarget(val) {
target = val;
}
function getTarget() {
return target;
}
And that turns the first snippet of code into this:
// page has multiple checkboxes
$("input[type='checkbox']").live('click', function {
setTarget($(this));
$("form").ajaxSubmit({ dataType: "script" });
}
Then on the server's response I call getTarget where I need to. This seems hackish... Any suggestions?
It's unclear what you're actually trying to do, but I feel like you want to be looking at the success parameter for that AJAX call. The success callback function should execute in parent scope and do what you're looking for.
See 'success' on this page in the jQuery docs.
So what you are trying to do is get the form to submit the content via ajax whenever the user checks/unchecks a checkbox? And because there are several checkboxes, you need to find out which one triggered the submit, so you can change its value to whatever is stored on the server?
If you submit the entire form everytime, why don't you reply with all the checkboxes values, and then change each and every one of them? If not, get the server to reply with the id and the value of the checkbox, then use jquery to find the checkbox with that ID and then change it's value.
How about:
jQuery(function($) {
// give it scope here so that the callback can modify it
var cb,
cbs = $('input[type="checkbox"]');
cbs.live('click', function {
// taking away var uses the most recent scope
cb = $(this);
// disable checkboxes until response comes back so other ones can't be made
cbs.attr('disabled', 'true'); // 'true' (html5) or 'disabled' (xhtml)
// unless you are using 'script' for something else, it's best to use
// a callback instead
$('form').ajaxSubmit({
success : function(response) {
// now you can modify cb here
cb.remove(); // or whatever you want
// and re-enable the checkboxes
cbs.removeAttr('disabled');
}
});
}
});
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.