I'd like to use JavaScript only (no jQuery) to replace an inline onmenuclick event handler generated by a third party control with my own.
The custom control has added the following HTML to the page:
<ie:menuitem
menugroupid="200"
description="Create a site for a team or project."
text="New Site"
onmenuclick="if (LaunchCreateHandler('Site')) { STSNavigate('\u002fsites\u002fsd\u002f_layouts/newsbweb.aspx') }"
iconsrc="/_layouts/images/newweb32.png" type="option" id="zz4_MenuItem_CreateSite"></ie:menuitem>
I'm trying to replace the onmenuclick handler with:
var createSiteMenuItem = document.getElementById('zz4_MenuItem_CreateSite');
if (createSiteMenuItem)
createSiteMenuItem.onmenuclick = function () { alert('Hello!'); }
The original handler still fires! I'm making sure the script runs after the document has loaded.
Is this the correct approach?
The trouble is that directly assigning to onmenuclick is unreliable and non-standard. You need to use attachEvent() (IE) or addEventListener() (everyone else).
Edit:
As explained below, the actual problem was that in Javascript, element attributes are case-sensitive, despite the HTML, which isn't. So any reference to the menu click event in Javascript has to refer to it as "onMenuClick".
The id may be generated dynamically. So one time it is 'zz4_MenuItem_CreateSite' the next time it is something else. Way to check: observe the html source on multiple downloads, see if the ids vary.
This msdn article seems to point in that direction.
Suggestion: wrap the menu items in a div with an id that you assign. Then walk the dom tree within your div to find the right element to modify.
I've marked staticsan's answer as correct - onmenuclick is non-standard and that's why the problem is occurring. However the original resolution suggested wasn't quite right. This has since been corrected, but here's the back story for completeness...
I debugged this in Visual Studio and could see that onmenuclick is recognised as an expando instead of an event. This means attachEvent and addEventListener do not apply and fail when used.
The resolution was far more simple. I changed the casing to that shown in the Visual Studio debugger so it read onMenuClick instead of onmenuclick. The "faux-event" now fired correctly.
Related
I am using a Javascript plugin (several lines of code) that from times to times is released a new version.
For this reason I am trying to avoid changing the original source code in order to affect my wishes.
One way that is "half" working for me is to find all the elements that are using a specific CSS class (or group of classes) and them I am removing it (or do something else with them) in order to do what I want.
The part that is not working is the "trigger/event" to process this action. During the execution of this plugin new elements are created and removed and once again I am having "wrong" entries once again.
My question: How can I "catch" all the elements that are "from a moment to the other" using the CSS class XXX? and then execute my own code.
Notes: I was reading the Jquery .on() but I need to specify an event, however the issue is that I do not know the many "places/events" from the original source code are processing this.
Update:
At this point I am "manually" calling my function:
function MyOverrideAction(){
$.each( $( ".sch-gantt-terminal" ), function( key, value ) {
// here I have all my logic.... based on several rules (non relevant to my stackoverflow question)
});
}
I just want that this function is executed every instance when some HTML element is using my target css class.
It is much easier to redefine the CSS class after the original definition. One way to do it is to attach an inline style tag at the bottom of the document which redefines the style. You can use jQuery.append for this. For example see this.
Maybe you search something like this:
https://stackoverflow.com/a/3219767/5035890
If you listen a change in the DOM you can apply all actions that you need. With MutationObserver you can achieve it. Please, consider of the compatibility.
Good luck
Although it has already been asked, I want to adress the issue of correct jQuery programming.
Method #1:
<script>
function DoClickAction() {
// Some work
return false;
}
</script>
Do some work
VS
Method #2:
<script>
$(function() {
$("#ActionButton").on("click", DoClickAction);
}
function DoClickAction() {
// Some work
return false;
}
</script>
Do some work
I'm having a discussion with my colleagues about this, and my opinion is that both methods have enough pro and cons to not be able to say "this is the right way", but if I have to choose I tend to prefer Method #1, this is why:
Method #1 pros:
When debugging someone else code, you can easily follow which jQuery code is executed when somebody presses the link.
When you dynamically load (AJAX call) the content, it will always work, no need to rebind your jQuery events.
Method #2 pros:
It will produce less HTML code for the browser to download, because the script file will be cached and the onclick attribute is not necessary. Although this example uses more code.
You can re-use the code easily by using the same attributes, although using the onclick with 1 function is kind of the same thing.
What are your thoughts on this?
Instead of listing the pro's of either method, let me focus on the con's of method 1:
Change a function name == change the entire markup
All event handlers reside in the global scope. Working with closures can be a bit of a pain, then.
adding new elements dynamically (through JS or via ajax response) means that you'll either have to parse the markup and add the attribute one by one, or you'll have to send markup containing, essentially, JS function calls. Not safe, not clean
Each attribute is a new listener. The more attributes you have, the heavier the event loop will become
Mixing JS and HTML is not considered good practice. Think of it as separation of concern. The markup is there to provide the client with a UI. JS's job (in a browser) is to enhance the user experience. They have to work together, but have different tasks. Hence, they should be treated as separate entities.
As far as the second method goes, the only "cons" I can think of are:
Your code is slightly harder to understand, but if somebody can't work out what an event listener is, he shouldn't be working on your code, IMO.
Debugging can be harder, and older browsers might leak (jQ does contain an awful lot of X-browser related code, so it doesn't apply here. It does when you're writing vanillaJS)
In addition to this, method2 has another major pro, that you've not listed: delegation. At first, delegation looks hard, but It's easy, jQuery's $.delegate makes it easier, still, using $.on with a selector also delegates the event.
Basically, delegation allows you to deal with all events, for example click, for the entire page, or a section of the page, using a single listener. This as opposed to binding the event to each and every element. Thus: 1 listener on the event loop versus tens/hundreds. It's pretty obvious which is the more performant way of doing things.
Suppose you have a navigation div on a page, that looks like this:
<div id='nav'>
<ul>
<li id='nav-home'>Some pseudo-link</li>
<li id='nav-page1'>Another</li>
</ul>
</div>
You want to pick up on the user, clicking one of the <li> tags. The first method you listed makes for a right mess: <li id='nav-home' onclick='clickNav(event, this)'>. I'm passing the event object and this (a DOM reference) to have access to everything delegation gives me access to.
Using delegation, I can simply do this:
//jQ
$('#nav').on('click','li',function(e)
{
$.ajax({//you know the gist
url: 'ajax/' + $(this).id().replace('nav-',''),
success: function(){}
});
});
//vanillaJS:
document.getElementById('nav').addEventListener('click',function(e)
{
e = e || window.event;
var target = e.target || e.srcElement;
if (e.tagName.toLowerCase() === 'li')
{
//perform ajax call
}
},false);
I myself am very much partial to #2, as it provides a clean separation of JavaScript and HTML. The negatives of not having the action of a button be immediately visible in the HTML can be completely negated by browser plugins.
Furthermore, as you've already stated, sometimes I want to attach an onclick event to, say, every row of a table, and setting the OnClick attribute of an element on every row is much more wasteful than simply attaching a click handler to each of them with a single line of code elsewhere.
I've got a page with some Javascript / jQuery stuff, for example:
(function()
{
$('.tip').tooltip();
$('.test').click(function()
{
alert('Clicked!')
});
}();
On the page I insert some HTML with jQuery so the DOM changes. For example, I insert a extra element with the class "tip" or "test". The just new inserted elements doesn't work because jQuery is working with the non-manipulated DOM and the just inserted elements aren't there. So I've searched around and came to this solution for the "click":
$('body').on('click','.click',function()
{
alert('Clicked!')
});
I don't understand why, but this way it's working with the manipulated DOM and the jQuery stuff works on the new inserted elements. So my first question is, why does this work and just the click() function not? And the second question, why do I have to point to the "body"?
Finally, my third question is, how get this done with the tooltip?
I know that there is so many information about this subject (previous the delegate() and live() function I've read) but I can't found a explanation about it. And I can't get my third question solved with the information I found.
I'm looking forward to your responses!
Extra question:
4) Is it recommended to point always to the "body" for this kind of situations? It's always there but for possible performance issues?
So my first question is, why does this work and just the click()
function not?
Because the event handler is now delegated to a parent element, so it remains even after replacing/manipulating child elements.
Ancient article on event delegation for your perusal - but the concepts remain the same:
http://www.sitepoint.com/javascript-event-delegation-is-easier-than-you-think/
And the second question, why do I have to point to the "body"
You don't, any suitable parent element will do. For example, any direct parent (a div wrapper, for instance) which does not get replaced.
Finally, my third question is, how get this done with the tooltip?
You need to re-initialize your tooltip plugin on the newly inserted elements. For example:
$.get("foo.html", function (html) {
$("#someDiv").html(html);
$("#someDiv").find(".tip").tooltip();
});
The click() event doesn't work when you manipulate the DOM because JQuery is not watching for DOM changes. When you bind the click() event it is selecting the elements that are on the page at that time. New ones are not in the list unless you explicitly bind the event.
Because you have pointed the click() event on the body. JQuery then checks to see if the target of the click matches any of the event handlers (like what you have created) match the element clicked. This way any new elements will get the event 'associated' with them.
Because the tooltip isn't an event that you can place on the body, you will need to re-initialize it when the element is created.
EDIT:
For your fourth question, is it depends. The advantage of binding to the body is that you don't accidentally bind an event to an element more than once. The disadvantage is that you are adding event handlers that need to be checked on each event and this can lead to performance issues.
As for your concerns about DRY, put the initialization of the tooltips into a function and call that when you add them. Trying to avoid having the same function call is a little overkill in this regard, IMO.
Events are bound to the specific object you are binding it to.
So something like $('.tip').tooltip() will perform the tooltip() functionality on $('.tip') which is actually just a collection of objects that satisfies the css selector .tip. The thing you should take note of is, that collection is not dynamic, it is basically a "database" query of the current page, and returns a resultset of HTML DOM objects wrapped by jQuery.
Therefore calling tooptip() on that collection will only perform the tooltip functionality on the objects within that collection, anything that was not in that collection when tooltip is called will not have the tooltip functionality. So adding an element that satisfies the .tip selector, after the tooltip() call, will not give it the tooltip functionality.
Now, $('body').on('click','.click', func) is actually binding the click event to the body tag (which should always exist :P), but what happens is it captures whether the click event has passed through an element your target css selector (.click in this case), so since the check is done dynamically, new elements will be captured.
This is a relatively short summary of what's going on... I hope it helped
UPDATE:
Best way for your tooltip thing is to bind tooltip after you have added elements, e.g.
$('#container').load('www.example.com/stuff', function() {
$('.tip', $(this)).tooltip();
});
I have this problem, but in short: I have a self calling function (function TCC() {...})(); that generates two sections of elements, among other things. These elements are generated with the class btn. Another JQuery function of $('.btn').click(function () {...}; [with an appropriate console.log() ] is showing that all elements on the page with class btn are being clicked except the two aforementioned sections' elements.
One comment suggests "The issue occurs because you bind the click event, before the "column button generator" loop.", and a suggested solution of:
$.each(cols, function(i) {...}).done(function(){ //Add click event handler now });
First question is can someone elaborate on 'because you bind the click event, before the "column button generator" loop.'
Second question is about the proposed solution. Since I have two separate lines of code for generation, and the $('btn').on('click', function() {...}); block is over 50 lines, how do implement a solution without duplicating the lines of code, as I would assume the .done() method would require this. The page in question is here.
The current scope structure of the generation code relative to the .on('click') is as follows (sorry for pic, but StackOverflow didn't like the formatting for some reason):
Does the scope of the generation lines of code effect this, and if so, can I pull them out of the self calling function to fix the 'bind to click event' the suggestion spoke of?
I appreciate your help and expertise, as learning programming from scratch, these types of structure lessons are often left out of the online learning tools often included in textbooks.
You really should've posted the actual code. But no matter. I think the only issue why your click events are not being called, is that you've used an incorrect syntax.
The on function of jQuery, has the following syntax -
HTML
<div class='button_container'>
<button> This is a button</button>
<button> This is a button</button>
</div>
JavaScript
$('.button_container').on('click', 'button', function(){
console.log('I have been clicked!');
});
//Note the the second selector 'button' within the declaration
Since the on function basically replaces the live and delegate functions, the handler is now defined for all buttons in div.buttons, irrespective of when and how it was created (meaning dynamically created). This can be bit misleading.
If you do a simple select of the sort
$('button').on('click', function(){ /* do something **/ })
jQuery assumes that you are talking about direct event binding - i.e, the dom element is already loaded. It won't affect any dynamically created elements (mind you, if you attach the bindings after the creation code, it will still work on those elements, as they are already present in the DOM).
Unless you provide the second selector, this the default behavior. This is a bit confusing, but moving on. If you want to target those elements before actually creating them, do something of this sort
//Right syntax
$(document).on('click','button', function(){ /* do something **/ })
//This will bind to all buttons
As I begin to develop more and more complicated JavaScript solutions, I wonder what sort of options JavaScript gives me to monitor changes in my environment. I've seen so many solutions that constantly ping whatever element they are wanting to monitor, but for any sort of resource heavy application (or for any case depending on the standards of the developer) that becomes bloated and too hackish to be a viable method. Which brings me to my question, "What are the limitations of JavaScript's onchange event?". Specifically, right now I'm trying to monitor the size of the window. Is there a way to utilize the .onchange event for things like this? How would you solve this?
I'm very interested to hear what everyone has to say.
The size of the window can be monitored with onresize.
The onchange event, as Rahul says, applies to fileupload, select, text and textarea. However, it only fires once focus is shifted away from the item and onto something else, and the content has changed.
The onchange attribute is a DOM property. It is not provided by Javascript.
W3schools reports that only some elements support its use, per their page about the onchange event:
Supported by the following HTML tags:
<input type="text">, <select>,
<textarea>
Supported by the following JavaScript
objects:
fileUpload, select, text, textarea
If you want to monitor the size of the window you basically have two options:
- use the onresize attribute
- set an interval that checks via polling.
Try out jQuery, it's tiny and insanely powerful.
jQuery has two function that should be of use to you: width() and height()
Example (based on example from the docs linked above):
function showWidth(ele, w) {
$("someDiv").text("The width for the " + ele +
" is " + w + "px.");
}
$('#someElement').change(function () {
showWidth("document", $(document).width());
});
Of course, the same applies to height(). Good luck.
window.onresize=function(){
exampleFunction();
}
is what I do. I am not very keen of onchange, if you changed the element with JS, you can also call the event in the same JS. (why do it like: JS -> element -> JS, when you can go: JS -> element and JS)