Add form items with javascript based on dropdown selection - javascript

I am building a recipe-database for myself (just to learn programming) and I've setup a base page with bootstrap, python, flask and sqlalchemy. I added the ingredients to the database and it is working fine. Now i can build recipes (another db.class) by adding these ingredients and combine them with the amount of them. I've prepared a form to setup all the required inputs.
For the ingredients i want to have a dropdown with all ingredients availabe in the db (done and working, see below). Now i want to add elements above in a container dynamically by selection of a dropdown-item. I staret by adding static elements first and it is working - when i use "onclick". This is what i have so far:
HTML of the dropdown:
<div id="container">
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Ingredient</label>
<div class="col-sm-2"><input
type="text"
class="form-control"
id="amount"
name="amount">
</div>
<label for="amount" class="col-sm-1 col-form-label">g / ml</label>
<div class="dropdown col-sm-2">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" id="Dropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" onclick="addIngredient()">
Choose Ingredient
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
{% for ingredient in ingredients %}
<a class="dropdown-item" href="#">{{ ingredient.name }}</a>
{% endfor %}
</div>
</div>
</div>
This is the javascript I am calling:
function addIngredient() {
var container = document.getElementById("container");
var div_outer = document.createElement("div");
div_outer.setAttribute("class","form-group row");
container.appendChild(div_outer);
var label = document.createElement("label");
label.innerHTML = "Test";
label.setAttribute("class","col-sm-2 col-form-label");
div_outer.appendChild(label);
var div_inner = document.createElement("div");
div_inner.setAttribute("class","col-sm-2");
div_outer.appendChild(div_inner);
var input = document.createElement("input");
input.innerHTML = "Amount";
input.setAttribute("class","form-control");
div_inner.appendChild(input);
var label_unit = document.createElement("label");
label_unit.innerHTML = "g (ml)";
label_unit.setAttribute("class","col-sm-1 col-form-label");
div_outer.appendChild(label_unit);
var delete_btn = document.createElement("button");
delete_btn.setAttribute("type", "button");
delete_btn.setAttribute("class", "close");
delete_btn.setAttribute("onclick", "");
div_outer.appendChild(delete_btn)
var span = document.createElement("span");
span.setAttribute("aria-hidden", "true");
span.innerHTML = "×"
delete_btn.appendChild(span)
}
As a first step i wanted to replace the text of "label" with the selection of the Dropdown with that:
//Getting Value
var selObj = document.getElementById("Dropdown");
var selValue = selObj.options[selObj.selectedIndex].value;
//Setting Value
label.innerHTML = selValue;
Question 1: But as soon as I change the event from "onclick" to "onchange" it stops working. Why?
Question 2: How can i make the created items identifiable with a unique id, so that i can use the label-text to identify the ingredient-id and add them to the recipe-class. How can i generate that id?
Unfortunately i dont now much Java or HTML yet but i try to learn :)

Question 1:
The onchange attribute fires the moment when the value of the element is changed.
Tip: This event is similar to the oninput event. The difference is that the oninput event occurs immediately after the value of an element has changed, while onchange occurs when the element loses focus. The other difference is that the onchange event also works on elements.
So if you want the onchange to be fired you should consider creating a drop-down list using HTML <select> Tag.
Question 2:
You can easily retrieve the id stored in your db of the item and set it in an attribute by your choice.

Related

Pre-populate current value of WTForms field in order to edit it

I have a form inside a modal that I use to edit a review on an item (a perfume). A perfume can have multiple reviews, and the reviews live in an array of nested documents, each one with its own _id.
I'm editing each particular review (in case an user wants to edit their review on the perfume once it's been submitted) by submitting the EditReviewForm to this edit_review route:
#reviews.route("/review", methods=["GET", "POST"])
#login_required
def edit_review():
form = EditReviewForm()
review_id = request.form.get("review_id")
perfume_id = request.form.get("perfume_id")
if form.validate_on_submit():
mongo.db.perfumes.update(
{"_id": ObjectId(perfume_id), <I edit my review here> })
return redirect(url_for("perfumes.perfume", perfume_id=perfume_id))
return redirect(url_for("perfumes.perfume", perfume_id=perfume_id))
And this route redirects to my perfume route, which shows the perfume and all the reviews it contains.
This is the perfume route:
#perfumes.route("/perfume/<perfume_id>", methods=["GET"])
def perfume(perfume_id):
current_perfume = mongo.db.perfumes.find_one({"_id": ObjectId(perfume_id)})
add_review_form = AddReviewForm()
edit_review_form = EditReviewForm()
cur = mongo.db.perfumes.aggregate(etc)
edit_review_form.review.data = current_perfume['reviews'][0]['review_content']
return render_template(
"pages/perfume.html",
title="Perfumes",
cursor=cur,
perfume=current_perfume,
add_review_form=add_review_form,
edit_review_form=edit_review_form
)
My issue
To find a way to get the review _id in that process and have it in my perfume route, so I can pre-populate my EditReviewForm with the current value. Otherwise the form looks empty to the user editing their review.
By hardcoding an index (index [0] in this case):
edit_review_form.review.data = current_perfume['reviews'][0]['review_content']
I am indeed displaying current values, but of course the same value for all reviews, as the reviews are in a loop in the template, and I need to get the value each review_id has.
Is there a way to do this, before I give up with the idea of allowing users to edit their reviews? :D
Please do let me know if my question is clear or if there's more information needed.
Thanks so much in advance!!
UPDATE 2:
Trying to reduce further my current template situation to make it clearer:
The modal with the review is fired from perfume-reviews.html, from this button:
<div class="card-header">
<button type="button" class="btn edit-review" data-perfume_id="{{perfume['_id']}}" data-review_id="{{review['_id']}}" data-toggle="modal" data-target="#editReviewPerfumeModal" id="editFormButton">Edit</button>
</div>
And that opens the modal where my form with the review is (the field in question is a textarea currently displaying a WYSIWYG from CKEditor:
<div class="modal-body">
<form method=POST action="{{ url_for('reviews.edit_review') }}" id="form-edit-review">
<div class="form-group" id="reviewContent">
{{ edit_review_form.review(class="form-control ckeditor", placeholder="Review")}}
</div>
</form>
</div>
Currently this isn't working:
$(document).on("click", "#editFormButton", function (e) {
var reviewText = $(this)
.parents(div.card.container)
.siblings("div#reviewContent")
.children()
.text();
$("input#editReviewContent").val(reviewText);
});
and throws a ReferenceError: div is not defined.
Where am I failing here? (Perhaps in more than one place?)
UPDATE 3:
this is where the button opens the modal, and underneath it's where the review content displays:
<div class="card container">
<div class="row">
<div class="card-header col-9">
<h5>{{review['reviewer'] }} said on {{ review.date_reviewed.strftime('%d-%m-%Y') }}</h5>
</div>
<div class="card-header col-3">
<button type="button" class="btn btn-success btn-sm mt-2 edit-review float-right ml-2" data-perfume_id="{{perfume['_id']}}" data-review_id="{{review['_id']}}" data-toggle="modal" data-target="#editReviewPerfumeModal" id="editFormButton">Edit</button>
</div>
</div>
<div class="p-3 row">
<div class=" col-10" id="reviewContent">
<li>{{ review['review_content'] | safe }}</li>
</div>
</div>
</div>
You can do this with jQuery as when you open the form, the form will automatically show the review content in there. It will be done by manipulating the dom.
Also, add an id to your edit button, in this example, I have given it an id "editFormButton".
Similarly, add an id to the div in which review content lies so that it is easier to select, I have given it an id "reviewContent"
Similarly, add an id to edit_review_form.review like this edit_review_form.review(id='editReviewContent')
<script>
$(document).on("click", "#editFormButton", function (e) {
var reviewText = $(this)
.parents("div.row")
.siblings("div.p-3.row")
.children("div#reviewContent")
.children()
.text();
$("input#editReviewContent").val(reviewText);
});
</script>
Don't forget to include jQuery.
Also, you can do it with pure javascript. You can easily search the above equivalents on google. This article is a good start!

Getting specific parent element using jQuery using eq()

I would like to be able to dynamically update a quantity field when I click on a '+' (Plus Button) However, depending where I click on the button, I am getting a different result for the parent element.
The HTML that is in use to call the function is below. This is HTML from my frontend developer and I am trying to connect a few things to make this work.
<div id="item-list" class="product_list_container">
<div class="bluedog-flex-container noPaddingLeftRgt payhub-well col-xs-12 col-sm-12 col-lg-12" data-id="32" style="margin-top:0;">
<div class="subtotal-div col-xs-12 col-sm-12 col-lg-12">
<div class="bluedog-inner-flex-container product_info_container col-xs-12 col-sm-12 col-lg-12">
<div class="order-overview-left noPaddingLeft alignLeft col-xs-4 col-sm-4 col-lg-4">Macbook Pro # 50.00</div>
<div class="quantity_range_div col-xs-6 col-sm-6 col-lg-6">
<div class="value-button createPage_decrease_qty decrease_value_btn" style="display:inline;" onclick="decreaseQty(event)"><i class="far fa-minus"></i></div>
<input type="number" class="itm_qty" id="number" value="1">
<div class="value-button increase_value_btn" style="display:inline;" onclick="increaseQty(event)"><i class="far fa-plus"></i></div>
</div>
<input type="hidden" name="item_price" data-qty="1" data-prod-name="Macbook Pro" data-id="32" data-tax="0" value="50.00"><a class="popup-close-btn order-overview-right close_btn_plans col-xs-2 col-sm-2 col-lg-2" onclick="removeItem(event)"> <i class="far fa-times-circle hosted_small_delete"></i></a>
</div>
</div>
</div>
</div>
What I am trying to achieve is, when I click on the Plus Button, (Screenshot attached) https://prnt.sc/oq770q the value will increase.
Originally, I was just using the following code to try and get the parent DIV id, which in this case is item-list
var master = $(e.target).parents().eq(5).attr('id'); // Name of item-list container
However, was receiving unexpected results, whereby if I tapped on the outside of the plus button, the surround grey area, it would not increase, but only if I tapped directly on the plus icon itself.
I noticed it was picking up a different element and the eq(5) was returning an undefined value. As you will see in in the script below, I have put in an if statement for a class check to see which part of the button has been pressed and this returns the expected results.
However, I dont' see this as scalable and transferable if someone was to change the FontAwesome Tag, as the class would likely be different.
function increaseQty(e) {
var clicked = e.path[0];
if ($(clicked).hasClass('fa-plus')) {
var master = $(e.target).parents().eq(5).attr('id'); // Name of item-list container
} else {
var master = $(e.target).parents().eq(4).attr('id'); // Name of item-list container
}
var input = $(e.target).parent().parent().find('input.itm_qty');
var qty = $(input).val();
var newQty = parseInt(qty) + 1;
$(input).val(newQty);
updatePrices(master);
}
I would like to know how, if possible, that regardless where on the container that is being clicked, the plus icon or the gray area around it, to increase the value, without having to do a dirty class check.
Many thanks,
David.
Based on you html code I am seeing like
<input type="number" class="itm_qty" id="number" value="1">
In this line you have id number so you can easily update.Based on your description your above line repeat. So you modify your increaseQty like that.
function increaseQty(e) {
var input = $(e.target).prev('input.itm_qty');
var qty = $(input).val();
var newQty = parseInt(qty) + 1;
$(input).val(newQty);
updatePrices(master);
}
If you see in your code increaseQty bind with div element and input itm_qty is sibling of div. So I am hoping above code works.
So to answer my own question, with some help from #Dhiren (Thank You)
I found out that I could assign FontAwesome Icon to the class of a SPAN. Therefore I have changed the Divs which contained the FontAwesome Icons to Spans, and assigned the FA Icon class to the span, removing the element.
<div class="quantity_range_div col-xs-6 col-sm-6 col-lg-6">
<span class="value-button createPage_decrease_qty decrease_value_btn far fa-minus" style="display:inline;" onclick="decreaseQty(event)"></span>
<input type="number" class="itm_qty" id="number" value="1">
<span class="value-button increase_value_btn far fa-plus" style="display:inline;" onclick="increaseQty(event)"></span>
</div>
In doing so I was able to use a single parents().eq(4) line of code, as the target will now always be a span, and not a Div element or an element.
function increaseQty(e) {
var master = $(e.target).parents().eq(4).attr('id');
var input = $(e.target).prev('input.itm_qty');
var qty = $(input).val();
var newQty = parseInt(qty) + 1;
$(input).val(newQty);
updatePrices(master);
}
Dhiren, thank you for the prev() suggestion as it has helped me better refactor not only this function, but more also! I appreciate the help and support.
David

Input value onchange does not remove error class

I have form with some inputs Adults and child. When I select the values of Adults and child maximum than required and I have logic to display error message. If I change values to less than required (Using onchange) the error message does not go away.
I am able to check while submitting values. My code is working for submit function and having issue while onchange the input of Adults and child. The error message still remains.
Should onchange function inside submit function?
noLink : function() {
$('.nolink').on('click', function(e){
e.preventDefault();
});
},
var $adultsInput = $('#bookingAdultCount'),
$childrenInput = $('#bookingChildrenCount'),
$bookingError = $('#booking-error'),
$bookingForm = $('#bookingWidgetForm'),
$bookingForm.submit(function(event) {
// logic for submit
});
// onchange event that does not work
$adultsInput.on('change', function() {
$adultsInput.parent().removeClass('booking-error');
$childrenInput.parent().removeClass('booking-error');
});
<div class="filter__options-wrapper__filter-option">
<div class="input-container">
<input id="bookingAdultCount" class="initGrow doNotClose" name="Adults" placeholder="Adults" min="1" max="10" readonly>
</div>
<ul class="filter__option__guests-list growMe filter__options-wrapper__single-choice-list open">
<li><a class="nolink" href="#0" data-value="1" data-input-target="bookingAdultCount">1 Adult</a></li>
<li><a class="nolink" href="#0" data-value="2" data-input-target="bookingAdultCount">2 Adults</a></li>
<li><a class="nolink" href="#0" data-value="3" data-input-target="bookingAdultCount">3 Adults</a></li>
<li><a class="nolink" href="#0" data-value="4" data-input-target="bookingAdultCount">4 Adults</a></li>
<li><a class="nolink" href="#0" data-value="5" data-input-target="bookingAdultCount">5 Adults</a></li>
</ul>
</div>
The problem you are having is because change and input events aren't fired when you set the value of an input in JavaScript (which I presume you are doing by way of the <a class="nolink"....>.
Here is some code to demonstrate:
var test = document.getElementById('test');
var btn = document.getElementById('changeval');
//use input instead of change to demonstrate. Input is the same as change except for every character added to the text field.
test.addEventListener('input', (evt) => { console.log(evt.target.value); });
btn.addEventListener('click', (evt) => {
test.value += "a";
});
<input type='text' id='test' value='' />
<button id='changeval'>Change Value</button>
You will have to move your change logic to wherever you are handling the click events for those links.
FYI, The reason for this behavior, I believe, is if you were to change the value of the field in javascript, inside the change event listener for that very field, you would cause an infinite loop. So browser vendors don't expose the change event if the change source was JS (because you can just move all relevant logic to the place where you were setting that value anyhow).

jQuery script works just for one button on the product catalog

I have a product catalog and I want to save on localstorage the products selected by the user.
The jquery script only gets the first product on each page on each click... it simply ignores the rest of the products, and the console prints the same object.
Here is my HTML+TWIG code
{% for products in pagination %}
<div class="product col-sm-6 col-lg-6 col-md-6 hero-feature">
<div id="{{attribute(products ,'id')}}" class="product thumbnail">
<img id="prodImage" class="img-responsive img-rounded" src="{{attribute(products ,'image')}}" style="width:150px;height:150px" >
<div class="product caption">
<h4 id="prodPrice" class="product pull-right"><b>{{ attribute (products, 'price') }}Lei</b></h4>
<h4 id="prodName" style="height:100px;width:200px;">
<a id="prodLink"
href="{{ attribute (products, 'affiliatelink') }}"
target="_blank">{{attribute ( products, 'name') }}</br></a>
</h4>
</div>
<div class="add-to-cart" class="product" >
<button id="buttonProd" class="oneButton btn btn-danger " value="Save" type="button">Adauga</button>
</div>
</div>
</div>
{% endfor %}
Here is the jquery script
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<script type='text/javascript' defer="defer">
$(document).ready(function() {
var prodId = $('.thumbnail').attr("id");
$("#buttonProd").on("click", 'button', function(event){
event.preventDefault();
var products =
{
prodID :$('.thumbnail').attr("id"),
prodName :$('#prodName').text(),
prodPrice :$('#prodPrice').text(),
prodImage :$('#prodImage').attr('src'),
prodLink :$('#prodLink').attr('href')
};
localStorage.setItem('products-' + prodId, JSON.stringify(products));
var retrievedObject = JSON.parse(localStorage.getItem('products-' + prodId));
console.log('retrievedObject: ', retrievedObject);
});
});
</script>
How can I make the script take each product proprieties on click. Thank you in advance.
In JQuery, the assumption is made that all ID's will be unique. Since you're repeating "#buttonProd", JQuery will only select the first one to bind the action to. If you want to bind to multiple elements, you'll either have to give each button a unique ID or use some other selector to attach your jQuery functionality.
From the documentation for the ID Selector:
Calling jQuery() (or $()) with an id selector as its argument will return a jQuery object containing a collection of either zero or one DOM element.
As the other answers have eluded to you should be using a different selector. I recommend simply adding a descriptive class to each element you wish to grab data from.
<div class="products">
<div class="product">
<div class="productName">First Product</div>
<div class="productPrice">5.00</div>
</div>
<div class="product">
<div class="productName">Second Product</div>
<div class="productPrice">4.00</div>
</div>
<button id="buttonProduct">Log Product Info</button>
</div>
If you notice in the above HTML each div that contains a product's name or a product's price shares the same class productName and productPrice respectively. In addition each container class for each product has the same class as well: product.
This will allow us to utilize the JQuery class selector $(".") to iterate over each product container. We do this using the .each() function. We use the .find() function to locate productName and productPrice in each iteration of the loop.
$(document).ready(function() {
$("#buttonProduct").click(function(){
var products = [];
// Notice the dot in $(".product") that is the class selector
// the .each iterates over every element that matches the preceding selector
$(".product").each(function(){
products.push({
// The .find() selects an element inside $(this)
// that matches the parameter (either .productName or .productPrice
productName : $(this) .find('.productName').html(),
productPrice : $(this).find('.productPrice').html()
});
});
console.log(products);
});
});
For a working example of this check out this jsfiddle. (I noticed you had console.log() in your code so that's where I output the result.)
You want to save the product in localStorage on clicking of corresponding button right?
for that instead of binding click event via jquery, put it in html and move the click code to a function saveProduct()
HTML:
<button id="buttonProd" class="oneButton btn btn-danger " value="Save" type="button" onclick="saveProduct({{attribute(products ,'id')}})">Adauga</button>
JS:
function saveProduct(event, prod_id){
event.preventDefault();
var products =
{
prodID :prod_id,
prodName :$('#'+prod_id+' #prodName').text(),
prodPrice :$('#'+prod_id+' #prodPrice').text(),
prodImage :$('#'+prod_id+' #prodImage').attr('src'),
prodLink :$('#'+prod_id+' #prodLink').attr('href')
};
localStorage.setItem('products-' + prod_id, JSON.stringify(products));
var retrievedObject = JSON.parse(localStorage.getItem('products-' + prod_id));
console.log('retrievedObject: ', retrievedObject);
});
}
You need to change #buttonProd from an id to a class. An id is only supposed to appear once on a page, so jQuery will only apply it to one. Change it to a class in your markup and your script and it should work fine. Same for prodName, prodPrice, prodImage, and prodLink. Anything that will be going inside the loop needs to be a class, and any id should be unique, like you have {{attribute(products ,'id')}}

Getting value from input control using jQuery

I am using the teleriks treeview control (asp.net mvc extensions), where I may have up to three children nodes, like so (drumroll...... awesome diagram below):
it has its own formatting, looking a bit like this:
<%=
Html.Telerik().TreeView()
.Name("TreeView")
.BindTo(Model, mappings =>
{
mappings.For<Node1>(binding => binding
.ItemDataBound((item, Node1) =>
{
item.Text = Node1.Property1;
item.Value = Node1.ID.ToString();
})
.Children(Node1 => Node1.AssocProperty));
mappings.For<Node2>(binding => binding
.ItemDataBound((item, Node2) =>
{
item.Text = Node2.Property1;
item.Value = Node2.ID.ToString();
})
.Children(Node2 => Node2.AssocProperty));
mappings.For<Node3>(binding => binding
.ItemDataBound((item, Node3) =>
{
item.Text = Node3.Property1;
item.Value = Node3.ID.ToString();
}));
})
%>
which causes it to render like this. I find it unsual that when I set the value it is rendered in a hidden input ? But anyway:...
<li class="t-item">
<div class="t-mid">
<span class="t-icon t-plus"></span>
<span class="t-in">Node 1</span>
<input class="t-input" name="itemValue" type="hidden" value="6" /></div>
<ul class="t-group" style="display:none">
<li class="t-item t-last">
<div class="t-top t-bot">
<span class="t-icon t-plus"></span>
<span class="t-in">Node 1.1</span>
<input class="t-input" name="itemValue" type="hidden" value="207" />
</div>
<ul class="t-group" style="display:none">
<li class="t-item">
<div class="t-top">
<span class="t-in">Node 1.1.1</span>
<input class="t-input" name="itemValue" type="hidden" value="1452" />
</div>
</li>
<li class="t-item t-last">
<div class="t-bot">
<span class="t-in">Node 1.1.2</span>
<input class="t-input" name="itemValue" type="hidden" value="1453" />
</div>
</li>
</ul>
</li>
</ul>
What I am doing is updating a div after the user clicks on a certain node. But when the user clicks on a node, I want to send the ID not the Node text property. Which means I have to get it out of the value in these type lines <input class="t-input" name="itemValue" type="hidden" value="1453" />, but it can be nested differently each time, so the existing code I ahve doesn't ALWAYS work:
<script type="text/javascript">
function TreeView_onSelect(e) {
//`this` is the DOM element of the treeview
var treeview = $(this).data('tTreeView');
var nodeElement = e.item;
var id = e.item.children[0].children[2].value;
...
</script>
So based on that, what is a better way to get the appropriate id each time with javascript/jquery?
edit:
Sorry to clarify a few things
1) Yes, I am handling clicks to the lis of the tree & want to find the value of the nested hidden input field. As you can see, from the telerik code, setting item.Value = Node2.ID.ToString(); caused it to render in a hidden input field.
I am responding to clicks anywhere in the tree, therefore I cannot use my existing code, which relied on a set relationship (it would work for first nodes (Node 1) not for anything nested below)
What I want is, whenever there is something like this, representing a node, which is then clicked:
<li class="t-item t-last">
<div class="t-bot">
<span class="t-in">Node 1.1.2</span>
<input class="t-input" name="itemValue" type="hidden" value="1453" />
</div>
</li>
I want the ID value out of the input, in this case 1453.
Hope this now makes a lot more sense.
if possible would love to extend this to also store in a variable how nested the element that is clicked is, i.e. if Node 1.1.2 is clicked return 2, Node 1.1 return 1 and node 1 returns 0
It's a little unclear what you're asking, but based on your snippet of JavaScript, I'm guessing that you're handling clicks to the lis of the tree & want to find the value of the nested hidden field? If so, you want something like this:
function TreeView_onSelect(e) {
var id = $(e.item).find(".t-input:first").val();
}
Edit: In answer to your follow-up question, you should be able to get the tree depth with the following:
var depth = $(e.item).parents(".t-item").length;
In jQuery you can return any form element value using .val();
$(this).val(); // would return value of the 'this' element.
I'm not sure why you are using the same hidden input field name "itemValue", but if you can give a little more clarity about what you are asking I'm sure it's not too difficult.
$('.t-input').live('change',function(){
var ID_in_question=$(this).val();
});

Categories

Resources