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).
Related
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.
My HTML code (delivery methods list of radio buttons) is:
<ul id="shipping_method" class="woocommerce-shipping-methods">
<li>
<input type="radio" name="shipping_method[0]" data-index="0" id="shipping_method_0_local_pickup8" value="local_pickup:8" class="shipping_method" checked="checked"><label for="shipping_method_0_local_pickup8">Local pickup</label>
</li>
<li>
<input type="radio" name="shipping_method[0]" data-index="0" id="shipping_method_0_edostavka-package-door-stock7138" value="edostavka-package-door-stock:7:138" class="shipping_method"><label for="shipping_method_0_edostavka-package-door-stock7138">Postamat: <span class="woocommerce-Price-amount amount">265 <span class="woocommerce-Price-currencySymbol">₽</span></span></label>
</li>
<li>
<input type="radio" name="shipping_method[0]" data-index="0" id="shipping_method_0_flat_rate1" value="flat_rate:1" class="shipping_method"><label for="shipping_method_0_flat_rate1">Courier delivery <span class="woocommerce-Price-amount amount">350 <span class="woocommerce-Price-currencySymbol">₽</span></span></label>
</li>
</ul>
It cannot changed (Woocommerce HTML generation). At least I do not want to change it.
My task is add Javascript handlers on selection change event:
jQuery(document).ready(function($){
let methods = jQuery('.shipping_method’);
methods.each(function(index) {
$(this).bind('change', function() {
alert('Change event fired for ' + this.val());
});
});
});
Handlers are not fired when delivery method (radio button with class == ’shipping_method’) changed.
Change handler for whole list of radio also doesn’t work:
methods.bind('change', function() {
alert(this.val());
});
What’s wrong?
Firstly in your JS you're using the wrong type of apostrophe, ’ instead of ', which is unsupported in JS, to close the jQuery selector string.
let methods = jQuery('.shipping_method’); // change the last apostrophe on this line
Secondly, in the change event handler this refers to the Element object which has no val() method. You need to call it on a jQuery object, so use $(this).val() instead.
Lastly, bind() is deprecated and should not be used. Use on() instead, and make sure you're using an up to date version of jQuery, ideally at least something 3.x. You also don't need the each() loop, as you can bind event handlers to a collection of Elements in a single jQuery object. Try this:
jQuery(document).ready(function($) {
$('.shipping_method').on('change', function() {
console.log('Change event fired for ' + $(this).val());
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul id="shipping_method" class="woocommerce-shipping-methods">
<li>
<input type="radio" name="shipping_method[0]" data-index="0" id="shipping_method_0_local_pickup8" value="local_pickup:8" class="shipping_method" checked="checked"><label for="shipping_method_0_local_pickup8">Local pickup</label>
</li>
<li>
<input type="radio" name="shipping_method[0]" data-index="0" id="shipping_method_0_edostavka-package-door-stock7138" value="edostavka-package-door-stock:7:138" class="shipping_method"><label for="shipping_method_0_edostavka-package-door-stock7138">Postamat: <span class="woocommerce-Price-amount amount">265 <span class="woocommerce-Price-currencySymbol">₽</span></span></label>
</li>
<li>
<input type="radio" name="shipping_method[0]" data-index="0" id="shipping_method_0_flat_rate1" value="flat_rate:1" class="shipping_method"><label for="shipping_method_0_flat_rate1">Courier delivery <span class="woocommerce-Price-amount amount">350 <span class="woocommerce-Price-currencySymbol">₽</span></span></label>
</li>
</ul>
It's worth noting that both of these issues were visible in the console, which is where you should look first when trying to debug JS logic.
#Rory: Awesome! Yes, your advice works great!
Just so you know there's a way to get which shipping method is clicked using a WooCommerce hook:
function prefix_detect_chosen_shipping_method(){
if ( ! defined( 'DOING_AJAX' ) ){
return;
}
$chosen_method = WC()->session->get( 'chosen_shipping_methods' );
if( ! is_array( $chosen_method ) ){
return;
}
$chosen_method = $chosen_method[0];
// Do something with the chosen method.
}
add_action( 'woocommerce_cart_calculate_fees', 'prefix_detect_chosen_shipping_method', 10, 2 );
However, this wouldn't work for me because I needed to fire some javascript based on the selected shipping method. Here's how I accomplished it using some JavaScript that is present only on the checkout page:
(function( $ ) {
'use strict';
$( document ).ready( function(){
$( document.body ).on( 'updated_checkout', () =>{
let prefixShippingMethods = document.querySelectorAll('#shipping_method .shipping_method');
for (const prefixShippingMethod of prefixShippingMethods ){
// We have to check for the hidden type as well because if only one shipping method is available WC doesn't show radio buttons.
if( lpacShippingMethod.checked || lpacShippingMethod.type === 'hidden' ){
// Do something now that you know which method is checked
// NOTE! The shipping method name will not be exact, you will have to use indexOf() for comparisons
if( prefixShippingMethod.value.indexOf('local_pickup') ){
//This will happen when Local Pickup is selected
}
}
}
});
});
})( jQuery );
You can clean up JS as you wish, I couldn't detect this event in vanilla JS, thats why I'm using jQuery here.
I have the following HTML:
<div class="uploadimage" >
<img src="test.png" />
<div class="form-inline" >
<button type="button" class="fileupload"> <i class="fa fa-folder-open"></i>
<input type="file" class="upload">
</button>
<button type="button" class="btnupload"> <i class="fa fa-cloud-upload"></i> </button>
</div>
</div>
in jQuery I have the following code:
$(".fileupload input").change(function () {
var input = this;
// this works but I think there is a better way
var image = $(this).closest('.form-inline').siblings('img');
});
I already get the image element but Im sure the peformance of that is no good.
Any clue if there is a better way?
There are various ways you could do this,
One of the way is to find the container div in your case which contains relevant img
$(".fileupload input").change(function () {
// this works but I think there is a better way
var image = $(this).closest('.uploadimage').find('img');
});
For your concern of which way could be better,
.siblings() : If you refer the documentation here https://api.jquery.com/siblings/ , this method matches all the specified selector and creates a new elements. This method should be used according to me only when you are going to manipulate an element, like changing css and properties. Internally, ofcourse it might be triggering find to match / get the element.
.closest() : This https://api.jquery.com/closest/ will be better in your case as compare to .sibilings(). It will not create a new element also will find only required element you are trying to search in the DOM.
You could always use Event Delegation to access both the bound element and it's descendants that match the selector. Here is an example of a delegated event that allows me to target different elements from the event object with jQuery.on().
$('.uploadimage').on('change', 'input', function(event) {
console.log(event);
var input = $(event.currentTarget);
console.log(input);
var input = $(event.target);
console.log(input);
var image = $(event.delegateTarget).find('img');
console.log(image);
var image = $(event.delegateTarget.firstElementChild);
console.log(image);
}).find('input').change();
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"/>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="uploadimage" >
<img src="test.png" />
<div class="form-inline" >
<button type="button" class="fileupload"> <i class="fa fa-folder-open"></i>
<input type="file" class="upload">
</button>
<button type="button" class="btnupload"> <i class="fa fa-cloud-upload"></i> </button>
</div>
</div>
When the browser triggers an event or other JavaScript calls jQuery's
.trigger() method, jQuery passes the handler an Event object it can
use to analyze and change the status of the event. This object is a
normalized subset of data provided by the browser; the browser's
unmodified native event object is available in event.originalEvent.
For example, event.type contains the event name (e.g., "resize") and
event.target indicates the deepest (innermost) element where the event
occurred.
When jQuery calls a handler, the this keyword is a reference to the
element where the event is being delivered; for directly bound events
this is the element where the event was attached and for delegated
events this is an element matching selector. (Note that this may not
be equal to event.target if the event has bubbled from a descendant
element.) To create a jQuery object from the element so that it can be
used with jQuery methods, use $( this ).
I would usually do like this:
You can add/generate id to the img element, and add a reference to in in the a data attribute of the btn or any other item what has to refer to it. This is easy even if generating these elements in a loop.
HTML
<div class="uploadimage" >
<img id="testimg1" src="test.png" />
<div class="form-inline" >
<button type="button" class="fileupload" data-imgitem="testimg1"> <i class="fa fa-folder-open"></i>
<input type="file" class="upload">
</button>
<button type="button" class="btnupload"> <i class="fa fa-cloud-upload"></i> </button>
</div>
</div>
JS
$(".fileupload input").change(function () {
var input = this
var reference = $(this).data(imgitem); // get id
var image = $('#' + reference); // get DOM element by id
});
I have a input field, whose value is set by the controller by response.query, within the template I also have two links which perform read and delete calls to the id entered in the query. Here is the template.
<div class="input-group">
<input type="text" th:value="*{response.query}" />
<div class="input-group-btn">
<ul class="dropdown-menu dropdown-menu-right">
<li>Read</li>
<li>Delete</li>
</ul>
</div>
</div>
Unfortunately, the href parameter always gets passed as null. Is there alternate approach to this?
Well let me see if I understand what you want :
For example you put '5' in the text input. Now you want to href to:
http://localhost:8080/read/5
And you have a controller which looks something like this :
#RequestMapping(value="/read/{id}", method=RequestMethod.GET)
public String read(#PathVariable String id, Model model) {
// your logic goes here
return "read";
}
Firstly my suggestion is that you can just change your code from :
<li>Read</li>
to:
<li>Read</li>
You can't get response.query because it's on client side then you call it. In your case you need the javascript, which will be construct your link's URL (othervise you need a form to submit value of the input) :
<div class="input-group">
<input type="text" id="myval" th:value="*{response.query}" />
<div class="input-group-btn">
<ul class="dropdown-menu dropdown-menu-right">
<li>Read</li>
<li>Delete</li>
</ul>
</div>
</div>
<script th:inline="javascript">
/*<![CDATA[*/
function onReadClick() {
var id = $('#myval').value;
/*[+
var myUrl = [[#{/read}]] + id;
+]*/
window.location.href = myUrl;
});
/*]]>*/
</script>
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();
});