The project I'm working on uses double quotes for strings in HTML and JavaScript by default.
This has been decided and there is nothing I can do about it.
What hasn't been decided however, is how we should deal with nested quotations.
There are three options I can think of:
Begin with outermost quote & alternate.
element.innerHTML = "<a href='...'></a>";
<button onclick="alert('...')"></button>
Begin with innermost quote & alternate.
element.innerHTML = '';
<button onclick='alert("...")'></button>
Escape everything.
element.innerHTML = "";
<button onclick="alert("...")"></button>
Which is the best practice in terms of overall convenience, style, safety, maintainability, portability, etc.? And please suggest a better way if there is other than the above.
I think the examples you gave are structured a little weird. Regardless, here's another option that I think you'll like better: template literals. You use backticks to define them. However, to use it effectively, you'll have to define your function outside of the string you passed into the HTML parameter for onclick.
Here's an example:
<button onclick="foo()">Click Me!</button>
<script>
function foo() {
alert(`"Hello"`);
}
</script>
Related
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.
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.
If I define a JavaScript code snippet in my HTML, like so:
<div id=myElem onMyUpdate="alert('Update called for ' + this.id)">...
then what is the most elegant way of evaluating it from within JavaScript with this properly assigned?
What I came up with so far is something like this:
if (elem.hasAttribute('onMyUpdate'))
(function () { eval(elem.getAttribute('onMyUpdate')) }).call(elem);
which looks terrible (to me), but works. Any better/more elegant alternatives?
MDN says there used to be the second argument to eval() for doing just that but it's deprecated now; MDN then suggests to use operator with() instead, which, if you follow the link provided, turns out to be made deprecated by the latest standard. Dead end, in other words.
(As a side note, StackOverflow ignores the word this in search terms and thus it may miss relevant answers - is there a way of telling it not to?)
Edit: I forgot to mention: no jQuery please, just vanilla JavaScript
How about this:
if(elem.hasAttribute('onMyUpdate')) {
var fun = new Function(elem.getAttribute('onMyUpdate'));
fun.call(elem);
}
Ideally, you should do this completely unobtrusively, and without the use of eval:
<div id="myElem"></div>
.
var elem = document.getElementById('myElem');
elem.onMyUpdate = function () {
alert(this.id);
};
// ...
elem.onMyUpdate && elem.onMyUpdate();
Instead of using this you can use elem.
<div id=myElem onMyUpdate="alert('Update called for ' + elem.id)">...
js:
if (elem.hasAttribute('onMyUpdate')){ // all variable available here will be available inside eval.
eval( elem.getAttribute('onMyUpdate') );
}
A more elegant solution would be to use custom events, bind handlers to some custom events on your HTML elements and trigger them in some other parts of your code. See this tutorial and the answer by Sidharth Mudgal for some examples.
I'm seeing code in the following form - is such use of eval() safe?
function genericTakeAction(frm_name,id,pagenum,action)
{
var rset=eval("document."+frm_name);
var x=eval("document."+frm_name+".edit_key");
var y=eval("document."+frm_name+".cAction")
if(x)
x.value=id;
if(y)
y.value=action;
page_list(pagenum);
}
Its used as:
<a href="javaScript:;" onClick="genericTakeAction('frmSearch',
'<?php echo $rec_id;?>','<?php echo $pagenum?>','makeOpen')"
class='link6'>Make Open</a>
Whether it's right or wrong, it's needlessly complicated.
function genericTakeAction(frm_name,id,pagenum,action)
{
var rset = document[frm_name];
var x = rset.edit_key;
var y = rset.cAction;
if(x)
x.value=id;
if(y)
y.value=action;
page_list(pagenum);
}
This works because in JavaScript, you can access an object's properties in one of two ways: Either using dotted syntax and a literal identifier, e.g. x = obj.foo;, or using bracket syntax and a string identifier, e.g. x = obj["foo"];. (Note how foo was not in quotes in the first one, but was in quotes for the second; but both do exactly the same thing. Also note that since the property name is a string in the second case, you can use any expression that results in a string, so y = "f"; x = obj[y + "oo"]; also works.)
P.S. It's wrong
eval() is generally frowned upon because, as you are already aware, it is considered unsafe.
In the browser environment, however, it is less of an issue, because in fact, any user could eval() any code they wanted to, using tools like Firebug, etc.
There is still an issue, in that the eval() embedded in the code can be run without the user knowing that he was triggering an eval(), but it's still much less of an issue than in a server-side environment like PHP.
eval() is actually typically used as you've shown to run JSON code being returned from a server-side request. Newer browsers can import JSON more safely using a dedicated JSON parse() function, but older browsers do not have this function and are forced to use eval() for this. Most JSON libraries have eval() in their code somewhere for this reason, but will generally do some sanitisation of the input before running it through eval().
Even if it might look a little bit convoluted, as others have already mentioned, from a pure security perspective, you have to make sure that the 'frm_name' parameter of the genericTakeAction() function can never contain user-supplied data.
In your example, the 'frm_name' parameter contains the hard-coded literal 'frmSearch'. So it is ok as long as this genericTakeAction() function does not get called somewhere else with user-supplied data for the 'frm_name' parameter.
See http://en.wikipedia.org/wiki/Cross-site_scripting#Traditional_versus_DOM-based_vulnerabilities
The documentation of some JavaScript APIs shows the following snippets as an example of how to invoke some function:
<button type="button" onClick="foo.DoIt(72930)">Click</button>
<button type="button" onClick="foo.DoIt(42342::37438)">Click</button>
:: is obviously used here to allow either one or two arguments to be passed to the function.
What does :: do in JavaScript?
And how does the function know if one or two values were passed? How does it read them?
On closer look, the examples show other weird stuff like
<button type="button" onClick="foo.Bar(72//893)">Click</button>
<button type="button" onClick="foo.Qux(425;1,34::)">Click</button>
At least the // looks just wrong.
So I guess it's not some fancy new syntax that I'm not aware of, but maybe the examples are just missing quotes around a single string argument.
It was certainly not the case at the time of your question, but right now :: is a valid ES7 operator. It's actually a shortcut for bind.
::foo.bar
is equivalent to
foo.bar.bind(foo)
See an explanation here for examples:
Nothing. It is a syntax error.
>>> alert(42342::37438)
SyntaxError: missing ) after argument list
:: has nothing to do with the number of parameters. You can do that already in JavaScript with a normal comma:
function SomeFunction(param1, param2) {
//...
}
SomeFunction('oneParam'); // Perfectly legal
Also, based on Tzury Bar Yochay's answer, are you sure you're not looking at something like the following?
$('this::is all one::parameter'); // jQuery selector
In which example did you see that? So far, JavaScript does not have a double colon operator!
The double colon replaced the single-colon selectors for pseudo-elements in CSS3 to make an explicit distinction between pseudo-classes and pseudo-elements. But that is CSS3, not JavaScript! Not At ALL!
It must be a typo for
<button type="button" onClick="foo.DoIt('72930')">Click</button>
<button type="button" onClick="foo.DoIt('42342::37438')">Click</button>
It could be using ECMAScript for XML (ECMA-357 standard) which would imply the double quotes are a XPath operator.
See ECMAScript for XML
I am guessing that the parameter list for foo.DoIt() is generated by code, and one the values was empty.
Perhaps it's a typo, and the whole thing is expected to be in quotes.