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)
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 am currently learning js and jquery to assist me with my designs, a common problem that I am having is that I can get it to do what I want it to but I have no idea if the way in which it has been coded is efficient. Could anyone see a better way to code this:
$(".cal-check a").click(function(e) {
e.preventDefault();
$(".agenda").addClass("active");
});
$(".agenda .close-panel").click(function(e) {
e.preventDefault();
$(".agenda").removeClass("active");
});
I want to click on a calendar event then it adds the class active to another class within the calendar called agenda which then brings up the agenda. I then remove it by clicking on a close panel element. Many thanks
You could cache the .agenda selector like so:
var $agenda = $(".agenda");
$(".cal-check a").click(function(e) {
e.preventDefault();
$agenda.addClass("active");
});
$agenda.find(".close-panel").click(function(e) {
e.preventDefault();
$agenda.removeClass("active");
});
I recommend not changing classes, that will usually be rather intensive on the browser, because one class change will mean that all the classes have to be reparsed. This is usually very bad for more aggressive stuff, like animation, but if you have performance considerations, you should take that into advisement.
I think this is already efficient.
Just something that might help in the future is to try and dive into the DOM as little as possible. In this small example it wont make a difference but for example create a variable for agendaClass instead of using jquery every time to fetch it.
var agendaClass = $(".agenda");
$(".cal-check a").click(function(e) {
e.preventDefault();
agendaClass .addClass("active");
});
$(".agenda .close-panel").click(function(e) {
e.preventDefault();
agendaClass .removeClass("active");
});
That should be efficient enough. How you can optimize your code strongly depends on your DOM structure.
Behind the scene, jQuery with its Sizzle search engine will use built in methods for DOM search, if those are available (native search will be always faster than search done with JS). In your case everything should be Ok, especially in modern browsers, as they have querySelectorAll and .cal-check a and .agenda selectors will be executed with that built in method. Also, there is getElementByClassName which could be used to find .agenda.
Both of those methods are supported by most of modern browsers (provided links have a list of supported browser), so talking about browsers like IE8+, Firefox and Chrome will be fast enough with your selectors. At the same time IE7 has no functions like above and Sizzle will be forced to go through numerouse elements to find elements you are looking for. Maybe you can limit that amount specifying some container with id, in that case it will look inside that elements only:
$("#someId .agenda"), for instance. You may want additionally add some tag: $("#someId div.agenda"). This way you will limit amount of elements to search with divs (getElementsByTagName could be used) inside #someId (getElementById). That way you may increase speed in IE7 and other old browsers with no support of getElementByClassName and querySelector
Plus, you may cache search results as it was already mentioned here.
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.
I just wrote a $().bind('event') function and then got concerned that this kind of a call might be very expensive if jQuery has to run through each element in the DOM to bind this event.
Or maybe, it's just about as efficient as an event could be. The jQuery docs I've read aren't making this clear. Any opinions?
There are two things that can make your event binding code slow: the selector and the # of bindings. The most critical of the two is the # of bindings, but the selector could impact your initial performance.
As far as selectors go, just make sure you don't use pure class name selectors like .myclass. If you know that the class of myclass will always be in a <div> element, make your selector be div.myclass as it will help jQuery find the matching elements faster. Also, don't take advantange of jQuery letting you give it huge selector strings. Everything it can do with string selectors it can also do through functions, and this is intentional, as it is (marginally, admittedly) faster to do it this way as jQuery doesn't have to sit around to parse your string to figure out what you want. So instead of doing $('#myform input:eq(2)'); you might do $('input','#myform').eq(2);. By specifying a context, we are also not making jQuery look anywhere it doesn't have to, which is much faster. More on this here.
As far as the amount of bindings: if you have a relatively medium-sized amount of elements then you should be fine - anything up to 200, 300 potential element matches will perform fine in modern browsers. If you have more than this you might want to instead look into Event Delegation.
What is Event Delegation? Essentially, when you run code like this:
$('div.test').click(function() {
doSomething($(this));
});
jQuery is doing something like this behind the scenes (binding an event for each matched element):
$('div.test').each(function() {
this.addEventListener('click', function() {
doSomething(this);
}, false);
});
This can get inefficient if you have a large amount of elements. With event delegation, you can cut down the amount of bindings done down to one. But how? The event object has a target property that lets you know what element the event acted on. So you could then do something like this:
$(document).click(function(e) {
var $target = $(e.target);
if($target.is('div.test')) { // the element clicked on is a DIV
// with a class of test
doSomething($target);
}
});
Thankfully you don't actually have to code the above with jQuery. The live function, which is advertised as an easy way to bind events to elements that do not yet exist, is actually able to do this by using event delegation and checking at the time an action occurs if the target matches the selector you specify to it. This has the side effect, of course, of being very handy when speed is important.
The moral of the story? If you are concerned about the amount of bindings your script has just replace .bind with .live and make sure you have smart selectors.
Do note, however, that not all events are supported by .live. If you need something not supported by it, you can check out the livequery plugin, which is live on steroids.
Basically, you're not going to do any better.
All it is doing is calling attachEventListener() on each of your selected elements.
On parse time alone, this method is probably quicker than setting inlined event handlers on each element.
Generally, I would consider this to be a very inexpensive operation.