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);
});
Related
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.
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');
}
});
I have a script with the following (only showing the applicable lines)
var setUploadDoneIndicator = function(form)
{
if ($(form).find('[id$=_upload_done_indicator]').is(':checked') == false)
{
console.log("Setting indicator");
$(form).find('[id$=_upload_done_indicator]').trigger('click');
}
}
var unsetUploadDoneIndicator = function(form)
{
if ($(form).find('[id$=_upload_done_indicator]').is(':checked') == true)
{
console.log("UnSetting indicator");
$(form).find('[id$=_upload_done_indicator]').trigger('click');
}
}
$('[id$=_upload_form]').each(function (event)
{
current_form = this;
$(this).fileupload(
{
done: function (e, data)
{
setUploadDoneIndicator(current_form);
}
});
}
This ticks a checkbox correctly, the idea is to listen on the checked state of the checkbox on another JS File. If I call unsetUploadDoneIndicator() right after setUploadDoneIndicator() in this script, it unticks the tickbox.
Then in another JS file I have
$('#pricing_ab_upload_done_indicator').change(function()
{
if ($(this).is(':checked'))
{
console.log("got checked");
unsetUploadDoneIndicator($('#pricing_ab_upload_form'));
}
});
Which calls the function in the first script, but does not untick the tickbox. I am unsure whether the pointer is passed over correctly, when printing out the received object in unsetUploadDoneIndicator() it does print out a JQuery object which seems correct, the
if ($(form).find('[id$=_upload_done_indicator]').is(':checked')
returns true, but the trigger does not happen, so i'm not sure if the element is actually found.
If I change the second script with the following, the trigger to uncheck the box does not happen either
$('#pricing_ab_upload_done_indicator').change(function()
{
if ($(this).is(':checked'))
{
console.log("got checked");
$('#pricing_ab_upload_done_indicator').trigger('click');
}
});
Why would the trigger not happen?
It's easier to just remove your functions and use the following:
$('[id$=_upload_form]').each(function (event) {
current_form = this;
$(this).fileupload( {
done: function (e, data) {
var checkbox = $(current_form).find('[id$=_upload_done_indicator]');
// Set the opposite value
checkbox.prop("checked", !checkbox.prop("checked"));
}
});
}
Avoid binding multiple events on the same action. I see you have a .change event on the checkbox as well, which will result in the "click" event triggering twice. This will make it look like nothing happened.
On a sidenote, if you are using the same selector often (like $(current_form).find('[id$=_upload_done_indicator]') in your code example), it's better to cache it in a variable to increase performance. It's also easier to work with to write a short variable name instead of repeating the entire selector.
Following #Dark Ashelin's advice, I added a custom event. This seems like a much simpler and more logical way of implementing this type of functionality.
In the first script when the upload is done I have
$(current_form).trigger('event_upload_completed');
In the second script I have
$('#pricing_ab_upload_form').on('event_upload_completed', function()
{
console.log("Upload completed");
});
This way does not require a callback to the first script from the second script to reset the state of the checkbox (it does not require a dummy html element at all)
I tested this in the latest Chrome and FireFox and in IE11. If this method has compatibility issues with older IE's please comment on this answer
Is it possible to detect if certain events are supported in certain browsers?
I can detect if the browser supports document.addEventListener, but I need to know if it supports the event DOMAttrModified. Firefox and Opera support it, but Chrome and others do not.
Updated answer:
Yes, you can feature-detect this. Create an element, listen for the event, and change an attribute on the element. In my tests, you don't even have to add the element to the DOM tree, making this a nice, contained feature detection.
Example:
function isDOMAttrModifiedSupported() {
var p, flag;
flag = false;
p = document.createElement('p');
if (p.addEventListener) {
p.addEventListener('DOMAttrModified', callback, false);
}
else if (p.attachEvent) {
p.attachEvent('onDOMAttrModified', callback);
}
else {
// Assume not
return false;
}
p.setAttribute('id', 'target');
return flag;
function callback() {
flag = true;
}
}
Live copy
Firefox triggers the callback on all of the modifications above; Chrome on none of them.
Original answer:
You can feature-detect whether some events are supported, as shown on this handy page. I don't know if you can test specifically for that one, but if you can, that code may well get you started.
Update: I dumped Kangax's code into JSBin and tried it, doesn't look like that sniffing technique works for that event (unless I have the name spelled incorrectly or something; Firefox is showing "false"). But my technique above does.
function isDOMAttrModifiedSupported () {
var supported = false;
function handler() {
supported = true;
}
document.addEventListener('DOMAttrModified', handler);
var attr = 'TEST';
document.body.setAttribute(attr, 'foo'); // aka $('body').attr(attr, 'foo');
document.removeEventListener('DOMAttrModified', handler);
document.body.setAttribute(attr, null);
return supported;
}
Is there something I can do like this (perhap via a plugin)
if ( ! $('form#contact input]').hasFocus()) {
$('form#contact input:first]').focus();
}
Basically, set focus to the first input, but only if the user has not already clicked into anything?
I know this will work too, but is there anything more elegant?
$(function() {
var focused = false;
$('form#contact input]').focus(function() {
focused = true;
});
setTimeout(function() {
if ( ! focused) {
$('form#contact input:first]').focus();
}
}, 500);
});
There is no native solution but yes there is a more elegant way you can do it:
jQuery.extend(jQuery.expr[':'], {
focus: "a == document.activeElement"
});
You're defining a new selector. See Plugins/Authoring. Then you can do:
if ($("...").is(":focus")) {
...
}
or:
$("input:focus").doStuff();
$('input:focus')
It's CSS. You don't need to create a "custom selector." It already exists! http://www.w3schools.com/CSS/pr_pseudo_focus.asp
Just attach whatever process you want to do to that selector, and it will weed it out if the element in question is not focused. I did this recently to keep a keyup from instantiating an email input error check when the e-mail input wasn't being used.
If all you're trying to do is check if the user has focused on anything themselves, just do this:
if($('input:focus').size() == 0){
/* Perform your function! */
}
jQuery 1.6 now has a dedicated :focus selector.
I had trouble with cletus approach, using jQuery 1.3.2 and Firefox 3.6.8, because the string "a == document.activeElement" was not a valid function.
I fixed it defining a function for the focus key. In fact, all other keys defined in jQuery.expr[':'] are defined as functions. Here's the code:
jQuery.extend(jQuery.expr[':'], {
focus: function(e){ return e == document.activeElement; }
});
So, now it works as expected.
However, I was experiencing some strange behaviour in Firefox 3.6.8 (maybe a bug in FF?). If I clicked on an input text while the page was rendering, and if I called is(":focus") on page load, I would get an error from the browser, reported by FireBug, and the script would break.
To solve this, I surrounded the code with a try...catch block, returning false on error. Use it if you want to prevent your users from experiencing the same error:
jQuery.extend(jQuery.expr[':'], {
focus: function(e){
try{ return e == document.activeElement; }
catch(err){ return false; }
}
});
Frustratingly difficult to find a solution to this problem considering the solution is actually very simple:
if (document.activeElement == this) {
// has focus
}
if (document.activeElement != this) {
// does not have focus
}
No, there isn't.
However, you can simulate it like this:
$(':input')
.data('focused', false)
.focus(function() { $.data(this, 'focused', true); })
.blur(function() { $.data(this, 'focused', false); });
There is a plugin http://plugins.jquery.com/project/focused
Also you can check Using jQuery to test if an input has focus
Here is a succinct way to do it.
$(document.activeElement)
or to plug it into your example..
if ($('form#contact input]')[0]!=$(document.activeElement)) { ... }
I know this is an old question, but may be my solution will help someone :)
since this didnt worked for me:
if ($(this)!=$(document.activeElement)) { ... }
..were "this" is returned from blur function. So i did this:
if ($(document.activeElement).attr("class") != "input_textbox"){ ... }
$('*:focus')
(Necro ftw, but still valid and useful)