Dynamically adding siblings and child elements using JavaScript/jQuery - javascript

I want a form that gives user the ability to dynamically add fields. What I need is the ability to add dynamic children and giving every child the ability to add its N number of children. Much like this
Parent 1
-- Child 1-1
-- Child 1-2
-- Child 1-2-1
-- Child 1-2-2
-- Child 1-2-3
-- Child 1-2-4
-- Child 1-2-4-1
-- Child 1-2-4-2
-- Child 1-3
-- Child 1-4
-- Child 1-5
-- Child 1-5-1
Parent 2
Parent 3
-- Child 3-1
Everything is dynamic, and a user can go as deep as they want to. So far, I'm able to achieve something similar to the JsFiddle link and I'm stuck badly after going 2 levels deep.
P.S: The numbers are added to show the relationship between a child to its siblings, its parent, and its children.
Update 1: This is what I've been able to achieve so far: JsFiddle
Update 2: Did some more work on this and was able to get it this far: jsFiddle

as discussed, i have worked it out on the fiddle.
please use the wrapElement() function to wrap your element in a div to your desire. In case you're loosing the fiddle, here's the code
$('.level_1').on('click', spawn);
function spawn(){
// check level
var level = stripLevel(this.className);
if (level !== '') {
var countOthers = this.parentNode.querySelectorAll("[class^='level_" + level +"']").length;
var x = wrapElement(level, countOthers);
if (level.length == 1) {
$('#addedElements').append(x);
} else {
//x.insertAfter(this);
$(this).parent().append(x);
}
}
}
// strip level
var stripLevel = function(className) {
var index = className.indexOf('_');
if(index > -1) {
return className.substr(index + 1);
} else {
return '';
}
}
// wrapper element
var wrapElement = function(level, number) {
var div = $('<div></div>');
if (level.length == 1) {
// it's parent
var input = $('<input type="text" name="foo_" />');
div.append(input);
} else {
// it's child
var span = $('<span>child level ' + level + '-' + number + '</span>');
div.append(span);
}
// add button
var button = $('<input class="level_' + level + '-' + number + '" type="button" value="Add Navigation" />');
button.on('click', spawn);
div.append(button);
div.css('margin-left', (level.length * 10) + 'px');
return div;
}

I wouldn't use the onclick attributes in html, but add the event handlers with javascrip
if your child nodes all use the same code, you could try to use a recursive approach:
function spawn(event) {
$(this).append(child);
$(child).on('click', function(event) {spawn(event);});
}
$('.parent').on('click', function(event) {spawn(event);});
was a first idea (with jquery), maybe it inspires you.
p.s. lacking the rep to make this comment, so it's an answer instead >.>

Based on the user3154108's answer, here's a recursive solution, you can start with:
$('.parent').on('click', spawn);
function spawn(){
var x = $('<input class="' + this.className + '-child" type="button" value="Add Navigation" />');
x.on('click', spawn);
x.insertAfter(this);
}
http://jsfiddle.net/Z9SBa/23/

Related

How to click to clone all id's that match these classes

I'm creating a page where I click a button and add content to a div. Right now I have three buttons that all have at least one class. Each class corresponds to a single div id that has its own content. What I'm trying to do is grab each class that is on the button, find the div id that has the same name, clone it, and append it to the #return div.
I have a working example that will only clone the first class name it sees on the button. How can I have my jquery find all div id's that match the classes on the button, clone them, and append them to #return?
These buttons can have the same class but when it's cloned in the #return, I don't want it cloned again. I have an if statement that tries to prevent this but it ends up deleting them. How can I stop this?
I have fiddle here that shows this jquery working:
$(document).ready(function() {
$("[id^=button]").click(function() {
var myclass = $(this).attr("class").split(" ");
if ($(this).is(".a1,.a2,.a3")) {
$("div#" + myclass).first().clone().appendTo("#return");
}
if ($("#return div#" + myclass).length > 1) {
$("#return div#" + myclass + ":last").remove();
}
});
});
The problem is that myclass is an array, so when you add that as a string to create a selector like "div#" + myclass it ends up creating a selector "div#a1,a2" which is not what you want.
You should iterate over the myclass array and perform your logic for each class.
Something like this
$(document).ready(function() {
$("[id^=button]").click(function() {
var myclass = $(this).attr("class").split(" ");
if ($(this).is(".a1,.a2,.a3")) {
myclass.forEach(function(currentClass) {
if ($("#return div#" + currentClass).length == 0)
$("div#" + currentClass).first().clone().appendTo("#return");
});
}
});
});
Updated demo at: https://jsfiddle.net/zyech6L3/2/
you need to iterate through to check what classes are available.
like:
for(var j=0; j<myclass.length; j++){
$("div#" + myclass[j]).first().clone().appendTo("#return");
}
see this fiddle:
https://jsfiddle.net/c4soamkr/

Removing elements from a document in Javascript [duplicate]

This question already has answers here:
JavaScript DOM remove element
(4 answers)
Closed 8 years ago.
Working on building this to-do list app to learn JS better.
I am able to insert text into box and have it add a new div element to the page with the relevant text residing within the div. The code that adds the div to the page automatically applies the class .newItem then adds an incrementing id i0, i1, i2, etc each time it's clicked. Everything works without an issue. Now, I have been fiddling around with the code to be able to click a single div element and have it remove itself from the page, but can't seem to get it to work.
var iDN = 0;
//Function that adds a new div with a custom incrementing id number
document.getElementById('add_button').onclick = function () {
var taskName = document.getElementById('add_task').value; // Store the value in the textbox into a variable
document.querySelector('.shit_to_do').innerHTML += '<div class = "newItem" id = "i' + iDN + '"' + 'onclick = removeEl()' + '>' + taskName + '</div>';
iDN += 1;
};
document.getElementById('testing').onclick = function () {
var parentNode = document.querySelector('.shit_to_do');
parentNode.removeChild(parentNode.children[0]);
}
function removeEl() {
for (i = 0; i < iDN; i++) {
if (document.getElementById('i' + i).onclick) {
document.getElementById('i' + i).display.style = 'none';
}
alert(i);
}
}
The for loop was really some random option I was trying to figure out how things were working onclick for each div, but didn't get to far with that one.
tl;dr:
I want to add click events to the new divs added onto the page in a single, universal function.
The value of document.getElementById('i' + i).onclick will be null if you've not set a handler to this attribute/property, otherwise it will be a function. null is always falsy, a function is always truthy.
To remove your element, you'll either have to look at this or e.target where e is the click event, and then call the DOM method node.removeChild(child).
The "quick and dirty" solution is to pass this into removeEl and remove it that way,
// ...
document.querySelector('.shit_to_do').innerHTML += '<div class="newItem" id="i' + iDN + '" onclick="removeEl(this)">' + taskName + '</div>';
// ...
function removeEl(elm) {
elm.parentNode.removeChild(elm);
}
I also removed the strange spacing between attribute names and values in your HTML
A perhaps "cleaner" solution is to create your nodes and attach listeners all by using DOM methods
function createDiv(index, taskname) {
var d = document.createElement('div');
d.setAttribute('id', 'i' + index);
d.textContent = taskname;
return d;
}
function removeElm() {
this.parentNode.removeChild(this);
}
var iDN = 0;
document.getElementById('add_button').addEventListener('click', function () {
var taskName = document.getElementById('add_task').value,
list = querySelector('.shit_to_do'),
div = createDiv(iDN, taskName);
div.addEventListener('click', removeElm);
list.appendChild(div);
iDN += 1;
});
This way means the browser does not re-parse any HTML as it not use element.innerHTML, which is a dangerous property may destroy references etc (along with usually being a bit slower)
Helpful links
node.addEventListener
document.createElement
node.appendChild

Create multiple selection at the same time when using document.selection in Javascript

I've known how to use the document.selection to do the highlighting. For example
/* http://jsfiddle.net/4J2dy/ */
$("#content").on('mouseup', function() {
highlighting();
});
var highlighting = function () {
var seleted_str = (document.all) ? document.selection.createRange().text : document.getSelection();
if(seleted_str != "") {
var stringToBeHighlighted = seleted_str.getRangeAt(0);
var span = document.createElement("span");
span.style.cssText = "background-color: #80deea";
span.className = "MT";
stringToBeHighlighted.surroundContents(span);
}
};
But there is something I don't know how to achieve.
Let's say that I have four layers created with the same content at the same time.
And I would like to select a sentence on the controlling layer while all the same sentence in the other three layers will be selected too.(See image below)
After the selection, I would like to pop out a menu(which I can do), and get the DOM element based on which button is pressed.(See image below)
Could anyone tell me how to achieve this? Or it just can't be done? I would be grateful if someone could answer for me.
It's kind of possible, and I would appreciate the input of SO user Tim Down as he knows a lot about JS Range/Selections, but I'll present my partial solution already.
Instead of selecting the 4 layers, you could just store the startOffset & endOffset in an external object that is updated on mouseup. The only by-effect this has, is that the user's selection will only get the color of the span when they click a layer button.
The advantage is that you can now simply work with DOM Textnodes as opposed to ranges/ selection (more complex, to me anyway).
I've chosen to find the layers with a data-layer attribute on the buttons and a corresponding id on the layers themselves. I handled the 'appending' of the 'selected span' by slicing the text content of the text nodes in the layers, like so:
layer.innerHTML = txt.slice(0, selText.start)
+ '<span class="MT" style="background-color: #80deea">'
+ txt.slice(selText.start, selText.end) + '</span>'
+ txt.slice(selText.end, txt.length);
See it in action here. I've added a cleanSelection function so only one selection is possible at a time (the start & end counters fail because selection ranges don't take into account HTML tags, so you have to get rid of the spans).
Final notes:
The fiddle will not work in browsers not supporting getElementsByClassName
The fiddle only supports one selection at a time.
The fiddle does not extensively test all conditions (eg, whether the nodetype of the first child is truly a text node, etc. But it ain't hard to add that yourself)
Entire JS code as reference (also in fiddle):
// this object will hold the start & end offsets of selection value
var selText = false;
// selText will be updated on mouseup
document.body.onmouseup = getSelText;
// on button click, selText will be highlighted
document.body.onclick = function(e) {
var target = e.target || e.srcElement, range, layer, txt;
// only do if it's a layer button & the selection is non-empty
if (target.getAttribute('data-layer') && selText !== false) {
// first remove previous spans, they break the startOffset & endOffset of the selection range
cleanSelection();
// get the clicked layer
layer = document.getElementById(target.getAttribute('data-layer'));
// this assumes that the first node in the layer is a textNode
txt = layer.firstChild.nodeValue;
// let's append the selection container now
layer.innerHTML = txt.slice(0, selText.start)
+ '<span class="MT" style="background-color: #80deea">'
+ txt.slice(selText.start, selText.end) + '</span>'
+ txt.slice(selText.end, txt.length);
// ...and empty the 'real selection'
window.getSelection().collapse();
// log results to console
console.log('From char ' + selText.start + ' to char ' + selText.end + ', in ' + layer.id);
}
};
function getSelText () {
var seleted_str = (document.all) ? document.selection.createRange().text : document.getSelection(), stringToBeHighlighted;
if(seleted_str !== "") {
stringToBeHighlighted = seleted_str.getRangeAt(0);
selText = {
start: stringToBeHighlighted.startOffset,
end: stringToBeHighlighted.endOffset
};
} else {
selText = false;
}
}
function cleanSelection() {
var getText, mtSpan = document.getElementsByClassName('MT');
for ( var i = 0; i < mtSpan.length; i++) {
getText = mtSpan[i].innerHTML;
mtSpan[i].previousSibling.nodeValue = mtSpan[i].previousSibling.nodeValue + getText + mtSpan[i].nextSibling.nodeValue;
mtSpan[i].parentNode.removeChild(mtSpan[i].nextSibling);
mtSpan[i].parentNode.removeChild(mtSpan[i]);
}
}

Filtering divs based on classes, using jQuery

Looking for some help on how to write a function to filter out certain divs with certain classes.
Essentially I have thrown together a quick e-commerce example. There are lists of different filters, with values. There are then products. Each product div has a number of classes applied to it, e.g "green" or "adult" or "wool" - these are the filterable parameters.
Not being savvy at all with JS I'm trying to write something, but looking for some advice. Here is basically what I'm after:
Starts with displaying all
If user selects GREEN, all items that do not have GREEN attributed are display:none'd (with a fade transition
Rep #2 for any attribute checked
Notes: multiple attributes can be checked, when items are unchecked, everything needs to reappear.
Any help? I guess it's basically linking up the value of each checkbox to the class.
Not sure if there is a better way codewise to do this... data attributes maybe?
Working example of the code here (obviously no JS)
Updated your fiddle and added some jQuery to hide the divs where the classes don't match the selected checkboxes.
Demo: fiddle
JS is a bit verbose, you can refactor it further if you like:
$(document).ready(function() {
var allSelectedClasses;
allSelectedClasses = '';
$('input[type="checkbox"]').click(function(){
//ensure the correct classes are added to the running list
if(this.checked){
allSelectedClasses += '.' + $(this).val();
}else{
allSelectedClasses = allSelectedClasses.replace($(this).val(), '');
}
//format the list of classes
allSelectedClasses = allSelectedClasses.replace(' ', '');
allSelectedClasses = allSelectedClasses.replace('..', '.');
var selectedClasses;
var allSelected;
allSelected = '';
//format these for the jquery selector
selectedClasses = allSelectedClasses.split(".");
for(var i=0;i < selectedClasses.length;i++){
var item = selectedClasses[i];
if(item.length > 0){
if(allSelected.length == 0){
allSelected += '.' + item;
}else{
allSelected += ', .' + item;
}
}
}
//show all divs by default
$("div.prodGrid > div").show();
//hide the necessary ones, include the 2 top level divs to prevent them hiding as well
if(allSelected.length > 0){
$("div.prodGrid > div:not(" + allSelected + ")").hide();
}
});
});
I added a new class to your Colors ul. Hope that's okay.
Here's a crude version of a filtering function, it only takes colors into account so you have to modify it yourself to take everything into account but the basic outline is there.
It can be refactored massively! :)
Since you're using jQuery:
$('ul.colorFilter li input[type="checkbox"]').click(function(){
var checkedBoxes = $('ul.colorFilter li input[type="checkbox"]:checked');
var listOfClasses = [];
checkedBoxes.each(function(index, el){
listOfClasses.push(el.value);
});
if(listOfClasses.length >= 1){
$('div.prodGrid').children('div').hide();
for(var i = 0; i < listOfClasses.length; i++){
$('div.prodGrid > div.'+listOfClasses[i]).show();
}
} else {
$('div.prodGrid > div').show();
}
});
I made a fiddle as well:
http://jsfiddle.net/Z9ZVk/4/

Remove a child from parent and add it as Sibling to Parent

I have a Bullet Region like
HelloHowareyou
No i am selecting are and changing to Number Bullet.
so my list should change like
HelloHowareyou
want to end the Disc bullet after second child.
want to add Third child as sibling to parent.
Want to again make Disc bullet to fourth child and add it as sibling to parent.
How can i do this.
This is actually a non-trivial and very interesting problem. However, you need to first know a couple of things:
The bullets on a list item are determined by its list; ul is for unordered lists (ie. disk bullets), and ol is for ordered lists (ie. numbered bullets).
You cannot have an li without its parent being either ul or ol.
You cannot have a ul be a direct child of a ol or vice versa (they can be children of an li though, but then they'll be sublists)
This means that every time you switch a list, you need to make sure that the item you're switching has a parent of the correct (and opposite) type, and the items before and after it are also in (separate) lists of the correct type. You will in many cases need to create those lists (or delete them, when they become empty).
Anyway, words are worthless, here's the code (I'm using jQuery, but the idea should be the same regardless of what you use):
$('li').on('click', function () {
var $listItem = $(this);
var $list = $(this).parent();
var $items = $list.children();
var itemIndex = $items.index($listItem);
var numItems = $items.length;
var curType = $list.is('ul') ? 'ul' : 'ol';
var newType = curType === 'ul' ? 'ol' : 'ul';
var $prev = $list.prev();
var $next = $list.next();
if (itemIndex === 0) {
// The item we're switching is the first Item in the list
if (!$prev.is(newType)) {
$prev = $('<' + newType + '/>');
$prev.insertBefore($list);
}
$prev.append($listItem);
} else if (itemIndex === numItems - 1) {
// The item we're switching is the last Item in the list
if (!$next.is(newType)) {
$next = $('<' + newType + '/>');
$next.insertAfter($list);
}
$next.prepend($listItem);
} else {
// The item is in the middle, we need to split the current list into 3.
$tailList = $('<' + curType + '/>');
$tailList.append($listItem.nextAll());
$tailList.insertAfter($list);
$middleList = $('<' + newType + '/>');
$middleList.append($listItem);
$middleList.insertAfter($list);
}
if (numItems === 1) {
// list used to have only one Item, so it's now empty, and should be removed.
$list.remove();
if ($prev.is(newType) && $next.is(newType)) {
// The two surrounding lists are of the same type and should be merged.
$prev.append($next.children());
$next.remove();
}
}
});
I'm using the click event on a list item to switch a list item. Here's a jsFiddle link for you to play around with the implementation and validate that everything works as expected: http://jsfiddle.net/8Z9rf/
The code can definitely be optimized for speed/performance, but I was aiming for simplicity and clarity, I hope I managed to accomplish this.

Categories

Resources