I'd like to finish this function, but I'm not sure where I'm going wrong.
I'm trying to add and remove divs called 'milestones' with various inputs inside of them with jquery. I have the 'add milestone' button working currently. Each milestone div has a 'delete milestone' button that, when clicked, should delete that div entirely. For some reason I'm not able to interact with the buttons inside those dynamically crated divs.
I'm also trying to incremement the Milestone #.
The HTML
<!-- Milestone Title -->
<div class="row">
<div class="col-md-5">
<div class="form-group">
<label class="control-label">Milestone Title</label>
<input class="form-control" type="text" name="Milestone[0]
[MilestoneTitle]" placeholder="Dusty Bench" required />
</div>
</div>
</div>
<!-- Milestone Deadline -->
<div class="row">
<div class="col-md-5">
<div class="form-group">
<label class="control-label">Deadline</label>
<input type="text" class="form-control datetimepicker" name="Milestone[0][MilestoneEndDate]" placeholder="Deadline" required/>
</div>
</div>
</div>
<!-- Milestone Description -->
<div class="row">
<div class="col-md-5">
<div class="form-group">
<label class="control-label">Milestone Description</label>
<textarea class="form-control" id="exampleTextarea" name="Milestone[0][Description]" rows="3" required>
</textarea>
</div>
</div>
</div>
<div class="additional-milestones">
</div>
<!-- + Add Milestone Button -->
<div class="row">
<div class="col-md-5">
<a class="add-milestone btn btn-primary">
+ Add Another Milestone
</a>
</div>
</div>
<!-- End Set Milestones Tab #2 -->
The JQuery
$().ready(function() {
//Max amount of milestones
var max_milestone = 5
// Initial Milestone Count
var x = 1;
//function for add milestone button
//if button.add-milestone is clicked
$('.add-milestone').click(function(e) {
//console.log('dope')
e.preventDefault();
if (x < max_milestone) {
x++; //increment milestones
$('.additional-milestones').before('<h4>Milestone #1</h4><div class="row"><div class="col-md-5"><div class="form-group"><label class="control-label">Milestone Title</label><input class="form-control" type="text" name="Milestone[0][MilestoneTitle]" placeholder="Dusty Bench" required /></div></div></div><div class="row"><div class="col-md-5"><div class="form-group"><label class="control-label">Deadline</label><input type="text" class="form-control datetimepicker" name="Milestone[0][MilestoneEndDate]" placeholder="Deadline" required/></div></div></div><div class="row"><div class="col-md-5"><div class="form-group"><label class="control-label">Milestone Description</label><textarea class="form-control" id="exampleTextarea" name="Milestone[0][Description]" rows="3" required> </textarea></div></div></div><div class="row"><div class="col-md-5"><a class="delete-milestone btn btn-danger"> - Delete This Milestone</a></div></div><hr>');
}
});
//then
//populate with milestone form with Milestone+Next Milestone Number
//Delete Milestone
$('.delete-milestone').click(function() {
console.log('dope')
//$(this).parent().remove();
});
});
The CSS
.btn {
border-width: 1px;
background-color: transparent;
font-weight: 400;
opacity: 0.8;
border-color: #888888;
color: #888888;
}
.btn-primary {
border: solid 1px #447DF7;
}
.btn-danger {
border: #FB404B solid 1px;
}
My JS fiddle is here.
Thank you!
What is going wrong with your code is that you are attaching event handler to elements with class .delete-milestone initially in your code, whereas these elements are added dynamically. As a result when the code is executed, it tries to attach a event handler to the elements with class .delete-milestone but actually there are no elements present.
You can instead attach event handler after the element is injected into the HTML. Have a look at this modified jsfiddle of your code.
Or you can also make use of onclick attribute.
And for incrementing the milestone #, you can have a separate variable to keep a count. Increment when element is added and decrement when it is deleted.
Your javascript defines the click event on .delete-milestones before it exists. You have to call .on click after it was added to the dom.
Try this:
$('.add-milestone').click(function() {
$newMilestone = $('<h4>Milestone #1</h4><div class="row"><div class="col-md-5"><div class="form-group"><label class="control-label">Milestone Title</label><input class="form-control" type="text" name="Milestone[0][MilestoneTitle]" placeholder="Dusty Bench" required /></div></div></div><div class="row"><div class="col-md-5"><div class="form-group"><label class="control-label">Deadline</label><input type="text" class="form-control datetimepicker" name="Milestone[0][MilestoneEndDate]" placeholder="Deadline" required/></div></div></div><div class="row"><div class="col-md-5"><div class="form-group"><label class="control-label">Milestone Description</label><textarea class="form-control" id="exampleTextarea" name="Milestone[0][Description]" rows="3" required> </textarea></div></div></div><div class="row"><div class="col-md-5"><a class="delete-milestone btn btn-danger"> - Delete This Milestone</a></div></div>');
$('.additional-milestones').before($newMilestone);
$newMilestone.find('.delete-milestone').on('click', function() {
console.log('dope')
});
});
First, the code waits for the document to become ready. It's going to run that code you posted once, at the beginning.
When the document is ready, $('.delete-milestone') will get a list of buttons that are currently on the page. .click(...) will add event listeners to them.
Later, if you add more buttons, no code adds event listeners to them.
Here are two ideas on how to make it work:
When you create a button, add an event listener.
Use delegated events to handle events on anything that matches a selector that bubbles up to a common ancestor.
Related
I'm trying to create a way to add entries to my form so that the user can choose how many he wants, but I can't get the remove part working.
JavaScript
var i = 1;
var divContent = document.getElementById('formulario');
//Click to add a field
function cria() {
//This add a HTML Inputs and divs who the ID is variable how the 'i' is increasedf
document.getElementById('formulario').innerHTML += '<div class="mb-1 col-3" id="div'+i+'"><label for="nomeTx0" class="form-label">Nome</label><input type="text" class="form-control" id="nomeTx'+i+'" name="nomeTx'+i+'" required></div><div class="mb-1 col-3" id="div2'+i+'"><label for="taxa'+i+'" class="form-label">Valor</label><input type="float" class="form-control" id="taxa'+i+'" name="taxa'+i+'" required></div>- Remover campo';
i++;
}
function remove(div1, div2){
var div = document.getElementById(div1);
var div2 = document.getElementById(div2);
div.remove();
div2.remove();
i--;
}
And now the HTML
<form>
<h4 class="card-tittle text-center">Taxas</h4>
<div id="formulario" class="form row align-items-start">
<div class="mb-1 col-3" id="0">
<label for="nomeTx0" class="form-label">Nome</label>
<input type="text" class="form-control" id="nomeTx0" name="nomeTx0" required>
</div>
<div class="mb-1 col-3" id="0">
<label for="taxa0" class="form-label">Valor</label>
<input type="float" class="form-control" id="taxa0" name="taxa0" required>
</div>
</div>
+ adicionar campo
<div class="mb-1 col-lg-12" style="text-align: center;">
<button class="btn btn-primary col-5" id="Enviar" type="submit" text="Enviar">Adicionar Taxas</button>
</div>
</form>
ID="taxa"+i but when I call the remove(); error is printed to me saing the variable is null.
This is really not the right approach in the first place.
Your fundamental problem is that you are relying on ids to know what element(s) to add and remove and this is leading you to concatenate an id onto dynamically created elements, made from long strings with variables concatenated into them. In reality, you should avoid ids whenever possible as they make your code very brittle and don't scale well.
This is a perfect use for the HTML <template> element. As you can see from the re-worked code below, all ids have been removed - - you don't need them. Additionally, instead of long strings with a variable concatenated into it, you just need to copy/clone the template whenever you need one. Then, you can use "event delegation" and smartly organized HTML to just set up a single click event on a master wrapper element, where the actual element that was clicked (the event.target) can be checked. If it was a remove button, then just remove the entire wrapper that is the nearest ancestor to the remove button that was clicked.
You can now add and remove as many items as you like with no need for an id or counting variables!
// Get a reference to the template, outer div and the add "button"
const template = document.querySelector("template");
const wrapper = document.querySelector(".wrapper");
const add = document.querySelector(".add");
// Set up the add event in Javascript, not with inline HTML
add.addEventListener("click", function(event){
var clone = template.content.cloneNode(true); // Clone the template
wrapper.appendChild(clone);
});
// Set up a wrapper level click event that any clicks within it will bubble up to
wrapper.addEventListener("click", function(event){
// Test to see if it was remove "button" that was clicked
if(event.target.classList.contains("remove")){
// Just remove the closest ancestor div that holds that particular group
// and remove it.
event.target.closest("div.templateWrapper").remove();
}
});
.mb-1.col-lg-12 {
text-align:center;
}
.mb-1.col-3 {
margin:2px;
}
.add, .remove {
cursor:pointer;
color:blue;
}
.labelName { display:inline-block; width:3em; }
/* This is just to better see the groups */
.templateWrapper, .form {
background-color:aliceblue;
padding:5px;
margin:8px;
}
<!-- This will not initially be shown on the page.
It will be used to copy from when/if needed. -->
<template>
<div class="templateWrapper">
<div class="mb-1 col-3">
<label class="form-label"><span class="labelName">Nome</span>
<input type="text" class="form-control" name="nomeTx" required>
</label>
</div>
<div class="mb-1 col-3">
<label class="form-label"><span class="labelName">Valor</span>
<!-- An input does not have a type=float -->
<input class="form-control" name="taxa" required>
</label>
</div>
<span class="remove">- Remover campo</span>
</div>
</template>
<form>
<h4 class="card-tittle text-center">Taxas</h4>
<!-- Hyperlinks are for navigation, not JavaScript click hooks. Any visible element
supports a click event. Use span and div for generic clickable inline or block
elements that need to have click event handlers. -->
<span class="add">+ adicionar campo</span>
<div class="wrapper">
<div class="form row align-items-start">
<div class="mb-1 col-3">
<label class="form-label"><span class="labelName">Nome</span>
<input type="text" class="form-control" name="nomeTx0" required>
</label>
</div>
<div class="mb-1 col-3">
<label class="form-label"><span class="labelName">Valor</span>
<input type="float" class="form-control" name="taxa0" required>
</label>
</div>
</div>
</div>
<div class="mb-1 col-lg-12">
<!-- A button does not have a "text" attribute -->
<button class="btn btn-primary col-5" type="submit">Adicionar Taxas</button>
</div>
</form>
You would like to pass the ids of the html elements to function remove , instead you pass something else.
Try this:
function remove(d1, d2){
//what are passing to function... id , or something else ?
console.log(d1,d2);
// now I force the arguments passed to function to a valid value id for test
var a = document.getElementById('div1'); // id is div1
var b = document.getElementById('div21'); // id is div21
//Ask to parentNode to remove his child
a.parentNode.removeChild(a);
b.parentNode.removeChild(b);
i--;
}
The problem in your code is that you don't pass a string to the remove function but instead you pass the whole element. That is why document.getElementById can't find anything because it expects a string as a parameter. I refactored you code a bit and also when removing the fields the link - Remover campo stayed and was not deleted. I fixed that as well by passing a third argument to the remove function.
var i = 1;
var divContent = document.getElementById('formulario');
//Click to add a field
function cria() {
//This add a HTML Inputs and divs who the ID is variable how the 'i' is increasedf
document.getElementById('formulario').innerHTML += '<div class="mb-1 col-3" id="div'+i+'"><label for="nomeTx0" class="form-label">Nome</label><input type="text" class="form-control" id="nomeTx'+i+'" name="nomeTx'+i+'" required></div><div class="mb-1 col-3" id="div2'+i+'"><label for="taxa'+i+'" class="form-label">Valor</label><input type="float" class="form-control" id="taxa'+i+'" name="taxa'+i+'" required></div>- Remover campo';
i++;
}
function remove(div1, div2, link){
var div = document.getElementById(div1);
var div2 = document.getElementById(div2);
var link = document.getElementById(link);
divContent.removeChild(div);
divContent.removeChild(div2);
divContent.removeChild(link)
i--;
}
The easiest way to do your code working it change your function "cria". (it's not the best option)
You miss ' '.
You have this.
onclick="remove(div'+i+',div2'+i+')"
You need this.
onclick="remove(\'div'+i+'\',\'div2'+i+'\')"
Javascript just doesn't understand that these parameters are strings.
And the full function "cria" after changes.
function cria() {
//This add a HTML Inputs and divs who the ID is variable how the 'i' is increasedf
document.getElementById('formulario').innerHTML += '<div class="mb-1 col-3" id="div'+i+'"><label for="nomeTx0" class="form-label">Nome</label><input type="text" class="form-control" id="nomeTx'+i+'" name="nomeTx'+i+'" required></div><div class="mb-1 col-3" id="div2'+i+'"><label for="taxa'+i+'" class="form-label">Valor</label><input type="float" class="form-control" id="taxa'+i+'" name="taxa'+i+'" required></div>- Remover campo';
i++;
}
I'm working with Laravel 5 and I've the following HTML pages:
HTML 1
<div class="row">
#foreach($postList as $post)
#include('Pages.Post.postInGroup', ['post'=>$post, 'commentsList'=>$commentsList])
#endforeach
</div>
HTML 2
<form id="msform" action="{{route('comments.store')}}" method="post">
{{ csrf_field() }}
<div class="row align-items-center">
<div class="col-3 col-sm-3 col-md-2 col-lg-2 col-xl-1" style="display: inline-block;">
<img src="{{url(Auth::user()->picture_path)}}" style="border-radius: 50%;" width="30" height="30" alt="User Picture">
</div>
<div class="col-9 col-sm-9 col-md-6 col-lg-6 col-xl-9" style="display: inline-block;">
<textarea class="form-control" placeholder="Post a comment" id="comment_content {{$post->id}}" name="comment_content" rows="1"></textarea>
</div>
<div class="col-1 col-sm-1 col-md-1 col-lg-1 col-xl-1" >
<input type="submit" name="comment_button {{$post->id}}" class="btn btn-primary" value="Comment" style="background-color: #228B22;"/>
<input type="hidden" value="{{$post->id}}" name="postId" id="postId">
<input type="hidden" value="{{$theGroup->id}}" name="groupId" id="groupId">
</div>
</div>
</form>
<script src="{{ url('js/jquery-3.3.1.min.js') }}"></script>
<script>
$(document).ready(function() {
$('#msform > div > div > input[name=comment_button {{$post->id}}]').prop('disabled', true);
//while user is typing disable and enable based on the value.
$('#msform > div textarea').on("input", function() {
$(this).parents('.row').find('input[name=comment_button {{$post->id}}]').prop('disabled', $(this).val() == '');
});
});
</script>
The HTML 1 code explains how the HTML 2 code is repeated based on the number of objects in $postList.
What I'm trying to do is disable the button corresponding to the textarea, as long as its textarea is empty, but I do not get the desired result, because when I fill in any textarea, all the buttons are re-abilitated, and that's not what I want. For reasons of data extrapolation, I cannot change the name of the textarea and I would like this script works only on the submit button in HTML 2.
To explain better my problem, I'll made an example:
I have cycled 3 times HTML 2 by HTML 1, so I'll have:
Textarea(id="comment_content 1") - Button (name="comment_button 1")
Textarea(id="comment_content 2") - Button (name="comment_button 2")
Textarea(id="comment_content 3") - Button (name="comment_button 3")
If I want to write in the 2nd textarea with id comment_content 2, then I will have to enable only the button adjacent to that textarea, comment_button 2. I hope my problem is clear.
You're including the same external script and same inline script multiple times, as many times as there are posts. This is inefficient, you should include the external Javascript only once per page.
You can refactor this code to address your bug by creating a listener that listens to all textareas and then uses a data attribute on the textarea to determine which button should have a state change.
Step 1: Add the post ID to the textarea in a data attribute
<textarea data-post="{{ $post->id }}"
class="form-control"
placeholder="Post a comment"
id="comment_content {{$post->id}}"
name="comment_content" rows="1"></textarea>
Step 2: Add the post ID to the button in a data attribute
<input data-post="{{ $post->id }}"
type="submit"
name="comment_button {{$post->id}}"
class="btn btn-primary"
value="Comment"
style="background-color: #228B22;"/>
Step 3: Refactor your Javascript to use the data-post value when determining what the user is interacting with, e.g:
$(document).ready(function() {
$('#msform > textarea[data-post]').on("input", function() {
var id = $(this).data('post');
$('input[data-post="' + id + '"]').prop('disabled', $(this).val() == '');
});
});
Here's an example of how it'll work, click "Run code snippet" to see it in action.
$(document).ready(function() {
$('textarea[data-post]').on("input", function() {
var id = $(this).data('post');
$('input[data-post="' + id + '"]').prop('disabled', $(this).val() == '');
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<textarea data-post="1"></textarea>
<input data-post="1" type="submit" disabled="true"/>
</div>
<div>
<textarea data-post="2"></textarea>
<input data-post="2" type="submit" disabled="true"/>
</div>
<div>
<textarea data-post="3"></textarea>
<input data-post="3" type="submit" disabled="true"/>
</div>
I have a page with a list of li elements, each called .section. The page starts with just one section, but the user can add more with a click. Each section has a dropdown called .wip-location and three input fields called .section-number, .section-name, and .section-description. (The -number and -description inputs are irrelevant but I included them here just in case they are causing problems.)
Every time the dropdown is changed, I'd like the selected text to get filled into the .section-name input.
This works the first time (when there is only one .section, .wip-location, and .section-name on the page), but as soon as the user adds more .sections, it appears that Jquery is unable to figure out which element to act upon, and no inputs are filled.
HTML
<li class="section">
<div class="form-group row">
<label class="col-sm-2 text-sm-right">Section Number</label>
<div class="col-sm-7">
<input class="section-number" type="number" step="0.01" min="1" />
</div>
<label class="col-sm-2 text-sm-right">Section Name</label>
<div class="col-sm-7">
<input class="section-name" />
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 text-sm-right">Section Description</label>
<div class="col-sm-7">
<textarea class="section-description" />
</div>
<label class="col-sm-2 text-sm-right">WIP Location</label>
<div class="col-sm-7">
#Html.DropDownListFor(m => m.WipLocation,
new SelectList(Model.WipLocations, "Key", "Value"),
"-- select --",
new { #class = "wip-location" })
</div>
</div>
</li>
jQuery
// Automatically add Wip Location choice to Section Name input
$('.wip-location').change(function () {
var $location = $('option:selected', $(this)).text();
var $section = $(this).closest('.section');
var $sectionName = $section.find('.section-name');
$sectionName.val($location);
});
As I said, when there is only one .section and .wip-location on the page, it works perfectly. I suspect jQuery gets confused when there are multiple .wip-locations or something, but I'm not sure if that's really the problem or how to fix it.
Since it is dynamically added you could try to call the event like this:
$(document).on('change', '.wip-location', function(){/*...*/})
I have a (+) sign and a (-) sign. If the user clicks on the + sign than whatever their in the row will automatically get generated the same with new id.
Now when user click on + sign than div id and text box under it will get changed.
Code below for div as follows:
<div class="row" id="Div0">
<div class="col-md-3">
<div class="form-group">
<label for="name">Last Name</label>
<input type="text" class="form-control" id="txtLastName0" placeholder="Enter name" required="required" />
</div>
</div>
<div class="col-md-9"></div>
</div>
Now when user clicks on + sign the new row with text box with new id txtLastName1 will get generated.
Now on click of + sign how do i get new id of textbox and a div with new row.
Div1 and textbox1 will get generated
Basically what you should do is, keep the markup you want to generate as a template in your DOM and when user clicks the "Add" button, clone this markup and append to the DOM (to a container div). then update the Id's of the input and divs as needed.
A simply sample would be like
<div class="row" id="template" style="display: none;">
<div class="col-md-3">
<div class="form-group">
<label for="name">Last Name</label>
<input type="text" class="form-control lname" id="txtLastName"
placeholder="Enter name" required="required" />
</div>
</div>
<div class="col-md-9"></div>
</div>
<button id="btnAdd">+</button>
<div id="container"></div>
Now wire up a click event handler to the Add/+ button. You may use the jQuery clone() method.
$(function () {
$("#btnAdd").click(function(e) {
e.preventDefault();
var childCount = $("#container").children().length;
var c = $("#template").clone().show();
c.attr("id", "Div" + childCount );
c.find(".lname").attr('id', 'txtLastName' + childCount);
$("#container").append(c);
});
});
For deleting, you can add a event handler on the delete button and remove the specific div from dom. jQuery remove() method will do it. Use closest() and find as needed to get the correct div to remove.
Here is a working simple jsbin sample for your reference
I have multiple form input and text fields on which I want a popover to display when the field is in focus. Since the content of some of the popovers can be lengthy I don't want to declare the content inside the input tag. Instead I have the following:
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control js-tooltip-trigger" id="name" maxlength="50" >
<div class="js-tooltip" style="display: none;">
<p><strong>Your name.</strong></p>
<p>Enter your full name. This will be used ...</p>
</div>
</div>
<div class="form-group">
<label for="ref">Reference</label>
<input type="text" class="form-control js-tooltip-trigger" id="ref" maxlength="50" >
<div class="js-tooltip" style="display: none;">
<p><strong>Your reference is optional.</strong></p>
<p>Enter a code of your choice for reference purposes.</p>
</div>
</div>
I have the following javascript
$(function () {
$('.js-tooltip-trigger').popover({
html: true,
trigger: 'focus',
content: function () {
return $(correct tool tip).html();
}
});
});
How can I get the correct popover to display or returned in the above javascript. What if I add a data attribute to the tool-tip content to link it to each input field e.g <div class="js-tooltip" style="display:none;" data-tooltip="name"> and then use some jquery to find and return it. How would you do this with jquery? Does anyone have a more elegant solution?
Also how do I get the popover to remain with the input field and auto position upon window resize. Currently it floats away when I resize the window.
Manage to figure it out myself using the above html:
$(function () {
$('.js-tooltip-trigger').popover({
html: true,
trigger: 'focus',
content: function (e) {
return $(this).parent(".form-group").find(".js-tooltip").html();
}
});
});
Can anyone think of a more elegant solution?
You can do like this way...Here is the DEMO
Jquery Part
$(function(){
// Enabling Popover Example 1 - HTML (content and title from html tags of element)
$("[data-toggle=popover]").popover();
// Enabling Popover Example 2 - JS (hidden content and title capturing)
$(".form-control").popover({
html : true,
content: function() {
return $('.js-tooltip').html();
},
title: function() {
return $('.js-tooltip').html();
}
});
});
HTML Part
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control js-popover-trigger" id="name" maxlength="50" data-container="body" data-toggle="popover" data-placement="bottom" data-content="Your name" >
<div class="js-tooltip" style="display: none;" id="n1">
<p><strong>Your name.</strong></p>
<p>Enter your full name. This will be used ...</p>
</div>
</div>
<div class="form-group">
<label for="ref">Reference</label>
<input type="text" class="form-control js-popover-trigger" id="ref" maxlength="50" data-container="body" data-toggle="popover" data-placement="bottom" data-content="Your reference is optional">
<div class="js-tooltip" style="display: none;" id="t2">
<p><strong>Your reference is optional.</strong></p>
<p>Enter a code of your choice for reference purposes.</p>
</div>
</div>
I'm not a fan of how this works, but essentially you have to create an element that stays in the position you want the popover , and append the popover to it. In this case, I have used the container you already have in place for the tooltip html. I'm creating container ID's dynamically so you don't have to worry about doing that in your html.
So for this HTML:
<div class="form-group pos-relative">
<label for="ref">Reference</label>
<input type="text" class="form-control js-tooltip-trigger" id="ref" maxlength="50">
<span class="js-tooltip" style="display: none;">
<p><strong>Your reference is optional.</strong></p>
<p>Enter a code of your choice for reference purposes.</p>
</span>
</div>
This CSS:
.pos-relative{
position:relative;
}
.js-tooltip{
position:absolute;
top:0;
right:0;
}
And this JS--here's where it happens:
$(function () {
$('.js-tooltip-trigger').each(function(ind, ele){
var $ele = $(ele),
$ttSpan = $ele.next('.js-tooltip'),
ttHtml = $ttSpan.html(),
rndID = 'ttid'+ String(Math.random()).substr(2);
// set the ID, strip the style, and empty the html--
// we already have it stored in the var above
$ttSpan.attr('id', rndID).removeAttr('style').html('');
// make the popovers
$ele.popover({
html: true,
trigger: 'focus',
placement: 'right',
container: '#'+rndID,
content: ttHtml
});
});
});
See it in action here