getElementsByClassName not working when I change to different class name - javascript

I've looked over numerous Stack Overflow discussions on getElementsByClassName but can't seem to find anything that can help me resolve this particular issue. To explain . . .
I have the following javascript
field_to_update.innerHTML = '';
var elOptNew = document.createElement('option');
elOptNew.text = '---'
elOptNew.value = '';
field_to_update.add(elOptNew);
field_to_update.options[0].selected = true;
var track_names = document.getElementsByClassName('wpaudio');
for (i=0; i<track_names.length; i++) {
var track_name = track_names[i].innerHTML;
var elOptNew = document.createElement('option');
elOptNew.text = track_name.replace("&", "&");
elOptNew.value = track_name;
field_to_update.add(elOptNew); // standards compliant; doesn't work in IE
}
and I am looking to extract the names of a list of audio files using the line var track_names = document.getElementsByClassName('wpaudio') which refers to the following code included in the functions.php file of a wordpress child theme.
<ol id="audioFilesList" class="reactionFormAudio">
<?php
// loop through rows (parent repeater)
while( have_rows('song_upload') ): the_row(); ?>
<li><p class="wpaudio" name="audioFileName"><?php the_sub_field('track_name'); ?></p><br>
The above scenario works fine. BUT i have decided to not use the ordered list of audio files as listed above - but instead us the default audio playlist that can be created within a wordpress post, which produces the following code:
<div class="wp-playlist-tracks">
<div class="wp-playlist-item wp-playlist-playing">
<a class="wp-playlist-caption" href="https://www.futureproofpromotions.com/wp-content/uploads/2020/09/01_WhereToBegin-128.mp3">
1.
<span class="wp-playlist-item-title">
“Where To Begin” </span>
<span class="wp-playlist-item-artist"> — Alice Clayton</span>
</a>
<div class="wp-playlist-item-length">2:57</div>
</div>
<div class="wp-playlist-item">
<a class="wp-playlist-caption" href="https://www.futureproofpromotions.com/wp-content/uploads/2020/09/02_BeingAlone-128.mp3">
2.
<span class="wp-playlist-item-title">
“Being Alone” </span>
<span class="wp-playlist-item-artist"> — Brosnan</span>
</a>
<div class="wp-playlist-item-length">3:15</div>
</div>
</div>
</div>
However, when I swap the javascript line from document.getElementsByClassName('wpaudio'); to document.getElementsByClassName('wp-playlist-caption'); in order to reference the different class name in the the new html, it doesn't display any names!
I am very new indeed to javascript so this may be obvious to someone skilled in that language, but I do have a limited knowlege of php.
Would anybody be able to explain why when i change the class name/reference in the above scenario I get no names displayed?
FYI I have also tried changing document.getElementsByClassName() to document.querySelectorAll() which also works well, when referencing the original class ('.wpaudio') - but again produces no result when referencing ('.wp-playlist-caption') or any other class name nested within it (such as .wp-playlist-item-title or .wp-playlist-item-artist).
Any help with the above would be most appreciated

Since wp-playlist-caption has multiple nested elements, you can either query for each one of them and string them together or take the lazy route and use .innerText. That will extract the rendered plaintext inside the element and its children.
// Wait until the page has loaded
document.addEventListener('DOMContentLoaded', () => {
var field_to_update = document.getElementById('reactionForm_strongestTrack');
field_to_update.innerHTML = '';
var elOptNew = document.createElement('option');
elOptNew.text = '---'
elOptNew.value = '';
field_to_update.add(elOptNew);
field_to_update.options[0].selected = true;
// Search for all playlist-captions inside the playlist-tracks list.
// 'Currently playing' has a playlist-caption too,
// limiting to the tracklist excludes it
document.querySelectorAll('.wp-playlist-tracks .wp-playlist-caption')
.forEach( // The following arrow function will be called for each element
track => {
// There are nested elements but we just want the plaintext
let track_name = track.innerText;
// Create a new element and append to the select as before
let elOptNew = document.createElement('option');
elOptNew.text = track_name.replace("&", "&");
elOptNew.value = track_name;
field_to_update.add(elOptNew);
});
});
Additional reference for arrow functions.

Related

Dynamic JS/HTML elements appear out of order only in GAS? [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 1 year ago.
Improve this question
I have a dynamic HTML form that I generate from Google Apps Script. I expect the elements to appear in the same order that they appear in the input object, which is the same order by which I append the elements. But the elements actually appear in an unexpected order.
I followed this other post to try to sort this out but the elements still appear out of order when I run my code in GAS. The thing is, when I run my code in jsfiddle, it works as expected, i.e. the elements appear in the same order as they do in the input object. The elements just don't order as expected in GAS.
Why do the elements appear out of order in GAS but appear in order in jsfiddle? How do I resolve this in GAS using vanilla JS?
Copy of jsfiddle code:
HTML
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<div id='input_parent'></div>
</body>
<br><input type="button" value="Submit" onClick="test()">
</form>
JS
inputObj = {"first field":{"required":true,"dataType":"select","options":["first opt","second opt"]},"second field":{"required":true,"dataType":"text","options":"none"}}
// Section
section = document.getElementById("input_parent");
div = document.createElement("div");
div.setAttribute("id", "input_child");
section.appendChild(div);
var fields = Object.keys(inputObj);
Array.from(fields).forEach((arg) => {
// Label
section = document.getElementById("input_parent");
label = document.createElement("label");
label.setAttribute("id", "label_"+arg);
label.setAttribute("for", arg);
label_txt = document.createTextNode(arg+":");
label.appendChild(label_txt);
section.appendChild(label);
if (inputObj[arg].dataType == "select") {
// Create select element
section = document.getElementById("input_parent");
const select_element = document.createElement("select");
select_element.setAttribute("id", "select_"+arg);
section.appendChild(select_element);
var options = inputObj[arg].options
for(let o = 0; o < options.length; o++)
{
var element = document.getElementById("select_"+arg);
const option = document.createElement("option");
var text = document.createTextNode(arg+":");
option.textContent = options[o];
option.setAttribute("value", options[o]);
element.appendChild(option);
};
} else {
section = document.getElementById("input_parent");
input_field = document.createElement("input");
input_field.setAttribute("id", "input_"+arg);
input_field.setAttribute("type", inputObj[arg].dataType);
section.appendChild(input_field);
}
});
Additional info in response to #Nikko J. The following code should reproduce the results in the images below.
dynamHtmlTbrlsht.html to render form.
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<div id='input_parent'></div>
</body>
<br><input type="button" value="Submit" onClick="test()">
</html>
<script type='text/javascript'>
window.onload = function(){
google.script.run.withSuccessHandler(addElements).getElementInfo();
}
</script>
<script type='text/javascript'>
function addElements(inputObj) {
// Section
section = document.getElementById("input_parent");
div = document.createElement("div");
div.setAttribute("id", "input_child");
section.appendChild(div);
var fields = Object.keys(inputObj);
Array.from(fields).forEach((arg) => {
// Label
section = document.getElementById("input_parent");
label = document.createElement("label");
label.setAttribute("id", "label_"+arg);
label.setAttribute("for", arg);
label_txt = document.createTextNode(arg+":");
label.appendChild(label_txt);
section.appendChild(label);
if (inputObj[arg].dataType == "select") {
// Create select element
section = document.getElementById("input_parent");
const select_element = document.createElement("select");
select_element.setAttribute("id", "select_"+arg);
section.appendChild(select_element);
var options = inputObj[arg].options
for(let o = 0; o < options.length; o++)
{
var element = document.getElementById("select_"+arg);
const option = document.createElement("option");
var text = document.createTextNode(arg+":");
option.textContent = options[o];
option.setAttribute("value", options[o]);
element.appendChild(option);
};
} else {
section = document.getElementById("input_parent");
input_field = document.createElement("input");
input_field.setAttribute("id", "input_"+arg);
input_field.setAttribute("type", inputObj[arg].dataType);
section.appendChild(input_field);
}
});
}
</script>
trblsht.gs to create input object. (Note that this is simplified for resolving the problem at hand. In reality, inputObj is generated by running a few functions that dynamically create the object and fetch options from an external source.)
function getElementInfo() {
var inputObj = {"first_fild":{"required":true,"dataType":"select","options":["option 1","option 2","option 3","option 4"]},"second_field":{"required":true,"dataType":"text","options":"none"},"third_field":{"required":true,"dataType":"text","options":"none"},"fourth_field":{"required":true,"dataType":"text","options":"none"},"fifth_field":{"required":false,"dataType":"select","options":["option 1","option 2","option 3","option 4","option 5","option 6","option 7","option 8","option 9","option10"]},"sixth_field":{"required":false,"options":"none"},"seventh_field":{"required":false,"dataType":"select","options":["option 1","option 2","option 3","option 4","option 5","option 6"]}}
Logger.log("inputObj: "+JSON.stringify(inputObj))
return inputObj;
}
For completeness, the following lines are in an onEdit function that generates the form when the active cell == "troubleshoot".
function myOnEditTriggerFunc() {
// do other stuff
var currentRange = SpreadsheetApp.getActiveRange();
var currentVal =
currentRange.getValue().toString().replace(/(^\s+|\s+$)/g,"");
if (currentVal == "troubleshoot") {
openHTML("dynamHtmlTbrlsht","Troubleshoot",400)
return;
}
}
openHTML() referenced in above function.
function openHTML(htmlFile,htmlTitle,height) {
var html = HtmlService.createHtmlOutputFromFile(htmlFile)
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setHeight(height);
SpreadsheetApp.getUi()
.showModalDialog(html, htmlTitle);
return;
};
Output form showing unexpected order of elements:
Output log showing expected order of elements:
The jsfiddle shows the normal js.
I started to wonder if the issue is with Array.from(fields).forEach((arg) in dynamHtmlTbrlsht.html. I intentionally used Array instead of Object since Array is ordered whereas Object is not always ordered (depends on ES). Maybe there's something related to V8 runtime that's affecting this and I'm not picking up on?
When I saw your script, I thought that the reason for your issue is due to that the data is a JSON object. When the document about JSON is seen, it says as follows. Ref
In JSON, they take on these forms:
An object is an unordered set of name/value pairs. An object begins with {left brace and ends with }right brace. Each name is followed by :colon and the name/value pairs are separated by ,comma.
And, in your script, the values are retrieved from the JSON object using var fields = Object.keys(inputObj). In order to confirm this using your script, when console.log(Object.keys(inputObj)) is put after the line of function addElements(inputObj) { at Javascript side and before the line of return inputObj; at Google Apps Script side, each log shows as follows.
For Javascript side, it's ["fourth_field", "seventh_field", "second_field", "sixth_field", "third_field", "fifth_field", "first_fild"].
For Google Apps Script side, it's ["first_fild","second_field","third_field","fourth_field","fifth_field","sixth_field","seventh_field"].
It is found that the order of keys is different between the Javascript side and Google Apps script side. I thought that this might be the reason for your issue.
If you want to use the order of ["first_fild","second_field","third_field","fourth_field","fifth_field","sixth_field","seventh_field"], how about the following modification?
Modified script 1:
In this pattern, the keys are set as an array in order.
Google Apps Script side:
From:
return inputObj;
To:
return [inputObj, ["first_fild","second_field","third_field","fourth_field","fifth_field","sixth_field","seventh_field"]];
Javascript side:
From:
function addElements(inputObj) {
// Section
section = document.getElementById("input_parent");
div = document.createElement("div");
div.setAttribute("id", "input_child");
section.appendChild(div);
var fields = Object.keys(inputObj);
To:
function addElements([inputObj, fields]) {
// Section
section = document.getElementById("input_parent");
div = document.createElement("div");
div.setAttribute("id", "input_child");
section.appendChild(div);
// var fields = Object.keys(inputObj);
Modified script 2:
In this pattern, the keys are set as Object.keys(inputObj) in order. By this, the same order can be used between the Google Apps Script side and the Javascript side.
Google Apps Script side:
From:
return inputObj;
To:
return [inputObj, Object.keys(inputObj)];
In this pattern, the modification of the Javascript side is the same as pattern 1.

How do I change more than one element?

EDIT: I changed the var to class but I might have some error in here.
Here it goes, I want to have this paragraph in which the user can change the name on the following paragraph. The code I'm using only changes one name but the rest remains the same.
<script type="text/javascript">
function changey(){
var userInput = document.getElementById('userInput').value;
var list = document.getElementByClassName('kiddo');
for (let item of list) {
item.innerHTML = userInput;
}
}
</script>
<input id="userInput" type="text" value="Name of kid" />
<input onclick="changey()" type="button" value="Change Name" /><br>
Welcome to the site <b class="kiddo">dude</b> This is how you create a document that changes the name of the <b class="kiddo">dude</b>. If you want to say <b class="kiddo">dude</b> more times, you can!
No error messages, the code only changes one name instead of all three.
Use class="kiddo" instead of id in the html.
You can then use var kiddos = document.getElementsByClassName('kiddo') which will return an array of all the elements of that class name stored in kiddos.
Then you just need to loop through the values and change what you want.
Example of loop below:
for (var i = 0; i < kiddos.length; i++) {
kiddos[i].innerHTML = userInput;
}
id should be unique on the page. Javascript assumes that there is only one element with any given id. Instead, you should use a class. Then you can use getElementsByClassName() which returns an entire array of elements that you can iterate over and change. See Select ALL getElementsByClassName on a page without specifying [0] etc for an example.
Hello You should not use id, instead use class.
Welcome to the site <b class="kiddo">dude</b> This is how you create a document that changes the name of the <b class="kiddo">dude</b>. If you want to say <b class="kiddo">dude</b> more times, you can!
After That on Js part :
<script type="text/javascript">
function changey(){
var userInput = document.getElementById('userInput').value;
var list = document.getElementByClassName('kiddo');
for (let item of list) {
item.innerHTML = userInput;
}
}
</script>
you should use class instated of id. if you use id then the id [kiddo] must be unique
In short, document.querySelectorAll('.kiddo') OR
document.getElementsByClassName('kiddo') will get you a list of elements to loop through. Take note of querySelectorAll, though - it uses a CSS selector (note the dot) and doesn't technically return an array (you can still loop through it, though).
See the code below for some full working examples (const and arrow functions are similar to var and function, so I'll put up a version using old JavaScript, too):
const formEl = document.querySelector('.js-name-change-form')
const getNameEls = () => document.querySelectorAll('.js-name')
const useNameFromForm = (formEl) => {
const formData = new FormData(formEl)
const nameValue = formData.get('name')
const nameEls = getNameEls()
// Set the text of each name element
// NOTE: use .textContent instead of .innerHTML - it doesn't get parsed, so it's faster and less work
nameEls.forEach(el => el.textContent = nameValue)
}
// Handle form submit
formEl.addEventListener('submit', (e) => {
useNameFromForm(e.target)
e.preventDefault() // Prevent the default HTTP request
})
// Run at the start, too
useNameFromForm(formEl)
.name {
font-weight: bold;
}
<!-- Using a <form> + <button> (submit) here instead -->
<form class="js-name-change-form">
<input name="name" value="dude" placeholder="Name of kid" />
<button>Change Name</button>
<form>
<!-- NOTE: Updated to use js- for js hooks -->
<!-- NOTE: Changed kiddo/js-name to spans + name class to remove design details from the HTML -->
<p>
Welcome to the site, <span class="js-name name"></span>! This is how you create a document that changes the name of the <span class="js-name name"></span>. If you want to say <span class="js-name name"></span> more times, you can!
</p>
var formEl = document.querySelector('.js-name-change-form');
var getNameEls = function getNameEls() {
return document.querySelectorAll('.js-name');
};
var useNameFromForm = function useNameFromForm(formEl) {
var formData = new FormData(formEl);
var nameValue = formData.get('name');
var nameEls = getNameEls(); // Set the text of each name element
// NOTE: use .textContent instead of .innerHTML - it doesn't get parsed, so it's faster and less work
nameEls.forEach(function (el) {
return el.textContent = nameValue;
});
};
// Handle form submit
formEl.addEventListener('submit', function (e) {
useNameFromForm(e.target);
e.preventDefault(); // Prevent the default HTTP request
});
// Run at the start, too
useNameFromForm(formEl);
<button class="js-get-quote-btn">Get Quote</button>
<div class="js-selected-quote"><!-- Initially Empty --></div>
<!-- Template to clone -->
<template class="js-quote-template">
<div class="js-quote-root quote">
<h2 class="js-quote"></h2>
<h3 class="js-author"></h3>
</div>
</template>
You have done almost everything right except you caught only first tag with class="kiddo".Looking at your question, as you need to update all the values inside tags which have class="kiddo" you need to catch all those tags which have class="kiddo" using document.getElementsByClassName("kiddo") and looping over the list while setting the innerHTML of each loop element to the userInput.
See this link for examples:https://www.w3schools.com/jsref/met_document_getelementsbyclassname.asp
try:
document.querySelectorAll('.kiddo')
with
<b class="kiddo">dude</b>

Using javascript to get sum from input title

Below is a javascript which is able to get the input value & needs external libraries.
I know this seem odd but I have to use javascript to grab the price from the input title and no external libraries is required. Is it possible to work from input title ?
<?php
include_once('database_conn.php');
$sqlCDs = 'SELECT CDID, CDTitle, CDYear, catDesc, CDPrice FROM nmc_cd b inner join nmc_category c on b.catID = c.catID WHERE 1 order by CDTitle';
$rsCDs = mysqli_query($conn, $sqlCDs);
while ($CD = mysqli_fetch_assoc($rsCDs)) {
//have a look at the input field below
echo "\t<div class='item'>
<span class='CDTitle'>{$CD['CDTitle']}</span>
<span class='CDYear'>{$CD['CDYear']}</span>
<span class='catDesc'>{$CD['catDesc']}</span>
<span class='CDPrice'>{$CD['CDPrice']}</span>
<span class='chosen'><input type='checkbox' id="yourId" name='CD[]' value='{$CD['CDID']}' title='{$CD['CDPrice']}' /></span>
</div>\n";
}
?>
JS:
function isChecked(chosen) {
//and here it is called again
var valOfTitle = document.selectElementById("yourId").getAttribute('title');
var number = parseFloat(valOfTitle);
if(chosen.is(':checked')) {
sum = sum + parseFloat(valOfTitle);
} else {
sum = sum - parseFloat(valOfTitle);
}
$('#total').valOfTitle(sum.toFixed(2));
};
Just use getAttribute if you dont want to use any external libraries.
element.getAttribute("title")
i hope i understood you correctly:
you can access the data in your input field via jQuery's attr() method.
// "valOfTitle" is just a name..could also be "george" or "germany"
// "yourInputElementId needs to be the id of your element (written in
// "")
var valOfTitle = $(yourInputElementId).attr('title');
without jQuery:
var valOfTitle = document.selectElementById(yourInputFieldId).getAttribute('title');
if you want it as a number you can just do:
var number = parseFloat(valOfTitle);
if you don't want to add an id to your element you also can use another javascript selector, p.e. getElementsByName- but note: this will return a HTML Collection and not a single element, which means you will have to loop over this collection to access your data.
http://www.w3schools.com/jsref/met_doc_getelementsbyname.asp
maybe this google search may help you too:
https://www.google.de/search?q=javascript+selectors&oq=javascript+selectors&aqs=chrome..69i57.4015j0j7&sourceid=chrome&es_sm=91&ie=UTF-8#q=pure+javascript+selectors
or even
How can I use the jQuery-like selector in pure JavaScript

Within an if-statement inside of a loop, how can I get the children of an element and push them into an array?

I'm making a meal planning / grocery list application with JavaScript and jQuery. Basically, this is how it works:
The user adds recipes through a form. The user enters the name of the recipe as well as the ingredients associated with that recipe.
When submitted, each recipe is stored in a <dl id="recipeList"> element. The name of the recipe is stored as a <dt class="recipe"> and each ingredient is stored as a <dd class="ingredient">.
For each day of the week, the user may click on a "Plan a Meal" anchor. This brings up a copy of the #recipeList. When the user clicks on a <dt>, a class="meal" is applied to it and the rest of the list is removed.
The next step is for the user to click on the "Generate Grocery List" anchor. When the user does this, JavaScript should loop through each .meal and create an array, #mealsArray. JavaScript should then loop through each class="recipe" and check to see if the .innerHTML of it matches an item in the #mealsArray. It does this just fine, but the problem is after a match is found, it should get the children of the class="recipe" (i.e., the <dt class="ingredient">) and push them into #groceriesArray.
JavaScript will not find the children of the <dt class="recipe">. I have tried numerous ways of coding this, such as:
this.children
this.childNodes
this.children()
this.children("dt")
this.children(".ingredient")
this.contents()
this.find(".ingredient")
It usually finds something strange like [Object HTMLElement] or returns an error message like Type Error: this.children() is not a function.
It seems like this so be so simple, but I have no idea what to do. I will provide my code below — apologies for how sloppy it is.
Here is the HTML:
<form id="addRecipeForm">
<label>Name</label><input type="text" id="recipeName">
<label>Ingredients</label><input type="text" class="recipeIngredients">
<label>Ingredients</label><input type="text" class="recipeIngredients">
<label>Ingredients</label><input type="text" class="recipeIngredients">
<button id="recipeButton">Add Recipe</button>
</form>
<dl id="recipeList"></dl>
<div>
<h3>Sunday</h3>
Plan a Meal
</div>
<div>
<h3>Monday</h3>
Plan a Meal
</div>
<!-- And so on, until Saturday -->
Generate Grocery List
<ul id="groceryList"></ul>
Here is the JavaScript:
var recipeList = $("#recipeList");
var recipeIngredients = $(".recipeIngredients");
var planAnchor = $(".planAnchor");
var groceryListAnchor = $("#groceryListAnchor");
var groceryList = $("#groceryList");
////////// ADD A RECIPE //////////
$("#recipeButton").click(function(e) {
e.preventDefault();
var recipeName = $("#recipeName").val();
var recipeIngredients = $(".recipeIngredients");
recipeList.append("<dt class='recipe'></dt>");
recipeList.children("dt").last().text(recipeName);
for (i = 0; i < recipeIngredients.length ; i++) {
$("<dd class='ingredient'></dd>").text(recipeIngredients[i].value).appendTo(recipeList);
};
});
////////// PLAN A MEAL //////////
planAnchor.click(function(e) {
e.preventDefault();
var dayInPlanning = $(this).parent("div");
var availableRecipes = recipeList.clone();
availableRecipes.children("dd").remove();
availableRecipes.attr("id", "availableRecipes");
$(this).parent("div").append(availableRecipes);
$(this).remove();
availableRecipes.children("dt").click(function(e) {
e.preventDefault();
var selectedRecipe = $(this);
var para = $("<p class='meal'></p>");
para.appendTo(dayInPlanning);
para.text(selectedRecipe.text());
availableRecipes.remove();
});
////////// GENERATE GROCERY LIST //////////
///////// THIS IS WHERE THE PROBLEM LIES //////////
groceryListAnchor.click(function(e) {
e.preventDefault();
var mealsArray = [];
var groceriesArray = [];
// Create an array of .meal elements
$(".meal").each(function() {
mealsArray.push(this.innerHTML);
});
console.log("mealsArray is " + mealsArray);
$(".recipe").each(function() {
console.log("Checking " + this.innerHTML);
// Match the innerHTML of each .recipe to the items in the mealsArray
if ($.inArray(this.innerHTML, mealsArray) > -1) {
console.log("We found " + this.innerHTML + " in the array!");
// Get the children of that recipe, and place them in groceriesArray
// *** Not Working ***
groceriesArray.push(this.children.innerHTML)
} else {};
});
console.log("The grocery list is " + groceriesArray);
});
To explain this simply. They're two types of elements jQuery Elements and JavaScript Elements. this is a JavaScript element. jQuery functions only work with jQuery Elements. So to make it work, use:
$(this).myjQueryFunction();
so for you:
$(this).children();
In depth
When creating jQuery elements using $(), it does a few things. jQuery uses Sizzle for selecting elements. If what's passed into $() is already an element. It doesn't do anything. Of it is, it will turn it into an element. Depending on which of the two jQuery uses. It will return an element. This is a regular JavaScript element but what makes it so special? The jQuery functions can only be run after $. The reason is how you create chained-JavaScript functions using prototype:
//Kind of how it's created
$.prototype.children = function () {
//jQuery's code to get children
}
This is making it so children() can only be run off of $.
Can you try cast this Dom Element as a jQuery Element:
//so instead of this.children() use
$(this).children()
and in this case if you want HTML it will be
$(this).children().html()
but it will get you first child HTML only, you can try the followng to get for all:
html_contents = ""
$.each($(this).children(), function(){
html_contents+=$(this).html();
});

Refactoring JavaScript code in regard to a lot of DOM methods

I'm a beginner in JavaScript, and i'm writing a simple To-do list application. That accepts user input and adds it as a task in a form of a checkbox.
The problem is that, the code has become more and more repetitive, I tried to make functions for the most repeating parts. But i feel that there's a better way to do that using some kind of DOM native functions.
Here's my code (i'm writing a comment where i feel that there're better choice to make):
window.onload = function(){
submitBtn.addEventListener("click", function(){
//Some code ...
var task = document.createElement("input");
task.id = "task" + i;
task.type = "checkbox";
var taskLabel = document.createElement("label");
taskLabel.htmlFor = "task" + i;
taskLabel.appendChild(document.createTextNode(textBox.value));
//This is from a function i've created to create buttons
var deleteBtn = createButton("delete");
var undoBtn = createButton("undo", "none");
var divideBtn = createButton("divide");
var taskContainer = document.createElement("p");
//A LOT of appendChild is happening .. How can i minimize that using
//native DOM methods
taskContainer.appendChild(task);
taskContainer.appendChild(taskLabel);
taskContainer.appendChild(deleteBtn);
taskContainer.appendChild(divideBtn);
taskContainer.appendChild(undoBtn);
taskPool.appendChild(taskContainer);
//the rest of the code ....
});
}
This is the reason most people use jQuery and template systems like Handlebars or Mustache. It's actually a lot faster, not to mention cleaner to implement and thus easier to maintain, to simply insert your complete view block into the DOM instead of manually creating the individual DOM elements and appending them one by one.
The recommended way to manipulate the DOM using jQuery is to do something like:
var taskName = 'task'+i,
taskLabel = textBox.value,
taskHtml =
'<div>
<input type="checkbox" name="'+taskName+'">
<label for="'+taskNAme+'">'+taskLabel+'</label>
<fieldset>
<button id="btnDel'+i+'">Delete</button>
<button id="btnDiv'+i+'">Divide</button>
<button id="btnUndo'+i+'">Undo</button>
</fieldset>
</div>';
$(taskHtml).appendTo('#TaskPool');
Creating large DOM constructs in a single go through innerHTML is a lot faster than manually creating and appending the individual elements.
However, this still requires mixing HTML and JS, which is pretty ugly. So that's why these days developers opt for templates, which allow you to do something like:
<script id="task-template" type="text/x-handlebars-template">
<div>
<input type="checkbox" name="task{{i}}">
<label for="task{{i}}">{{label}}</label>
<fieldset>
<button id="btnDel{{i}}">Delete</button>
<button id="btnDiv{{i}}">Divide</button>
<button id="btnUndo{{i}}">Undo</button>
</fieldset>
</div>
</script>
Then in your click handler:
var source = $("#task-template").html(),
template = Handlebars.compile(source),
context = {
i: i,
label: textBox.value
},
html = template(context);
$('#TaskPool').append(html);
And from there, you can take it one step further and add two-way data binding, such as through Angular.js, Kockout.js or jsViews (+ jsRender). Then you just have something like this:
<ol id="TasksList">
<li ng-repeat="task in tasks | orderBy:orderProp">
<input type="checkbox" name="{{task.name}}" ng:model="task.checked">
<label for="{{task.name}}">{{task.label}}</label>
<fieldset>
<button ng-click="deleteTask(task.id)">Delete</button>
<button ng-click="divideTask(task.id)">Divide</button>
<button ng-click="undoTask(task.id)">Undo</button>
</fieldset>
</li>
</ol>
And in your controller:
$scope.orderProp = 'id';
$scope.nextId = 0;
$scope.addTask = function(label) {
var id = $scope.nextId++,
task = {
id: id,
label: label,
name: 'task'+id,
checked: false
};
$scope.tasks.push(task);
};
$scope.deleteTask = function(id) { ... };
$scope.divideTask = function(id) { ... };
$scope.undoTask = function(id) { ... };
If you want to minimize your code wherever you can then using a library like jquery would be one of your best options.
However, if in this case you'd prefer to stick to 'pure' javascript, you could avoid all the appends up there, by adding a method to the Node object of the DOM similar to this one:
/* is Node.prototype.multiAppend already defined? */
if( typeof Node.prototype.multiAppend !== "function" ) {
Node.prototype.multiAppend = (function() {
/*get the Array.prototype.slice method to use on the returned function*/
var slice = [].slice;
/*the function multiAppend comes to be*/
return function() {
var i = 0,
max = arguments.length;
for (; i < max; i++) {
this.appendChild(arguments[i]);
}
/*allow chainability*/
return this;
};
})();
}
This would allow you to do something like this:
var item = document.getElementsByClassName('item')[0],
/*create elements*/
h1 = document.createElement("h1"),
div = document.createElement("div"),
p = document.createElement("p");
/*append them to item with just one line*/
item.multiAppend(h1, div, p)​​​;​​​​​
Take a look at the demo here: http://jsfiddle.net/8mjBR/2/ *

Categories

Resources