Today was the day of two exams and javascript to get over it. Only the latter is relevant here. For my first couple of hours with the language — although I admit I used it before — I learned quite a lot. It’s like most things: when you start doing it, you actually learn it. Today would not have been a great javascript day without the help of Sjoerd Visscher. Sjoerd is not only my colleague, but also the Wikipedia of javascript, javascript in Internet Explorer and everything.
When I started playing a little I found out about .href
. After alert
ing it, it seemed Mozilla resolved the URI. Wait, let me finish. As this appeared to be correct per the interface of the A
element — I never understood interfaces completely till now — I had to find another way. Fortunately I have some DOM knowledge and I quickly tried .getAttribute('href')
which worked.
After playing around a bit more I fired up Internet Explorer. To my surprise it didn’t work out. All the DOM hype around the web — mostly on weblogs — made me think that IE is pretty good with that, however, .href
apparently equals .getAttribute('href')
in IEs implementation. And quickly .getAttribute('href').substring(9)
was replaced with the ever more ugly regular expression and IE compatible equivalent: .href.match(/comment-(\d+)/)[1]
. Ugh! Did I mention Sjoerd already?
Same thing happened with .setAttribute
. I was trying to set an onclick
event. Here’s what I ended up with: theLink.onclick = new Function("", "replyToComment("+comments[i].firstChild.href.match(/comment-(\d+)/)[1]+")");
. Not a pretty sight. For completeness, here are the functions:
function replyToComment(commentId){ var comment,url,link,metaP,commentAsString,textarea; comment = document.getElementById('comment-'+commentId); url = "http://annevankesteren.nl"; // without slash link = comment.lastChild.firstChild.href.substring(comment.lastChild.firstChild.href.match(/#/).index,url.length); metaP = comment.removeChild(comment.lastChild); commentAsString = comment.innerHTML; commentAsString = commentAsString.replace(/<[^>]+>/g, function(m) { return m.toLowerCase(); }); commentAsString = "<blockquote cite=\""+link+"#comment-"+commentId+"\">"+commentAsString+"</blockquote>"; comment.appendChild(metaP); textarea = document.getElementById('comment'); textarea.value += commentAsString; textarea.focus(); } function addReplyLinks(){ var comments,theLink; comments = document.getElementById('comments'); comments = comments.nextSibling; if(comments.nodeType != 1) comments = comments.nextSibling; comments = comments.getElementsByTagName('p'); for(i=0;i<comments.length;i++){ if(comments[i].className.indexOf('meta') != -1){ theLink = document.createElement("a"); theLink.appendChild(document.createTextNode('↓')); theLink.title = "Reply to " + comments[i].childNodes[0].title.substring(19); theLink.href = "#comment"; theLink.onclick = new Function("", "replyToComment("+comments[i].firstChild.href.match(/comment-(\d+)/)[1]+")"); comments[i].appendChild(document.createTextNode(' · ')); comments[i].appendChild(theLink); } } }
Obviously, this is Beta Production Work™ and still needs some cleanup. For even more thoroughness I might add that addReplyLinks
is loaded in screen by Scott Andrew’s addEvent
handler. Albeit slightly modified.
The reason behind all this is Lars Kasper, who puts a lot of effort in his replies and this is my small ‘javascript enhancement experiment’ to help him with that. If you still haven’t figured out what I changed; don’t you care. These are the days of unobtrusive javascript. Hip, cool and low profile.
I test.
Sounds a lot like every experience I've had with JS/DOM—frustrating.
I truly wish I knew JS better, but every time I need to do something with it I end up never wanting to touch it again.
I test.
Would be cool if it also added two new lines after the closing tag of <blockquote>
.
Attribute values are also converted to lowercase, and you should actually use a charset parameter for script files aswell (and why not style sheets?), especially when you use non-ASCII characters.
Talking about unobtrusive javascript, alerting ugh!
to old browsers doesn't seem very unobtrusive to me... ;)
You now need to add some more scripting to disable the use of e.g. GreaseMonkey and all other kinds of user scripts that may mess around with your DOM ;)
By the way: the most prefered way to attach eventhandlers is ofcourse by using the appropiate method for that: addEventListener (and for IE it's counterpart of the broken IE5 event model attachEvent). setAttribute is not meant to attach scripting data.
But because of the broken propriety event model in IE indeed using the DOM 1 method is the best alternative.
I wrote one of these auto-reply things for my blog a while back but I never got around to implementing it. I quite like your unobtrusive (visually) implementation. Better than the clunky buttons I was using.
Tino is correct- while setting the onclick property “works,” the DOM Event Model is the correct way to do this sort of thing.
IE's JavaScript implementation is just as frustrating as it's implementation of other standards, but you knew that already. Watch out for it's bizarre behavior when you want to stop event propagation when using DOM Events.
If you want to do JavaScript, go get the big O'Reilly book, it's a must-have for anyone starting out with this stuff.
And you're right, JavaScript has come into it's own this year, due in large part (I think) to Google's extensive use of it for it's new applications.
I test.
Welcome to the dark side
:)
zcorpan, I should not and theoretically Apache should do it as I have AddDefaultCharset utf-8
specified, but as it doesn’t and I’m a UTF-8 fanatic I added AddCharset utf-8 .js .css
to be sure and apparently that did the trick. Ugh! I also fixed the alerting thing, which was there mainly for debugging. About attribute values, you are right. Any suggestions?
Tino, that was actually the first thing I tried, but it didn’t work out.
Really cool functionality!
This together with something like Derek's or Jonathan's new commenting functionality would be great!
So shoot me. Apparently AddDefaultCharset
only applies to text/plain
and text/html
.
Anne> Did you try it using the addEvent macro? And by the way, although everyone probably knows about it already, one of the must reads beside the O'Reilly books is the wonderful Quirksmode, I don't think I've seen any other website with that much quality javascript packed at the same place
theLink.addEventListener('click', new Function('replyToComment(\'' + somevalues + '\')'), false);
…should work, but you may want to do this:
theLink.addEventListener('click', replyToComment, false); function replyToComment() { var commentId = this.parentNode.firstChild.href.match(/comment-(\d+)/)[1]; // etcetera }
In regards to IEs broken getAttribute. IE treats element.getAttribute(sAttrName)
as element[sAttrName]
and therefore element.getAttribute("class")
will not work because there is no such property. The end conclusion is to use DOM properties wherever possible.
Javascript as a language is really nice. Just see Sjoerd's Loell. Too bad that there are no standard libraries that does anything useful, besides DOM that is - when it works as supposed which isn't often enough.
To add IE support for addEventListener I came up with this:
/* IE addEventListener * * Adds a wrapper around IE's attachEvent and lets you use addEventListener * It also works around the issue where the normal scope of the event triggered * is the window object instead of the event's target. Unfortunately in bubbling * fase (IE does not support capturing fase) this means that the 'this'-keyword * will always point to the element that triggered the event instead of the current * element in the bubbling fase. That is a shortcoming of IE's event-model, and you * may want to consider to fall back to the DOM 1 model (but then you cannot * dynamically attach multiple functions to the same event on one element, although * you could probably write a wrapper for that too). */ if (window.attachEvent && document.getElementsByTagName) { var el = document.getElementsByTagName('*'), i = el.length; while (i--) { el[i].addEventListener = function(type, funcref, onlyBubbles) { this.attachEvent('on'+type, new Function('attachEventWrapper('+funcref+')')); /* or if you like (works around the 'this'-keyword issue, but doesn't allow multiple functions to the same event on the element): this['on'+type] = funcref; */ } } function attachEventWrapper(funcref) { funcref.apply(event.srcElement); } }
Your function is far too complicated Tino.
Here is the little event handling registerer from Matt Kruse's website.
// Utility function to add an event listener function addEvent(o,e,f){ if (o.addEventListener){ o.addEventListener(e,f,true); return true; } else if (o.attachEvent){ return o.attachEvent("on"+e,f); } else { return false; } }
To use it, just do
// Automatically attach a listener to the window onload, to convert the trees addEvent(object,"event",function_name);
Example:
addEvent(window,"load",processNodes);
masklinn, that function was written once by Scott Andrew, as mentioned in my post.
masklinn: you're missing the point; first of all it is much more 'natural' to use a method on an object than a functioncall. Secondly Scott's method doesn't account for the difference between addEventListener and attachEvent with respect to scope. He even uses capturing on the addEventListener wereas IE's method only supports bubbling - that's also something you should be aware of.
Basically using object.onevent=function_name is better that using this addEvent function because than at least you will have the same behavior in IE.
Also I'm not too font on using the onload handler on the window object. Especially on pages that contain images the onload is triggered only after all the images have loaded, which in many cases is too late and will cause noticable 'jumpy' effects on the page. Don't shy to use some inline scripting here and there; the DOM is already available during page rendering, and some functionality you may want to add to elements as soon as they are available in the DOM. Yes, it pollutes markup, but that doesn't make it obtrusive. (There should actually be some kind of onrenderend event...).
Anne > damn, missed it, shame on me
Tino > About bubbling Vs capturing, the only thing I can tell you is that... nothing stopped me from editing the function to use bubbling in both cases (yes, I was aware of that). About the jumpy effect, I see what you mean and I think I'd agree with you, but I have yet to be annoyed by this specific problem.
Side note: Anne, have you ever thought of putting access keys on your preview/post buttons? Say S on the preview button, for example (IM style)
I test.
Thank you very much! :-)
(A good example for useful JavaScript.)
So shoot me. Apparently
AddDefaultCharset
only applies totext/plain
andtext/html
.
You can use the following to force charsets on other content types:
AddType text/javascript;charset=utf-8 .js
AddType text/css;charset=utf-8 .css
By the way Anne; your javascript generates a strict warning:
Warning: function addReplyLinks does not always return a value Source File: http://annevankesteren.nl/js/comments Line: 49
Using new Function(...)
is a slow way to do it; it has the same performance penalty as eval()
. A better idea would be to simply pass a function — e.g. replyToComment
— and have it deduce whatever information it needs to, as Tino suggested.
Do you need the first empty string parameter in the Function()
constructor? Can't you just write it like this:
theLink.onclick = new Function("replyToComment(" + comments[i].firstChild.href.match(/comment-(\d+)/)[1] + ")");
I at least think you can.
masklin: this is not a good idea:
if (o.addEventListener){ o.addEventListener(e,f,true); return true; }
Do not use capturing event handlers, make the third argument to addEventListener a "false". See http://my.opera.com/hallvors/journal/47
I agree with Aankhen; avoid the Function
constructor if at all possible. (Hint: having been writing JavaScript since it was born in Netscape 2, I've never found a situation where it was necesssary.)
Remember that in an event handler, this
holds a reference to the element on which the event is firing. Therefore you can set the event handler:
theLink.onclick = handleLinkClick;
and then just do the stuff to get the information out of the link using this
; for example:
function handleLinkClick() { alert(this.getAttribute("href")); }
will pop up an alert with the value of the href attribute of the clicked link.
It is a real pain that IE always tacks the protocol and domain on the front of the href
attribute. You might want to be warned that it does the same thing with the img
element's src
attribute.
Good luck with your experiments; as a long-term programmer, I've found JavaScript to be a great language to work with.
Nick, this
sounds useful. (Pun intended for a bit.) If I find some time next weekend (not this) I’ll try it out. Or maybe a little bit earlier. Throughout the week or so.
I'm trying to get this working on my site, but I don't quite seem to manage. I wish I knew more JavaScript. Anyway, here's how my comments list is structured:
<ol id="comment-list"> <li id="comment-1"> <dl> <dt class="gravatar"><img src="/some/gravatar" width="20" height="20" alt="This commenter’s Gravatar" /></dt> <dt><a href="http://domain.ext/" title="Go to domain.ext">Someone</a>:</dt> <dd><p>Blah blah blah.</p></dd> <dd class="comment-posted"><cite><a href="#comment-1" title="Permanent link for comment 1 on This Post" rel="bookmark">Comment posted on May 30th, 2005 @ 6:01 pm</a></cite></dd> </dl> </li> </ol>
I made the following changes to the JavaScript:
url = "http://annevankesteren.nl"; // without
slash
into url = "http://mathibus.com";
,
obviously;
comments =
comments.getElementsByTagName('p');
into comments =
comments.getElementsByTagName('dd');
(see my comments list
structure above);
if(comments[i].className.indexOf('meta') !=
-1){
into
if(comments[i].className.indexOf('comment-posted') !=
-1){
(see my comments list structure above).
Any ideas?
Never mind, I got it working now. Thanks for the email-wise help, Anne!
Never mind, I got it working now. Thanks for the email-wise help, Anne!
Mathias, thanks for sharing this resource, this is one nice snippet of code! I'd love to use this on my site. I'll try to integrate this on the weekend. Thanks Anne.