new here and would like some help.
I'm working on a project and where I created some elements with dynamically generated ids but I can't tailor my existing code to work with dynamic ids.
Here is my HTML:
And here is my working JS script:
$('#headline1').keyup(updateCount).keydown(updateCount);
document.getElementById("headline1").addEventListener("change", updateCount);
function updateCount() {
var cs = $(this).val().length;
$('#countHeadline1').text(cs);
cs = $(this).val();
$('#headlineText1').text(cs);
}
And here is what I tried among other solutions but nothing seems to work:
var c = 1,
var headline1 = "headline1" + c;
var countHeadline1 = "countHeadline1" + c;
var headlineText1 = "headlineText1" + c;
$('#' + headline1).keyup(updateCount).keydown(updateCount);
document.getElementById("headline1" + c).addEventListener("change", updateCount);
function updateCount() {
var cs = $(this).val().length;
$('#' + countHeadline1).text(cs);
cs = $(this).val();
$('#' + headlineText1).text(cs);
}
c += 1;
Thank you,
EDIT: here is the code that create the element on click on a button:
jQuery('#add').on('click', function() {
c += 1;
jQuery('#parent').append('<hr><div>Headline 1:<br /><div><INPUT TYPE="text" placeholder="Headline" onkeydown="javascript:stripspaces(this)" id="headline1'+ c +'" maxlength="30" style="width: 350px;float:left;min-height: 32px;"><span id="countHeadline1'+ c +'" class="adCounter" style="color: rgb(255, 255, 255);float:left;position: inherit;margin-left: 0px;">0</span></div></div>');
});
An alternative is using Event Delegation for dynamically created elements.
Another approach is the feature data-attributes along with the function $data which stores useful information, in this case, the current id of the created element.
Additionally, this approach uses the CSS class inputs to identify the textfields and bind the event input to execute the function updateCount for any changes on those textfields.
function updateCount() {
var identifier = $(this).data('identifier'); //'data attribute' along with function 'data'.
$('#countHeadline1' + identifier).text($(this).val().length);
$('#headlineText1' + identifier).text($(this).val());
}
var c = 1;
$('#add').on('click', function() {
c += 1;
$('#parent').append('<br><br><br>Headline ' + c + ':<br /><INPUT TYPE="text" class="inputs" data-identifier="' + c + '" placeholder="Headline" id="headline1' + c + '" maxlength="30" style="width: 350px;float:left;min-height: 32px;"/><span id="countHeadline1' + c + '" class="adCounter" style="color: rgb(0, 0, 0);float:left;position: inherit;margin-left: 0px;">0</span><br><span id="headlineText1' + c + '" class="headLine" style="color: rgb(0, 0, 0);float:left;position: inherit;margin-left: 0px;">0</span>');
});
$('#parent').on('input', '.inputs', updateCount); // Event delegation
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='parent'>
Headline 1:
<br>
<INPUT class='inputs' TYPE="text" data-identifier='1' placeholder="Headline" id="headline11" maxlength="30" style="width: 350px;float:left;min-height: 32px;" />
<span id="countHeadline11" class="adCounter" style="color: rgb(0, 0, 0);float:left;position: inherit;margin-left: 0px;">
0
</span><br>
<span id="headlineText11" class="headLine" style="color: rgb(0, 0, 0);float:left;position: inherit;margin-left: 0px;">
0
</span>
</div><br>
<button id='add'>Add</button>
Important: I've removed this onkeydown="javascript:stripspaces(this)", so you need to decide where to call that function.
I understand using IDs seems like a direct way to target elements, but it might be more and more complicated as you keep adding content or elements to your app/page, so try to think of html as a structured set of elements that can have "patterns" or "templates", like this:
<div class="headline">
<input type="text">
<span class="headlineCount">0</span>
<span class="headlineText">Text</span>
</div>
With this in mind, lets check some important concepts of using JavaScript with HTML, that jQuery eases a lot
You can "traverse" HTML as a tree, with parents and children leafs (you might want to read this)
You can select elements using more than just IDs (you might want to read this).
Interacting with HTML inputs trigger events (you might want to read this)
Using html as the one stated above, you can have this:
<div class="headline">
<input type="text" value="One">
<span class="headlineCount">0</span>
<span class="headlineText">Headline One</span>
</div>
<div class="headline">
<input type="text" value="Two">
<span class="headlineCount">0</span>
<span class="headlineText">Two</span>
</div>
Two groups of class headline, each contains the input field, the count number and the text.
And with jQuery you can use code like this:
function update () {
var inputElement = this
$(inputElement).nextAll('.headlineCount').text(inputElement.value);
$(inputElement).nextAll('.headlineText').text(inputElement.value);
}
jQuery('.headline input').on('change', update)
Which basically reads like:
jQuery('.headline input').on('change', update)
For all input elements that are inside any element with class headline, attach a change event listener with update function.
function update () {
var inputElement = this
$(inputElement).nextAll('.headlineCount').text(inputElement.value);
$(inputElement).nextAll('.headlineText').text(inputElement.value);
}
The update function when triggered, will receive the <input> element as "context" (CHECK NOTE) and the this "variable" will refer to it.
Then with jQuery we do:
$(inputElement).nextAll('.headlineCount').text(inputElement.value);
Which means: starting from the input element find all elements with class headlineCount that are AFTER it, and change its text to the value of the input element.
I hope this small explanation is enough to get you started, indeed you're starting to handle some of the most important concepts of Front-End development, please don't stop refering to Mozilla documentation site (MDN) its wonderful.
NOTE: The this keyword is a somewhat advanced topic, to start I highly recommend to think of it as a "context" reference. But make sure to check it in MDN too!
Edit: now with Live demo: https://jsfiddle.net/3w7t1bhm/
Okay, after your edit, seeing that you have complete control over the HTML that's generated, I would definitely recommend going away from using IDs. Probably the easiest way would be to add classes to the headline and headlineText elements, and use them.
So, first, add classes to the elements that need them, when appending them in the click handler:
<INPUT TYPE="text" placeholder="Headline" onkeydown="javascript:stripspaces(this)" id="headline1'+ c +'" maxlength="30" style="width: 350px;float:left;min-height: 32px;" class="headline">
<span id="headlineText1'+ c +'" class=headline-text>
Then you can bind the change event on all elements with the headline class (You shouldn't need to bind to keyup and keydown as long as you've bound to change):
$(".headline").change(updateCount);
You can then update the count using jQuery's next method, and update the text using the parent and find methods:
function updateCount() {
var this$ = $(this);
var val = this$.val();
var cs = val.length;
this$.next().text(cs);
//Go up to the grandparent, which is the main div that wraps everything added in a single click, then search inside that for the element with the headline-text class
this$.parent().parent().find(".headline-text").text(val);
}
This makes some assumptions about the structure of the HTML that's being added, but if you need to change that structure, it would only take minor changes to this code to work with it.
Related
I'm trying to use a input number type to update how many times a particular amount of content is added to the page. In the example I'm doing it with a p tag but in my main model I'm using it on a larger scale with multiple divs. However, I can't seem to be able to get this to work. If someone can see where I'm going wrong that would be very helpful.
function updatePage() {
var i = document.getElementById("numerInput").value;
document.getElementById("content").innerHTML =
while (i > 1) {
"<p>Content<p/><br>";
i--;
};
}
<input type="number" value="1" id="numberInput">
<br>
<input type="button" value="Update" onclick="updatePage()">
<div id="content">
<p>Content
<p>
<br>
</div>
First, you have quite a few problems that need addressing:
You are setting the .innerHTML to a while loop, which is invalid because a loop doesn't have a return value. And, inside your loop, you just have a string of HTML. It isn't being returned or assigned to anything, so nothing will happen with it.
You've also mis-spelled the id of your input:
document.getElementById("numerInput")
Also, don't use inline HTML event attributes (i.e. onclick) as there are many reasons not to use this 20+ year old antiquated technique that just will not die. Separate all your JavaScript work from your HTML.
Lastly, your HTML is invalid:
"<p>Content<p/><br>"
Should be:
"<p>Content</p>"
Notice that in addition to fixing the syntax for the closing p, the <br> has been removed. Don't use <br> simply to add spacing to a document - do that with CSS. <br> should be used only to insert a line feed into some content because that content should be broken up, but not into new sections.
Now, to solve your overall issue, what you should do is set the .innerHTML to the return value from a function or, more simply just the end result of what the loop creates as I'm showing below.
// Get DOM references just once in JavaScript
let input = document.getElementById("numberInput");
let btn = document.querySelector("input[type='button']");
// Set up event handlers in JavaScript, not HTML with standards-based code:
btn.addEventListener("click", updatePage);
function updatePage() {
var output = ""; // Will hold result
// Instead of a while loop, just a for loop that counts to the value entered into the input
for (var i = 0; i < input.value; i++) {
// Don't modify the DOM more than necessary (especially in a loop) for performance reasons
// Just build up a string with the desired output
output += "<p>Content</p>"; // Concatenate more data
};
// After the string has been built, update the DOM
document.getElementById("content").innerHTML = output;
}
<input type="number" value="1" id="numberInput">
<br>
<input type="button" value="Update">
<div id="content">
<p>Content</p>
</div>
And, if you truly do want the same string repeated the number of times that is entered into the input, then this can be a lot simpler with string.repeat().
// Get DOM references just once in JavaScript
let input = document.getElementById("numberInput");
let btn = document.querySelector("input[type='button']");
// Set up event handlers in JavaScript, not HTML with standards-based code:
btn.addEventListener("click", updatePage);
function updatePage() {
// Just use string.repeat()
document.getElementById("content").innerHTML = "<p>Content</p>".repeat(input.value);
}
<input type="number" value="1" id="numberInput">
<br>
<input type="button" value="Update">
<div id="content">
<p>Content</p>
</div>
As #ScottMarcus pointed out you had the following issues:
While Loops do not need a ; at the end of while(args) {}
Your .innerHTML code was in the wrong place
You had a typo in getElementById("numerInput") which I changed to getElementById("numberInput")
Code
function updatePage() {
// Get input value
var numberInput = document.getElementById("numberInput").value;
// Will be used to store all <p> contents
var template = "";
while (numberInput > 0) {
// Add all contents into template
template += "<p>Content<p/><br>";
numberInput--;
}
// Append upon clicking
document.getElementById("content").innerHTML = template;
}
<input type="number" value="1" id="numberInput">
<br>
<input type="button" value="Update" onclick="updatePage()">
<div id="content">
</div>
I'm having a kind of problem that I think is related to how I generate my HTML... I use a JavaScript function to generate some HTML, but then it begins to misfunction... let me first paste some of my code
First, my raw HTML
<div id="effect">
<label for="s_effect">Effect: </label>
<select name="s_effect" id="s_effect" onchange="ne();">
<option value="">Select your Effect</option>
</select>
<div id="effect_description"></div>
<div id="effect_options"></div>
</div>
Then, I have a function that loads "s_effect" based on an array (that's fine and working, np).
Then, my ne() (new effect) function:
function ne(){
reset();
e = g('s_effect');
if(newEffect(e.options[e.selectedIndex].value)){
console.log("new effect created");
updateScreen();
}
}
It basically "resets" parts of the screen (error tracking and that, stuff not related with my problem), then calls to updateScreen() (note: g function is just a synonym for document.getElementById)
It goes to this function (sorry it's a lot of code...)
function updateScreen(){
if(project.effect instanceof Effect){
lock("instant");
lock("event");
showDescription();
generateOptions();
}else if(project.effect == null){
unlock("instant");
unlock("event");
}
if(project.check()){
generateButton();
}else{
npButton();
}
}
That basically, locks some part of the window, then get some HTML on calls below.
generateDescription(), the part is giving trouble, does the following:
function generateOptions(){
g('effect_options').innerHTML = '';
effectID = project.effect.calculateId();
if(effectID === false)
return false;
g('effect_options').innerHTML = project.effect.parameters.HTMLOptions;
return true;
}
It calls to an object attribute that basically dumps some HTML code:
<div>
<label for="1_color">Color: </label><input type="color" id="1_color" name="1_color" onchange="updateColor('1_color');" value="#FFFFFF">
<input type="text" name="1_color_rgb" id="1_color_rgb" onchange="updateColor('1_color_rgb');" value="#FFFFFF">
</div>
<div id="extra"></div>
<div>
<input type="button" value="Add Node" onclick="addNode();">
</div>
Finally, addNode() makes an innerHTML += [new div on "extra"] but increasing the number (2_color, 2_color_rgb, and so on).
function addNode(){
var count = ++(project.effect.parameters.colorCount);
g('extra').innerHTML +=
'<div><label for="' + count + '_color">Color: </label><input type="color" id="' + count + '_color" name="' + count + '_color" onchange="updateColor(\'' + count + '_color\');" value="#FFFFFF" />' +
'<input type="text" name="' + count + '_color_rgb" id="' + count + '_color_rgb" onchange="updateColor(\'' + count + '_color_rgb\');" value="#FFFFFF" /></div>' +
}
To this point everything is working fine, even "updateColor" works (it updates the value of each input so you can choose a color by filling any input).
The problem comes when I modify any x_color or x_color that has been added via button... It adds a new "node" but restarts the values of previous inputs.
I debugged it, and by the point is doing the adding, the innerHTML of "extra" shows all inputs with "#FFFFFF" values (initial), but on the screen, the values are right...
Any help with this may be appreciated.
PS: I'm using chrome.
Thank you!
Just to clarify, as #Forty3 answered, the problem was the fact that I was modifying the innerHTML each time, making my browser to re-render extra each time.
As he suggested, I edited my function, now looks like
function addNode(){
var count = ++(project.effect.parameters.colorCount);
var nDiv = document.createElement("div");
nDiv.innerHTML = "whatever I was putting...";
g('extra').appendChild(nDiv);
}
Now it works fine.
Thank you all for the support.
The issue, it appears, is that by reassinging the .innerHTML property of the g('extra') DOM element, you are telling the browser to re-render the element based on the HTML -- not the DOM elements and values contained within.
In other words, when you add a color, the g('extra').innerHTML gets updated with the new HTML to render an additional color selection block (i.e. 2_color). When a user then picks a new color, the browswer will update the value for the 2_color DOM element but doesn't necessarily update the innerHTML property for g('extra'). Then, when another color block is added, the innerHTML is updated once more and the browser re-renders it thereby "resetting" the values.
Instead of constructing the additional controls using HTML string, use DOM manipulation (e.g. .createElement() and .append()) in order to add your new options.
I'm trying to write a function with jQuery that will create a series of new inputs next to a series of unique inputs that already exist. The function should add event listeners to each of the new inputs so that whenever its value changes (something is typed into it, for example), the value of the original input next to it has the same value.
Eventually I will simply hide the original form so that the end user only sees the new form. I'm doing this for the sake of being able to control which parts of the form the user can see/modify and also to have more control over the way the form looks. The reason I have to do all this is because I have to do all this is because I'm modifying a Microsoft Sharepoint item creation form, and the only way to modify it is to add javascript to the page. If there's a better way of doing this I'm all ears. I'm relatively new to coding and very new to coding in Sharepoint.
Anyways, here's what I have:
var inputIdArr = [
'OrigInput1',
'OrigInput2',
'OrigInput3',
'OrigInput4',
'OrigInput5',
'OrigInput6',
'OrigInput7'
];
function newInputs(arr) {
for (str in arr) {
var elem = $( "[id='" + inputIdArr[str] + "']" );
var parent = elem.parent();
var newInputId = `newInput${str}`
var newInput = `<input type='text' id=${newInputId} />`;
parent.append(newInput);
$( `[id=${newInputId}]` ).change(function() {
console.log(newInputId + " changed");
elem.val($( `[id=${newInputId}]` ).value);
});
}
}
$(document).ready(function(){
newInputs(inputIdArr);
});
Currently, the console always logs "newInput7 changed". I'm not sure how to fix it so it logs that the correct new input has changed. I could also use guidance on where to go once that's done.
Programmatically keeping track of the generated ids of dynamically created elements is an anti-pattern that leads to needlessly verbose code and a maintenance headache.
Instead you can make the logic much more succinct and extensible by simply using classes and DOM traversal to group and relate elements to each other, something like this:
$('input.orig').after('<input type="text" class="appended" />');
$('#container').on('input', 'input.appended', function() {
$(this).prev('.orig').val(this.value);
})
.appended {
border: 1px solid #C00;
}
input {
display: block;
margin: 2px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="container">
<input type="text" class="orig" />
<input type="text" class="orig" />
<input type="text" class="orig" />
</div>
Also note that using attribute selectors to find elements by id is redundant when the # id selector is available and much faster - even though it's not needed for this to work.
This should give you rought start,, I've created 7 original fields, and your function does new fields successfully. I've added event or binding on new fields outside of your loop because there was some issue I think.
// fields selection
var inputIdArr = [
'OrigInput1',
'OrigInput2',
'OrigInput3',
'OrigInput4',
'OrigInput5',
'OrigInput6',
'OrigInput7'
];
// function for creating new fields
function newInputs(arr) {
for (str in arr) {
var elem = $("[id='" + inputIdArr[str] + "']");
var parent = elem.parent();
var newInputId = `newInput${str}`
var newInput = `<input type='text' id=${newInputId} />`;
parent.append(newInput);
}
}
// create new inputs on dom ready
$(document).ready(function() {
newInputs(inputIdArr);
});
// bind new inputs and send value to org inputs
$(document).on('input', '[id^="newInput"]', function(el){
$(this).prev().val( $(this).val() );
});
input:nth-child(1){ border:1px solid red; }
input:nth-child(2){ border:1px solid blue; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div><input type="text" id="OrigInput1"></div>
<div><input type="text" id="OrigInput2"></div>
<div><input type="text" id="OrigInput3"></div>
<div><input type="text" id="OrigInput4"></div>
<div><input type="text" id="OrigInput5"></div>
<div><input type="text" id="OrigInput6"></div>
<div><input type="text" id="OrigInput7"></div>
I have multiple forms on a page and also multiple input boxes with plus/minus signs.
I'm having trouble to get those input boxes to work seperately. Probably because of some wrong/same id's or something like that or maybe a wrong setup of my code. The thing is I can't find my error in the code and I don't get any errors in my console.
What I have:
function quantity_change(way, id){
quantity = $('#product_amount_'+id).val();
if(way=='up'){
quantity++;
} else {
quantity--;
}
if(quantity<1){
quantity = 1;
}
if(quantity>10){
quantity = 10;
}
$('#product_amount_'+id).val(quantity);
}
And my html:
//row 1
<div class="amount"><input type="text" name="quantity" value="1" id="product_amount_1234"/></div>
<div class="change" data-id="1234">
+
-
</div>
//row 2
<div class="amount"><input type="text" name="quantity" value="1" id="product_amount_4321"/></div>
<div class="change" data-id="4321">
+
-
</div>
I thought something like this would do the trick but it doesn't :(
$(document).ready(function(){
$('.change a').click(function(){
var id = $(this).find('.change').data('id');
quantity_change(id)
});
});
Any help greatly appreciated!
You should use closest() method to get access to the parent div with class change, then you can read the data attribute id's value.
var id = $(this).closest('.change').data('id');
alert(id);
Since you are already binding the click event using unobutrusive javascript, you do not need the onclick code in your HTML markup.
Also your quantity_change method takes 2 parameters and using both, but you are passing only one. You may keep the value of way in HTML 5 data attributes on the anchor tag and read from that and pass that to your method.
<div class="change" data-id="1234">
+
-
</div>
So the corrected js code is
$(document).ready(function(){
$('.change a').click(function(e){
e.preventDefault();
var _this=$(this);
var id = _this.closest('.change').data('id');
var way= _this.data("way");
quantity_change(way,id)
});
});
Here is a working sample.
I am using ASP.Net MVC along with Jquery to create a page which contains a contact details section which will allow the user to enter different contact details:
<div id='ContactDetails'>
<div class='ContactDetailsEntry'>
<select id="venue_ContactLink_ContactDatas[0]_Type" name="venue.ContactLink.ContactDatas[0].Type">
<option>Email</option>
<option>Phone</option>
<option>Fax</option>
</select>
<input id="venue_ContactLink_ContactDatas[0]_Data" name="venue.ContactLink.ContactDatas[0].Data" type="text" value="" />
</div>
</div>
<p>
<input type="submit" name="SubmitButton" value="AddContact" id='addContact' />
</p>
Pressing the button is supposed to add a templated version of the ContactDetailsEntry classed div to the page. However I also need to ensure that the index of each id is incremented.
I have managed to do this with the following function which is triggered on the click of the button:
function addContactDetails() {
var len = $('#ContactDetails').length;
var content = "<div class='ContactDetailsEntry'>";
content += "<select id='venue_ContactLink_ContactDatas[" + len + "]_Type' name='venue.ContactLink.ContactDatas[" + len + "].Type'><option>Email</option>";
content += "<option>Phone</option>";
content += "<option>Fax</option>";
content += "</select>";
content += "<input id='venue_ContactLink_ContactDatas[" + len + "]_Data' name='venue.ContactLink.ContactDatas[" + len + "].Data' type='text' value='' />";
content += "</div>";
$('#ContactDetails').append(content);
}
This works fine, however if I change the html, I need to change it in two places.
I have considered using clone() to do this but have three problems:
EDIT: I have found answers to questions as shown below:
(is a general problem which I cannot find an answer to) how do I create a selector for the ids which include angled brackets, since jquery uses these for a attribute selector.
EDIT: Answer use \ to escape the brackets i.e. $('#id\\[0\\]')
how do I change the ids within the tree.
EDIT: I have created a function as follows:
function updateAttributes(clone, count) {
var f = clone.find('*').andSelf();
f.each(function (i) {
var s = $(this).attr("id");
if (s != null && s != "") {
s = s.replace(/([^\[]+)\[0\]/, "$1[" + count + "]");
$(this).attr("id", s);
}
});
This appears to work when called with the cloned set and the count of existing versions of that set. It is not ideal as I need to perform the same for name and for attributes. I shall continue to work on this and add an answer when I have one. I'd appreciate any further comments on how I might improve this to be generic for all tags and attributes which asp.net MVC might create.
how do I clone from a template i.e. not from an active fieldset which has data already entered, or return fields to their default values on the cloned set.
You could just name the input field the same for all entries, make the select an input combo and give that a consistent name, so revising your code:
<div id='ContactDetails'>
<div class='ContactDetailsEntry'>
<select id="venue_ContactLink_ContactDatas_Type" name="venue_ContactLink_ContactDatas_Type"><option>Email</option>
<option>Phone</option>
<option>Fax</option>
</select>
<input id="venue_ContactLink_ContactDatas_Data" name="venue_ContactLink_ContactDatas_Data" type="text" value="" />
</div>
</div>
<p>
<input type="submit" name="SubmitButton" value="AddContact" id='addContact'/>
</p>
I'd probably use the Javascript to create the first entry on page ready and then there's only 1 place to revise the HTML.
When you submit, you get two arrays name "venue_ContactLink_ContactDatas_Type" and "venue_ContactLink_ContactDatas_Data" with matching indicies for the contact pairs, i.e.
venue_ContactLink_ContactDatas_Type[0], venue_ContactLink_ContactDatas_Data[0]
venue_ContactLink_ContactDatas_Type[1], venue_ContactLink_ContactDatas_Data[1]
...
venue_ContactLink_ContactDatas_Type[*n*], venue_ContactLink_ContactDatas_Data[*n*]
Hope that's clear.
So, I have a solution which works in my case, but would need some adjustment if other element types are included, or if other attributes are set by with an index included.
I'll answer my questions in turn:
To select an element which includes square brackets in it's attributes escape the square brackets using double back slashes as follows: var clone = $("#contactFields\[0\]").clone();
& 3. Changing the ids in the tree I have implemented with the following function, where clone is the variable clone (in 1) and count is the count of cloned statements.
function updateAttributes(clone, count) {
var attribute = ['id', 'for', 'name'];
var f = clone.find('*').andSelf();
f.each(function(i){
var tag = $(this);
$.each(attribute, function(i, val){
var s = tag.attr(val);
if (s!=null&& s!="")
{
s = s.replace(/([^\[]+)\[0\]/, "$1["+count+"]");
tag.attr(val, s);
}
});
if ($(this)[0].nodeName == 'SELECT')
{ $(this).val(0);}
else
{
$(this).val("");
}
});
}
This may not be the most efficient way or the best, but it does work in my cases I have used it in. The attributes array could be extended if required, and further elements would need to be included in the defaulting action at the end, e.g. for checkboxes.