How to link multiple iterations of a Javascript object to multiple elements? - javascript

I have a page that has to display multiple copies of the same Javascript object simultaneously - they will be manipulated individually later, so each element has to be linked a unique copy of the object.
I have this (very simplified) object:
function Person(index){
this.index = index,
this.name = 'Dave',
this.photo = 'photo.jpg',
// ... other properties ...
this.changeName = function(name){
this.name = name;
}
// ... other methods ...
}
I use document.createElement in a for loop to create multiple divs, each of which should display the default name and photo, plus buttons to run functions that div only. The index is passed to the new object, and the divs are displayed in a container element.
function createPeople(num){
for(let i = 0; i < num; i++){
const individual = document.createElement('DIV');
individual.person = new Person(i);
const index = individual.person.index;
const name = '<p>' + individual.person.name + '</p>';
const button = '<button onclick=changeName()>Change name ' + index + '</button>';
// ... other HTML elements ...
individual.innerHTML = name + button;
document.getElementById('container').appendChild(individual);
}
}
The button shows the correct index, but I don't know how to make it's changeName() function target the specific object for that div, or for any changes in each object to be reflected in the correct div.

Insert the button into the document, make sure you have a reference to it, then use addEventListener on it. Don't use an inline handler.
Also, to avoid undesirable possible unsafe scripts running, only insert static HTML with .innerHTML - for dynamic content from the user, use .textContent.
individual.innerHTML = `
<p></p>
<button>Change name ${i}</button>
`;
individual.querySelector('p').textContent = individual.person.name;
individual.querySelector('button').addEventListener('click', () => {
this.changeName();
});
Assigning an individual instance to a property of a DOM element is weird though - if you need to use it elsewhere, consider making a Map mapping elements to their instances instead.

Related

Is there any way to cyclically update only a <div> content in pure Javascript?

I'm using a div with a <li> element inside where I add some dynamically created elements (localStorage keys to be more specific). I want to update the div every "x" seconds but only the <div>, not the whole page. I know that it can be done with Ajax and JQuery ($( "#here" ).load(window.location.href + " #here" );).
But is there any way to do this whit pure js?
UPDATE
The specific case is that im picking all the localstorage keys and appending them to a list inside a div. When i use like this (window.onload) it works fine. But I want to update it cyclically. If i use a "setinterval" or a button to call the function the previous keys appends repeatedly. (like: file 1.... then: file 1, file 1 file 2 ... then: file 1 file 1 file 2 file 1 file 1 file 1 file 2 file 3)
window.onload = function cargararchivos() {
localstorage = []
var keys = Object.keys(localStorage);
for (let i = 0; i < keys.length; i++) {
var elemento_lista = document.createElement('li')
elemento_lista.innerHTML = keys[i]
var lista = document.getElementById('lista_archivos')
lista.appendChild(elemento_lista)
localstorage[ keys[i] ] = localStorage.getItem( keys[i] )
elemento_lista.onclick = function()
{alert(JSON.parse(localStorage[keys[i]]))}}
};
This can be done using the setInterval function. It executes a function of your choice after a pre-determined amount of time.
To do this your DIV must have an unique ID, so we can access it using Javascript's getElementById() method.
Here's a simple example, which updates a div with the current time every 1000 milliseconds:
function update() {
document.getElementById("myDiv").innerHTML = new Date();
}
var interval = setInterval(update, 1000);
<div id="myDiv">
</div>
The reason your method works the first time, but doesn't work after that is because the method adds DOM nodes. After the second call, you're seeing DOM nodes from both the first call and second call. After the third call, you'd see nodes from the first, second, and third calls... and so on.
If you want to repeatedly call a method like cargararchivos() that adds DOM nodes, you must first remove any DOM elements that have been previously added. I've updated your code, assuming lista_archivos starts empty (no child elements):
function cargararchivos() {
localstorage = []
var keys = Object.keys(localStorage);
var lista = document.getElementById('lista_archivos')
// Ensure any previously added DOM nodes are removed.
while (lista.firstChild) {
lista.removeChild(lista.firstChild);
}
for (let i = 0; i < keys.length; i++) {
var elemento_lista = document.createElement('li')
elemento_lista.innerHTML = keys[i]
lista.appendChild(elemento_lista)
localstorage[ keys[i] ] = localStorage.getItem( keys[i] )
elemento_lista.onclick = function()
{alert(JSON.parse(localStorage[keys[i]]))}
}
};

How can I save DOM elements inside of a javaScript object? I want to update the page by updating the object

How can I save DOM elements inside of a javaScript object? I want to update the page by updating the object.
Bellow I created and Added elements to the website using a for loop. I want to save some of those elements inside some objects properties so that I can update the html in the page by updating the textContent of the property value in the object.
for ( var i = 1 ; i <= numberOfPlayers ; i++ ){
//create object for each player.
var name = nameInputArray[i].value;
players["player" + i] = {
name: name,
score: 0,
fireStreak: 0,
xStreak: 0,
html: {}
}
//Create a Player box for each player.
var playerBox= document.createElement('div');
playerBox.classList.add('player_box');
document.getElementById('player-box-container').appendChild(playerBox);
var playerName = document.createElement('div');
playerName.textContent = players["player" + i].name;
playerName.classList.add('player_names');
playerBox.appendChild(playerName);
var playerScoreHeading = document.createElement('div');
playerScoreHeading.classList.add('player_score_heading');
playerScoreHeading.textContent = "Player Score:";
playerBox.appendChild(playerScoreHeading);
var playerScoreDiv = document.createElement('div');
playerScoreDiv.classList.add('player_score');
playerBox.appendChild(playerScoreDiv);
var playerScoreNumber = document.createElement('span');
playerScoreNumber.classList.add('player_score_number');
playerScoreNumber.textContent = players["player" + i].score;
playerScoreNumber.id = "player_score_number_" + i;
playerScoreDiv.appendChild(playerScoreNumber);
//THIS IS THE PART NOT WORKING!!!!!!!!!!!!!!!!!(THE NEXT LINE)
players["player" + i].html.score = document.getElementById("player_score_number_" + i);
playerScoreDiv.innerHTML += "pts";
}
for some reason when I try to access:
function changeScore(){
players.player1.html.score.textContent = 30;
}
It would change the inside the object but it will not render on the webpage.
Please help.
By reassigning an HTML string to playerScoreDiv.innerHTML, you lose all the element objects that you had previously created as descendants of playerScoreDiv: they are recreated (with obviously different object references) from the HTML.
So instead of:
playerScoreDiv.innerHTML += "pts";
do:
playerScoreDiv.appendChild(document.createTextNode('pts'));

JS- Call a method from a generated button for a specific object

I m new at coding , and I m searching for a way to launch a specific prototype method for each object of my array from a generated button
I've got json data to loop , and include each object on a js array.
//My data got 3 objects
var readArr = JSON.parse(myData)
var js_arr = [];
// loop on the array of object
for (var i = 0; i < readArr.length; i++) {
var generatedObject = new Object();
//init method from prototype
generatedObject.init(readArr[i].prop1, readArr[i].prop2, readArr[i].prop3, readArr[i].prop4, readArr[i].prop5,
readArr[i].prop6, readArr[i].prop7, readArr[i].prop8);
js_arr.push(generatedObject);
//generated a div which will contains a button for each occurence
var newCntent = document.createElement('div');
newCntent.id = readArr[i].prop1 + i;
newCntent.className = "cntent";
document.getElementById('zone_btn').appendChild(newCntent);
//create a button inside the div
var newBtn = document.createElement('div');
newBtn.id = i + readArr[i].prop1;
newBtn.className = "etalonBtn";
newBtn.innerHTML = readArr[i].prop1;
document.getElementById(newCntent.id).appendChild(newBtn);
I've got a prototype method on my Object class and what I want is launch that method for each object linked with a button.
The first generated button will launch the method for js_arr[0], the second for js_arr[2],...how to code the fact that when I click on the button , the method must be call by "the object of the array which permetted your creation"
I don't have any clue on the way I can do this I tried several things:
newBtn.addEventListener('click', function () {
read_arr[i].DisplayUpdate();
});
};
(returning read_arr[i] is not defined), so I just want to change that read_arr[i] by this now famous :"the object of the array which permetted your creation" !
and I really need help on this...
Thank you by anticipation and sorry for this preschool english.
The issue is that when you're getting to the click function (i.e. - when actually clicking the button) you're already out of the loop, so js doesn't know what 'read_arr[i]' is.
You need to pass 'read_arr[i]' as a parameter to the function.
Basically something like this:
newBtn.addEventListener('click', function (object) {
object.DisplayUpdate();
},js_arr[i]);
This will change the function's signiture to also take a parameter- object, and also pass js_arr[i] as that parameter.
Hope that helps
you could also assign and bind the function to call when you create the button in your loop:
var newBtn = document.createElement('div');
newBtn.id = i + readArr[i].prop1;
newBtn.className = "etalonBtn";
newBtn.innerHTML = readArr[i].prop1;
newBtn.onclick = jsArr[i];

Merging css classes with jQuery/Javascript

I have a problem. I have a bunch webpage that makes heavy use of multiple css classes.
<div class="class1 class2 class3">foo</div>
Unfortunately, I have a "browser" (for lack of a better term) that can not handle multiple css classes in that manner.
I can identify all the elements with multiple classes but now I need to create new classes that merge them. First attempt was to inline all the styles into the style attribute, however that was far too slow, and bloated the document needlessly.
What I now want to do is find an element with multiple classes. Create a new class which is a combination, and replace the elements class with the newly created one, as well as any other elements with the same class combination.
Any thoughts on how best to approach this.
Loop through all tags. Split the class names into an array. Sort it to get it into a predictable order. Join the string back together.
$(document).ready(function() {
var classList = {};
$("*").each(function() {
var temp;
if (this.className) {
temp = this.className.split(" "); // split into array
temp.sort(); // put in predictable order
this.className = temp.join(""); // put class name back without spaces
classList[this.className] = true; // add to list
}
});
// classList object contains full list of all classNames used
});
FYI, it seems really odd that you'd have a browser that supports jQuery, but doesn't support CSS styles for multiple class names. You do realize that you are going to have to supply completely different stylesheets that work off the concatenated names, right? And, if you can change the stylesheets, it makes me wonder why you can't change the HTML.
Working implementation: http://jsfiddle.net/jfriend00/uPET7/
Summary: This function returns an ordered list of all duplicate class names, which can easily be used to merge classes.
To start off, get a useful list of duplicates:
var multi = {};
$("*[class]").each(function(){
var class = this.className.replace(/^\s+|\s+$/g,"").replace(/\s+/g,".");
if(!/\./.test(class)) return; //Ignore single classes
if(multi[class]){
multi[class]++;
} else {
multi[class] = 1;
}
});
//Now, merge duplicates, because .class1.class2 == .class2.class1
var multi_nodup = {};
for(var classes in multi){
var a_classes = classes.split(".");
var a_classes = a_classes.sort();
var a_classes = a_classes.join(".");
if(multi_nodup[a_classes]){
multi_nodup[a_classes] += multi[classes];
} else {
multi_nodup[a_classes] = multi[classes]
}
}
//Now, multi_npdup is a map of all duplicate classnames
var array_multi = [];
for(var classes in multi_nodup){
array_multi.push([multi_nodup[classes], classes]);
}
array_multi.sort(function(x,y){return y[0]-x[0]});
//array_multi is an array which looks like [["class1.class2.class2", 33],
// ["class3.class4", 30], ...]
// = A list, consisting of multiple class names, where multiple classnames
// are shown, together with the nuber of occurences, sorted according to
// the frequence
Execute my function, and output variable array_multi. This will show you a map of multiple class names, so that you can replace multiple classnames, accordingly.
Because of the special way I stored the class names, you can use $("." + array_multi[n][0]) to access all elements which have a set of classname which equals to the set as described at the nth position in array_multi.
Example of readable output:
//Overwrites current document!
var list = "";
for(var i=0; i<array_multi.length; i++) list += array_multi[i][0] + "\t" + array_multi[i][1];
document.open();
document.write("<pre>"+list+"</pre>")
document.close();
Automatic conversion
A way to automate the merging of the classnames i by adding all separate class properties to a JavaScript string, and add it to an object. This is the most reliable way to get the exact CSS properties, because attempting to get the classnames through the document.styleSheets object can produce slightly different results. Example:
var classStyle = {};
classStyle["class1"] = "border:1px solid #000;";
classStyle["class2"] = "color:red";
//Make sure that each declaration ends with a semicolon:
for(var i in classStyle) if(!/;$/.test(classStyle[i])) classStyle[i] += ";";
//Initialise
var all_styles = {};
for(var i=0; i<array_multi.length; i++){
all_styles[array_multi[i][1]] = "";
}
//This loop takes definition precedence into account
for(var currentCName in classStyle){
var currentClass = new RegExp("(?:^|\\.)" + currentCName + "(?:\\.|$)");
// Rare occasion of failure: url("data:image/png,base64;....")
var separateProps = classStyle[currentCName].split(";");
var prop_RE = {};
for(var p=0; p<separateProps.length; p++){
var cssProperty = separateProps[p];
if(!/:/.test(cssProperty)) continue; //Invalid CSS property
prop_RE[cssProperty] = new RegExp("(^|;)\\s*" + cssProperty.match(/(\S+)\s*:/gi)[1] + "\\s*:[^;]+;?", "gi");
}
for(var class in all_styles){
if(currentClass.test(class)){
for(var k in prop_RE){
all_styles[class] = all_styles[class].replace(prop_RE[k],"$1") + k;
}
}
}
}
//To finish off:
var allClassesToString = "";
for(var class in all_styles){
var newClass = class.replace(/\./g, "_");
$("."+class).each(function(){
this.className = newClass;
});
allClassesToString += "."+newClass + "{" + all_styles[class] + "}\n";
}
// allClassesToString <------- This variable now holds a string of all duplicate CSS classes!
//Example:
var style = $("<style>");
style.text(allClassesToString);
style.appendTo($("head:first"));
Does not seem to crazy to accomplish this,
Loop through every element that has more than 1 class. Sort the classes (doesn't matter how as long as it is consistent) then merge them together to create the new class. Keep a list of all new css classes and check against them in case of duplicates.
To get all the styles from an element see here

Display names of variables (or everything) in window[], html/javascript

I'm trying to make a poor man's Firebug inside of an HTML page. I created an external script that is placed somewhere in the target page. Initialized with <body onload="monitor_init()">. It also brings in a style sheet to absolutely position a partially-opaque table with the results.
So it runs through everything (values) in window[], displays it in one column, as well as the typeof(window[i]) in another column. I'd also like to have another column that displays the name of the variable or object.
function monitor_init()
{
var css = document.createElement("link");
css.setAttribute("href","jsmonitor.css");
css.setAttribute("rel","stylesheet");
css.setAttribute("type","text/css");
document.getElementsByTagName("head")[0].appendChild(css);
var temp = document.createElement("div");
temp.setAttribute("id","monitor_box");
var temp2 = document.createElement("table");
temp2.setAttribute("id","monitor_output");
temp.appendChild(temp2);
document.body.appendChild(temp);
monitor();
}
var monitor_speed = 100;
function monitor()
{
while(document.getElementById("monitor_output").childNodes.length > 0)
{
document.getElementById("monitor_output").removeChild(document.getElementById("monitor_output").lastChild);
}
for (i in window)
{
if (["function","undefined"].indexOf(typeof(window[i]))!=-1)
{
continue;
}
// This is where I tried to make a first column displaying the name, couldn't find anything that worked.
// Disregard the .name, that was just last-ditch stuff
//var temp = document.createElement("tr");
//var temp2 = document.createElement("td");
//temp2.appendChild(document.createTextNode(window[i].name));
//temp.appendChild(temp2);
var temp = document.createElement("tr");
var temp2 = document.createElement("td");
temp2.appendChild(document.createTextNode(typeof(window[i])));
temp.appendChild(temp2);
var temp2 = document.createElement("td");
temp2.appendChild(document.createTextNode(window[i]));
temp.appendChild(temp2);
document.getElementById("monitor_output").appendChild(temp);
}
setTimeout("monitor()",monitor_speed);
}
By using typeof I was able to skip displaying the values of functions and some undefined things. I was hoping that if I could get access to the name I could also skip stuff like the history, location, and other things that aren't JavaScript numbers, strings, array objects, etc., i.e. global variables that I created in other scripts on the page.
You haven't really asked a specific question, but I think you're trying to say "How do I get the name of each property of window?"
In your existing code you're using a for-in loop to go through the various properties. What I think you need to know is that your index variable will hold the name of the (current) property:
for (i in window) {
alert("Property name: " + i);
alert("Property value: " + window[i]);
}
The looping construct you're using will provide this information:
for (i in window) {
// at the point, i is a string containing the name of the property
// window[i] contains the value of that property
document.write('window.' + i + ' is ' + window[i] + '<br>');
}
but as another poster asked - why can't you use firebug lite?

Categories

Resources