JQuery add form elements iterate ids - javascript

I don't care about the content in the form, just want to add a new set of them every time I click add. I will however be adding some conditional fields. But with that in mind, do I actually need to iterate ids? And is the clone event the best way to handle this?
<div id="wrapper">
<div id="condition-1">
<select id="trigger-1" class="trigger">
<option value="0">Select...</option>
<option value="1">View Count</option>
<option value="2">Comment Count</option>
</select>
<select id="operator-1" class="operator">
<option value="0">Select...</option>
<option value="1">Is Greater Then</option>
<option value="2">Less Than</option>
</select>
<div id="input-1" class="input">
<input id="number-1"></input>
</div>
</div>
<div id="add-1" class="add">Add more</div>
I've been digging through a lot of examples of jQuery .clone() ,have not been able to apply any examples to a structure like this. My goal is to add a new set of form elements every time I click "Add more". I don't care if it clones the actual content, just want to clone and then iterate the id's appropriately.
I tried using something similar to:
var cur_num = 1;
var cloned = $("#condition-" + cur_num).clone(true, true).get(0);
++cur_num;
cloned.id = "condition-" + cur_num; // Change the div itself.
$(cloned).find("*").each(function(index, element) { // And all inner elements.
if(element.id)
{
var matches = element.id.match(/(.+)_\d+/);
if(matches && matches.length >= 2) // Captures start at [1].
element.id = matches[1] + "-" + cur_num;
}
});
$(cloned).appendTo($("#condition-wrapper"));*/
The above block of code will clone and append the block I want. But as it iterates the first condition id to #condition-2, every block after is coming up #condition-2 and I have no idea how to change the children of condition-1 to trigger-2 operator-2. Any help is appreciated, thank you!

try this
$(function(){
$('.add').bind('click',function(){
var $this=$(this),
$cloned=$this.prev().clone(),
re=/(\d+)$/,
counter=1,
cid=$cloned.attr('id').replace(re,function(n){
return counter+=parseInt(n);
});
$cloned.attr({id:cid})
.find('[id]')
.each(function(i,o){
var iid=$(o).attr('id').replace(re,counter);
$(o).attr({id:iid});
})
.end()
.insertBefore($this);
});
})

Related

Parse HTML without IDs or classes

Is it possible to use Javascript to parse html without IDs or classes?
For example I want to delete everything from "Starting date" to "Starting time"
<div class="timer">
<!--Starting date-->
<select>
<option value="1"></option>
<option value="2"></option>
</select>
<!--Starting time-->
<select>
<option value="1"></option>
<option value="2"></option>
</select>
<!--more select tags here>
</div>
This work after making the nodelist static
let hideDate = true; // your condition
const startText = "Starting date",
endText = "Starting time";
if (hideDate) {
let i = 0;
const nodes = [...document.querySelector(".timer").childNodes]; // make list static
for (; i < nodes.length; i++) {
if (nodes[i].nodeType === 3 &&
nodes[i].textContent.trim() === startText) break;
}
for (; i < nodes.length; i++) {
if (nodes[i].nodeType === 3 &&
nodes[i].textContent.trim() === endText) break;
nodes[i].remove();
}
}
<div class="timer">
Starting date
<select>
<option value="1"></option>
<option value="2"></option>
</select>
Starting time
<select>
<option value="1"></option>
<option value="2"></option>
</select>
</div>
i think you can do something like this:
you have to select this element from javaScript for example:
var elementsContainer = document.getElementsByClassName/TagNames('element class or element tag name');
this will return you a list of elements, so you need to get one of them, like this:
elementsContainer = elementsContainer[0]
this will give you one element, so after this, you can delete its content like this
elementsContainer.innerHTML/innerText = '';
you can choose one of this property innerHTML or innerText according to which you need
Well for your problem. I would suggest to have a look at the concepts of jquery remove(), and jquery empty(). here are some working test demos of these two methods.
<script>
$(document).ready(function(){
$(".timer select").remove();
});
</script>
The above code will completely remove the elements inside the div element. see here Jquery Remove Wroking Example
<script>
$(document).ready(function(){
$(".timer").empty();
});
</script>
This method removes not only child (and other descendant) elements, but also any text within the set of matched elements
Jquery Empty Working Example
Similar to .empty(), the .remove() method takes elements out of the DOM. Use .remove() when you want to remove the element itself, as well as everything inside it.
If you want to do it with core javascript then have a look at it.
Jquery Remove method using core JS
let timer = document.querySelector('.timer')
const from = 'Starting date'
const to = 'Starting time'
let html = timer.innerHTML
timer.innerHTML = html.slice(0, html.search(from)) + html.slice(html.search(to))
<div class="timer">
Starting date
<select>
<option value="1">1</option>
<option value="2">2</option>
</select>
Starting time
<select>
<option value="1">1</option>
<option value="2">2</option>
</select>
</div>

Selecting dynamically added elements by class name

I have am HTML code which for simplicity looks like this:
<div class="main-container">
<div class="group-area group1" id="group1">
<select class="slct" id="slct1">
<option>Group A</option>
<option>Group B</option>
<option>Group C</option>
</select>
<div class="participant-area">
<!-- empty, can be filled with "<div class='participant'></div>" elements -->
</div>
</div>
<button class="add-group">Show another group</button>
</div>
In the above inteface, the user can select the name of the group from the select drop down, and the the participants of that group will be shown in the 'participant-area'. They will be drawn from a presaved list, and will be added using jQuery append:
<script>
$(document).on('change', '.slct', function() {
var number = $(this).attr("id").charAt(4); //gets the number '1' from the id name
var key = $(this).find("option:selected").val(); //gets the value to be used later
var constructedClass = ".group" + number; //result: "group1"
presavedList.forEach(participant => {
$(constructedClass + " .participant-area") //selecting participant area that is inside group1
.append($("<div>").addClass("participant")
.append($("<h2>").text(participant.name))
);
}
})
</script>
However, user can also click on the 'add-group' button at the end of the main container, and have another area just like the first one displayed, that can be used to see participants of a different group. But this time, the classes will be group2 instead of group1, slct2 instead of slct1, and so on. This is done by having a global variable that is incremented whenever the button is clicked:
<script>
var areaNumber = 1;
$(".add-group").click(function () {
areaNumber++;
$(".main-container")
.append($("<div>").addClass( "group"+areaNumber).addClass("group-area").attr("id", "group"+ areaNumber)
.append($("<select>")) //etc... Reconstruct the same one as original
.append($("<div>")) //etc... Reconstruct the same one as original
});
</script>
My problem is related selecting the groupN class of the dynamically created elements (like group2, group3, etc). In the first function above - after a second area has been created and its select value changed - the change is being detected normally and the $(document).on('change', '.slct', function() {...}) is being fired normally. However, the 5th line in that function:
$(constructedClass + " .participant-area").append(//etc)
is not working: the constructedClass is not being detected by the function, even though it exists in the time of firing it - but I believe it's not being detected because it was not present at the time of initial parsing of javascript. Is that correct? Is there any way to solve this? (Be able to select dynamically generated elements by their uniquely generated class names?).
Thank you for reading this far and for any help you can offer.
Do not use incremental id and class attributes. It is an anti-pattern. It makes your code needlessly complex, more verbose, and difficult to maintain.
A much better solution is to group common elements by behaviour using a single class attribute. That way you can use DOM traversal to relate them to each other. It also allows you to clone() content (as it's all identical) without the need to spaghetti-fy your JS by filling it with HTML.
With that said, try this:
let presavedList = [{ name: 'Foo bar' }, { name: 'Lorem ipsum' }]
$(document).on('change', '.slct', function() {
var html = presavedList.map(item => `<div class="participant"><h2>${item.name}</h2></div>`);
$(this).next('.participant-area').html(html);
});
$(".add-group").click(function() {
var $clone = $('.group:first').clone();
$clone.find('select').val('');
$clone.find('.participant-area').empty();
$clone.appendTo('.main-container');
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="main-container">
<div class="group-area group">
<select class="slct">
<option value="">Please select...</option>
<option>Group A</option>
<option>Group B</option>
<option>Group C</option>
</select>
<div class="participant-area"></div>
</div>
<button class="add-group">Show another group</button>
</div>

When registering both onchange and onclick on a select, the click event is triggered twice

Goal: Have a select whose option have nested structure when user clicks on the select, but when user selects an option the option should be displayed "normally" (ie with no leading spaces).
Attempted solution using JS and Jquery: My JS is far from sophisticated so I apologize in advance :)
I attempted to use .on("change") and .on("click") to change the selected option value (by calling .trim() since I achieve the "nested" structure with ). I'm also storing the original value of the selected option because I want to revert the select menu to its original structure in case the user selects another option.
The problem: The function registered for .on("click") is called twice, thus the select value immediately resets itself to its original value.
I suspect there is a much, much easier solution using CSS. I will be happy to accept an answer that will suggest such solution.
JSFiddle: https://jsfiddle.net/dv6kky43/9/
<form>
<select id="select">
<option value=""></option>
<option value="a"> a</option>
<option value="b"> b</option>
</select>
</form>
<textarea id="output"/>
var orig;
var output = $("#output");
output.val("");
function onDeviceSelection(event){
output.val(output.val() + "\nonDeviceSelection");
var select = event.target;
orig = select.selectedOptions[0].text;
select.selectedOptions[0].text = select.selectedOptions[0].text.trim()
}
function resetDeviceSelectionText(event) {
output.val(output.val() + "\nresetDeviceSelectionText");
var select = event.target;
if (orig !== undefined){
select.selectedOptions[0].text = orig;
}
}
$("#select").on("change", onDeviceSelection);
$("#select").on("click", resetDeviceSelectionText);
If you are already using jQuery, why not utilize data function to store the original value. This way you will also be able to specify different nest levels.
(function($){
$(document).on('change', 'select', function(event) {
$(this).find('option').each(function(index, element){
var $option = $(element);
// Storing original value in html5 friendly custom attribute.
if(!$option.data('originalValue')) {
$option.data('originalValue', $option.text());
}
if($option.is(':selected')) {
$option.html($option.data('originalValue').trim());
} else {
$option.html($option.data('originalValue'));
}
})
});
})(jQuery);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form>
<select id="select">
<option value=""></option>
<option value="a"> a</option>
<option value="b"> b</option>
</select>
</form>
Once caveat I see is, the selected option will appear trimmed on the list as well, if dropdown is opened after a previous selection has been made:
Will it still work for you?
Instead of keeping the state of the selected element i would simply go over all options and add the space if that option is not selected:
function onDeviceSelection(event){
// Update textarea
output.val(output.val() + "\nonDeviceSelection");
// Higlight the selected
const {options, selectedIndex} = event.target;
for(let i = 0; i < options.length; i++)
options[i].innerHTML = (i === selectedIndex ? "":" ") + options[i].text.trim();
}
$("#select").on("change", onDeviceSelection);
Note that you need to use innerHTML to set the whitespace...

Only first input field automatically being filled in by JQuery function

I'm trying get a form to fill in inputs automatically when a user puts in information in the footage field which should then be plugged into an equation with JQuery and then the answer should be output in postQuantity. Since the user can add inputs the form is set up that each footage should go with a corresponding postQuantity signified by its suffix number. So that footage2 should be used to find postQuantity2 and 3 for 3 and so on. The problem is that only the first field is automatically filling in and that anytime a different footage class is changed nothing happens. Any insight on where I went wrong and advice on how to fix it will be greatly appreciated! Thanks! Here is a JSFiddle - http://jsfiddle.net/gv0029/TwH5n/
HTML:
<form>
<fieldset id="fence">
<div class="inputFence">
<fieldset class="fenceDescripton">
<legend><strong>Fence Description</strong></legend>
<label>Footage<input name="footage_1" class="footage" /></label>
<select name="fenceHeight_1" class="fenceHeight">
<!--got rid of "select" from the value. not needed at all-->
<option value="">Select Fence Height</option>
<!--got rid of the ids completely. the numbers from the values are all you need-->
<option value="6">6 Ft.</option>
<option value="8">8 Ft.</option>
</select>
</fieldset>
<fieldset class="post">
<legend><strong>Post Type</strong>
</legend>
<label>Post Quantity:
<input type="postQuantity" name="postQuantity_1" class="postQuantity" value="" />
</label>
<select name="postMeasurements_1" class="postMeasurements">
<option value="">Select Post Measurements</option>
<option value="23/8 x .065 x 8">2 3/8 x .065 x 8</option>
<option value="23/8 x .095 x 8">23/8 x .095 x 8</option>
</select>
</fieldset>
</div>
</fieldset>
<div>
<input type="button" id="btnAddFence" value="Add Another Fence" />
<input type="button" id="btnDelFence" value="Remove Fence" />
</div>
</form>
JS:
//Quantity for Posts
$("[class^='footage']").bind('keypress keydown keyup change', function(){
var footage = parseFloat($(this).val(),10);
var total = '';
var parts = $(this).attr('name').split("_");
var fenceNumber = parts[1];
if(!isNaN(footage)){
total = Math.ceil(footage /7);
$(":input[name='postQuantity_" + fenceNumber + "'" + ']').val(total.toString());
} else {
$(":input[name='postQuantity_" + fenceNumber + "'" + ']').val("");
}
});
//Dynamic Fence Input Fields
$('#btnAddFence').click(function () {
// create the new element via clone()
var newElem = $('.inputFence:last').clone();
// insert the new element after the last "duplicable" input field
$('.inputFence:last').after(newElem);
// enable the "remove" button
$('#btnDelFence').removeAttr('disabled');
//get the input name and split into array (assuming your clone is always last)
var parts = $('.fenceHeight:last').attr('name').split("_");
//change the second element of the array to be one higher
parts[1]++;
//join back into a string and apply to the new element
$('.fenceHeight:last').attr('name', parts.join("_"));
//do the same for other two inputs
parts = $('.postQuantity:last').attr('name').split("_");
parts[1]++;
$('.postQuantity:last').attr('name', parts.join("_"));
parts = $('.postMeasurements:last').attr('name').split("_");
parts[1]++;
$('.postMeasurements:last').attr('name', parts.join("_"));
parts = $('.footage:last').attr('name').split("_");
parts[1]++;
$('.footage:last').attr('name', parts.join("_"));
// business rule: you can only add 5 names
//if (newNum == 5)
//$('#btnAdd').attr('disabled','disabled');
});
$('#btnDelFence').click(function () {
//remove the last inputFence
$('.inputFence:last').remove();
// if only one element remains, disable the "remove" button
if ($('.inputFence').length == 1) $('#btnDelFence').attr('disabled', 'disabled');
});
$('#btnDelFence').attr('disabled', 'disabled');
I think you want somethign like this:
http://jsfiddle.net/TwH5n/3/
the line I changed was:
$(document.body).on('keydown', '[class^="footage"]'
this binds the event to all future '[class^="footage"]' elements
ideally you would not do this on body since its not efficient. find a closer parent, or attach the event to each clone upon creation.
The jsfiddle is wrong
Anyway, you are using .bind method which is bind event handlers to currently existing elements. You can use .live method which is working for selector instead of elements, but this is deprecated in version 1.7, and removed in 1.9
As of http://api.jquery.com/bind/
For more flexible event binding, see the discussion of event
delegation in .on() or .delegate().
Hope this helps
P.S. Right example without using deprecated methods
$('form').on('keypress keydown keyup change',"[class^='footage']", function(){});
http://jsfiddle.net/TwH5n/7/

Append to a div with a recurring class

I have a piece of JS that appends the following to a web form on the click of a button. When the div .remove is clicked the closest new-attribute div is removed. It works fine.
<div class="new-attribute">
<h3>New Attribute</h3>
<label for="attributeName3">Name:</label>
<input class"attribute"="" type="text" name="attributeName3">
<label for="attributeType3">Type:</label>
<select id="t" class="attribute" name="attributeType3">
<option value="text" selected="">Text</option>
<option value="checkbox">Checkbox</option>
<option value="select-list">Select Option List</option>
<option value="notes">Notes</option>
</select>
<div class="option"></div>
<div class="remove">Delete</div>
</div>
In the div "option" I have code to add another form field on the selection of "select-list" in the select input. It does not work. I do not know why. No variable names are clashing. I'm away I shouldnt give the select an id because it is recurrent, I just want to get it working before I make it compliant.
Heres the Js that I'm having trouble with:
//select temp
var select="<div class=\"new-option\">"
+ "<h3>new option</h3>"
+ "<label for=\"attributeName"+count+"\">New otion:</label>"
+ "<input class\"attribute\" type=\"text\" name=\"attributeName"+count+"\">"
+ "</div>";
//get value of select
$('#t').change(function() {
var selectVal = $('#t :selected').val();
if (selectVal == "select-list") {
$(this).closest('.option').append(select);
}
});
The code works with $(select).appendTo('.option');
However it appends the code to every instance of the "option" class on the page. I only want it to append to the current or closest one.
Thank you in advance
The problem is because closest() looks up the DOM for the nearest parent element matching the selector, but .option is a sibling of #t. Try using next() instead:
$('#t').change(function() {
if ($(this).val() == "select-list") {
$(this).next('.option').append(select);
}
});
Try this
http://jsfiddle.net/7WC4G/1/
$('#bob').change(function(){
$('#nuform').append('<p>your new form here</p>');
});
Avoid using next, prev as it could fail if you change the order.
Instead try using siblings instead of closest in your code:
$(this).siblings('.option').append(select);
Read more about it here: http://api.jquery.com/siblings/

Categories

Resources