For creating new CSS rules in a stylesheet, as of now, I only know .insertRule() (excluding the non-standard ways).
Are there any other ways of creating more CSS rules? Something, for example:
Warning: this is not standard, it is just an example to display the intent
var rule = new CSSStyleRule();
rule.selectorText = '.selector';
rule.style.display = 'block';
rule.style.color = 'red';
ruleSet.append(rule);
Anything like or somewhat like the above works as an answer. Instead of new, it may be document.createCssRule() or stylesheet.createCssRule()...
I just this it would be useful for a piece of software I'm developing which left me if there's a programatically way of modifying values in such interface where to add new stuff is not restricted to a parsed string.
Do not worry about IE9 and below when answering, please.
Note: (to unclose) This is not about how to find and modify current CSS rules, this is about making new ones without using the text parser which is what .insertRule() is without building a complete CSS string like .insertRule() requires.
You can add an empty rule, get it, and modify it:
function appendRule(sheet) {
var len = sheet.cssRules.length;
sheet.insertRule('*{}', len);
return sheet.cssRules[len];
}
var rule = appendRule(document.styleSheets[0]);
rule.selectorText = '.selector';
rule.style.display = 'block';
rule.style.color = 'red';
<span class="selector">I should become red.</span>
<span>I should be at the next line and not become red.</span>
However, modifying selectorText does not seem to work on Firefox.
You could append a new style element to your head and just append rules to it:
function addStyle(newRules){
if(document.getElementById("jsAddedRules")){
document.getElementById("jsAddedRules").innerHTML+=newRules;
}else{
var style=document.createElement("style");
style.setAttribure("id","jsAddedRules");
style.innerHTML=newRules;
document.getElementsByTagName("head")[0].appendChild(style);
}
}
And then just call your function when you want to add rules:
addStyle(
".selector{\
display:block;\
color:red;\
}"
);
Related
This question is similar (I think) to this question on SO: How to move all computed CSS styles from one element and apply them to a different element using JavaScript?
What I want to do is find all styles that have been applied by way of css sheets and set the explicitly (as in using a style= attribute) on the element. The reason I want to do this is so that MS Word (2010) will pick up the styling if I open it there
Here's my code
var $this = $(this);
var styles = window.getComputedStyle(this, null);
$this.css(styles);
I also tried with
var styles = document.defaultView.getComputedStyle(this, null);
When I put a try catch around the last line ($this.css(styles), I am getting an error message "invalid calling object". I don't understand what the problem is
Well, the following snippet does what you want, though I don't think it's what you needed. MS Word is NOT a browser, I'm not sure if it supports all the styles or if jQuery works there...
var $this = $('b');
var styles = window.getComputedStyle($this[0]);
$this.css(
Object.values(styles).reduce(
(a, e) => 0*(a[e] = styles[e]) || a, {}
)
)
console.log(`style="${$this.attr('style')}"`)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<b>I have styles</b>
I am thinking about the best way to dynamically add and remove css rules.
*I don't want to use the deleteRule() function since it requires an index that cannot be stored (in the sense that if I add a rule, the index of the previously inserted rule may change)
Then, ideally I would like to add a rule for which I also have the reference, so that I cannot eliminate it, but at least I'd be able to change it. However, I'm afraid this cannot be done.
The question then is, is this code feasible?
https://jsfiddle.net/b4t3z5w4/1/
var rule = ".item_1 { display: inline;}";
var stylesheet = document.styleSheets[1];
var rules = document.styleSheets[1].cssRules;
var myrule = ".item0 {display:visible;}";
var mycssrule = stylesheet.insertRule(myrule,rules.length);
myrule.style.display = 'none'; // doesn't work (it is just a string)
mycssrule.style.display = 'none'; // doesn't work (insertRule only returns I guess a true)
(I know I can get the css rule by parsing the cssRules and finding the rule and storing then the reference, I just wanted to know if is there a way to do it through insertRule(), it seems it should be possible)
Simpler than what I thought, I can select the rule since I have the index
https://jsfiddle.net/b4t3z5w4/2/
var rule = ".item_1 { display: inline;}";
var stylesheet = document.styleSheets[1];
var rules = document.styleSheets[1].cssRules;
var myrule = ".item0 {display:visible;}";
var i = rules.length;
stylesheet.insertRule(myrule,rules.length);
var mycssrule = rules[i];
mycssrule.style.width = '80px';
console.log(mycssrule);
In practice, what are the advantages of using createElement over innerHTML? I am asking because I'm convinced that using innerHTML is more efficient in terms of performance and code readability/maintainability but my teammates have settled on using createElement as the coding approach. I just wanna understand how createElement can be more efficient.
There are several advantages to using createElement instead of modifying innerHTML (as opposed to just throwing away what's already there and replacing it) besides safety, like Pekka already mentioned:
Preserves existing references to DOM elements when appending elements
When you append to (or otherwise modify) innerHTML, all the DOM nodes inside that element have to be re-parsed and recreated. If you saved any references to nodes, they will be essentially useless, because they aren't the ones that show up anymore.
Preserves event handlers attached to any DOM elements
This is really just a special case (although common) of the last one. Setting innerHTML will not automatically reattach event handlers to the new elements it creates, so you would have to keep track of them yourself and add them manually. Event delegation can eliminate this problem in some cases.
Could be simpler/faster in some cases
If you are doing lots of additions, you definitely don't want to keep resetting innerHTML because, although faster for simple changes, repeatedly re-parsing and creating elements would be slower. The way to get around that is to build up the HTML in a string and set innerHTML once when you are done. Depending on the situation, the string manipulation could be slower than just creating elements and appending them.
Additionally, the string manipulation code may be more complicated (especially if you want it to be safe).
Here's a function I use sometimes that make it more convenient to use createElement.
function isArray(a) {
return Object.prototype.toString.call(a) === "[object Array]";
}
function make(desc) {
if (!isArray(desc)) {
return make.call(this, Array.prototype.slice.call(arguments));
}
var name = desc[0];
var attributes = desc[1];
var el = document.createElement(name);
var start = 1;
if (typeof attributes === "object" && attributes !== null && !isArray(attributes)) {
for (var attr in attributes) {
el[attr] = attributes[attr];
}
start = 2;
}
for (var i = start; i < desc.length; i++) {
if (isArray(desc[i])) {
el.appendChild(make(desc[i]));
}
else {
el.appendChild(document.createTextNode(desc[i]));
}
}
return el;
}
If you call it like this:
make(["p", "Here is a ", ["a", { href:"http://www.google.com/" }, "link"], "."]);
you get the equivalent of this HTML:
<p>Here is a link.</p>
User bobince puts a number of cons very, very well in his critique of jQuery.
... Plus, you can make a div by saying $(''+message+'') instead of having to muck around with document.createElement('div') and text nodes. Hooray! Only... hang on. You've not escaped that HTML, and have probably just created a cross-site-scripting security hole, only on the client side this time. And after you'd spent so long cleaning up your PHP to use htmlspecialchars on the server-side, too. What a shame. Ah well, no-one really cares about correctness or security, do they?
jQuery's not wholly to blame for this. After all, the innerHTML property has been about for years, and already proved more popular than DOM. But the library certainly does encourage that style of coding.
As for performance: InnerHTML is most definitely going to be slower, because it needs to be parsed and internally converted into DOM elements (maybe using the createElement method).
InnerHTML is faster in all browsers according to the quirksmode benchmark provided by #Pointy.
As for readability and ease of use, you will find me choosing innerHTML over createElement any day of the week in most projects. But as you can see, there are many points speaking for createElement.
While innerHTML may be faster, I don't agree that it is better in terms of readability or maintenance. It may be shorter to put everything in one string, but shorter code is not always necessarily more maintainable.
String concatenation just does not scale when dynamic DOM elements need to be created as the plus' and quote openings and closings becomes difficult to track. Consider these examples:
The resulting element is a div with two inner spans whose content is dynamic. One of the class names (warrior) inside the first span is also dynamic.
<div>
<span class="person warrior">John Doe</span>
<span class="time">30th May, 2010</span>
</div>
Assume the following variables are already defined:
var personClass = 'warrior';
var personName = 'John Doe';
var date = '30th May, 2010';
Using just innerHTML and mashing everything into a single string, we get:
someElement.innerHTML = "<div><span class='person " + personClass + "'>" + personName + "</span><span class='time'>" + date + "</span></div>";
The above mess can be cleaned up with using string replacements to avoid opening and closing strings every time. Even for simple text replacements, I prefer using replace instead of string concatenation.
This is a simple function that takes an object of keys and replacement values and replaces them in the string. It assumes the keys are prefixed with $ to denote they are a special value. It does not do any escaping or handle edge cases where $ appears in the replacement value etc.
function replaceAll(string, map) {
for(key in map) {
string = string.replace("$" + key, map[key]);
}
return string;
}
var string = '<div><span class="person $type">$name</span><span class="time">$date</span></div>';
var html = replaceAll(string, {
type: personClass,
name: personName,
date: date
});
someElement.innerHTML = html;
This can be improved by separating the attributes, text, etc. while constructing the object to get more programmatic control over the element construction. For example, with MooTools we can pass object properties as a map. This is certainly more maintainable, and I would argue more readable as well. jQuery 1.4 uses a similar syntax to pass a map for initializing DOM objects.
var div = new Element('div');
var person = new Element('span', {
'class': 'person ' + personClass,
'text': personName
});
var when = new Element('span', {
'class': 'time',
'text': date
});
div.adopt([person, when]);
I wouldn't call the pure DOM approach below to be any more readable than the ones above, but it's certainly more maintainable because we don't have to keep track of opening/closing quotes and numerous plus signs.
var div = document.createElement('div');
var person = document.createElement('span');
person.className = 'person ' + personClass;
person.appendChild(document.createTextNode(personName));
var when = document.createElement('span');
when.className = 'date';
when.appendChild(document.createTextNode(date));
div.appendChild(person);
div.appendChild(when);
The most readable version would most likely result from using some sort of JavaScript templating.
<div id="personTemplate">
<span class="person <%= type %>"><%= name %></span>
<span class="time"><%= date %></span>
</div>
var div = $("#personTemplate").create({
name: personName,
type: personClass,
date: date
});
You should use createElement if you want to keep references in your code. InnerHTML can sometimes create a bug that is hard to spot.
HTML code:
<p id="parent">sample <span id='test'>text</span> about anything</p>
JS code:
var test = document.getElementById("test");
test.style.color = "red"; //1 - it works
document.getElementById("parent").innerHTML += "whatever";
test.style.color = "green"; //2 - oooops
1) you can change the color
2) you can't change color or whatever else anymore, because in the line above you added something to innerHTML and everything is re-created and you have access to something that doesn't exist anymore. In order to change it you have to again getElementById.
You need to remember that it also affects any events. You need to re-apply events.
InnerHTML is great, because it is faster and most time easier to read but you have to be careful and use it with caution. If you know what you are doing you will be OK.
Template literals (Template strings) is another option.
const container = document.getElementById("container");
const item_value = "some Value";
const item = `<div>${item_value}</div>`
container.innerHTML = item;
Say I have a list like this:
<ul id='dom_a'>
<li>foo</li>
</ul>
I know how to insert elements in the ul tag with:
Element.insert('dom_a', {bottom:"<li>bar</li>"});
Since the string I receive contains the dom id, I need to insert the inner HTML instead of the whole element. I need a function to do this:
insert_content('dom_a', {bottom:"<ul id='dom_a'><li>bar</li></ul>"});
And obtain:
<ul id='dom_a'>
<li>foo</li>
<li>bar</li>
</ul>
How should I do this with Prototype ?
Here is the solution I have come up with, can anyone make this better ?
Zena.insert_inner = function(dom, position, content) {
dom = $(dom);
position = position.toLowerCase();
content = Object.toHTML(content);
var elem = new Element('div');
elem.innerHTML = content; // strip scripts ?
elem = elem.down();
var insertions = {};
$A(elem.childElements()).each(function(e) {
insertions[position] = e;
dom.insert(insertions);
});
}
I think you could parse the code block in your variable, then ask it for its innerHTML, and then use insert to stick that at the bottom of the actual node in the DOM.
That might look like this:
var rep_struct = "<ul id='dom_a'><li>bar</li></ul>";
var dummy_node = new Element('div'); // So we can easily access the structure
dummy_node.update(rep_struct);
$('dom_a').insert({bottom: dummy_node.childNodes[0].innerHTML});
I think you can slim down the code a bit by simply appending the innerHTML of the first child of temporary element:
Zena.insert_inner = function(dom, position, content) {
var d = document.createElement('div');
d.innerHTML = content;
var insertions = {};
insertions[position] = d.firstChild.innerHTML;
Element.insert(dom, insertions);
}
Not too much of an improvement though, example here.
I've been looking into the Prototype Documentation and I found this: update function.
By the way you described it, you could use the update function in order to find the current bottom content and then update it (just like innerHTML) by adding the desired code plus the previous stored code.
You could use regular expression to strip the outer element.
Element.Methods.insert_content = function(element, insertions) {
var regex = /^<(\w+)[^>]*>(.*)<\/\1>/;
for (key in insertions) {
insertions[key] = regex.exec(insertions[key])[2];
}
Element.insert(element, insertions);
};
Element.addMethods();
$('dom_a').insert_content({bottom:"<ul id='dom_a'><li>bar</li></ul>"});
If you are using PrototypeJS, you might also want to add script.aculo.us to your project. Builder in script.aculo.us provides a nice way to build complex DOM structures like so:
var myList = Builder.node("ul", {
id: "dom_a"
},[
Builder.node("li", "foo"),
Builder.node("li", "bar"),
]);
After this, you can insert this object which should be rendered as HTML anywhere in the DOM with any insert/update functions (of PrototypeJS) or even standard JavaScript appendChild.
$("my_div").insert({After: myList});
Note that in PrototypeJS insert comes in 4 different modes: After, Before, Top and Bottom. If you use insert without specifying a "mode" as above, the default will be Bottom. That is, the new DOM code will be appended below existing contents of the container element as innerHTML. Top will do the same thing but add it on top of the existing contents. Before and After are also cool ways to append to the DOM. If you use these, the content will be added in the DOM structure before and after the container element, not inside as innerHTML.
With Builder however, there is one thing to keep in mind, .. okay two things really:
i. You cannot enter raw HTML in the object as content... This will fail:
Builder.node("ul", "<li>foo</li>");
ii. When you specify node attributes, keep in mind that you must use className to signify HTML attribute class (and possibly also htmlFor for for attribute... although for attribute seems to be deprecated in HTML5(?), but who does not want to use it for labels)
Builder.node("ul", {
id: "dom_a",
className: "classy_list"
});
I know you are scratching your head because of point i. > What, no raw HTML, dang!
Not to worry. If you still need to add content which might contain HTML inside a Builder created DOM, just do it in the second stage using the insert({Before/After/Top/Bottom: string}). But why'd you want to do it in the first place? It would be really good practice if you wrote an once for all function that generates all kinds of DOM elements rather than stitching in all sorts of strings. The former approach would be neat and elegant. This is something like the inline style versus class type of question. Good design should after all separate content from meta content, or formatting markup / markdown.
One last thing to keep handy in your toolbox is Protype's DOM traversal in case you want to dynamically insert and delete content like a HTML Houdini. Check out the Element next, up, down, previous methods. Besides the $$ is also kinda fun to use, particularly if you know CSS3 selectors.
EDIT [how can I] change the color of every R and r in my HTML document with javascript?
I'd use the highlight plugin for jQuery. Then do something like:
$('*').highlight('r'); // Not sure if it's case-insensitive or not
and in CSS:
.highlight { background-color: yellow; }
Doable, but not super easy. There's no CSS way to do it.
Basically, you'll need to use Javascript and iterate through the all nodes. If it's a text node, you can search it for "R" and then replace the R with a <span style="color:red">R</span>
I am obviously simplifying this a bit, it's probably better to just dynamically add a "highlight" class, rather than hard code a style, and have that defined in CSS. Similarly, I'm sure you'll wanna parameterize the search string. Also, this doesn't take into account what the text node is, for instance, I have special handling to skip comments, but you'll probably find there's other things (script nodes?) you also need to skip.
function updateNodes(node) {
if (node.nextSibling)
updateNodes(node.nextSibling);
if (node.nodeType ==8) return; //Don't update comments
if (node.firstChild)
updateNodes(node.firstChild);
if (node.nodeValue) { // update me
if (node.nodeValue.search(/[Rr]/) > -1){ // does the text node have an R
var span=document.createElement("span");
var remainingText = node.nodeValue;
var newValue='';
while (remainingText.search(/[Rr]/) > -1){ //Crawl through the node finding each R
var rPos = remainingText.search(/[Rr]/);
var bit = remainingText.substr(0,rPos);
var r = remainingText.substr(rPos,1);
remainingText=remainingText.substr(rPos+1);
newValue+=bit;
newValue+='<span style="color:red">';
newValue+=r;
newValue+='</span>';
}
newValue+=remainingText;
span.innerHTML=newValue;
node.parentNode.insertBefore(span,node);
node.parentNode.removeChild(node);
}
}
}
function replace(){ updateNodes(document.body);
}
Yes this is possible with a little Javascript, a smattering of CSS and some regex.
First, you need to define a style which provides the colour you require (in my example below I refer to a CSS class called "new-colour"), and then run some regex over your HTML content which does a search and replace. You are looking to change all 'r' and 'R' characters into something like this (as an example):
<span class="new-colour">r</span>
If you don't know regex, there are oodles of resources out there to get you started. You will be pleased to know that your requirement is very simple, so no worries there. Here are a couple of links:
regexlib.com
8 regular expressions you should know
You would need to use the DOM (or jQuery) to iterate through every text node in the document. Whenever you find the letter R, apply a transformation that wraps the letter in an appropriate element.
e.g. Transform the text node "art" into "a<span class="colored">r</span>t". This adds two new text nodes, "r" and "t", and the new span element.
The highlight plugin for jQuery is one option. Another option - especially since to-morrow - you might want to extend your highlighting into keywords or other terms is to use Google's Closure goog.dom.annotate Class. The beauty of this Class is that it will actually parse the dom tree properly and ONLY
annotate the relevant terms. It will also allow you to EXCLUDE elements or elements with certain classes.
A common problem with annotations is that you can mess your HTML, if you are not careful.
For example the 'simple solution posted above'
var body = document.getElementsByTagName("body")[0];
var html = body.innerHTML
.replace(/(^|>[^<rR]*)([rR])/g, "$1<em>$2</em>");
body.innerHTML = html;
will surely also capture terms in any style attributes. If you had this:
<p class="red">text......</p>
It will become
<p class="<span class="red">r</span>ed .....
that will break your html.
In general DOM parsing is 'slow', so try and avoid annotating the whole body of a webpage, ask yourself why you only need to highlight the R's? Actually I am curious why do you want to annotate the r's?:)
Plain JS solution without need of any 20kB JS library:
var body = document.getElementsByTagName("body")[0];
var html = body.innerHTML
.replace(/(^|>[^<rR]*)([rR])/g, "$1<em>$2</em>");
body.innerHTML = html; // note that you will lose all
// event handlers in this step...