Is there a name for the programming paradigm jQuery suggests? [duplicate] - javascript
I have heard many times that using JavaScript events, such as onClick(), in HTML is a bad practice, because it's not good for semantics. I would like to know what the downsides are and how to fix the following code?
link
You're probably talking about unobtrusive Javascript, which would look like this:
link
with the logic in a central javascript file looking something like this:
$('#someLink').click(function(){
popup('/map/', 300, 300, 'map');
return false;
});
The advantages are
behaviour (Javascript) is separated from presentation (HTML)
no mixing of languages
you're using a javascript framework like jQuery that can handle most cross-browser issues for you
You can add behaviour to a lot of HTML elements at once without code duplication
If you are using jQuery then:
HTML:
<a id="openMap" href="/map/">link</a>
JS:
$(document).ready(function() {
$("#openMap").click(function(){
popup('/map/', 300, 300, 'map');
return false;
});
});
This has the benefit of still working without JS, or if the user middle clicks the link.
It also means that I could handle generic popups by rewriting again to:
HTML:
<a class="popup" href="/map/">link</a>
JS:
$(document).ready(function() {
$(".popup").click(function(){
popup($(this).attr("href"), 300, 300, 'map');
return false;
});
});
This would let you add a popup to any link by just giving it the popup class.
This idea could be extended even further like so:
HTML:
<a class="popup" data-width="300" data-height="300" href="/map/">link</a>
JS:
$(document).ready(function() {
$(".popup").click(function(){
popup($(this).attr("href"), $(this).data('width'), $(this).data('height'), 'map');
return false;
});
});
I can now use the same bit of code for lots of popups on my whole site without having to write loads of onclick stuff! Yay for reusability!
It also means that if later on I decide that popups are bad practice, (which they are!) and that I want to replace them with a lightbox style modal window, I can change:
popup($(this).attr("href"), $(this).data('width'), $(this).data('height'), 'map');
to
myAmazingModalWindow($(this).attr("href"), $(this).data('width'), $(this).data('height'), 'map');
and all my popups on my whole site are now working totally differently. I could even do feature detection to decide what to do on a popup, or store a users preference to allow them or not. With the inline onclick, this requires a huge copy and pasting effort.
It's not good for several reasons:
it mixes code and markup
code written this way goes through eval
and runs in the global scope
The simplest thing would be to add a name attribute to your <a> element, then you could do:
document.myelement.onclick = function() {
window.popup('/map/', 300, 300, 'map');
return false;
};
although modern best practise would be to use an id instead of a name, and use addEventListener() instead of using onclick since that allows you to bind multiple functions to a single event.
With very large JavaScript applications, programmers are using more encapsulation of code to avoid polluting the global scope. And to make a function available to the onClick action in an HTML element, it has to be in the global scope.
You may have seen JS files that look like this...
(function(){
...[some code]
}());
These are Immediately Invoked Function Expressions (IIFEs) and any function declared within them will only exist within their internal scope.
If you declare function doSomething(){} within an IIFE, then make doSomething() an element's onClick action in your HTML page, you'll get an error.
If, on the other hand, you create an eventListener for that element within that IIFE and call doSomething() when the listener detects a click event, you're good because the listener and doSomething() share the IIFE's scope.
For little web apps with a minimal amount of code, it doesn't matter. But if you aspire to write large, maintainable codebases, onclick="" is a habit that you should work to avoid.
Revision
Unobtrusive JavaScript approach was good in the PAST - especially events handler bind in HTML was considered as bad practice (mainly because onclick events run in the global scope and may cause unexpected error what was mention by YiddishNinja)
However...
Currently it seems that this approach is a little outdated and needs some update. If someone want to be professional frontend developper and write large and complicated apps then he need to use frameworks like Angular, Vue.js, etc... However that frameworks usually use (or allow to use) HTML-templates where event handlers are bind in html-template code directly and this is very handy, clear and effective - e.g. in angular template usually people write:
<button (click)="someAction()">Click Me</button>
In raw js/html the equivalent of this will be
<button onclick="someAction()">Click Me</button>
The difference is that in raw js onclick event is run in the global scope - but the frameworks provide encapsulation.
So where is the problem?
The problem is when novice programmer who always heard that html-onclick is bad and who always use btn.addEventListener("onclick", ... ) wants to use some framework with templates (addEventListener also have drawbacks - if we update DOM in dynamic way using innerHTML= (which is pretty fast) - then we loose events handlers bind in that way). Then he will face something like bad-habits or wrong-approach to framework usage - and he will use framework in very bad way - because he will focus mainly on js-part and no on template-part (and produce unclear and hard to maintain code). To change this habits he will loose a lot of time (and probably he will need some luck and teacher).
So in my opinion, based on experience with my students, better would be for them if they use html-handlers-bind at the beginning. As I say it is true that handlers are call in global scope but a this stage students usually create small applications which are easy to control. To write bigger applications they choose some frameworks.
So what to do?
We can UPDATE the Unobtrusive JavaScript approach and allow bind event handlers (eventually with simple parameters) in html (but only bind handler - not put logic into onclick like in OP quesiton). So in my opinion in raw js/html this should be allowed
<button onclick="someAction(3)">Click Me</button>
or
function popup(num,str,event) {
let re=new RegExp(str);
// ...
event.preventDefault();
console.log("link was clicked");
}
link
But below examples should NOT be allowed
<button onclick="console.log('xx'); someAction(); return true">Click Me</button>
link
The reality changes, our point of view should too
There are a few reasons:
I find it aids maintenence to separate markup, i.e. the HTML and client-side scripts. For example, jQuery makes it easy to add event handlers programatically.
The example you give would be broken in any user agent that doesn't support javascript, or has javascript turned off. The concept of progressive enhancement would encourage a simple hyperlink to /map/ for user agents without javascript, then adding a click handler prgramatically for user agents that support javascript.
For example:
Markup:
<a id="example" href="/map/">link</a>
Javascript:
$(document).ready(function(){
$("#example").click(function(){
popup('/map/', 300, 300, 'map');
return false;
});
})
It's a new paradigm called "Unobtrusive JavaScript". The current "web standard" says to separate functionality and presentation.
It's not really a "bad practice", it's just that most new standards want you to use event listeners instead of in-lining JavaScript.
Also, this may just be a personal thing, but I think it's much easier to read when you use event listeners, especially if you have more than 1 JavaScript statement you want to run.
Your question will trigger discussion I suppose. The general idea is that it's good to separate behavior and structure. Furthermore, afaik, an inline click handler has to be evalled to 'become' a real javascript function. And it's pretty old fashioned, allbeit that that's a pretty shaky argument. Ah, well, read some about it #quirksmode.org
onclick events run in the global scope and may cause unexpected
error.
Adding onclick events to many DOM elements will slow down the
performance and efficiency.
Two more reasons not to use inline handlers:
They can require tedious quote escaping issues
Given an arbitrary string, if you want to be able to construct an inline handler that calls a function with that string, for the general solution, you'll have to escape the attribute delimiters (with the associated HTML entity), and you'll have to escape the delimiter used for the string inside the attribute, like the following:
const str = prompt('What string to display on click?', 'foo\'"bar');
const escapedStr = str
// since the attribute value is going to be using " delimiters,
// replace "s with their corresponding HTML entity:
.replace(/"/g, '"')
// since the string literal inside the attribute is going to delimited with 's,
// escape 's:
.replace(/'/g, "\\'");
document.body.insertAdjacentHTML(
'beforeend',
'<button onclick="alert(\'' + escapedStr + '\')">click</button>'
);
That's incredibly ugly. From the above example, if you didn't replace the 's, a SyntaxError would result, because alert('foo'"bar') is not valid syntax. If you didn't replace the "s, then the browser would interpret it as an end to the onclick attribute (delimited with "s above), which would also be incorrect.
If one habitually uses inline handlers, one would have to make sure to remember do something similar to the above (and do it right) every time, which is tedious and hard to understand at a glance. Better to avoid inline handlers entirely so that the arbitrary string can be used in a simple closure:
const str = prompt('What string to display on click?', 'foo\'"bar');
const button = document.body.appendChild(document.createElement('button'));
button.textContent = 'click';
button.onclick = () => alert(str);
Isn't that so much nicer?
The scope chain of an inline handler is extremely peculiar
What do you think the following code will log?
let disabled = true;
<form>
<button onclick="console.log(disabled);">click</button>
</form>
Try it, run the snippet. It's probably not what you were expecting. Why does it produce what it does? Because inline handlers run inside with blocks. The above code is inside three with blocks: one for the document, one for the <form>, and one for the <button>:
let disabled = true;
<form>
<button onclick="console.log(disabled);">click</button>
</form>
Since disabled is a property of the button, referencing disabled inside the inline handler refers to the button's property, not the outer disabled variable. This is quite counter-intuitive. with has many problems: it can be the source of confusing bugs and significantly slows down code. It isn't even permitted at all in strict mode. But with inline handlers, you're forced to run the code through withs - and not just through one with, but through multiple nested withs. It's crazy.
with should never be used in code. Because inline handlers implicitly require with along with all its confusing behavior, inline handlers should be avoided as well.
Related
How to deal with eslint complaints about used-vars for functions called from dom events
When the function is called from an event such as a mouse click on a button or link, eslint complains about unused-vars because the only reference is the attached event in the HTML page. I like the unused-vgars warning otherwise. My current approach is to disable the lines with the eslint-disable line comment, i.e. const addStreetAddressLine = () => { // eslint-disable-line no-unused-vars but now I am sprinkling that big comment throughout my code - 3 functions so far. Is there another approach that is a bit cleaner?
It would be better to attach the listeners using Javascript. Inline handlers have way too many problems: they have crazy scoping rules, require global pollution to work, and can require ugly escaping when the function needs to be called with a string argument. Use addEventListener instead. Eg, change <span onclick="addStreetAddressLine()"> to <span class="someSpan"> document.querySelector('.someSpan').addEventListener('click', addStreetAddressLine); That will get rid of the linter warning and make your code more maintainable. The someSpan is just there to give you a way to select the element. You don't have to add a class, you just need some way to precisely select the element.
jQuery best practice - using selectors vs function(variable)
This is a general question about best practice in jQuery syntax/code organisation. Consider the following snippet, used in a jQuery AJAX function: if(obj.status == "error"){ $("#alert").html(obj.message); } I have also seen this written as: function alert_box(str) { var html_str = ''; $("#alert").html(html_str); } if(obj.status == "error"){ alert_box(obj.message); } Functionally, this is precisely the same. My question: Is there any semantic or practical reason for differentiating between the two? What about load time / performance issues?
This is seems to be a question of "why use functions in general"? The idea behind a function is that you're making a code block reusable without having to write out the same code again. If you want to do that same thing in several places throughout your script, the function makes sense. If you only do that once, it may not be as practical. Then again, functions also help you hide details where you don't care about them - so you can summarize an action while the details of that action are somewhere in that function definition. In this specific case, that function is broken anyway. Rather than using the passed in argument str, you have an empty variable html_str that you're replacing the html contents of an element with. Also, there's no need here to use html rather than text, which is better performance. function alert_box(str) { $("#alert").text(str); } Even though this is only a one liner, this can still be practical because it would let you use alert_box in several places throughout the script and not have to change those places later if you decide to change what alert_box does. Even something like changing the id of the element would require changes in several places, for example. It also worth noting that this function searches the DOM for "#alert" each time it runs. It would be most optimal to cache that reference like this: $alert = $("#alert"); function alert_box(str) { $alert.text(str); } A few things that are great to study: KISS DRY SOLID aka OOP
How to execute string containing function name AND parameters?
In javascript, I have a string like this: "doSomething('param1','param2')" And I want to execute it. I am aware that I could normally use window['doSomething']('param1', 'param2'); But that only works if my function name is separate from the arguments. In my case they are already combined. I think I can use eval() but the consensus seems to be that it should be avoided. Is there another way? EDIT: To answer the request for more info: I am using this string like this: <a id="YesButton" onclick="closeModalView_Yes("doSomething('param1','param2')")"> Where closeModalView_Yes will close the modal yes/no window and then execute the given function, although at times I may pass it doSomethingElse(param1) which only takes one parameter.
Use eval, just like: eval( "console.log( 'hey', 'here I am' )" ); However eval is pretty dangerous and it's not recommended. If you can (still we don't have much info about your case), render your JavaScript between <script> tags in your template, making it a "regular code", much easier to debug. Also a good practice is to pass data (i.e. with JSON) rather than code. Try rethinking your logic or provide additional information.
<a id="YesButton" onclick="closeModalView_Yes("doSomething('param1','param2')")"> You really shouldn't pass that as a string, but as a function: closeModalView_Yes(function(){ doSomething('param1','param2'); }); together with function closeModalView_Yes(callback) { // do whatever needs to be done to close the window // and after that callback(); } Btw, with your current approach the HTML is not even valid, it would need to be <a id="YesButton" onclick="closeModalView_Yes("doSomething('param1','param2')")"> <!-- ^^^^^^ ^^^^^^ --> You could've avoided that by registering the event via javascript instead of inline attributes.
Overwrite jQuery to execute code after element has become visible - how to?
I am developing a small javascript framework for internal use in our company. In some cases our framework is overwriting basic jQuery functionality in order to handle internal logic or fix browser bugs etc. I want these cases to be completely transparent for our developers so they can just keep using jQuery the way they are used to. I have come across a problem that I can’t seem to get my head around to do in a nice transparent way – at least I can’t come up with a good way to do it - so I turn to you :). What I want to do is overwrite jQuery in a way so I can execute a certain piece of code each time something “becomes visible”. For instance if a developer runs the show() method, I want to execute my code after the element has become visible. In fact no matter how the developer “makes an element visible” (e.g. show(), css(), animate() etc.) I want my code to run I know that show() for instance has a “callback” parameter that can be used for just that. But the point is; this should be totally transparent for the developer. He does not need to know that this code is “run underneath”. All my overwrites is done using closures, so I can save a reference to the original method, do my thing and execute the original. Here is an example of how I am doing this with the show method: (function ($) { var originalShowMethod = jQuery.fn.show; jQuery.fn.show = function (speed, easing, callback) { // Do stuff before var theReturn = jQuery(originalShowMethod.call(this, speed, easing, callback)); // Do stuff after return theReturn; }; })(jQuery); My initial approach was to just simply do it this way (running my code at “// Do stuff after”). That also works great except when you are passing a speed parameter (because then it's using something like setTimeout or setInterval internally), or using one of the other “show an element” methods. Is there some master “show an element” method in jQuery that I can overwrite so all methods that has something to do with “showing elements” will be affected by my piece of code? Or do I have to overwrite all "showing methods"? Can anyone give an example of how I can accomplice this task? and what i need to overwrite if that's the way to do it. Thanks in advance.
I think you want something like the .is() and the :visible selector, it sounds like you might want to run a function like this using setInterval function checkElementVis(){ if($(".element").is(":visible")){ alert("is visible"); } else { alert("not visible"); } }
Is there a way to jail in Javascript, so that the DOM isn't visible
I would really like to provide the user some scripting capabilities, while not giving it access to the more powerful features, like altering the DOM. That is, all input/output is tunneled thru a given interface. Like a kind of restricted javacsript. Example: If the interface is checkanswer(func) this are allowed: checkanswer( function (x,y)={ return x+y; } but these are not allowed: alert(1) document.write("hello world") eval("alert()") EDIT: what I had in mind was a simple language that was implemented using javascript, something like http://stevehanov.ca/blog/index.php?id=92
(Edit This answer relates to your pre-edit question. Don't know of any script languages implemented using Javascript, although I expect there are some. For instance, at one point someone wrote BASIC for Javascript (used to have a link, but it rotted). The remainder of this answer is therefore pretty academic, but I've left it just for discussion, illustration, and even cautionary purposes. Also, I definitely agree with bobince's points — don't do this yourself, use the work of others, such as Caja.) If you allow any scripting in user-generated content, be ready for the fact you'll be entering an arms race of people finding holes in your protection mechanisms and exploiting them, and you responding to those exploits. I think I'd probably shy away from it, but you know your community and your options for dealing with abuse. So if you're prepared for that: Because of the way that Javascript does symbol resolution, it seems like it should be possible to evaluate a script in a context where window, document, ActiveXObject, XMLHttpRequest, and similar don't have their usual meanings: // Define the scoper var Scoper = (function() { var rv = {}; rv.scope = function(codeString) { var window, document, ActiveXObject, XMLHttpRequest, alert, setTimeout, setInterval, clearTimeout, clearInterval, Function, arguments; // etc., etc., etc. // Just declaring `arguments` doesn't work (which makes // sense, actually), but overwriting it does arguments = undefined; // Execute the code; still probably pretty unsafe! eval(codeString); }; return rv;; })(); // Usage: Scoper.scope(codeString); (Now that uses the evil eval, but I can't immediately think of a way to shadow the default objects cross-browser without using eval, and if you're receiving the code as text anyway...) But it doesn't work, it's only a partial solution (more below). The logic there is that any attempt within the code in codeString to access window (for instance) will access the local variable window, not the global; and the same for the others. Unfortunately, because of the way symbols are resolved, any property of window can be accessed with or without the window. prefix (alert, for instance), so you have to list those too. This could be a long list, not least because as bobince points out, IE dumps any DOM element with a name or an ID onto window. So you'd probably have to put all of this in its own iframe so you can do an end-run around that problem and "only" have to deal with the standard stuff. Also note how I made the scope function a property of an object, and then you only call it through the property. That's so that this is set to the Scoper instance (otherwise, on a raw function call, this defaults to window!). But, as bobince points out, there are just so many different ways to get at things. For instance, this code in codeString successfully breaks the jail above: (new ('hello'.constructor.constructor)('alert("hello from global");'))() Now, maybe you could update the jail to make that specific exploit not work (mucking about with the constructor properties on all — all — of the built-in objects), but I tend to doubt it. And if you could, someone (like Bob) would just come up with a new exploit, like this one: (function(){return this;})().alert("hello again from global!"); Hence the "arms race." The only really thorough way to do this would be to have a proper Javascript parser built into your site, parse their code and check for illegal accesses, and only then let the code run. It's a lot of work, but if your use-case justifies it...
T.J. Crowder makes an excellent point about the "arms race." It's going to be very tough to build a watertight sandbox. it's possible to override certain functions, though, quite easily. Simple functions: JavaScript: Overriding alert() And according to this question, even overriding things like document.write is as simple as document.write = function(str) {} if that works in the browsers you need to support (I assume it works in all of them), that may be the best solution. Alternative options: Sandboxing the script into an IFrame on a different subdomain. It would be possible to manipulate its own DOM and emit alert()s and such, but the surrounding site would remain untouched. You may have to do this anyway, no matter which method(s) you choose Parsing the user's code using a white list of allowed functions. Awfully complex to do as well, because there are so many notations and variations to take care of. There are several methods to monitor the DOM for changes, and I'm pretty sure it's possible to build a mechanism that reverts any changes immediately, quite similar to Windows's DLL management. But it's going to be awfully complex to build and very resource-intensive.
Not really. JavaScript is an extremely dynamic language with many hidden or browser-specific features that can be used to break out of any kind of jail you can devise. Don't try to take this on yourself. Consider using an existing ‘mini-JS-like-language’ project such as Caja.
Sounds like you need to process the user entered data and replace invalid mark-up based on a white list or black-list of allowed content.
You can do it the same way as Facebook did. They're preprocessing all the javascript sources, adding a prefix to all the names other than their own wrapper APIs'.
I got another way: use google gears WorkerPool api See this http://code.google.com/apis/gears/api_workerpool.html A created worker does not have access to the DOM; objects like document and window exist only on the main page. This is a consequence of workers not sharing any execution state. However, workers do have access to all JavaScript built-in functions. Most Gears methods can also be used, through a global variable that is automatically defined: google.gears.factory. (One exception is the LocalServer file submitter, which requires the DOM.) For other functionality, created workers can ask the main page to carry out requests.
What about this pattern in order to implement a sandbox? function safe(code,args) { if (!args) args=[]; return (function(){ for (i in window) eval("var "+i+";"); return function(){return eval(code);}.apply(0,args); })(); } ff=function() { return 3.14; } console.log(safe("this;"));//Number console.log(safe("window;"));//undefined console.log(safe("console;"));//undefined console.log(safe("Math;"));//MathConstructor console.log(safe("JSON;"));//JSON console.log(safe("Element;"));//undefined console.log(safe("document;"));//undefined console.log(safe("Math.cos(arguments[0]);",[3.14]));//-0.9999987317275395 console.log(safe("arguments[0]();",[ff]));//3.14 That returns: Number undefined undefined MathConstructor JSON undefined undefined -0.9999987317275395 3.14 Can you please provide an exploit suitable to attack this solution ? Just to understand and improve my knowledge, of course :) THANKS!
This is now easily possible with sandboxed IFrames: var codeFunction = function(x, y) { alert("Malicious code!"); return x + y; } var iframe = document.createElement("iframe"); iframe.sandbox = "allow-scripts"; iframe.style.display = "none"; iframe.src = `data:text/html, <script> var customFunction = ${codeFunction.toString()}; window.onmessage = function(e) { parent.postMessage(customFunction(e.data.x, e.data.y), '*'); // Get arguments from input object } </script>`; document.body.appendChild(iframe); iframe.onload = function() { iframe.contentWindow.postMessage({ // Input object x: 5, y: 6 }, "*"); } window.onmessage = function(e) { console.log(e.data); // 11 document.body.removeChild(iframe); }