jQuery click event handler attached to dom element - javascript

I am having hard time while building e-commerce cart module with jquery.
Lets say that if i write a tags in html like this:
<div class="add-to-cart">+</div>
and then target it in my app:
this.$products,
this.$pa,
this.$ip,
this.$products = $('.shopperProducts'),
this.$pa = this.$products.find('.shopperAdd');
var self = this;
this.$ip = function() {
var init = function(action, product) {
/.../
};
self.$pa.on('click', function(event) {
event.preventDefault();
init('add', this);
});
};
This method is possible while im displaying products because they are displayed by php on page refresh so i have all the + links generated by php on html.
The problem is on the checkout file, this is the page when i display entire cart filled with products, cart must be generated and handled in jQuery and AJAX.
And code that i showed you doesnt work in cart page beacuse those links are appended for each product via jQuery into the DOM.
I have been study possible methods and there are few, the most in favour is to do this:
$(document).on('click', self.$pa, function(event) {
The problem with that solution is that it also is considered practice to be avoided due to high resources drain, i can see the difference in execution time myselfe, it takes a lot longer on low end devices. Is there some neat trick that can be used or method that is considered good practice to do in that situation?
<--- EDIT (Solution) --->
Instead of calling:
this.$products = $('.shopperProducts'),
this.$pa = this.$products.find('.shopperAdd');
on the beginning, i have to call it after i load elements into DOM and then they became targetable, then i just have to use self.$ip(); and event handlers can be attached. Without using any sort of workarounds, the solution was just to change order of executing commands.

There are two main strategies that you can use for adding click handlers for elements that you dynamically add to the dom.
One, You can add click handlers to the DOM element each time you create one
var addToCartButton = $('<div class="add-to-cart">+</div>');
addToCartButton.on('click', function(){
init('add', this);
};
// then you add your DOM element to the page
$('.container').append(addToCartButton);
Two, you can have a master click event listener on the page listen for all clicks where your buttons fall, and in your click handler, figure out whether the user is clicking on your element or not. This is ultimately more efficient and you don't have to add or remove event handlers each time you add elements to your page. This pattern is called event delegation, and here's another post on Stack that probably explains it better than I can
What is DOM Event delegation?
$('.container').click(function(event){
if ($(event.target).is('.add-to-cart') || $(event.target).parents().is('.add-to-cart')) {
// handle add to cart
}
})
BTW, your use of the self variable doesn't actually do anything, and neither does declaring this.$pa. You're basically accessing the property "$pa" of your this object, but not doing anything it.

Related

jQuery scripts on ajax content WITHOUT using $(document).on

I am using an infinite scroll plugin which uses ajax.
When the 'next page' is loaded via ajax, all other ajax related scripts that are on the next page do not work. I have been told that I have to use 'delegated events'(ie change $(id).click() to $(document).on) - problem is that means editing multiple plugins and changing dozens of function calls.
Is there any way I can avoid changing everything to $(document).on and do something cool with the infinite scroll?????
I'd much rather modify the infinite scroll plugin rather than modifying other ajax related plugins to make them fit.
Unfortunately you have very few options here, and switching to delegated events is by far the best of them.
The problem is that your old code was assigning behaviour to "particular elements" when what it should really have been doing is creating page-wide responses to "certain types of actions".
I see 3 possibilities, and only one of them is guaranteed to work.
Run any scripts that are needed on new pages each time a new page is loaded. The downside here being that unless you are careful about also "tearing down" between content loads you will have behaviours repeating or colliding with each other (eg: double popups, broken animations).
Encapsulate the dynamic areas in <iframe>s. Depending on your architecture this may or may not be possible, and certainly won't be easy to integrate with some kind of infinite scrolling plugin which already expects a certain page structure.
Bite the bullet and fix the crappy code.
Loading scripts inside your ajax loaded content is a bad way to start with anyway. What you need is event delegation to attach itself to any dynamically added elements.
$("body").on("click", ".yourclass", function() {
//This function will run for every element with `yourclass` class you load via ajax
});
If you must keep using .click() then you must have a function you can call on the new content to re-hook the events every time you add more content to the page.
e: though it is worth noting that a change from .click to .on can often be handled by a properly structured find/replace
Event delegation is the correct solution. The issue is that the HTML elements on the "next page" were not part of the DOM when the page loaded. Therefore, if you did something like:
$(function() {
$('#some-element-on-the-next-page').click(function() {
foo();
});
});
Your handler did not bind.
I wouldn't attach the events to $(document). I would attach them to the closest parent which is available when the DOM loads. For example, the body tag or the fixed width wrapper which is the first child of the body (assuming your layout uses this type of structure.)
Make sure that the element that you attach to is not emptied with .empty() or repopulated with .html() as that will break the binding. Attaching the delegated handlers lower down on the DOM tree will give you better performance since the events will not have to bubble all the way up to the document node to fire your methods.
You shouldn't need to rewrite all of your functions and plugins, just the bindings to the events that fire them.
I typically use the module pattern and de-couple my method definitions from the click handlers. All of my methods are defined in the outer closure. I'll have a "document ready" section where I bind user events like clicks.
For example:
var myModule = (function() {
var public = {};
public.foo = function() {
// do something cool here
};
// document ready
$(function () {
$('#site-container').on('click', '.js-foo', function() {
public.foo();
});
});
return public;
})();
If you need to change the bindings in the future you will only need to change the call inside the document ready section.

How to add event handlers in jQuery to code which is rendered after the jQuery itself

EDIT: The Issue has been solved, as it turns out, the Select2 library had a custom command for this typa thing:
$("#element").on("change", function (e) { ... }
// Defined as "change"
I'm using a dropdown menu library called Select2 3.2. In short, the code takes a bunch of select and option tags, and generates a cool drop down search list.
However, after the site is rendered; when I click 'view source', all my select and option tags are still there, but when I right click the fancy new generated menus themselves and select "inspect element" (using google chrome), the html is TOTALLY different.
I think that this is causing the problem, all this new code is rendered from the custom library's JS, and after my jQuery event commands.
Specifically, here is my command:
$(document.body).on('click', '.select2-result-label', function() {
var name = $(this).text();
var post_to = '/myurl/';
$.post(post_to, { dat: dat},
function(response) {
...
}, 'json'
)
I believe the on() method takes care of this kinda stuff but apparently not, any help would be appreciated!
RELEVANT EDIT:
Here is a blurb from another Stack Overflow post:
The view page source page shows you the exact text that
was returned by the server.
Inspect element actually shows you the fully rendered DOM tree.
Knowing that, maybe solving this will be easier.
Here is a JS Fiddle related:
http://jsfiddle.net/JpvDt/47/
Try to make the alert "worked" appear when you click on an "x" in the multi bar.
Right now my code has it to register the class which contains the x's.
$(document.body).on("click", ".select2-search-choice-close", alert("worked"));
Scenario 1:
Your problem is may be you bind on method for whole DOM which is really BAD. So always try to bind that to the closest div (closest parent element) which your controls are exist.
About Event performance from Jquery API says like below.
Attaching many delegated event handlers near the top of the document
tree can degrade performance. Each time the event occurs, jQuery must
compare all selectors of all attached events of that type to every
element in the path from the event target up to the top of the
document. For best performance, attach delegated events at a document
location as close as possible to the target elements. Avoid excessive
use of document or document.body for delegated events on large
documents.
Scenario 2:
Call your on event like below (with off event).
$(#yourElement).off('click').on('click', '.select2-result-label', function() {
var name = $(this).text();
var post_to = '/myurl/';
$.post(post_to, { dat: dat},
function(response) {
...
}, 'json'
)
I hope this will help to you.
As it turns out, the Select2 library had a custom command for future changes to the toolbar.
Read more here: http://ivaynberg.github.com/select2/#programmatic
It's vital to note that many standardized jQuery calls won't work with Select2, you must use their custom set-up.
$("#element").on("change", function (e) { ... }
// Defined as "change"
Just replace $(document.body) by $(document)

Properly tidying up a jQuery Dialog

I hit a problem when using jQuery's Dialog widget...
I have a solution, but wondered if there was a more standard way (or I had mis-understood something):
Background
I have a web site that makes heavy use of AJAX, in that most of the time only portions of the page are updated. One portion of the page contains some JS that opens a dialog. When flipping between that portion and another, on opening the dialog for a second time things get messed up.
Reason
$el.dialog() removes the DOM element that is to become the popup ($el[0]) from its original place in the document hierarchy and appends it to the document body instead. When I then remove the popup element's original parent element, the popup element doesn't get removed.
This then means that doing this (changing / removing that portion of the page and then changing it back) all again results in duplicate element IDs which unsurprisingly confuses the hell out of the dialog widget.
Solution
I have come up with a solution that overrides the $.fn.dialog function and makes use of jQuery special events. It attaches a listener to the custom event 'destroyed' on the original parent element, the 'destroyed' event is triggered when jQuery removes any element, the listener reacts to this event by removing the popup element wherever it now might be in the document heirarchy.
Here it is:
(function($) {
$.event.special.destroyed = {
remove: function(o) {
if (o.handler) {
o.handler.apply(this, arguments);
}
}
};
var originalDialogFn = $.fn.dialog;
$.fn.dialog = function(firstArg) {
if (!this.data('dialog') && firstArg != 'destroy' && !this.data('dialogCleaner')) {
this.data('dialogCleaner', true);
var $parent = this.parent();
var $dialogEl = this;
$parent.bind('destroyed', function(e) {
if (this == $parent.get(0)) {
$dialogEl.remove();
}
});
}
return originalDialogFn.apply(this, arguments);
};
})(jQuery);
Are there any better ways of doing this? It seems like a slight flaw in the way the jQuery dialog works, in that it's not that easy to tidy it up nice and generically.
Of course I am aware of the dialog('destroy') method but doesn't seem particularly easy to hook that into my page fragment/portion handling.
You could do what I do in these situations. Capture the parent element prior to making the dialog and then, after the dialog is created, detach it from the DOM and re-append it back to the parent element.
var dlg = $('selector.dialog'),
dlgParent = dlg.parent();
dlgParent.append(dlg.dialog().detach());
This works especially well when dealing with ASPX forms (because any server-side tags that I need to get a postback value from must remain within the form).

How to bind to submit event in jQuery Mobile pages

Tried to bind submit event (or click or whatever) to an element within a jQuery mobile page. What I wanted to do is get the value from an input within an form element within a jQuery page and store it in cookies/localStorage. Every time I come back to this page I want to restore the input field.
Currently I ended up in using this script:
$('.pageClassSelector').live('pageinit', function (e) {
$('.classSelector').submit(function () {
var q = $('.inputClassSelector').val();
// store to localStorage ...
});
// load from localStorage ...
$('.q').val(lastSearchString);
});
This approach is documented there http://jquerymobile.com/test/docs/api/events.html
Since it seems possible that identical pages are hold in the DOM, ID selectors are not quite useful. My problem now is that everytime I navigate to this page the submit event is bound again and thus results in storing DIFFERENT (!) values. The submit event seems to be fired multiple times and much more interesting with the value before last.
Am I doing anything completly wrong? Any hints how to do scripting in jquery mobile properly?
TRY1:
I placed now the submit event binding within the pagebeforeshow event like so:
$('#PageId').on('pagebeforeshow', function (e) {
$('.classSelector').on('submit', function () {
var q = $('.q').val();
alert('stored: ' + q);
}
$('.q').val(lastSearchString);
}
But the value going to be stored is still the value before last, when I was navigating the page before. The first time it works as it should.
TRY2:
This is what I have now and it looks like it does that what I want. I select now the current page and select only the form which is a child of this page.
Is this good practice?
$('div:jqmData(id="PageId")').on('pagebeforeshow', function (e) {
$(this).find('#form').on('submit', function () {
var q = $(this).find('#input').val();
localStorage.setItem("key", q);
return true;
});
lastSearchString = localStorage.getItem("key");
$(this).find('#input').val(lastSearchString);
});
Your requirement to load from local storage and store on the page needs to be done by binding to the pagebeforeshow event (look at the section "Page transition events") and not the pageinit event like you are currently doing.
$('.pageClassSelector').on('pagebeforeshow', function (e) {
// load from localStorage ...
$('.q').val(lastSearchString);
});
Furthermore generally each page element (where you have data-role='page') should have a unique ID so you can use that instead of the CSS selector.
Multiple events firing when navigating pages sounds like multiple bindings to me, which is a known problem with jQuery Mobile. Bindings are not unbound when navigating pages, because everything is loaded through AJAX. See for example this StackOverflow Question: Jquery mobile .click firing multiple times on new page visit or my solution.
$('.classSelector').on('submit', function(){})
Try to use the constraction to bind your event to element.
Look likes some data was loaded through ajax request

How would I stop a live jQuery event handler from firing more than once?

I'm trying to post-process content that is added to the DOM via the excellent SyntaxHighlighter project.
When SH formats text, it generates a bunch of HTML and then writes that HTML into the DOM. It creates a div with class "syntaxhighlighter".
I want to listen for the insertion of these new divs, and when they are inserted, I want to manipulate some of the html and then overwrite the html in the DOM with this new html.
My problem is that when I write this new html back to the DOM, it re-triggers the event that fires my handler in the first place, and thus I get stuck in browser-killing recursion.
Here's what I have so far:
$('div.syntaxhighlighter').live('DOMNodeInserted', function(event){
//console.log(event);
var input = event.target.innerHTML;
var newInput = input.replace(/(dbo\..*?)(\(|\s)/ig,"<a href='?obj=$1'>$1</a>$2");
$(event.target).unbind();
event.stopPropagation();
$(event.target).html(newInput);
return false;
});
Neither the unbind nor the stopPropagation nor the return false will get the behavior I want. I think I understand why, but I do not know how to stop it (my jQuery-foo is weak, I'm afraid)
Can you do something like..
if ( !$(this).data('writing') ) {
$(this).data('writing', 1);
// your function call that makes it invoke itself
$(this).data('writing', 0 );
}
did you try delegate (needed for inserted DOM element) together with the one method's,
after all the one method is specially created just for preventing anything from firing more than once?

Categories