JS variable to HTML elements and vice versa - javascript

In the beginning I have JSON data, and I need to convert and output the list to html.
var frut =
{
"wtfrut": [
["0x01", "Apple"],
["0x02", "Orange"],
["0x03", "Pineapple"],
["0x04", "Banana"]
],
[other irrelevant elements]
}
I made it an html <select> plus list of <options> . . .
<select>
<option data-index="0x01">Apple</option>
<option data-index="0x02">Orange</option>
<option data-index="0x03">Pineapple</option>
<option data-index="0x04">Banana</option>
</select>
. . . and stuck it in a js variable.
This <select> list is a cell in a table, and needs to appear in a couple hundred rows.
While building the table, when I need to display the dropdown, I need to go back thru and find the selected attribute of each <select><option>
Problem 1)
The best I can get from
var select = document.createElement("select");
var options = document.createElement("option");
options.setAttribute("value", element[1]);
...
select.appendChild(options);
return select;
is [object HTMLSelectElement] where the dropdown was supposed to be. return select.value returns the value attribute of the first item on the list.
Therefore, I have resorted to stuffing var dropDown with raw html.
out += "<option value=\"" + element[1] + "\" data-hex = \"" + element[0] + "\" data-index = \"" + index + "\">";
because it works. dropDown winds up with the <select> and all <option>s. And it works when I call it with
"<td class=\"vkeyName\" data-f4key-index = \"+index+\">" + dropDown + "</td>"
Problem 2)
Now that that's working, I try to take dropDown back to js at render time (during the loop that produces the above <td>) and figure out which <option> needs to be chosen as default for the dropdown. select.length returns the string length which I understand. It's just a js string.
Overall
What I don't understand is how to get data over the threshold between js variable and valid html element, in either direction. To make that js string into a list of valid html elements that can be output to the html page... Or to take valid html elements, put them into a variable to be worked by js.
getElementBy* and document.write doesn't work. I presume because I don't have the document yet, I'm building objects.
At this point I'm uninterested in js libraries and helpers. This is a learning project and I want to understand this so that things like jQuery aren't so magical.

I made a small example of a way how you could do create a combobox that generates an Array of some kind of data, and how you could help out yourself by using some callback functions to get the value and the text, and how to choose which element should be preselected, and how you could react on changes in the html element.
You can always use document.getElementById, but you have to wait until you are sure that the page got loaded, one way to do it, is to wait for the window.onload function to fire (which means that the DOM is ready to be manipulated, scripts and css are loaded)
In vanilla javascript, you can do it by registering a callback function on the load event, like this:
window.addEventListener('load', function() { ... });
To generate the combobox, I made a small helper namespace and added a comboBoxGenerator, that takes an object in, and generates the combobox in your desired targetElement.
I then iterate the data and for each element, get the value and text over a callback function (that you define when you called the generator) and it returns the value and the text for that single option. It also determines if the element should be preselected.
By registering to the change event of the combobox, you can then find out which element was actually selected, and for that I also added a small function that displays that the function got changed
The 'use strict;' statement helps to add for example forEach function to the array, and will help you to keep your code more clean
I also documented the source a bit, so that you hopefully understand what everything is doing :)
'use strict';
var helper = {};
(function(ns) {
function comboBoxGenerator(options) {
// get the element that you are targetting
var el = document.getElementById(options.target),
cmb = document.createElement('select'),
option;
// iterate the data, and for each element in the array, create an option and call the defined callback functions
options.data.forEach(function(item) {
option = document.createElement('option');
option.value = options.valueSelector(item);
option.text = options.textSelector(item);
option.selected = options.isSelected(item);
// add the option to the combobox
cmb.appendChild(option);
});
// listen to changes on the combobox and then call the selectionChanged event
cmb.addEventListener('change', function(e) {
// this = cmb because of the bind statement on below
// call the selectionChanged callback function, and assing the cmb as the this for the callback function (.apply(this, ...))
options.selectionChanged.apply(this, [this.options[this.selectedIndex]]);
}.bind(cmb));
el.appendChild(cmb);
}
// set the combo function on the helper by either reusing an existing function, or the function just written above
ns.combo = ns.combo || comboBoxGenerator;
}(helper));
// wait till all resources are loaded, and then generate the combobox
window.addEventListener('load', function() {
var dummyData = {
"wtfrut": [
["0x01", "Apple"],
["0x02", "Orange"],
["0x03", "Pineapple"],
["0x04", "Banana"]
]
}, selectedValue = "0x03";
// call the helper method with an object defining the data, targetelement, and callback functions
helper.combo({
target: 'myTable',
data: dummyData.wtfrut,
valueSelector: function(item) {
// item would be like ["0x01", "Apple"], return "0x01" for value
return item[0];
},
textSelector: function(item) {
return item[1];
},
isSelected: function(item) {
// check if the item matches a selectedValue if so, return true, not false
return item[0] === selectedValue;
},
selectionChanged: function(item) {
// gets called when the selection is changed, item = Option, value is the current value, this = combobox
selectedValue = item.value;
console.log('selectedValue changed to ' + selectedValue + ' index = ' + this.selectedIndex);
}
});
});
<div>
<div id="myTable">
</div>
</div>

Related

How to get the ID of the select-option element with "change" event listener?

I am new into javascript, and I've been working on this "project", but I need some help because I'm stuck. I might've not expressed my self correctly in the title so here it is:
I would like to get the ID of an option element (<select> <option id="#"> </select>) by using the "change" event listener on the <select>. So when I choose for example "Action" from the select dropdown, I'd like that change to trigger a function that will get that element's ID and use it in a function down below. Here's the code that I have so far, which basically does the following:
1.) Gets the genre list;
2.) Then for every item in the response.data.genres, sets a number which corresponds to the length of the array (total 19 items).
3.) If the selected "option" element matches the name of the genre in the array, then it defines the genre ID(the integer) and makes another request to the API in order to list the movies matching that genres ID. Thanks in advance.
//Genres
function genres(){
//API request.
axios.get("https://api.themoviedb.org/3/genre/movie/list?api_key=<API_KEY>&language=en-US")
.then((response)=>{
//console.log(response);
let genres = response.data.genres;
genres.length;
console.log(genres)
for(var i = 0; i < genres.length; i++){
var genresId = response.data.genres[i];
var tag = document.getElementById("Thriller");
console.log(genresId);
if(tag.id === genresId.name){
let genre = genresId.id;
axios.get("https://api.themoviedb.org/3/discover/movie?api_key=<API_KEY>&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1&with_genres="+genre)
.then((response)=>{
console.log(response);
})
}
}
})
}
So there are two steps if I understand correctly.
1. get the list of genres and fill a selectbox with it.
2. get a list of movies if an option in the selectbox is selected.
The first step, you can do with an innerHTML method. For every genre that is returned, you build a string like <option value='genre'>genre</option>. With innerHTML you add these options to the select box. The value property is what you use to see which option is selected.
Next we add an eventlistener to the dropbox so our script will react to the changes the user makes. The event we're listening for is 'change' and it will trigger the function 'getMovies'. See Mozilla docs for more info. event.target.value will give you the value of the selected option, which you can use as genre id.
Inside this function you will do your second api call to get your movie list.
A simple example without the api calls is this:
let genreDropdown = document.getElementById('genre');
genreDropdown.innerHTML = getGenres();
genreDropdown.addEventListener("change", getMovies);
function getGenres(){
let genres = ['action', 'romcom', 'thriller']; //this would be replaced with the api call to get the genres
let innerHtml = '';
for(var i = 0; i < genres.length; i++){
var option = '<option value='+genres[i]+'>'+genres[i]+'</option>';
innerHtml += option;
}
return innerHtml;
}
function getMovies(event) {
let genre = event.target.value;
alert(genre) //you can replace this with the api call to get the movies.
}
<select id='genre'>
<option>loading...</option>
</select>
The following Code will look for a <select> (its id to be exact) Element and on change it will output the ID of the Direct Child (<option> in this case).
$(document).ready(function(){
$("#myDropdown").on("change", function(){
the_id = $(this).children(":selected").attr("id")
$("#output").html(the_id);
});
});
I have made an Example for you > JS Fiddle
Hope you can use the jQuery Code.

For through html elements and appending only appends to last element

I have a text input and on focusout, I have couple of select elements which I want to fill with the text field's value.
And I have bunch of select tags with 'NameSelect' class
$('.textField').focusout(function() {
var name = $(this).val();
var NameOption = $('<option>', { value: name, text: name, attrid: '1'});
var selects = $('#mainForm').find('.NameSelect');
$(selects).each(function(i, obj) {
console.log($(obj)); // it seems to get the right select
$(obj).append(NameOption);
})
}
However, when I do that, even though the selects get all the right elements and for loop for the right count, it only appends the option input to the latest object, not all of them.
What am I missing here?
The issue is because NameOption holds a reference to the option, hence if you append() it multiple times it will move between each parent element.
To fix this you can either clone() the element when you append it:
selects.append(NameOption.clone());
Or you could just provide append() with a string to create a new element each time it's called:
$('.textField').focusout(function() {
var name = $(this).val();
$('#mainForm').find('.NameSelect').append('<option value="' + name + '" attrid="1">' + name + '</option>');
})
});
Note that in both cases the each() is not required.

Jquery multiple selects trigger change

I have a category tree and I am using Jquery on my page to generate a new category select after a previous category has been chosen.
What I need is, if I pass a list of categories (which are ancestors and children of each other), use the code to perform the value selection (which will call the method that I have written previously).
Here is the code that is responsible to fire upon the category change:
$(document.body).on('change', '.category-select' ,function(event){
var select_rank = parseInt($(event.target).attr('rank'));
var current_category = event.target.value;
select_category(select_rank, current_category, true)
});
When page is rendered there is code that will create the first select. Then on each select change - the select_category function will render a new select (if the category has subcategories).
I am passing a list of categories in an array called cat_list. If it is set, I need the selects to be triggered on last select:
if(cat_list != null){
for(cat_id in cat_list){
$('.category-select').last().val(cat_id).change();
}
}
You're not iterating over the array correctly. In your code, cat_id is the array index, not the value from the array. Try this:
if (cat_list) {
var last_select = $('.category-select:last');
$.each(cat_list, function(i, cat_id) {
last_select.val(cat_id).change();
});
}

JQuery get calling <script> tag when dynamically loaded

How can I locate the tag which calls a JQuery script, when
the tag is dynamically loaded, so won't be the last
tag on the page?
I'm using the MagicSuggest autosuggest library. I want to give certain suggested items a different background color depending on their contents, which I'm currently doing by adding JQuery inside a tag, which I'm adding on to the String which is returned to be rendered inside the selection div. Then, to get the div the item is suggested in, I need to essentially get the parent() of the tag, and change it's css() properties. How can I get this current script tag however?
I'm currently assigned each new tag an id generated from incrementing a JS variable - which works, but isn't very 'nice'! Is there anyway I can directly target the tag with JQuery?
If it perhaps makes it clearer, here is my current selectionRenderer function.
selectionRenderer: function(a){
var toRet = a.english;
var blueBgScript = "<script id=ft" + freeTextFieldID + ">$('#ft" + freeTextFieldID + "').parent().css('background', 'blue');</script>"
if(a.id==a.english){
toRet += blueBgScript;
freeTextFieldID++;
}
return toRet;
},
Why don't you add some code at afterrender event instead? Add some tag to flag the options that need a different background, then detect the parents and add a class (or edit the bg property) or whatever you like:
var newMS = $('#idStr').magicSuggest({
data: 'states.php',
displayField: 'english',
valueField: 'id',
selectionRenderer: function(a){
var toRet = a.english;
if(a.id==a.english) toRet = "<span class='freetext'>" + toRet + "</span>";
return toRet;
},
});
$(newMS).on('selectionchange', function(event,combo,selection){
var selDivs = $(event.target._valueContainer[0].parentNode).children('div'); //Get all the divs in the selction
$.each(selDivs,function(index,value){ //For each selected item
var span = $(value).children('.freetext'); //It if contains a span of class freetext
if(span.length == 1) $(value).css('background','blue'); //Turn the background blue
});

Faster way to populate <select> with Javascript

I have two <select> boxes on a form. Selecting an item in the first <select> box will determine what should appear in the second <select> (Using Ajax http_request).
In some cases there can be a large 500 (guess) items in the second select and it takes time 5-10 seconds to update in IE. Firefox seems to work perfectly.
I wonder if there is a faster way to achieve this. Currently the server creates a string passes it to the client which is then broken up and add each item to the select by creating an option element and then adding it to the <select>.
I did try to create the whole select item as a string on the server and add that to the form but for some reason it wouldn't work in Firefox (missed something?)
Thanks
500 elements is not a lot, even for IE. You must be doing something else to cause the lag.
I just tried with 500+ options in IE6, IE7, FF2 and FF3 and all were near instantaneous. I used this code:
var data = [
{ text: 'foo', value: 'bar' },
// ...
{ text: 'foo', value: 'bar' }
];
var select = document.getElementsByTagName('select')[0];
select.options.length = 0; // clear out existing items
for(var i=0; i < data.length; i++) {
var d = data[i];
select.options.add(new Option(d.text, i))
}
I would suggest profiling the bit of code that is fetching the data and populating the drop down. Something else might be taking up the time. For example, check that the code that "breaks up" the string value returned from the server is sharp (sounds like you're doing your own custom parsing there).
The first code is fine but this works better for me:
var data = [
{ text: 'uno', value: '1' },
{text: 'dos', value: '2' }
];
var select = document.getElementById('select-choice-1');
select.options.length = 0; // clear out existing items
for (var i = 0; i < data.length; i++) {
var d = data[i];
select.options.add(new Option(d.text, d.value))
}
Setting it using SelectElement.innerHTML would be the fastest... but that FAILS in IE.
Last I checked, you can do this in IE, if you wrap all the options in a bogus <div> tag, or set the entire .outerHTML of the select list in IE.
The problem with all these answers with SelectElement.innerHTML is that you cannot do this trick with SELECTs. The solution is to use innerHTML on the PARENT of the SELECT element itself. So in your ajax/jquery/whatever code create a string that contains ALL of the SELECT HTML, and then get the holder (a div or span or whatever) and set the innerHTML to the string you've constructed.
You will need to isolate the SELECT from the page and give it an explicit parent element (span or div) to prevent other html elements from being casualties when you destroy/reconstruct the SELECT element.
Short answer:
parentselectelement.removeChild(selectelement);
parentselectelement.innerHTML = "<select ...><options...></select>";
I would create the whole select on the server and inject it into the page. That approach bypasses annoying browser discrepancies, and reduces the complexity of the client-side code.
You did mention that you tried that, but it failed in firefox. I would suggest persevering in getting it to work, posting another question asking for help on that issue, or editing your question to show us what you made that didn't work in firefox.
Don't forget to append to document.createDocumentFragment() first, before appending that to the SELECT.
It would help greatly to see your code.
IF you are creating an <option> element and appending it each iteration, you should consider creating all the <option> elements at once, and then appending them all at once.
So (in psuedocode):
// Don't do this:
for choice in choices:
option = new Options(choice)
select.append(option)
// Do this instead
var options = array()
for choice in choices:
options.append( new Options(choice) )
for option in options:
select.append(option)
// Or you might try building the select element off-screen and then appending to the DOM
var options = array()
var select = new SelectElement()
for choice in choices:
select.append( new Options(choice) )
dom_element.append(select)
When I use the first version of this it works but can be very slow in updating the second select
<html>
<form id='myform' method='post' action='$_SERVER[PHP_SELF]'>
<table>
<tr><td><select onselect='CALL_AJAX_AND_UPDATE();'></select></td></tr>
<tr><td><select id='selectid'></select></td></tr>
</table>
</form>
</html>
<script type=\"text/javascript\">
function UPDATE( updatedata )
{
var itemid = document.getElementById('selectid');
var data = updatedata.split( '|' );
var len = data.length;
for( i=0; i < len; i ++ )
{
var opt = document.createElement('option');
opt.text = data[i];
try
{
itemid.add( opt, null );
}
catch(ex)
{
itemid.add( opt );
}
}
}
</script>
This version works in IE, but firefox doesn't seem to post the second selects data. Have I missed something with this.
<html>
<form id='myform' method='post' action='$_SERVER[PHP_SELF]'>
<table>
<tr><td><select onselect='CALL_AJAX_AND_UPDATE();'></select></td></tr>
<tr><td><div id='addselect'></div></td></tr>
</table>
</form>
</html>
<script type=\"text/javascript\">
function UPDATE( updatedata )
{
// update data is full select html
var itemid = document.getElementById('addselect');
itemid.innerHTML = updatedata;
}
</script>
I like Crescent Fresh's and Kemal Fadillah's answers since both use:
select.options.add(new Options(name, value))
As for the data object I recommend a small tweak as follows:
var data = {
'uno': 1,
'dos': 2
};
var select = document.getElementById('select-choice-1');
select.options.length = 0; // clear out existing items
for (var i in data) {
select.options.add(new Option(i, data[i]));
}
If you don't mind using CoffeeScript then the following code fills a select list with JSON data in a couple of lines.
HTML
<select id="clients"></select>
CoffeeScript
fillList=($list, url)=>
$.getJSON(url)
.success((data)->
$list
.empty()
.append("<option value=\"#{item.Id}\">#{item.Name}</option>" for item in data)
)
$ ->
fillList($('#clients'), '/clients/all')
Note : The loop inside the append generates the entire html string before calling the append method once. This is nice and efficient.
Example JSON
[
{"Id":"1","Name":"Client-1"},
{"Id":"2","Name":"Client-2"}
]

Categories