Javascript createElement function - javascript

I have some code which is replicated throughout the main.js file
Javascript duplicates examples:
Example 1
for (i = userVote; i < 5; i++) {
var newVotesInput = document.createElement("input");
newVotesInput.id = "vote" + i;
newVotesInput.classList.add("vote", "displayn");
newVotesInput.type = "radio";
newVotesInput.name = "vote";
newVotesInput.value = i;
someElement.appendChild(newVotesInput);
}
Example 2
for (i = 1; i <= userVote; i++) {
var newVotesLabel = document.createElement("label");
newVotesLabel.id = "voteLabel" + i;
newVotesLabel.classList.add("voteHover");
newVotesLabel.htmlFor = "vote" + i;
someElement.appendChild(newVotesLabel);
}
Example 3
var newImg = document.createElement("img");
newImg.classList.add("babeImg", "boxsb", "leftIn");
newImg.id = "imgSrc";
newImg.src = jsonData.imgSrc;
someElement.appendChild(newImg);
Example 4
var newShuffle = document.createElement("img");
newShuffle.classList.add("shuffleImg");
newShuffle.id = "shuffleImg";
newShuffle.src = "assets/img/refresh.png";
someElement.appendChild(newShuffle);
Example 5
newImg.classList.add("babeImg", "boxsb", "leftIn");
newImg.id = "imgSrc";
newImg.src = jsonData.imgSrc;
imgInner.appendChild(newImg);
As you can see the examples do the same which is creating an element but there are several parameters which differs from example to example.
How do i make a function that i can use for all of them?
right now i only know how to build one for exactly one specific example like so ->
function createElement(element, id, cls, src){
var newElement = document.createElement(element);
element.id = id;
element.classList.add(cls);
element.src = src;
someElement.appendChild(element);
}
This is bascially Example 4.. but how do i make this more efficient and useable on all of the examples?

I would suggest the following function:
function createElement(type, attributes) {
var element = document.createElement(type);
for (var key in attributes) {
if (key == "class") {
element.classList.add.apply(element.classList, attributes[key]); // add all classes at once
} else {
element[key] = attributes[key];
}
}
someElement.appendChild(element);
}
The function takes a type (input, img, etc) and a list of attributes (i.e. an object/dictionary). We loop through the attributes and add them to the new element. The only special case is with classes, with use a different syntax (and we can have multiple classes). Here is how you can use it:
var input = createElement("input", {
"id": "myId",
"class": ["one-class", "another-class"],
"name": "someName"
});
Note that the class has an array as a value.
The result will be:
<input id="myId" class="one-class another-class" name="someName">
Here is the jsfiddle.
EDIT
To better understand: in JS, objects are like dictionaries, i.e. a bunch of key-value pairs (and prototypes, but this is not important for this discussion).
If you have an object with an id attribute, you can access it using one of the following syntax:
myObject.id // object style
myObject["id"] // dictionary style
The second syntax is very useful, since you can use variables to access object attributes:
var attr = "id";
myObject[attr];
Now, we can also iterate over an object, as we do with arrays. The only difference is that with arrays, the for in syntax returns the index, while with objects it returns the key or property name.
var myObject = { "id": 1, "name": "a name" }; // a basic object
for(var key in myObject) console.log(key, myObject[key]);
// will print
// id 1
// name a name
If you understand these two mecanics, the code I gave you will get much clearer.

Related

Making variables assigned HTML ids with a function

I can make variables one by one like this:
var bookName = document.getElementById('bookName').value,
author = document.getElementById('author').value,
translator = document.getElementById('translator').value,
pageCount = document.getElementById('pageCount').value,
publisher = document.getElementById('publisher').value,
isbn = document.getElementById('isbn').value,
printingYear = document.getElementById('printingYear').value;
But it's so hard to write and it doesn't fit with the DRY rule. So I changed the code to this:
function variableMaker(argument) {
var tags = document.getElementsByTagName(argument);
for (var i = 0; i < tags.length; i++) {
var tags[i].name = tags[i].value;
}
}
variableMaker(input);
But I can't understand if it is the true way or if it is working? How do I check if it's true or not?
In this code, I tried to get the computer find all the input tags and make variables with their name property and assign it to its values for each of them.
If I understand correctly then you want to gather data from all <input> elements. If so, then you need to call it like this:
variableMaker('input'); // use quotes!
Still even then your function does not return anything, it just ends.
You'd also better create your own object for the return collection, instead of adding values to an existing object.
Here is a working solution:
function variableMaker(tagName) {
var elements = document.getElementsByTagName(tagName);
var items = {};
for (var i = 0; i < elements.length; i++) {
var elem = elements[i];
items[elem.id] = elem.value; // Use id as key, each points to the corresponding value.
}
return items;
}
var values = variableMaker('input');
console.log(values); // show the entire return object
console.log(values.author); // access individual items from the return object
console.log(values.isbn);
<input type="text" id="author" value="Dahl">
<input type="text" id="isbn" value="1234">
.

Fill javascript object with form data

I have an object declared, and I have an html form with some matching fields.
All fields in the form are in the object, but the object also has a couple of additional variables and functions.
I'd like to fill the object with the data entered in the form, what I'm trying right now overwrites the declared object, and so doesn't have the functions nor the other variables.
The object :
var Container = {
nodes: [],
Contains: function (Node) {
for (var i = 0; i < this.nodes.length; i++) {
if (this.nodes[i].nodeID === Node.nodeID)
return (i);
}
return (-1);
}
How I fill it from the form :
const handleContainerForm = event => {
event.preventDefault();
ContainerFormToJSON(form.elements);
var i = JSONData.Contains(Container);
if (i === -1)
JSONData.containers.push(Container);
else
JSONData.container[i] = Container;
output = JSON.stringify(JSONData, null, " ");
displayContents(output);
};
The form has ID, Title, Folder, Image and Description as fields, so this last Container object doesn't have the Contains() function nor the nodes[] array.
How do I end up with a complete, filled version of the object I have declared ?
In ContainerFormToJSON function, before the statement
return Container
define:
//container.nodes and container.contains
You are right, JavaScript is very different from C#, especially in regards to OOP. But that doesn't make it better or worse.
In JavaScript, you don't need to declare an object's properties, like you have to when you use classes. I think that you only want to serialize the form's input values to JSON. I recommend not to use an object that additionally has a nodes property and a Contains method.
If you need to keep a copy of the unserialized object, create two objects:
class Container {
constructor () {
this.nodes = [];
}
indexOf (node) {
return this.nodes.findIndex(n => n.nodeID === node.nodeID);
}
}
Container.nodeID = 0; // think of it as a static property
function extractValues (elements) {
// 'elements' is an array of <input> elements
// the 'container' will be filled and serialized
var container = new Container();
for (var index in elements) {
var element = elements[index];
container[element.name] = element.value;
}
container.nodeID = Container.nodeID++; // give the container a unique ID
return container;
}
var inputs = document.querySelectorAll('input');
var jsonData = new Container();
document.querySelector('button').addEventListener('click', function () {
var newContainer = extractValues(inputs);
var index = jsonData.indexOf(newContainer);
if (index === -1) {
jsonData.nodes.push(newContainer);
} else {
jsonData.nodes[index] = newContainer;
}
var jsonString = JSON.stringify(jsonData, null, ' ');
console.log(jsonString);
});
<input name="containerID">
<input name="containerTitle">
<!-- ... -->
<button>Serialize</button>
Please note: only setting an object's properties doesn't make it to JSON. It's only JSON if it's serialized to a string. I recommend this article. To serialize a JavaScript object, use JSON.stringify.
Edit:
Looking at the edit of your question, I think it might be preferable to create a Container class. Both jsonData and the containers of the form data will be instances of that class. It can contain other containers (nodes), and can get the index of such a nested container using the indexOf method. I implemented this in the above code snippet. Whenever you hit the "Serialize" button, a new container with the current <input>s' contents will be added to jsonData. The JSON form of jsonData will be logged to the console.
I hope this is what you are looking for. To better understand JavaScript OOP,
take a look at some of the articles at MDN.

jQuery.data only saves data from the last Element

i am trying to use jQuery.data() and save an Object to my HTML-Elements. Everytime i add an list-Element to my unordered List it only saves the last object to the specific li-Element. Every other li-Elements saved data gets thrown away!
I've built a little Example. JSBin-Example
On the left, i create a List with an Object saved to it. On the right i am trying to show the data related to the Object.
Why does it only show the Object related to the last HTML-Element?
Working example:
JSBin-Example
That's because you are modifying innerHTML property of the wrapper element. What happens is in each iteration the elements are regenerated, the current elements are removed and the new elements don't have any stored data. Using innerHTML property is the worst way of modifying element contents. You just need to create a li element and append it to the wrapper element:
var random = 0;
// var testObject = [];
function addNewItem(){
random += 1;
var id = "testId" + random;
var text = "This is my " + random + ". text";
var data = {id: id, text: text};
// testObject.push(data);
// You can pass an object as the second argument
// to jQuery constructor and it calls the
// corresponding methods as setter
$('<li></li>', {
text: text + JSON.stringify(data),
id: id,
data: data
}).appendTo('#listId');
}
// bind and trigger click event
$("#add").on('click', addNewItem).click();
I changed
for(var i = 0; i < testObject.length; i++){
var listItem = "";
var id = testObject[i].id;
listItem += liStart + id + liStart2;
listItem += testObject[i].text;
listItem += liEnd;
unorderedList.innerHTML += listItem;
$("#"+id).data(testObject[i]);
}
to this in your updatelist function
//for(var i = 0; i < testObject.length; i++){
var id = testObject[testObject.length-1].id;
listItems += liStart + id+"savedData" + liStart2;
listItems += JSON.stringify($("#"+id).data());
listItems += liEnd;
//}
savedData.innerHTML += listItems;
and it fixed the issue
To help you understand my comment on the question I thought it best I'd give an example of what I meant.
I didn't have enough time to fully go through the solution but wanted to give an example of what I'd call more readable code.
I've added all variables at the top of the function. This will allow you to read and find items much quicker if you needed to alter them.
I've also merged a lot of the string values that you had into an object, namely the li element.
I've never used $.data() as an object before so wasn't really aware how I could use it to set the values in the updateSavedData() $('li'), although the console.log() does show the correct key / values.
$(document).ready(function(){
var uID = 0;
var testObject = [];
var unorderedList = $("#listId");
var savedList = $("#savedData");
var TOL = 0; //TestObjectLength
var textTemplate = "This is my [0] text!";
function addNewItem(){
uID++;
testObject.push({id: uID, text: textTemplate.replace("[0]", uID)});
TOL = testObject.length-1;
updateList();
}
function updateList(){
var li = $('<li>', { id: testObject[TOL].id, data: testObject[TOL], text: testObject[TOL].text });
li.appendTo(unorderedList);
updateSavedData(li.data());
}
function updateSavedData(li){
console.log(JSON.stringify(li));
$('<li>', JSON.stringify(li)).appendTo(savedList);
}
addNewItem();
$("#add").on('click', addNewItem);
});
Working Example
http://jsbin.com/ralizazahe/1/edit?js,console,output
Anyone that wants to progress on that please do as I'd also like to see how this could be progressed more.
Update
Taken it a step more and refactored to this
$(document).ready(function(){
var $displayList = $("#listId");
var $savedList = $("#savedData");
var textTemplate = "This is my {0} text!";
var uID = 0; //Unique ID
var data = { id: null, text: null }; //Gives a reference
function init(){
uID++;
data = { id: uID, text: textTemplate.replace("{0}", uID) };
}
function addNewItem(){
init();
$('<li>', data).appendTo($displayList);
updateSavedData(data);
}
function updateSavedData(li){
$('<li>', li).appendTo($savedList);
}
addNewItem();
$("#add").on('click', addNewItem);
});
http://jsbin.com/bajekagoli/1/edit?js,console,output

create array, use $.each to loop, and add value if not in array

I have a bunch of .defined in a text and want to create an array of unique values with javascript. So basically, for each anchor with class defined, I want to first check the array to see if the pair already exists. If exists, go to next anchor. If does not exist, add to array. This is the code I have tried using, but it does not remove duplicate values.
var arr = new Array();
y = 0;
$("a.defined").each(function() {
var spanishWord = this.text;
var englishWord = this.title;
if(spanishWord in arr) {
console.log("do nothing");
} else {
arr.push({key: spanishWord, value: englishWord});
y++;
}
For example, I have these tags in the text:
<a title="read">Leer</a>
<a title="work">Trabajar</a>
<a title="like">Gustar</a>
<a title="read">Leer</a>
<a title="sing">Cantar</a>
<a title="like">Gustar</a>
And I would like my array to look like:
Spanish Word | English Word
Leer read
Trabajar work
Gustar like
Cantar sing
but instead it looks like:
Spanish Word | English Word
Leer read
Trabajar work
Gustar like
Leer read
Cantar sing
Gustar like
Any ideas?
I would do this in two steps.. one to eliminate duplicates, and one to create the array:
http://jsfiddle.net/uN4js/
var obj = {};
$('a.defined').each(function() {
obj[this.text] = this.title;
});
var arr = [];
for (var prop in obj) {
if (obj.hasOwnProperty(prop))
arr.push({key: prop, value: obj[prop]});
};
console.log(arr);
If the object is sufficient and you don't really need an array, you could stop after the object is created.
You can probably just use a javascript object here:
var dict = {};
y = 0;
$("a.defined").each(function() {
var spanishWord = this.text;
var englishWord = this.title;
dict[spanishWord] = englishWord;
}
And there isn't really a need for unique checks, since newer values will just overwrite the older ones. If you don't want that behaviour, you can do this:
var dict = {};
y = 0;
$("a.defined").each(function() {
var spanishWord = this.text;
var englishWord = this.title;
if (!(spanishWOrd in dict)) {
dict[spanishWord] = englishWord;
}
}
Javascript's in operator is not used for testing inclusion, it's used for iteration in a for .. in .. loop.
Other answers have suggested correctly that you need either .indexOf or JQuery's $.inArray method to test inclusion in an array, but there is a simpler (and faster) way of solving this problem: use a dictionary of key/value pairs instead!
var dict = {};
$("a.defined").each(function() {
dict[this.textContent] = this.title;
});
Afterwards, you can use for key in dict to iterate over the list of unique Spanish words, and dict[key] to get the corresponding English translation.
Try this:
JavaScript
var arr = {};
$("a").each(function() {
var spanishWord = this.text;
var englishWord = this.title;
if(spanishWord in arr) {
console.log("do nothing");
} else {
arr[spanishWord] = englishWord;
}
});
console.log(arr);

Getting Attributes from User submitted text! RegExp?

I am trying to pull the attributes out of piece of submitted text in Javascript and change it to an array.
So the user submits this:
<iframe src="http://www.stackoverflow.com/" width="123" height="123" frameborder="1"></iframe>
and I would get:
arr['src'] = http://www.stackoverflow.com/
arr['width'] = 123
arr['height'] = 123
arr['frameborder'] = 1
Just need a regexp I think but any help would be great!
I recommend to use a RegExp to parse user-inputed HTML, instead of creating a DOM object, because it's not desired to load external content (iframe, script, link, style, object, ...) when performing a "simple" task such as getting attribute values of a HTML string.
Using similar (although similarcontradiction?) methods as in my previous answer, I've created a function to match quoted attribute values. Both quoted, as non-quoted attributes are matched.
The code currently returns an object with attributes from the first tag, but it's easily extensible to retrieve all HTML elements (see bottom of answer).
Fiddle: http://jsfiddle.net/BP4nF/1/
// Example:
var htmlString = '<iframe src="http://www.stackoverflow.com/" width="123" height="123" frameborder="1" non-quoted=test></iframe>';
var arr = parseHTMLTag(htmlString);
//arr is the desired object. An easy method to verify:
alert(JSON.stringify(arr));
function parseHTMLTag(htmlString){
var tagPattern = /<[a-z]\S*(?:[^<>"']*(?:"[^"]*"|'[^']*'))*?[^<>]*(?:>|(?=<))/i;
var attPattern = /([-a-z0-9:._]+)\s*=(?:\s*(["'])((?:[^"']+|(?!\2).)*)\2|([^><\s]+))/ig;
// 1 = attribute, 2 = quote, 3 = value, 4=non-quoted value (either 3 or 4)
var tag = htmlString.match(tagPattern);
var attributes = {};
if(tag){ //If there's a tag match
tag = tag[0]; //Match the whole tag
var match;
while((match = attPattern.exec(tag)) !== null){
//match[1] = attribute, match[3] = value, match[4] = non-quoted value
attributes[match[1]] = match[3] || match[4];
}
}
return attributes;
}
The output of the example is equivalent to:
var arr = {
"src": "http://www.stackoverflow.com/",
"width": "123",
"height": "123",
"frameborder": "1",
"non-quoted": "test"
};
Extra: Modifying the function to get multiple matches (only showing code to update)
function parseHTMLTags(htmlString){
var tagPattern = /<([a-z]\S*)(?:[^<>"']*(?:"[^"]*"|'[^']*'))*?[^<>]*(?:>|(?=<))/ig;
// 1 = tag name
var attPattern = /([-a-z0-9:._]+)\s*=(?:\s*(["'])((?:[^"']+|(?!\2).)*)\2|([^><\s]+))/ig;
// 1 = attribute, 2 = quote, 3 = value, 4=non-quoted value (either 3 or 4)
var htmlObject = [];
var tag, match, attributes;
while(tag = tagPattern.exec(htmlString)){
attributes = {};
while(match = attPattern.exec(tag)){
attributes[match[1]] = match[3] || match[4];
}
htmlObject.push({
tagName: tag[1],
attributes: attributes
});
}
return htmlObject; //Array of all HTML elements
}
Assuming you're doing this client side, you're better off not using RegExp, but using the DOM:
var tmp = document.createElement("div");
tmp.innerHTML = userStr;
tmp = tmp.firstChild;
console.log(tmp.src);
console.log(tmp.width);
console.log(tmp.height);
console.log(tmp.frameBorder);
Just make sure you don't add the created element to the document without sanitizing it first. You might also need to loop over the created nodes until you get to an element node.
Assuming they will always enter an HTML element you could parse it and read the elements from the DOM, like so (untested):
var getAttributes = function(str) {
var a={}, div=document.createElement("div");
div.innerHTML = str;
var attrs=div.firstChild.attributes, len=attrs.length, i;
for (i=0; i<len; i++) {
a[attrs[i].nodeName] = attrs[i].nodeValue];
}
return a;
};
var x = getAttributes(inputStr);
x; // => {width:'123', height:123, src:'http://...', ...}
Instead of regexp, use pure JavaScript:
Grab iframe element:
var iframe = document.getElementsByTagName('iframe')[0];
and then access its properties using:
var arr = {
src : iframe.src,
width : iframe.width,
height : iframe.height,
frameborder : iframe.frameborder
};
I would personally do this with jQuery, if possible. With it, you can create a DOM element without actually injecting it into your page and creating a potential security hazard.
var userTxt = '<iframe src="http://www.stackoverflow.com/" width="123" height="123" frameborder="1"></iframe>';
var userInput = $(userTxt);
console.log(userInput.attr('src'));
console.log(userInput.attr('width'));
console.log(userInput.attr('height'));
console.log(userInput.attr('frameborder'));

Categories

Resources