PDA

View Full Version : manipulating css using javascript and the base html statement



casaschi
07-16-2010, 07:23 PM
Hello,

I came across a very odd browser behavior when trying to modify a css class using javascript and at the same time having a base html statement in my html file.

Without the base html statement, all browsers work fine and I can change the css class definition using javascript easily.
With a base html statement, only FireFox still works while Internet Explorer and Google Chrome dont work anymore. If there is a cross-domain issue, while one browser does work and the others dont?

An example of what I'm talking about, with the base statement:
http://freebsdcluster.org/~casaschi/tmp/example-base.html
Without the base statement:
http://freebsdcluster.org/~casaschi/tmp/example-nobase.html

Any idea how to tweak the code in the case with the base html statement in order for the javascript to work with all browser (modifying the class definition) ???

Keep in mind, I dont want a suggestion of another way to change the color, this is only an example. I want to be able to manipulare css classes with javascript when a base html statement is in my html code.

This is essentially the code:


<!--
-->
<base href='http://www.google.com'>

<style id='myStyle' type='text/css'>

.myStyle {
color: red;
}

</style>

<script type="text/javascript">

var lastColor = 'red';
function toggleColor() {
lastColor = lastColor == 'red' ? 'green' : 'red';
myObject = document.getElementById('myStyle');
if (myObject.sheet) { myObject.sheet.cssRules[0].style.color = lastColor; } // Mozilla style
if (myObject.styleSheet) { myObject.styleSheet.rules[0].style.color = lastColor; } // IE style
}

</script>

<a href="javascript: toggleColor();"><span class=myStyle>click here to toggle color</span></a>

Thanks in advance...

jscheuer1
07-16-2010, 09:31 PM
IE 8, in both IE 8 mode and IE 7 mode, it works fine with both pages here. Chrome does not, as you say, failing on the page with the base tag.

However, this is not the way these things are done.

What one generally does is to change the scripted .style.color property of the element itself, ex:


document.getElementsByTagName('div')[0].style.color = 'red';

Or collectively change/toggle the class name of all the elements. This approach (though there are other variations) generally requires having two identical classes defined, except for the color or whatever thing(s) you want to change with the class change. The jQuery library has easier and more precise methods available for this. But in plain javascript, one can do:


var els = document.getElementsByTagName('*'), i = els.length - 1;
for (i; i > -1; --i){
if(els[i].className === 'oneclass'){
els[i].className = 'theotherclass';
} else if(els[i].className === 'theotherclass') {
els[i].className = 'oneclass';
}
}

With additional coding and/or a slightly different approach in defining the two classes' rules, one can be more precise in plain javascript as well.

casaschi
07-16-2010, 10:55 PM
However, this is not the way these things are done.

You are missing the point.

The code I posted is just a very basic example to point to the issue.
As part of a more complex context, it would be very beneficial to me to change dynamically the definition of a class, rather than reassigning all elements to another class.

Regardless of other ways to skin this cat, it appears that the code I posted is a valid javascript code that is executed significantly different by different browsers... does anyone know which browser behaves correctly (i.e. is the operation legal as assumed by FF or somehow forbidden as assumed by GC)?

In other words, either there is something intrinsically wrong with my code (and saying that things should be done differently is not enough to me) or something is wrong with some browser... but which is which?

jscheuer1
07-17-2010, 12:10 AM
I don't think I missed the point. However, you are entitled to your opinion.

As a partial answer to your (what seems to me to be a) new question, I do know that security on GC is tighter for some things than in other browsers. This could account for its behavior here. And/or it could be a bug*.

My point is that there are perfectly good ways to accomplish your aims that probably will not suffer from these issues, and that at the same time are cross browser without having to resort to two different approaches.

If you wish to cling to altering style blocks, something I've never seen used for this. Feel free. It may be able to be worked out. I doubt it though I could be wrong.

At the same time, I believe that however complex your intentions are, they can be achieved by more established methods.



*Based upon observations in this thread:

http://www.dynamicdrive.com/forums/showthread.php?p=230504#post230504

casaschi
07-17-2010, 08:58 AM
I don't think I missed the point. However, you are entitled to your opinion.

Well, you are missing the point. In fact, copied verbatim from my initial post, this is the point you are missing:


Keep in mind, I dont want a suggestion of another way to change the color, this is only an example. I want to be able to manipulare css classes with javascript when a base html statement is in my html code.

jscheuer1
07-17-2010, 10:00 AM
And I gave you a way to manipulate css classes . . .

Just not the way you apparently want.

Have a nice day.

casaschi
07-17-2010, 10:11 AM
And I gave you a way to manipulate css classes . . .

Just not the way you apparently want.

this definitely confirms that you are missing the point of my initial request completely

jscheuer1
07-17-2010, 11:21 AM
Not in my opinion. But I'm tired of arguing that point. Here's a workaround (may need further tweaking):


function toggleColor() {
lastColor = lastColor == 'red' ? 'green' : 'red';
myObject = document.getElementById('myStyle');
if (myObject.sheet) { // Mozilla style w/kludge to overcome base href in browsers that cannot read cssRules (Chrome, Safari, perhaps others)
var base = document.getElementsByTagName('base'), baseHref;
if(base.length && !myObject.sheet.cssRules){
baseHref = base[base.length - 1].href;
base[base.length - 1].href = '#';
}
myObject.sheet.cssRules[0].style.color = lastColor;
if(base.length && typeof baseHref !== 'undefined'){
base[base.length - 1].href = baseHref;
}
}
if (myObject.styleSheet) { myObject.styleSheet.rules[0].style.color = lastColor; } // IE style
}

However, the base tag can present other problems. Try it in Firefox with:


<base target="_blank">

What fun!

That can be fixed by avoiding firing of this:


<a href="javascript: toggleColor();" . . .

like so:


<a href="javascript: toggleColor();" onclick="toggleColor(); return false;">

Yeah it needed tweaking:


function toggleColor() {
lastColor = lastColor == 'red' ? 'green' : 'red';
myObject = document.getElementById('myStyle');
if (myObject.sheet) { // Mozilla style w/kludge to overcome base href in browsers that cannot read cssRules (Chrome, Safari, perhaps others)
var base = document.getElementsByTagName('base'), baseHref;
if(base.length && !myObject.sheet.cssRules){
for (var i = base.length - 1; i > -1; --i){
if(base[i].href){
baseHref = [i, base[i].href];
base[i].href = '#';
break;
}
}
}
myObject.sheet.cssRules[0].style.color = lastColor;
if(baseHref){
base[baseHref[0]].href = baseHref[1];
}
}
if (myObject.styleSheet) { myObject.styleSheet.rules[0].style.color = lastColor; } // IE style
}

casaschi
07-17-2010, 12:55 PM
Here's a workaround (may need further tweaking)

The idea of temporarily resetting base.href might actually work.

Thanks for the suggestion!

jscheuer1
07-17-2010, 02:22 PM
Oh, it works alright. The second version is to be preferred in case of something like so (and other variations, technically invalid, but can be done):


<base href='http://www.google.com'>
<base target="_blank">

where it is essential to get the correct base tag.

I still think that your objectives can be achieved via more established methods. If you would be willing to use the jQuery script library, this could perhaps be easier and more precise perhaps than what you are trying here.

With it you can target an entire class of elements like so:


$('.someclass').css({color: 'red', backgroundColor: '#fff'});

You can include as many css property/value pairs as you like. They can even be determined on the fly by variable assignments. Here's your example translated (base will have no effect upon it):


<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<style type='text/css'>

.myStyle {
color: red;
}

</style>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
<script type="text/javascript">
jQuery(function($){
var ms = $('.myStyle');
$('#toggler').toggle(function(){ms.css({color: 'green'});}, function(){ms.css({color: 'red'});}).click(
function(e){e.preventDefault();});
});
</script>
</head>
<body>

<a id="toggler" href="javascript: toggleColor();"><span class=myStyle>click here to toggle color</span></a>

</body>
</html>

casaschi
07-17-2010, 02:46 PM
Oh, it works alright.

Identifying the correct base statment is not the issue.
I control the html file and I can add a single base statement, or even add an ID to the base statement, so there wont be any issues there.

However, I've been too quick to rejoy... while Chrome is fixed by your trick of resetting the base href, still it does not work on IE7 (even when applying your trick to the styleSheet / rules branch of the code). Even after deleting the base element object alltoghether, IE7 stops executing the code somewhere when modifying the CSS sheet (still works if there is no base statement to start with).

azoomer
07-18-2010, 03:14 PM
casaschi !
With the risk of getting my head bitten off, I can tell you my observation on this issue.
When I change the base url to be within the same domain as the document, the javascript you have written works in all browsers, including chrome , IE6, 7 and 8. So to me it indicates a cross script security issue that some browsers react to. I don't know why you want to use the base tag or if referring to a url within the same domain would defeat your purpose. But anyway, just an observation, no offence.

jscheuer1
07-18-2010, 09:12 PM
When I change the base url to be within the same domain as the document, the javascript you have written works in all browsers, including chrome , IE6, 7 and 8.

Nice catch, azoomer! I haven't tested it, but it makes sense. The only reason to raise a security violation would be if the base href were to a different domain. Even then, it seems a bit of overkill for this functionality. If it's a security issue, which I believe it is, having the base href hard coded to the same domain should fix it.

However, regardless if that actually works in IE 7 and less (where I would be the most skeptical, though it very well could) and casaschi can use a same domain base href for his/her purposes (watch out for www vs not www - though technically the same domain, browsers will think not), these sort of issues are reasons to use more established methods for changing styles. More established methods should also make it easier to keep track of what was changed and how, and to later get it back to the way it was.

Actually editing css stylesheets on the fly is an idea whose time is perhaps coming, though I'm uncertain if it will ever be as efficient as more established methods. It's somewhat of a race (in my mind) between when all browsers will support it without these issues, and when all browsers will support getElementsByClassName(). Most do getElementsByClassName() today. We're just waiting on the ones that don't to die out.

casaschi
07-18-2010, 10:01 PM
Nice catch, azoomer! I haven't tested it, but it makes sense. The only reason to raise a security violation would be if the base href were to a different domain. Even then, it seems a bit of overkill for this functionality. If it's a security issue, which I believe it is, having the base href hard coded to the same domain should fix it.

Unfortunately, I need the base url in a different domain :-(


However, regardless if that actually works in IE 7 and less (where I would be the most skeptical, though it very well could) and casaschi can use a same domain base href for his/her purposes (watch out for www vs not www - though technically the same domain, browsers will think not), these sort of issues are reasons to use more established methods for changing styles. More established methods should also make it easier to keep track of what was changed and how, and to later get it back to the way it was.

You keep insisting on using "more established means" to achieve what I need... well, let me try to explain why I need to change the classes on the fly.

The javascript application is a chessboard displaying games on web pages.

The basic tool is a javascript file doing all the magic and some nice bitmap/fonts files. The web developer prepares the html file and the css files. The css file must contain the definition of a number of classes used by the javascript to set chessboard colors, highlighting squares, moves font and colors and so on. The javascript assumes for instance that the .whiteSquare class defines how a chessboard white square is displayed. The .highlightedWhiteSquare defines how a white square is shown when highlighted. The javascript looks at the game and decides when a square needs to be highlighted, then reassigns the class accordingly.

So far so good, everything done with the established methods.

Then, I decided to provide as part of the package a pre-built html file, a sort of multi-purpose html file that displays a chessboard customized based on url parameters (so usable by non html/javascript/css experts).
For instance, board.html?lightColorHex=FF0000 displays a chessboard with white squares colored as red.

So... the javascript in board.html needs to read the URL parameter and modify the .whiteSquare class accordingly.
I cannot code a differentWhiteSquare class (to switch to in a more established fashion) in board.html because I dont know in advance what the color will be.
I cannot scan the whole board and assign the color to each element that needs it because the javascript will later use the established method of changing appearance by reassigning classes according to events (such as a move is made and a different square has to be highlighted).

Other issue is, I cant really re-code the whole javascript just for this application... and everything really works just nice without a base html statement in the board.html file.

For a very specific application, it would be nice to host board.html on a different domain than the javascript, the bitmaps and the fonts... this is when I noticed the erratic behavior of web browsers:
- firefox works all the time, no much cross domain security issues
- chrome does not work, but can be fooled by temporarily changing the base href to "#"
- IE8 seem to work all the time (I dont have IE8 to test with) while IE7 only works if I start without the base statement, first change the css and then create a base statement
The behaviour is too erratic, IE7 seems hitting a bug and chrome seems confused about cross domain security (why a script in file.html should be forbidden to modify css statements defined within file.html itself).

So... in a nutshell, I already use the established method of reassigning classes to objects according to functions... now I need a mechanism to reset those classes to arbitrary new values.

PS: if you are interested, the chessboard javascript is at http://pgn4web.casaschi.net

jscheuer1
07-18-2010, 10:56 PM
If that's all it is, a one time change of style while the page is loading, you can use document.write().

However, if you have server side code available to you, that could be even better. Or at least it would just make more sense to me.

Anyways, if you are doing this all with javascript, you must already have a way to determine what the query string is and that parses out the variables you need from it. There are several such methods around. Then all you need to do is document.write() a new style section below the existing one as the page is being parsed. This new style section will need to have only the changed property/value pairs for each class, example:


<script type="text/javascript">
document.write('<style type="text/css">\n' +
'.WhiteSquare {\n' +
' background-color: ' + varNameContainingTheValue + ';\n' +
'}\n' +
'<\/style>');
</script>

If you have server side code, you just have it react to the query string and fill in the desired values as it serves the page.

Added Later:

Using document.write in this fashion might still cause a security violation in one or more of these overly zealous browsers. Except I truly doubt that it could if the base tag comes after the script that writes the new styles. The browser has no way of knowing at that point that there will be a base href.

Alternatively, if it's done on the server side, there isn't anything any browser could possibly object to.

azoomer
07-19-2010, 01:33 AM
Casachi ! I just thought I would show you my testpage with newbie attempts at solving the mystery using jquery. As expected, I didn't succeed . In the first two attempts I tried to manipulate the stylesheet, it worked in chrome and FF, but not in IE at all. The last attempt is probably exactly what you don't want, but it worked. Instead of changing the class I added a class that would override only the color. In my mind it sort of comes to the same result in that you have a class named myStyle but with a different color to all the elements with that classname. I haven't exactly missed your point, just can't make it work the way you need it, but it was fun trying.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html><head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<base href="http://google.com/">
<style id="myStyle" type="text/css">
.myStyle {
color: red;
font-weight: bold;
padding: 10px;
text-align: center;
}
.green{
color: green;
}
</style>
<!--
<script type="text/javascript">
$(document).ready(function(){
$('#toggler').click(function(){
var el = $('#myStyle');
el.html(el.html().replace(/color: red;/ig, "color: green;"));
});
});
</script>

<script type="text/javascript">
$(document).ready(function(){
$('#toggler').click(function(){
$('#myStyle').append('.myStyle {color: green;}');
});
});
</script>
-->

<script type="text/javascript">
$(document).ready(function(){
$('#toggler').click(function(){
$('.myStyle').addClass('green');
});
});
</script>
</head>
<body>
<h1>this is with a base statement</h1>

<a href="javascript:void(0)" id="toggler"><div class="myStyle">click here to change color</div></a>
<br><br>
<br><br>
</body></html>

casaschi
07-19-2010, 02:07 PM
If that's all it is, a one time change of style while the page is loading, you can use document.write().

Tried the "document.write" technique... seems working and allowing for the base statement in any browser.
Probably the cleaner solution, changing class rules dynamically is supported very differently by different browsers.