I'm currently practicing with using the DOM and manipulating it. Right now, I'm practicing with creating code for a general clicker style game. The tools goes up every time that tool is clicked and I have a builder that a player can purchase and open/unlock (if the count of the tools is enough to purchase).
I am having a hard time trying to call multiple functions, but also while manipulating the DOM by appending div elements (if that makes sense). I know that I will have to loop thru the data below and then outside of the loop I would need to append some div elements to the builder container.
The data that I'm working with:
data = {
tools: 200,
builders: [
{ id: 'builder_A', price: 100, unlocked: false },
{ id: 'builder_B', price: 400, unlocked: false },
{ id: 'builder_C', price: 700, unlocked: false }
]
};
The specs that I'm working with:
calls document.getElementById()
appends some builder div elements to the builder container
const builderContainer = document.getElementById('builder_container');
assert.isAbove(builderContainer.childNodes.length, 0);
unlocks any locked builders that need to be unlocked
code.renderBuilders(data);
expect(data.builders[0].unlocked).to.be.equal(true);
expect(data.builders[1].unlocked).to.be.equal(true);
expect(data.builders[2].unlocked).to.be.equal(false);
only appends unlocked builders
code.renderBuilders(data);
const builderContainer = document.getElementById('builder_container');
expect(builderContainer.childNodes.length).to.be.equal(2);
expect(builderContainer.childNodes[0].childNodes).to.have.length(5);
deletes the builder container's children before appending new builders
const builderContainer = document.getElementById('builder_container');
const fakeBuilder = document.createElement('div');
container.appendChild(fakeBuilder);
code.renderBuilders(data);
expect(builderContainer.childNodes.length).to.be.equal(2);
expect(builderContainer.childNodes[0].childNodes).to.have.length(5);
Here is the code that I've written thus far:
function renderBuilders(data) {
let builderContainer = document.getElementById('builder_container');
for (let i = 0; i < data.length; i++) {
const newBuilder = currentBuilder; // storing the current builder in a new variable, but not sure if this would be the correct route?
currentBuilder = makeBuilderDiv(); // which already creates a builder container div
let deletedBuilder = deleteAllChildNodes(); // this function deletes the builder container's children before appending new builders
let unlockedBuilder = getUnlockedBuilders(); // this function would unlock any locked builders that need to be unlocked
}
builderContainer.appendChild(builder);
}
However, but when I'm trying to append outside of the loop, I'm getting this error (for a different function that was already created):
function updateToolsPerSecondView(tps) {
const tpsView = document.getElementById('tps');
tpsView.innerText = tps;
}
TypeError: Attempted to wrap getElementById which is already wrapped
I believe I'm going to have to call those 3 functions in the loop and possibly create new variables to store, but I can't seem to figure out what I'm missing in the loop itself and why I'm getting the error.
Any help would be appreciated!
Whether your code execute in the loop or not, getElementById should be executed once. The error message says TypeError: Attempted to wrap getElementById which is already wrapped, which one of tags with the function has a tag as itself. In the function updateToolsPerSecondView it changed tpsView inner text to a html tag. It should be a string. You may change like from
tpsView.innerText = tps;
to
tpsView.innerText = tps.innerText;
tpsView.innerText can not have tag as its value.
Related
I am trying to build an chrome extension for lichess.org to permanently remove some elements from the website.
As the elements (in this case divs) can reappear if the user navigates through the website, I implemented a MutationObserver to remove the divs again as soon as they get added again. However, even though the function to remove them is called and they don't change their data-id, they are only removed when the function is called for the first time.
This is what I've tried so far:
These are the divs I want to remove. They are exactly the same after reappearing.
const bullet1 = document.querySelector('[data-id="1+0"]');
const bullet2 = document.querySelector('[data-id="2+1"]');
This is the MutationObserver. The div gets added with the addedNode.
const parent_lobby = document.querySelector("#main-wrap > main");
const mutationObserver = new MutationObserver(mutations => {
if (mutations[0].addedNodes[0].className == "lobby__app lobby__app-pools"){
remove_bullet_QP();
}
})
mutationObserver.observe(parent_lobby, {childList: true})
This is the function called to remove the elements. The first call of the method that happens as soon a the webiste is opened.
function remove_bullet_QP(){
bullet1.remove();
bullet2.remove();
}
remove_bullet_QP();
I've also tried to overwrite the divs before calling the function to remove them, but it didn't change the result.
Thank you for your help.
I changed bullet1 and bullet2 from const to let and reassigned them in every function call. This seems to work.
As I've said in my question, I've tried reassigning before, but without changing it to let. This should have given me an error message, somehow it didn't.
I am working on a simple blog website as my first Web Dev project. In the page where I write a blog, I am using a simple svg icon with an onClick listener to add a paragraph to the blog. This works like Medium where when you click on the "+" icon, it adds a new paragraph for you to write. Here is the code of my onClick event listener function:
function addNewPara() {
// let newContent = {
// date: blogContent.date,
// author: blogContent.author,
// title: blogContent.author,
// contentArray: blogContent.contentArray.push("")
// };
// setBlogContent(newContent);
setBlogContent(prevValue => {
prevValue.contentArray.push("")
return {
...prevValue,
}
});
}
This has two ways where I update the state value. The issue here is that when I click the plus icon and this method is triggered, it pushes two empty strings into the contentArray key.
If I uncomment and switch to the other method, I get the following error:
TypeError: blogContent.contentArray.map is not a function
The TypeError occurs where I am using the array to render the relevant HTML elements. What could be the issues behind this?
this should be your setable method
setBlogContent({...blogContent, contentArray: [...blogContent.contentArray, "your new item"]})
I currently try to build a form with javascript which has two functionalities:
1) Adding elements dynamically to a list
2) Identify through a button click a certain element (e.g. with the highest value)
See (wanted to add pictures directly to my post, but I am lacking StackOverflow reputation - so here are they as links):
https://i.ibb.co/KxvV5Ph/Bildschirmfoto-2019-11-03-um-19-12-51.png
First functionality works fine (see above, added installations). The second doesnt. My plan was the following:
1) When an element gets added to the list I also push it as an object of the class "installation" to the array installations = []
2) When I click on "Identify Longest Duration" I iterate through a map function over the installations array (and output the highest value as an alert).
Unfortunately, the installations array is empty when I call it from another function.
Get values from form
var instStorage = document.getElementById("instStorage");
var instMasse = document.getElementById("instMasse");
var instPrice = document.getElementById("instPrice");
var instDischarge = document.getElementById("instDischarge");
const installations = [] ; // empty installations array
Adding values to DOM, Calling another function to add values to installations array
const createInstallation = () => {
... (working code to add vars from 1) to list element in DOM)...
addInstallation(); // calling another function to add installation to installations array
}
Class Definition of installation
class installation {
constructor(storage, masse, price, discharge) {
this.storage = storage;
this.masse = masse;
this.price = price;
this.discharge = discharge;
}
... (getter functions here) ...
summary = () => {
return `Installation Specs: Storage ${this.getStorage()},
Mass ${this.getMasse()}, Price ${this.getPrice()} and Discharge
Rate
${this.getDischarge()}`;
}
}
Adding installation to installations array
const addInstallation = () => {
installations.push(new installation(instStorage, instMasse, instPrice, instDischarge));
(...)
}
When I call for test purposes my summary function within the createInstallation() function (after calling addInstallation()) everything works fine; the first element of the installations array gets displayed:
alert(installations[0].summary());
See:
https://i.ibb.co/Khc6R7r/Bildschirmfoto-2019-11-03-um-19-32-41.png
When I call the summary function from the event listener of the "Identify Longest" button the installations array is suddenly empty, even though I added an element a second before (which should have been added to the installations array).
See:
https://i.ibb.co/80bTFWY/Bildschirmfoto-2019-11-03-um-19-36-48.png
I am afraid that's a problem of scope; but I don't see how to fix it.
Help is appreciated :-)
Thanks a lot!
Get values from form
var instStorage = document.getElementById("instStorage");
That is what is not happening. getElementById() gets you an element, by id. Not its value.
Assuming that instStorage and friends are input elements (which is not shown unfortunately), you may want to change the code to actually get their value:
var instStorage = document.getElementById("instStorage").value;
var instMasse = document.getElementById("instMasse").value;
var instPrice = document.getElementById("instPrice").value;
var instDischarge = document.getElementById("instDischarge").value;
I am cobbling together some scripts to create floating clouds on my page.
My problem occurs when I try to call the movechip() function.
Prior to calling that function (either in the for loop or in the function where the clouds are constructed) divs and images were appended to the container div and the objects contained the correct attributes (verified using the element inspector in developer tools)
I have tried moving the movechip() call from the constructor to the for loop but that has not helped
I removed the original styling (with css) and attached the styling with javascript. This has also not helped.
Using the full instructions for the use of the script (can be found at the bottom of this post) I have made a function which makes an image element, appends it to a div, appends the div to the page and calls the "chip" constructor function. I am calling the function in a for loop for the number of clouds I want on the page.
Here is the for loop:
for ( var i = 0; i <= cloudNo/4; i ++ ) {
// call a function which creates a new div and chip object
var cloudName = "flyingCloud" + i.toString();
newCloud(i);
movechip(cloudName);
}
Here is the function:
// cloud function
function newCloud(number) {
// assign a 'name' to the cloud to be used as the id and then passed to the chip function
var cloudName = "flyingCloud" + number.toString();
// create a div element to house the image
var cloud = document.createElement('div');
// create an image element
var cloudImg = document.createElement('img');
// append the image to the div
cloud.appendChild(cloudImg);
// assign image src as cloud url
cloudImg.src = cloudImageUrl;
// assign the cloudname as the ID
cloud.id = cloudName;
// set the style of the cloud div
cloud.setAttribute('style', 'position:absolute; left: -500px; width:50; height:62; font-size:10px;');
// append the cloud to the container div
cloudDiv.appendChild(cloud);
// create a new chip
cloud = new Chip(cloudName, 362,362);
}
but I keep getting this error:
moveobj.js:71 Uncaught TypeError: Cannot read property 'style' of null(…)
here is a link to the problem code, line 71 is trying to access the style property of the 'chip':
http://samisite.com/extras/HTMLobj-1640/moveobj.js
I realise this may be a very easy problem to fix but can't seem to figure out what's wrong. I would be really grateful for any input.
full instructions can be found here: http://dynamicdrive.com/dynamicindex4/flyimage.htm
setAttribute only overrides the style attribute to the last one! I didn't know that so I found another solution for you
Styling JS Dom object
cloud.style.position = "absolute";
cloud.style.left = "-500px";
cloud.style.width = "50px";
cloud.style.height = "62px";
cloud.style.fontSize = "10px";
I have put together a fun API for game creation. In the code I create a prototype for Mover and then extend it with several specific prototypes (Gold, Monster, and Hero). Each one is based on a img tag with a given ID. I use type-specific information in the constructor and a single template method for each type. Most of the functional code in Mover depends on those type-specific details. I have included one example for simplicity.
I use method calls in a separate script to create and destroy instances of the Mover child types. When I create and destroy one instance at a time everything works as intended. The image updates, the sound plays and it is removed after the correct delay. If I create two or more, however, only the last one works as expected. So if I make gold, moster, hero. Only the hero will remove correctly. The other two will play the audio, but don't appear to update.
I ran into the same problem when I tried to attach a function to the onclick event for more than one instance. Only the last one worked and the others did nothing. Obviously I'm missing something about the way java handles method assignments. Any explanation you can offer would help.
Thanks,
BSD
function Mover()
{
}
Mover.prototype.InitTag = function()
{
this.HTMLtag.src=this.imageURL;
this.HTMLtag.style.position="absolute";
this.HTMLtag.style.width=characterSize;
this.HTMLtag.style.height=characterSize;
this.Position(Math.floor(Math.random()*(MaxW-characterSize)+(characterSize/2)),Math.floor(Math.random()*(MaxH-characterSize)+(characterSize/2)));
}
Mover.prototype.Destroy = function()
{
var disp = this.HTMLtag.display;
this.HTMLtag.src=this.destroyURL
this.HTMLtag.display = disp;
this.destroyAudio.play();
this.RemoveTag();
}
function Monster(id)
{
this.MonsterID = id;
this.HTMLtag = document.getElementById("monster"+id);
this.imageURL = "monster1.jpg";
this.destroyURL = "monster2.jpg";
this.destroyAudio = monsterAudio;
}
Monster.prototype = new Mover();
Monster.prototype.RemoveTag = function()
{
var mID = this.MonsterID;
setTimeout(function() {field.DeleteMonster(mID)}, 1000);
}
function Hero()
{
this.HTMLtag = document.getElementById("hero");
this.imageURL = "hero1.jpg";
this.destroyURL = "hero2.jpg";
this.destroyAudio = heroAudio;
}
Hero.prototype = new Mover();
Hero.prototype.RemoveTag = function()
{
setTimeout(function() {field.DeleteHero()}, 5000);
}
function Gold(id)
{
this.GoldID = id;
this.HTMLtag = document.getElementById("gold"+id);
this.imageURL = "gold1.jpg";
this.destroyURL = "gold2.jpg";
this.destroyAudio = goldAudio;
}
Gold.prototype = new Mover();
Gold.prototype.RemoveTag = function()
{
var mID = this.GoldID;
setTimeout(function() {field.DeleteGold(mID)}, 1000);
}
---------UPDATE UPDATE UPDATE-----------
I have at least partially fixed the problem. I have gotten it to work, but I still don't know why it didn't function as intended. I noticed that while my browser's (Chrome) developer tools could visually identify the most-recently-added Mover when it was being destroyed, it could not do so with the any other movers.
Tag of most recently added Mover can be identified in Chrome developer tools.
This suggested that Mover.HTMLtag was not actually the same as document.getElementById('mover1'). I was able to confirm this by looking at the variables in the GoldField.DeleteMover. At the line indicated mover.src has not changed, but movers[id].HTMLtag.src has been correctly updated. In the most-recently-added case they were both the same.
GoldField.prototype.DeleteMover = function(id)
{
var isHero = false;
if(this.Hero!=null && id==this.Hero.myID)
{
this.Hero = null;
isHero = true;
}
else if(this.Tower!=null && id==this.Tower.myID)
{
this.Tower = null;
}
var mover = document.getElementById("mover"+id);
if(!isHero)
{
this.tag.removeChild(mover);//<<< HERE HERE HERE HERE
delete this.movers[id];
}
}
So, I changed one line in Mover.Destroy. By finding the tag by ID and setting the src. I was able to reliable behavior. It would appear that Mover.HTMLtag is not reliable the same after the second Mover is added. Any explanation?
Mover.prototype.Destroy = function()
{
document.getElementById(this.HTMLtag.id).src=this.destroyURL;
this.HTMLtag.src=this.destroyURL;//old method
this.destroyAudio.play();
this.RemoveTag();
}
On suspicion that this might extend to other updates to this.HTMLtag I set up some basic movement of the Hero. It works great, but if you add one additional Mover of any kind it no longer moves. That narrows down the question considerably. Why would constructing a second Mover cause the prototype members to change?
So I debug your code and I found the cause of your problem. The problem was when you create a new instance of monster you storing a reference to it on the monster var. And when you delete it you don't delete / update the reference to it. So your delete function myField.DeleteMover(id) try to delete a monster already deleted. How to solve this.
// create an array to keep ref to our instances
var monsters= [];
// var monster = null;
function addMonster()
{
// monster = goldField.AddMonster();⏎
// push every monster in array
monsters.push(goldField.AddMonster());
}
function killMonster()
{
// if array.length is true
if (monsters.length) {
// call the destroy function on the last ref
monsters[monsters.length - 1].Destroy();
// remove the last ref from array using pop
monsters.pop();
}
//monster.Destroy();
}
This is working however I think all of this should be done in the objects itself. And you should not care about it here.
Another advice try to use more array methods. Avoid using delete on array index because it mess with index and count instead use splice(index, 1) same for add item in array use push instead of arbitrary index.
Anyway funny game! Good luck to finish it.
Edit, after your answer I go back an test.
To make it work I do this.
// First go inGoldField.prototype.DeleteMover and replace the ugly delete index by
this.movers.splice(id, 1);
// Then in the Mover.prototype.Destroy
// This part is a a little blurred for me.
// the current HTMLtag looks good but when I console.log like this
console.log('before', this.HTMLtag);
this.HTMLtag = document.querySelector("#mover" + this.myID);
console.log('after', this.HTMLtag);
// They are not equal look like the first is outdated
You should convert all your delete and add to splice and push methods.
This is just a quick debug I don't know why the selector is outdated.
So I check the code again and I make it work without refreshing the selector. The problem is caused by the creation of dom element with innerHTML.
First reset
this.HTMLtag.src=this.destroyURL
Then instead of
//Mover.prototype.Destroy
this.tag.innerHTML+="<img id='mover"+this.moverCount+"'>";
I create a img dom el.
var img = document.createElement("img");
img.setAttribute('id', 'mover' + this.moverCount);
this.tag.appendChild(img);
All Monsters are now deleted with the image.
I don't check for the hero but first you should update your innerHTML and reply if there is still a problem. I don't think there is any problem with some prototype.