I'm looking for a way to change some table headers. And some of these headers contain addition HTML code, like line breaks.
This is the typical setup of the table on the page (page source):
<table class="awesometable" style="width: 100%; margin-right: 0;">
<tr>
<th style="background: red; color: white;">Male<br /> contestant</th>
<th style="background: red; color: white;">Female<br /> contestant</th>
</tr>
</table>
Where Male<br /> contestant, Female<br /> contestant (and a series of other slight variation) should be changed to a simple symbol like ♂ and ♀.
Using document.body.innerHTML.replace() replaces the entire DOM with new nodes, which is undesired, and can also change other instances were the text in the table header is also in plain text and were it shouldn't be changed, although this is rare, and only a minor issue.
Using a selective nodeType == 3 fails on the additional line breaks.
Any ideas are welcome, code examples (here or at, for example, jsfiddle) are always preferred. Thanks.
Try this:
var headers = document.getElementsByTagName('th');
headers[0].innerHTML = '♂';
headers[1].innerHTML = '♀';
Shows the concept. Better to give the respective cells an id or class to identify them rather than using the content. You could also do something like:
var cell, headers = document.getElementsByTagName('th');
for (var i=0, iLen=headers.length; i<iLen; i++) {
cell = headers[i];
if (cell.innerHTML.match('Male') ) {
cell.innerHTML = '♂';
}
else if (cell.innerHTML.match('Female')) {
cell.innerHTML = '♀';
}
}
I'd recommend using jQuery just because it's so much friendlier when dealing with the DOM. Note also that it's probably slightly faster -- which may matter if you have a large table -- not to assign something to innerHTML until you've done all the operations on the string. That is, store the value of innerHTML in a variable, run replace functions, then place the result back in innerHTML. (Note also that I'm replacing your <br /> with <br>. At least in Chrome, innerHTML will convert XHTML closed single tags to valid HTML, which here is <br>.)
Here's a more extensible method (jsFiddle Example):
var replacements = [
["Male<br>contestant", "♂"],
["Female<br>contestant", "♀"]
];
function runReplacement() {
$(".awesometable th").each(function() {
var $this = $(this);
var ih = $this.html();
$.each(replacements, function(i, arr) {
ih = ih.replace(arr[0], arr[1]);
});
$this.html(ih);
});
}
In addition to the above, you can use .text() instead of .html() by just ignoring the <br> part of each cell (e.g. replacing "Malecontestant" instead of "Male<br>contestant"). Example here.
EDIT:
And for correcting an example below:
var headers = document.getElementsByTagName("th");
for (var i = 0; i < headers.length; i++) {
headers[i].innerHTML = headers[i].innerHTML.replace("Male<br>contestant", "♂")
.replace("Female<br>contestant", "♀");
}
For silliness like focusing on terseness (70 characters shorter, ignoring excess whitespace):
$("th").each(function(){$(this).html($(this).html()
.replace("Male<br>contestant","♂").replace("Female<br>contestant","♀"));});
What's the problem with something like
function replace(text)
{
return text.replace('Male', '♂').replace('Female', '♀');
}
var ths = document.getElementById('myTable').getElementsByTagName('th'),
i = ths.length,
th, text;
while (i-- && (th = ths[i], text = th.innerText))
{
if (text)
{
// IE, Safari, Chrome, Opera
th.innerText = replace(text);
}
else
{
// Everything but IE <9
th.textContent = replace(th.textContent);
}
}
As requested, jsFiddle: http://jsfiddle.net/mattball/4EwGt/
Yes, innerText || textContent is a PITA.
I want to replace the entire header, Male<br /> contestant and Female<br /> contestant, not just the first word, and trying the whole in your example doesn't work. Only seems to remove the line break, doesn't change the text.
Use this replace function instead:
function replace(text)
{
return text.replace('Male\ncontestant', '♂')
.replace('Female\ncontestant', '♀');
}
Demo: http://jsfiddle.net/mattball/8W3cM/
Related
Is it possible to make changes to a CSS rule-set dynamically (i.e. some JS which would change a CSS rule-set when the user clicks a widget)
This particular CSS rule-set is applied to lots of elements (via a class selector) on the page and I want to modify it when the user clicks the widget, so that all the elements having the class change.
You can, but it's rather cumbersome. The best reference on how to do it is the following article: Totally Pwn CSS with Javascript (web archive link).
I managed to get it to work with Firefox and IE - I couldn't in Chrome, though it appears that it supports the DOM methods.ricosrealm reports that it works in Chrome, too.
This is a modern version based on Totally Pwn CSS with Javascript. It's ES6 I hope don't mind.
function getCSSRule(ruleName) {
ruleName = ruleName.toLowerCase();
var result = null;
var find = Array.prototype.find;
find.call(document.styleSheets, styleSheet => {
result = find.call(styleSheet.cssRules, cssRule => {
return cssRule instanceof CSSStyleRule
&& cssRule.selectorText.toLowerCase() == ruleName;
});
return result != null;
});
return result;
}
This function returns a CSSStyleRule that you can use like this:
var header = getCSSRule('#header');
header.style.backgroundColor = 'red';
Also document.styleSheets list references of the CSSStylesSheets Objects. Other way to acces a specific sytleSheet in the page is by assigning an id to the style or link element in the html code, and get it in javascript using document.getElementById('my-style').sheet. This are some useful methods:
Major Browsers and IE9+ : insertRule(), deleteRule(), removeProperty().
Major Browsers, Firefox? and IE9+ : setProperty().
<stye id="my-style" ...
....
var myStyle = document.getElementById('my-style').sheet
myStyle.insertRule('#header { background: red; }', 0);
It is also possible to dynamically create a new style element to store dynamic created styles, I think should be way to avoid conflicts.
You can edit CLASS in document styleshets as follows
[...document.styleSheets[0].cssRules].find(x=> x.selectorText=='.box')
.style.background= 'red';
function edit() {
[...document.styleSheets[0].cssRules].find(x=> x.selectorText=='.box')
.style.background= 'red';
}
.box {
margin: 10px;
padding: 10px;
background: yellow;
}
<button onclick="edit()" >Click me</button>
<div class="box" >My box 1</div>
<div class="box" >My box 2</div>
<div class="box" >My box 3</div>
I tried the code via link from #alex-gyoshev comment, but it dosn't work
it fails on the CSS rules with Google fonts in Chrome
it fails on FireFox security checks
So I changed it slightly, but deleted delete functionality since it wasn't needed for me. Checked in IE 11, FireFox 32, Chrome 37 and Opera 26.
function getCSSRule(ruleName) { // Return requested style object
ruleName = ruleName.toLowerCase(); // Convert test string to lower case.
var styleSheet;
var i, ii;
var cssRule = false; // Initialize cssRule.
var cssRules;
if (document.styleSheets) { // If browser can play with stylesheets
for (i = 0; i < document.styleSheets.length; i++) { // For each stylesheet
styleSheet = document.styleSheets[i];
if (!styleSheet.href) {
if (styleSheet.cssRules) { // Browser uses cssRules?
cssRules = styleSheet.cssRules; // Yes --Mozilla Style
} else { // Browser usses rules?
cssRules = styleSheet.rules; // Yes IE style.
} // End IE check.
if (cssRules) {
for (ii = 0; ii < cssRules.length; ii++) {
cssRule = cssRules[ii];
if (cssRule) { // If we found a rule...
// console.log(cssRule);
if (cssRule.selectorText) {
console.log(cssRule.selectorText);
if (cssRule.selectorText.toLowerCase() == ruleName) { // match ruleName?
return cssRule; // return the style object.
}
}
}
}
}
}
}
}
return false; // we found NOTHING!
}
Depending on what you're trying to achieve, a better solution might be to change/add a class to a containing element (body would do!), and define classes accordingly.
.yourclass { color: black }
#wrapper.foo .yourclass { color: red }
#wrapper.bar .yourclass { color: blue }
then you can just use
document.getElementById('wrapper').className='foo';
(or your chosen js framework's wrapper for the same) to change everything with class yourclass inside whatever your wrapper element is.
The APIs for editing stylesheets with JS are, sadly, not consistent across browsers. The YUI Stylesheet Utility attempts to smooth over these differences so you could just use that. You could also look at the source code to figure out how it works if you don't want to use YUI itself.
give your style tag an id, like <style id="ssID">
if someonelse is making your styles for you
tell THAT person to give the style tag an id -
that way you can access it directly without
scrambling around wondering what its index is
// create a hash table
var cssHash = {};
// loop through and populate the hash table
for (let i in (r = ss0.sheet.rules)) {
// selectorText is the name of the rule - set the value equal to the rule
cssHash[r[i].selectorText] = r[i];
}
now you have a hash table for everything in the style sheet -
note that some values will be undefined, but not for
any of the things you care about
if you have, for instance, a class called #menuItem
and you want to change its color to black, do this
cssHash['#menuItem'].style.color = #000;
that line will set the color of the style of the rule
whose index was looked up in the hash table (cssHash)
by the name '#menuItem'
more importantly, you probably have several different
classes that you want to change all at once
kind of like when you switched majors in college
let's say you have four different classes
and you want to set all of their background colors
to the same value, that some user selected from an input
the color selector tag is <input id="bColor" type="color">
and the class rules you want to change are called
#menuItem .homeAddr span and #vacuum:hover
// create a listener for that color selector
bColor.addEventListener('input', function (e) {
// loop through a split list of the four class names
'#menuItem .homeAddr span #vacuum:hover'.split(' ').forEach(function (obj) {
// use the hash table to look up the index of each name
// and set the background color equal to the color input's value
cssHash[obj].style.backgroundColor = bColor.value;
});
}, false); // false added here for the sake of non-brevity
While setAttribute is nice, there is a standard way of doing this across most browsers:
htmlElement.className = 'someClass';
To do it over many elements, you will need a cross browser solution:
function getElementsByClassName( className, context, tagName ) {
context = context || document;
if ( typeof context.getElementsByClassName === 'function' )
return context.getElementsByClassName( className );
if ( typeof context.getElementsByTagName !== 'function' )
return [];
var elements = typeof tagName === 'string' ? context.getElementsByTagName( tagName ) :
context.getElementsByTagName('*'),
ret = [];
for ( var i = 0, il = elements.length; i < il; i++ )
if ( elements[ i ].className.match( className ) )
ret.push( elements[ i ] );
return ret;
}
var elements = getElementsByClassName('someClass');
for ( var i = 0, il = elements.length; i < il; i++ )
elements[ i ].className = 'newClass';
You may want to replace the line:
if ( elements[ i ].className.match( className ) )
With some Regular Expression, but you will have to escape special characters in that case.
To check all stylesheets for the rule and set it:
Your rule:
.aaa: {
background-color: green
}
[...document.styleSheets].flatMap(s=>[...s.cssRules])
.find(i=>i.selectorText=='.aaa').style.backgroundColor = 'red';
Note that the css styles, when accessed through javascript, do not have dashes in them. In the example above, background-color becomes backgroundColor
How can i add an createElement(br) before and after the table? I tried it with doc.body.appendChild(p); at the end, but nothing happens. i don't know where i have to write the appendchild
function insertBlock(border)
{
var doc = document.getElementById("frame").contentWindow.document;
var p = doc.createElement("br");
var range = doc.getSelection().getRangeAt(0);
myParent=document.getElementById("frame").contentWindow.document.body;
if (border == true)
{
myTable=document.createElement("table");
myTable.setAttribute("style","border: 1px solid #000000;");
}
else
{
myTable=document.createElement("table");
}
// IE5, IE6 benoetigen unbedingt tbody-Element
myTBody=document.createElement("tbody");
myRow=document.createElement("tr");
myCell=document.createElement("td");
myText=document.createTextNode("Die erste Zelle");
myCell.appendChild(myText);
myRow.appendChild(myCell);
myCell=document.createElement("td");
myText=document.createTextNode("Die zweite Zelle");
myCell.appendChild(myText);
myRow.appendChild(myCell);
myTBody.appendChild(myRow);
myTable.appendChild(myTBody);
myParent.appendChild(myTable);
range.insertNode(myTable);
}
Should be here:
myParent.appendChild(p);
myParent.appendChild(myTable);
myParent.appendChild(p.cloneNode());
Notice that the second one is a clone. When it's not. Only the second one will be added. (You can not add an element twice).
You can also add two different objects of course.
myParent.appendChild(p);
myParent.appendChild(myTable);
myParent.appendChild(p2);
Or withoud variables:
myParent.appendChild(doc.createElement("br"));
myParent.appendChild(myTable);
myParent.appendChild(doc.createElement("br"));
Try this:
myTable.insertAdjacentHTML('afterend','<br />');
myTable.insertAdjacentHTML('beforebegin','<br />');
if you want to do it using createElement('br').
var p = doc.createElement("br");
myParent.insertBefore(p,myTable);
myParent.insertBefore(p.cloneNode(),myTable.nextSibling);
I have a large HTML source, and I'd like to break it into multiple parts. I've been able to achieve most of this task, but I'm struggling with a single aspect.
When all of the HTML is wrapped in tags, I have no problem; however, if text nodes are mixed with HTML tags, I'm unable to capture all parts.
What am I doing wrong?
Below, is a jsFiddle that shows an example of the problem.
http://jsfiddle.net/acrashik/aDm8L/1/
Here is the code I have written so far to attempt to break up the HTML:
function parseElement(selector,parts, cycle) {
var cc = $(selector),
content = cc.children(),
total = content.length,
maxHeight = cc.height(),
spaceLeft = maxHeight,
cycle = cycle || 0;
function addToPage(elem,elemSize) {
elem.appendTo(parts[cycle]);
spaceLeft -= elemSize;
}
function startNewPage() {
cycle++
parseElement(selector,parts, cycle);
}
$.each(content,function(index,v){
var elem = $(v),
tag = elem[0].tagName.toLowerCase(),
elemSize = elem.outerHeight(true);
if (elemSize <= spaceLeft) {
addToPage(elem,elemSize);
} else if (elemSize > spaceLeft) {
startNewPage();
}
});
}
Question
How can I parse all the HTML text, even unwrapped text nodes, preserving structure and order?
Update
Thanks for help, case solved, only possible way to measure text node height is to wrap it, here is code how to achieve that:
$(selector).contents().filter(function(){
return this.nodeType === 3
}).wrap('<span />');
and here is fully working example:
http://jsfiddle.net/acrashik/aDm8L/20/
thanks everyone.
The problem you are facing comes from using jQuery.children():
Note also that like most jQuery methods, .children() does not return
text nodes;
Instead, you should use the native DOM property, childNodes which does contain text nodes:
cc[0].childNodes;
Or, better yet, pass in the reference to the DOM node:
function parseElement(element, parts, cycle) {
var cc = $(element),
content = element.childNodes,
total = content.length,
maxHeight = cc.height(),
spaceLeft = maxHeight,
cycle = cycle || 0;
//Your other functions down here...
}
And call like:
parseElement(document.getElementById('source2'), ['#part3','#part4']);
Notice You'll need to revisit your other methods to accommodate these changes.
You can use css to split into several columns. I don't know your targeted browsers but here is the code (only on modern browsers) :
html :
<div class="columns">
very long text split into 4 columns
</div>
css :
.columns {
-webkit-columns: 4;
-moz-columns: 4;
columns: 4;
}
sample here : http://codepen.io/raphaelgoetter/pen/ehfxb
I want to know if we can change tag name in a tag rather than its content. i have this content
< wns id="93" onclick="wish(id)">...< /wns>
in wish function i want to change it to
< lmn id="93" onclick="wish(id)">...< /lmn>
i tried this way
document.getElementById("99").innerHTML =document.getElementById("99").replace(/wns/g,"lmn")
but it doesnot work.
plz note that i just want to alter that specific tag with specific id rather than every wns tag..
Thank you.
You can't change the tag name of an existing DOM element; instead, you have to create a replacement and then insert it where the element was.
The basics of this are to move the child nodes into the replacement and similarly to copy the attributes. So for instance:
var wns = document.getElementById("93");
var lmn = document.createElement("lmn");
var index;
// Copy the children
while (wns.firstChild) {
lmn.appendChild(wns.firstChild); // *Moves* the child
}
// Copy the attributes
for (index = wns.attributes.length - 1; index >= 0; --index) {
lmn.attributes.setNamedItem(wns.attributes[index].cloneNode());
}
// Replace it
wns.parentNode.replaceChild(lmn, wns);
Live Example: (I used div and p rather than wns and lmn, and styled them via a stylesheet with borders so you can see the change)
document.getElementById("theSpan").addEventListener("click", function() {
alert("Span clicked");
}, false);
document.getElementById("theButton").addEventListener("click", function() {
var wns = document.getElementById("target");
var lmn = document.createElement("p");
var index;
// Copy the children
while (wns.firstChild) {
lmn.appendChild(wns.firstChild); // *Moves* the child
}
// Copy the attributes
for (index = wns.attributes.length - 1; index >= 0; --index) {
lmn.attributes.setNamedItem(wns.attributes[index].cloneNode());
}
// Insert it
wns.parentNode.replaceChild(lmn, wns);
}, false);
div {
border: 1px solid green;
}
p {
border: 1px solid blue;
}
<div id="target" foo="bar" onclick="alert('hi there')">
Content before
<span id="theSpan">span in the middle</span>
Content after
</div>
<input type="button" id="theButton" value="Click Me">
See this gist for a reusable function.
Side note: I would avoid using id values that are all digits. Although they're valid in HTML (as of HTML5), they're invalid in CSS and thus you can't style those elements, or use libraries like jQuery that use CSS selectors to interact with them.
var element = document.getElementById("93");
element.outerHTML = element.outerHTML.replace(/wns/g,"lmn");
FIDDLE
There are several problems with your code:
HTML element IDs must start with an alphabetic character.
document.getElementById("99").replace(/wns/g,"lmn") is effectively running a replace command on an element. Replace is a string method so this causes an error.
You're trying to assign this result to document.getElementById("99").innerHTML, which is the HTML inside the element (the tags, attributes and all are part of the outerHTML).
You can't change an element's tagname dynamically, since it fundamentally changes it's nature. Imagine changing a textarea to a select… There are so many attributes that are exclusive to one, illegal in the other: the system cannot work!
What you can do though, is create a new element, and give it all the properties of the old element, then replace it:
<wns id="e93" onclick="wish(id)">
...
</wns>
Using the following script:
// Grab the original element
var original = document.getElementById('e93');
// Create a replacement tag of the desired type
var replacement = document.createElement('lmn');
// Grab all of the original's attributes, and pass them to the replacement
for(var i = 0, l = original.attributes.length; i < l; ++i){
var nodeName = original.attributes.item(i).nodeName;
var nodeValue = original.attributes.item(i).nodeValue;
replacement.setAttribute(nodeName, nodeValue);
}
// Persist contents
replacement.innerHTML = original.innerHTML;
// Switch!
original.parentNode.replaceChild(replacement, original);
Demo here: http://jsfiddle.net/barney/kDjuf/
You can replace the whole tag using jQuery
var element = $('#99');
element.replaceWith($(`<lmn id="${element.attr('id')}">${element.html()}</lmn>`));
[...document.querySelectorAll('.example')].forEach(div => {
div.outerHTML =
div.outerHTML
.replace(/<div/g, '<span')
.replace(/<\/div>/g, '</span>')
})
<div class="example">Hello,</div>
<div class="example">world!</div>
You can achieve this by using JavaScript or jQuery.
We can delete the DOM Element(tag in this case) and recreate using .html or .append menthods in jQuery.
$("#div-name").html("<mytag>Content here</mytag>");
OR
$("<mytag>Content here</mytag>").appendTo("#div-name");
Update: I just narrowed my problem to this:
Why doesn't this work:
var tmp = document.createElement('tbody');
tmp.innerHTML="<tr><td>hello</td></tr>";
tmp is getting the string hello. the tr and td html is lost (on FireFox).
Why is that? and how can I make such html injection work?
Original question:
I need to inject arbitrary HTML after a arbitrary element in arbitrary HTML documents.
I came across this method (inject the html string into dynamically generated div, get its firstchild element and insert it in the right place):
var tmp = document.createElement('div');
tmp.innerHTML = _injected_html;
var new_w = tmp.firstChild;
var parent = insertion_point.parentNode;
parent.insertBefore(new_w, insertion_point.nextSibling);
The problem is that this does not work when trying to inject table elements.
if the injected html is for example
"<tr> <td> table data </td> </tr>"
The _tmp.innerHTML = _injected_html; would not accept it (adding tr under div element).
Any idea how to make this work for any tag?
Are you testing in IE by any chance? Most likely it does work in other browsers.
Here's why
edit: Wait, you're inserting something into the table that looks like <div><tr><td>... that's not going to work. Why don't you replace the document.createElement('div') by document.createElement('tr'), and remove the <tr> tags from the _injected_html?
Something like this (tested in Firefox):
<script>
var i = 3;
function f() {
var table = document.getElementById('someTable');
var children = table.children[0].children;
var after = children[Math.round(Math.random() * (children.length - 1))];
var html = "<td>" + i++ + "</td>";
g(html, after);
}
function g(_injected_html, insertion_point) {
var tmp = document.createElement('tr');
tmp.innerHTML = _injected_html;
var new_w = tmp.firstChild;
var parent = insertion_point.parentNode;
parent.insertBefore(new_w, insertion_point.nextSibling);
}
</script>
<table id="someTable" onclick="f();">
<tr><td>1</td></tr>
<tr><td>2</td></tr>
</table>
The second line of f() is a little awkward, but it gets the first child of the table (which is a <tbody>, and then its children (the actual <tr>s).
<div><td><lol/>
..isn't valid HTML! Containers are required for table rows/cols/heads, list items, definition lists and so on. Could you somehow validate the HTML for proper containers before injecting it?
The following javascript will allow you to inject HTML/etc into the local page:
var example = "<p>test</p>"
document.body.appendChild(example);
That said, you will have to customize the code depending on what you are inserting.
For a table, you must insert tr's into tbody. When you write html
<table><tr><td>abc</td></tr></table>
IE, FF, Chrome, Safari at least (don't know about others directly) will modify this to be:
<table><tbody><tr><td>abc</td></tr></tbody></table>
Therefore, something like:
var tmp = document.createElement('tr');
tmp.innerHTML = "<td>def</td>";
var new_w = tmp.firstChild;
var parent = insertion_point.parentNode;
parent.insertBefore(new_w, insertion_point.nextSibling);
if insertion_point is a tr tag.
But honestly, with Jquery there are more elegant ways of going about this as christina toma notes.