Get element by ID returns null - javascript

const data = [{color : "red"},{color : "blue"}, {color : "green"} ];
function libraryRoot() {
load();
return (`<div id="appDiv">
${data.map(function(value){
return `<div><p>Color ${value.color} from libraryRoot</p>`
}).join("")}
</div>
`);
}
window.onload = libraryRoot;
function load() {
let a = document.getElementById("appDiv");
console.log(a);
}
let defaultLayout = libraryRoot();
document.getElementById("root").innerHTML = defaultLayout;
<div>
<div id="root"></div>
</div>
Hi Guys i modified the script as you guys suggested, but still the return value at the first instance prints null, and then it prints the div.can you guys help me where im going wrong.
All i wanted to do is i want to call the "appDiv" id and wirte a button funcion to it. like on click {//do something}.
updated Codepen Project

You won't be able to access the element until it's written to the DOM.
Notice the word document in document.getElementById(). It's a method of the document API.
DOM stands for Document Object Model.
If you want to modify it before then then split your string literal into different pieces. Assign specific variables to important parts of the element.
Then modify them. Concatenate them back into one string and add them to the DOM.
Your code could use some comments. It's a little unclear what you're trying to do.
Here I've modified your pen to show some different ways to handle each color in your data array. You can do some conditional logic and return different template strings based on that. You could easily pass data object properties to another javascript function using the onclick attribute.
Hopefully this helps you get closer to your goal
const data = [{color : "red"},{color : "blue"}, {color : "green"} ];
function alertFunction(color) {
alert(color);
}
function libraryRoot(){
return('<div id="appDiv">' +
data.map(function(value) {
var result = `<div><p>Color ${value.color} from libraryRoot</p>`;
if (result.indexOf("red") > -1) { // checking if the div includes red
return result;
} else if (`${value.color}` == "blue") { // checking if the objects color prop includes blue
return "<p>blue</p>";
} else {
return `<button onclick="alertFunction('${value.color}')">Button with function</button>`;
}
}).join("")
+ "</div>"
);
}
window.onload = libraryRoot;
document.getElementById("root").innerHTML = libraryRoot();
<div>
<div id="root"></div>
</div>

The first issue is that you are calling load() first, which is trying to access the appDiv node that doesn't exist yet. So rethink the order on that.
The second issue is that you never actually added the appDiv content to the DOM. You can use things like document.appendChild(libraryRoot()); or something like that, though I have never injected a string as a DOM node - I use createElement for that. The link has some examples.

Related

Trying to make a function that takes in variable names as strings

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.

Custom word translator in React

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.

assign a variable in handlebars

I am working on the stencil platform on big commerce. this platform uses the handlebars syntax. I need to be able to set a value based on one of the parameters in my URL, more that like the 'window.location.pathname', and i need to be able to access this new variable across the site. I am able to make something work two different ways using regular JavaScript, but i do not want to recreate my script in every place throughout the site. So basically, I could use some help getting one of my 2 vanilla scripts into a handlebars for formatting. What i have that works is shown below:
<p id="BrandLogo"></p>
<script>
var directory = window.location.pathname;
var branding;
var str = directory.includes("blackvue");
if (str === false) {
branding = value one;
} else {
branding = value 2
}
document.getElementById("BrandLogo").innerHTML = branding;
</script>
or
<p id="BrandLogo"></p>
<script>
var directory = window.location.pathname;
var branding;
if (str == '/URL-I-Want-To-Focus-On/') {
branding = value one;
} else {
branding = value 2
}
document.getElementById("BrandLogo").innerHTML = branding;
</script>
Thanks in advance
If you are trying to set and use a local variable within handlebars try something like this:
First, create a helper function named 'setVar':
var handlebars = require('handlebars');
handlebars.registerHelper("setVar", function(varName, varValue, options) {
options.data.root[varName] = varValue;
});
Next, set your variable(s) using the function you created in the first step.
{{setVar "greeting" "Hello World!"}}
{{#if some-condition}}
{{setVar "branding" "Value one"}}
{{else}}
{{setVar "branding" "Value 2"}}
{{/if}}
Finally, use the variable(s) on your page:
<div>
<h1 id="BrandLogo">{{greeting}}</h1>
</div>
document.getElementById("BrandLogo").innerHTML = {{branding}};
If you are trying to add a serial number inside a nested 'each' or based on a condition inside nested each following helper works good:
hbs.registerHelper('serialNo', function (options) {
var currentSerialNo = options.data.root['serialNo'];
console.log("############Current serial No is:"+currentSerialNo);
if (currentSerialNo === undefined) {
currentSerialNo = 1;
} else {
currentSerialNo++;
}
options.data.root['serialNo'] = currentSerialNo;
return currentSerialNo;
});
Now inside your template you can simply use it like:
{{serialNo}}
Everytime {{serialNo}} is encountered it prints a serial number one greater than before.

Rendering custom html tag with react.js

What I'm trying to do is quite easy at first however I get an (obviously completely useless) error from webpack and I'm wondering how it can be fixed, I want a simple "custom" tag to be rendered by React, the code is as follows:
let htmlTag = "h" + ele.title.importance;
let htmlTagEnd = "/h" + ele.title.importance;
return(
<{htmlTag} key={elementNumber}>{ele.title.content}<{htmlTagEnd}>
);
Basically instead of having a predefined tag I want to have my own {template} tag, I know in this situation there would be work arounds for this (e.g. defining a className with my "importance" value and adding some css for that), but for the sake of science I'd like to know how (and if) this can be done in react/jsx.
JSX doesn't allow you to use dynamic HTML tags (dynamic components would work). That's because whenever you use something like <sometag ... />, an HTML element with tag name sometag is created. sometag is not resolved as a variable.
You also can't do what you have shown above. JSX expressions are not valid in place of a tag name.
Instead, you have to call React.createElement directly:
return React.createElement(
"h" + ele.title.importance,
{
key: elementNumber,
},
ele.title.content
);
Edit
My initial answer was not correct, you cannot use a variable directly and would need to use the createElement method described in Felix's answer. As noted below, and utilised in the blog post I originally linked, you can use object properties, so I've made an example of this, which hopefully will be useful as an answer to the question.
class Hello extends React.Component {
constructor() {
super();
this.state = {
tagName: "h1"
};
}
sizeChange(i) {
this.setState({
tagName: 'h' + i
});
}
changeButtons() {
var buttons = [];
for (let i=1; i<=6; i++) {
buttons.push(<button onClick={() => this.sizeChange(i)}>H{i}</button>);
}
return buttons;
}
render() {
return (
<div>
{this.changeButtons()}
<this.state.tagName>
Change Me
</this.state.tagName>
</div>
);
}
}
JSFiddle here
Original Answer
It can be done, although I don't think it is officially supported so may break in the future without warning. The caveat to this approach is that the variable name you choose for your tag cannot be the same as an HTML element.
var Demo = React.createClass({
render: function() {
const elementTag = 'h' + ele.title.importance;
return(
<elementTag>
Header x contents
</elementTag>
);
}
});
More explanation and a fuller example can be found here

Aurelia: always call method in the view (problems after upgrade)

We've upgraded Aurelia (in particular aurelia-framework to 1.0.6, aurelia-bindong to 1.0.3) and now we're facing some binding issues.
There's a list of elements with computed classes, and we had a method int the custom element that contained the list:
getClass(t) {
return '...' +
(this.selected.indexOf(t) !== -1
? 'disabled-option' :
: ''
) + (t === this.currentTag
? 'selected-option'
: ''
);
}
And class.one-way="$parent.getClass(t)" for the list element, everything was OK.
After the upgrade it simply stopped to work, so whenever the selected (btw it's bindable) or currentTag properties were modified, the getClass method just wasn't called.
I partially solved this by moving this logic to the view:
class="${$parent.getClass(t) + (selected.indexOf(t) !== -1 ? 'disabled-option' : '') (t === $parent.currentTag ? 'selected-option' : '')}"
I know that looks, well... bad, but that made t === $parent.currentTag work, but the disabled-option class still isn't applied.
So, the question is:
How do I force Aurelia to call methods in attributes in the view?
P.S.
I understand that it might cause some performance issues.
Small note:
I can not simply add a selected attribute to the list element since I don't to somehow modify the data that comes to the custom element and I basically want my code to work properly without making too many changes.
UPD
I ended up with this awesome solution by Fabio Luz with this small edit:
UPD Here's a way to interpret this awesome solution by Fabio Luz.
export class SelectorObjectClass {
constructor(el, tagger){
Object.assign(this, el);
this.tagger = tagger;
}
get cssClass(){
//magic here
}
}
and
this.shown = this.shown(e => new SelectorObjectClass(e, this));
But I ended up with this (defining an extra array).
You have to use a property instead of a function. Like this:
//pay attention at the "get" before function name
get getClass() {
//do your magic here
return 'a b c d e';
}
HTML:
<div class.bind="getClass"></div>
EDIT
I know that it might be an overkill, but it is the nicest solution I found so far:
Create a class for your objects:
export class MyClass {
constructor(id, value) {
this.id = id;
this.value = value;
}
get getClass() {
//do your magic here
return 'your css classes';
}
}
Use the above class to create the objects of the array:
let shown = [];
shown[1] = new MyClass('someId', 'someValue');
shown[2] = new MyClass('someId', 'someValue');
Now, you will be able to use getClass property:
<div repeat.for="t of shown" class.bind="t.getClass">...</div>
Hope it helps!
It looks pretty sad.
I miss understand your point for computing class in html. Try that code, it should help you.
computedClass(item){
return `
${this.getClass(item)}
${~selected.indexOf(item) ? 'disabled-option': ''}
${item === this.currentTag ? 'selected-option' : ''}
`;
}
Your code not working cause you miss else option at first if state :/
Update:
To toggle attribute state try selected.bind="true/false"
Good luck,
Egor
A great solution was offered by Fabio but it caused issues (the data that was two-way bound to the custom element (result of the selection) wasn't of the same type as the input and so on). This definitely can be fixed but it would take a significant amount of time and result in rewriting tests, etc. Alternatively, yeah, we could put the original object as some property blah-blah-blah...
Anyway:
There's another solution, less elegant but much faster to implement.
Let's declare an extra array
#bindable shownProperties = [];
Inject ObserverLocator
Observe the selected array
this.obsLoc.getArrayObserver(this.selected)
.subscribe(() => this.selectedArrayChanged);
Update the shownProperties
isSelected(t) {
return this.selected.indexOf(t) !== -1;
}
selectedArrayChanged(){
for(var i = 0; i < this.shown.length; i++){
this.shownProperties[i] = {
selected: this.isSelected(this.shown[i])
}
}
}
And, finally, in the view:
class="... ${shownProperties[$index].selected ? 'disabled-option' : '')} ..."
So, the moral of the story:
Don't use methods in the view like I did :)

Categories