I am trying to target anchor tags and trigger an ajax request. Using jQuery this is very easy:
$(document.body).on('click', "a", function (event) {
'use strict';
if ($(this).is('.a-btn')) {
event.preventDefault();
} else if ($(this).is('.no-sp')) {
//
} else {
address = $(this).attr("href")
event.preventDefault();
App.Ajax.Page(address + '/');
}
});
However using native javascript, I would imagine using event.target would do the trick.
But this does not work, because the event always targets whatever element is inside the anchor tag:
App.Ajax.Navigate = function () {
'use strict';
document.body.addEventListener('click', function (e) {
e.preventDefault();
console.log(e.currentTarget);
if (e.target.tagName === 'a') {
var element, link;
element = e.target;
link = element.href;
if (App.HTML.hasClass(element, 'a-btn')) {
e.preventDefault();
} else if (App.HTML.hasClass(element, 'no-sp')) {
return;
} else {
e.preventDefault();
App.Ajax.Page(link);
}
}
}, true);
window.onpopstate = function (event) {
App.Page.type = event.state.type;
App.Page.Replace(event.state.content, event.state.type, App.Empty, false);
};
};
I want to use native javascript to do what jquery does in the first code snippet, is it possible?
https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
https://developer.mozilla.org/ru/docs/Web/API/Element/closest - 2 polifills here
event.target.closest("a")
Don't forget about polifill for most browsers.
(function(e){
if (!e.matches) {
e.matches = e.mozMatchesSelector || e.msMatchesSelector || e.oMatchesSelector || e.webkitMatchesSelector;
}
if (!e.closest) {
e.closest = function (css) {
for (var node = this; node; node = node.parentElement) {
if (node.matches(css)) {
return node;
}
}
return null;
}
}
})(Element.prototype);
Related
I'm converting a dropdown from JQuery to vanilla JS, and I'm having some issues when I click on the dropdown it opens and I can select a value inside of it, but when I click on said value the value will be selected and it will instantly close.
I managed to narrow the problem down to this line
if($('*[data-toggle], *[data-totoggle]').is(e.target) || $('*[data-toggle], *[data-totoggle]').has(e.target).length){
return false;
}
In my vanilla js I replaced the above jquery code with this
if (document.querySelector('*[data-toggle], *[data-totoggle]') === e.target) {
return false;
}
This does not seem to achive the same thing as the jquery has and is functions, are there any equivalent of those in vanilla JS ?
codesandbox example > https://codesandbox.io/s/little-frost-tzefn?file=/src/index.js
Vanilla js version :
(function () {
document.querySelectorAll('*[data-toggle]').forEach(element => element.addEventListener('click', function (e) {
e.preventDefault();
const toToggle = element.dataset.toggle
// Close all other toggle elements and toggle the one you clicked
document.querySelectorAll('*[data-totoggle], *[data-toggle]').forEach(function (toggleElements) {
if (toggleElements.dataset.toggle === toToggle || toggleElements.dataset.totoggle === toToggle) {
toggleElements.classList.toggle('active')
} else if (!toggleElements.classList.contains('onlyClick')) {
toggleElements.classList.remove('active');
}
})
// toggle text if needed
if (element.dataset.toggletext) {
const toggleText = element.dataset.toggletext.split(',')
if (element.classList.contains('active')) {
element.html(toggleText[0])
} else {
element.html(toggleText[1])
}
}
// Remove toggle function from permanent elements
if (element.classList.contains('permanent')) {
element.querySelector('*[data-toggle="' + toToggle + '"]').removeAttribute('data-toggle');
element.querySelector('*[data-totoggle="' + toToggle + '"]').removeAttribute('data-totoggle');
}
}))
// close elements when clicking outside of them
document.addEventListener('mouseup', function (e) {
if (document.querySelector('*[data-toggle], *[data-totoggle]') === e.target) {
return false;
}
document.querySelectorAll('*[data-toggle], *[data-totoggle]').forEach(function (element) {
if (!element.classList.contains('onlyClick')) {
element.classList.remove('active')
}
})
if (document.querySelector('*[data-slidetoggle], *[data-toslidetoggle]') === e.target) {
return false;
}
document.querySelectorAll('*[data-slidetoggle]').forEach(function (element) {
if (!element.classList.contains('onlyClick')) {
element.classList.remove('active')
$('*[data-toslidetoggle]').slideUp();
}
})
});
// slide toggle elements
document.querySelectorAll('*[data-slidetoggle]').forEach(element => element.addEventListener('click', function () {
const slideToggle = this.dataset.slidetoggle
this.classList.toggle('active');
$('*[data-toslidetoggle="' + slideToggle + '"]').slideToggle(200);
if (this.dataset.toggletext) {
const toggleText = this.dataset.toggletext.split(',')
if (this.classList.contains('active')) {
this.html(toggleText[0])
} else {
this.html(toggleText[1])
}
}
}))
document.querySelector('*[data-trigger]').addEventListener('click', function (e) {
const toTrigger = e.target.dataset.trigger
document.querySelector('.' + toTrigger).trigger('click');
})
})();
I have a fiddle here: https://jsfiddle.net/419r62t8/1/
View.prototype.render = function (viewCmd, parameter) {
var that = this;
var viewCommands = {
showEntries: function () {
that.$todoList.innerHTML = that.template.show(parameter);
},
removeItem: function () {
that._removeItem(parameter);
},
updateElementCount: function () {
that.$todoItemCounter.innerHTML = that.template.itemCounter(parameter);
},
contentBlockVisibility: function () {
that.$main.style.display = that.$footer.style.display = parameter.visible ? 'block' : 'none';
},
setFilter: function () {
that._setFilter(parameter);
},
clearNewTodo: function () {
that.$newTodo.value = '';
},
editItem: function () {
that._editItem(parameter.id, parameter.title);
},
editItemDone: function () {
that._editItemDone(parameter.id, parameter.title);
}
};
viewCommands[viewCmd]();
};
View.prototype._itemId = function (element) {
var li = $parent(element, 'li');
return parseInt(li.dataset.id, 10);
};
View.prototype._bindItemEditDone = function (handler) {
var that = this;
$live('#todo-list li .edit', 'blur', function () {
if (!this.dataset.iscanceled) {
handler({
id: that._itemId(this),
title: this.value
});
}
});
$live('#todo-list li .edit', 'keypress', function (event) {
if (event.keyCode === that.ENTER_KEY) {
// Remove the cursor from the input when you hit enter just like if it
// were a real form
this.blur();
}
});
};
View.prototype._bindItemEditCancel = function (handler) {
var that = this;
$live('#todo-list li .edit', 'keyup', function (event) {
if (event.keyCode === that.ESCAPE_KEY) {
this.dataset.iscanceled = true;
this.blur();
handler({id: that._itemId(this)});
}
});
};
View.prototype.bind = function (event, handler) {
var that = this;
if (event === 'newTodo') {
$on(that.$newTodo, 'change', function () {
handler(that.$newTodo.value);
});
} else if (event === 'itemEdit') {
$live('#todo-list li label', 'dblclick', function () {
handler({id: that._itemId(this)});
});
} else if (event === 'itemRemove') {
$live('#todo-list .destroy', 'click', function () {
handler({id: that._itemId(this)});
});
} else if (event === 'itemEditDone') {
that._bindItemEditDone(handler);
} else if (event === 'itemEditCancel') {
that._bindItemEditCancel(handler);
} else if (even === 'itemComplete') {
that._bindItemComplete(handler);
}
};
EDIT: I am thinking I can bind a new function here to add an strike-through to the "completed" items on the todo list. Completing them on single click or by adding a checkbox for completing it.
I've got the CSS but I'm lacking the JS to tie it all together.
I am attempting to create a simple strike through on-click to show when an item has been marked as completed. Any help would be much appreciated.
You're close with the CSS, but the best bet is to replace the checkbox with an image (svg if you can) when it is checked.
text-decoration: line-through will not help here -- this only works with text.
Often the checkbox's label will get the image and the checkbox itself will be hidden (a label can be clicked and perform the same actions as the input/checkbox itself)
Check this Answer out and see if it'll help you along your path:
Pure CSS Checkbox Image replacement
Try adding a jquery event listener to the id of the checkbox. Tell id to toggle the css on click and you should be good to go. Do you know how to achieve that?
How can I remove an attribute of an element on click outside or on another div of same type? Here's my code:
HTML:
<div id="container">
<div data-something></div>
<div data-something></div>
</div>
JavaScript:
var elements = document.querySelectorAll("[data-something]");
Array.prototype.forEach.call(elements, function(element) {
// Adding
element.onclick = function() {
this.setAttribute("data-adding", "");
};
// Removing -- Example
element.onclickoutside = function() {
this.removeAttribute("data-adding");
};
});
I would probably use a click handler on the document, and then remove the attribute from any element that had it that wasn't in the bubbling path.
document.addEventListener("click", function(e) {
Array.prototype.forEach.call(document.querySelectorAll("*[data-adding][data-something]"), function(element) {
var node, found = false;
for (node = e.target; !found && node; node = node.parentNode) {
if (node === element) {
found = true;
}
}
if (!found) {
element.removeAttribute("data-adding");
}
});
}, false);
...or something along those lines.
Live Example:
document.addEventListener("click", function(e) {
Array.prototype.forEach.call(document.querySelectorAll("*[data-adding]"), function(element) {
var node, found = false;
for (node = e.target; !found && node; node = node.parentNode) {
if (node === element) {
found = true;
}
}
if (!found) {
element.removeAttribute("data-adding");
}
});
}, false);
*[data-adding] {
color: red;
}
<div data-adding data-something>One</div>
<div data-adding data-something>Two</div>
You can use Node.contains() inside a global click event handler to check if a click is outside an element, and handle the event appropriately:
box = document.getElementById('box');
lastEvent = document.getElementById('event');
box.addEventListener('click', function(event) {
// click inside box
// (do stuff here...)
lastEvent.textContent = 'Inside';
});
window.addEventListener('click', function(event) {
if (!box.contains(event.target)) {
// click outside box
// (do stuff here...)
lastEvent.textContent = 'Outside';
}
});
#box {
width: 200px;
height: 50px;
background-color: #ffaaaa;
}
<div id="box">Click inside or outside me</div>
<div>Last event: <span id="event">(none)</span>
</div>
I'm building a decision tree in JavaScript. I do not have jQuery available to me for this project.
I would like to be able to have buttons, placed anywhere in the decision tree (Hidden or displayed anywhere on the page), with the same class name. The listener on the JS side would then run a function.
Here is what I am using for and ID based listener. It works well but I need to be able to have multiple buttons with the same class or name available. Although I have seen examples of this, I cannot get it to function properly.
function q1a1() {
var q1a1button = document.getElementById("q1answer1");
if(q1a1button.addEventListener){
q1a1button.addEventListener("click", function() { q1answer1();}, false);
} else if(q1a1button.attachEvent){
q1a1button.attachEvent("onclick", function() { q1answer1();});
}
};
if(window.addEventListener){
window.addEventListener("load", q1a1, false);
} else if(window.attachEvent){
window.attachEvent("onload", q1a1);
} else{
document.addEventListener("load", q1a1, false);
}
function q1answer1() {
//DO SOME STUFF
}
This also needs to work in as many versions of IE as possible. For single class handling I'm using querySelectorAll.
What you are really looking for is JavaScript Event Delegation. In your case, you have BUTTON elements, which I'm going to assume are <button> tags. Now you want to know when one of those buttons was clicked and then run a function:
if (document.addEventListener) {
document.addEventListener("click", handleClick, false);
}
else if (document.attachEvent) {
document.attachEvent("onclick", handleClick);
}
function handleClick(event) {
event = event || window.event;
event.target = event.target || event.srcElement;
var element = event.target;
// Climb up the document tree from the target of the event
while (element) {
if (element.nodeName === "BUTTON" && /foo/.test(element.className)) {
// The user clicked on a <button> or clicked on an element inside a <button>
// with a class name called "foo"
doSomething(element);
break;
}
element = element.parentNode;
}
}
function doSomething(button) {
// do something with button
}
Anywhere on the page that a <button class="foo">...</button> element appears, clicking it, or any HTML tag inside of it, will run the doSomething function.
Update: Since Event Delegation is used, only a single click handler is registered on the document object. If more <button>s are created as a result of an AJAX call, you don't have to register click handlers on those new <button>s since we take advantage of the click event bubbling up from the element the user clicked on to the document object itself.
If you don't have jquery:
if (document.body.addEventListener){
document.body.addEventListener('click',yourHandler,false);
}
else{
document.body.attachEvent('onclick',yourHandler);//for IE
}
function yourHandler(e){
e = e || window.event;
var target = e.target || e.srcElement;
if (target.className.match(/keyword/))
{
//an element with the keyword Class was clicked
}
}
If you use a cross browser library like jquery:
HTML:
<div class="myClass">sample</div>
<div class="myClass">sample 2</div>
JS:
function theFuncToCall(event){
//func code
}
$(document).on('click', '.myClass', theFuncToCall);
var buttons = document.querySelectorAll(".MyClassName");
var i = 0, length = buttons.length;
for (i; i < length; i++) {
if (document.addEventListener) {
buttons[i].addEventListener("click", function() {
// use keyword this to target clicked button
});
} else {
buttons[i].attachEvent("onclick", function() {
// use buttons[i] to target clicked button
});
};
};
This answer is a bit overkill, but it should show you ways you could structure your code in a "modern" way even if you're still targeting old browsers
Write code to add event listeners so there is minimal difference between new and old browsers
var listen = (function () { // will return the handler for use in unlisten
if (window.addEventHandler) {
return function (node, type, handler) {
node.addEventListener(type, handler);
return handler;
};
} else if (window.attachEvent) {
return function (node, type, handler) {
var fn = function (e) {
if (!e) {
e = window.event;
}
if (!e.target && e.srcElement) {
e.target = e.srcElement;
}
return handler.call(this, e);
};
node.attachEvent('on' + type, fn);
return fn;
};
} else {
throw new Error('Events not supported in this environment');
// or
// return function ... node['on' + type] = function () { ... };
}
}());
and if you'd like the reverse, too
var unlisten = (function () { // use handler given by listen
if (window.removeEventListener) {
return function (node, type, handler) {
node.removeEventListener(type, handler);
};
} else if (window.detachEvent) {
return function (node, type, handler) {
node.detachEvent('on' + type, handler);
};
} else {
throw new Error('Events not supported in this environment');
// or
// return function ... node['on' + type] = null;
}
}());
Write your click handler
function clickHandler(e) {
// do stuff
}
Wrap your click handler in a function to choose only clicks on buttons with the right class
function wrappedClickHandler(e) {
var tokens, i;
if (e.target.tagName !== 'INPUT' && e.target.tagName !== 'BUTTON') {
return;
}
tokens = (e.target.className || '').split(' ');
for (i = 0; i < tokens.length; ++i) {
if (tokens[i] === 'theClassTokenWeWant') {
return clickHandler.call(this, e);
// or
// return clickHandler.call(e.target, e);
}
}
}
Add this as a listener to a common ancestor node
var h = listen(document, 'click', wrappedClickHandler);
// .. later, if desired
unlisten(document, 'click', h);
Would the simpler way of writing the event delegation function be to add it to the container of the buttons? For example,
// Select Container Element
const questionContainer = document.querySelector(".container");
// Listen For Clicks Within Container
questionContainer.onclick = function (event) {
// Prevent default behavior of button
event.preventDefault();
// Store Target Element In Variable
const element = event.target;
// If Target Element Is a Button
if (element.nodeName === 'BUTTON') {
// Event Code
}
}
Let's say we have a unordered list with a class:
<ul class="list-class">
<li>All</li>
<li>Breakfast</li>
<li>Lunch</li>
<li>Dinner</li>
<li>Snack</li>
</ul>
Let's say now I wanted to create some function that does different things based on which item has been clicked on:
function whichElement() {
alert("The -- Link has been clicked");
}
How can this be done without creating a separate function for each item and writing an inline onclick="" event? I'm open to using jQuery as well.
How about this if I understand correctly:
var items = document.querySelectorAll('.list-class li');
[].forEach.call(items, function(item){
var text = item.textContent.trim().toLowerCase();
item.addEventListener('click', function(){
if (text == 'all') {
//...
}
//...
});
});
You may try this
function addEvent(elem, event, fn)
{
if (elem.addEventListener)
{
elem.addEventListener(event, fn, false);
}
else
{
elem.attachEvent("on" + event, function() {
return(fn.call(elem, window.event));
});
}
}
addEvent(window, 'load', function(e){
var list = document.querySelector('.list-class');
addEvent(list, 'click', function(e){
e = e || window.event;
var el = e.target || e.srcElement;
alert(el.innerHTML);
});
});
DEMO.
Add a click handler to the ul, something like:
$('.list-class').on(
'click',
function(e){
e = e || event;
var from = e.target || e.srcElement;
if (/^a$/i.test(from.tagName)){
alert('you clicked '+from.innerHTML);
}
});
See this jsFiddle
Do a Jquery Trick as
$("#list-class li").click(function() {
alert($(this).prevAll().length+1);
});
Here is the FIDDLE
EDIT: event.target returns DOM element that initiated the event.
$('.list-class').click(function(e){
alert(e.target.nodeName);
});
Check this in JSFiddle
Not to jump on the bandwagon as this is similar to others (but not quite)...
$('.list-class>li').on('click',
function(){
alert('clicked ' + $('a',this)[0].innerHTML); //eg "clicked Lunch" etc
}
);
http://jsfiddle.net/znySy/
This simply alerts the text of the link clicked, but equally within the function you could switch on the text eg...
function(){
switch ($('a',this)[0].innerHTML) {
case 'Lunch' : // do something for Lunch
case 'Breakfast' : // do something for Breakfast
default : // do something for not Lunch or Breakfast
}
}
You can do it in POJS like so, this should be cross-browser compatible and doesn't use any 3rd party libraries. Other advantage is that there is only one event listener per element of the named class, uses event propagation.
Javacsript
/*jslint maxerr: 50, indent: 4, browser: true */
/*global alert */
(function () {
"use strict";
function addEvent(elem, event, fn) {
if (typeof elem === "string") {
elem = document.getElementById(elem);
}
function listenHandler(e) {
var ret = fn.apply(null, arguments);
if (ret === false) {
e.stopPropagation();
e.preventDefault();
}
return ret;
}
function attachHandler() {
window.event.target = window.event.srcElement;
var ret = fn.call(elem, window.event);
if (ret === false) {
window.event.returnValue = false;
window.event.cancelBubble = true;
}
return ret;
}
if (elem.addEventListener) {
elem.addEventListener(event, listenHandler, false);
} else {
elem.attachEvent("on" + event, attachHandler);
}
}
function whichElement(e) {
var target = e.target;
if (target.tagName === "A" && target.parentElement.tagName === "LI" && target.parentElement.parentElement.className === "list-class") {
alert("The " + target.firstChild.nodeValue + " Link has been clicked");
}
}
addEvent(document.body, "click", whichElement);
}());
On jsfiddle
If you were using some newer/custom HTML tags or XML then you may need to consider tagName case sensitivity, and write the following to be certain.
if (target.tagName.toUpperCase() === "A" && target.parentElement.tagName.toUpperCase() === "LI" && target.parentElement.parentElement.className === "list-class") {
In jquery terms the above could be written as
Javascript
$(document).on('click', '.list-class>li>a', function (e) {
alert("The " + e.target.firstChild.nodeValue + " Link has been clicked");
});
On jsfiddle
In jquery they call this event delegation.
<ul class="list-class">
<li>All</li>
<li>Breakfast</li>
<li>Lunch</li>
</ul>
<script>
$(".list-class li").find('a').each(function(){
$(this).click(function(){
switch($(this).attr('id'))
{
case "hrefID1";
//do what ever
break;
case "hrefID2";
//do what ever
break;
case "hrefID3";
//do what ever
break;
}
});
});
</script>