How to make the materializecss dropdown work when added dynamically - javascript

Following is an example of a sample webform I made in Google Apps Script, where I'm trying to dynamically add three select dropdowns and an input element whenever the add button is clicked. The elements should render in following order -
dropdown dropdown input dropdown.
I'm using materialize framework for this.
After a lot of trying and going through the materializecss documentation, I was able to render the text input field as expected. But, the dropdowns still won't render. Clearly, I'm making some mistake, cannot figure out what and where.
I'm including the code files-
Code.gs
function doGet(e) {
Logger.log(e);
return HtmlService.createTemplateFromFile('form_materialize').evaluate();
}
function include(fileName){
return HtmlService.createHtmlOutputFromFile(fileName).getContent();
}
form_materialize.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<!-- google font pack link -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<!-- Mini materialize.css cdn link -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<?!= include('css_scripts'); ?>
</head>
<body>
<div class="container">
<div class = "row">
<h1>A Sample Form</h1>
</div>
<div id="productsection">
<!-- product details like "Product Type"(dropdown), "Products"(dropdown), "Product Qty"(text input field), "Unit"(dropdown) to be added here dynamically -->
</div>
<div class = "row">
<a class="btn-floating btn-large waves-effect waves-light red" id="addproduct"><i class="material-icons">add</i></a>
</div>
</div>
<!-- Mini materialize.js cdn link -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<?!= include('js_scripts_materialize'); ?>
</body>
</html>
js_scripts_materialize.html
<script>
document.addEventListener('DOMContentLoaded', function() {
var elems = document.querySelectorAll('select');
var instances = M.FormSelect.init(elems, options);
});
let counter = 0;
const orderTypeList = ["PH", "ECOM"];
const optionList = ["Test Product 1", "Test Product 2", "Test Product 3", "Test Product 4", "Test Product 5"];
const unitOptionList = ["KGS", "PCS", "BAGS"];
document.getElementById("addproduct").addEventListener("click", addInputField);
function addInputField(){
counter++;
// everytime when "add product" button is clicked, the following elements must be added to the "<div id="produc></div>" tag.
// <div class="row">
// <div class="input-field col s4" id="divone">
// <select id="productX">
// <option>option-i</option>
// <option>option-1</option>
// <option>option-2</option>
// ...
// ...
// <option>option-n</option>
// <select>
// </select>
// <div class="input-field col s4" id="divtwo">
// <input id="productqtyX" type="text">
// <label for="productqtyX">Quantity</label>
// </div>
// <div class="input-field col s4" id="divthree">
// <select id="productUnitX">
// <option>option-1</option>
// <option>option-2</option>
// ...
// ...
// <option>option-n</option>
// </select>
// </div>
// </div>
// creates a new div of class row
const newDivElem = createElementTemplate('div', null, ['row']);
// creates a new select tag for order type dropdown
const newOrderTypeSelectElem = createElementTemplate('select', "ordertype" + counter.toString());
// generates the content of the dropdown for products and is inserted to the above "productX" select tag
createOptionsElem(newOrderTypeSelectElem, orderTypeList);
// creates a new select tag for product dropdown
const newProductSelectElem = createElementTemplate('select', "product" + counter.toString());
// generates the content of the dropdown for products and is inserted to the above "productX" select tag
createOptionsElem(newProductSelectElem, optionList);
// creates a input element for quantity input
const newQtyInputElem = createElementTemplate('input', 'productqty' + counter.toString(), ['validate']);
newQtyInputElem.type = 'text';
// creates a label for the quantity input element
const newQtyLabelElem = createElementTemplate('label');
newQtyLabelElem.textContent = "Quantity";
//Creates a new select element for product quantity unit(dropdown)
const newUnitSelectElem = createElementTemplate('select', 'productqtyunit' + counter.toString());
// generates the content of the dropdown for units and is inserted to the above "productqtyunitX" select tag
createOptionsElem(newUnitSelectElem, unitOptionList);
//create inner "div" tags with class "input-field col s4" as described in materializecss documentation
const innerDivElems = [];
for(let i = 0; i < 4; i++){
innerDivElems.push(createElementTemplate('div', `div${(Number(i) + 1)}`, ['input-field', 'col', 's3']));
}
innerDivElems[0].appendChild(newOrderTypeSelectElem);
innerDivElems[1].appendChild(newProductSelectElem);
innerDivElems[2].appendChild(newQtyInputElem);
innerDivElems[2].appendChild(newQtyLabelElem);
innerDivElems[3].appendChild(newUnitSelectElem);
//Inserts select, quantityInput, quanityLabel, newUnitSelectTag tags in div child
for(let i in innerDivElems){
newDivElem.appendChild(innerDivElems[i]);
}
// Finally, appends the newly created div tag to the productSection tag.
document.getElementById('productsection').appendChild(newDivElem);
}
function createOptionsElem(selectElem, optionsArr){
const newDefaultOptionElem = document.createElement('option');
newDefaultOptionElem.disabled = true;
newDefaultOptionElem.setAttribute('selected', true);
newDefaultOptionElem.textContent="Choose your option";
selectElem.appendChild(newDefaultOptionElem);
for(let i in optionsArr){
const newOptionElem = document.createElement('option');
newOptionElem.textContent = optionsArr[i];
newOptionElem.value = optionsArr[i];
// Inserts the option tag in select tag
selectElem.appendChild(newOptionElem);
}
}
// function to create a new element
function createElementTemplate(tagType, idVal, classNameList){
const newElement = document.createElement(tagType);
if(idVal !== undefined)
newElement.id = idVal;
if(classNameList !== undefined){
for(let i in classNameList){
newElement.classList.add(classNameList[i]);
}
}
return newElement;
}
</script>

Although I'm not sure whether I could correctly understand your expected result, how about the following modification?
In this modification, your js_scripts_materialize.html is modified.
Modified script:
I think that in this case, this part might not be required to be used.
document.addEventListener('DOMContentLoaded', function() {
var elems = document.querySelectorAll('select');
var instances = M.FormSelect.init(elems, options);
});
And also, please modify addInputField() as follows.
From:
document.getElementById('productsection').appendChild(newDivElem);
To:
document.getElementById('productsection').appendChild(newDivElem);
var elems = document.querySelectorAll('select'); // Added
M.FormSelect.init(elems); // Added
By this modification, I thought that when you click a red button, you can see the dropdown lists.

Related

Dynamically created Select Options behave differently when called from a button vs directly in function Materialize CSS

document.addEventListener('DOMContentLoaded', function() {
var elems = document.querySelectorAll('select');
var instances = M.FormSelect.init(elems, {});
});
const container = document.getElementById("stringcontainer");
var select = createSelect(1);
select.addEventListener('change', hello);
container.appendChild(select);
function add(){
var select1 = createSelect(2);
select1.addEventListener('change', hello);
container.appendChild(select1);
}
function createSelect(num){
//Create array of options to be added
var array = ["Option 1","Option 2","Option 3"];
//Create and append select list
var selectList = document.createElement("select");
selectList.id = "asp"+num;
//selectList.className = "browser-default";
selectList.required = true;
selectList.innerHTML += "<option disabled selected>Choose Option</option>"
//Create and append the options
for (var i = 0; i < array.length; i++) {
var option = document.createElement("option");
option.value = array[i].split(" ").join("").toLowerCase();
option.text = array[i];
selectList.appendChild(option);
}
return selectList;
}
function hello(){
console.log("Added EventListener");
}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/#materializecss/materialize#1.1.0/dist/css/materialize.min.css">
<div class="row">
<div id="stringcontainer"/>
</div><!-- CLOSE ROW -->
<div class="row">
<div class="input-field col s3">
<button id="astring" class="waves-effect waves-light btn-small"
onclick="add()">Add</button>
</div>
</div><!-- CLOSE ROW -->
<script src="https://cdn.jsdelivr.net/npm/#materializecss/materialize#1.1.0/dist/js/materialize.min.js"></script>
Called from JS as a function:
Called from a button with the same function:
The result is an invisible unstyled select when you press the button.
I'm using Materialize CSS. The eventListener is also not working properly when adding from the button also. It works for a console log but anything more complex like getSelectedValues(), it fails
I expected the Select to render the same way as the first image. Can anyone explain why this happening and offer a solution?
The problem is, you apply the css only once in the beginning by using
document.addEventListener('DOMContentLoaded', function() {
var elems = document.querySelectorAll('select');
var instances = M.FormSelect.init(elems, {});
});
You have to do the same, each time you add a new select. So, the following should work the way you want it to.
document.addEventListener('DOMContentLoaded', function() {
applyCSS();
});
const container = document.getElementById("stringcontainer");
var select = createSelect(1);
select.addEventListener('change', hello);
container.appendChild(select);
function add(){
var select1 = createSelect(2);
select1.addEventListener('change', hello);
container.appendChild(select1);
applyCSS();
}
function createSelect(num){
//Create array of options to be added
var array = ["Option 1","Option 2","Option 3"];
//Create and append select list
var selectList = document.createElement("select");
selectList.id = "asp"+num;
//selectList.className = "browser-default";
selectList.required = true;
selectList.innerHTML += "<option disabled selected>Choose Option</option>"
//Create and append the options
for (var i = 0; i < array.length; i++) {
var option = document.createElement("option");
option.value = array[i].split(" ").join("").toLowerCase();
option.text = array[i];
selectList.appendChild(option);
}
return selectList;
}
function hello(){
console.log("Added EventListener");
}
function applyCSS(){
var elems = document.querySelectorAll('select');
var instances = M.FormSelect.init(elems, {});
}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/#materializecss/materialize#1.1.0/dist/css/materialize.min.css">
<div class="row">
<div id="stringcontainer"/>
</div><!-- CLOSE ROW -->
<div class="row">
<div class="input-field col s3">
<button id="astring" class="waves-effect waves-light btn-small"
onclick="add()">Add</button>
</div>
</div><!-- CLOSE ROW -->
<script src="https://cdn.jsdelivr.net/npm/#materializecss/materialize#1.1.0/dist/js/materialize.min.js"></script>
I moved the code, which applies the CSS into applyCSS and then call it once in the DOMContentLoaded event listener and also each time the add function is called.

Get individual input values from dynamic input fields

After creating dynamic inputs along with its dynamic id's by append, only the latest generated input field gives values
Other dynamic input fields return empty string
Note: I'm able to get all values of dynamic inputs by using for loop and pushing inside an array, but i also want to get each input individually on key up . But only final input field created gives value
Here is my fiddle,
https://jsfiddle.net/xd8nvktf/3/
Not able to come up with a solution, Help is appreciated
--Thanks
HTML
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<button type = "button" class = "btn btn-primary" id = "addInput">Add Input Fields</button>
<div id = "inputAdder">
</div>
</body>
JS/JQUERY
var a = 0;
var fieldValue;
var dataStored = [];
var valueStored = [];
$("#addInput").on("click", function() {
a += 1;
fieldValue = `#filter-value${a}`;
$("#inputAdder").append(
`<div class = "row" id="appender${a}">
<div class="col-md-4 padderSpace">
<input id="filter-value${a}">
</div>
</div>`
)
dataStored.push({value:fieldValue});
console.log(dataStored)
})
$("#inputAdder").on("keyup",fieldValue,changeFunc);
function changeFunc(){
console.log($(fieldValue).val());
valueStored = [];
for(let i = 0;i<=dataStored.length;i++){
valueStored.push({value:$(dataStored[i].value).val()})
console.log(valueStored)
}
}
You need to pass current textbox id to your changeFunc function.
function changeFunc(e){
console.log($("#" + e.target.id).val());
}
Here is working sample.

Adding event handler to many dynamically generated buttons with jQuery

I have a dynamically generated form with groups of checkboxes representing categories of companies. These eventually get plotted on a dynamic chart (not shown here). Each group of companies has a toggle button to turn all the checkboxes on or off in each category.
I have a jQuery handler for the first toggle button (Tech Giants) using its ID and then checks or unchecks all the boxes in that group accordingly.
My question is this, which refers to the last portion of code in the block below. Instead of manually coding a handler for each toggle button, how can I create one that will apply to all of them on the page? Each button should only check or uncheck all the boxes in its specific category. Note that the first button on the page is separate, and not part of the category selection checkboxes we want to handle.
Here's the code in JSFiddle:
https://jsfiddle.net/gq5tw309/
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!-- This button is different than the other buttons -->
<button class="button-text" id="customize-button">Open User Settings</button>
<!-- Placeholder for dynamic form -->
<div id="company-selection-form"></div>
<script type="text/javascript">
function toMachineString(humanString) {
var machineString = humanString.replace(/\s+/g, '-').toLowerCase();
machineString = machineString.replace('&','');
return machineString;
}
// Setup the form
var categories = new Map([
['Tech Giants',['Alphabet','Amazon','Apple','Facebook','Microsoft']],
['Semiconductors', ['AMD','Intel','Nvidia']],
['Telecoms',['AT&T','Verizon','Sprint','T-Mobile']]
// ...
]);
// Build company selection form inputs
let companySelectionHTML = '';
for (let category of categories) {
categoryName = category[0];
categoryList = category[1];
// Setup a div to differentiate each category of companies.
// Will be used for turning on/off categories en masse
companySelectionHTML += `<div id="${toMachineString(categoryName)}">\n`;
// Category heading
companySelectionHTML += `<h4>${categoryName}</h4><button id="btn-${toMachineString(categoryName)}" data-checked="true">Toggle</button>`;
categoryList.forEach(companyName => {
companySelectionHTML += `
<label class="checkbox-label">
<input id="x-${toMachineString(companyName)}" class="checkbox" type="checkbox" name="company" value="${companyName}" checked>
<label for="x-${toMachineString(companyName)}">${companyName}</label>
</label>`;
});
companySelectionHTML += '</div>\n</div>\n</div>\n';
}
// Append to DOM
const companySelectionId = document.getElementById('company-selection-form');
companySelectionId.insertAdjacentHTML('beforeend', companySelectionHTML);
// Arm the toggle button
// HOW DO I APPLY THIS TO ALL THE TOGGLE BUTTONS INSTEAD OF JUST ONE?
$(document).ready(function(){
$('#tech-giants').click(function() {
// Access the data object of the button
var buttonData = $(this).data();
// Set all checkboxes 'checked' property
$('#tech-giants input[type=checkbox]').prop('checked', !buttonData.checked);
// Set the new 'checked' opposite value to the button's data object
buttonData.checked = !buttonData.checked;
// Update the chart -- code goes here
// dynamically-refresh-chart();
});
});
</script>
Thank you!
First bind your event like so for dynamically generated HTML (the buttons):
$('body').on("click", ".yourClass", function () {
//Your code goes here
});
Then use the class on the button instead of an ID, to apply the event listener to all buttons with the given class.
You could do it like this: bind the click() event to all buttons that have an id starting with "btn" $(document).on("click", "button[id^='btn']", function() {}); or just add a class to all toggle buttons and bind the click() event to them, which I did in the following code.
function toMachineString(humanString) {
var machineString = humanString.replace(/\s+/g, '-').toLowerCase();
machineString = machineString.replace('&', '');
return machineString;
}
// Setup the form
var categories = new Map([
['Tech Giants', ['Alphabet', 'Amazon', 'Apple', 'Facebook', 'Microsoft']],
['Semiconductors', ['AMD', 'Intel', 'Nvidia']],
['Telecoms', ['AT&T', 'Verizon', 'Sprint', 'T-Mobile']]
// ...
]);
// Build company selection form inputs
let companySelectionHTML = '';
for (let category of categories) {
categoryName = category[0];
categoryList = category[1];
// Setup a div to differentiate each category of companies.
// Will be used for turning on/off categories en masse
companySelectionHTML += `<div id="${toMachineString(categoryName)}">\n`;
// Category heading
companySelectionHTML += `<h4>${categoryName}</h4><button id="btn-${toMachineString(categoryName)}" class="category" data-checked="true">Toggle</button>`;
categoryList.forEach(companyName => {
companySelectionHTML += `
<label class="checkbox-label">
<input id="x-${toMachineString(companyName)}" class="checkbox" type="checkbox" name="company" value="${companyName}" checked>
<label for="x-${toMachineString(companyName)}">${companyName}</label>
</label>`;
});
companySelectionHTML += '</div>\n</div>\n</div>\n';
}
// Append to DOM
const companySelectionId = document.getElementById('company-selection-form');
companySelectionId.insertAdjacentHTML('beforeend', companySelectionHTML);
$(document).ready(function() {
$(document).on("click", ".category", function() {
var buttonData = $(this).data();
$(this).closest("div").find('input[type=checkbox]').prop('checked', !buttonData.checked);
buttonData.checked = !buttonData.checked;
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button class="button-text" id="customize-button">Open User Settings</button>
<div id="company-selection-form"></div>

Adding onclick() function to a checkbox created using DOM manipulation and passing this as argument

So here's what's happening, I am creating a to-do list application whenever you type and press enter into first div, a task is generated into second div along with a checkbox, so when you click the checkbox the task gets strike through text-decoration. For this I added an onclick() function to the the checkbox but every time I click on checkbox it throws an error saying:
"Cannot read property 'setAttribute' of undefined"
here's my javascript code.
function add_task() {
var i = Math.floor(Math.random() * Math.floor(500));
var work = document.getElementById('task').value; //Save input from user
var text_node = document.createTextNode(work);
if (!work.trim()) //Check if user entered value is empty or not
{
alert("A task requires something to do ;)\ntry adding some text...");
return;
}
var task_list = document.createElement("ul"); //Create an unordered list
task_list.setAttribute("type", "none");
var create_task = document.createElement("INPUT"); //Create A CHECKBOX
create_task.setAttribute("type", "checkbox");
create_task.setAttribute("id", i);
create_task.setAttribute("onclick", "task_done(this)");
var checkbox_name = document.createElement("label"); //Create a label(user entered task) for checkbox
checkbox_name.setAttribute("for", i);
checkbox_name.appendChild(text_node);
task_list.appendChild(create_task); //Append CHECKBOX to unordered list
task_list.appendChild(checkbox_name); //Append label to unordered list
document.getElementById("div1").appendChild(task_list); //Append all elements to div
}
function task_done(t) {
document.t.setAttribute("class", "strike"); //getting error
here
}
function clear_field() {
document.getElementById('task').value = null;
}
function detect(e) {
if (e.keyCode == 13) {
add_task();
clear_field();
}
}
here's the HTML code in case you need it
<html>
<head>
<script type = "text/javascript" src = "action.js" ></script>
<link rel="stylesheet" href='to-do_style.css'>
<link href="https://fonts.googleapis.com/css?family=Raleway"
rel="stylesheet">
</head>
<body>
<div id="div1" class="left">
<h1>Tasks</h1>
Add tasks by typing into right panel and <br>press enter.
</div>
<div id="div2" class="right">
I need to...<br>
<textarea id='task' rows="15" cols="76"onkeypress="detect(event)">
</textarea>
</div>
</body>
</html>
Actually you can pass the id to the onclick handler
function add_task() {
var i = Math.floor(Math.random() * Math.floor(500));
var work = document.getElementById('task').value; //Save input from user
var text_node = document.createTextNode(work);
if (!work.trim()) //Check if user entered value is empty or not
{
alert("A task requires something to do ;)\ntry adding some text...");
return;
}
var task_list = document.createElement("ul"); //Create an unordered list
task_list.setAttribute("type", "none");
var create_task = document.createElement("INPUT"); //Create A CHECKBOX
create_task.setAttribute("type", "checkbox");
create_task.setAttribute("id", i);
// changed here
create_task.setAttribute("onclick", "task_done('" + i + "')");
var checkbox_name = document.createElement("label"); //Create a label(user entered task) for checkbox
checkbox_name.setAttribute("for", i);
checkbox_name.appendChild(text_node);
task_list.appendChild(create_task); //Append CHECKBOX to unordered list
task_list.appendChild(checkbox_name); //Append label to unordered list
document.getElementById("div1").appendChild(task_list); //Append all elements to div
}
function task_done(t) {
console.log(t)
document.getElementById(t).setAttribute("class", "strike");
}
function clear_field() {
document.getElementById('task').value = null;
}
function detect(e) {
if (e.keyCode == 13) {
add_task();
clear_field();
}
}
.strike {
outline: 2px solid green;
}
<div id="div1" class="left">
<h1>Tasks</h1>
Add tasks by typing into right panel and <br>press enter.
</div>
<div id="div2" class="right">
I need to...<br>
<textarea id='task' rows="15" cols="76" onkeypress="detect(event)">
</textarea>

Get text value from a button s created dynamically jquery

So i am dynamically adding buttons to a table based on array values returned from my generateFolderTree function, problem is i can't seem to get the text value of a clicked button or even get any events when i click the created buttons. How can i get the name of a clicked button? Code below....Thanks
Jquery
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>
$(function(){
$("#selectFolder").click(runMyFunction);
});
function runMyFunction(){
google.script.run
.withSuccessHandler(successCallback)
.withFailureHandler(showError)
.generateFolderTree();
$("#hiddenAttrib").hide();
}
function showError(error) {
console.log(error);
window.alert('An error has occurred, please try again.');
}
function successCallback(returnedArray)
{
console.log("returnedArray" + returnedArray);
var folders = returnedArray;
console.log("folders" + folders);
var i = 0;
//row;
for( i=0; i<folders.length;i++)
{
console.log("i = " + i);
var row = $('<p><tr><button class = "selectedFolder">' + folders[i] + '</button></tr></p>');
$("#source").append(row.html());
}
}
$(".selectedFolder").click(function () {
var text = $(this).text();
console.log(text);
$("#dialog-status").val(text);
});
</script>
Show.html
<!-- USe a templated HTML printing scriphlet to import common stylesheet. -->
<?!= HtmlService.createHtmlOutputFromFile("Stylesheet").getContent(); ?>
<!-- Use a templated HTML printing scriptlet to import JavaScript. -->
<div>
<div class = "block" id = "dialog-elements">
<button class = "selectFolder" id = "selectFolder" >Select a Folder</button>
</div>
<!-- This block is going to be hidden until the user selects a folder -->
<div class = "block" id = "hiddenAttrib">
<p><label for = "SelectedFolder"> Selected Folder: </label></p>
<p><label id = "foldername"> Folder Name: </label></p>
<p><label id = "folderid"> Folder ID: </label></p>
</div>
<div class = "folderTable" id = "folderTable">
<p><label class = "showStatus" id = "dialog-status">Selected Folder: </label></p>
<table style = "width:100%" id = "source">
</table>
</div>
</div>
<!-- Use a templated HTML printing scriptlet to import JavaScript. -->
<?!= HtmlService.createHtmlOutputFromFile('ShowJavaScript').getContent(); ?>
$('document').on('click', '.selectedFolder', function () {
alert($(this).text())
});
Put this piece of code of yours in $(document).ready
$(".selectedFolder").click(function () {
var text = $(this).text();
console.log(text);
$("#dialog-status").val(text);
});
use
$(ELEMENT/CLASS/ID).on('click', function(){});
instead of
$(ELEMENT/CLASS/ID).click
click() function doesnt consider elements added to DOM dynamically before we used to use live() for attaching events for dynamically created element but since live() is depreciated we should use on()
on() acts as live()

Categories

Resources