How can I clone a new row without also copying the text that has been inserted inside the contenteditable div? I want a new blank div to type into. Many thanks
<div id="wrapper">
<div id="row">
<div id="left">
<p><strong>Heading</strong></p>
<div contenteditable="true" data-placeholder="Type here...">
</div>
<div id="right">
<p><strong>Heading</strong></p>
<div contenteditable="true" data-placeholder="Type here...">
</div>
</div>
</div>
<button id="button" onlick="duplicate()">Add another row</button>
document.getElementById('button').onclick = duplicate;
var i = 0;
var original = document.getElementById('row');
function duplicate() {
var clone = original.cloneNode(true);
clone.id = "row" + ++i;
original.parentNode.appendChild(clone);
}
Recommendations
Do not assign #ids to anything that would be duplicated use .class and .querySelector(). It's actually a better practice not to use #id at all since it will become increasingly difficult to adapt your code in the future. There are exceptions when it is actually an advantage rather than a hinderance (ex. checkboxes/radiobuttons association with labels).
When handling events, do not use on event attributes -- instead, use on event properties or .addEventListener() to handle events such as "click". It appears that in the OP code on event attribute and property was used to handle the same click event.
<!-- Do not use this --> <button id="button" onlick="duplicate()">
<!-- Use this: listenerElement.onclick = duplicate
or this: listenerElement.addEventListener('click', duplicate)
-->
Add event handlers (ex. duplicate()) with on event property (ex. .onclick) or with .addEventListener() on a parent element of all elements that need an event handler. So instead of each button listening for a user to click it -- have div#wrapper (in example below it is main) listen for a user to click a button. It's strongly recommended that you familiarize yourself with event delegation.
Keeping what was previously mentioned in mind, the following example has:
all #ids replaced by .class.
the onclick="duplicate()" removed from button
the event handler (renamed addRow(e)) uses .insertAdjacentHTML() to render a template literal of section.row behind the section.row of the button.addRow that the user clicked. Details are commented in the example below
the event handler added to main in order to delegate click events for all buttons present when page has been loaded as well as any buttons added dynamically in the future
document.querySelector('main').onclick = addRow;
some CSS added and some minor changes to HTML for semantics and styling
Details are commented in the example below
Note: it wasn't clear whether it was one button or a button on every row. This example is designed for the latter. Please comment below if the former (a single button) is desired.
// Pass the Event Object
const addRow = event => {
// This is the tag user has clicked
const btn = event.target;
// This is the string that will be rendered
const tmp = `<section class="row"><div class="left part"><header>Heading</header><div contenteditable>Type here...</div></div><div class="right part"><header>Heading</header><div contenteditable>Type here...</div></div><button class="addRow">Add Row</button></section>`;
// This is the row of the tag user has clicked
let ref = btn.parentElement;
// if this tag has class .addRow...
if (btn.matches('.addRow')) {
/*
... render a string (tmp) after the
row of the clicked button (ref) into HTML
*/
ref.insertAdjacentHTML('afterEnd', tmp);
}
};
document.querySelector('main').onclick = addRow;
.row {
display: flex;
flex-flow: row nowrap;
justify-content: flex-start;
align-items: flex-start;
}
.part {
display: flex;
flex-flow: column nowrap;
justify-content: flex-start;
width: 42vw;
}
.right {
margin-left: 10px;
}
button {
display: block;
width: max-content;
margin-left: auto;
padding: 2px 4px;
}
<main>
<section class="row">
<div class="left part">
<header>Heading</header>
<div contenteditable>Type here...</div>
</div>
<div class="right part">
<header>Heading</header>
<div contenteditable>Type here...</div>
</div>
<button class='addRow'>Add Row</button>
</section>
</main>
The following example is designed with one button
<!DOCTYPE html>
<html lang="en">
<head>
<title></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<style>
main {
width: 100vw;
}
.box {
width: 80vw;
margin: 10px auto;
padding: 5px;
border: 2px solid black;
}
.row {
display: flex;
flex-flow: row nowrap;
justify-content: flex-start;
align-items: flex-start;
}
.part {
display: flex;
flex-flow: column nowrap;
justify-content: flex-start;
width: 36vw;
margin: 4px auto;
}
.right {
margin-left: 10px;
}
button {
display: block;
width: max-content;
margin: 4px 30px 4px auto;
padding: 2px 4px;
}
[data-ph]::before {
content: attr(data-ph)
}
</style>
</head>
<body>
<main>
<div class="box">
<section class="row">
<div class="left part">
<header>Heading</header>
<div data-ph='Type here...' contenteditable></div>
</div>
<div class="right part">
<header>Heading</header>
<div data-ph='Type here...' contenteditable></div>
</div>
</section>
</div>
<button class='addRow'>Add Row</button>
<div class="box">
<section class="row">
<div class="left part">
<header>Heading</header>
<div data-ph='Type here...' contenteditable></div>
</div>
<div class="right part">
<header>Heading</header>
<div data-ph='Type here...' contenteditable></div>
</div>
</section>
</div>
<button class='addRow'>Add Row</button>
</main>
<script>
const addRow = event => {
const btn = event.target;
const tmp = `<section class="row"><div class="left part"><header>Heading</header><div data-ph="Type here..." contenteditable></div></div><div class="right part"><header>Heading</header><div data-ph="Type here..." contenteditable></div></div></section>`;
let ref = btn.previousElementSibling;
if (btn.matches('.addRow')) {
ref.insertAdjacentHTML('beforeEnd', tmp);
}
};
document.querySelector('main').onclick = addRow;
</script>
</body>
</html>
You need to select the contenteditable elment and replace the html
function duplicate() {
var clone = original.cloneNode(true);
clone.id = "row" + ++i;
clone.querySelector('div[contenteditable="true"]').innerHTML = '';
original.parentNode.appendChild(clone);
}
because you now said it is more than one, you need querySelectorAll
function duplicate() {
var clone = original.cloneNode(true);
clone.id = "row" + ++i;
clone.querySelectorAll('div[contenteditable="true"]').forEach(function(elem) {
elem.innerHTML = '';
});
original.parentNode.appendChild(clone);
}
Related
i have the following html structure:
<div id='main'>
<div id='612'>
</div>
...
<div id='1'>
</div>
</div>
As you can see i have several div going in a decreasing order and i wuold like to make a button that activate a jQuery script that can sort those div in both ways (decreasing and increasing).
I already created the button and linked it to the script but i can't find a way to sort the divs. Is possible?
Edit:
Posted the entire code on pastebin:
https://pastebin.com/DZWdNMZk
Edit:
Posted the entire code on Github if it any help:
https://github.com/mattiac02/divs
This is one option. Setting the display of the parent to flex, then using js to set the order property of the children. Hopefully the demonstration helps!
document.querySelector("#orderEm")
.addEventListener("click", () => {
document.querySelectorAll("div > div")
.forEach(div => {
div.style.order = div.id;
});
});
document.querySelector("#reverse")
.addEventListener("click", () => {
document.querySelector("#container")
.classList.toggle("reverse");
});
#container {
display: flex;
justify-content: space-around;
}
#container.reverse {
flex-direction: row-reverse;
}
div > div {
height: 50px;
width: 50px;
font-size: 15pt;
border: 1px solid black;
margin: 1em;
display: flex;
justify-content: center;
align-items: center;
}
<div id="container" data-sorted="unsorted">
<div id="2">2</div>
<div id="3">3</div>
<div id="1">1</div>
</div>
<button id="orderEm">Order 'Em!</button>
<button id="reverse">Reverse 'Em!</button>
One of of doing this is to get the elements into an array and sort the array and re-append them to the main div:
$('#btnAsc').click(function() {
//gets all div children of the main div, this returns a jQuery array with the elements
var divs = $('#main div');
//sorts the divs based on the id attribute
var ordered = divs.sort(function(a, b) {
return $(a).attr('id') - $(b).attr('id');
});
//empty the main div and re-append the ordered divs
$('#main').empty().append(ordered)
});
$('#btnDesc').click(function(){
var divs = $('#main div');
var ordered = divs.sort(function(a, b) {
return $(b).attr('id') - $(a).attr('id');
});
$('#main').empty().append(ordered)
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id='main'>
<div id='612'>
612
</div>
<div id='1'>
1
</div>
<div id='2'>
2
</div>
<div id='3'>
3
</div>
<div id='5'>
5
</div>
</div>
<input type="button" id="btnAsc" value="Asc">
<input type="button" id="btnDesc" value="Desc">
File manager = html container that contains a table-like list of files.
I have made a drop zone as big as the file manager instead of making a designated drop-zone.
I got inspired by this design:
https://www.youtube.com/watch?v=seXXWRygRkY
I only took the drop zone highlight idea from this video.
If you watch the video you will see that at 0:15 he goes over an element and the drop-zone blinks. This happens with my drop zone in A LOT of places.
My current file manager with div hierarchy :
<template>
<section
#dragover.prevent="dragOk = true"
#drop.prevent="addFile"
#drop.stop.prevent="dragOk = false"
#dragleave="dragOk = false"
>
<div class="top_container">
<div :class="`mid_container ${dragOk ? 'drag-ok' : ''}`">
<div class="title">
<h1>
File
<span>Manager</span>
</h1>
</div>
<!-- TODO: File Manager Component -->
<div v-cloak class="file-manager-container">
<div class="file-line header">
<div class="file-name">File name:</div>
<div class="file-size">Size:</div>
<div class="action-buttons">Actions:</div>
</div>
<div
:class="`file-line ${file.status ? 'wrong-file' : ''}`"
v-for="(file, index) in currentFiles"
:key="index"
>
<!-- left -->
<div class="file-name">
{{ file.name }}
<span v-if="file.status"> - {{ file.status }}</span>
</div>
<!-- middle -->
<div class="file-size">{{ file.size }} kb</div>
<!-- right -->
<div class="action-buttons">
<span>
<i class="far fa-edit"></i>
</span>
<span #click.prevent="currentFiles.splice(index, 1)">
<i class="fa fa-trash" aria-hidden="true"></i>
</span>
</div>
</div>
</div>
<!-- <span v-if="uploading" class="progress-bar">
<progress :value="progress" max="100">{{progress}}%</progress>
</span>-->
<div class="upload-message" v-if="message">
<div>{{ message }}</div>
</div>
</div>
</div>
</section>
</template>
<style scoped>
.drag-ok {
background: pink;
opacity: 0.5;
z-index: 100;
}
</style>
Issue:
If I drag items over some borders or text the dropzone blinks from pink to the default color. If I drop files when the dropzone isn't pink, the browser will open said file.
Here is a fiddle to ilustrate the problem: http://jsfiddle.net/m3wzbyoL/23/
You will have to select a file from your OS, drag it over the area and move it around there and you will see crazy flashes.
Adding pointer-events: none; to the .drop container will cancel every event from the child element and I do not want this.
If I add pointer-events: none; to .drop .highlight will make the drag events to not work.
Two things:
Ensure you are only toggling the drag-ok class on the dragenter and dragleave events. dragover will fire every few hundred milliseconds and is only for capturing events as you are dragging.
Disable pointer-events on all the children of the drop-zone target in CSS when the drag-ok class is active (not the drop-zone target itself). This will ensure that no other events from children will interfere while dragging.
Note: code provided as a minimal example, doesn't match code in question exactly..
$('.drop').on('dragenter', function(e) {
$(this).addClass('drag-ok');
})
.on('dragleave', function(e) {
$(this).removeClass('drag-ok');
})
.drop {
height: auto;
width: 200px;
background: #aaa;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.drop.drag-ok {
border: 2px dashed black;
background: black;
opacity: 0.5;
}
/**
* The important bit:
* disable pointer events on all children elements of
* the drop zone element *only* when the dragenter
* event has fired (.drag-ok is active)
*/
.drop.drag-ok * {
pointer-events: none;
}
.img {
height: 100px;
width: 100px;
background: red;
margin: 5px 0;
}
.img:hover {
background: pink;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<div draggable="true">drag me</div>
<div class="drop">
<span>Drop here</span>
<div class="img"></div>
<div class="img"></div>
<div class="img"></div>
<div class="img"></div>
</div>
I've been playing around with HTML and I created a column that immediately appears when I open my file in a browser. I tried moving the column and row classes around but I can't figure out how to get it so that the column doesn't appear until after I select an option from the dropdown menu. I was wondering how I could fix this issue?
<html>
<head>
<title>Testing Display</title>
</head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.center{
text-align: center;
border: 3px solid #73AD21;
}
{
box-sizing: border-box;
}
.column {
float: left;
width: 30%;
padding: 10px;
height: 2000px;
}
.row:after {
content: "";
display: table;
clear: both;
}
</style>
<body>
<div class ="center">
<p><div><h1>Testing Display</h1></div><br /><p>
<div class="dropdown">
<form>
<select name="list" id="list" accesskey="target" onchange="display(this)">
<option value="none">Choose an option</option>
<option value="one">Option one</option>
</select>
<input type=button value="Select" onclick="display()"/>
<div id="add"></div>
</form>
</div>
</div>
<div class="row">
<div class="column" style="background-color:#aaa;">
<h2>Column 1</h2>
<p>Some text..</p>
<div id="div"></div>
</div>
</div>
<script src="order.js"></script>
</body>
</html>
Have the initial visibility of column to hidden.
Have a Javascript function to add a new class to column onChange. Something like this
function onSelect() {
//or add id to easily access unique single element.
var element = document.getElementByClassName("column")[0];
element.classList.add("showCol");
}
.column {
visibility: hidden;
...
}
.showCol {
visibility: visible;
...
}
Can also add styles and remove style onChange instead of toggling classes.
Javascript Solution, call this method in onchange event of drop down list:
function ddlChange(){
if (document.getElementById("list").value === "one"){
document.getElementsByClassName("column").style.display = "table-cell";
}
}
Using jQuery:
$("#list").change(function () {
if($('#list option:selected').val() === "one")
$(".column").css("display","table-cell");
});
Alternatively, you can also try to add or remove a class instead of changing inline CSS value.
I have a list of DIVS that have buttons inside. By default, all buttons are hidden. When I click within a DIV area, the current button inside of this clicked DIV are should show (class='.db') AND all previously clicked/shown buttons should be hidden (class='.dn'). In other words, at any time there should be only one button (currently clicked) shown and all other should be hidden.
I want to use vanilla Javascript and tried this below, but it won't work. I feel there is some small error but don't know where.. Note - the DIVS and buttons don't have their own unique IDs (they only have the same CSS (.posted) classes.
PS - maybe it'd be better not to add this onClick="t();" to each DIV and use an 'addEventListener' function, but this is way too much for me ; )
CSS:
.dn {display:none}
.db {display:block}
.posted {
height: 50px;
width: 100px;
background-color: green;
border: 2px solid red;
}
HTML:
<div class="posted" onClick="t();">
<button class="dn">Reply</button>
</div>
<div class="posted" onClick="t();">
<button class="dn">Reply</button>
</div>
<div class="posted" onClick="t();">
<button class="dn">Reply</button>
</div>
JAVASCRIPT:
function t()
{
var x=document.getElementsByClassName("posted"),i,y=document.getElementsByTagName("button");
for(i=0;i<x.length;i++)
{
x[i].y[0].className="dn";
};
x.y[0].className='db';//make sure the currently clicked DIV shows this button (?)
}
You might want to read more about selector, how to select class, block level etc.
some link might be helpful:
CSS selector:
https://www.w3schools.com/cssref/css_selectors.asp
jQuery selector:
https://api.jquery.com/category/selectors/
Solution - Using jQuery:
$('.posted').on('click', function() {
//find all class called posted with child called dn, then hide them all
$('.posted .dn').hide();
//find this clicked div, find a child called dn and show it
$(this).find('.dn').show();
});
.dn {
display: none
}
.db {
display: block
}
.posted {
height: 50px;
width: 100px;
background-color: green;
border: 2px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="posted">
<button class="dn">Reply1</button>
</div>
<div class="posted">
<button class="dn">Reply2</button>
</div>
<div class="posted">
<button class="dn">Reply3</button>
</div>
Solution - Pure js version:
//get list of div block with class="posted"
var divlist = Array.prototype.slice.call(document.getElementsByClassName('posted'));
//for each div
divlist.forEach(function(item) {
//add click event for this div
item.addEventListener("click", function() {
//hide all button first
divlist.forEach(function(el) {
el.getElementsByTagName('button')[0].classList.add('dn');
});
//show button of the div clicked
this.getElementsByTagName('button')[0].classList.remove('dn');
}, false);
});
.dn {
display: none
}
.db {
display: block
}
.posted {
height: 50px;
width: 100px;
background-color: green;
border: 2px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="posted">
<button class="dn">Reply1</button>
</div>
<div class="posted">
<button class="dn">Reply2</button>
</div>
<div class="posted">
<button class="dn">Reply3</button>
</div>
You can do this with with plain JavaScript using Event Bubbling, querySelector and the element classList attribute like this.
Change your HTML to look like this:
<div class="posts">
<div class="posted">
<button class="dn">Reply</button>
</div>
<div class="posted" >
<button class="dn">Reply</button>
</div>
<div class="posted" >
<button class="dn">Reply</button>
</div>
</div>
Then use JavaScript like this:
var posts = document.querySelector('.posts');
var allPosted = document.querySelectorAll('.posted');
//clicks bubble up into the posts DIV
posts.addEventListener('click', function(evt){
var divClickedIn = evt.target;
//hide all the buttons
allPosted.forEach(function(posted){
var postedBtn = posted.querySelector('button');
postedBtn.classList.remove('db');
});
// show the button in the clicked DIV
divClickedIn.querySelector('button').classList.add('db')
});
You can find a working example here: http://output.jsbin.com/saroyit
Here is very simple example using jQuery .siblings method:
$(function () {
$('.posted').click(function () {
$('button', this).show();
$(this).siblings().find('button').hide();
});
});
https://jsfiddle.net/3tg6o1q7/
What I want is to click on #bt-1 and change the color of #target-1, click on #bt-2 and change the color of #target-2...
I started writing a particular click event handler for each #bt-n / #target-n but as the site got bigger I thought about using a loop. My approach was using a for loop with variables in jQuery selectors. Here is my code:
$(document).ready(function() {
var total = $('.target').length;
for(n=1; n<=total; n++) {
var num = String(n);
$('#bt-'+num).on('click', function() {
$('#target-'+num).toggleClass('yellow');
});
}
});
.wrapper {
display: flex;
text-align: center;
}
.button, .target {
padding: 20px;
margin: 10px;
}
.button {
background: gray;
}
#target-1 {
background: red;
}
#target-2 {
background: green;
}
#target-3 {
background: blue;
}
.yellow {
background: yellow !important;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrapper">
<div id="bt-1" class="button">
<h1>Button 1</h1>
</div>
<div id="target-1" class="target">
<h1>Target 1</h1>
</div>
</div>
<div class="wrapper">
<div id="bt-2" class="button">
<h1>Button 2</h1>
</div>
<div id="target-2" class="target">
<h1>Target 2</h1>
</div>
</div>
<div class="wrapper">
<div id="bt-3" class="button">
<h1>Button 3</h1>
</div>
<div id="target-3" class="target">
<h1>Target 3</h1>
</div>
</div>
I don't understand why it only targets the last #target-n as the loop seems to be working on #bt-n. I also thought about using an array but can't figure out how to implement it.
I managed to make it work using $(this).siblings('.target')... which do not require the for loop and ids but a parent element for each .button / .target, in this case .wrapper Code Here. Although this was a good solution, I would like to understand what I did wrong and how to properly implement a loop to achieve this without using the parent .wrapper. Thank you.
The reason that only the last item gets affected is because the loop has completed before any event fires. Therefore n holds the last value in the loop. To fix this you need to use a closure:
for (n = 1; n <= total; n++) {
(function(n) {
$('#bt-' + n).on('click', function() {
$('#target-' + n).toggleClass('yellow');
});
})(n);
}
That said, a much better approach would be avoid the loop and to use DOM traversal to find the .target related to the clicked .button, like this:
$('.button').click(function() {
$(this).next('.target').toggleClass('yellow');
});
.wrapper {
display: flex;
text-align: center;
}
.button,
.target {
padding: 20px;
margin: 10px;
}
.button {
background: gray;
}
#target-1 {
background: red;
}
#target-2 {
background: green;
}
#target-3 {
background: blue;
}
.yellow {
background: yellow !important;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrapper">
<div id="bt-1" class="button">
<h1>Button 1</h1>
</div>
<div id="target-1" class="target">
<h1>Target 1</h1>
</div>
</div>
<div class="wrapper">
<div id="bt-2" class="button">
<h1>Button 2</h1>
</div>
<div id="target-2" class="target">
<h1>Target 2</h1>
</div>
</div>
<div class="wrapper">
<div id="bt-3" class="button">
<h1>Button 3</h1>
</div>
<div id="target-3" class="target">
<h1>Target 3</h1>
</div>
</div>
It is unwise to register a lot of event handlers. You can bind one event handler and perform action for given specific idx read from element id, eg:
$('body').on('click', function (event) {
if (!event.target.id.match(/^bt-\d+/)) {
return; //id of clicked element does not match bt-{number}
}
var idx = event.target.id.replace('bt-', ''); //remove prefix "bt-" and leave only numeric postfix
$('#target-' + idx).toggleClass('yellow');
});
Explanation:
When you bind click on body element You are getting access to all click events from child elements that not cancelled passing that event up. Element that has been clicked in saved inside event.target and it has property id in event.target.id.
On this id property I call match function with regular expression - it will match string which starts ^ from bt- and have any number \d at least one one + .
if (!event.target.id.match(/^bt-\d+/)) {
return; //id of clicked element does not match bt-{number}
}
There is negation of this statement, so If this id is not in format bt-someNumber it will not go further.
var idx = event.target.id.replace('bt-', '');
Than takes id and replaces bt- part in it with empty string ''
$('#target-' + idx).toggleClass('yellow');
Finally You are toggling class on element with same number as button but with different prefix target- instead of bt-.
Happy hacking!