let's say I have a form that contains several "select" element that the User can choose.
when ready to submit, I need to find all the "select" that have changed.
how can I do it?
for example:
<form>
<input type="text" name="a"/>
<select name="b">...</select>
<select name="c">...</select>
<select name="d">...</select>
</form>
Using jQuery, something like this should work:
$('select').change(function() {
$(this).addClass('changed');
});
$('form').submit(function() {
var changed_selects = $('select.changed');
// do what you want with the changed selects
});
I would take advantage of the defaultSelected property on HTMLOptionElement instead of trying to keep track of selects that have changed:
form.onsubmit = function() {
var selects = form.getElementsByTagName("select")
, i
, changedSelects = []
, selected
, select;
/* Iterate over all selects in the form: */
for (i = 0; i < selects.length; i++) {
select = selects[i];
/* Get the currently selected <option> element: */
selected = select[select.selectedIndex];
/* Determine if the currently selected option is the one selected by default: */
if (!selected.defaultSelected) {
changedSelects.push(select);
}
}
alert(changedSelects.length);
}
You can iterate over all of the select elements on your form, determine if the selected option is the one that was selected by default, and push each one whose selected option wasn't the default into an array.
Example: http://jsfiddle.net/andrewwhitaker/abFr3/
You could register an event listener (using onchange, etc.) to determine if any changes were made.
Alternatively, you could keep a private copy of all of the previous values, then run a comparison before submit to check for changes.
Do something like:
var selectedVal = $("select[name='a'] option:selected").val();
//check this with your default selected val
You can attach the onchange event listener to each <select> element. When the event is triggered, you can store the actual elements in an a temporary array and reference it later when you are ready to submit. Note that the array can hold the actual DOM elements.
You need to clarify what you mean by "the select have changed".
If you mean have changed from their default value, then you should have one option on each select (usually the first) set as the default selected option. Then you can iterate over the selects and see if the option with the selected attribute (or where the defaultSelected property is true) is the selected option, e.g.
<script>
function anyChanged(){
var select, selects = document.getElementsByTagName('select');
for (var i=0, iLen=selects.length; i<iLen; i++) {
select = selects[i];
if (!select.options[select.selectedIndex].defaultSelected) {
// the selected option isn't the default
alert('select ' + (select.id || select.name) + ' changed');
}
}
}
</script>
<select name="one">
<option selected>one
<option>two
<option>three
</select>
<button onclick="anyChanged()">Check</button>
However, if you want to see if the user has changed the selected option based on some other logic, you need to say what it is.
Related
In a bespoke shopping cart, I pull from my database a list of options that a particular product has. The number of these options can vary by product.
I then turn that list of options into a JavaScript array.
An example with 3 options:
{"Small":{"Super":{"Pack":"parta","Case":"parte"},"Maxi":{"Pack":"partb","Case":"partf"}},"Large":{"Super":{"Pack":"partc","Case":"partg"}},"X Large":{"Maxi":{"Pack":"partd"}}}
Using the above, I would now like to generate an HTML select field, listing all the "first" options:
<select>
<option value="Small">Small</option>
<option value="Large">Large</option>
<option value="X Large">X Large</option>
</select>
Once the user has selected the first option, I then need another <select> box to then load with options that are available for their selection. So, if they selected "Small" from above, the new select box would be:
<select>
<option value="Super">Super</option>
<option value="Maxi">Maxi</option>
</select>
Finally, when they select from this list, a 3rd select box loads in the final options, along with the part numbers as values:
<select>
<option value="parta">Pack</option>
<option value="parte">Case</option>
</select>
The number of options can vary, from zero to 4. But, each time when options are available, I need to pull the part number based on the users selection. The part number doesn't necessarily need to be the value of the last select, it can be pushed to a new hidden variable.
I can achieve this using ajax, by making an ajax call every time a selection is chosen, but can it be done via JavaScript / jQuery, without having to make ajax calls, given that the array is on the page and available to use?
When you dynamically create the select element, also determine which "node" in your tree structure goes with that element, and use it to:
add a default "Please select..." option to the select element
populate the select element further with the real options
determine the deeper node when an option is selected (also when the initial selection is made when the element is created), and use it to create the next select element with the same function
This cascade stops when the deeper node does not exist (when "Please select..." is selected) or it happens to be a string and not an object.
Here is some code for inspiration:
let optionTree = {"Small":{"Super":{"Pack":"parta","Case":"parte"},"Maxi":{"Pack":"partb","Case":"partf"}},"Large":{"Super":{"Pack":"partc","Case":"partg"}},"X Large":{"Maxi":{"Pack":"partd"}}};
let container = document.querySelector("#container");
addSelector(optionTree);
function addSelector(node) {
let select = document.createElement("select");
// Start with the default option:
let option = document.createElement("option");
option.text = "Please select...";
select.add(option);
for (let key in node) { // Populate the select element
let option = document.createElement("option");
option.value = key;
option.text = key;
select.add(option);
}
container.appendChild(select); // Add it to the page
function change() {
// Remove select elements that come after the selection
while (select.nextElementSibling) {
select.nextElementSibling.remove();
}
let key = select.value;
if (node[key] && typeof node[key] !== "string") {
addSelector(node[key]); // Create the next select element(s)
}
}
// Call the above function whenever a selection is made
select.addEventListener("change", change);
change(); // ... and also call it now
}
<div id="container"></div>
Assuming you already have the three select elements in the DOM, you can populate the first select, and add a change event listener to both the first and second to achieve this. Try this
let $s1 = document.querySelector('#select-1');
let $s2 = document.querySelector('#select-2');
let $s3 = document.querySelector('#select-3');
let object = {"Small":{"Super":{"Pack":"parta","Case":"parte"},"Maxi":{"Pack":"partb","Case":"partf"}},"Large":{"Super":{"Pack":"partc","Case":"partg"}},"X Large":{"Maxi":{"Pack":"partd"}}};
// add options to first select
$s1.innerHTML = '<option></option>'; // empty select
Object.keys(object).forEach(val => $s1.append(new Option(val, val))); // append children
$s1.dispatchEvent(new Event('change')); // trigger change event
// listen to change event on first select/get options for second select
$s1.addEventListener('change', function(e){
$s2.innerHTML = '<option></option>'; // empty select
// append children
Object.keys(object[e.target.value] ?? []).forEach(val => {
$s2.appendChild(new Option(val, val));
});
// trigger change event
$s2.dispatchEvent(new Event('change'));
});
// listen to change event on second select/get options for third select
$s2.addEventListener('change', function(e){
$s3.innerHTML = '<option></option>'; // empty select
// append children
Object.entries(object[$s1.value]?.[e.target.value] ?? []).forEach(([key, val]) => {
$s3.appendChild(new Option(key, val));
});
});
select {min-width:80px}
<select id="select-1"></select>
<select id="select-2"></select>
<select id="select-3"></select>
I am creating a form builder script. I have a select input where the user can select the form element they want to use, depending on their selection ("select", "checkbox" or "radio") another form field is displayed allowing users to input their options.
Users can create as many instances of form elements as they want, so each select input has a dynamically created id that corresponds to the id of the hidden form field. I then use jQuery to determine whether the "options" field should be hidden or not (triggered on change of the form elements select input).
Currently, for every instance, I have the following code addedabove the select input:
<script>
jQuery(document).ready(function($) {
var arr = ['select', 'checkbox', 'radio'];
var thisForm = 'select.input-type-118';
function showHideSelect() {
var val = $(thisForm + ' option:selected').val();
var selectOptions = $('#select-options-118')
if (arr.indexOf(val) >= 0) {
selectOptions.show();
} else {
selectOptions.hide();
}
}
showHideSelect();
$(thisForm).change(function() {
showHideSelect();
});
});
</script>
Where var thisForm and var selectOptions are added dynamically and refer to the select option below this script.
I'm wondering if there is a better way to do this rather than repeat several instances of this, at the moment, a users page cold look like this:
<script>
...
</script>
<select>
...
</select>
<textarea>
This is hidden depending on the select option
</textarea>
<script>
...
</script>
<select>
...
</select>
<textarea>
This is hidden depending on the select option
</textarea>
<script>
...
</script>
<select>
...
</select>
<textarea>
This is hidden depending on the select option
</textarea>
...etc...etc
My concern is that I don't think it's best practice to have so many instances of the same script, but I'm unsure how to write a global script that will allow me to show/hide the textarea on an individual basis.
I have shown a more accurate depiction of my workings on this jsfiddle here:
https://jsfiddle.net/46stb05y/4/
You can use Event Delegation Concepts. https://learn.jquery.com/events/event-delegation/
With this you can change your code to
$(document).on('change','select',function() { //common to all your select items
showHideSelect($(this)); // passing the select element which trigerred the change event
});
This will work even on the select items that are added dynamically
You must change your function to receive the element as the parameter.
function showHideSelect($selectElement) {
var val = $selectElement.val();
var selectOptionsId = $selectElement.attr('class').replace('input-type','select-options');
var selectOptions = $("#"+selectOptionsId);
if (arr.indexOf(val) >= 0) {
selectOptions.show();
} else {
selectOptions.hide();
}
}
Here is the Working JsFiddle
I use select2 for selectbox and made <optgroup> selectable, but I can't get the value when I select it.
It's very important to get all options of the <optgroup> when you type optgroup name, as a result I can't replace <optgroup> with ususal <option>.
See jsfiddle example http://jsfiddle.net/iptspl/41r158dm/1/
You can try using the choice value on the event (if it exists) then check that for children and then combine their values.
on("select2-selecting", function(e) {
var value, choice = e.choice;
if(choice.children && choice.children.length > 0) {
value = [];
$(choice.children).each(function(index, child) {
value.push($(child.element).val());
});
value = value.join(",");
} else {
value = e.val;
}
alert(value);
});
At any rate, whatever you do you will most likely need to do something similar which is detect if it is an optgroup and then traverse the children and create the value.
I have a PHP page that creates multiple selects depending on how many the page before it gives it and creates the same number of options that there are selected (it's to choose the priority).
<select name="select1" id="idSelect" onchange="javascript:SelectOnChange();">
<option value="Select one">Select one</option>
<option value="1">1</option>
<option value="2">2</option>
</select>
What I want to do is, whenever the user chooses an option that was already selected in another selection, to show an alert in order to let them know that their choice has already been taken and deny the change.
I want to compare the currently selected option to every previously selected option every time the user selects something from every select.
Basically your looking for a change event to loop through each select and notify duplicates.
$("select").change(function() {
var currentSelection = this.value;
$("select").each(function() {
if (this.value == currentSelection) {
alert("you've already chosen this!");
}
});
});
Something like this should work
Listen for change events
Get the element's seletedIndex
Grab all of the selects with getElementsByTagName()
Loop through and get the selected index
Compare to see if used
This could maybe work :
var $selects = $('select');
// WHen a select is changed.
$selects.onchange(function() {
// Storing it's selected value, assuming there is only one per select.
var value = $(this).selected().first().attr('value');
var $changedSelect = $(this);
// For each select...
$selects.each(function() {
var $otherSelect = $(this);
// ...that is not the one that just changed.
if( ! $otherSelect.is($changedSelect)) {
// If that other select's selected value is the same as the changed one's
if($otherSelect.selected().first().attr('value') === value) {
alert('...');
}
}
});
}
I didn't try it though, you might have to change a few details in it if it doesn't work.
I have a form UI whereby several sections require duplicate HTML select list to be updated dynamically from a single, dynamically-updatable select list.
The dynamically-updatable list works just fine, in that new options can be added and removed on-the-fly. I can then get this update to propagate through each of the duplicate lists using JQuery .find(). I have even added a bit of logic to maintain the currently selected index of the original select list.
What I'm not able to do is maintain the selected state of each of the duplicate select lists as new options are added and removed from the original select list. As each update to the original select list iterates through each duplicate select list, they lose their currently selected option index.
Here is an example of my conundrum--*EDIT--I would encourage you to try and execute the code I've provided below and apply your theories before suggesting a solution, as none of the suggestions so far have worked. I believe you will find this problem a good deal trickier than you might assume at first:
<form>
<div id="duplicates">
<!--// I need for each of these duplicates to maintain their currently selected option index as the original updates dynamically //-->
<select>
</select>
<select>
</select>
<select>
</select>
</div>
<div>
<input type="button" value="add/copy" onclick="var original_select = document.getElementById('original'); var new_option = document.createElement('option'); new_option.text = 'Option #' + original_select.length; new_option.value = new_option.text; document.getElementById('original').add(new_option); original_select.options[original_select.options.length-1].selected = 'selected'; updateDuplicates();" />
<input type="button" value="remove" onclick="var original_select = document.getElementById('original'); var current_selected = original_select.selectedIndex; original_select.remove(original_select[current_selected]); if(original_select.options.length){original_select.options[current_selected < original_select.options.length?current_selected:current_selected - 1].selected = 'selected';} updateDuplicates();" />
<select id="original">
</select>
</div>
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.min.js"></script>
<script type="text/javascript">
function updateDuplicates(){
$("#duplicates").find("select").html($("#original").html());
}
</script>
</form>
It is important to note that the duplicate HTML select lists should remain somewhat arbitrary, if at all possible (i.e.; no ID's) as this method needs to apply generically to other dynamically-created select lists throughout the document.
Thanks in advance!
Still not 100% sure what you're asking but it seems like this should do what you're looking for and is a few less lines of code.
(function () {
function updateDuplicates() {
$("#duplicates").find("select").html($("#original").html());
$('#duplicates select').each(function () {
var lastSelectedValue = $(this).data('lastSelectedValue');
$(this).val(lastSelectedValue || $(this).val());
});
}
$(document).ready(function () {
$('button:contains(remove)').bind('click', function (e) {
e.preventDefault();
var original_select = document.getElementById('original'),
current_selected = original_select.selectedIndex;
original_select.remove(original_select[current_selected]);
if (original_select.options.length) {
original_select.options[current_selected < original_select.options.length ? current_selected : current_selected - 1].selected = 'selected';
}
updateDuplicates();
});
$('button:contains(add/copy)').bind('click', function (e) {
e.preventDefault();
var original_select = document.getElementById('original'),
new_option = document.createElement('option');
new_option.text = 'Option #' + original_select.length;
new_option.value = new_option.text;
document.getElementById('original').add(new_option);
original_select.options[original_select.options.length - 1].selected = 'selected';
updateDuplicates();
});
$('#duplicates select').bind('change', function () {
$(this).data('lastSelectedValue', $(this).val());
});
} ());
} ());
EDIT: I changed your markup to be
<button>add/copy</button>
<button>remove</button>
just set the currently selected item/value of select to some variable, then do your operation,
finally reselect the value to the select.
Okay, I think I have a workable approach to a solution, if not a clumsy one. The tricky part isn't adding a value to the original list, because the added option is always at the end of the list. The problem comes in removing a select option because doing so changes the index of the currently selectedIndex. I've tested using Google Chrome on a Mac with no errors. I have commented the code to demonstrate how I approached my solution:
<form>
<div id="duplicates">
<!--// Each of these select lists should maintain their currently selected index //-->
<select>
</select>
<select>
</select>
<select>
</select>
</div>
<div>
<!--// Using a generic function to capture each event //-->
<input type="button" value="add/copy" onClick="updateDuplicates('add');" />
<input type="button" value="remove" onClick="updateDuplicates('remove');" />
<select id="original">
</select>
</div>
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.min.js"></script>
<script type="text/javascript">
function updateDuplicates(editMode){
///* Capture the selectedIndex of each select list and store that value in an Array *///
var original_select = document.getElementById('original');
var current_selected = new Array();
$("#duplicates").find("select").each(function(index, element) {
current_selected[index] = element.selectedIndex;
});
switch(editMode){
case "add":
var new_option = document.createElement('option');
new_option.text = 'Option #' + original_select.length;
new_option.value = new_option.text;
original_select.add(new_option);
original_select.options[original_select.options.length-1].selected = 'selected';
///* Traverse each select element and copy the original into it, then set the defaultSelected attribute for each *///
$("#duplicates").find("select").each(function(index, element){
$(element).html($("#original").html());
///* Retrieve the currently selected state stored in the array from before, making sure it is a non -1 value, then set the defaultSelected attribute of the currently indexed element... *///
if(current_selected[index] > -1){
element.options[current_selected[index]].defaultSelected = true;
}
});
break;
case "remove":
var current_index = original_select.selectedIndex;
original_select.remove(original_select[current_index]);
///* Thou shalt not remove from thine empty list *///
if(original_select.options.length){
original_select.options[current_index > 0?current_index - 1:0].selected = 'selected';
}
///* Traverse each select element and copy the original into it... *///
$("#duplicates").find("select").each(function(index, element){
$(element).html($("#original").html());
///* Avoid operating on empty lists... *///
if(original_select.options.length){
///* Retrieve the currently selected state stored in the array from before, making sure it is a non -1 value... *///
if(current_selected[index] > -1){
///* If the stored index state is less or equal to the currently selected index of the original... *///
if(current_selected[index] <= current_index){
element.options[current_selected[index]].defaultSelected = true;
///* ...otherwise, the stored index state must be greater than the currently selected index of the original, and therefore we want to select the index after the stored state *///
}else{
element.options[current_selected[index] - 1].defaultSelected = true;
}
}
}
});
}
}
</script>
</form>
There is plenty of room to modify my code so that options can be inserted after the currently selectedIndex rather than appended to the end of the original select list. Theoretically, a multi-select list/menu should work as well. Have at thee.
I'm sure one of the geniuses here will be able to do this same thing with cleaner, prettier code than mine. Thanks to everyone who reviewed and commented on my original question! Cheers.
If you can reset a little, I think that the problem is you are setting your select list's HTML to another list's HTML. The browser probably doesn't try to preserve the currently selected item if all the of underlying html is being changed.
So, I think what you might try doing is explicitly adding the option elements to the target lists.
Try this jsfiddle. If you select an item other than the default first item and click "add", notice that the selected item is maintained. So you need to be a little more surgical in your managing of the target list items.
Maybe that'll help or maybe I missed the point.