Javascript: Equivalent Destructuring assignment for IE [duplicate] - javascript

I have a javascript code that is given below that is ES6 compatible however IE 11 does not support this. What would be the replacement code for this such that it works across all browsers?
[...document.querySelectorAll('.row')]
Im using this for 'click' event handling:
Array.prototype.slice.call(document.querySelectorAll('.row'))
.forEach(function(header) {
return header.addEventListener('click', function(e) {
headerClick(e, header, header.querySelector('.exy'))
});
});

For all browsers, you can use Array.prototype.slice via call or apply (it works on any array-like object):
Array.prototype.slice.call(document.querySelectorAll('.row'))
About your updated question:
Im using this for 'click' event handling:
Array.prototype.slice.call(document.querySelectorAll('.row'))
.forEach(function(header) {
return header.addEventListener('click', function(e) {
headerClick(e, header, header.querySelector('.exy'))
});
});
I wouldn't use querySelectorAll for this at all, I'd use event delegation. Presumably all of those .row elements are inside a common container (ultimately, of course, they're all in body, but hopefully there's a container "closer" to them than that). With event delegation, you do this:
Hook click just once, on the container
When a click occurs, check to see if it passed through one of your target elements en route to the container
For your quoted code, that looks something like this:
// A regex we'll reuse
var rexIsRow = /\brow\b/;
// Hook click on the container
document.querySelector("selector-for-the-container").addEventListener(
"click",
function(e) {
// See if we find a .row element in the path from target to container
var elm;
for (elm = e.target; elm !== this; elm = elm.parentNode) {
if (rexIsRow.test(elm.className)) {
// Yes we did, call `headerClick`
headerClick(e, elm, elm.querySelector('.exy'));
// And stop looking
break;
}
}
},
false
);
On more modern browsers, you could use elm.classList.contains("row") instead of the regular expression, but sadly not on IE9 or earlier.
That said, rather than maintaining a separate codebase, as gcampbell pointed out you could use ES6 (ES2015) features in your code and then transpile with a transpiler that converts them (well, the ones that can be converted, which is a lot of them) to ES5 syntax. Babel is one such transpiler.

Related

Inline Editing of Textarea – How to get the id?

I use jqInlineEdit for inline editing on a web page. Everything works, except I don't know how to get the id of the item which I need for saving the change to the database(via Django).
The HTML looks like this:
<div id="remark14756" class="remark" data-cid="14756">
Sample Text
</div>
That's the JavaScript:
<script src="/static/inline-edit.jquery.js"></script>
<script>
$(".remark").inlineEdit({
type: 'textarea',
onChange: function (e, text, html) {
// Executes when exiting inline edit mode and a change has been made
c_id = $(this).attr("data-cid");
alert("Test: ", c_id)
}
});
</script>
Obviously, $(this) does not work in this context. I tried everything and searched a lot but I can't find how to do it the right way. Does anybody know the answer?
The inlineEdit docs say:
onChange(this, text, html) - Executes when exiting inline edit mode and a change has been made
with the use of this being quite misleading.
therefore the first param is actually the Element.
$(".remark").inlineEdit({
type: 'textarea',
onChange: function (elem, text, html) {
// `this` refers to inlineEdit instance plugin
// `elem` is the currently edited element
const c_id = $(elem).attr("data-cid");
alert(c_id); // 14756
}
});
That plugin is not performing in an expected "jQuery Plugin" way.
Usually properly written plugins should:
bind all methods to the Element callee,
(in case of Event methods) the first parameter should always refer to the original Event.
allowing a developer to reference it using the this keyword to get the native JS Element or either doing $(this) inside the exposed public Methods just like we're expected from native jQuery Methods, and to have accessible the Event (i.e: useful in case we use arrow functions to extract the currentTarget since the inexistence of this keyword)
$someElem.on('click', function(evt) {
const $el = $(this); // what we're used to
});
$someElem.on('click', (evt) => {
const $el = $(evt.currentTarget); // the importance of always passing the Event as first param
});
clearly not implemented in that plugin.

document.querySelector() on elements not in DOM?

I'm working on a website, with jQuery but I'm trying to not use it anymore. In jQuery you can add an even listener on a element that wasn't on the website or wasn't created yet and no problem. I have elements that are only on the DOM when you're logged in, and I only have one JS file for the whole website.
Problem is, for example, when you're logged in you can't see the "log in" button, it's not even in the DOM, but it still have the event listener in the code, no error on the console, script runs well.
$("#logInButton").on("click", somefunction);
But, using document.querySelector("#logInButton").onclick = somefunction and being logged in already, it throws an error because document.querySelector("#logInButton") is null.
I can do like:
let logInButton = document.querySelector("#logInButton");
logInButton ? logInButton.onclick = somefunction : "";
And it works well, but I know it's not a good practice. Any workaround or improvement to that, not using jQuery?
JSFiddle if what happens. (See console)
And it works well, but I know it's not a good practice.
If having #logInButton on the page is optional, that's perfectly good practice — other than using onclick rather than addEventListener (but that's probably a matter of style). Naturally, you'd have this code in a script linked at the end of the document, just prior to the </body> tag (or trigger it via a DOMContentLoaded callback).
But if you want the equivalent of the jQuery, you need to think in jQuery's "set-based" mindset and use querySelectorAll:
// Not very efficient
document.querySelectorAll("#logInButton").forEach(function() {
// Set up the handler here using `this`
});
Except that jQuery optimizes queries using #id format to a getElementById call (which is dramatically faster) and then uses an if (like yours) to build the set with either one element or zero.
Perhaps in your quest to not use jQuery, you might give yourself a couple of helper functions to take its place, as the DOM API is quite verbose. If you like jQuery's set-based nature, you might even make them set-based:
function MyQuery(selector) {
if (!selector) {
this.data = [];
} else if (typeof selector === "string") {
// (jQuery takes it further than this, search in an unminified version for `rquickExpr`)
var id = /#([\w-]+)/.match(selector);
if (id) {
var e = document.getElementById(id[0]);
this.data = e ? [e] : [];
} else {
this.data = Array.from(document.querySelector(selector));
}
} else {
/* ...handle other things, such as DOM elements or arrays of them...? */
this.data = /*...*/;
}
}
MyQuery.prototype = {
constructor: MyQuery,
on: function(eventName, handler) {
this.data.forEach(function(element) {
element.addEventListener(eventName, handler);
});
return this;
}
// ...etc...
};
function qset(selector) {
return new MyQuery(selector);
}
Then
qset("#logInButton").on("click", /*...*/);
Of course, you might find yourself basically recreating jQuery. But if you keep it lean...
Side note: Using forEach on the return value of querySelectorAll requires an up-to-date browser, or that you polyfill it:
if (typeof NodeList !== "undefined" &&
NodeList.prototype &&
!NodeList.prototype.forEach) {
Object.defineProperty(NodeList.prototype, "forEach", {
value: Array.prototype.forEach
});
}
For truly obsolete browsers (like IE8), you'd have to polyfill Array.prototype.forEach first.
You can do it the same way jQuery does it, using event bubbling.
document.addEventListener('click', function (ev) {
if (ev.target.id === 'someIdHere') {
console.log('click');
}
});

AttachEvent Not Working on IE11

I am trying to make two checkboxes, out of which only one can be selected at any point of time. I searched the forums a lot and found a few suggestions.
if (document.attachEvent){
// For IE Browsers.
document.attachEvent("DOMContentLoaded", function (event) {
var saSelector = document.querySelector('input[name=saWrite]');
var cgSelector = document.querySelector('input[name=cgWrite]');
if (cgSelector !== null) {
cgSelector.attachEvent('change', function (event) {
if (cgSelector.checked) {
document.querySelector('input[name=saWrite]').checked = false;
}
});
}
if (saSelector !== null) {
saSelector.attachEvent('change', function (event) {
if (saSelector.checked) {
document.querySelector('input[name=cgWrite]').checked = false;
}
});
}
});
}
I wrote a similar function with addEventListener in place of attachEvent for non-IE browsers. That works on Firefox. But this method somehow doesn't work for IE. Am I doing something wrong here? Any suggestions would be helpful. I wish i could use JQuery for this. But i cant.
https://jsfiddle.net/20g7ym8q/
You say you want to use JQuery but you can't. I realize starting out that may seem like a real limitation, but it isn't. Anything you can do with JQuery you can do with JavaScript.
Your code won't work on IE11 because attachEvent has been deprecated and removed in favor of accepting addEventListener as the standard way to attach an event in all modern browsers. If you're looking for generational support without JQuery and without code duplication, setting up your own Object to use as an intermediate layer between your code and the browser is probably the best way to go about this.
function $(ele) {
return {
ele: document.querySelector(ele),
on: function(ev, fn) {
(document.attachEvent) ?
this.ele.attachEvent(ev, fn) :
this.ele.addEventListener(ev, fn);
},
checked: function(change) {
if(typeof change !== undefined) this.ele.checked = change;
return this.ele.checked;
}
}
}
The above is a function that returns an Object with two methods and a property. It works similarly to JQuery for familiarity and consistency, but it is without the overhead of including the entire JQuery library.
The methods allow you to add an event using .on with an event type and function as parameters. The methods also allow you to set or get the checked property of the specified element. .checked() will simply return a boolean as to whether the box is checked, .checked(boolean) will set the elements property to the desired state.
In practice, to solve your dilemma of only one allowable check box, you could do this:
var sa = $('input[name="saWrite"]');
var cg = $('input[name="cgWrite"]');
cg.on('click', function(ev) {
sa.checked(false)
});
sa.on('click', function(ev) {
cg.checked(false);
});

setting events programmatically

I am setting the className of a table row in my code, is it possible to do something similiar to set an event on a row? This is along the lines of what I would like to do :
for (var i = 1; i < numRows; i++) {
var ID = table.rows[i].id;
if (i % 2 == 0) {
table.rows[i].className = "GridRow";
table.rows[i].onmouseout = "GridRow";
}
else {
table.rows[i].className = "GridRowAlt";
table.rows[i].onmouseout = "GridRowAlt";
}
}
Yes, you can assign a function instance to the event handler that way:
table.rows[i].onmouseout = function() { ... };
Be careful doing that in loops, because you're creating a new function on every loop and the function closes over the data in scope (and so has an enduring reference to it, not a copy of it as of when the function was created; see this other recent question for more). But don't worry, closures are not complicated once you understand how they work.
In general, this is called "DOM0" event handling because it involves a method of attaching event handlers that was created prior to the first DOM specification. As of DOM2, there's a better way addEventListener:
table.rows[i].addEventListener('mouseout',function() { ... }, false);
It's "better" because you can have more than one event handler on the same event of the same element, whereas with the DOM0 mechanism, assigning a new event handler disconnects the previous one (if any).
On IE prior to IE9, sadly, addEventListener wasn't supported but it did have the very similar attachEvent:
table.rows[i].attachEvent('onmouseout',function() { ... });
Note the differences:
addEventListener's event names don't have the "on" prefix
addEventListener has one more param than attachEvent, which you almost always want to set false
Update:
All of the examples above are for inline anonymous functions, which is a bit unlike me, because I don't like anonymous functions. So just for clarity, from an events perspective, a function is a function. It can be a named function you declare elsewhere, or an inline anonymous function, whatever:
// Hook up...
table.rows[i].addEventListener('mouseout', handleRowMouseOut, false);
// Nice, reusable function defined elsewhere
function handleRowMouseOut(event) {
// ...
}
Off-topic: It's these sorts of browser differences that lead me to geneerally recommend using a library like jQuery, Prototype, YUI, Closure, or any of several others. They smooth over differences for you as well as providing lots of handy utility functions.
table.rows[i].onmouseout = "GridRow"; doesn't make a lot of sense, table.rows[i].onmouseout = function(){alert('hello');}; or some other valid script ought to work though.
Why don't you just use jQuery or some other JavaScript framework? This way your code gets more simple.
var i = 0;
$('#some_table tr').each(function() {
if (i % 2 == 0) {
$(this).addClass('GridRow');
$(this).mouseout(function(evt) { /* your GridRow function */ });
} else {
$(this).addClass('GridRowAlt');
$(this).mouseout(function(evt) { /* your GridRowAlt function */ });
}
i++;
})
Sultan
The original question is not to alert "GridRow". I'm quit sure GridRow is a function name. Fortunately each function is a child of window so write window["GridRow"].
I would add a well known bind-event function, because you need it quite often.
var bindEvent=function(elem,evt,func){
if(elem.addEventListener){
elem.addEventListener(evt,func,false);
}
else if(elem.attachEvent){
elem.attachEvent('on'+evt,function(){
func.call(event.srcElement,event);
})
}
};
and then:
bindEvent(table.rows[i],"mouseout",window["GridRow"]);

How to expand an onchange event with JavaScript

This is a question I ran into about expanding on an element's JavaScript onchange event. I have several select elements that conditionally will have one onchange event attached to each of them (when they change to specific values, it hides/unhides certain elements). I want to conditionally add or append to another onchange event so they set a global variable if they do change without modifying or disabling the previous function already attached to them. Is there a way to "append" an additional function or add more functionality onto the already existing one?
Here is what I believe an example would be like:
<select id="selectbox1">
<option>...</option>
<option>...</option>
</select>
if (<certain conditions>) {
document.getElementById("selectbox1").onchange = function () {
//hides elements to reduce clutter on a web form ...
}
}
....
if (<other conditions>) {
document.getElementById("selectbox1").onchange = function2 () {
//set global variable to false
}
}
Alternatively I'd like to simply add the 1-liner "set global variable to false" to the original function.
You can cheat by simply having a composite function that calls the other functions.
document.getElementById("selectbox1").onchange = function() {
function1();
function2();
}
You can also use the observer pattern, described in the book Pro JavaScript Design Patterns. I have an example of its use in an article (here).
//– publisher class —
function Publisher() {
this.subscribers = [];
};
Publisher.prototype.deliver = function(data) {
this.subscribers.forEach(function(fn) { fn(data); });
};
//– subscribe method to all existing objects
Function.prototype.subscribe = function(publisher) {
var that = this;
var alreadyExists = publisher.subscribers.some(function(el) {
if (el === that) {
return;
}
});
if (!alreadyExists) {
publisher.subscribers.push(this);
}
return this;
};
You want to look at the addEventListener() and attachEvent() functions (for Mozilla-based browsers and IE respectively).
Take a look at the docs for addEventListener() and attachEvent().
var el = document.getElementById("selectbox1");
try { //For IE
el.attachEvent("onchange", function(){ code here.. });
}
catch(e) { //For FF, Opera, Safari etc
el.addEventListener("change", function(){ code here.. }, false);
}
You can add multiple listeners to each element, therefore more than one function can be called when the event fires.
Can you use jQuery? This will allow you to bind/manipulate/unbind events pretty easily. The only hitch is event handlers are activated in the order they are bound.
if (<certain conditions>) {
$("#selectbox1").bind("change", eventdata, function1);
}
if (<other conditions>) {
$("#selectbox1").bind("change", eventdata, function1);
}
And, you can also look into triggering custom events, if your needs are complex. For example, instead of "interpreting" onChange, maybe there is a way to specifically trigger custom events. See the last example on jQuery's page.
If you use jQUery you would have something like
<select id="selectbox1">
<option>...</option>
<option>...</option>
</select>
if (<certain conditions>) {
$("#selectbox1").change(function () {
//hides elements to reduce clutter on a web form ...
});
}
....
if (<other conditions>) {
$("#selectbox1").change(function () {
//set global variable to false
});
}
This will mostly take care of browser compatibility as well.
There are currently three different methods for defining event handlers (a function which is fired when a certain event is detected): the traditional method, the W3C method, and the Microsoft method.
Traditional method
In the traditional method, event handlers are defined by setting the onevent property of an element in Javascript (as you are doing in your example code), or by setting the onevent attribute in an HTML tag (such as <select onchange="...">). While this is the simplest method to use, its use is generally frowned upon now, because as you have discovered, it is rather rigid -- it is not easy to add and remove event handlers to an element that already has an event handler attached. As well, it is not considered proper practice anymore to mix javascript in with HTML, but rather it should be contained within or loaded via a <script> tag.
W3C / Microsoft methods
The W3C (World Wide Web Consortium) and Microsoft both define their own event models. The two models works essentially the same way, but use different syntaxes. The Microsoft model is used in Internet Explorer, and the W3C model is used in other browsers (Firefox, Opera, Safari, Chrome, etc.). In both of these models, there are functions provided to add event handlers (addEventListener for W3C, attachEvent for Microsoft) and remove event handlers (removeEventListener / detachEvent). This allows you to dynamically add and remove specific handlers to an element; in your case, you could add the first handler based on the first condition and the second based on the second condition. The "problem" with these methods is that there are two of them, and thus both methods need to be used in order to ensure that your event handler will be registered in all browsers (there are also a few subtle differences between the two models, but those differences are not important to the scope of this question). In fact, if you look, you will find a large number of "addEvent" functions which use both methods as necessary (and generally fall back to the traditional method for older browsers). For example, a contest was run on the QuirksMode blog back in 2005 to build the best "addEvent" function, the result of which (along with the winning function) you can see here.
As well, if you use a javascript library such as Prototype or jQuery, they come with built in event handling functions that will take care of the above for you.
Have a look at addEventListener - https://developer.mozilla.org/en/DOM/element.addEventListener
I feel as though I may be missing something important about your question, but would this more simple solution not work for you?
Simply check for the conditions inside of the onChange event and perform the actions as desired. It would be much easier than having to dynamically re-add/remove eventListeners
document.getElementById("selectbox1").onchange = function () {
if (<certain conditions>) {
//hides elements to reduce clutter on a web form ...
}
if (<other conditions>) { ... }
}

Categories

Resources