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.
Related
My situation is that I am trying to trigger a single event using the jQuery .trigger() method. However the element I am triggering has multiple click event listeners.
Actually finding what these listeners are and what they trigger from the source code is probably not viable as its included in the sites main JS file and its all minified and pretty much unreadable.
At the moment I know that the element when clicked performs some kind of ajax call and loads more data into the DOM of the page (which is what i want to trigger), however it also displays an overlay (which is what I want to suppress temporarily).
As its just an overlay there are workaround I can make; using a display:none on it straight after click etc. However it would be much more elegant if i could somehow suppress all click events on this element except the desired event.
Any ideas if this is actually possible? And if so how I would go about it?
You need to register your own event at the top of the event chain. And cancel the event chain in your event. Here is a solution with writing a custom jquery extention.
$.fn.bindFirst = function (which, handler) {
var $elm = $(this);
$elm.unbind(which, handler);
$elm.bind(which, handler);
var events = $._data($elm[0]).events;
var registered = events[which];
registered.unshift(registered.pop());
events[which] = registered;
}
$("#elm").bindFirst("click", function(e) {
// edit: seems like preventing event does not work
// But your event triggers first anyway.
e.preventDefault();
e.stopPropagation();
});
Reference:
https://gist.github.com/infostreams/6540654
EDIT:
https://jsfiddle.net/8nb9obc0/2/
I made a jsFiddle and it seems like event preventing does not work in this example. There might be another solution.
Is there a way to be notified or perform some callback function once an event has finished propagating in JavaScript?
Equivalently, and more specifically: is there a way to 'prioritize' an event and make sure that it is called after every other event listener has been fired (similarly almost to the !important value in CSS).
For instance, if I have 3 event listeners - 2 attached to the window and 1 to some button element. Can I force a certain one of those events to be called LAST, regardless of where it lies in the DOM? I understand that there are event phases and the ability to attach a listener to the capture or bubbling phase but this still means there's a preset order.
edit: the specific problem:
I'm attempting to build components (in React JS) which are aware of a click being registered outside of themselves (i.e. anywhere on the window/document except themselves) - often as a way of closing/hiding the component. Each of these components will register a listener on the window object which fires a function belonging to that component.
The trouble is, when another component [B] (inherently lower down in the DOM than the window) is clicked to let's say toggle the display of [A], [B]'s event fires first and toggles the state 'showA', the event bubbles up and [A]'s window event listener kicks in and re-toggles the state 'showA' - so, [A] remains hidden after changing state twice. I can't use stopPropagation as other window events need to fire. I've tried to unbind listeners but this doesn't happen in time.
An example of what currently happens all in one go is:
'show thing' button clicked
add listener to window for closing 'thing'
'window but not thing' was clicked
remove listener to close 'thing'
If only I could wait until the click event had finished bubbling before adding the new listener, I'd have no issue
I did leave an answer to your original question but I see you've updated it. I wouldn't say this is React specific but a common implementation for components that need to close/de-activate when the document is clicked.
For instance, the following snippet is an implementation for a speed dial spin out button;
(function () {
var VISIBLE_CLASS = 'is-showing-options',
btn = document.getElementById('.btn'),
ctn = document.getElementById('.ctn'),
showOpts = function(e) {
var processClick = function (evt) {
if (e !== evt) {
ctn.classList.remove(VISIBLE_CLASS);
ctn.IS_SHOWING = false;
document.removeEventListener('click', processClick);
}
};
if (!ctn.IS_SHOWING) {
ctn.IS_SHOWING = true;
ctn.classList.add(VISIBLE_CLASS);
document.addEventListener('click', processClick);
}
};
btn.addEventListener('click', showOpts);
}.call(this));
When the user clicks the button, the container is shown for the speed dial options and an event listener is bound to the document. However, you need to make sure that the initial event that is fired is not the one that triggers the takedown straight away (this is sometime a gotcha). This check is made with if (e !== evt) .... For further clicks the event check is made and the relevant action taken ending in removal of the event listener from the document.
Of course in your particular case if you want to only close when the element isn't clicked then you could make relevant checks on the evt.target and evt.currentTarget in the callback (in the snippet case, processClick).
Hopefully, this can help you out with registering close down callbacks for your individual components.
I've got a WinJS ListView that has its items created using a templating function (the itemTemplate option points to a function). The returned item has a div inside it that has the win-interactive class so that it can accept input. Specifically, that div needs to be able to scroll to show more content that can fit on the ListView item.
The scrolling works perfectly with the win-interactive class applied to the div. The problem I'm trying to solve is allowing a normal click (mouse down, mouse up) to still trigger the oniteminvoked event on the ListView, while still allowing the div inside a ListView item to be scrolled.
I figured this would be easy: I'd bind a click event listener to the div with the win-interactive class, and simple pass that click event up to another element on the ListView item, ensuring that eventually the oniteminvoked event would be trigger.
I've bound the click listener to the win-interactive div, and it's being triggered as expected. However, and I cannot figure out how to trigger a click/item invocation on another part of the ListView item. I've tried using properties of the event (currentTarget) to get parent or sibling elements and triggering events on them, but I simply can't figure out how to trigger an event on an element like event.currentTarget.
tl;dr:
How can I allow scrolling within a ListView item while still allowing a normal click to trigger the item invocation?
Overall, the ListView just handles clicks by passing them to whatever itemInvoked handler you register for the control. This means you should be able to just bypass that whole chain and invoke your handler directly with the appropriate item index, which is easy to obtain.
Within your item's click handler, if you have the ListView object handy, then list.currentItem will be the item with the focus (and obviously the one clicked), and its index property will be what normally gets passed to itemInvoked. With this you can then call your itemInvoked handler directly, building up the appropriate event object, or you can separate your handler's code into another method and call that one instead.
As a basic example, starting with a Grid app template project, I added win-interactive to the item-title element (which could by any other like your scrolling region) in pages/groupedItems/groupedItems.html:
<h4 class="item-title win-interactive" data-win-bind="textContent: title"></h4>
In the ready method of pages/groupedItems/groupedItems.js, I attached the ListView's object to the page control for later use:
var listElement = element.querySelector(".groupeditemslist");
var list = listElement.winControl;
this._list = list;
And then hooked up click listeners to the item-title elements as below. I'm doing it this way because the items are created through templates; if you have an item rendering function instead, then you can add the listeners in that piece of code directly.
var that = this;
list.addEventListener("loadingstatechanged", function () {
if (list.loadingState == "complete") {
var interactives = element.getElementsByClassName("item-title");
for (var i = 0; i < interactives.length; i++) {
interactives[i].addEventListener("click", that._itemClick.bind(that));
}
}
});
The implementation of _itemClick in my page control looks like this:
_itemClick: function (e) {
var item = this._list.currentItem;
//Can also get the index this way
var index = this._list.indexOfElement(e.currentTarget);
this._itemInvoked({"detail" : { itemIndex : item.index} })
},
where this._itemInvoked is the same handler that comes with the template.
Note that with win-interactive, you don't get the usual pointerDown/pointerUp behaviors for items that do the little down/up animations, so you might want to include those too if you deem it important (using WinJS.UI.Animation.pointerDown/pointerUp methods to do the effects).
Finally, it's probably possible to go up the chain from the e.currentTarget element inside _itemClick and simulate a click event on the appropriate parent, but I think that's more trouble than it's worth here having traced through the WinJS code that does all that. Much more direct to just call your own itemInvoked.
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).
Assume I get a table element with ID="emTab", how do I call JS to click it?
Thanks.
document.getElementById("emTab").onclick = function() {
// your code goes here
};
See element.onclick
To trigger click event
document.getElementById("emTab").click();
See element.click
The click method is intended to be
used with INPUT elements of type
button, checkbox, radio, reset or
submit. Gecko does not implement the
click method on other elements that
might be expected to respond to
mouse–clicks such as links (A
elements), nor will it necessarily
fire the click event of other
elements.
Non–Gecko DOMs may behave differently.
When a click is used with elements
that support it (e.g. one of the INPUT
types listed above), it also fires the
element's click event which will
bubble up to elements higher up the
document tree (or event chain) and
fire their click events too. However,
bubbling of a click event will not
cause an A element to initiate
navigation as if a real mouse-click
had been received.
Cross browser way
If you can use jQuery then it would be
$("#emTab").trigger("click");
Firing events cross-browser - http://jehiah.cz/archive/firing-javascript-events-properly
its simple using JQuery
$('#emTab').click(functionToCall);
while in JS
document.getElementById('emTab').onclick = function() {};
for details on DOM events:
http://www.howtocreate.co.uk/tutorials/javascript/domevents