I’m Rahul and I’m new to coding. I have a query related to DOM event. Please look at the following code snippet -
let door1 = document.getElementById('one');
door1.src = "closed_door.svg";
const isClicked = (door) => {
if(door.src === "closed_door.svg") {
return true;
}
else {
return false;
}
};
door1.onclick = () => {
if(isClicked(door1)) {
door1.src = "beach.svg";}
};
To give you brief, one is an id for an element. Without isClicked, I am able to successfully change the src from closed door to beach on clicking. But when I introduce isClick, it doesn’t change. Can someone please tell me what I’m missing. I’ll be very thankful
Note - I'm building game similar to this - https://s3.amazonaws.com/codecademy-content/projects/chore-door/chore-door-final/index.html They are using the same process as mine. So please suggest a solution that tells me about the error I'm making here rather than an alternative to the problem
Regards
Rahul
As reported here:
the src reflected property will be the resolved URL — that is, the absolute URL that that turns into. So if that were on the page http://www.example.com, document.getElementById("foo").src would give you "http://www.example.com/images/example.png".
so to get the real src attribute you should use .getAttribute('src') like so:
const isClicked = (door) => {
if(door.getAttribute('src') === "closed_door.svg") {
return true;
}
else {
return false;
}
};
Ans also BTW, you can just shortcut it to:
const isClicked = (door) => door.getAttribute('src') === "closed_door.svg";
Replace your code with this..
let door1 = document.getElementById('one');
door1.src = "https://s3.amazonaws.com/codecademy-content/projects/chore-door/images/closed_door.svg";
const isClicked = (door1) => {
if(door1.src === "https://s3.amazonaws.com/codecademy-content/projects/chore-door/images/closed_door.svg") {
return true;
}
else {
return false;
}
};
door1.onclick = () => {
if(isClicked(door1)) {
door1.src = "https://s3.amazonaws.com/codecademy-content/projects/chore-door/images/beach.svg";}
};
Related
In my extension I want to edit the document on a few specific document edits.
My actual use case is a bit complicated so I have created a minimal example. The code below listens for any document edit. If the word "hello" exists in the edit (i.e. the user pasted some code that contains the word "hello") then we replace the change range with the pasted text but just make it upper case.
We also console.log if the edit was successful, and any potential reason the edit was rejected.
vscode.workspace.onDidChangeTextDocument(event => {
for (const change of event.contentChanges) {
if (change.text.includes("hello")) {
activeEditor.edit(editBuilder => {
editBuilder.replace(change.range, change.text.toUpperCase());
}).then(
value => console.log("SUCCESS: "+value),
reason => console.log("FAIL REASON: "+reason),
);
}
}
});
A working example would be selecting some text in a document and pasting in the text const hello = 5;. As expected, the extension replaces the text with CONST HELLO = 5; and logs SUCCESS: true.
But when I paste in some text that automatically get formatted I run into problems. If I were to paste in:
const hello = 5;
const lol = 10;
const lmao = 20;
Including all the whitespaces/tabs, then vscode wants to "format" or correct my lines, i.e. remove the whitespace. So the resulting text will be:
const hello = 5;
const lol = 10;
const lmao = 20;
The extension tries to make it uppercase still but only prints SUCCESS: false. No reason is logged at all; the reject function is not executed.
Why does the edit not succeed? Should I await the other edits somehow or keep re-trying the edit until it succeeds? Am I logging the rejection incorrectly?
In case it helps, here is code I use - I found it better to have the editBuilder outside the loop. I think you can adapt it for your purposes:
editor.edit( (editBuilder) => {
// put your for (const change of event.contentChanges) {} here
for (const match of matches) {
resolvedReplace = variables.buildReplace(args, "replace", match, editor.selection, null, index);
const matchStartPos = document.positionAt(match.index);
const matchEndPos = document.positionAt(match.index + match[0].length);
const matchRange = new vscode.Range(matchStartPos, matchEndPos);
editBuilder.replace(matchRange, resolvedReplace);
}
}).then(success => {
if (!success) {
return;
}
if (success) { ... do something here if you need to }
});
One solution is just to "keep trying again". I do not like this solution, but it is a solution nevertheless, and it currently works for my use-case.
async function makeReplaceEdit(range: vscode.Range, text: string, maxRetries = 10) {
for (let i = 0; i <= maxRetries; i++) {
const editor = vscode.window.activeTextEditor;
if (!editor) return;
const success = await editor.edit(editBuilder => {
editBuilder.replace(
range,
text
);
}, { undoStopBefore: false, undoStopAfter: false });
if (success) break;
}
};
vscode.workspace.onDidChangeTextDocument((event) => {
// See if any change contained "hello"
let foundHello = false;
for (const change of event.contentChanges) {
if (change.text.includes("hello")) {
foundHello = true;
}
}
if (foundHello) {
console.log("inside1");
const editor = vscode.window.activeTextEditor;
if (!editor) return;
makeReplaceEdit(editor.document.lineAt(0).range, "Change");
}
});
class IBSProgressExtension extends Autodesk.Viewing.Extension{
constructor(viewer, options) {
super(viewer, options);
}
load() {
//For proof of concept project, I will simply store the externalIds here in a variable.
const allExternalIds = [
'8a00f4c7-0709-4749-88b6-abb0ddccf965-0006879a',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-000688ee',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068961',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068963',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068a78',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068a0d',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068a0f',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068a11',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068a13',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068c2f',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068c31',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068c33',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b2e',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b30',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b32',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b34',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b3e',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b36',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b38',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b3a',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b3c'
];
this.viewer.model.getExternalIdMapping(data => onSuccessMapping(data));
function onSuccessMapping(data) {
const resArray = [];
allExternalIds.forEach(externalId => {
if (data[externalId]) resArray.push(data[externalId], externalId);
});
console.log(resArray);
};
console.log('IBSProgressExtension is loaded.');
return true;
}
};
Autodesk.Viewing.theExtensionManager.registerExtension("IBSProgressExtension", IBSProgressExtension);
Please have a look at my extension and please help me figure out why is this happening.
Every time i run it, the devtools logs: ViewerExtension.js:31 Uncaught TypeError: Cannot read properties of undefined (reading 'getExternalIdMapping').
The extensions get loaded before the model so the getExternalIdMapping() method does not have the model properties yet. To handle this scenario, we usually recommend using the viewer events such as Autodesk.Viewing.GEOMETRY_LOADED_EVENT to “catch” the moment when the model is available. It’s better to wait for the event. This will be fired when the model/drawing finishes loading.
Instead of:
this.viewer.model.getExternalIdMapping(data => onSuccessMapping(data));
Try this:
this.viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, (x) => {
this.viewer.model.getExternalIdMapping(data => onSuccessMapping(data));
});
Please test this and see if it's helpful. I tried to incorporate items from your comments to help you structure it out.
class IBSProgressExtension extends Autodesk.Viewing.Extension {
constructor(viewer, options) {
super(viewer, options);
this._externalIds = null;
//Eventually will want to pass in your external IDs to this function, I assume:
//this._externalIds = options.externalIds
this._doStuff = () => {
this.startDoingStuff();
};
}
load() {
console.log("loading extension");
//For now, hard coded like your example.
this._externalIds = [
"8a00f4c7-0709-4749-88b6-abb0ddccf965-0006879a",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-000688ee",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068961",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068963",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068a78",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068a0d",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068a0f",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068a11",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068a13",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068c2f",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068c31",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068c33",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b2e",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b30",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b32",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b34",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b3e",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b36",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b38",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b3a",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b3c",
];
//Not sure if this is truly the right event, but it worked when I tested on mine.
this.viewer.addEventListener(Autodesk.Viewing.MODEL_LAYERS_LOADED_EVENT, this._doStuff);
}
startDoingStuff() {
console.log("startDoingStuff executing");
this.getDbIds(this._externalIds).then((CombinedArray) => {
this.setCustomColors(CombinedArray);
});
}
setCustomColors(arrayOfIDs) {
console.log("setCustomColors executing");
var somecolor = "#7D5B51";
var threecolor = new THREE.Color(somecolor);
var vectcolor = new THREE.Vector4(threecolor.r, threecolor.g, threecolor.b, 1);
arrayOfIDs.forEach((e) => {
this.viewer.setThemingColor(e[0], vectcolor, this.viewer.getVisibleModels()[0]);
});
}
getDbIds(externalIds) {
console.log("getDbIds executing");
return new Promise((resolve) => {
this.viewer.model.getExternalIdMapping((d) => {
//console.log("getDbIdFromExternalId Executed");
let responseArr = [];
externalIds.forEach((externalId) => {
if (d[externalId]) responseArr.push([d[externalId], externalId]);
});
console.log("resolving", responseArr);
resolve(responseArr);
});
});
}
}
Autodesk.Viewing.theExtensionManager.registerExtension("IBSProgressExtension", IBSProgressExtension);
Regarding this, I was trying to achieve 3 things at this stage.
Get the externalIds from Mongodb.
Compare the externalIds with the ones gotten from getExternalIdMapping().
Get DbIds of those that matched.
Solved these by realising 2 and 3 can be put inside a.then() after .loadDocumentNode().
thank you for the help in advance! Please forgive me if this is a stupid question as I a JS noobie.
I am trying to cycle through an array of URL's that I have nested inside of an object. What I am expecting to happen is for the url's (which are google maps) to show up on my website and for the person using my website to be able to click back and forth between the different map URL's. Right now what's happening is the last item (a google maps url) of myCities.index is being properly displayed, but when I click my previous and next buttons it is going to a blank page. I have looked at my JS console and there are no visible errors there. Can anyone give me some advice as to how to fix this? My code is as follows.
const myCities = {
index:
[url1, url2, url3]
}
const prevButton = document.querySelector('.prevBtn');
const nextButton = document.querySelector('.nextBtn');
const map = document.getElementById("myCitiesss");
nextButton.addEventListener('click', nextCity);
prevButton.addEventListener('click', prevCity);
function prevCity() {
myCities.index--;
updateSrc();
checkButtons();
}
function nextCity() {
myCities.index++;
updateSrc();
checkButtons();
}
function updateSrc() {
map.src = myCities.index;
}
function checkButtons() {
prevButton.disabled = false;
nextButton.disabled = false;
if (myCities.index === 2) {
nextButton.disabled = true;
}if (myCities.index === 0) {
prevButton.disabled = true;
}
}
updateSrc();
checkButtons();
The problem is you're treating myCities.index as both the container of your data and as a counter (hence you're running incrementation/decrementation operations on it).
The counter should be a separate variable.
const myCities = [url1, url2, url3]; //<-- can now be a flat array
let index = 0; //<-- this is our counter, i.e. tracker
The relevant code changes are then: (I also took the opportunity to demonstrate some optimisations and efficiencies you can make)
nextButton.addEventListener('click', () => nextPrevCity('next'));
prevButton.addEventListener('click', () => nextPrevCity('prev'));
function nextPrevCity(which) { //<-- one function to handle both next and prev
which == 'next' ? index++ : index--;
map.src = myCities[index];
checkButtons();
}
function checkButtons() {
prevButton.disabled = false;
nextButton.disabled = false;
if (index === myCities.length - 1) //<-- check against array length, not fixed number 2
nextButton.disabled = true;
if (!index) //<-- because 0 is a falsy value, so same as "if (index === 0)"
prevButton.disabled = true;
}
[ -- EDIT -- ]
Forgot the following at the bottom of the code:
map.src = myCities[0];
checkButtons();
I am using a JS class, I have following code:
class Field {
public Value = null;
public Items = [];
public UniqueKey = null;
public getItems() {
let items = [...this.Items];
items = items.filter((item) => {
if (item.VisibleIf) {
const matched = item.VisibleIf.match(/\$\[input:(.*?)\]/g);
if (matched?.length) {
const srv = Service.getInstance();
for (let match of matched) {
match = match.slice(8, -1);
if (srv.Fields?.length) {
let found = srv.Fields.find((x) => x.UniqueKey === match);
if (found) {
item.VisibleIf = item.VisibleIf.replace(
`$[input:${match}]`,
found.Value ?? ''
);
return JSON.parse('' + eval(item.VisibleIf));
}
}
}
}
}
return true;
});
return items;
}
public getInputTitle() {
let title = this.Title;
const matched = title.match(/\$\[input:(.*?)\]/g);
if (matched?.length && title) {
const srv = Service.getInstance();
for (let match of matched) {
match = match.slice(8, -1);
if (srv.Fields?.length) {
let found = srv.Fields.find((x) => x.UniqueKey === match);
if (found) {
title = title.replace(`$[input:${match}]`, found.Value ?? '');
}
}
}
}
return title;
}
}
Now I have a Vue component:
<div v-for="Field in Fields" :key="Field.UniqueKey">
<v-select
v-if="Field.Type == 'Select'"
:label="Field.getInputTitle()"
v-model="Field.Value"
:items="Field.getItems()"
item-text="Value"
item-value="Id"
/>
<v-input
v-else-if="Field.Type == 'Input'"
v-model="Field.Value"
:label="Field.getInputTitle()"
/>
</div>
// JS
const srv = Service.getInstance();
Fields = srv.getFields(); // <- API call will be there.
So basically, data comes from an API, having Title as Input $[input:uniqueKey], in a component I am looping over the data and generating the fields. See getInputTitle function in Field class, it works very well. All the fields which are dependent on the $[input:uniqueKey] are changing when I start typing into that field on which other fields are dependent.
Now I have pretty much same concept in the getItems function, so basically, what I want to do is whenever I type into a field and that field exists in the VisibleIf on the Items, the VisibleIf will be like '$[input:uniqueKey] < 1', or any other valid JavaScript expression which can be solved by eval function. But the getItems function is called only 1st time when page gets loaded, on the other hand the getInputTitle function which is pretty much same, gets called every time when I type into the field.
I tried to explain at my best, I will provide any necessary information if needed.
Any solution will be appreciated. Thanks.
You are updating the Object itself in here:
item.VisibleIf = item.VisibleIf.replace( `$[input:${match}]`, found.Value ?? '' );
Even though you tried to copy the array, but you have done shallow copy of the object in here: let items = [...this.Config.Items];
I suggest the following solution:
const visibleIf = item.VisibleIf.replace(
`$[input:${match}]`,
found.Value ?? ''
);
const val = '' + helpers.evalExp('' + visibleIf);
if (helpers.isJSON(val)) {
return JSON.parse(val);
}
Means instead of changing the VisibleIf object, just store it into the variable and just use that.
I hope that it will fix your issue. Let me know if it works.
I am a new Developer and am working on a Tic Tac Toe SPA. I have a working game, but I want to customize the game tokens. I have tried creating a DOM element a few different ways, which were all successful. Here is the problem:
Every time I go to make a second move for a player, the DOM image disappears and reappears in the new square selected. Obviously this is not the desired action. Is there something I don't know about creating a DOM element. I have googled this and read countless articles and watched countless videos.
const stark = document.createElement('img')
stark.src = 'https://i.imgur.com/d70XlET.png'
stark.height = 80
stark.width = 80
const lanister = document.createElement('img')
lanister.src = 'https://i.imgur.com/d70XlET.png'
lanister.height = 80
lanister.width = 80
const play = (event) => {
if (gameOver === false) {
if (event.target.innerHTML === '') {
$('#' + event.target.id).append(turn)
}
}
}
'turn' is a variable that works with a toggle function to switch between players and stores whichever players turn it is(i.e. 'stark')
I would be really grateful if someone could point me in the direction of a resource where I could learn more about this.
const player1 = stark
const player2 = lanister
let turn = player1
let prevTurn = player2
const togglePrevTurn = () => {
if (!gameOver) {
if (prevTurn === player1) {
prevTurn = player2
} else {
prevTurn = player1
}
}
}
const toggleTurn = () => {
if (!gameOver) {
if (turn === player1) {
turn = player2
} else {
turn = player1
}
$('#message').text(turn + " 's turn")
}
}
Whenever you use Javascript's appendChild or jQuery's append, when you pass it an element, that element gets removed from its previous location in the DOM (if it's in the DOM), and then gets inserted at the new position. It sounds like what you need to do is explicitly create a new element each time, which you might do with cloneNode().
Also, probably best to name your variables precisely - if turn is an image, name it to make it clear that it's an image, perhaps currentPlayerImage.
In addition, because you already have a reference to the event.target, there's no need to reselect it with $('#' + event.target.id) - just select event.target:
const play = (event) => {
if (gameOver === false) {
if (event.target.innerHTML === '') {
$(event.target).append(currentPlayerImage.cloneNode());
}
}
}