Properly tidying up a jQuery Dialog - javascript

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).

Related

Can I trigger an event on a GWT widget using jQuery?

I am trying to automate a website that was built using GWT. My automation uses jQuery to select an appropriate element and then call the jQuery click() function to trigger a click event.
However, the expected action doesn't take place. Clicking the element with the mouse brings up a dialog box, but using jQuery does nothing. If I use jQuery to add a new click handler, I see the new handler executed in both cases, but the original handler only in the "real" click case.
Stepping into the Javascript code, I see very complicated code dealing with stack depth, leading me to think doing this automation may not be directly possible.
Does anyone know of a way to programmatically fire an event on a GWT-generated element? Or should this be working normally, and this site uses uniquely complicated code?
Edit: The code I'm using is quite simple:
var searchButton = jQuery('div.GH1CUEEFLB.GH1CUEEMLB:first');
if (searchButton && searchButton.length > 0) {
searchButton.click();
}
Stepping through the code shows that it selects the correct element, and proceeds to call click(). The existing event handler for the widget, according to Chrome's debugger, is complicated. Stepping through the process leads to a rabbit hole that is quite difficult to follow:
function(){
var stackIndex, returnTemp;
$stack_0[stackIndex = ++$stackDepth_0] = null;
try {
returnTemp = entry0(($location_0[stackIndex] = '57' , jsFunction), this, arguments);
$stackDepth_0 = stackIndex - 1;
return returnTemp;
}
catch (e) {
throw $location_0[stackIndex] = '63' , e;
}
$stackDepth_0 = stackIndex - 1;
}
The solution in this case was to trigger the click event on one of the child elements within the div. The event handler was attached to a particular div surrounding all the components of the button (label, icon, borders, etc). Triggering the event on that parent element did nothing. However, if I instead selected one of the leaf nodes in that subtree (say, the label itself), then triggering the click event brought up the dialog box as desired.
I guess the event handler's code was actively determining the exact element that triggered the event, but was not expecting the parent div to be that trigger source.
var searchButton = jQuery('div.GH1CUEEJT:first');
The above selects the leaf node upon which to trigger the event, even though the parent node 'div.GH1CUEEFLB.GH1CUEEMLB' held the event handler.

jQuery click event handler attached to dom element

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.

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)

Executing functions BEFORE Bootstrap popover is displayed?

I've recently started using Twitter's new Bootstrap 2.0.1 and its Javascript popovers.
I want to write a script so that no more than one popover can be displayed at one time. In other words, when a new popover is generated for whatever reason (e.g. the client clicks or hovers over a new element with a popover), all of the PREVIOUSLY displayed popovers are hidden first.
Here's the function that initially sets up all of the popovers for my webpage:
$(function (){
$("[rel=popover]").popover({placement:'left', trigger:'click', html:true});
});
What I need, I think, is to write a function that hides all popovers. I would call that function BEFORE displaying every popover, to ensure that only one popover is displayed at a time. The function might look like this, I imagine:
function hidePopovers(){
$(function (){
$("[rel=popover]").popover('hide');
});
}
But my problem is figuring out WHERE (or HOW) to call this hidePopovers function. I want to call it when a popover is triggered, but before the popover is displayed. Help?
Oh, and just to clear up any confusion, the new Bootstrap now has a 'click' trigger that allows you to display popovers upon clicking. More details about it can be found here.
Thank you so much!
Considering what you have presented as the problem to solve, I think that it would be much more efficient to simply store a reference to the last popover open, rather than execute the hide() method on every single popover element you might select on the page. As far as I understand it, you only want a single popover to be open in the first place, so there should only ever be at most a single one to hide.
The following would do the trick:
var $visiblePopover;
$('body').on('click', '[rel="popover"]', function() {
var $this = $(this);
// check if the one clicked is now shown
if ($this.data('popover').tip().hasClass('in')) {
// if another was showing, hide it
$visiblePopover && $visiblePopover.popover('hide');
// then store reference to current popover
$visiblePopover = $this;
} else { // if it was hidden, then nothing must be showing
$visiblePopover = '';
}
});​
JSFiddle
Technically, you could potentially change the selector where the delegate handler is attached (in the example code 'body' is used) to a more specific element of the page, allowing you to attach the only-one-visible-at-a-time behavior to only a subset of the popovers on the page.
For instance, if you had a specific form where the popovers would appear too close together, but other popups on the page wouldn't collide/overlap, you could select just the form (e.g., '#some_form_id'), and only the popups in the form would have the behavior.
JSFiddle
Note: In this latter example, I also optimized the code a bit by changing the stored reference to only use the actual Popover object, rather than the jQuery-ized DOM element it is attached to.
Haven't tested this but something like this might work:
Set the trigger to manual.
Listen for click events and on click, call hidePopovers(), and then show the clicked popover.
$(function (){
function hidePopovers(){
$(function (){
$("[rel=popover]").popover('hide');
});
}
$("[rel=popover]").popover({placement:'left', trigger:'manual', html:true});
$("[rel=popover]").click(function() { hidePopovers(); $(this).popover('show');});
});

Retrieving html control by specifying coordinates

How can I get the id of a html control just by specifying the coordinates of a triggered event (like onmousedown, onmouseup, onclick, etc..). the coordinates can be got by:
e.clientX , e.clientY where e is the event object.
The idea is to get the id of control on which a click event is done without having any onClick event in that control's definition.
This will dispense the need of specifying the onClick event for multiple controls.
I do not believe that this is possible, but fortunately (if I understand your requirements correctly) you do not need to:
If you want to get the HTML element where a user clicked, without specifying a click event handler on each element, simply specify a click handler on a top level element (one that contains all the other interesting elements - maybe even "document"), and then look at the MouseEvent's target property - it will specify the HTML element that received the click initially and not the element where you specified the onclick event handler (this can be gotten to simply by using the "this" keyword).
If you have firebug, try this out in your firebug console right here on StackOverflow:
document.getElementById('question').onclick = function(e) {
var target = window.event?window.event.srcElement:e.target;
alert("Got click: " + target);
}
Then click anywhere on your question text to get an alert with the correct HTML element :-) .
This is a very good question, lets suppose the function we are looking for is something like this:
document.elementFromPoint = function(x,y) { return element; };
This obscure function is actually implemented in Firefox 3.0 using the gecko layout engine.
https://developer.mozilla.org/en/DOM/document.elementFromPoint
It doesn't work anywhere else though. You could build this function yourself though:
document.elementFromPoint = function(x,y) {
// Scan through every single HTML element
var allElements = document.getElementsByTagName("*");
for( var i = 0, l = allElements.length; i < l; i++ ) {
// If the element contains the coordinate then we found an element:
if( getShape(allElements[i]).containsCoord(x,y) ) {
return allElements[i];
}
}
return document.body;
};
That would be very slow, however, it could potentially work! If you were looking for something like this to make your HTML code faster then find something else instead...
Basically what that does is it goes through every single HTML element there is in the document and tries to find one which contains the coordinate. We can get the shape of an HTML element by using element.offsetTop and element.offsetWidth.
I might find myself using something like this someday. This could be useful if you want to make something universal across the entire document. Like a tooltip system that works anywhere, or a system that launches context menus at any left click. It would be preferable to find some way to cache the results of getShape on the HTML element...
this will dispense the need of specifying the onClick event for multiple controls.
If this is your only goal, I suggest using jQuery to elegantly specify event handlers on multiple elements.
This one of examples from jQuery tutorial that does exactly this:
$(document).ready(function() {
$("a[href*=/content/gallery]").click(function() {
// do something with all links that point somewhere to /content/gallery
});
})
When your HTML page renders positions of various elements will be derived dynamically by the rendering engine of your browser. The only elements which can reliably be tested for their resulting layout properties are images.
To do what you want, therefore, you would need to use absolute positioning for all your elements and have a page map stored elsewhere to tie up controls to locations. This would be way too complicated I think!
Although it contradicts your question somewhat, you should, via javascript or server side, attach onclick events to your controls. Sorry!
You don't actually need the position. What you need is the target property of the event object. I don't know how this is handled in jQuery but here's a quick example inspired from the above resource:
JavaScript
<script type="text/javascript">
window.onload = function() {
document.body.onclick = function(event) {
alert(event.target.id);
}
}
</script>
CSS
div {
width: 200px;
height: 200px;
margin: 30px;
background: red;
}
HTML
<div id="first-div"></div>
<div id="second-div"></div>

Categories