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.
Related
I have a view (I'll call parent) that has another view inside of it ( a child view). The parent view has elements that use a "context menu" event (when right clicked, a set of menu options show up). When a user selects an item from the menu, it fires a callback. From there, I trigger an event in the child view. Here is my trigger:
that.trigger("editFileFolder", {fileType: fileType});
In my child view's initialize function, I have this:
this.listenToOnce(this.options.parent,'editFileFolder', this.editFileObjectEvent);
The editFileObjectEvent function calls another view to be created (a dialog with some fields, a cancel button and a save button). If the user clicks cancel, everything works fine. If the use clicks the save button, the view does an ajax call, saves to the server, and closes the dialog. But, the next time the user right clicks and selects the same menu item, the editFileObjectEvent gets called twice (resulting in the dialog being added twice to the parent view).
Can anyone explain why it's getting called twice and how to resolve this? If you wish to see specific code, let me know and I can add it. There's a lot, so I don't want to overwhelm the question.
thanks
I think the function that calls this.listenToOnce is called twice. You should try to avoid this. However if this is not possible you can make sure the listeners is only bound once by unbinding it before binding:
this.stopListening(this.options.parent, 'editFileFolder', this.editFileObjectEvent);
this.listenToOnce(this.options.parent, 'editFileFolder', this.editFileObjectEvent);
Or you could have a property on your instance to prevent from binding twice:
if (!this.__boundListener) {
this.listenToOnce(this.options.parent, 'editFileFolder', this.editFileObjectEvent);
this._boundListener = true;
}
All listeners are registered in this._listeningTo and you might be able to search though that to check if the event is already bound.
You can also refactor your code:
MyModel.extend({
initialize: function() {
this.bindListenToOnce = _.once(this.bindListenToOnce);
},
abc: function() {
// do stuff
this.bindListenToOnce();
// do stuff
},
bindListenToOnce: function() {
this.listenToOnce(this.options.parent, 'editFileFolder', this.editFileObjectEvent);
}
});
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.
Forgive me if I word the title wrong, I'm speculating on what my problem might be as I'm not a javascript coding expert. I have a series of divs that are generated by a php loop with unique ids created by adding the unique id contained in an auto increment column in the mysql db table that contains all the info for the row.
When the user clicks on the div this function fires off:
onclick=\"showModal('".$rowInfo['ID']."_row-id')\"
javascript code:
function showModal(ID) { /* code that shows hidden modal window */ }
This works fine, however now I need to start adding javascript buttons (in my case img tags with onclick functions) to the div with the showModal onclick function.
I added this code to the showModal(ID) function:
var downArrow = document.getElementById(ID+'_down-arrow'); // Down arrow is the button users click to show addition buttons/divs
downArrow.addEventListener('click',arrowCheck,false); // checks to see if down arrow was clicked, if so arrowCheck function runs and stops propagation.
arrowCheck function:
function arrowCheck(e) { e.stopPropagation(); }
This bit of code also works but ONLY AFTER the user has clicked the div once, the first time the div is clicked both functions fire off (ie the modal window and the extra buttons that the down arrow shows) but after the first click the addEventListener does it's job and clicking the down arrow only shows extra buttons, elsewhere brings up the modal, etc.
I'm guessing I need to create the event listener before the user clicks the div and fires off showModal(), is this correct? I'm not sure how to create a unique event listener for each down arrow image before the div is clicked, or even if I need to. Thanks for any help!
Since the event listener is being created within the modal function, you need to call the modal function before the other listener is even added.
To get your desired results, you could either directly add the onclick method to the image that the PHP code creates, or you could detect when the page is finished loading and add those listeners then. To do the latter, though, you'd need to either query for something the images all have in common, like a classname, or you'd have to keep track of the IDs used and manually add a listener for each. e.g.:
<body onload="initEventhandling()">
initEventHandling = function () {
var buttons = document.getElementsByClassName("image-button");
for (button in buttons) {
button.addEventListener('click', arrowCheck, false);
}
}
David Millar's code pointed me in the right direction. I tried to put the initEventHandling function in the onload of the body tag but I couldn't get it to work. It executed the function but I could not get the eventListeners to work. I solved it by creating an event listener using the onload for each img tag, so my code ended up like this:
<img id=\"".$appChart['ID']."_down-arrow\" onload=\"addEvent('".$appChart['ID']."')\" onclick=\"clickReviewArrow('".$appChart['ID']."')\" src='...' />
javascript:
function addEvent(ID) { var downArrow = document.getElementById(ID+'_down-arrow');
downArrow.addEventListener('click',arrowCheck,false); }
function arrowCheck(e) { e.stopPropagation(); }
David Millar may have been suggesting this, if so I'll mark his answer.
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.
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