Javascript - Apply same property to several variables - javascript

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
);

Related

JS calling ID by variable

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.

JS HTML Object, search for elements who's classes contain

I'm generating some HTML from JSON, which leaves me with an HTML object with a dynamic amount of children, depends on one the user and if they change the JSON.
I need to keep a reference to certain elements depending on the classes, so the user may change the name of the classes, but they will need to have certain keywords, so, for example, I want to keep a reference to marvLightbox__close.
Someone could change this to something like something__close, so how can I search this HTML object's children for just close?
I have not yet appended this object to the DOM, it's just in memory.
P.S. NO JQUERY!
Edit
Found out I can use this, but I feel like it's cheating a bit! Plus I need to support IE8...
document.querySelectorAll('[class*=-img]')
Edit 2
The Polyfill actually isn't too many lines, so it's not too bad after all...
if (!document.querySelectorAll) {
document.querySelectorAll = function (selectors) {
var style = document.createElement('style'), elements = [], element;
document.documentElement.firstChild.appendChild(style);
document._qsa = [];
style.styleSheet.cssText = selectors + '{x-qsa:expression(document._qsa && document._qsa.push(this))}';
window.scrollBy(0, 0);
style.parentNode.removeChild(style);
while (document._qsa.length) {
element = document._qsa.shift();
element.style.removeAttribute('x-qsa');
elements.push(element);
}
document._qsa = null;
return elements;
};
}
Found out I can use this, but I feel like it's cheating a bit! Plus I need to support IE8...
document.querySelectorAll('[class*=-img]')
The Polyfill actually isn't too many lines, so it's not too bad after all...
if (!document.querySelectorAll) {
document.querySelectorAll = function (selectors) {
var style = document.createElement('style'), elements = [], element;
document.documentElement.firstChild.appendChild(style);
document._qsa = [];
style.styleSheet.cssText = selectors + '{x-qsa:expression(document._qsa && document._qsa.push(this))}';
window.scrollBy(0, 0);
style.parentNode.removeChild(style);
while (document._qsa.length) {
element = document._qsa.shift();
element.style.removeAttribute('x-qsa');
elements.push(element);
}
document._qsa = null;
return elements;
};
}

JavaScript get list of styles currently applied to an element

List only rendered styles, not arbitrary ones that aren't applied
I've tried many things to get the styles applied to an element but have come up blank.
Please do not cite getComputedStyle as being a solution unless you can solve the junk returns issue.
The primary problem is that window.getComputedStyle(document.querySelector('ANY ELEMENT')).fill will return "rgb(0, 0, 0)", which is not the correct style in almost any instances, and has no apparent way to destinguish if its actually being applied or not.
The above example is not the only problem case; there are tons of rules returned by getComputedStyle which are wrong and will drastically change the look of the page if they are applied.
Static parsing is not an option as there are cases where the .css files are on another server with no cross-origin headers; which also hides styles usually found in document.styleSheets.
Is there any way to get a list of the applied styles and nothing else?
As requested this code will demonstrate the problem (on Chrome):
var all = document.getElementsByTagName('*');
for(var i in all)
if (all[i].style) all[i].style.cssText = window.getComputedStyle(all[i]).cssText;
EDIT: My answer has code which works on all browsers. I keep above to preserve comment thread.
Here are the version that don't need to check depth.
The problem in your code is the assign of inline style in the previous element will affect the getComputedStyle result of the next result. It mean the value of getComputedStyle is always changing in the loop. You can first store it in an array like this.
var all = document.getElementsByTagName('*');
tmpArr = []
for(var i in all) {
if (all[i].style) {
tmpArr[i] = window.getComputedStyle(all[i]).cssText;
}
}
for(var i in all) {
if (all[i].style) {
all[i].style.cssText = tmpArr[i]; ;
}
}
console.log("finish");
You can change tmpArr[i] = window.getComputedStyle(all[i]).cssText; to tmpArr[i] = window.getComputedStyle(all[i]).cssText + "-webkit-text-fill-color:#691099!important"; to test whether it work
It will be slow if you open the inspector since there are too much inline style, but it will solve the problem if all you need is just put the style to be inline style.
Partial Answer (Updated):
It is possible to get only the active styles by calling my function getRenderedStyles:
getRenderedStyles now bypasses active stylesheets for more accurate output.
function getRenderedStyles(element) {
var tmpele, tmpstyle, elestyle, varstyle, elecolor, eletag;
var styles = {};
var defstyle = {};
elestyle = window.getComputedStyle(element);
elecolor = elestyle.color;
eletag = element.tagName;
var frag = document.createDocumentFragment();
frag.appendChild(document.documentElement);
tmpele = document.appendChild(document.createElement(eletag));
tmpstyle = window.getComputedStyle(tmpele);
styles['color'] = elecolor===tmpstyle.color?undefined:elecolor;
tmpele.style.color = elecolor; // workaround for color propagation on other styles
for (var i in tmpstyle)
defstyle[i] = tmpstyle[i];
tmpele.remove();
document.appendChild(frag);
varstyle = element.style;
for (var i in varstyle) {
if ((((typeof varstyle[i])==="string"))&&(i!=="cssText")) {
if ((defstyle[i]!==elestyle[i]))
styles[i] = elestyle[i];
}
}
return styles;
}
Sadly there's a caviat as the browser still seemingly returns invalid styles in some cases. Often shifting the locations of elements.
To verify this you may run the following code, which takes into account parent/child inheritance, in an attempt to properly apply the current styles to the page:
function DOMDepth(element) {
var cur = element;
var deep = 0;
while(cur.parentNode)
deep++, cur = cur.parentNode;
return deep;
}
function getElementsByDepth() {
var all = document.getElementsByTagName('*');
var depth_map = {};
var deepest = 0;
for(var i in all) {
var depth = DOMDepth(all[i]);
deepest = depth>deepest?depth:deepest;
depth_map[depth] = depth_map[depth] || [];
depth_map[depth].push(all[i]);
}
depth_map['deepest'] = deepest;
return depth_map;
}
function inlineComputedStyles() {
var depth_map = getElementsByDepth();
for (var i = depth_map.deepest; i>0; i--) {
var elements = depth_map[i];
for (var j in elements) {
var styles = getRenderedStyles(elements[j]);
for (var k in styles) {
elements[j].style[k] = styles[k];
}
}
}
}
I have tested the preceeding and can confirm it does not suffer the color problems of the snippet in the question. Sadly I am uncertain as to why some elements still shift or if there's a way to fix it.
Special thanks to Kit Fung for pointing out the inheritance problem.

how to find common in inline styles?

I have an array of sequential dom element nodes which may or may not have inline styles. I need to end up with an object or array with only keys and values common to all the nodes. Needs to work in IE8+, chrome and FF.
I can't even get one nodes styles into an array without a bunch of other stuff being included as well.
I've tried to use node[x].style but it seems to return a lot of extraneous stuff and other problems.
//g is node array
s=[];
for(k in g)
{
if(g.hasOwnProperty(k) && g[k]) s[k]=g[k];
}
console.log(s);
gives me ["font-weight", cssText: "font-weight: bold;", fontWeight: "bold"] which is close but I only want fontWeight: "bold" in the array. In any case, this only works in chrome.
The only idea I have at the moment that might work is using the cssText and splitting on semi-colons and splitting again on colons but that seems an ugly and slow way to do it especially as I then need to compare to a bunch of nodes and do the same to their styles.
So, I'm hoping someone can come up with a simple elegant solution to the problem posed in the first paragraph.
If you truly want ONLY styles that are specified inline in the HTML for the object, then you will have to deal with text of the style attribute as you surmised.
The .style property will show you more styles than were specified on the object itself (showing you default values for some styles) so you can't use that.
Here's a function that takes a collection of DOM nodes and returns a map of common styles (styles that are specified inline and are the same property and value on every object):
function getCommonStyles(elems) {
var styles, styleItem, styleCollection = {}, commonStyles = {}, prop, val;
for (var i = 0; i < elems.length; i++) {
var styleText = elems[i].getAttribute("style");
if (styleText) {
// split into an array of individual style strings
styles = styleText.split(/\s*;\s*/);
for (var j = 0; j < styles.length; j++) {
// split into the two pieces of a style
styleItem = styles[j].split(/\s*:\s*/);
// only if we found exactly two pieces should we count this one
if (styleItem.length === 2) {
prop = styleItem[0];
val = styleItem[1];
// if we already have this style property in our collection
if (styleCollection[prop]) {
// if same value, then increment the cntr
if (styleCollection[prop].value === val) {
++styleCollection[prop].cntr;
}
} else {
// style tag didn't exist so add it
var newTag = {};
newTag.value = val;
newTag.cntr = 1;
styleCollection[prop] = newTag;
}
}
}
}
}
// now go through the styleCollection and put the ones in the common styles
// that were present for every element
for (var prop in styleCollection) {
if (styleCollection[prop].cntr === elems.length) {
commonStyles[prop] = styleCollection[prop].value;
}
}
return(commonStyles);
}
Working demo: http://jsfiddle.net/jfriend00/JW7CZ/

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