JS calling ID by variable - javascript

Hi All
First data:
let NewDivForGame_0 = document.createElement('div');
let NewDivForGame_1 = document.createElement('div');
let NewDivForGame_2 = document.createElement('div');
and so on...12
Next:
NewDivForGame_0.id = 'Key';
NewDivForGame_1.id = 'string_1';
NewDivForGame_2.id = '1a1';
and so on...12
Next: append.
Next:
for (i=0;i<=12;i++){
document.getElementById("NewDivForGame_"+i.id).style.width ="35px"; //ERROR
document.getElementById("NewDivForGame_"+[i].id).style.height= "35px"; //ERROR
document.getElementById("NewDivForGame_"+[i].id).style.backgroundColor = "blue";
console.log('Create size div run #'+i);
It doesn't work. Help me please. Please write a solution.
tried:
1)document.getElementById("NewDivForGame_"+i.id).style.width = "35px"; //ERROR
2)document.getElementById("NewDivForGame_"+[i].id).style.width = "35px"; //ERROR
3)
let DetectPer = "NewDivForGame_";
document.getElementById(DetectPer+i.id).style.width = "35px"; //ERROR
It doesn't work. Help me please. Please write a solution.

Another example, maybe not so short as then one of #mplungjan, but it shows how it can be done differently.
If You want to create elements you can use simple for loop to do it, but then you need to add them to DOM as a child of other DOM element.
In example below I've added first 'div' as a child of body, second as child of first and so on.
Because all elements references where stored in newDivForGame array we can use it to change style properties using simple for loop.
{
const newDivForGame = [];
for (let i = 0; i < 12; ++i) {
newDivForGame.push(document.createElement('div'));
newDivForGame[i].id = `key${i}`;
document.body.appendChild(newDivForGame[I]);
}
for (const elem of newDivForGame) {
elem.style.width = '35px';
elem.style.height = '35px';
elem.style.background = 'blue';
}
}

You cannot build your selectors like that - it is wishful thinking.
To do what you are trying you would need eval, or worse:
window["NewDivForGame_"+i].id
Neither which are recommended
Why not access them using querySelectorAll, here I find all elements where the id starts with NewDivForGame
document.querySelectorAll("[id^=NewDivForGame]").forEach(div => {
div.style.width ="35px";
div.style.height= "35px"; //ERROR
div.style.backgroundColor = "blue";
})
or use css and a class
.blueStyle {
width: 35px;
height: 35px;
background-color: blue;
}
and do
NewDivForGame.classList.add("blueStyle")
or
document.querySelectorAll("[id^=NewDivForGame]").forEach(div => div.classList.add("blueStyle"))

The main problems with your code are these lines:
for (i=0;i<=12;i++){
document.getElementById("NewDivForGame_"+i.id).style.width ="35px"; //ERROR
document.getElementById("NewDivForGame_"+[i].id).style.height= "35px"; //ERROR
document.getElementById("NewDivForGame_"+[i].id).style.backgroundColor = "blue";
console.log('Create size div run #'+i);
...
From what I can tell, you're attempting to access your variables by expecting your browser to evaluate the result of concatenating a string with a number.
Aside from that, you're attempting to access the id property from i, which as it stands, is a number. The number primitive does not have an id property, but based on your code it seems you might have been mixing it up with your string/eval assumption.
Lastly, your line of [i] was actually creating an array with the number i being the single and only element. Arrays likewise do not have an id property.
(Un)Fortunately, Javascript doesn't work this way. At least not exactly that way; in order for the browser or container to do what you expect, there's a few methods that could be used, but I'm only going to reference two; the dreaded eval(), which I won't get into due it being a dangerous practice, and an object literal definition. There are of course other ways, such as the other existing answer(s) here, but:
// Here, we define an object literal and assign it properties for each div manually.
let gameDivs = {
NewDivForGame_0: document.createElement('div'),
NewDivForGame_1: document.createElement('div'),
// etc
};
// And then assign the id values sort of like you do in your question;
gameDivs.NewDivForGame_0.id = 'Key';
gameDivs.NewDivForGame_1.id = 'string_1';
gameDivs.NewDivForGame_2.id = '1a1';
// etc
for (i=0;i<=12;i++){
// Before finally using square bracket notation to target the
// properties by a dynamic name;
document.getElementById(gameDivs["NewDivForGame_"+i].id).style.width ="35px";
document.getElementById(gameDivs["NewDivForGame_"+i].id).style.height= "35px";
document.getElementById(gameDivs["NewDivForGame_"+i].id).style.backgroundColor = "blue";
console.log('Create size div run #'+i);
}
Of course, you don't even need to select them by their id if you have the reference to them, which you do:
for (i=0;i<=12;i++){
// Before finally using square bracket notation to target the
// properties by a dynamic name;
gameDivs["NewDivForGame_"+i].style.width ="35px";
gameDivs["NewDivForGame_"+i].style.height= "35px";
gameDivs["NewDivForGame_"+i].style.backgroundColor = "blue";
console.log('Create size div run #'+i);
}
This example assumes the divs are appended to the document.
This methodology uses square brackets on an object literal. As you may, or may not be aware, square brackets should be used when accessing an object's property in a dynamic way, such as what you were trying to do with string concatenation earlier.
Due to the way objects behave, you could even go so far as to generate the divs with a for-loop and then add them to the object:
let gameDivs = {};
for (let i = 0; i<=12; i++) {
let gameDiv = document.createElement('div');
gameDivs["NewDivForGame_"+i] = gameDiv;
// append them too;
document.body.appendChild(gameDiv);
}
Of course, I have no idea what pattern you're using for creating element ids, but in general the above would work.

Related

Trying to create a custom method using prototype property but i get Cannot read property 'replace' of undefined

So I'm messing around with JS trying to create a method which adds CSS style to elements without removing currently applied style:
// ==================================================================================================
// this method adds style to an element using CSS inline syntax without removing unaltered CSS styles
// ==================================================================================================
Element.prototype.cssStyle = function (style)
{
let styleChars = style.split(''); // split each character of the style arg as an item in an array
let positions = [];
for(let i=0; i<styleChars.length; i++){
if(styleChars[i] === '-'){
positions.push(i+1);
};
};
positions.forEach(function (position){ // for each match
styleChars.splice(position, 1, style[position].toUpperCase()); // make that character uppercase
});
styleChars.splice(0, 0, '[["'); // add a "[[" item on the first position
styleChars.splice(styleChars.length, 0, '"]]'); //add a "[[" on the last position
style = styleChars.join('') // join back the array into a string
style = style.replace(/:/g, "\",\"").replace(/;/g, "\"],[\"").replace(/-/g, ""); // replace some character in order to make the string look like an array
style = JSON.parse(style); // parse the string into an array
for(let i=0; i<style.length; i++){ // for each item in the array
let property = style[i][0].replace(/ */, ""); // remove some characters which might inhibit normal execution
let value = style[i][1].replace(/;/, "").replace(/ */, ""); //remove some characters which might inhibit normal execution
this.style[property] = value // change style of the element
};
return this.getAttribute('style'); //return all inline CSS styles
}
so if I try to style an element like this:
Element.cssStyle('background-color: white; color: #000')
It works as expected, but if I add a ; at the end of the parameter string I get this
Element.cssStyle('background-color: white; color: #000;')
'Uncaught TypeError: Cannot read property 'toUpperCase' of undefined'
Even though I don't see any apparent issues with the replace method, what could it be?
Replacing whitespace at that exact line works just fine, but trying to replace ; I get that error.
Also how badly written is my code?
Thanks!
Here is an example:
Element.prototype.cssStyle = function(styleStr) {
let styles = styleStr.split(';')
styles.forEach(style => {
if (!style.trim()) return;
let name = style.split(':')[0].trim();
let value = style.split(':')[1].trim();
this.style[name] = value;
})
return this.getAttribute('style'); //return all inline CSS styles
}
let testEl = document.getElementById("test")
console.log(testEl.cssStyle("color: white; background-color: black;"))
<p id="test">This is a test paragraph</p>
A few things to note:
This does NOT parse all CSS but I believe it works for your examples.
It is NOT recommended to modify the prototype of an object because if you are using someone else's code along with yours you might run into problems of overwriting each other's modifications.
The code works by splitting the string into each style segment and then it loops over those with forEach and changes the style of the element using this.style
Documentation:
Array.forEach()
String.trim()
String.split()
Hopefully, this helps.
Here is how I would do it:
Element.prototype.cssStyle = function(str) {
// Split styles
const styles = str.split(';');
// For each of them
for (let style of styles) {
// Get the property and value without extra spaces (using trim)
const [property, value] = style.split(':').map(s => s.trim());
// If none of them is empty
if (property.length && value.length) {
const camelCaseProperty = kebakCaseToCamelCase(property);
this.style[camelCaseProperty] = value;
}
}
return this.getAttribute('style');
};
function kebakCaseToCamelCase(str) {
return str.replace(/-(.)/g, (match, capture) => capture.toUpperCase());
}
document.querySelector('span')
.cssStyle('display: block; background-color: red; color: white;');
<span>Hello world</span>
but as #anbcodes proved it in his answer, I think you can even skip the camel case conversion

Alter unappened element's html tag type with javascript

So I have an element created by another process, created in a method akin to
var their_element = document.createElement("div");
/* Lots of stuff is done to their_element */
And that object is being passed to me later. It has not been appended anywhere yet. The problem is, I need it to be different html tag type when it finally hits the html, such as:
<form></form>
How do i change it? The solutions I have found involve editing after it's appended, not before.
Edit:
Also, learned nodeName can't be assigned for this.
their_element.nodeName = "FORM"
doesn't work.
Also, this doesn't work either:
their_element.tagName = "FORM"
Also this didn't work either:
var outside = their_element.outerHTML;
outside = outside.replace(/div/g, 'form');
their_element.outerHTML = outside;
All of these still leave it as a DIV when appended.
(And I'm not looking for jQuery)
Check on this for cross-browser compatability, but there are properties and methods on elements that could be of use. Particularly, Element.attributes, Element.hasAttributes(), and Element.setAttribute(). See: https://developer.mozilla.org/en-US/docs/Web/API/Element/attributes
I'm not going to use ES6 here, so we don't have to worry about transpiling. You can update if you want:
var el = document.createElement('div');
el.id="random";
el.style.background="red";
el.style.width="200px";
el.style.padding="10px";
el.style.margin="10px";
el.innerHTML="<input type='submit' value='submit'>";
console.log({"Element 1": el});
var newEl = document.createElement('form');
console.log({"Element 2 Before Transformation": newEl})
if (el.hasAttributes()) {
var attr = el.attributes
for (var i = 0; i < attr.length; i++) {
var name = attr[i].name, val = attr[i].value;
newEl.setAttribute(name, val)
}
}
if (el.innerHTML) { newEl.innerHTML = el.innerHTML }
console.log({"Element 2 After Transformation": newEl})
document.body.append(el);
document.body.append(newEl);
There are certain properties you need to account for like innerHTML, innerText, and textContent that would overwrite one another if multiples are set. You may also have to account for childNodes and what not.

How can I get the final css property used for a dom node? [duplicate]

Let's say the rule is as follows:
.largeField {
width: 65%;
}
Is there a way to get '65%' back somehow, and not the pixel value?
Thanks.
EDIT: Unfortunately using DOM methods is unreliable in my case, as I have a stylesheet which imports other stylesheets, and as a result the cssRules parameter ends up with either null or undefined value.
This approach, however, would work in most straightforward cases (one stylesheet, multiple separate stylesheet declarations inside the head tag of the document).
Most easy way
$('.largeField')[0].style.width
// >>> "65%"
This is most definitely possible!
You must first hide() the parent element. This will prevent JavaScript from calculating pixels for the child element.
$('.parent').hide();
var width = $('.child').width();
$('.parent').show();
alert(width);
See my example.
Now... I wonder if I'm first to discover this hack:)
Update:
One-liner
element.clone().appendTo('body').wrap('<div style="display: none"></div>').css('width');
It will leave behind a hidden element before the </body> tag, which you may want to .remove().
See an example of one-liner.
I'm open to better ideas!
There's no built-in way, I'm afraid. You can do something like this:
var width = ( 100 * parseFloat($('.largeField').css('width')) / parseFloat($('.largeField').parent().css('width')) ) + '%';
You could access the document.styleSheets object:
<style type="text/css">
.largeField {
width: 65%;
}
</style>
<script type="text/javascript">
var rules = document.styleSheets[0].rules || document.styleSheets[0].cssRules;
for (var i=0; i < rules.length; i++) {
var rule = rules[i];
if (rule.selectorText.toLowerCase() == ".largefield") {
alert(rule.style.getPropertyValue("width"));
}
}
</script>
Late, but for newer users, try this if the css style contains a percentage:
$element.prop('style')['width'];
A jQuery plugin based on Adams answer:
(function ($) {
$.fn.getWidthInPercent = function () {
var width = parseFloat($(this).css('width'))/parseFloat($(this).parent().css('width'));
return Math.round(100*width)+'%';
};
})(jQuery);
$('body').html($('.largeField').getWidthInPercent());​​​​​
Will return '65%'. Only returns rounded numbers to work better if you do like if (width=='65%'). If you would have used Adams answer directly, that hadn't worked (I got something like 64.93288590604027). :)
Building on timofey's excellent and surprising solution, here is a pure Javascript implementation:
function cssDimensions(element) {
var cn = element.cloneNode();
var div = document.createElement('div');
div.appendChild(cn);
div.style.display = 'none';
document.body.appendChild(div);
var cs = window.getComputedStyle
? getComputedStyle(cn, null)
: cn.currentStyle;
var ret = { width: cs.width, height: cs.height };
document.body.removeChild(div);
return ret;
}
Hope it's helpful to someone.
I have a similar issue in Getting values of global stylesheet in jQuery, eventually I came up with the same solution as above.
Just wanted to crosslink the two questions so others can benefit from later findings.
Convert from pixels to percentage using cross multiplication.
Formula Setup:
1.)
(element_width_pixels/parent_width_pixels) = (element_width_percentage / 100)
2.) element_width_percentage =
(100 * element_width_pixels) / parent_width_pixels
The actual code:
<script>
var $width_percentage = (100 * $("#child").width()) / $("#parent").width();
</script>
A late response but wanted to add on for anyone 2020+ who stumbles across this. Might be more for niche cases but I wanted to share a couple options.
If you know what the initial % value is you can also assign these values to variables in the :root of the style sheet. i.e
:root {
--large-field-width: 65%;
}
.largeField {
width: var(--large-field-width);
}
When you want to access this variable in JS you then simply do the following:
let fieldWidth = getComputedStyle(document.documentElement).getPropertyValue('--large-field-width');
// returns 65% rather than the px value. This is because the % has no relative
// size to the root or rather it's parent.
The other option would be to assign the default styling at the start of your script with:
element.style.width = '65%'
It can then be accessed with:
let width = element.style.width;
I personally prefer the first option but it really does depend on your use case. These are both technically inline styling but I like how you can update variable values directly with JS.
You could put styles you need to access with jQuery in either:
the head of the document directly
in an include, which server side script then puts in the head
Then it should be possible (though not necessarily easy) to write a js function to parse everything within the style tags in the document head and return the value you need.
There's nothing in jQuery, and nothing straightforward even in javascript. Taking timofey's answer and running with it, I created this function that works to get any properties you want:
// gets the style property as rendered via any means (style sheets, inline, etc) but does *not* compute values
// domNode - the node to get properties for
// properties - Can be a single property to fetch or an array of properties to fetch
function getFinalStyle(domNode, properties) {
if(!(properties instanceof Array)) properties = [properties]
var parent = domNode.parentNode
if(parent) {
var originalDisplay = parent.style.display
parent.style.display = 'none'
}
var computedStyles = getComputedStyle(domNode)
var result = {}
properties.forEach(function(prop) {
result[prop] = computedStyles[prop]
})
if(parent) {
parent.style.display = originalDisplay
}
return result
}
You can use the css(width) function to return the current width of the element.
ie.
var myWidth = $("#myElement").css("width");
See also:
http://api.jquery.com/width/
http://api.jquery.com/css/

Javascript - Apply same property to several variables

I have this :
var el1 = document.getElementById("el1");
var el2 = document.getElementById("el2");
var el3 = document.getElementById("el3");
el1.style.width = "100px";
el2.style.width = "100px";
el3.style.width = "100px";
Is there any ways to make this code shorter, and more maintainable ?
What if I need to have :
el1.style.width = "100px";
el2.style.width = "145px";
el3.style.width = "120px";
Thank you
Well, you can do it by writing a loop:
var data = {
el1: '100px',
el2: '100px',
el3: '100px'
};
for (var el in data) {
document.getElementById(el).style.width = data[el];
}
Really, however, this kind of manipulation of multiple DOM elements is exactly the kind of task libraries like jQuery were written to do. "Have you tried jQuery?" is a bit of a joke on this site, but this is an occasion where it is very appropriate. In this case, for instance, using jQuery:
$('#el1, #el2, #el3').width(100);
Note that this also protects against cases where the elements don't exist. My code above, like yours, will cause an error if any of the elements aren't present in the DOM when the code is run.
I guess you could write a function which does something like this, which lets you pass an Array of ids to get your Nodes and an Array of Objects with how to style each node, respectively.
function stylise(nodeIds, styles) {
var i = nodeIds.length, j, e;
while (i--) { // loop over elements
if (!styles[i]) continue; // no style for this element, skip
e = document.getElementById(nodeIds[i]); // get by id
if (!e) continue; // no element, skip
for (j in styles[i]) if (styles[i].hasOwnProperty(j)) { // loop styles
e.style[j] = styles[i][j]; // apply style
}
}
}
stylise( // then in future to apply CSS just need
['el1', 'el2', 'el3'], // element ids
[{width: '100px'}, {width: '145px'}, {width: '120px'}] // styles
);

Create reusable document fragment from the DOM

I would like to have a document fragment/element on the shelf to which I've connected a bunch of other elements. Then whenever I want to add one of these element-systems to the DOM, I copy the fragment, add the unique DOM ID and attach it.
So, for example:
var doc = document,
prototype = doc.createElement(), // or fragment
ra = doc.createElement("div"),
rp = doc.createElement("div"),
rp1 = doc.createElement("a"),
rp2 = doc.createElement("a"),
rp3 = doc.createElement("a");
ra.appendChild(rp);
rp.appendChild(rp1);
rp.appendChild(rp2);
rp.appendChild(rp3);
rp1.className = "rp1";
rp2.className = "rp2";
rp3.className = "rp3";
prototype.appendChild(ra);
This creates the prototype. Then I want to be able to copy the prototype, add an id, and attach. Like so:
var fr = doc.createDocumentFragment(),
to_use = prototype; // This step is illegal, but what I want!
// I want prototype to remain to be copied again.
to_use.id = "unique_id75";
fr.appendChild(to_use);
doc.getElementById("container").appendChild(fr);
I know it's not legal as it stands. I've done fiddles and researched and so on, but it ain't working. One SO post suggested el = doc.appendChild(el); returns el, but that didn't get me far.
So... is it possible? Can you create an on-the-shelf element which can be reused? Or do you have to build the DOM structure you want to add from scratch each time?
Essentially I'm looking for a performance boost 'cos I'm creating thousands of these suckers :)
Thanks.
Use Node.cloneNode:
var container = document.getElementById('container');
var prototype = document.createElement('div');
prototype.innerHTML = "<p>Adding some <strong>arbitrary</strong> HTML in"
+" here just to illustrate.</p> <p>Some <span>nesting</span> too.</p>"
+"<p>CloneNode doesn't care how the initial nodes are created.</p>";
var prototype_copy = prototype.cloneNode(true);
prototype_copy.id = 'whatever'; //note--must be an Element!
container.appendChild(prototype_copy);
Speed Tips
There are three operations you want to minimize:
String Parsing
This occurs when you use innerHTML. innerHTML is fast when you use it in isolation. It's often faster than the equivalent manual-DOM construction because of the overhead of all those DOM method calls. However, you want to keep innerHTML out of inner loops and you don't want to use it for appending. element.innerHTML += 'more html' in particular has catastrophic run-time behavior as the element's contents get bigger and bigger. It also destroys any event or data binding because all those nodes are destroyed and recreated.
So use innerHTML to create your "prototype" nodes for convenience, but for inner loops use DOM manipulation. To clone your prototypes, use prototype.cloneNode(true) which does not invoke the parser. (Be careful with id attributes in cloned prototypes--you need to make sure yourself that they are unique when you append them to the document!)
Document tree modification (repeated appendChild calls)
Every time you modify the document tree you might trigger a repaint of the document window and update the document DOM node relationships, which can be slow. Instead, batch your appends up into a DocumentFragment and append that to the document DOM only once.
Node lookup
If you already have an in-memory prototype object and want to modify pieces of it, you will need to navigate the DOM to find and modify those pieces whether you use DOM traversal, getElement*, or querySelector*.
Keep these searches out of your inner loops by keeping a reference to the nodes you want to modify when you create the prototype. Then whenever you want to clone a near-identical copy of the prototype, modify the nodes you have references to already and then clone the modified prototype.
Sample Template object
For the heck of it, here is a basic (and probably fast) template object illustrating the use of cloneNode and cached node references (reducing the use of string parsing and Node lookups).
Supply it with a "prototype" node (or string) with class names and data-attr="slotname attributename" attributes. The class names become "slots" for text-content replacement; the elements with data-attr become slots for attribute name setting/replacement. You can then supply an object to the render() method with new values for the slots you have defined, and you will get back a clone of the node with the replacements done.
Example usage is at the bottom.
function Template(proto) {
if (typeof proto === 'string') {
this.proto = this.fromString(proto);
} else {
this.proto = proto.cloneNode(true);
}
this.slots = this.findSlots(this.proto);
}
Template.prototype.fromString = function(str) {
var d = document.createDocumentFragment();
var temp = document.createElement('div');
temp.innerHTML = str;
while (temp.firstChild) {
d.appendChild(temp.firstChild);
}
return d;
};
Template.prototype.findSlots = function(proto) {
// textContent slots
var slots = {};
var tokens = /^\s*(\w+)\s+(\w+)\s*$/;
var classes = proto.querySelectorAll('[class]');
Array.prototype.forEach.call(classes, function(e) {
var command = ['setText', e];
Array.prototype.forEach.call(e.classList, function(c) {
slots[c] = command;
});
});
var attributes = proto.querySelectorAll('[data-attr]');
Array.prototype.forEach.call(attributes, function(e) {
var matches = e.getAttribute('data-attr').match(tokens);
if (matches) {
slots[matches[1]] = ['setAttr', e, matches[2]];
}
e.removeAttribute('data-attr');
});
return slots;
};
Template.prototype.render = function(data) {
Object.getOwnPropertyNames(data).forEach(function(name) {
var cmd = this.slots[name];
if (cmd) {
this[cmd[0]].apply(this, cmd.slice(1).concat(data[name]));
}
}, this);
return this.proto.cloneNode(true);
};
Template.prototype.setText = (function() {
var d = document.createElement('div');
var txtprop = (d.textContent === '') ? 'textContent' : 'innerText';
d = null;
return function(elem, val) {
elem[txtprop] = val;
};
}());
Template.prototype.setAttr = function(elem, attrname, val) {
elem.setAttribute(attrname, val);
};
var tpl = new Template('<p data-attr="cloneid id">This is clone number <span class="clonenumber">one</span>!</p>');
var tpl_data = {
cloneid: 0,
clonenumber: 0
};
var df = document.createDocumentFragment();
for (var i = 0; i < 100; i++) {
tpl_data.cloneid = 'id' + i;
tpl_data.clonenumber = i;
df.appendChild(tpl.render(tpl_data));
}
document.body.appendChild(df);
I'd be shocked if innerHTML wasn't faster. Pre-compiled templates such as those provided by lo-dash or doT seem like a great way to go!
Check out this simple example:
http://jsperf.com/lodash-template
It shows you can get 300,000 ops/sec for a fairly complex template with a loop using lo-dash's pre-compiled templates. Seems pretty fast to me and way cleaner JS.
Obviously, this is only one part of the problem. This generates the HTML, actually inserting the HTML is another problem, but once again, innerHTML seems to win over cloneNode and other DOM-based approaches and generally the code is way cleaner.
http://jsperf.com/clonenode-vs-innerhtml-redo/2
Obviously you can take these benchmarks worth a grain of salt. What really matters is your actual app. But I'd recommend giving multiple approaches a try and benchmarking them yourself before making up your mind.
Note: A lot of the benchmarks about templates on JSPerf are doing it wrong. They're re-compiling the template on every iteration, which is obviously going to be way slow.

Categories

Resources