jQuery unable select element from getJSON - ajax

I'm using the .each method with the .getJSON method to print out objects in a JSON file. This works fine, however I am unable to add a click function to an element that has been printed out. I am trying to bind a function to the div with 'click' ID.
var loadData = function () {
$.getJSON("profiles2.json", function (data) {
var html = [];
html.push("<div id='click'>Click here</div>");
$.each(data.profiles, function (firstIndex, firstLevel) {
html.push("<h2>" + firstLevel.profileGroup + "</h2>");
});
$("#data").html(html.join(''));
});
};
$(document).ready(function () {
loadData();
$("#click").click(function () {
console.log('clicked');
});
});

$.getJSON() (like other Ajax methods) is asynchronous, so it returns immediately before the results have come back. So your loadData() method also returns immediately and you then try to bind a handler to an element not yet added.
Move the .click(...) binding into the callback of $.getJSON(), after adding the element(s), and it will work.
Alternatively, use a delegated event handler:
$("#data").on("click", "#click", function() {
console.log('clicked');
});
...which actually binds the handler to the parent element that does exist at the time. When a click occurs it then tests whether it was on an element that matched the selector in the second parameter.
And as an aside, don't bind click handlers to divs unless you don't care about people who are physically unable to (or simply choose not to) use a mouse or other pointing device. Use anchor elements (styled as you see fit) so that they're "click"-accessible via the keyboard and the mouse.

$.getJSON is an asynchronous call and probably hasn't finished by the time you are trying to bind to the element that it injects into your DOM. Put your binding inside the $.getJSON call after you append the element to the page at the bottom.

Related

jQuery Mobile 1.4+ pagecontainerbeforechange bug?

I am experimenting with the new way of handling page events in jqM and have run into a curious issue. When handling the pagecontainerbeforechange event
$(document).on('pagecontainerbeforechange',function(e,u){test(e,u,'changing');})
function test(e,u,msg){console.log($(u.toPage));}
Attempting to put a jQuery object wrapper around u.toPage - as done above - produces strange behavior.
Check out this fiddle to see what I mean
Click on the Second Page button and then view the console. Nothing will happen (the second page is not shown) and you will see a message along the lines of *Uncaught error:syntax error, unrecognized expression http://jsfiddle.net/egn7g5xb/1/show/#second
Now comment out Line 7 and run the fiddle again. No such issue this time and the second page gets shown.
Perhaps someone here might be able to explain what is going on here?
On initial run, jQuery Mobile creates a fake page before navigating to first page in DOM. At that stage, pagecontainerbeforechange fires twice and returns .toPage as an object.
Later on, upon navigating to other pages, it fires twice again; however, it returns a string first time (URL/hash) and second time it returns an object which is the page itself.
Therefore, when using that event, you have to determine whether .toPage is an object or a string.
$(document).on("pagecontainerbeforechange", function (e, data) {
if (typeof data.toPage == "string") {
/* parse url */
}
if (typeof data.toPage == "object") {
/* manipulate page navigating to */
}
});
Note that pagecontainerbeforetransition is similar to beforechange, however, it fires once and returns .toPage as an object.
First, create your pagecontainer events within $(document).on("pagecreate", "#first", function(){ .. }).
Then the selector for these events should be $(":mobile-pagecontainer") or $("body") NOT $(document).
function test(e,u,msg)
{
console.log(msg);
var IsJQ = u.toPage instanceof $;
console.log(IsJQ);
if (IsJQ){
console.log(u.toPage.data());
} else {
console.log(u.toPage);
}
console.log('---');
}
$(document).on("pagecreate", "#first", function(){
$(":mobile-pagecontainer").on('pagecontainerbeforechange', function (e, u) {
test(e,u,'changing');
});
$(":mobile-pagecontainer").on('pagecontainerchange',function(e,u){
test(e,u,'changed');
});
});
Updated FIDDLE

BackboneJS calling a views render function from an event bound to a collection

RoomsDGView = Backbone.View.extend({
collection: roomcollection,
initialize: function(){
var template = _.template( $("#search_template").html(), {} );
this.$el.html( template );
this.collection.bind('add', this.modeladded);
this.collection.bind('remove', this.modelremoved);
this.collection.bind('change', this.collectionchanged);
console.log(this);
this.render();
},
render: function(){
// Compile the template using underscore
console.log("running the render function...");
//renderrender();
/*$("#roomsList").jqGrid('clearGridData');
roomcollection.each(function (room,i){
var temp = jQuery.parseJSON(JSON.stringify(room));
$("#roomsList").jqGrid('addRowData', i,{idrooms: temp["idrooms"], roomname: temp["roomname"],
occupants: temp["occupants"]});
});*/
},
events: {
"click input[type=button]": "doSearch"
},
doSearch: function(){
// Button clicked
console.log(this);
},
modeladded: function() {
console.log("room added...");
$("#roomsList").jqGrid('clearGridData');
//My intent is to call the views render function
//her. I tried using this.render() and also
//this.collection.bind('add', this.modeladded(this));
//modeladded: function(view) {
// view.render();
console.log(this);
},
modelremoved: function() {
console.log("room removed...");
$("#roomsList").jqGrid('clearGridData');
},
collectionchanged: function() {
console.log("room changed...");
$("#roomsList").jqGrid('clearGridData');
}
});
I have tried many different ways to call the views render: method from inside the code for modeladded:. Use of this.render inside model added shows that the this object at that point has no render function. I also tried passing the view in something like:
this.collection.bind('add', this.modeladded(this));
modeladded: function(view) {
view.render();
which also leads to a console error that no render() can be found. Does anyone know how to call the views render: from inside modeladded?
For the time being I moved the render function out of the views render: and into a JavaScript function declared renderGlobal() declared in global scope and I know it does work that way but I don't think that is really the backbone.js way.
This is the error that is coming out of the console:
Uncaught TypeError: Object [object Object] has no method 'render'
Thank you for posting....
You're binding your event handler using bind (AKA on):
this.collection.bind('add', this.modeladded);
But, as usual with JavaScript, the value of this inside a function depends on how the function is called, not how it is defined (ignoring bound functions of course). You're not specifying a specific this for your function anywhere so you're not getting any particular this when it is called. If you give bind the third context argument:
this.collection.bind('add', this.modeladded, this);
// ------------------------------------------^^^^
then Backbone will call modeladded with the specific this and you'll find that this inside modeladded will be your view.
You could also use _.bind, Function.prototype.bind, or $.proxy to produce a bound version of your callback:
this.collection.bind('add', _(this.modeladded).bind(this));
this.collection.bind('add', this.modeladded.bind(this));
this.collection.bind('add', $.proxy(this.modeladded, this));
All of those produce new functions so you won't be able to unbind them without stashing the bound functions somewhere. You'll usually avoid using these when you have the option to specify the context (AKA this) explicitly.
There's also listenTo:
listenTo object.listenTo(other, event, callback)
Tell an object to listen to a particular event on an other object. The advantage of using this form, instead of other.on(event, callback, object), is that listenTo allows the object to keep track of the events, and they can be removed all at once later on. The callback will always be called with object as context.
So you can (and should) say this:
this.listenTo(this.collection, 'add', this.modeladded);
That will take care of giving you the desired this and makes it easier to clean up your event handlers when you're done with them. Similarly for the other event handlers you're using.

Get click handler from element in JQuery

How can I get a reference to the click handler of an element in JQuery?
Here is what I am trying to do:
Store the click handler,
Change the click handler for the next click,
Restore the original click handler
var originalClick = $(settings.currentTarget).click;
$(settings.currentTarget).off("click");
$(settings.currentTarget).click(function (e) {
e.preventDefault();
settings.model.modal.close();
$(settings.currentTarget).off("click");
$(settings.currentTarget).click(originalClick);
});
The above code works the first time, however, when I click on the element again it fails:
Uncaught TypeError: Object [object HTMLAnchorElement] has no method 'on'
Update:
I now realize that this is a really bad design that I am trying to do. I have resolved this issue by maintaining a visibility boolean in my viewmodel, if it is true, don't re-open the modal.
$(...).click is the jQuery click() method, not your event handler.
You can use an undocumented internal API to get the list of event handlers:
jQuery._data( elem, "events" );
What happens if you try it this way?
var originalClick = $(settings.currentTarget).click;
$(settings.currentTarget).off("click");
$(settings.currentTarget).on("click",function (e) {
e.preventDefault();
settings.model.modal.close();
$(settings.currentTarget).off("click");
$(settings.currentTarget).click(originalClick);
});

Element within AJAX-fetched HTML cannot by found by getElementbyID

It's because it hasn't initialized yet. If I put in an alert(), it allows the browser to be freed up and initialize the element. The question, then, is how can I force it to initialize the element without using an alert box?
Here's the pertinent code...
$().ready(function() {
AJAX_LoadResponseIntoElement ("mybody", "skin1.txt");
AJAX_LoadResponseIntoElement ("contentdiv", "index.txt");
initPage();
});
AJAX_LoadResponseIntoElement (id, file) simply fetches "file" with an XMLHTTPRequest and loads it into id's innerHTML.
initPage() works until it calls setContentHeight(), which works up until this point:
if (DOMheight > y_lbound) { document.getElementById('containerdiv').style.height = (DOMheight+container_ymod) + 'px'; }
If I put alert(document.getElementById('containerdiv')); prior to this line, it says that it's NULL, even though the "containerdiv" element should have been loaded with the very first call to AJAX_LoadResponseIntoElement.
If I put TWO copies of alert(document.getElementById('containerdiv')); prior to this line, the first one says NULL, and the second says "[Object HTMLDivElement]".
Clearly, then, it is just a problem of "containerdiv" not being initialized.
So, once again, the question is how can I force the initialization of these elements after being fetched by the XMLHTTPRequest, without using an alert()?
It seems that AJAX_LoadResponseIntoElement() is asynchronous, since it uses XMLHTTPRequest internally. One way to solve your problem would be to modify that function so it takes a callback function argument and calls it when the request succeeds:
function AJAX_LoadResponseIntoElement(elementId, fileName, callback)
{
// Issue XMLHTTPRequest and call 'callback' on success.
}
Then use the modified function like this:
$(document).ready(function() {
AJAX_LoadResponseIntoElement("mybody", "skin1.txt", function() {
AJAX_LoadResponseIntoElement("contentdiv", "index.txt", initPage);
});
});

Event removal in Mootools, and syntax of event addition

So I have been adding my events thusly:
element.addEvent('click', function() {
alert('foobar');
});
However, when attempting to remove said event, this syntactically identical code (with "add" switched to "remove") does not work.
element.removeEvent('click', function() {
alert('foobar');
});
I assume this is because the two functions defined are not referenced the same, so the event is not technically removed. Alright, so I redefine the event addition and removal:
element.addEvent('click', alert('foobar'));
element.removeEvent('click', alert('foobar'));
Which works great, except now when the page loads, the click event is fired even before it's clicked!
The function is removed, though, which is great......
update: when you do .addEvent('type', function(){ }) and .removeEvent('type', function(){ }), even though the functions may have the same 'signatures', they are two separte anonymous functions, assigned on the fly. function 1 is !== to function 2 - hence there is no match when MooTools tries to remove it.
to be able to remove an exact handler, o:
function handler(){ ... }
el.addEvent('click', handler);
// .. later
el.removeEvent('click', handler);
Internally, events are actually a map of keys to functions in element storage. have a look at this fiddle i did a while back for another SO question - http://www.jsfiddle.net/mVJDr/
it will check to see how many events are stacked up for a particular event type on any given element (or all events).
similarly, removeEvent looks for a match in the events storage - have a look on http://jsfiddle.net/dimitar/wLuY3/1/. hence, using named functions like Nikolaus suggested allows you to remove them easily as it provides a match.
also, you can remove events via element.removeEvents("click") for all click events.
your page now alerts because you pass on alert as the function as well as execute it with the params 'foobar'. METHOD followed by () in javascript means RUN THE METHOD PRECEDING IT IMMEDIATELY, NOT LATER. when you bind functions to events, you pass the reference (the method name) only.
to avoid using an anonymous function and to pass argument,s you can do something like:
document.id('foobar').addEvent('click', alert.bind(this, 'foo'));
as bind raps it for you, but removing this will be even more complicated.
as for event delegation, it's:
parentEl.addEvents({
"click:relay(a.linkout)": function(e, el) {
},
"mouseover:relay(li.menu)": function(e, el) {
}
});
more on that here http://mootools.net/docs/more/Element/Element.Delegation#Element:removeEvent
keep in mind it's not great / very stable. works fine for click stuff, mouseenter is not to be used delegated, just mouseover - which means IE can fire mouseout when it should not. the way i understand it, it's coming improved in mootools 2.0
edit updating to show an example of bound and unbound method within a class pattern in mootools
http://www.jsfiddle.net/wmhgw/
var foo = new Class({
message: "hi",
toElement: function() {
return this.element = new Element("a", {
href: "http://www.google.com",
text: "google",
events: {
"click": this.bar.bind(this), // bind it
"mouseenter": this.bar // unbound -> this.element becomes this
}
});
},
bar: function(event) {
event.stop();
// hi when bound to class instance (this.message will exist)
// 'undefined' otherwise.
console.log(this.message || "undefined");
}
});
document.id(new foo()).inject(document.body);
the mouseenter here will be unbound where this will refer to the default scope (i.e the element that triggered the event - the a href). when bound, you can get the element via event.target instead - the event object is always passed on to the function as a parameter.
btw, this is a slightly less familiar use of class and element relation but it serves my purposes here to illustrate binding in the context of classes.
assig the function to a variable and use the same reference to add and remove the event.
if you use an anonymous function you will get to different references
var test = function(){ alert('test: ' + this.id); }
$('element').addEvent('click', test);
...
$('element').removeEvent('click', test);
addEvent : Attaches an event listener to a DOM element.
Example -
$('myElement').addEvent('click', function(){
alert('clicked!');
});
removeEvent : Works as Element.addEvent, but instead removes the specified event listener.
Example -
var destroy = function(){ alert('Boom: ' + this.id); } // this refers to the Element.
$('myElement').addEvent('click', destroy);
//later...
$('myElement').removeEvent('click', destroy);
This means when you add an event with a eventhandler not an anonymous function if you than remove the event than it will be removed.

Resources