JavaScript: Get ID from currently dragged CLASS item - javascript

My application has multiple stones:
-> "let dragStone"
and one container where one of these stones can be placed:
-> "let putCircleNextStoneField"
I want to append the container (parent node) with the stone that is dragged onto it (child node).
The Error code is: "Parameter 1 is not of type 'Node'".
I know that the parameter isn't working beacause the dragStone variable isn't just a reference to the ID but an array-like object of all child elements which have the CLASS name: ".stone".
How do I get the reference to the id of the stone that is currently dragged?
function dragFromStoneFieldToNextStoneField() {
let dragStone = document.querySelector("#stoneField").querySelectorAll(".stone");
let putCircleNextStoneField = document.querySelector("#nextStoneField");
dragStone.forEach(stone => {
stone.classList.add("cursorPointer");
});
dragStone.forEach(stone => {
stone.addEventListener("dragstart", () => {
});
});
putCircleNextStoneField.addEventListener("dragover", e => {
e.preventDefault();
putCircleNextStoneField.classList.add("draggedOver");
putCircleNextStoneField.appendChild(dragStone); //ERROR IN THIS LINE
});
putCircleNextStoneField.addEventListener("dragleave", e => {
putCircleNextStoneField.classList.remove("draggedOver");
});
putCircleNextStoneField.addEventListener("drop", e => {
putCircleNextStoneField.classList.remove("draggedOver");
});
}
dragFromStoneFieldToNextStoneField();

I came up with a solution:
function dragFromStoneFieldToNextStoneField() {
let dragStone = document.querySelector("#stoneField").querySelectorAll(".stone");
let putCircleNextStoneField = document.querySelector("#nextStoneField");
let currentStone;
dragStone.forEach(stone => {
stone.classList.add("cursorPointer");
});
dragStone.forEach(stone => {
stone.addEventListener("dragstart", () => {
currentStone = stone;
});
});
putCircleNextStoneField.addEventListener("dragover", e => {
e.preventDefault();
putCircleNextStoneField.classList.add("draggedOver");
putCircleNextStoneField.appendChild(currentStone);
});
putCircleNextStoneField.addEventListener("dragleave", e => {
putCircleNextStoneField.classList.remove("draggedOver");
});
putCircleNextStoneField.addEventListener("drop", e => {
putCircleNextStoneField.classList.remove("draggedOver");
});
}
dragFromStoneFieldToNextStoneField();

I think that referencing the node is better but I'll post here in the case someone in the future need a example of setData and getData:
function receive(event) {
const sourceId = event.dataTransfer.getData("text");
// find the element and clone it
const element = document.getElementById(sourceId).cloneNode(true);
const container = document.querySelector(".target");
container.appendChild(element);
}
function onDrag(event) {
event.dataTransfer.setData("text", event.target.id);
}
function allowDrop(event) {
event.preventDefault();
}
.target {
padding: 12px;
box-sizing: border-box;
border: 1px dashed #2a2;
border-radius: 4px;
}
.container {
margin: 20px 0;
display: flex;
}
.source {
background-color: #f8f8ff;
box-sizing: border-box;
padding: 8px;
margin: 8px;
color: #08c;
}
.source:hover {
background-color: #f0f0ff;
}
<div class="target" ondrop="receive(event)" ondragover="allowDrop(event)">
drop here
</div>
<div class="container">
<div id="item-104" class="source" draggable="true" ondragstart="onDrag(event)">
draggable #104
</div>
<div id="item-208" class="source" draggable="true" ondragstart="onDrag(event)">
draggable #208
</div>
<div id="item-37" class="source" draggable="true" ondragstart="onDrag(event)">
draggable #37
</div>
</div>

Related

JQuery Refactoring ( separate codes by functionality, improve readability)

recently I have been studying way of refactoring Jquery code to handling UI events.
It was hard to find documents which wrote recently.
So, I write my result via this document and hope reviewing my decision from skilled developers such as you.
<purpose of refactoring>
separate codes by functionality
improve readability
Jquery source is composed by html elements' event. So I usually saw coding style below
$("#nameText").on("input", function () {
//Some of actions to control html elements, extract data, validation, ajax request, etc,,,
}
$("#surveyText").on("input", function () {
//Some of actions to control html elements, extract data, validation, ajax request, etc,,,
}
$("#submitBtn").on("click", function (e) {
//Some of actions to control html elements, extract data, validation, ajax request, etc,,,
}
It's pretty simple and we can read which action is needed and conducted by some elements action.
But that could be verbose and confusing us because there are many roles and we have to check the element's css attributes which related selector.
Some times, css class name or id don't have proper name which not enough explain role of element or in case of, use navigator function like find() or selector by element hierarchy.
$("#s_f01").find("li").on("click", function () {
//Some of actions to control html elements, extract data, validation, ajax request, etc,,,
}
$(".s_btn01").on("click", function () {
//Some of actions to control html elements, extract data, validation, ajax request, etc,,,
}
$("#submitBtn").on("click", function (e) {
//Some of actions to control html elements, extract data, validation, ajax request, etc,,,
}
And functions for particular element could be spread out by modified history for adding function or some of modification.
//Codes for Survey Form (line 10 - 200)
$("#s_f01").find("li").on("click", function () {
//Some of actions to control html elements, extract data, validation, ajax request, etc,,,
}
$(".s_btn01").on("click", function () {
//Some of actions to control html elements, extract data, validation, ajax request, etc,,,
}
$("#submitBtn").on("click", function (e) {
//Some of actions to control html elements, extract data, validation, ajax request, etc,,,
}
,,,
//added after statistics function for enable Survey form action(line 1200)
$("#SurveyFormOpenBtn").on("click", sendStatisticInfo_SurveyFormOpened());
<Solution>
separate functions by Component to make sure functions will be used by which Component
each object have Event and Function. Event will have argument which will be passed by Jquery Element and functions will be used by Events.
it makes more easily understanding each function's purpose
it prevent spread out particular Element's functions.
$(function () {
var categoryListArea = {
someEvent: function ($jqueryEl) { },
someFunc: function (str, etc) {}
}
var survayArea = {
someEvent: function ($jqueryEl) { },
someFunc: function (str, etc) {}
}
var HeaderArea = { }
extract code by functionality and declare as function and event to number 1's xxxArea Object.
via Event function name, we can understand what will be conducted by triggered jquery function.
each xxxArea Object make more easily separate functions by each components.
each functions will be maintained by each components.
//AS-IS
$("#nameText").on("input", function () {
console.log('test');
var inputtedText = $(this).val();
//1. Dicide display status of Name Lable Element
if (inputtedText !== '') {
$('#surveyLable').show();
} else {
$('#surveyLable').hide();
}
//2. Dicide display status of Name input Element
if (inputtedText !== '') {
$('#surveyText').show();
} else {
$('#surveyText').hide();
}
//3. Prevent input html characotrs
if (validateHtml(inputtedText)) {
alert('&<>\"\'\`=/ are not allowed');
$(this).val(escapeHtml(inputtedText));
}
});
//TO-Be
var survayArea = {
inputNameTextEvent: function ($el) {
var inputtedText = $el.val();
//1. Dicide display status of Name Lable Element
survayArea.swithDisplayStatus(inputtedText, $('#surveyLable'));
//2. Dicide display status of Name input Element
survayArea.swithDisplayStatus(inputtedText, $('#surveyText'));
//3. Prevent input html characotrs
// validateHtml and escapeHtml is used commonly so don't include particular object which represents some component
if (validateHtml(inputtedText)) {
alert('&<>\"\'\`=/ are not allowed');
$el.val(escapeHtml(inputtedText));
}
},
swithDisplayStatus: function (str, El) {
//Decide display status
if (str !== '') {
El.show();
} else {
El.hide();
}
}
}
$("#nameText").on("input", function () {
survayArea.inputNameTextEvent($(this));
});
function validateHtml(string) {
let reg = /[&<>"'`=\/]/g
return reg.test(string);
}
function escapeHtml(string) {
return String(string).replace(/[&<>"'`=\/]/g, '');
};
hire is my sample code which explain concept.
if there are more efficient or more practical way, please let me know.
<SAMPLE CODE>
sample.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script
src="https://code.jquery.com/jquery-3.6.0.min.js"
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
crossorigin="anonymous"
></script>
<script src="sample.js"></script>
<style>
ul#category-tabs {
list-style: none;
padding: 0;
margin: 0;
padding: 0;
}
ul#category-tabs li {
display: block;
position: relative;
margin: 0;
border-bottom: 1px #ececec solid;
padding: 10px 18px;
}
ul.sub-category-tabs li {
padding: 2px !important;
}
ul.sub-category-tabs li {
border-bottom: 0px !important;
}
ul#category-tabs li a {
color: #333;
font-weight: 700;
margin-bottom: 0;
font-size: 12px;
}
ul#category-tabs li a i {
top: 12px;
right: 18px;
position: absolute;
cursor: pointer;
width: 16px;
height: 16px;
padding: 2px;
color: #ed6663;
}
.bodyContainer {
float: left;
overflow: auto;
position: relative;
}
</style>
</head>
<body>
<div id="headerArea" class="bodyContainer">
<div class="barCategory" id="barCategory">
<ul id="category-tabs">
<li>
<a href="javascript:void" class="main-category">
Home<i class="fa fa-minus"></i
></a>
<ul class="sub-category-tabs">
<li>CompanyInfo</li>
<li>Product</li>
<li>Pricing</li>
<li>Contact</li>
</ul>
</li>
</ul>
</div>
</div>
<div id="survayArea" class="bodyContainer">
<h3>Dummy survey form</h3>
<div>
<form action="" id="surveyForm">
<div id="nameLable">Name:</div>
<input
type="text"
id="nameText"
value=""
placeholder="Please input name"
/><br /><br />
<div id="surveyLable" style="display: none">Survey:</div>
<input
type="text"
id="surveyText"
style="display: none"
/><br /><br />
<input
type="button"
id="submitBtn"
value="Submit"
style="display: none"
/>
</form>
</div>
</div>
</body>
</html>
AS-IS
sample.js
$(function () {
$("#nameText").on("input", function () {
console.log('test');
var inputtedText = $(this).val();
//1. Dicide display status of Name Lable Element
if (inputtedText !== '') {
$('#surveyLable').show();
} else {
$('#surveyLable').hide();
}
//2. Dicide display status of Name input Element
if (inputtedText !== '') {
$('#surveyText').show();
} else {
$('#surveyText').hide();
}
//3. Prevent input html characotrs
if (validateHtml(inputtedText)) {
alert('&<>\"\'\`=/ are not allowed');
$(this).val(escapeHtml(inputtedText));
}
});
$("#surveyText").on("input", function () {
var inputtedText = $(this).val();
//1. Dicide display status of Submit Button
if (inputtedText !== '') {
$('#submitBtn').show();
} else {
$('#submitBtn').hide();
}
//2. Prevent input html characotrs
if (validateHtml(inputtedText)) {
alert('&<>\"\'\`=/ are not allowed');
$(this).val(escapeHtml(inputtedText));
}
});
$("#submitBtn").on("click", function (e) {
e.preventDefault();
//1. Submit survey form
$("#surveyForm").trigger("submit");
});
})
function validateHtml(string) {
let reg = /[&<>"'`=\/]/g
return reg.test(string);
}
function escapeHtml(string) {
return String(string).replace(/[&<>"'`=\/]/g, '');
};
TO-BE
sample.js
$(function () {
var headrAreaEvents = {
//...
}
var survayArea = {
inputNameTextEvent: function ($el) {
var inputtedText = $el.val();
//1. Dicide display status of Name Lable Element
survayArea.swithDisplayStatus(inputtedText, $('#surveyLable'));
//2. Dicide display status of Name input Element
survayArea.swithDisplayStatus(inputtedText, $('#surveyText'));
//3. Prevent input html characotrs
survayArea.preventHtmlCharactors(inputtedText, $(this));
},
swithDisplayStatus: function (str, $el) {
//Dicide display status
if (str !== '') {
$el.show();
} else {
$el.hide();
}
},
inputSurveyTextEvent: function ($el) {
var inputtedText = $el.val();
//1. Dicide display status of Submit Button
survayArea.swithDisplayStatus(inputtedText, $('#submitBtn'));
//2. Prevent input html characotrs
survayArea.preventHtmlCharactors(inputtedText, $(this));
},
preventHtmlCharactors: function (inputtedText) {
if (validateHtml(inputtedText, $el)) {
alert('&<>\"\'\`=/ are not allowed');
$el.val(escapeHtml(inputtedText));
}
},
submitSurveyFormEvent: function ($el) {
$("#surveyForm").trigger("submit");
}
}
$("#nameText").on("input", function () {
survayArea.inputNameTextEvent($(this));
});
$("#surveyText").on("input", function () {
survayArea.inputSurveyTextEvent($(this));
});
$("#submitBtn").on("click", function () {
survayArea.submitSurveyFormEvent($(this));
});
})
function validateHtml(string) {
let reg = /[&<>"'`=\/]/g
return reg.test(string);
}
function escapeHtml(string) {
return String(string).replace(/[&<>"'`=\/]/g, '');
};
complement
// actually, there aren't need to pass param $(this) to submitSurveyFormEvent method.
submitSurveyFormEvent: function ($el) {
$("#surveyForm").trigger("submit");
}
$("#submitBtn").on("click", function () {
survayArea.submitSurveyFormEvent($(this));
});
//So I tried to remove on event's anonymous function and input survayArea.submitSurveyFormEvent() directly.
//But if do that, submit event was executed infinitely (like executed infinite loop)
//if anyone knows why about this, pleas let me know.
$("#submitBtn").on("click",survayArea.submitSurveyFormEvent());

is it possible to observe element existence and removal permanently using JS MutationObserver?

I'm completely new with MutationObserver. I need to detect if an element exists or is removed permanently. I found that example which really awesome. but it works just for element existence and just once for the first time! it doesn't feel element removal and never feels element existence after the first time.
HTML
<button onclick="appendS()">add message</button>
<button onclick="remove()">remove message</button>
<div id='imobserved'></div>
CSS
button{padding:30px; margin: 10px;}
#imobserved{border: 1px solid black; padding: 10px;}
JS
function appendS(){
let s = document.createElement('span');
s.id = "message";
s.innerText = "some message !"
document.querySelector('#imobserved').appendChild(s);
}
function remove(){
document.querySelector('#imobserved').innerHTML = "";
}
function waitForAddedNode(params) {
new MutationObserver(function(mutations) {
var el = document.getElementById(params.id);
if (el) {
this.disconnect();
params.done(el);
}
}).observe(params.parent || document, {
subtree: !!params.recursive || !params.parent,
childList: true,
});
}
// Usage:
waitForAddedNode({
id: 'message',
parent: document.querySelector('#imobserved'),
recursive: false,
done: function(el) {
alert('i see you')
}
});
const observedEl = document.querySelector('#imobserved')
function append() {
const s = document.createElement('span');
s.id = "message";
s.innerText = "some message!";
observedEl.appendChild(s);
}
function remove() {
if (observedEl.lastChild) {
observedEl.lastChild.remove();
}
}
const observer = new MutationObserver(function(e) {
const addition = e[0].addedNodes.length;
const message = addition ? 'Element added' : 'Element deleted'
alert(message);
});
observer.observe(observedEl, {
subtree: true,
childList: true
});
button {
padding: 30px;
margin: 10px;
}
#imobserved {
border: 1px solid black;
padding: 10px;
}
<button onclick="append()">add message</button>
<button onclick="remove()">remove message</button>
<div id='imobserved'></div>
Here is how you can achieve it. I've changed your code a little bit
First of all, never create multiple DOM elements with the same ID, this is a bad idea. In the MutationObserver callback, you have plenty of information, including about which nodes have been added and which have been removed.
Feel free to run the snippet below, and see how it behaves.
function appendS() {
let s = document.createElement("span");
s.innerText = "some message !";
document.querySelector("#imobserved").appendChild(s);
}
function remove() {
document.querySelector("#imobserved").innerHTML = "";
}
function waitForAddedNode(params) {
new MutationObserver(function (mutationsList) {
for (const mutation of mutationsList) {
if (mutation.addedNodes.length) {
params.onAdd(mutation.addedNodes);
}
if (mutation.removedNodes.length) {
params.onDelete(mutation.removedNodes);
}
}
}).observe(params.parent, {
childList: true,
subtree: true
});
}
waitForAddedNode({
parent: document.querySelector("#imobserved"),
recursive: false,
onAdd: function (elements) {
alert("i see you added new element");
console.log(elements);
},
onDelete: function (elements) {
alert("i see you removed " + elements.length + " elements");
console.log(elements);
}
});
const addBtn = document.querySelector("#add");
const removeBtmn = document.querySelector("#remove");
addBtn.addEventListener("click", appendS);
removeBtmn.addEventListener("click", remove);
button {
padding: 30px;
margin: 10px;
}
#imobserved {
border: 1px solid black;
padding: 10px;
}
<body>
<button id="add">add message</button>
<button id="remove">remove message</button>
<div id="imobserved"></div>
</body>

Javascript - call one function for same element different event?

I am trying to make this more abstract. I need to find a way to use one function for the same element and different events. I would also like to be able to pass in an element argument but I know I can't do this with a callback. This is what I have thus far:
const divElement = document.querySelector('#test');
divElement.addEventListener(
'mouseenter',
(event) => {
divElement.classList.add('shadow');
console.log(event);
},
false
);
divElement.addEventListener(
'mouseleave',
(event) => {
divElement.classList.remove('shadow');
console.log(event);
},
false
);
Use CSS instead. Never use JS for what can be achieved in CSS.
#test {
background-color: white;
padding: 30px;
transition: all 0.4s ease;
}
#test:hover {
box-shadow: 0 0 10px #000;
}
<div id="test">
Lorem ipsum dolor sit amentur.
</div>
If for some reason you didn't reveal you need it to be event-listener-based, here's what I would do:
const divElement = document.querySelector('#test');
function handleMouseAction({type}) {
this.classList.toggle('shadow', type === 'mouseenter');
}
divElement.addEventListener('mouseenter', handleMouseAction, false);
divElement.addEventListener('mouseleave', handleMouseAction, false);
#test {
padding: 30px;
transition: all 0.4s ease;
}
.shadow {
box-shadow: 0 0 10px #000;
}
<div id="test">
Lorem ipsum dolor sit amentur.
</div>
You can write a helper function and call that from the two callbacks:
const divElement = document.querySelector('#test');
function handleEvent(event, action) {
divElement.classList[action]('shadow');
console.log(event);
}
divElement.addEventListener('mouseenter', (event) => handleEvent(event, 'add'), false);
divElement.addEventListener('mouseleave', (event) => handleEvent(event, 'remove'), false);
Alternatively, you can use partial application with closures to create the two callbacks from one abstract function:
const divElement = document.querySelector('#test');
function makeEventHandler(action) {
return (event) => {
divElement.classList[action]('shadow');
console.log(event);
};
}
divElement.addEventListener('mouseenter', makeEventHandler('add'), false);
divElement.addEventListener('mouseleave', makeEventHandler('remove'), false);
Of course #Wyck is right and in this particular example, you should do everything with CSS only :-)
My way to do that:
const divElement = document.querySelector('#test');
const events = ["mouseenter", "mouseleave"]
events.forEach(event => {
divElement.addEventListener(event, (e) => {
// TODO put logic here.
divElement.classList.add('shadow');
console.log(event);
})
})
Another way to do that, by using onmouseenter="" tag and onmouseleave="" tag, and letting them use the same callback.
const callback = (element) => {
element.classList.add("shadow");
}
.shadow {
display: block;
box-shadow: 0 0 10px black;
}
<div onmouseenter="callback(this)" onmouseleave="callback(this)">Hello World</div>

Function running twice using setdelay

My function is running twice , I tried using boolean value to stop it but it doesn't work, here is my code :
window.onload = function(){
setTimeout(addbutton, 2940) // Please increase it if it doesn't work, it doesn't work well if your connection is too long
};
function addbutton(){
var hreflink = "/admin/users/delete/"+id;
var Reportuser = document.getElementsByClassName("sg-button sg-button--solid-light sg-button--full-width");
var x = Reportuser.length-1
Reportuser[x].insertAdjacentHTML('beforebegin', '<button id=button class="sg-button sg-button--solid-mint sg-button--small sg-button"><span class="sg-button__text">Delete</span></button>');
console.log("%cButton added","color: blue; font-size: 20px");
function myFunction() {
window.location.href = hreflink;
}
document.getElementById("button").addEventListener("click", myFunction);
}
Refactored and converted your code to a minimal reproducable example using event delegation. The timeout works, once. If you are experiencing problems with connection times, maybe you should check async functions
setTimeout(addbutton, 500);
document.addEventListener("click", handle);
function handle(evt) {
if (evt.target.dataset.clickable) {
console.clear();
return console.log("Yes! It's clicked!");
}
return true;
}
function addbutton() {
[...document.querySelectorAll(".sg-button")].pop()
.insertAdjacentHTML("beforeend", `<button data-clickable="1">Delete</button>`);
console.log("button added to last div.sg-button ...");
}
.sg-button {
margin-bottom: 1.5rem;
border: 1px solid #999;
padding: 0.2rem;
width: 5rem;
text-align: center;
}
.sg-button button {
display: block;
margin: 0 auto;
}
<div class="sg-button sg-button--solid-light sg-button--full-width">1</div>
<div class="sg-button sg-button--solid-light sg-button--full-width">2</div>

How can I avoid passing a parameter to each function but still have access to it inside the function

When I click the button, I am opening a Jquery dialog and creating an object of CustomClass. I need this object in different functions. Is there a way to avoid passing it to each function but still have access to it inside the function?
Note: I am using the same code to open multiple dialogs through different click events.
JS Fiddle Link: https://jsfiddle.net/gwphxssq/1/
HTML:
<div class='btn1'>Button1</div>
<div class='btn2'>Button2</div>
<p class='plain-text'> Two dialog's open, one behind the other. Please drag the top dialog to see the other dialog below.
</p>
JS:
var Test = Test || {};
Test = {
CustomClass: function(fnSave) {
return {
dialogElem: null,
saveBtn: null,
fnSave: fnSave
}
},
Cache: function(obj, dialogElem) {
obj.dialogElem = $(dialogElem);
obj.saveBtn = $(dialogElem).find('.btnSave');
},
OpenDialog: function(option) {
var that = this;
var dynamicElem = '<div>Dialog' +
'<input type="button" class="btnSave" value="Save"/>' + '</div>';
var obj = new that.CustomClass(option);
$(dynamicElem).dialog({
open: function(event, ui) {
that.Cache(obj, this);
}
});
//obj is being passed to different functions. How can I avoid passing it to each function but still have access to the obj in each of the functions below?
that.BindEvents(obj);
that.SampleFunc1(obj);
that.SampleFunc2(obj);
},
BindEvents: function(obj) {
obj.saveBtn.on('click', function() {
obj.fnSave();
});
},
SampleFunc1: function(obj) {
//Need the obj here too
//Some code
},
SampleFunc2: function(obj) {
//Need the obj here too
//Some code
}
}
//Click Event for Button 1
$('.btn1').on('click', function() {
Test.OpenDialog(function() {
alert('First Dialog');
});
});
//Click Event for Button 2
$('.btn2').on('click', function() {
Test.OpenDialog(function() {
alert('Second Dialog');
});
});
CSS:
.btn1,
.btn2 {
background-color: grey;
width: 200px;
height: 20px;
text-align: center;
padding: 3px;
margin-bottom: 5px;
display: inline-block;
}
.plain-text {
color: red;
}
.btnSave {
float: right;
width: 80px;
height: 30px;
}
You could do a factory which creates new functions, and those functions have the object in their closure. For example:
OpenDialog: function (option) {
var that = this;
var dynamicElem = '<div>Dialog' +
'<input type="button" class="btnSave" value="Save"/>' + '</div>';
var obj = new that.CustomClass(option);
var fxns = that.createFxns(obj);
fxns.bindEvents();
fxns.sampleFunc1();
fxns.sampleFunc2();
},
createFxns: function(obj) {
return {
bindEvents: function () {
obj.on('click', function () {
obj.fnSave();
}
},
sampleFunc1: function () {},
sampleFunc2: function () {}
}
}
I don't see that you get much out of this pattern though. The main benefit of this is that you could pass those functions around to some other piece of code, and have the object already 'baked in'. That way the other piece of code doesn't even need to know that obj exists. In your case though, you're just calling them right away, and your class clearly needs to know about the existence of obj.

Categories