Re-writing simple jQuery into pure JavaScript - javascript

I'm building a JavaScript plugin which will bolt onto other websites.
The whole thing is written with pure JS but there's one bit I haven't been able to get away from jQuery with:
var key = "some_key";
var selector = "#my_input, input[name=my_input], .someInputs";
jQuery(document).on('change', selector, function() {
doSomething(key, this.value);
});
The reason I want to avoid jQuery is that I expect this JS to be included in a wide range of sites, many of which won't have jQuery. Some will have other frameworks already installed such as Mootools, some will have old versions of jQuery where .on() isn't supported, etc.
That, and I am ideally trying to keep it very lightweight, so adding in jQuery just for this tiny task seems excessive.

Here’s some futuristic JavaScript that does exactly the same thing:
var key = "some_key";
var selector = "#my_input, input[name=my_input], .someInputs";
document.addEventListener('change', function (e) {
if (e.target.matches(selector)) {
doSomething(key, e.target.value);
}
});
However, several browsers only support it with a prefix, so it’ll be closer to this:
var matches = (function () {
var names = ['matches', 'matchesSelector', 'mozMatchesSelector', 'webkitMatchesSelector', 'msMatchesSelector'];
for (var i = 0; i < names.length; i++) {
var name = names[i];
if (name in HTMLElement.prototype) {
return HTMLElement.prototype[name];
}
}
return null;
})();
var key = "some_key";
var selector = "#my_input, input[name=my_input], .someInputs";
document.addEventListener('change', function (e) {
if (matches.call(e.target, selector)) {
doSomething(key, e.target.value);
}
});
Assuming the selector isn’t dynamic and you need delegation, you can still do the verbose, manual check:
var key = "some_key";
document.addEventListener('change', function (e) {
var target = e.target;
if (target.id === 'my_input' ||
target.nodeName === 'INPUT' && target.name === 'my_input' ||
(' ' + target.className + ' ').indexOf(' someInputs ') !== -1) {
doSomething(key, target.value);
}
}, false);
As #T.J. Crowder points out, although this works for input elements, you’ll need to check an element’s parents in many cases. Here’s some even more futuristic JavaScript to accomplish the task:
function* ascend(element) {
do {
yield element;
} while ((element = element.parentNode));
}
var key = "some_key";
var selector = "#my_input, input[name=my_input], .someInputs";
document.addEventListener('change', function (e) {
var match = Array.from(ascend(e.target)).find(x => x.matches(selector));
if (match) {
doSomething(key, match.value);
}
});
If you smashed Firefox Nightly and Chrome together, this would work in that browser. We don’t have that, but feel free to shim Array.prototype.find!

Related

Custom vanilla JS for translation does not work on Android Chrome

I've been really struggling to use any (literally any!) client-side (e.g. web browser) translation library. Tested several: jquery-i18next, jquery.i18n, localizejs, translate-js. And guess what - none really worked as expected, not a single one would be just a plug-n-play solution. That's why I decided to write a vanilla Javascript code which would work as a simplest alternative. Here's the code:
let locale;
let dict = {
'en': {...},
'fr': {...}
};
function detectNavigatorLocale() {
const languageString = navigator.language || '';
const language = languageString.split(/[_-]/)[0].toLowerCase();
switch (language) {
case 'en':
return 'en';
case 'de':
return 'de';
default:
return 'en';
}
}
// replacement to $(document).ready() in jQuery
function docReady(fn) {
// see if DOM is already available
if (document.readyState === "complete" || document.readyState === "interactive") {
// call on next available tick
setTimeout(fn, 1);
} else {
document.addEventListener("DOMContentLoaded", fn);
}
}
// helper to get nested value in JSON-like object by dot-path string
function findProp(obj, prop, defval) {
if (typeof defval == 'undefined') defval = null;
prop = prop.split('.');
for (var i = 0; i < prop.length; i++) {
if (typeof obj[prop[i]] == 'undefined')
return defval;
obj = obj[prop[i]];
}
return obj;
}
let switchers = document.querySelectorAll('[data-locale]');
for (let i = 0; i < switchers.length; i++) {
switchers[i].onclick = function () {
let newLocale = switchers[i].getAttribute('data-locale');
locale = newLocale;
translate();
};
}
function translate(newLocale) {
let els = document.querySelectorAll('[data-i18n]');
for (let i = 0; i < els.length; i++) {
let path = els[i].getAttribute('data-i18n');
let translatation = findProp(dict[locale], path, 'undefined');
els[i].innerHTML = translatation;
}
// trigger repainting
window.dispatchEvent(new Event('resize'));
};
docReady(function () {
locale = detectNavigatorLocale();
translate();
});
And to make it work, the only thing to do in HTML is to add attributes to the elements which require translation as <p data-i18n="some.path.in.dictionary">fallback text</p>. To change the language I used <li data-locale="en">EN</li> and similar.
But here's the tricky part: why Desktop browser shows expected results, but several tested mobile browsers refuse to a) emit event on the locale switcher element and b) in some (Brave, Dolphin) even navbar in collapsed state does not unfold. I expect that latter is related to the JS handling in general in the selected browser, but why in Chrome for example the same code does not work?
First of all I replaced the bad practice of setting onclick with the following:
function switchLocale(loc) {
locale = loc;
translate();
}
let switchers = document.querySelectorAll('[data-locale]');
switchers.forEach(
function(switcher) {
switcher.addEventListener("click", function() {
// alert(switcher.id);
switchLocale(switcher.getAttribute('data-locale'));
})
}
)
and tested that it works fine. But the actual problem was that z-index was too low and the image below was overlayered above mobile nav. :)

addEventListener (and removeEventListener) function that need param

I need to add some listeners to 8 object (palms).
These object are identical but the behaviour have to change basing to their position.
I have the follow (ugly) code:
root.palmsStatus = ["B","B","B","B","B","B","B","B"];
if (root.palmsStatus[0] !== "N")
root.game.palms.palm1.addEventListener("click", palmHandler = function(){ palmShakeHandler(1); });
if (root.palmsStatus[1] !== "N")
root.game.palms.palm2.addEventListener("click", palmHandler = function(){ palmShakeHandler(2); });
if (root.palmsStatus[2] !== "N")
root.game.palms.palm3.addEventListener("click", function(){ palmShakeHandler(3); });
if (root.palmsStatus[3] !== "N")
root.game.palms.palm4.addEventListener("click", function(){ palmShakeHandler(4); });
if (root.palmsStatus[4] !== "N")
root.game.palms.palm5.addEventListener("click", function(){ palmShakeHandler(5); });
if (root.palmsStatus[5] !== "N")
root.game.palms.palm6.addEventListener("click", function(){ palmShakeHandler(6); });
if (root.palmsStatus[6] !== "N")
root.game.palms.palm7.addEventListener("click", function(){ palmShakeHandler(7); });
if (root.palmsStatus[7] !== "N")
root.game.palms.palm8.addEventListener("click", function(){ palmShakeHandler(8); });
I have two needs:
1) doesn't use an anonymous function on click event.
I wrote this code, but it doesn't work
root.game.palms.palm8.addEventListener("click", palmShakeHandler(8));
So this one works fine
root.game.palms.palm8.addEventListener("click", function(){ palmShakeHandler(8); });
But I didn't understand how remove the event listener.
I try this solution, but it doesn't work
root.game.palms.palm8.addEventListener("click", palmHandler = function(){ palmShakeHandler(8); });
root.game.palms.palm8.removeEventListener("click", palmHandler);
2) add and remove listener in a for cycle
I wrote the follow code but the behaviour is not correct.
for (i=1; i <= root.palmsStatus.length; i++){
if (root.palmsStatus[i-1] !== "N"){
root.game.palms["palm" + i].addEventListener("click", function(){ palmShakeHandler(i); });
}
}
the listeners was added but the value of the parameter passed to the palmShakeHandler is always 8.
Nobody could help me to fix these issues?
There is a actually, a perfect way to do that in JavaScript using the Function.prototype.bind method.
bind let you define extra parameters that will be passed, as arguments, of the function.
You should also keep in mind that bind creates a new function and doesn't modify the initial function.
Here is what it looks like:
function palmHandler(number) {
// your code working with `number`
}
var palmHandler8 = palmHandler.bind(null, 8)
// the palmHandler8 is now tied to the value 8.
// the first argument (here null) define what `this` is bound to in this function
This should fix your problem, and you will be able to remove handlers easily :)
Your code will look like this:
for (i=1; i <= root.palmsStatus.length; i++){
if (root.palmsStatus[i-1] !== "N"){
root.game.palms["palm" + i].addEventListener("click", palmShakeHandler.bind(null, i));
}
}
To be able to remove the handler afterward, you need to keep a reference to the function you create with bind. This would be the way to do this.
var boundHandler = handler.bind(null, i);
element.addEventListener(boundHandler);
element.removeEventListener(bounderHander);
If you want to know more about the awesome bind method in JavaScript, the MDN is your friend :) https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind
BTW, the problem with you function always returning 8 is a very common question in JavaScript. This thread will explain everything (spoiler, it's a matter of scoping :) ) https://stackoverflow.com/a/750506/2745879
So in case your array of »palms« is very huge, it is basically a bad Idea to add a single event listener to each of them, because that causes performance flaws. So I would suggest a different approach:
var handlers = [function (e) {}, …, function (e) {}];
root.game.palms.forEach(functiion (palm, idx) {
palm.setAttribute('data-idx', idx);
});
<palmsparent>.addEventListener('click', function (e) {
var c = e.target, idx = -1;
while (c) {
if (c.hasAttribute && c.hasAttribute('data-idx')) {
idx = parseInt(c.getAttribute('data-idx'));
break;
}
c = c.parentNode;
}
//here you also check for the »palm status«
if (idx >= 0) {
handlers[idx](c);
}
})
One event listener for all, much easier to remove and better for performance.
In your last solution you are pasing the same var to every function and that is what make al the functions work with 8 because is the last value of the variable.
To work arround that you can use "let" ( please at least use var, otherside that "i" is global and can be changed every where in the code) but since I dont know wich browser you target I propose other solution.
for (var i=1; i <= root.palmsStatus.length; i++){
if (root.palmsStatus[i-1] !== "N"){
root.game.palms["palm" + i].addEventListener("click", (function(index)
(return function(){
palmShakeHandler(index);
}))(i);
}
}
Since its look like You are targeting modern browsers I will use let.https://kangax.github.io/compat-table/es6/
for (var i=1; i <= root.palmsStatus.length; i++){
let index = i;
let intermediateFunction = function(){palmShakeHandler(index);};
if (root.palmsStatus[i-1] !== "N"){
root.game.palms["palm" + i].addEventListener("click",intermediateFunction);
root.game.palms["palm" + i].removeHandShake = function(){this.removeEventListener("click",intermediateFunction)};
}
}
So now you just need to call "removeHandShake" and will remove the listener,
I have code this right here so it ease some minor errors to pop

Function to check if element has any of these classes with native JS

<div id="test" class="a1 a2 a5"></div>
var element = document.getElementById("test")
if (hasAnyOfTheseClasses(element, ["a1", "a6"])) {
//...
}
Looking for a simple, lightweight function to check if a function has any of the listed classes without jQuery or another library.
Such function would be easy to implement, but there should be a canonical, fastest and simplest answer people can just copy-paste.
This seems vampire-ish, but I'm asking this so googlers won't have to write it themselves.
Not a duplicate - the linked question checks for one class, this question asks for checking any of the classes.
A jQuery version exists here.
Here's a functional implementation using Array.some and Element.classList.contains.
function hasAnyClass(element, classes) {
return classes.some(function(c) {
return element.classList.contains(c);
});
}
var div = document.getElementById("test");
console.log(hasAnyClass(div, ["hi", "xyz"]));
console.log(hasAnyClass(div, ["xyz", "there"]));
console.log(hasAnyClass(div, ["xyz", "xyz"]));
<div id="test" class="hi there"></div>
Note that these functions are not supported on older versions of IE, and will require a shim/polyfill.
You could use a regex, not sure that it's purely better but at least more flexible since your current test relies too much on spaces being entered correctly.
function hasAnyOfTheseClasses(element, classes) {
var className = element.className;
for (var i = 0; i < classes.length; i++) {
var exp = new RegExp('\b'+classes[i] + '\b');
if(exp.test(className)) return true;
}
return false;
}
just create a loop that check if each value in your array is a class in your passed element
function hasAnyOfTheseClasses(elem, tofind) {
classes = elem.className.split(' ');
for(var x in tofind) {
var className = tofind[x];
if (classes.indexOf(className) == -1){
return false;
}
}
return true;
}
Here's my implementation:
function hasAnyOfTheseClasses(element, classes) {
for (var i = 0; i < classes.length; i++) {
if ((' ' + element.className + ' ').indexOf(' ' + classes[i] + ' ') > -1) {
return true;
}
}
return false;
}
It's not elegant or fast. Works though. Feel free to edit to improve.
Use the .classList property to get the list of classes of an element. Then you can use the .contains() method to test each of the classes.
function hasAnyOfTheseClass(element, classes) {
var classList = element.classList;
return classes.some(function(class) {
return classList.contains(class);
});
}
How about using Array.prototype.some() and Array.prototype.indexOf():
function hasAnyClass(el, classes) {
var elClasses = el.className.split(' ');
return classes.some(c => elClasses.indexOf(c) >= 0)
}

How to add and remove classes in Javascript without jQuery

I'm looking for a fast and secure way to add and remove classes from an html element without jQuery.
It also should be working in early IE (IE8 and up).
Another approach to add the class to element using pure JavaScript
For adding class:
document.getElementById("div1").classList.add("classToBeAdded");
For removing class:
document.getElementById("div1").classList.remove("classToBeRemoved");
Note: but not supported in IE <= 9 or Safari <=5.0
The following 3 functions work in browsers which don't support classList:
function hasClass(el, className)
{
if (el.classList)
return el.classList.contains(className);
return !!el.className.match(new RegExp('(\\s|^)' + className + '(\\s|$)'));
}
function addClass(el, className)
{
if (el.classList)
el.classList.add(className)
else if (!hasClass(el, className))
el.className += " " + className;
}
function removeClass(el, className)
{
if (el.classList)
el.classList.remove(className)
else if (hasClass(el, className))
{
var reg = new RegExp('(\\s|^)' + className + '(\\s|$)');
el.className = el.className.replace(reg, ' ');
}
}
https://jaketrent.com/post/addremove-classes-raw-javascript/
For future friendliness, I second the recommendation for classList with polyfill/shim: https://developer.mozilla.org/en-US/docs/Web/API/Element/classList#wrapper
var elem = document.getElementById( 'some-id' );
elem.classList.add('some-class'); // Add class
elem.classList.remove('some-other-class'); // Remove class
elem.classList.toggle('some-other-class'); // Add or remove class
if ( elem.classList.contains('some-third-class') ) { // Check for class
console.log('yep!');
}
classList is available from IE10 onwards, use that if you can.
element.classList.add("something");
element.classList.remove("some-class");
I'm baffled none of the answers here prominently mentions the incredibly useful DOMTokenList.prototype.toggle method, which really simplifies alot of code.
E.g. you often see code that does this:
if (element.classList.contains(className) {
element.classList.remove(className)
} else {
element.classList.add(className)
}
This can be replaced with a simple call to
element.classList.toggle(className)
What is also very helpful in many situations, if you are adding or removing a class name based on a condition, you can pass that condition as a second argument. If that argument is truthy, toggle acts as add, if it's falsy, it acts as though you called remove.
element.classList.toggle(className, condition) // add if condition truthy, otherwise remove
To add class without JQuery just append yourClassName to your element className
document.documentElement.className += " yourClassName";
To remove class you can use replace() function
document.documentElement.className.replace(/(?:^|\s)yourClassName(?!\S)/,'');
Also as #DavidThomas mentioned you'd need to use the new RegExp() constructor if you want to pass class names dynamically to the replace function.
Add & Remove Classes (tested on IE8+)
Add trim() to IE (taken from: .trim() in JavaScript not working in IE)
if(typeof String.prototype.trim !== 'function') {
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g, '');
}
}
Add and Remove Classes:
function addClass(element,className) {
var currentClassName = element.getAttribute("class");
if (typeof currentClassName!== "undefined" && currentClassName) {
element.setAttribute("class",currentClassName + " "+ className);
}
else {
element.setAttribute("class",className);
}
}
function removeClass(element,className) {
var currentClassName = element.getAttribute("class");
if (typeof currentClassName!== "undefined" && currentClassName) {
var class2RemoveIndex = currentClassName.indexOf(className);
if (class2RemoveIndex != -1) {
var class2Remove = currentClassName.substr(class2RemoveIndex, className.length);
var updatedClassName = currentClassName.replace(class2Remove,"").trim();
element.setAttribute("class",updatedClassName);
}
}
else {
element.removeAttribute("class");
}
}
Usage:
var targetElement = document.getElementById("myElement");
addClass(targetElement,"someClass");
removeClass(targetElement,"someClass");
A working JSFIDDLE:
http://jsfiddle.net/fixit/bac2vuzh/1/
Try this:
const element = document.querySelector('#elementId');
if (element.classList.contains("classToBeRemoved")) {
element.classList.remove("classToBeRemoved");
}
I'm using this simple code for this task:
CSS Code
.demo {
background: tomato;
color: white;
}
Javascript code
function myFunction() {
/* Assign element to x variable by id */
var x = document.getElementById('para);
if (x.hasAttribute('class') {
x.removeAttribute('class');
} else {
x.setAttribute('class', 'demo');
}
}
Updated JS Class Method
The add methods do not add duplicate classes and the remove method only removes class with exact string match.
const addClass = (selector, classList) => {
const element = document.querySelector(selector);
const classes = classList.split(' ')
classes.forEach((item, id) => {
element.classList.add(item)
})
}
const removeClass = (selector, classList) => {
const element = document.querySelector(selector);
const classes = classList.split(' ')
classes.forEach((item, id) => {
element.classList.remove(item)
})
}
addClass('button.submit', 'text-white color-blue') // add text-white and color-blue classes
removeClass('#home .paragraph', 'text-red bold') // removes text-red and bold classes
You can also do
elem.classList[test ? 'add' : 'remove']('class-to-add-or-remove');
Instead of
if (test) {
elem.classList.add('class-to-add-or-remove');
} else {
elem.classList.remove('class-to-add-or-remove');
}
When you remove RegExp from the equation you leave a less "friendly" code, but it still can be done with the (much) less elegant way of split().
function removeClass(classString, toRemove) {
classes = classString.split(' ');
var out = Array();
for (var i=0; i<classes.length; i++) {
if (classes[i].length == 0) // double spaces can create empty elements
continue;
if (classes[i] == toRemove) // don't include this one
continue;
out.push(classes[i])
}
return out.join(' ');
}
This method is a lot bigger than a simple replace() but at least it can be used on older browsers. And in case the browser doesn't even support the split() command it's relatively easy to add it using prototype.
Just in case someone needs to toggle class on click and remove on other elements in JS only. You can try to do following :
var accordionIcon = document.querySelectorAll('.accordion-toggle');
//add only on first element, that was required in my case
accordionIcon[0].classList.add('close');
for (i = 0; i < accordionIcon.length; i++) {
accordionIcon[i].addEventListener("click", function(event) {
for (i = 0; i < accordionIcon.length; i++) {
if(accordionIcon[i] !== event.target){
accordionIcon[i].classList.remove('close');
}
event.target.classList.toggle("close");
}
})
}

Retrieve the "Placeholder" value with Javascript in IE without JQuery

I have some Javascript code that checks if a browser supports Placeholders and if it doesn't it creates them itself. Now this works on some older browsers but not all, especially IE.
All I need to do it get the "Placeholder" value, at the moment the placeholder in IE9 is "undefined".
Here is my code:
//Test if Placeholders are supported
var test = document.createElement("input");
if ("placeholder" in test) {
var testholder = true;
}
else {
var testholder = false;
}
//Fix unsupported placeholders
function placeHolder(id)
{
var demo = document.getElementById(id);
demo.className = "fix-hint";
demo.value = demo.placeholder;
demo.onfocus = function()
{
if (this.className == "fix-hint")
{
this.value = ""; this.className = "fix-nohint";
}
};
demo.onblur = function()
{
if (this.value === "")
{
this.className = "fix-hint"; this.value = demo.placeholder;
}
};
return false;
}
I am using 0% Jquery, I feel it's too bulky to solve small problems, plus I want to learn pure Javascript. Modernizr is a no too although I may come round to using it at some point.
UPDATE
This is the working code. Tested in IE 8 and 9. (The function call is within an if/else for "placeSupport".)
//Check if placeholders are supported
placeholderSupport = ("placeholder" in document.createElement("input"));
if(!placeholderSupport){
var placeSupport = false;
}else{
var placeSupport = true;}
//Support placeholders in older browsers
function placeHolder (id)
{
var el = document.getElementById(id);
var placeholder = el.getAttribute("placeholder");
el.onfocus = function ()
{
if(this.value == placeholder)
{
this.value = '';
this.className = "fix-nohint";
}
};
el.onblur = function ()
{
if(this.value.length == 0)
{
this.value = placeholder;
this.className = "fix-hint";
}
};
el.onblur();
}
If you're not sure if you're able to use certain functionality/attributes, try caniuse.com - you'll notice that placeholder is not available in IE9.
Try using getAttribute("placeholder")
getAttribute() returns the value of the named attribute on the
specified element. If the named attribute does not exist, the value
returned will either be null or "" (the empty string); see Notes for
details.
EXAMPLE
HTML
<input id="demo" placeholder="Rawr" />
JavaScript
var placeholder = document.getElementById("demo").getAttribute("placeholder");
console.log(placeholder);

Categories

Resources