I have a select menu that, on change through AJAX, generates another select menu, which in turn on change generates yet another select menu with yet another callback on change (lets say alert(chosenValue)). By working my way through these select menus the first time, I generate correctly the content for the second and third select, and the alert is printed correctly.
From the second time onward, when I generate new content for the third select, when choosing an option, multiple alerts are fired with the elements that were at that position in the select. I am guessing that these callbacks where generated and stayed there, because I generated them with $("select#dropdownMenu3").change(function() {...});.
Is there a way to remove such generated callbacks and have a "virgin" select#dropdownMenu3 again? I'm using jQuery, so big pluses if the solution includes it! Thanks!
EDIT: code becasue - reEDIT: I'm starting to thing that the problem is far more serious. Here is the whole thing
/*All the variables that you see used are defined somewhere up here*/
$("select#dropdownMenu1").change(function(){
$('select#dropdownMenu3').unbind('change');
var typeChosen = $(this).children(":selected").html();
if(!searchSuggestions){
$.get( "/searchdata", function( data ) {
data = JSON.parse(data);
searchSuggestions = data;
for(var i = 0; i < data.length; i++){
if(data[i].type == typeChosen){
$("#dropdownMenu2").empty();
$("#dropdownMenu2").append("<option disabled>Choose a Predicate</option>");
for(var j = 0; j < data[i].predicates.length; j++){
$("#dropdownMenu2").append("<option value='" + j + "'>" + data[i].predicates[j].predicate + "</option>");
}
}
}
$("#dropdownMenu2").select2();
$("select#dropdownMenu2").change(function(){
predicateChosenIndex = parseInt($(this).children(":selected").val());
$("#tdDropdownMenu3").html("");
$('#tdDropdownMenu3').append("<select class=\"js-example-basic-multiple\" id=\"dropdownMenu3\"><option disabled>Choose an Object</option></select>");
for(var i = 0; i < data.length; i++){
if(data[i].type == typeChosen){
for(var k = 0; k < data[i].predicates[predicateChosenIndex].values.length; k++){
$("#dropdownMenu3").append("<option value='" + k + "'>" + data[i].predicates[predicateChosenIndex].values[k].value + "</option>");
}
}
}
$("#dropdownMenu3").select2();
$("select#dropdownMenu3").change(function(){
objectChosenIndex = parseInt($(this).children(":selected").val());
for(var i = 0; i < data.length; i++){
if(data[i].type == typeChosen){
alert(data[i].predicates[predicateChosenIndex].values[objectChosenIndex].value + " chosen");
}
}
});
});
});
}
You can use:
$('select#dropdownMenu3').unbind('change');
But as long as we don't see how you generate your html, this might not be the best solution.
Related
This snippet from my code uses a function to take the data from my indexedDB Database and append it to a table.
The next thing I need to do is to add a button to delete a task from the list and database which I want to do by having a button at the end of each row of tasks which can select the task by ID and remove it from the database which should be reflected by the table.
const db = new Database();
document.addEventListener("DOMContentLoaded", function(event) {
setTimeout(function() {
db.getAllTodos(function(records) {
records.forEach(function() {
console.log(records.length)
var cols = ['taskName','taskDesc', 'taskDate','taskTime','taskPriority','taskLocation','taskImage']
for (var i = 0; i <= records.length; i++){
$('table').append('<tr></tr>' );
for (var j = 0; j < cols.length; j++) {
$('table tr:last-child').append ('<td>' + records[i][cols[j]] + '</td>' );
}
}
})
});
}, 1000);
});
Below are some images of my web page and database for reference:
https://imgur.com/a/5h6qMMJ
I was thinking of doing this by adding the button as data into the database but it always appeared as null or undefined when I tried to put it in.
Hi SourOddity,
Perhaps adding the button HTML tag just before the last TD or even better in a separated TD inside the same TR would be the trick for this case. You can then use the button ID to invoke the click event on it in order to run your delete function. I certainly hope this helps!
$('table tr:last-child').append ('<td>' + records[i][cols[j]] + '</td>' + "</td><button id='delbtn' type='button'>Delete</button></td>");
I have made some edits to the code, and now it looks like this:
const db = new Database();
document.addEventListener("DOMContentLoaded", function(event) {
setTimeout(function() {
db.getAllTodos(function(records) {
records.forEach(function() {
console.log(records.length);
var cols = ['taskName','taskDesc', 'taskDate','taskTime','taskPriority','taskLocation','taskImage'];
for (var i = 0; i <= records.length; i++){
$('table').append('<tr></tr>' );
for (var j = 0; j < cols.length; j++) {
var lastCell = $('table tr:last-child');
lastCell.append ('<td>' + records[i][cols[j]] + '</td>' );
}
lastCell.append("<td class=\"delete\"><button onclick=\"deleteTask()\">Delete</button></td>");
}
})
});
}, 1000);
});
function deleteTask() {
console.log("working");
}
This solution works to add the buttons, however I now get
this error:
The lines of code mentioned but not visible from database.js
async getAllTodos(callback) {
let store = this.database.transaction(["taskList"], "readonly").objectStore("taskList");
let allRecords = store.getAll();
allRecords.onsuccess = () => callback(allRecords.result);
}
Answer for adding button is above, gonna close this one as question is moving off topic.
This question already has answers here:
JavaScript DOM remove element
(4 answers)
Closed 3 years ago.
I am trying to make a simple list using html input box and js. the list are creating on clicking "add skill" and get removed when click on "remove". But when I try to add some skill after remove, the last removed item also get back.
var skillList="";
var i = 0;
function addSkill(){
var skills= document.getElementById("addSkill").value;
if(skills != ""){
skillList += "<li><span name='skillItem' id='skillItem"+ i +"'>" + skills + "</span> " +
"<a onclick='removeSkill()'>remove</a></li>";
i++;
document.getElementById("skill").innerHTML = skillList;
document.getElementById("addSkill").value="";
}
}
function removeSkill(){
skillList="";
var items = document.querySelectorAll("#skill li"),index,tab = [];
for(var j = 0; j < items.length; j++){
tab.push(items[j].innerHTML);
}
for(var j = 0; j < items.length; j++){
items[j].onclick = function(){
index = tab.indexOf(this.innerHTML);
items[index].parentNode.removeChild(items[index]);
tab.splice(j,1);
};
}
console.log(tab);
for(var j=0; j<tab.length;j++){
skillList += "<li>" + tab[j] + "</li>";
}
}
<td><label>skills:</label></td>
<td>
<ul id="skill"></ul>
<input type="text" name="skill" id="addSkill"/>
<a onclick="addSkill();" value="">add skill</a>
</td>
Just delete this piece of code
for(var j=0; j<tab.length;j++){
skillList += "<li>" + tab[j] + "</li>";
}
Your were adding it again...
Working now...
var skillList="";
var i = 0;
function addSkill(){
var skills= document.getElementById("addSkill").value;
if(skills != ""){
skillList += "<li><span name='skillItem' id='skillItem"+ i +"'>" + skills + "</span> " +
"<a onclick='removeSkill()'>remove</a></li>";
i++;
document.getElementById("skill").innerHTML = skillList;
document.getElementById("addSkill").value="";
}
}
function removeSkill(){
skillList="";
var items = document.querySelectorAll("#skill li"),index,tab = [];
for(var j = 0; j < items.length; j++){
tab.push(items[j].innerHTML);
}
for(var j = 0; j < items.length; j++){
items[j].onclick = function(){
index = tab.indexOf(this.innerHTML);
items[index].parentNode.removeChild(items[index]);
tab.pop(j,1);
};
}
}
<td><label>skills:</label></td>
<td>
<ul id="skill"></ul>
<input type="text" name="skill" id="addSkill"/>
<a onclick="addSkill();" value="">add skill</a>
</td>
You got all lost in the loops and indexes in your removeSkill function and it's way easier than what you are doing - one single line of code. No loops and no arrays are needed.
You've also got a bunch of other styles of coding that are ancient and should be avoided.
You don't need to use a hyperlink just because you want to give
someone something to click on. All visible elements support a click
event. Only use hyperlinks when you are actually navigating
somewhere.
You can't use table cells without a table.
Don't use inline HTML event attributes (onclick). Separate all your
JavaScript from your HTML and do the event binding with
.addEventListener().
Don't create new HTML by concatenating strings together. It becomes a
nightmare fast and requires you to use .innerHTML, which has
performance and security implications. Instead, create new DOM
objects, configure their properties as needed and then append them
into the document.
See the comments inline below:
// Get all the element references you'll need just once:
var skillList = document.querySelector("#skillList");
var newSkill = document.querySelector("#newSkill");
var btnAddSkill = document.querySelector("input[type='button'");
// Do all of your event binding in JavaScript, not with inline HTML event attributes
btnAddSkill.addEventListener("click", addSkill);
function addSkill(){
if(newSkill.value !== ""){
// Don't build new HTML by concatenating strings. Create elements and configure them as objects
var li = document.createElement("li");
li.textContent = newSkill.value;
// Only use hyperlinks for navigation, not to have something to click on. Any element can be clicked
var span = document.createElement("span");
span.classList.add("remove");
span.textContent = "remove skill";
span.addEventListener("click", removeSkill);
li.appendChild(span); // Add the span to the bullet
skillList.appendChild(li); // Add the bullet to the list
newSkill.value = "";
}
}
function removeSkill(){
// Just remove the closest <li> ancestor to the <span> that got clicked
skillList.removeChild(this.closest("li"));
}
.remove { display:inline-block; margin-left:10px; padding:2px 4px; background:rgba(200,0,225,.7); color:#ff0; cursor:pointer; }
li {margin:8px 0; border-bottom:1px dashed #e0e0e0; }
<h1>Skills:</h1>
<input type="text" name="skill" id="newSkill">
<input type="button" value="add skill">
<ul id="skillList"></ul>
I'm playing around with Javascript as a hobby and I've been having trouble accessing elements that I have dynamically created via another function.
Essentially, I have a link that dynamically creates a couple of dropdown selects with a few options. Then I have a second link which I would try to print some of the selected options onto console.
HTML:
create
collect
<div id="box"><br>
Javascript:
function maker() {
box.appendChild(document.createElement("br"));
for (i = 0; i < 2; i++) {
box.appendChild(document.createTextNode("test " + (i + 1) + " "));
for (k = 0; k < 2; k++) {
var dropdown = document.createElement("select");
box.appendChild(dropdown);
for (j = 0; j < nice.length; j++) {
var option = document.createElement("option");
option.value = nice[j];
option.text = nice[j];
option.id = 'option' + i + k;
console.log(option.id)
dropdown.appendChild(option);
}
}
box.appendChild(document.createElement("br"));
}
}
function getter() {
var test = document.getElementById("option01");
console.log(test.options[test.selectedIndex].value);
}
I've printed to console the option id's as they are created (seems to have no problem printing this), and added them to the DOM via appendChild. However with my second function, I am unable to retrieve the selected value of the options despite explicitly referencing the id.
My guess is that it has something to do with the order the scripts are loaded. Can anyone help me understand what's going on?
Attached is my JSFiddle file,
http://jsfiddle.net/c8h6gx2d/1/
Cheers,
The problem is that <option>s are nested inside of <select>s, and it's the *<select>*s which have a selectedIndex property. So, test.options[test.selectedIndex].value won't work when test is an <option> element. Try using getElementById to get one of the <select>s, for one, and then just access its .value (which is less cumbersome than checking selectedIndex):
var nice = [2, 3, 5];
function maker() {
box.appendChild(document.createElement("br"));
for (i = 0; i < 2; i++) {
box.appendChild(document.createTextNode("test " + (i + 1) + " "));
for (k = 0; k < 2; k++) {
var dropdown = document.createElement("select");
dropdown.id = 'select' + i;
box.appendChild(dropdown);
for (j = 0; j < nice.length; j++) {
var option = document.createElement("option");
option.value = nice[j];
option.text = nice[j];
dropdown.appendChild(option);
}
}
box.appendChild(document.createElement("br"));
}
}
function getter() {
var test = document.getElementById("select0");
console.log(test.value);
// same as:
// console.log(test.options[test.selectedIndex].value);
}
create
collect
<div id="box"><br>
Also note that duplicate IDs in a single document is invalid HTML, so if you ever call maker more than once, for your HTML to be valid, you might have a separate counter outside of maker that gets incremented:
const makeCount = 0;
function maker() {
// ...
dropdown.id = 'select' + makeCount + '_' + i;
// ...
makeCount++;
}
(or, avoid IDs entirely, if at all possible, numeric ID indicies are a code smell - use classes instead)
We need to discriminate between option elements and the select element. The select element is the one with which you want to interact most of the time, and the option elements are simply a collection of possible entries for the select element.
Your code as it stands now generates these sort of elements:
<select>
<option value="2" id="option00">2</option>
<option value="3" id="option00">3</option>
<option value="5" id="option00">5</option>
</select>
As you see all the options receive the same ID - which is generally forbidden in HTML documents. You might consider moving the ID indicator to the select element, which also gives you access to the value of the selected option.
Here is the revised JS code with comments before revisions:
var nice = [2, 3, 5];
function maker() {
box.appendChild(document.createElement("br"));
for (i = 0; i < 2; i++) {
box.appendChild(document.createTextNode("test " + (i + 1) + " "));
for (k = 0; k < 2; k++) {
var dropdown = document.createElement("select");
# Giving the select item an ID instead of each option
dropdown.id = 'select' + i + k;
box.appendChild(dropdown);
for (j = 0; j < nice.length; j++) {
var option = document.createElement("option");
option.value = nice[j];
option.text = nice[j];
option.id = 'option'
console.log(option.id)
dropdown.appendChild(option);
}
}
box.appendChild(document.createElement("br"));
}
}
function getter() {
# Getting the select element instead of the option
var selectElement = document.getElementById("select00");
# The value attribute of the select element is the value of the selected option
console.log(selectElement.value);
}
Update: I've tried the suggestions in the comments and it's still not working. I really have no idea why. I've consolidated it to a single loop and fixed the syntax errors noted. Here's the code as it looks now:
$(function() {
$("#json-one").change(function() {
var $dropdown = $(this);
$.getJSON("washroutines.json", function(data) {
var vals = [];
var $jsontwo = $("#json-two");
$jsontwo.empty();
for (var i = 0; i < data.length; i++){
if (data[i].make === $dropdown.val()) {
$jsontwo.append("<option value=\"" + data[i].model + "\">" + data[i].model + "</option>");
}
}
});
});
});
Any additional help would be much appreciated!
Original question:
I'm trying to create dependent drop down menus using a json object, and I'm having trouble getting the second menu to populate based on the first. When the first menu changes, the second goes to a bunch of "undefined"s.
$.getJSON("washroutines.json", function(data) {
var vals = [];
for (i = 0; i < data.length; i++){
if (data.make = $dropdown.val()) {
vals.push(data.model);
}
}
var $jsontwo = $("#json-two");
$jsontwo.empty();
for (i = 0; i < vals.length; i++){
$jsontwo.append("<option value\"" + vals[i] + "\">" + vals[i] + "</option>");
}
Please use small words when explaining things to me, I'm new at this!
contents of the JSON:
[{"make":"Maytag","model":"Bravos","prewashCycle":"Whitest Whites"},
{"make":"Maytag","model":"Awesome","prewashCycle":"Awesome Whitest Whites"},
{"make":"Whirlpool","model":"Cabrio","prewashCycle":"Extra Heavy"},
{"make":"Kenmore","model":"Elite","prewashCycle":"Awesome"}]
Try changing your for loop for this
for (var i = 0; i < data.length; i++){
if (data[i].make === $dropdown.val()) {
vals.push(data[i].model);
}
}
I am working on a GAS powered dependent dropdown, which I have now come very close to achieving with the help from stackoverflow, thanks to all.
But I have come to a point where there is a lack of documentation? Or answer to.
I am trying to get a specific column by name, by that I find the column that has the header/top row value of a certain input. In this case it's a dropdown running on an HTML page, that speaks with GAS.
Most of the script works just fine, it's the building what I call topics in the buttom of the HTML and the Code.gs that is causing me some trouble. Right now it's not getting the columns in anyway, but that's because I haven't found a way of doing it yet. The parts that are not working right now are: function buildTopicsList(rowsName) in the HTML and the function getTopics(subCategories,categories) in Code.gs
Here I want to get all the rows in a column with a "header" selected from a dropdown in the html.
The full HTML looks like this:
index.html
<div>
<select id="categories" onchange="google.script.run.withSuccessHandler(buildSubCategoriesList)
.getSubCategories(this.value)">
<option>Loading...</option>
</select>
<select id="subCategories" onchange="google.script.run.withSuccessHandler(buildTopicsList)
.getTopics(this.value)">
<option>Loading...</option>
</select>
<select id="topics">
<option>Loading...</option>
</select>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>
// This code in this function runs when the page is loaded.
$(function() {
google.script.run.withSuccessHandler(buildCategoriesList).getCategories();
});
function buildCategoriesList(sheetsName) {
var list = $('#categories');
list.empty();
for (var i = 0; i < sheetsName.length; i++) {
list.append('<option value="' + sheetsName[i] + '">' + sheetsName[i] + '</option>');
}
list.trigger("change");
}
function buildSubCategoriesList(columnsName) {
console.log(columnsName);
var list = $('#subCategories');
list.empty();
for (var i = 0; i < columnsName.length; i++) {
list.append('<option value="' + columnsName[i] + '">' + columnsName[i] + '</option>');
}
list.trigger("change");
}
function buildTopicsList(rowsName) {
console.log(rowsName);
var list = $('#topics');
list.empty();
for (var i = 0; i < rowsName.length; i++) {
list.append('<option value="' + rowsName[i] + '">' + rowsName[i] + '</option>');
}
}
</script>
and the code:
Code.gs
var ss = SpreadsheetApp.openById("1BK5urtTzqZ2kc89ZnbeMSIm2-bt4KLNTQghsxn0cXBI");
function doGet(request) {
return HtmlService.createTemplateFromFile('index')
.evaluate()
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename).getContent();
}
function getCategories(){
var sheetsName = [];
var sheets = ss.getSheets();
for( var i = 0; i < sheets.length; i++ ){
sheetsName.push( sheets[i].getName() )
}
return sheetsName;
}
function getSubCategories(categories){
var columnsName = [];
var sheet = ss.getSheetByName(categories);
var subRange = sheet.getRange(1, 1, 1, sheet.getLastColumn());
var columns = subRange.getValues()[0];
for( var i = 0; i < columns.length; i++ ){
columnsName.push( columns[i] )
}
return columnsName;
}
// this here is not working:
function getTopics(subCategories,categories){
var rowsName = [];
var sheet = ss.getSheetByName(categories);
var topRange = sheet.getRange(1, 1, 1, sheet.getLastColumn());
var rows = topRange.getValues()[0];
for( var i = 0; i < rows.length; i++ ){
rowsName.push( rows[i] )
}
return rowsName;
}
Any suggestions?
The server function getTopics(subCategories,categories) is being called by onchange attribute:
onchange="google.script.run.withSuccessHandler(buildTopicsList)
.getTopics(this.value)"
this.value is being passed to the getTopics(subCategories,categories) function. But the getTopics(subCategories,categories) function accepts two parameters, not one. And the first parameter, subCategories is never being used in the code. That's why you aren't getting anything. Put in a Logger.log('categories: ' + categories); statement at the top of the getTopics(subCategories,categories) function, and I'll bet that it shows nothing.
As a "side note". The doGet() function is using the createTemplateFromFile() method, but there are no scriptlets in your index.html file. The index.html file is not a template. If you were using a template, you wouldn't need to run some of the browser code when the html loads. I'm not saying that it's better to use the scriptlets, or suggesting you do that. But I just wanted you to know what is happening and what your options are.