How to create element onclick which removes the element's parent node? - javascript

I'm trying to make a box copier that creates boxes which each contain a button to delete itself. Each box is a duplicate of a hidden template box, and each has an id starting at box1:
This is what I have so far:
let boxcount = 0;
function removebox() {
this.parentNode.remove();
}
function addbox() {
var container = document.getElementById("container"),
box = document.getElementById("boxoriginal");
var boxcopy = box.cloneNode(true);
boxcount += 1;
boxcopy.id = "box" + boxcount;
container.appendChild(boxcopy);
var remover = document.createElement("DIV");
remover.innerHTML = "x";
remover.onclick = removebox;
document.getElementById(boxid).appendChild(remover);
}
The problem is that if I click on the X in box1 for instance, it removes the last box just added, rather than box1. I've tried something similar using EventListener but with the same result.
I'm brand new to JS, so I can only guess that I'm misunderstanding how this works.

Maybe not a perfect solution for what you are trying to achieve, but you might want to create a structure more like:
let doc, bod, M, I, SimpleBoxMaker; // for use on other loads
addEventListener('load', ()=>{
doc = document; bod = doc.body; M = tag=>doc.createElement(tag); I = id=>doc.getElementById(id);
SimpleBoxMaker = function(appendTo = bod){
this.container = M('div');
this.addBox = contentNode=>{
const container = this.container, box = M('div'), x_div = M('div');
box.className = 'box_div'; x_div.className = 'x_div'; x_div.innerHTML = '×';
box.appendChild(x_div);
if(contentNode)box.appendChild(contentNode);
x_div.onclick = ()=>{
box.remove();
}
container.appendChild(box);
return this;
}
appendTo.appendChild(this.container);
}
// below code can be put on a separate page using a `load` Event (besides // end load line)
const bigBox = new SimpleBoxMaker, addBox = I('add_box');
addBox.onclick = ()=>{
const div = M('div');
div.textContent = 'Before adding this node there were '+bigBox.container.children.length+' children in the container';
bigBox.addBox(div);
}
}); // end load
*{
box-sizing:border-box:
}
.box_div{
min-height:30px; border:1px solid #000; margin-top:2px;
}
.x_div{
cursor:pointer; display:flex; justify-content:center; align-items:center; width:30px; height:30px; background:#900; color:#fff; font:bold 24px san-serif; text-align:center; float:right;
}
<button id='add_box'>Add Box</div>

Related

How do I get my for loop to run before it prints?

I have this set up in html to allow a user to input a number, then click submit, and I am hoping to change how many periods will come out in the output box. Currently, just one period will print (I know this is calling to the variable declared in the conditional portion). I assume this means it is collecting the value before the loop runs, but I don't know how to fix that. Please help. (I'm happy to provide the rest of the code if needed but I'm fairly certain I have isolated the problem.
<script type="text/javascript">
function makepyramid() {
var numberin = document.getElementById("numberin");
var pyramid = numberin.value;
var mathablenumber = +numberin.replace;
for (n = 0, spaces ="."; n < mathablenumber; n++)
{
var spaces = spaces +=".";
}
var txtOutput = document.getElementById("txtOutput");
txtOutput.value = "Hi there, " + spaces + "!"
} // end makepyramid
</script>
Basically your problem is in the row var mathablenumber = +numberin.replace;. I dont know what does it do and why do you need it. If your input field is number type it already returns you the number type value and you can use it for iteration directly
I have edited your code a little:
function makepyramid() {
var numberin = document.getElementById("numberin");
var pyramid = numberin.value;
// You can leave as one line as well
//for (var n = 0, spaces = '.'; n < pyramid; n++) spaces +=".";
for (var n = 0, spaces = '.'; n < pyramid; n++) {
spaces +=".";
}
var txtOutput = document.getElementById("txtOutput");
txtOutput.value = "Hi there, " + spaces + "!"
} // end makepyramid
makepyramid();
<input id="numberin" value="5" type="number" />
<input id="txtOutput" type="text"/>
I'm not really sure of your goal here. Thought I'd show some technique:
let doc, htm, bod, nav, M, I, mobile, S, Q, aC, rC, tC, CharPyramid; // for use on other loads
addEventListener('load', ()=>{
doc = document; htm = doc.documentElement; bod = doc.body; nav = navigator; M = tag=>doc.createElement(tag); I = id=>doc.getElementById(id);
mobile = nav.userAgent.match(/Mobi/i) ? true : false;
S = (selector, within)=>{
var w = within || doc;
return w.querySelector(selector);
}
Q = (selector, within)=>{
var w = within || doc;
return w.querySelectorAll(selector);
}
aC = function(){
const a = [...arguments];
a.shift().classList.add(...a);
return aC;
}
rC = function(){
const a = [...arguments];
a.shift().classList.remove(...a);
return rC;
}
tC = function(){
const a = [...arguments];
a.shift().classList.toggle(...a);
return tC;
}
CharPyramid = function(char = '*'){
this.char = char;
this.build = height=>{
let p = M('div');
for(let i=1,r,c=this.char,l=height+1; i<l; i++){
r = M('div'); p.appendChild(r);
for(let n=0,d; n<i; n++){
d = M('div'); d.textContent = c; r.appendChild(d);
}
}
return p;
}
}
// magic happens below
const pyr_chars = I('pyr_chars'), pyr = I('pyr'), cp = new CharPyramid('.'), pyr_er = I('pyr_er');
pyr.appendChild(cp.build(+pyr_chars.value));
pyr_chars.oninput = function(){
let v = this.value;
if(v.match(/^[1-9][0-9]*$/)){
aC(this, 'yes'); pyr_er.textContent = pyr.innerHTML = ''; pyr.appendChild(cp.build(+v));
}
else{
rC(this, 'yes'); pyr.innerHTML = ''; pyr_er.textContent = 'Numbers Only';
}
}
}); //
*{
box-sizing:border-box;
}
btml,body{
background:#ccc;
}
label,input,.er{
font:bold 22px Tahoma, Geneva, sans-serif;
}
label{
color:#fff; text-shadow:-1px 0 #000,0 1px #000,1px 0 #000,0 -1px #000;
}
input{
width:100%; color:#000; padding:3px 5px; border:1px solid #c00; border-radius:3px; margin-top:2px;
}
input.yes{
border-color:#0c0;
}
.er{
color:#a00; font-size:14px; text-align:center; margin-top:3px;
}
.pyr{
margin-top:10px; text-align:center;
}
.pyr>div{
display:inline-block; background:#fff; color:#0b0; text-align:center;
}
.pyr>div>div>div{
display:inline-block;
}
<label for='pyr_chars'>Pyramid Height:</label>
<input class='yes' id='pyr_chars' type='number' value='9' min='1' />
<div class='er' id='pyr_er'></div>
<div class='pyr' id='pyr'></div>
Well, I may not get your point exactly,
anyway if you want to make your code be waiting for the for loop you can simply make it async, try this
async function processArray(array) {
array.forEach(item => {
// define synchronous anonymous function
// IT WILL THROW ERROR!
await func(item);
})
}
Also, you have a mistyping when you are initializing the spaces over and over again inside the loop. that will not get your work done at all.
define the var spaces outside the loop.

Drag and drop returns 'null' on item created by DOM

I'm currently working on a trello-like app, and have run into some issues with drag and drop events, and DOM. I basically have four lists, each containing a div with id "card-container". I'm able to move cards created in the source code as expected, however, when I add a card through DOM, and try to move it to another container, the container appends a child "null". Not sure what I am doing wrong.
When adding a new card, the function makeCard() is run by submitting a button.
draganddorp.js
const card = document.querySelector('.task-card');
const cards = document.querySelectorAll('.task-card')
const cardContainers = document.querySelectorAll('.card-container');
var draggingCard = null;
// card listeners
cards.forEach(addCardListeners);
// Loop through taskContainer boxes and add listeners
cardContainers.forEach(addContainerListeners);
// Drag Functions
function dragStart(event) {
this.className += ' card-hold';
setTimeout(() => (this.className = 'invisible'), 0); //set timeout so card wont dissapear
draggingCard = event.target;
}
function dragEnd() {
this.className = 'task-card';
draggingCard = null;
}
function dragOver(e) {
e.preventDefault();
}
function dragEnter(e) {
e.preventDefault();
this.className += ' card-container-hover';
}
function dragLeave() {
this.className = 'card-container';
}
function dragDrop() {
this.className = 'card-container';
this.append(draggingCard);
}
function addCardListeners(card) {
card.addEventListener('dragstart', dragStart);
card.addEventListener('dragend', dragEnd);
}
function addContainerListeners(cardContainer) {
cardContainer.addEventListener('dragover', dragOver);
cardContainer.addEventListener('dragenter', dragEnter);
cardContainer.addEventListener('dragleave', dragLeave);
cardContainer.addEventListener('drop', dragDrop);
}
makecard.js
function makeCard(destination) {
//defining all variables needed for creating a card
let getCardContainer = document.getElementById(destination);
let createTaskCard = document.createElement("div");
//varibles needed for task header
let createTaskHeader = document.createElement("div");
let createTags = document.createElement("div");
let createTag = document.createElement("span");
let createActionsBtn = document.createElement("div");
//varibles needed for task body
let createTaskBody = document.createElement("div");
let createTaskTitle = document.createElement("p");
//varibles needed for task footer
let createTaskFooter = document.createElement("div");
let createAsignee = document.createElement("div");
let createAsigneeIcon = document.createElement("span");
let createAsigneeMember = document.createElement("span");
let createDueDate = document.createElement("div");
let createDueDateDate = document.createElement("span");
let createDueDateIcon = document.createElement("span");
//creating card
createTaskCard.className = "task-card";
createTaskCard.setAttribute("draggable", true);
//addding class/id and HTML to task header
createTaskHeader.className = "task-card-header";
createTags.className = "tags";
createTag.className = "tag";
createTag.id = "tag-";
createTag.innerHTML = "someTags"
createActionsBtn.className = "actions";
//add action itself (svg)
//addding class/id and HTML to task body
createTaskBody.className = "task-card-body";
createTaskTitle.innerHTML = "someTitle"
//addding class/id and HTML to task footer
createTaskFooter.className = "task-card-footer";
createAsignee.className = "asignee";
createAsigneeIcon.className = "icon";
createAsigneeIcon.innerHTML = "I";
createAsigneeMember.innerHTML = "Assignee name";
createDueDate.className = "dueDate";
createDueDateDate.innerHTML = "someDate"
createDueDateIcon.className = "icon";
createDueDateIcon.innerHTML = "I";
//setting up structure
createTaskHeader.appendChild(createTags);
createTaskCard.appendChild(createTaskHeader);
createTags.appendChild(createTag);
createTaskHeader.appendChild(createActionsBtn);
createTaskCard.appendChild(createTaskBody);
createTaskBody.appendChild(createTaskTitle);
createTaskCard.appendChild(createTaskFooter);
createTaskFooter.appendChild(createAsignee);
createAsignee.appendChild(createAsigneeIcon);
createTaskFooter.appendChild(createDueDate);
createAsignee.appendChild(createAsigneeMember)
createDueDate.appendChild(createDueDateDate);
createDueDate.appendChild(createDueDateIcon);
//appending card to card container
getCardContainer.appendChild(createTaskCard);
}
html
<div class="task-card" draggable="true">
<div class="task-card-header">
<div class="tags">
<span class="tag">Priority</span>
<span class="tag">Design</span>
</div>
<div class="actions">
<a href="#">
<!--icon-->
</a>
</div>
</div>
<div class="task-card-body">
<p>Test</p>
</div>
<div class="task-card-footer">
<div class="asignee">
<span class="icon">
<!--icon-->
</span>
<span>Daniel Kjellid</span>
</div>
<div class="dueDate">
<span>23.05</span>
<span class="icon">
<!--icon-->
</span>
</div>
</div>
</div>
css
.card-container {
background: white;
height: auto;
margin: 2px;
min-height: 150px;
width: 115%;
}
.card-container-hover {
border: dashed 3px #F364A2 !important;
}
.card-dragging {
display: absolute;
}
.card-hold {
border: solid 5px #ccc;
}
.task-card {
border-radius: 8px;
box-shadow: 0 0 4px 0 rgba(0,0,0,0.50);
color: #3E4C59;
height: auto;
margin-bottom: 13px;
width: 100%;
}
It seems to me when you make a new card in the makeCard(destination) function, you do not add the dragstart snd dragend listeners to it. As such draggingCard is still null because it has not been set (which happens in the dragStart event listener).
Try adding this to the end of your makeCard function.
addCardListeners(createTaskCard);
Ofcourse, it is a bit hard to help without a working example.

Show Hidden Text in DIV on Hover

The desired effect is to have hidden text within a table cell show the full text across the cell when user hovers the mouse cursor over the text. See link to problem diagram to see what I mean. The "solution" denotes the desired effect while "problem" denotes what the code is currently doing. Much appreciated for the assistance.
var jobSourceID = 'jobSource' + jobIndex;
jobSource.innerHTML = '<div id="' + jobSourceID + '">' + jobSourceValue + '</div>';
document.getElementById(jobSourceID).style.marginLeft = '3px';
document.getElementById(jobSourceID).style.maxWidth = '55px';
document.getElementById(jobSourceID).style.overflow = 'hidden';
document.getElementById(jobSourceID).style.textOverflow = 'ellipsis';
document.getElementById(jobSourceID).style.whiteSpace = 'nowrap';
document.getElementById(jobSourceID).addEventListener('mouseover', function () {
document.getElementById(jobSourceID).style.overflow = 'visible';
document.getElementById(jobSourceID).style.backgroundColor = '#555555';
});
document.getElementById(jobSourceID).addEventListener('mouseout', function () {
document.getElementById(jobSourceID).style.overflow = 'hidden';
document.getElementById(jobSourceID).style.backgroundColor = '';
});
You can set 'jobSourceID' width as auto on mouseover function.
document.getElementById(jobSourceID).style.width = "auto";
If your problem ist just the styling of the background, you can style a SPAN inside the DIV and hide this instead of only the text.
document.body.innerHTML = '<div id="' + jobSourceID + '"><span>' + jobSourceValue + '<span></div>';
document.getElementById(jobSourceID).style.marginLeft = '3px';
document.getElementById(jobSourceID).style.maxWidth = '55px';
document.getElementById(jobSourceID).style.overflow = 'hidden';
document.getElementById(jobSourceID).style.textOverflow = 'ellipsis';
document.getElementById(jobSourceID).style.whiteSpace = 'nowrap';
document.getElementById(jobSourceID).addEventListener('mouseover', function () {
document.getElementById(jobSourceID).style.overflow = 'visible';
document.getElementById(jobSourceID).children[0].style.backgroundColor = '#555555';
});
document.getElementById(jobSourceID).addEventListener('mouseout', function () {
document.getElementById(jobSourceID).style.overflow = 'hidden';
document.getElementById(jobSourceID).children[0].style.backgroundColor = '';
});
You can do it like this:
*{
padding:0;
margin: 0;
}
td{
overflow:hidden;
}
td div{
background-color: #ccc;
width:50px;
}
td:hover{
overflow: inherit
}
td:hover div{
width:100%;
}
<table>
<tr>
<td>
<div>
asadgshfdadgfhgdgjgsfgfadsfahgdafgerre
</div>
</td>
</tr>
</table>
After trying all the suggested solutions posted, I see that none of them solves the problem I described in my post diagram. However, I did some more searching and after googling "html tooltips" I found some good articles explaining the concept such that tooltips was the solution I needed. So I managed to get this problem fixed on my own to my satisfaction.

Renew Loading JSON Repeat Last Post

I made a JSON script that bring posts from JSON page and when the user scroll down I renew loading to bring more posts and it works great but the problem is when the JSON load the first time it repeat the last post. here is a live demo of the script.
How you can see when you scroll down to the end the script show more posts but it is repeating the last post which called "Automatic Slideshow for Blogger with 3D Gallery".
HTML CODE
<div id="result-container">
<ol></ol>
<span class="loading">Memuat...</span>
</div>
CSS CODE
#result-container {
height:400px;
width:400px;
overflow:auto;
margin:50px auto;
font:normal normal 12px 'Trebuchet MS',Trebuchet,Geneva,Arial,Sans-Serif;
}
#result-container ol {
margin:0 0;
padding:0 0;
background-color:#B5D68C;
}
#result-container li {
margin:0 0;
padding:0 0;
list-style:none;
}
#result-container li:nth-child(even) {background-color:#A2C179}
#result-container li a {
display:block;
padding:5px 10px;
font-weight:bold;
color:#396B18;
text-decoration:none;
}
#result-container li a:hover {
background-color:#396B18;
color:white;
text-decoration:none;
}
#result-container .loading {
display:block;
height:26px;
font:normal bold 11px/26px Arial,Sans-Serif;
color:white;
text-align:center;
background-color:#B75A6F;
border-top:2px solid #222;
}
#result-container .loading.the-end {background-color:#666}
JAVASCRIPT CODE
var widget_config = {
home_page: 'http://www.dte.web.id', // Your blog homepage
container_id: 'result-container', // ID of the result container
script_id: 'load-on-scroll-end-script', // ID of the asynchronous script
max_result: 25, // Max result post at once script loading
end_text: 'Habis' // End text if all posts has been loaded
};
var elem = document.getElementById(widget_config.container_id),
inner = elem.getElementsByTagName('ol')[0],
loading = elem.getElementsByTagName('span')[0],
start = 0, // Dynamic start-index
max = widget_config.max_result;
function grabList(json) {
var list = json.feed.entry, link, skeleton = "";
if (list !== undefined) {
for (var i = 0; i < list.length; i++) {
for (var j = 0; j < list[i].link.length; j++) {
if (list[i].link[j].rel == "alternate") {
link = list[i].link[j].href;
}
}
skeleton += '<li>' + list[i].title.$t + '</li>';
}
inner.innerHTML += skeleton; // Insert the list to the container
loading.style.display = "none"; // Hide the loading indicator
} else {
// If the JSON is empty (list == undefined),
// add a new class to the loading indicator called `the-end`
loading.className += ' the-end';
// Replace the loading indicator text into `fully loaded!` for the example
loading.textContent = widget_config.end_text;
}
}
// Make an indirect script loader with two parameters: start-index and max-result post
function updateScript(a, b) {
var head = document.getElementsByTagName('head')[0],
script = document.createElement('script');
script.type = 'text/javascript';
script.id = widget_config.script_id;
script.src = widget_config.home_page + '/feeds/posts/summary?alt=json-in-script&start-index=' + a + '&max-results=' + b + '&callback=grabList';
// If there is an old script in the document...
if (document.getElementById(widget_config.script_id)) {
var oldScript = document.getElementById(widget_config.script_id);
// Remove the old script, and replace with the new one that has an updated start-index value
oldScript.parentNode.removeChild(oldScript);
}
head.appendChild(script);
}
// Start loading the callback script with start-index of 1
updateScript(1, max);
// When the container is being scrolled...
elem.onscroll = function() {
// ... check the scroll distance
if ((this.scrollTop + this.offsetHeight) == inner.offsetHeight) {
// If the distance equal to the height of the inner container...
start++; // Increase the start value by one
// then load the new script with an updated start-index
updateScript(start*max, max);
// and show the loading indicator
loading.style.display = "block";
}
};
Seems that the API for the link is starting on index 1 which is not taken into consideration when fetching the next page.
Try updateScript(start*max + 1, max);

Get value of element once then keep that value static

I am using
function expand(btn) {
var box = btn.parentNode.parentNode,
ipsum = box.getElementsByTagName("p")[0],
textSize = window.getComputedStyle(ipsum, null).getPropertyValue('font-size'),
lineHeight = window.getComputedStyle(ipsum, null).getPropertyValue('line-height'),
boxWidth = window.getComputedStyle(box, null).getPropertyValue('width'),
initialHeight = window.getComputedStyle(box, null).getPropertyValue('height'),
numText = parseInt(textSize),
numWidth = parseInt(boxWidth),
numHeight= parseInt(initialHeight);
if(box.style.height == "150px"){
box.style.height = "40px";
ipsum.style = "display:none";
}
else{
box.style.height = "150px";
ipsum.style = "display:inline";
}
console.log(lineHeight);
}
to get the initial height value of an element the only problem is that the element height changes frequently, but the first value obtained is always correct how can i get the initial value and keep it static?
how do i only store the value in the variable once, i need it in a variable to do calculations but as the value keeps changing i am getting the wrong number outputs.
You can refactor the function to store the initialHeight in a "private" variable the first time it's run:
var expand = (function() {
var initialHeight;
// Return a function that holds initialHeight in a closure
return function (btn) {
// Get box before setting/getting initialHeight
var box = btn.parentNode.parentNode;
// Set initialHeight only if undefined
initialHeight = initialHeight || window.getComputedStyle(box, null).getPropertyValue('height');
// Do other variables
var ipsum = box.getElementsByTagName("p")[0],
textSize = window.getComputedStyle(ipsum, null).getPropertyValue('font-size'),
lineHeight = window.getComputedStyle(ipsum, null).getPropertyValue('line-height'),
boxWidth = window.getComputedStyle(box, null).getPropertyValue('width'),
numText = parseInt(textSize),
numWidth = parseInt(boxWidth),
numHeight= parseInt(initialHeight);
if(box.style.height == "150px"){
box.style.height = "40px";
ipsum.style.display = "none";
} else {
box.style.height = "150px";
// If ipsum is a P, probably better to use "" (empty string) here
// so it returns to its default or inherited value
// ipsum.style.display = "inline";
ipsum.style.display = "";
}
console.log(lineHeight);
}
}());
The above is a proper refactoring, tested with the following markup:
<style type="text/css">
#box {border: 1px solid blue;}
#notBox {border: 1px solid red;}
#ipsum {border: 1px solid yellow;}
</style>
<div id="box">box
<div id="notBox">notBox
<input type="button" onclick="expand(this)" value="Expand…">
<p id="ipsum">ipsum</p>
</div>
</div>
Can you set it as an attribute? Something like:
// set the value once some place
box.setAttribute('data-init-height', window.getComputedStyle(…)… );
// when setting the initial height, check for the attribute first
initialHeight = box.getAttribute('data-init-height') || window.getComputedStyle(…)…;
Follow-up:
see fiddle
Each time you execute the function expand, you got a new value for initialHeight.
All that you need is to record it in a closure, with a hash if you're having more than 1 btn to handle, valued with arrays if you'd like to record multi heights for each btn. Just like this:
// predefine the function expand for furthre usage.
var expand;
(function() {
/*
having arrays as value, indexed like this:
{
<btn_1_id> : [<firstHeight>, <2nd Height>, ...],
<btn_2_id> : [],
...
}
*/
// Let's assume every btn is having an id. You may think another way yourself it they don't.
var initialHeightForMultiBtns = {};
expand = function(btn) {
// ...blablabla
initialHeightForMultiBtns[btn.id] = initialHeightForMultiBtns[btn.id] || [];
initialHeightForMultiBtns[btn.id].push(window.getComputedStyle(box, null).getPropertyValue('height'));
console.log(initialHeightForMultiBtns[btn.id][0]); // the real initialized height for the given btn.
// ...blablabla
}
})()
expand(btn_1); // let's expand btn_1 here.
Good luck.

Categories