New to Js, sorry if this is an obvious one.
I have some strings in my code that correspond to the names of variables. I'd like to put them into a function and have the function be able to make changes to the variables that have the same names as the strings.
The best example is where this 'string' is passed through from a data tag in html, but I have some other situations where this issue appears. Open to changing my entire approach too is the premise of my question is backwards.
<html>
<div data-location="deck" onClick="moveCards(this.data-location);">
</html>
var deck = ["card"];
function moveCards(location){
location.shift();};
Thanks!
A script should not depend on the names of standalone variables; this can break certain engine optimizations and minification. Also, inline handlers are nearly universally considered to be pretty poor practice - consider adding an event listener properly using Javascript instead. This will also allow you to completely avoid the issue with dynamic variable names. For example:
const deck = ["card", "card", "card"];
document.querySelector('div[data-location="deck"]').addEventListener('click', () => {
deck.shift();
console.log('deck now has:', deck.length + ' elements');
});
<div data-location="deck">click</div>
I think this can technically be done using eval, but it is good practice to think more clearly about how you design this so that you only access objects you directly declare. One example of better design might be:
container = {
obj1: //whatever this object is
...
objn:
}
function applyMethodToObject(object_passed){
container[object_passed].my_method();
}
I'm not sure I 100% follow what you're trying to do, but rather than trying to dynamically resolve variable names you might consider using keys in an object to do the lookup:
const locations = {
deck: ['card']
}
function moveCards (location) {
// if 'deck' is passed to this function, this is
// the equivalent of locations['deck'].shift();
locations[location].shift();
};
Here's a working demo:
const locations = {
deck: ['card 1', 'card 2', 'card 3', 'card 4']
};
function move (el) {
const location = el.dataset.location;
const item = locations[location];
item.shift();
updateDisplay(item);
}
// update the display so we can see the list
function updateDisplay(item) { document.getElementById('display').innerHTML = item.join(', ');
}
// initial list
updateDisplay(locations['deck']);
#display {
font-family: monospace;
padding: 1em;
background: #eee;
margin: 2em 0;
}
<div data-location='deck' onclick="move(this)">click to shift deck</div>
<div id="display">afda</div>
When you assign a value to an object in javascript you can access with dot or array notation. IE
foo = {};
foo.bar = "bar";
console.log(foo.bar);
console.log(foo["bar"]);
Additionally, global variables are added to the window object, meaning deck is available at window["deck"] or window[location] in your case. That means your moveCards function could do:
function moveCards(location) {
// perform sanity checks since you could change data-location="foo"
// which would then call window.foo.shift()
if (window[location]) {
window[location].shift();
}
}
That said, this probably isn't a great approach, though it's hard to say without a lot more context.
Related
I'm a beginner and I'm trying to add random jokes to a modal from an object. Until now everything works. What I want now is to clear the modal after I click the close button so that every time I click "lets have a laugh..." a new joke will appear without having to refresh the page.
Other suggestions to make the code cleaner are welcome.
const JokesObject = {
joke1: {
question: "what is the ultimate paradox",
answer: "There is no absolute truth"
},
joke2: {
question: "what turns coffee into code",
answer: "A programmer"
}
};
function jokie () {
const ListJokes = Object.keys(JokesObject);
let randomJoke = ListJokes[Math.floor(Math.random() * ListJokes.length)];
const joke = JokesObject[randomJoke];
const jokeModalQuestion = document.getElementById('joke');
const jokeModalAnswer = document.getElementById('answer');
const addJoke = document.createTextNode(joke.question);
const addAnswer = document.createTextNode(joke.answer);
jokeModalQuestion.appendChild(addJoke);
function answer () {
jokeModalAnswer.appendChild(addAnswer);
};
document.getElementById('giveAnswer').addEventListener('click', answer)
};
jokie();
const toggleModal = () => {
document.querySelector('.modal').classList.toggle('modal-hidden');
};
document.querySelector('.show-modal').addEventListener('click', toggleModal);
document.querySelector('.modal__close-bar').addEventListener('click', toggleModal);
To make that possible, you need to move your answer function outside the main function (jokie), and whenever you open your modal, you set a variable with the answer the current joke contains.
That way, when the answer button is clicked, it will always be filled with the currently selected joke.
Here are some suggestions to improve the quality of your code.
Please remember that beautiful code is subjective, some people might prefer a different way of doing things, but I think there's a general consensus about what "good" code looks like.
Use an array instead of an object
You are using an object when what you want is actually an array.
Instead of
const colors = { color1: 'yellow', color2: 'red' };
const firstColorKey = Object.keys(colors)[0];
const firstColor = colors[firstColorKey];
Do this
const colors = [ 'yellow', 'red' ];
const firstColor = colors[0];
Use better namings
Don't try to be fun when naming methods / variables. Instead of jokie, use setRandomJoke. Try to be as verbose as possible.
Think of functions as units of work
Instead of having a single function doing all the work, try to separate work into different units. For instance, your jokie function was doing two things: getting a random joke and filling the modal with the joke text. That can be decomposed into two different functions: getRandomJoke and fillJoke (again, remember function names should be as descriptive as possible).
Use consistent naming
If you are using a rule for, say, class selectors, keep it consistent. You are using giveAnswer, which uses camelCase, when other class selectors are using hyphen-case.
Plase your constants values outside the function
You are calling getElementById to get the DOM elements all the time, when only once should suffice. You can achieve that by placing the constants outside your function.
Use a code beautifier
To stop thinking about indentation, people nowadays use a tool to automatically format the code. Prettier is heavily used among Javscript devs https://prettier.io/
Look into BEM for naming CSS classes
BEM (or alternative frameworks / methodologies) is used to keep a consistent naming on CSS code. In your code, you are already using some of that by using __, but you can improve that by instead of using joke and answer class names, using modal__joke and modal__answer.
Code
const jokes = [
{
question: "what is the ultimate paradox",
answer: "There is no absolute truth",
},
{
question: "what turns coffee into code",
answer: "A programmer",
},
];
const jokeModalQuestion = document.getElementById("modal__joke");
const jokeModalAnswer = document.getElementById("modal__answer");
document.getElementById("answer").addEventListener("click", answer);
let currentAnswerTextNode;
function answer() {
jokeModalAnswer.innerText = currentAnswerTextNode;
}
function getRandomJoke(jokeList) {
return jokeList[Math.floor(Math.random() * jokeList.length)];
}
function fillJoke(joke) {
currentAnswerTextNode = joke.answer;
jokeModalQuestion.innerText = joke.question;
jokeModalAnswer.innerText = "";
}
function showModal() {
const joke = getRandomJoke(jokes);
fillJoke(joke);
document.querySelector(".modal").classList.remove("modal-hidden");
}
function hideModal() {
document.querySelector(".modal").classList.add("modal-hidden");
}
document.querySelector(".show-modal").addEventListener("click", showModal);
document
.querySelector(".modal__close-bar")
.addEventListener("click", hideModal);
.modal-hidden {
display: none
}
<button class="show-modal">Show Modal</button>
<div class="modal modal-hidden">
<button class="modal__close-bar">Close Modal</button>
<div id="modal__joke"></div>
<div id="modal__answer"></div>
<button id="answer">Answer</button>
</div>
I'd like to do something like this:
const vegetableColors = {corn: 'yellow', peas: 'green'};
const {*} = vegetableColors;
console.log(corn);// yellow
console.log(peas);// green
I can't seem to find or figure out how to do this but I really thought I had seen it done somewhere before! :P
NOTE: I'm using Babel with stage set to 0;
CONTEXT: I'm trying to be drier in JSX and not reference this.state or this.props everywhere. And also not have to keep adding properties to destructure if the data changes.
I think you're looking for the with statement, it does exactly what you are asking for:
const vegetableColors = {corn: 'yellow', peas: 'green'};
with (vegetableColors) {
console.log(corn);// yellow
console.log(peas);// green
}
However, it is deprecated (in strict mode, which includes ES6 modules), for good reason.
destructure all properties into the current scope
You cannot in ES61. And that's a good thing. Be explicit about the variables you're introducing:
const {corn, peas} = vegetableColors;
Alternatively, you can extend the global object with Object.assign(global, vegetableColors) to put them in the global scope, but really, that's worse than a with statement.
1: … and while I don't know whether there is a draft to allow such things in ES7, I can tell you that any proposal will be nuked by the TC :-)
I wouldn't recommend it, but you can use eval() to accomplish something similar:
vegetableColors = {corn: 'yellow', peas: 'green'};
function test() {
for ( let i=0; i < Object.keys(vegetableColors).length; i++ ) {
let k = Object.keys(vegetableColors)[i];
eval(`var ${k} = vegetableColors['${k}']`);
}
console.log(corn); // yellow
}
test();
console.log(corn); // undefined (out of scope)
I think you're looking for:
const {corn, peas} = vegetableColors;
Live on Babel's REPL
If Pointy's right that you're asking how to do this without knowing the names corn and peas, you can't with destructuring assignment.
You can at global scope only, using a loop, but I'm sure you don't want to do this at global scope. Still, just in case:
// I'm sure you don't really want this, just being thorough
Object.keys(vegetableColors).forEach((key) => {
Object.defineProperty(this, key, {
value: vegetableColors[key]
});
});
(Throw enumerable: true on there if you want these pseudo-constants to be enumerable.)
That works at global scope because this refers to the global object.
I came upon a situation where the object was user-created and the code that uses the object was also user-created. Because the with statement is deprecated, I made my own, using eval to destructure the entire object and call the function that uses the destructured object. Below is a working example.
const vegetableColors = { corn: 'yellow', peas: 'green' };
function with2(obj, func) {
eval(`
var { ${Object.keys(obj).join(",")} } = obj;
(${func.toString()})()
`);
}
/*
with(vegetableColors) {
console.log(corn);
console.log(peas);
}
*/
with2(vegetableColors, function() {
console.log(corn);
console.log(peas);
})
Let me show you my solution to the problem. I don't agree with those who think that destructuring object properties into local scope without specifying their names is bad idea. For me, this feature, if implemented, would be helpful. This would make our code shorter, and improve code maintenance by making it easy to change property names without changing the processing code. After all, there is the extract() function in PHP that does the same thing. Are PHP developers wrong?
My solution is not ideal as it uses eval but it is one liner and it works. Perhaps in the future we will have a solution from JavaScript developers.
function extract(o)
{
var result = [];
for(var key in o)
if(o.hasOwnProperty(key)) {
var item = 'var ' + key + '=' + JSON.stringify(o[key]);
result.push(item);
}
return result.join(';');
}
var vegetableColors = { corn: 'yellow', peas: { unripe: 'green', ripe: 'yellow' } };
eval(extract(vegetableColors));
console.log(corn); // yellow
console.log(peas); // {unripe: "green", ripe: "yellow"}
Update: scroll to see my solution, can it be improved?
So I have this issue, I am building a word translator thats translates english to 'doggo', I have built this in vanilla JS but would like to do it React.
My object comes from firebase like this
dictionary = [
0: {
name: "paws",
paws: ["stumps", "toes beans"]
}
1: {
name: "fur",
fur: ["floof"]
}
2: {
name: "what"
what: ["wut"]
}
]
I then convert it to this format for easier access:
dictionary = {
what : ["wut"],
paws : ["stumps", "toe beans"],
fur : ["floof"]
}
Then, I have two text-area inputs one of which takes input and I would like the other one to output the corresponding translation. Currently I am just logging it to the console.
This works fine to output the array of the corresponding word, next I have another variable which I call 'levelOfDerp' which is basically a number between 0 - 2 (set to 0 by default) which I can throw on the end of the console.log() as follows to correspond to the word within the array that gets output.
dictionary.map(item => {
console.log(item[evt.target.value][levelOfDerp]);
});
When I do this I get a "TypeError: Cannot read property '0' of undefined". I am trying to figure out how to get past this error and perform the translation in real-time as the user types.
Here is the code from the vanilla js which performs the translation on a click event and everything at once. Not what I am trying to achieve here but I added it for clarity.
function convertText(event) {
event.preventDefault();
let text = inputForm.value.toLowerCase().trim();
let array = text.split(/,?\s+/);
array.forEach(word => {
if (dictionary[word] === undefined) {
outputForm.innerHTML += `${word} `;
noTranslationArr.push(word);
} else {
let output = dictionary[word][levelOfDerp];
if (output === undefined) {
output = dictionary[word][1];
if (output === undefined) {
output = dictionary[word][0];
}
}
outputForm.innerHTML += `${output} `;
hashtagArr.push(output);
}
});
addData(noTranslationArr);
}
Also here is a link to the translator in vanilla js to get a better idea of the project https://darrencarlin.github.io/DoggoSpk/
Solution, but could be better..
I found a solution but I just feel this code is going against the reason to use react in the first place.. My main concern is that I am declaring variables to store strings inside of an array within the function (on every keystroke) which I haven't really done in React, I feel this is going against best practice?
translate = evt => {
// Converting the firebase object
const dict = this.state.dictionary;
let dictCopy = Object.assign(
{},
...dict.map(item => ({ [item["name"]]: item }))
);
let text = evt.target.value.toLowerCase().trim();
let textArr = text.split(/,?\s+/);
let translation = "";
textArr.forEach(word => {
if (dictCopy[word] === undefined) {
translation += `${word} `;
} else {
translation += dictCopy[word][word][this.state.derpLvl];
}
});
this.setState({ translation });
};
levelOfDerp is not defined, try to use 'levelOfDerp' as string with quotes.
let output = dictionary[word]['levelOfDerp' ];
The problem happens because setState() is asynchronous, so by the time it's executed your evt.target.value reference might not be there anymore. The solution is, as you stated, to store that reference into a variable.
Maybe consider writing another function that handles the object conversion and store it in a variable, because as is, you're doing the conversion everytime the user inputs something.
Code:
initialize: function() {
this.todos = [
{id: 100, text: 'Rich'},
{id: 200, text: 'Dave'}
];
},
activeTodos: function() {
this.todos = this.todos.length(function() {
return this.todos;
});
this.emitChange();
}
<p>Todo's Remaining: {this.activeTodos} </p>
activeItems: function(){
this.context.executeAction(activeToDosAction);
},
Explanation:
I am trying to print out the size of the array to the browser window (this can be seen in the <p> tags within the code). So far nothing is displaying and I cant figure out why. activeTodos should be calling the length of todos.
i can post more code if people require it. i am using reactjs hence the { } brackets within HTML
There are a couple of weird things there. If you only need to display the length of this.todos you can do something like this:
<p>Todo's Remaining: {this.todos.length} </p>
I think the problem here is that you pass a function to length. That is not required.
Your code should work if you change it to:
activeTodos: function() {
return this.todos.length;
}
I'm not sure what your function emitChange should be doing, so I omitted it for now in the code example. If you need it to be called, put it before the return as that is the last thing that will run in your function.
First, to access the length of an array you just have to do
myArray = [1, 2, 3];
myArray.length; //3
Moreover you have 2 fonctions : initialize and activeTodos where you use this.todos = ...
Without further explanations I assume these variables created with the keyword 'this' inside 2 different functions are out of score.
Verify that your this.todos refers to the same thing.
You do not need active.Todos() as .length is a built in function by itself.
Just do
Todo's Remaining: {this.todos.length}
Cheers.
I am writing some oop javascript code. I have a couple of instances of a class and have put different data into each. Unfortunately, as you will see with the example below, they appear to share the same data.
Is it possible to get two separate instances of my class? How would it be done.
Index.html
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<script type="text/javascript" src="test.js"></script>
<script type="text/javascript">
debugger;
// Do this because a page resart seems to keep old data
function SetGlobals()
{
var ui;
var el;
// Arr00
ui = document.getElementById("Arr00");
el = arr0.arrayGet(0);
ui.innerHTML = el.m_String;
// Arr01
ui = document.getElementById("Arr01");
el = arr0.arrayGet(1);
ui.innerHTML = el.m_String;
// Arr10
ui = document.getElementById("Arr10");
el = arr1.arrayGet(0);
ui.innerHTML = el.m_String;
// Arr11
ui = document.getElementById("Arr11");
el = arr1.arrayGet(1);
ui.innerHTML = el.m_String;
}
function MyOnLoad()
{
SetGlobals();
}
</script>
</head>
<body onload="MyOnLoad()" style="width:100%; height: 100%; padding: 0 0 0 0; margin: 0 0 0 0; overflow: hidden; background:#000000">
<div id="divScreen" style="display: block; width:100%; height="100%">
<div id="divMenu" style='float: left; background:#00FF00; border-color: #000000; border-width: 1px;'>
<table>
<tr>
<td>
Array 0/String 0: <label id="Arr00"></label>
</td>
</tr>
<tr>
<td>
Array 0/String 1: <label id="Arr01"></label>
</td>
</tr>
<tr>
<td>
Array 1/String 0: <label id="Arr10"></label>
</td>
</tr>
<tr>
<td>
Array 1/String 1: <label id="Arr11"></label>
</td>
</tr>
</table>
</div>
<div id="divMain" style='height: 100%; background:#0000FF; margin-left: 250px; border-color: #000000; border-width: 1px;'>
</div>
</div>
</body>
</html>
Test.js
var BaseARR = function()
{
_arr = []; // new Array();
// Public functions that can access private members
this.Add = function(arg)
{
var i, addAt;
if(arg==null || (addAt = FindEnterPos(arg))<0)
return false;
// since adding and not deleting anything, nothing of value will be returned
_arr.splice(addAt, 0, arg);
return true;
};
// This finds the entry position for in
FindEnterPos = function(arg)
{
return (_arr.length + 1);
};
this.arrayGet = function(i)
{
return ((_arr != null && i >= 0 && i < _arr.length) ? _arr[i] : null);
};
};
var stringId = function(id, str)
{
// public has a this. , privates have just var
this.m_Id = id; // int
this.m_String = str; // string
};
// This so allow statics
var stringIdARR = function()
{
BaseARR.call(this);
};
There are various problems in your code. Let me try to explain them.
First it is highly recommended to not put opening block braces on a single line in JavaScript. Why you may ask? Well run those two code snippets:
// using "braces on same line" style
(function () {
return {
key: 'value'
};
})();
// using "braces on line by themself"-style
(function ()
{
return
{
key: 'value'
}
})();
Both snippets will return different results, allthough the only difference is positioning of braces. The reason for this is semicolon insertion. In JavaScript semicolons are optional. So if the parser finds a newline character and the construct infront of the newline makes sense, it will insert a semicolon. In the second example this is what happens after the return statement. If you place your braces on the same line as the previous statement, you can circumvent such bugs.
The next thing you got wrong is that JavaScript has classes. JavaScript is an object oriented language, but unlike most other object oriented languages it does not have classes. In JavaScript objects inherit directly from other objects (their so called prototypes). What you currently arre referring to as a class is in reality a constructor function, which when invoked using the new keyword will create a new object, that will inherit from whatever object is stored in the constructors prototype field.
var anObject = {
key: 'value'
};
function MakeAnObject() {
}
MakeAnObject.prototype = anObject;
var o = new MakeAnObject();
console.log(o.key); // will output 'value'
If you set a property, the proerty will alwas be set on the object itself, it will never access the prototype chain, when setting a property.
If you read a property from an object, that does not have that property, JavaScript will search the objects prototype chain (that is all the objects that inherit from each other) for that property and returns it if found.
If an oject has a property itself, it's prototype chain will not be searched, so you can "override" an objects inherited properties by setting the porperty on the objects self.
Look at the following example:
function MakeThing() {
}
MakeThing.prototype = {
key: 'value'
};
var o1 = new MakeThing(), o2 = new MakeThing();
console.log(o1); // will output 'value'
console.log(o2); // will output 'value'
o2.key = 'other';
console.log(o1); // will output 'value'
console.log(o2); // will output 'other'
MakeThing.prototype.key = 'changed';
console.log(o1); // will output 'changed'
console.log(o2); // will output 'other'
delete o2.key;
console.log(o1); // will output 'changed'
console.log(o2); // will output 'changed'
With all that in mind I will have to tell you: there is no such thing as public and private members on an object in JavaScript. Members will always be public. There are some patterns which try to hide away certain information in an object using closures, but they function very different than private members in a traditional programming language. And worse: those patterns are clunky, produce terrible and very bad performing code. I suggest do not use them if you do not absoltuely require to.
So, what does all this mean? Well firstly, if you want to share attributes and methods between multiple objects, they will have to inherit from the same prototype and that prototype must contain those attributes and methods. Secondly if you set something on this it will be set on the current instance, not on the prototype. Thirdly have priavte and public members only by convention. If you absolutely require certain information to be strictly hidden from a certain subsystem, there are patterns for this (Crockford sealer unsealer should yield useable results).
All that said here a quick try at fixing your objects:
function BaseAAR {
this._arr = []; // note >this<. You created a global array in your code.
};
BaseAAR.prototype.add = function(arg) {
var i, addAt;
// always use identity (triple) operators when comparing to null!
if (arg === null || (addAt = this.findEnterPos(arg))<0)
return false;
// since adding and not deleting anything, nothing of value will be returned
this._arr.splice(addAt, 0, arg);
return true;
};
// This finds the entry position for in
BaseAAR.prototype.findEnterPos = function() {
return (this._arr.length + 1);
};
BaseAAR.prototype.arrayGet = function(i) {
return ((this._arr !== null && i >= 0 && i < this._arr.length) ? this._arr[i] : null);
};
function StringIdAAR(id, str) {
BaseAAR.call(this); // invoke the constructor of the base object
this.m_Id = id; // int
this.m_String = str; // string
}
StringIdAAR.prototype = BaseAAR.prototype; // innherit from StringIdAAR prototype
I am not completely sure if this code actually still does what you want it to do, but you should get the point how object oriented patterns in JavaScript should look like.
If you want to read more on how to write good JavaScript you should absolutely get the book "JavaScript: The Good Parts" by Douglas Crockford.
UPDATE: I also wrote an article on JavaScript object orientation and prototype based inheritance. This might be of interest for anybody passing by here.
For a great JavaScript inheritance example take a look at John Resig's Simple Inheritance implementation. I've been using a modified version of it for some time now. This article describes it in more detail.
Another library that offers similar functionality is base2, but may be overkill for your needs.
Lastly, another popular approach I've also used in the past is the Module Pattern. See this article as well for an in depth explanation of the pattern.