How to create dependent dropdowns when adding it dynamically? - javascript

How do I create a dynamic dependent dropdown list in Google AppsScript? What I want to achieve is, when I choose PH in order type, the product selection dropdown should have ['PH-Test Product 1', 'PH-Test Product 2', 'PH-Test Product 3'] options. And when I choose EC it should have ['EC-Test Product 1', 'EC-Test Product 2', 'EC-Test Product 3'].
Here's my code-
form.html
<head>
<base target="_top">
<?!= include('css_script'); ?>
</head>
<body>
<div class="container">
<div class = "row">
<h1>A Sample Form</h1>
</div>
<div id="productsection"></div>
<div class = "row">
<button id="addproduct">Add Product</button>
</div> <!-- end of row -->
</div>
<?!= include('js_script'); ?>
</body>
js_script.html
<script>
let counter = 0;
const orderTypeList = ["PH", "EC"];
const optionListPH = ["PH-Test Product 1", "PH-Test Product 2", "PH-Test Product 3"];
const optionListEC = ["EC-Test Product 1", "EC-Test Product 2", "EC-Test Product 3"];
document.getElementById("addproduct").addEventListener("click", addInputField);
function addInputField(){
counter++;
// creates a new div of class row
const newDivElem = createElementTemplate('div', `row${counter}`, 'row');
// creates a new select tag for order type dropdown
const newOrderTypeSelectElem = createElementTemplate('select', `ordertype${counter}`);
// function that populates the dropdown for products and is inserted to the above "ordertypeX" select tag
createOptionsElem(newOrderTypeSelectElem, orderTypeList);
// creates a new select tag for product dropdown
const newProductSelectElem = createElementTemplate('select', `product${counter}`);
// Code to switch options depending on ordertype
//------------------------- Does Not Work --------------------------
if(document.getElementById(`ordertype${counter}`).value === 'PH'){
const optionList = optionListPH;
}else{
const optionList = optionListEC;
}
//------------------------------------------------------------------
// generates the content of the dropdown for products and is inserted to the above "productX" select tag
createOptionsElem(newProductSelectElem, optionList);
newDivElem.appendChild(newOrderTypeSelectElem);
newDivElem.appendChild(newProductSelectElem);
// Finally, appends the newly created div tag to the productSection tag.
document.getElementById('productsection').appendChild(newDivElem);
}
function createOptionsElem(selectTag, optionsArr){
const newDefaultOptionTag = document.createElement('option');
newDefaultOptionTag.value = "";
// newDefaultOptionTag.select = false;
newDefaultOptionTag.textContent="Choose your option";
for(let i in optionsArr){
const newOptionTag = document.createElement('option');
newOptionTag.textContent = optionsArr[i];
newOptionTag.value = optionsArr[i];
// Inserts the option tag in select tag
selectTag.appendChild(newOptionTag);
}
}
// function to create a new element
function createElementTemplate(tagType, idVal, className){
const newElement = document.createElement(tagType);
if(idVal !== undefined)
newElement.id = idVal;
if(className !== undefined)
newElement.classList.add(className);
return newElement;
}
</script>
css_script.html
<style>
.row{
margin-top: 5px;
margin-bottom: 5px;
}
</style>
Code.gs
function doGet(e) {
Logger.log(e);
return HtmlService.createTemplateFromFile('form_basic').evaluate();
}
function include(fileName){
return HtmlService.createHtmlOutputFromFile(fileName).getContent();
}

Modification points:
In your script, when a button is clicked, 2 dropdown lists are created. By this, at the following script,
if(document.getElementById(`ordertype${counter}`).value === 'PH'){
const optionList = optionListPH;
}else{
const optionList = optionListEC;
}
in the case of your script, optionListPH is always used to the 1st dropdown list.
And, when you want to change the 2nd dropdown list by changing the 1st dropdown list, it is required to add more script for checking it.
When these points are reflected in your script, how about the following modification?
From:
// Code to switch options depending on ordertype
//------------------------- Does Not Work --------------------------
if(document.getElementById(`ordertype${counter}`).value === 'PH'){
const optionList = optionListPH;
}else{
const optionList = optionListEC;
}
//------------------------------------------------------------------
// generates the content of the dropdown for products and is inserted to the above "productX" select tag
createOptionsElem(newProductSelectElem, optionList);
newDivElem.appendChild(newOrderTypeSelectElem);
newDivElem.appendChild(newProductSelectElem);
// Finally, appends the newly created div tag to the productSection tag.
document.getElementById('productsection').appendChild(newDivElem);
To:
// Code to switch options depending on ordertype
//------------------------- Does Not Work --------------------------
const optionList = optionListPH; // Modified
//------------------------------------------------------------------
// generates the content of the dropdown for products and is inserted to the above "productX" select tag
createOptionsElem(newProductSelectElem, optionList);
newDivElem.appendChild(newOrderTypeSelectElem);
newDivElem.appendChild(newProductSelectElem);
// Finally, appends the newly created div tag to the productSection tag.
document.getElementById('productsection').appendChild(newDivElem);
// I added the below script.
newOrderTypeSelectElem.addEventListener("change", function() {
newProductSelectElem.innerHTML = "";
createOptionsElem(newProductSelectElem, this.value === 'PH' ? optionListPH : optionListEC);
});
When this modification is reflected in your script, when a button is clicked, 2 dropdown lists are created. And, when 1st dropdown list is changed, the 2nd dropdown list is refreshed with new values.
Note:
This modification is for your showing script. When you change your script, this script might not be able to be used. Please be careful about this.

Related

Display product details when click on button

<script>
//<body>
//<div class="prod"></div>
//<div class="details"></div>
//</body>
const product=document.querySelector(".prod");
var ourRequest=new XMLHttpRequest();
ourRequest.open('GET','http://inec.sg/assignment/retrieve_records.php');
ourRequest.onload=function(){
var data=JSON.parse(ourRequest.responseText);
renderHTML(data);
};
ourRequest.send();
function renderHTML(data){
for(var i=0;i<data.songs.length;i++){
product.innerHTML+=`
<div><img src="${data.songs[i].image}" height="200px"></div>
<div><h2>${data.songs[i].name}</h2></div>
<div><h2>$${data.songs[i].price}</h2></div>
<button id="">Details</button>
`;
}
}
//how do i display prod details when click on button? Able to hide the prod listing div, show the details in details div?
// details eg. data.songs.image, data.songs.brand, data.songs.style, data.songs.discount, data.songs.price
</script>
How do I display prod details when click on button? Able to hide the product listing div, show the details in details div?
Show these in details:
data.songs.image, data.songs.brand, data.songs.style, data.songs.discount, data.songs.price
This is what I came up with:
<button class="load">Load List</button>
<button class="hide">Hide List</button>
<div class="prod"></div>
<div class="details"></div>
<script>
const product = document.querySelector(".prod");
const details = document.querySelector(".details");
const loadBtn = document.querySelector(".load");
const hideBtn = document.querySelector(".hide");
loadBtn.addEventListener("click", () => { // on click of load button
// send request to get data
var ourRequest = new XMLHttpRequest();
ourRequest.open(
'GET',
'http://inec.sg/assignment/retrieve_records.php'
);
ourRequest.onload = function () {
var data = JSON.parse(ourRequest.responseText);
renderHTML(data);
};
ourRequest.send();
function renderHTML(data) {
product.innerHTML = ""; // empty product element
for (var i = 0; i < data.songs.length; i++) {
product.innerHTML += `
<div><img src="${data.songs[i].image}" height="200px"></div>
<div><h2>${data.songs[i].name}</h2></div>
<div><h2>$${data.songs[i].price}</h2></div>
<button data-index="${i}" class="detailsBtn">Details</button>
`; // data-index represents the index of the item in the songs array
}
let detailsBtns = document.querySelectorAll(".detailsBtn"); // get button element(s) previously created
for (var i = 0; i < detailsBtns.length; i++) {
let detailsBtn = detailsBtns[i];
detailsBtn.addEventListener("click", () => { // add on click event for individual details btn
// load details
let index = detailsBtn.dataset.index; // get index of song item
let song = data.songs[index];
details.innerHTML = `<h2>${song.image}</h2><h2>${song.brand}</h2><h2>${song.style}</h2><h2>${song.discount}</h2><h2>${song.price}</h2>`;
});
}
}
});
hideBtn.addEventListener("click", () => { // hide details when hide details button is clicked
product.innerHTML = ""; // empty product element
details.innerHTML = ""; // empty details element
});
</script>
First I declared and assigned common elements like product, details, hideBtn, and loadBtn.
I used the EventTarget.addEventListener() function to bind click events to loadBtn, hideBtn, and detailsBtn .
I used arrow functions for the callback functions of the events, but ES5 functions could certainly be used.
I used the HTMLElement.dataset attribute to link a song index to each product, so I can link the HTML of the product item(s) to their corresponding JS event functionality.
There are many other ways this could be done, but this is how I solved the problem.

Can't access/update class object properties after creation - javascript

The problem is I get "Uncaught ReferenceError: holder is not defined" when trying to access object properties after object creation.
I'm doing the To Do List project for the the Odin Project.
JSFIDDLE: https://jsfiddle.net/appredator/8tcg7yo9/312/
Essentially, I want to click the add button, gather the values from the input fields, and save the values to their correct properties in a corresponding object and then display them as well.
//HTML
<div class="grid-container">
<h2 class="projectName">
Project
</h2>
<h2 class="task">
Task
</h2>
<h2 class="dueDate">
Due Date
</h2>
<h2 class="status">
Status
</h2>
</div>
<!-- End Header Div -->
<!-- Begin To Do List Grid Rows -->
<div class="projects">
</div>
<!-- Button at bottom of page -->
<button id="addProject">
Add To Do
</button>
//JS
class Project {
constructor(name) {
this.name = name;
}
}
class ToDo {
constructor(projectName, task, dueDate, status) {
this.projectName = projectName;
this.task = task;
this.dueDate = dueDate;
this.status = status;
this.list =
[{
task: "",
status: "",
dueDate: "",
}];
}
}
// Add Project Button Logic
const addButton = document.querySelector("#addProject");
addButton.addEventListener('click', addProject);
function addProject(){
let counter = 0;
//Select bottom div for updates etc
var content = document.querySelector(".projects");
//Save projectNameField contents when enter is pressed on the ProjectName Field
const projectNameField = document.createElement("input");
projectNameField.classList.add('projectNameCell');
content.appendChild(projectNameField);
projectNameField.addEventListener('keydown', function onEvent(e) {
if (e.key === 'Enter' || e.key === 'Tab') {
//Get and save project name
/* var project = projectNameField.value; */
//Instantiate Project constructor with DOM value
let project = new Project(projectNameField.value);
console.log(e.key)
projectNameField.remove();
//Use constructor to display project name in text
const projectNameDisplay = document.createElement("h3");
projectNameDisplay.classList.add('projectNameCell');
projectNameDisplay.innerHTML = project.name;
content.appendChild(projectNameDisplay);
}
});
//Save titleField contents when enter is pressed on the ProjectName Field
const taskField = document.createElement("input");
taskField.classList.add('taskCell');
content.appendChild(taskField);
taskField.addEventListener('keydown', function onEvent(e) {
if (e.key === 'Enter' || e.key === 'Tab') {
//Get and save title
//Instantiate toDo constructor with DOM value
let holder = new ToDo(taskField.value);
holder.projectName = document.querySelector(".projectNameCell").innerHTML;
holder.task = taskField.value;
console.log(holder.task)
//Use constructor to display project name in text
const taskDisplay = document.createElement("h3");
taskDisplay.classList.add('titleCell');
taskDisplay.innerHTML = holder.task;
taskField.remove();
content.appendChild(taskDisplay);
}
});
const dueDateField = document.createElement("input");
dueDateField.classList.add('dueDateCell');
content.appendChild(dueDateField);
dueDateField.addEventListener('keydown', function onEvent(e) {
if (e.key === 'Enter' || e.key === 'Tab') {
//Instantiate toDo constructor with DOM value
holder.dueDate = dueDateField.value;
/* console.log(task.list.dueDate) */
dueDateField.remove();
//Use constructor to display project name in text
const dueDateDisplay = document.createElement("h3");
dueDateDisplay.classList.add('dueDateCell');
dueDateDisplay.innerHTML = holder.dueDate;
content.appendChild(dueDateDisplay);
}
});
const statusField = document.createElement("input");
statusField.classList.add('statusCell');
content.appendChild(statusField);
statusField.addEventListener('keydown', function onEvent(e) {
if (e.key === 'Enter' || e.key === 'Tab') {
//Instantiate toDo constructor with DOM value
task.status = statusField.value;
console.log(task.dueDate)
statusField.remove();
//Use constructor to display project name in text
const statusDisplay = document.createElement("h3");
statusDisplay.classList.add('statusCell');
statusDisplay.innerHTML = task.status;
content.appendChild(statusDisplay);
}
});
counter++;
}
**I have tried adding get / set methods to the ToDo class with no luck. I also tried factory functions...
This is a scoping problem right? The properties are local scoped to the holder object?... I cut my code down on this post, but the full code is at jsfiddle. The reference error happens after you hit tab or enter on the 3rd input field.... Please advise. I'm kind of puzzled why objects would be so hard to access and update after creation lol. I created the object so I could use it. I know it's something trivial about scope and when to use constructors/classes/factories, but I cant figure it out.**

update prices in text after deleting items from button in inner html

The div tags are created inside this const and I have used a delete function to delete items from order page. I want to update prices after deletion and updating in text displaying totalprice.
total_price = total_price + parseInt(item.prisinklmoms);
// Create a row for the item in view
const order_item = document.createElement('div');
order_item.className = `ordered_item`;
order_item.innerHTML = `<div class="product-name">${item.namn}</div>
<div class="product-percentage">${item.alkoholhalt}</div>
<div class="product-price">${item.prisinklmoms}</div>
<button id="div-button" onclick="delOrderlist(this)">Del</button>`;
orderContainer.appendChild(order_item);

Creating an editable HTML table from 2D array

So I am trying to make a flashcards website, where users can add, edit, and delete flashcards. There are two cards - front and back. The user can already add words, but cannot edit or delete them. For the purposes of this question I will use an example array:
var flashcards = [["Uomo", "Man"],["Donna", "Woman"],["Ragazzo", "Boy"]]
But I would like a more user-friendly way to edit the flashcards, preferably using a table like this:
<table>
<tr>
<th>Front</th>
<th>Back</th>
</tr>
<tr>
<td><input type="text" name="flashcard" value="Uomo"> </td>
<td><input type="text" name="flashcard" value="Man"></td>
</tr>
<tr>
<td><input type="text" name="flashcard" value="Donna"></td>
<td><input type="text" name="flashcard" value="Woman"></td>
</tr>
<tr>
<td><input type="text" name="flashcard" value="Ragazzo"></td>
<td><input type="text" name="flashcard" value="Boy"></td>
</tr>
</table>
<button type="button">Add more</button>
<br>
<button type="button">Save changes</button>
So they can update their flashcards editing the input fields, or clicking "add more" and it creating a new row. Clicking "save changes" updates the array to the content of the table.
I don't mind it not being a HTML table per se, but something that is easy to edit for the user.
I just cannot figure out the best way to approach this. Any advice?
I already recommended VueJS - it really is a pretty good tool for this problem. Regardless, I have typed up a basic solution using vanilla JavaScript. For the editing part it uses the contenteditable HTML attribute which allows the end-user to double click an element and change it's textContent.
The html display is basic so you can change it however to fit your needs
<div id=style="width: 100%;">
<ul id="table" style="list-style-type: none; display: inline-block;">
</ul>
</div>
<script>
var flashcards = [["Uomo", "Man"],["Donna", "Woman"],["Ragazzo", "Boy"]];
var displayedCard = []; //Using a parallel array to keep track of which side is shown
for(var i = 0; i < flashcards.length; i++){
displayedCard.push(0);
}
function renderFlashcardTable(){ //This will do the initial rendering of the table
let ulTable = document.getElementById("table");
for(var i = 0; i < flashcards.length; i++){
let card = flashcards[i];
let indexOfSideShown = displayedCard[i];
let li = document.createElement("li");
let cardValueSpan = document.createElement("span");
cardValueSpan.innerHTML = card[indexOfSideShown]; //Get the value of the side of the card that is shown
cardValueSpan.setAttribute("contenteditable", "true");
cardValueSpan.oninput = function(e){ //This method gets called when the user de-selects the element they have been editing
let li = this.parentElement;
let sideIndex = parseInt(li.getAttribute("side-index"));
card[sideIndex] = this.textContent;
}
li.appendChild(cardValueSpan);
li.appendChild(getFlipSidesButton(li));
li.setAttribute("side-index", indexOfSideShown);
li.setAttribute("card-index", i);
ulTable.appendChild(li);
}
}
function getFlipSidesButton(listItem){//This is generated for each card and when clicked it "flips the switch"
let btn = document.createElement("button");
btn.innerHTML = "Flip card";
btn.onclick = function(e){
let card = flashcards[listItem.getAttribute("card-index")];
let index = parseInt(listItem.getAttribute("side-index"));
let nextSide = (index == 1) ? 0 : 1;
listItem.setAttribute("side-index", nextSide);
listItem.children[0].innerHTML = card[nextSide];
}
return btn;
}
renderFlashcardTable();
</script>
I've put together a working sample using pure native javascript with a data-driven approach. You can have a look and understand the way how data should be manipulated and worked with in large Js application.
The point here is to isolate the data and logic as much as possible.
Hope this help.
Codepen: https://codepen.io/DieByMacro/pen/rgQBPZ
(function() {
/**
* Default value for Front and Back
*/
const DEFAULT = {
front: '',
back: '',
}
/**
* Class Card: using for holding value of front and back.
* As well as having `update` method to handle new value
* from input itself.
*/
class Card {
constructor({front, back, id} = {}) {
this.front = front || DEFAULT.front;
this.back = back || DEFAULT.back;
this.id = id;
}
update = (side, value) => this[side] = value;
}
/**
* Table Class: handle rendering data and update new value
* according to the instance of Card.
*/
class Table {
constructor() {
this.init();
}
/** Render basic table and heading of table */
init = () => {
const table = document.querySelector('#table');
const thead = document.createElement('tr');
const theadContent = this.renderRow('th', thead, { front: 'Front', back: 'Back' })
const tbody = document.createElement('tbody');
table.appendChild(theadContent);
table.appendChild(tbody);
}
/** Handling add event from Clicking on Add button
* Note the `update: updateFnc` line, this means we will refer
* `.update()` method of Card instance with `updateFnc()`, this is
* used for update value Card instance itself.
*/
add = ({front, back, id, update: updateFnc }) => {
const tbody = document.querySelector('#table tbody');
const row = document.createElement('tr');
const rowWithInput = this.renderRow('td', row, {front, back, id, updateFnc});
tbody.appendChild(rowWithInput);
}
renderInput = (side, id, fnc) => {
const input = document.createElement('input');
input.setAttribute('type','text');
input.setAttribute('name',`${side}-value-${id}`)
input.addEventListener('change', e => this.onInputChangeHandler(e, side, fnc));
return input;
}
renderRow = ( tag, parent, { front, back, id, updateFnc }) => {
const frontColumn = document.createElement( tag );
const backColumn = document.createElement( tag );
/** Conditionally rendering based on `tag` type */
if ( tag === 'th') {
frontColumn.innerText = front;
backColumn.innerText = back;
}else {
/** Create two new inputs for each Card instance. Each handle
* each side (front, back)
*/
const inputFront = this.renderInput('front', id, updateFnc);
const inputBack = this.renderInput('back', id, updateFnc);
frontColumn.appendChild(inputFront);
backColumn.appendChild(inputBack);
}
parent.appendChild(frontColumn)
parent.appendChild(backColumn)
return parent;
}
/** Getting new value and run `.update()` method of Card, now referred as `fnc` */
onInputChangeHandler = (event, side, fnc) => {
fnc(side, event.target.value);
}
}
class App {
/**
* Holding cards data
* Notice this is an object, not an array
* Working with react for a while, I see most of the times data as an object works best when it comes to cRUD, this means we don't have to iterate through the array to find the specific element/item to do the work. This saves a lot of time
*/
cards = {};
constructor(){
this.domTable = new Table();
this.domAdd = document.querySelector('#btn-add');
this.domResult = document.querySelector('#btn-result');
this.domAdd.addEventListener('click', this.onClickAddHandler );
this.domResult.addEventListener('click', this.onClickResultHandler );
}
onClickAddHandler = () => {
const id = uuid();
const newCard = new Card({id});
this.cards[id] = newCard;
this.domTable.add(newCard)
}
onClickResultHandler = () => {
/**
* Using `for ... in ` with object. Or you can use 3rd party like lodash for iteration
*/
for (const id in this.cards) {
console.log({
front: this.cards[id].front,
back: this.cards[id].back,
id: this.cards[id].id
});
}
};
}
// Start the application
const app = new App();
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/node-uuid/1.4.8/uuid.min.js"></script>
<div id="table"></div>
<button id="btn-add">Add</button>
<button id="btn-result">Result</button>
i think you can use In-Place Editing System and there's a good tutorial i found
Create an In-Place Editing System

Call field from another table in select firebase

I have a table categories and a documents. I want to create a new document and associate this document with a category already created.
The category table has the title field.
In the document creation form I want to put the categories already created in a select.
I tried doing this in the code below, but I ended up getting the select a key from the category -L0_xe_FIK9QdfTGhBDG for example, but I wanted the category title.
var documentosRef = firebase.database().ref('documentos');
var categoriasRef = firebase.database().ref('categorias');
var keyDocumento = ""
function initFirebase(){
categoriasRef.on('value', function(data) {
$('#categoria').html('');
for(categoria in data.val()){
option = "<option>"+categoria+"</option>"
$('#categoria').html($('#categoria').html()+option);
}
})
}
Structure:
{ "categorias": {
"L0_xe_FIK9QdfTGhBDG": {
"titulo": "Categorias 1"
},
"-L0a0FPFkXkKb3VNFN0c":{
"titulo": "Categorias 2"
}
}
Let check it out: JavaScript for/in Statement
var categorias = data.val();
for(var key in categorias){
option = "<option>"+categorias[key].titulo +"</option>"
$('#categoria').html($('#categoria').html()+option);
}

Categories