I have a dropdown that I want to be cloned and have a unique id. I managed to do it and it works on my website.
I am trying to make a "x" button to removed added clones and I can't get it to work.
The javascript:
var counter = 1;
function addInput(divName, template){
if (counter == 5) {
document.getElementById("add_more_text").remove();
} else {
var newdiv = document.createElement('div');
newdiv.innerHTML = document.getElementById(divName).innerHTML;
document.getElementById(template).appendChild(newdiv);
counter++;
}
var selectElements = document.querySelectorAll('select');
for (var i = 0; i < selectElements.length; i++){
selectElements[i].id = 'id-' + i;
selectElements[i].name = 'category' + i;
}
}
function removeInput(divName, template){
document.getElementById(template).removeChild(divName);
counter--;
}
The html:
<div id="template">
<select name="category0"><option>hi</option></select>
x
</div>
<div id="add_more"></div>
+ Add more
DEMO
Any help is much appreciated!
Simpler to modify remove function as follows:
function removeInput(obj) {
if (obj.parentNode.className == 'added') {
obj.parentNode.parentNode.removeChild(obj.parentNode);
counter--;
}
}
And have a link in template like this:
x
Class added is to distinguish new clones that can be removed:
newdiv.className = 'added';
Demo: http://jsfiddle.net/rjXXa/2/
in your onClick property
x
you are passing template and add_more
And in the handler function
function removeInput(divName, template){
the parameters are in a different order, so divName will contain 'template' and template will contain 'add_more'. Even if you fix this,
document.getElementById(template).removeChild(divName); // will throw error
because the div#add_more is not a child of div#template.
For fixing this, you need to pass a reference to the clicked element, like the following
x
and in your function
function removeInput(anchor){
var clone = anchor.parentNode; // div containing the anchor
if(clone.id!='template'){ // make sure we're not removing the original template
clone.parentNode.removeChild(clone);
counter--;
}
}
as in this Fiddle
Update
It's better to remove the add more option from display using css and make it visible later than removing/appending it in DOM, as follows
change the following in addInput() function
if (counter > 4) {
document.getElementById("add_more_text").style.display='none';
}
and in removeInput() function add
if (counter < 5) {
document.getElementById("add_more_text").style.display='block';
}
as in this Fiddle
Related
I have a large button on the left and a form on the right. When you click the button on the left it allows you to create 6 forms in total. That is the set limit and you can not go over it.
Problem #1 - If you select the X icon to delete one of the clones. And then begin adding more clones again. It breaks the set clone limit 1-6 and allows you to create infinite clones.
Problem #2 - How do i remove the X Icon from the first/initial form and only have it "allowed" on its clones.
Thanks!
JS FIDDLE
JS
var clones_limit = 5;
var cloned_nbr = $(".clonedInput").length-1; //Exclude Default (first) div
function clone()
{
if(cloned_nbr<clones_limit)
{
cloned_nbr++;
var new_clone = $(".clonedInput").first().clone();
$("#formy").append(new_clone);
rearrange();
}
}
function remove(){
$(this).parents(".clonedInput").remove();
cloned_nbr--;
rearrange();
}
function rearrange(){
var count = 1;
var totalCount = $(".clonedInput").length;
$(".clonedInput").each(function(){
$(this).attr("id", "clonedInput"+count).find(".label-nbr").text(count).end().
find(".category").attr("id","category"+count).end().find(".remove").toggle(totalCount!=1).attr("id","remove"+count).on("click", remove);
count++;
});
}
$(".clone").on("click", clone);
$(".remove").on("click", remove);
The problem was with the way you were attaching click event for .remove element. since you were attaching it on create, it used to trigger remove for all the elements recreated and hence decreasing the count of cloned_nbr back to 0. So just remove the click event attaching and make use of event delegation.
Faulty code
function rearrange() {
var count = 1;
var totalCount = $(".clonedInput").length;
$(".clonedInput").each(function() {
$(this).attr("id", "clonedInput" + count).find(".label-nbr").text(count).end()
.find(".category").attr("id", "category" + count).end().find(".remove")
.toggle(totalCount != 1).attr("id", "remove" + count).on("click", remove);
//^^^this was causing the issue
count++;
});
}
Below is the changes, you need to do.
Updated Code
function rearrange() {
var count = 1;
var totalCount = $(".clonedInput").length;
$(".clonedInput").each(function() {
$(this).attr("id", "clonedInput" + count).find(".label-nbr").text(count).end()
.find(".category").attr("id", "category" + count).end().find(".remove")
.toggle(totalCount != 1).attr("id", "remove" + count);
//^^^No need to attach here
count++;
});
}
$(document).on("click", ".remove", remove);//Event delegation
UPDATED FIDDLE HERE
UPDATE - 1
For Problem - 2 Just add below CSS
div[id^="clonedInput"]:first-child .remove{
display:none;
}
UPDATED FIDDLE 2
I have a lot of click handler functions which are almost (textually and functionally) identical. I've got a menu with maybe 10 items in it; when I click on an item, the click handler simply makes one div visible, and the other 9 div's hidden. Maintaining this is difficult, and I just know there's got to be a smart and/or incomprehensible way to reduce code bloat here. Any ideas how? jQuery is Ok. The code at the moment is:
// repeat this function 10 times, once for each menu item
$(function() {
$('#menuItem0').click(function(e) {
// set 9 divs hidden, 1 visble
setItem1DivVisible(false);
// ...repeat for 2 through 9, and then
setItem0DivVisible(true);
});
});
// repeat this function 10 times, once for each div
function setItem0DivVisible(on) {
var ele = document.getElementById("Item0Div");
ele.style.display = on? "block" : "none";
}
Create 10 div with a class for marking
<div id="id1" class="Testing">....</div>
<div id="id2" class="Testing">....</div>
<div id="id3" class="Testing">....</div>
and apply the code
$('.Testing').each(function() {
$(this).click(function() {
$('.Testing').css('display', 'none');
$(this).css('display', 'block');
}
}
$(document).ready(function (){
$("div").click(function(){
// I am using background-color here, because if I use display:none; I won't
// be able to show the effect; they will all disappear
$(this).css("background-color","red");
$(this).siblings().css("background-color", "none");
});
});
Use .siblings() and it makes everything easy. Use it for your menu items with appropriate IDs. This works without any for loops or extra classes/markup in your code. And will work even if you add more divs.
Demo
Fiddle - http://jsfiddle.net/9XSJW/1/
It's hard to know without an example of the html. Assuming that there is no way to traverse from the menuItem to ItemDiv - you could use .index and .eq to match up the elements based on the order they match with the selector.
var $menuItems = $("#menuItem0, #menuItem1, #menuItem2, ...");
var $divs = $("#Item0Div, #Item1Div, #Item2Div, ...");
$menuItems.click(function(){
var idx = $(this).index();
// hide all the divs
$divs.hide()
// show the one matching the index
.eq(idx).show();
})
Try
function addClick(i) {
$('#menuItem'+i).click(function(e) {
// set nine divs hidden, 1 visble
for( var j = 0; j < 10; ++j ) {
var ele = document.getElementById("Item"+j+"Div");
ele.style.display = (i == j ? "block" : "none");
}
});
}
// One click function for all menuItem/n/ elements
$('[id^="menuItem"]').on('click', function() {
var id = this.id; // Get the ID of the clicked element
$('[id^="Item"][id$="Div"]').hide(); // Hide all Item/n/Div elements
$('#Item' + id + 'Div').show(); // Show Item/n/Div related to clicked element
});
Obviously this would be much more logical if you were using classes instead:
<elem class="menuItem" data-rel="ItemDiv-1">...</elem>
...
<elem class="ItemDiv" id="ItemDiv-1">...</elem>
$('.menuItem').on('click', function() {
var rel = $(this).data('rel'); // Get related ItemDiv ID
$('.ItemDiv').hide(); // Hide all ItemDiv elements
$('#' + rel).show(); // Show ItemDiv related to clicked element
});
Save the relevant Id's in an array - ["Item0Div", "Item1Div", ...]
Create a generic setItemDivVisible method:
function setItemDivVisible(visible, id) {
var ele = document.getElementById(id);
ele.style.display = visible ? "block" : "none";
}
And set your click handler method to be:
function(e) {
var arrayLength = myStringArray.length;
for (var i = 0; i < idsArray.length; i++) {
setItemDivVisible(idsArray[i] === this.id, idsArray[i]);
}
}
I think this will do the trick
I have a snippet of code that applies a highlighting effect to list items in a menu (due to the fact that the menu items are just POST), to give users feedback. I have created a second step to the menu and would like to apply it to any element with a class of .highlight. Can't get it to work though, here's my current code:
[deleted old code]
The obvious work-around is to create a new id (say, '#highlighter2) and just copy and paste the code. But I'm curious if there's a more efficient way to apply the effect to a class instead of ID?
UPDATE (here is my updated code):
The script above DOES work on the first ul. The second ul, which appears via jquery (perhaps that's the issue, it's initially set to hidden). Here's relevant HTML (sort of a lot to understand, but note the hidden second div. I think this might be the culprit. Like I said, first list works flawlessly, highlights and all. But the second list does nothing.)?
//Do something when the DOM is ready:
<script type="text/javascript">
$(document).ready(function() {
$('#foo li, #foo2 li').click(function() {
// do ajax stuff
$(this).siblings('li').removeClass('highlight');
$(this).addClass('highlight');
});
//When a link in div is clicked, do something:
$('#selectCompany a').click(function() {
//Fade in second box:
//Get id from clicked link:
var id = $(this).attr('id');
$.ajax({
type: 'POST',
url: 'getFileInfo.php',
data: {'id': id},
success: function(msg){
//everything echoed in your PHP-File will be in the 'msg' variable:
$('#selectCompanyUser').html(msg)
$('#selectCompanyUser').fadeIn(400);
}
});
});
});
</script>
<div id="selectCompany" class="panelNormal">
<ul id="foo">
<?
// see if any rows were returned
if (mysql_num_rows($membersresult) > 0) {
// yes
// print them one after another
while($row = mysql_fetch_object($membersresult)) {
echo "<li>"."".$row->company.""."</li>";
}
}
else {
// no
// print status message
echo "No rows found!";
}
// free result set memory
mysql_free_result($membersresult);
// close connection
mysql_close($link);
?>
</ul>
</div>
<!-- Second Box: initially hidden with CSS "display: none;" -->
<div id="selectCompanyUser" class="panelNormal" style="display: none;">
<div class="splitter"></div>
</div>
You could just create #highlighter2 and make your code block into a function that takes the ID value and then just call it twice:
function hookupHighlight(id) {
var context = document.getElementById(id);
var items = context.getElementsByTagName('li');
for (var i = 0; i < items.length; i++) {
items[i].addEventListener('click', function() {
// do AJAX stuff
// remove the "highlight" class from all list items
for (var j = 0; j < items.length; j++) {
var classname = items[j].className;
items[j].className = classname.replace(/\bhighlight\b/i, '');
}
// set the "highlight" class on the clicked item
this.className += ' highlight';
}, false);
}
}
hookupHighlight("highliter1");
hookupHighlight("highliter2");
jQuery would make this easier in a lot of ways as that entire block would collapse to this:
$("#highlighter1 li, #highlighter2 li").click(function() {
// do ajax stuff
$(this).siblings('li').removeClass('highlight');
$(this).addClass('highlight');
});
If any of the objects you want to click on are not initially present when you run this jQuery code, then you would have to use this instead:
$("#highlighter1 li, #highlighter2 li").live("click", function() {
// do ajax stuff
$(this).siblings('li').removeClass('highlight');
$(this).addClass('highlight');
});
change the replace in /highlight/ig, it works on http://jsfiddle.net/8RArn/
var context = document.getElementById('highlighter');
var items = context.getElementsByTagName('li');
for (var i = 0; i < items.length; i++) {
items[i].addEventListener('click', function() {
// do AJAX stuff
// remove the "highlight" class from all list items
for (var j = 0; j < items.length; j++) {
var classname = items[j].className;
items[j].className = classname.replace(/highlight/ig, '');
}
// set the "highlight" class on the clicked item
this.className += ' highlight';
}, false);
}
So all those guys that are saying just use jQuery are handing out bad advice. It might be a quick fix for now, but its no replacement for actually learning Javascript.
There is a very powerful feature in Javascript called closures that will solve this problem for you in a jiffy:
var addTheListeners = function (its) {
var itemPtr;
var listener = function () {
// do AJAX stuff
// just need to visit one item now
if (itemPtr) {
var classname = itemPtr.className;
itemPtr.className = classname.replace(/\bhighlight\b/i, '');
}
// set the "highlight" class on the clicked item
this.className += ' highlight';
itemPtr = this;
}
for (var i = 0; i < its.length; i++) {
its[i].addEventListener ('click', listener, false);
}
}
and then:
var context = document.getElementById ('highlighter');
var items = context.getElementsByTagName ('li');
addTheListeners (items);
And you can call add the listeners for distinct sets of doc elements as many times as you want.
addTheListeners works by defining one var to store the list's currently selected item each time it is called and then all of the listener functions defined below it have shared access to this variable even after addTheListeners has returned (this is the closure part).
This code is also much more efficient than yours for two reasons:
You no longer iterate through all the items just to remove a class from one of them
You aren't defining functions inside of a for loop (you should never do this, not only for efficiency reasons but one day you are going to be tempted to use that i variable and its going to cause you some problems because of the closures thing I mentioned above)
I'm trying to write a form builder where users can generate a signup form. I need to limit the amount of items that the user can create however they also need to delete the items.
Originally I had
var limit = 5;
var counter = 0;
if (counter == limit) {
However when the user deleted items the counter remained the same and so they couldnt replace the deleted form element with a new item. So what I want to do is count how many items are currently active. I tried to do this by giving each new element a class (.kid) and then counting the amount of divs with that class but it didnt work.
Could anyone point me in the right direction? This is what I have so far however it doesn't work.
var limit = 6;
var num = $('.kid').length;
function addAllInputs(divName, inputType){
if (num == limit) {
alert("You have all ready added 6 form items");
}
else {
var newdiv = document.createElement('div');
newdiv.setAttribute('id', 'child' + (counter + 1));
newdiv.setAttribute('class', 'kid' );
Cheers all!
You need to capture the current counter in a closure. Decrease the counter when the user deletes an item and increase it after an item is created. Your code sample doesn't reveal how you handle the deletion, but I'll try to illustrate what I mean with a small code snippet:
$(document).ready(function () {
var limit = 5;
var counter = $('.kid').length;
$('#triggers_delete').click(function () {
/* delete the item */
counter--;
});
$('#triggers_creation').click(function () {
if (counter == limit) {
alert('Limit reached');
return false;
}
/* somehow determine divName and inputType
and create the element */
addAllInputs(divName, inputType);
counter++;
});
});
function addAllInputs(divName, inputType) {
/* just create the item here */
}
Is there any reason an approach like this won't work? Every time you go to add a new DIV, the length of the current collection is examined to see if it meets or exceeds the limit. Of course, you may need to refine the scope of your selector if there could be other DIVs of the form with the same class ID.
var limit = 6;
function addAllInputs(divName, inputType){
if ( $('.kid').length >= limit ) {
alert("You have all ready added 6 form items");
}
else {
var newdiv = document.createElement('div');
newdiv.setAttribute('id', 'child' + (counter + 1));
newdiv.setAttribute('class', 'kid' );
}
Edit: Just a note, I am assuming that you are either removing the deleted items from the DOM or differentiating them from active items with a different class or attribute. Otherwise, the approach I suggested will return a count that includes the deleted items as well.
The only real issue is that your num variable is being defined outside of the function. It will get the number of .kid elements at the time the page loads and will not update. Simply move this line inside the function:
var limit = 6;
function addAllInputs(divName, inputType){
var num = $('.kid').length;
...
Try this
var limit = 6;
function addAllInputs(divName, inputType){
if ($('.kid').length == limit) {
alert("You have all ready added 6 form items");
}
else {
var newdiv = $('div', { 'id': 'child' + (counter + 1), 'class': 'kid' } );
$("inputContainerSelector").append(newdiv);
}
this looks simple enough but I just can't get it to work. I could add DOM elements but I just can't remove them when using an array.
<script language="javascript">
fields = 0;
count = 0;
function addInput() {
if (fields != 10) {
var htmlText = "<input type='search' value='' name='field[]' />";
var remButton = "<input type='button' value='del' onclick='remove()' />";
var newElement = document.createElement('div');
newElement.id = 'SomeID'+fields;
newElement.innerHTML = htmlText + remButton + newElement.id;
var fieldsArea = document.getElementById('text');
fieldsArea.appendChild(newElement);
fields += 1;
} else {
...
}
count++;
}
// NEED HELP FROM HERE PLEASE
// You don't need to Loop, just get the button's id and remove that entire 'SomeID'
function remove() {
fieldsArea = document.getElementById('text');
fieldsArea.removeChild(SomeID1); <-----------------------THIS WORKS!
fieldsArea.removeChild(SomeID+count); <------------------THIS JUST WOULDN'T
count--;
}
</script>
In the remove function, writing SomeID1 works and delete the first added element but when I try to use a 'count', I just can't delete my 'elements'.
Any help would be most appreciated.
Thank you!
You have to get a reference to the element first. Currently you are passing an undefined variable SomeID to the function.
E.g.:
var element = document.getElementById('SomeID' + fields);
// or starting by zero: var element = document.getElementById('SomeID0');
element.parentNode.removeChild(element);
If you want to remove the div for which the button was clicked, you have to pass a reference to the corresponding div to the remove function.
'<input type="button" value="del" onclick="remove(this.parentNode)" />';
this will refer to the button and as it is a child of the div, this.parentNode refers to that div.
You also have to change your function to accept the element that should be removed:
function remove(element) {
element.parentNode.removeChild(element);
count--;
}
You probably also have to update fields, but I'm not sure how your code is supposed to work.
If you want to remove all of them you have to loop:
for(;fields--;) {
var element = document.getElementById('SomeID' + fields);
element.parentNode.removeChild(element);
}
Also have a look at the documentation of removeChild.
The removeChild needs a node (DOM element) as parameter. In this case
fieldsArea.removeChild(SomeID+count);
you could for example pass the node this way
fieldsArea.removeChild(document.getElementById('SomeID'+count));