Yesterday I read about a new paragraph linking system the New York Times has added to their site. The article linked to in the previous sentence describes it, but the gist is that you can add hash marks to focus in to a specific paragraph or sentence and do highlighting as well. This is really cool as it lets people linking to a story specifically call out a certain section. During lunch I decided to see if I could build something similar in jQuery. I specifically wanted to build a completely client side solution that did not - in any way - impact the textual data being served up from the CMS. I assume the NYT did the same. Here is what I came up with. It currently only supports paragraph selecting and highlighting, but I've got an idea on how to handle sentences as well. I wrote this very quickly so I'm sure it could be done much better.
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script>
$(document).ready(function() {
//get anchor
console.log('loaded');
if(document.location.hash != '') {
var hash = document.location.hash.substr(1,document.location.hash.length);
var type = hash.substr(0,1).toLowerCase();
//ok, so if hash is PN, go to paragraph N
if(type == "p" || type == "h" ) {
var pindex = hash.substr(1, hash.length);
pindex = parseInt(pindex);
if(isNaN(pindex)) return;
console.log('going to load paragraph '+pindex);
//now find it
var para = $("p:nth-child("+pindex+")");
var top = para.position().top;
console.log(top);
window.scrollTo(0, top);
if(type == "h") para.addClass("highlight");
console.log('done');
}
}
})
</script>
<style>
.highlight {
background-color: yellow;
font-weight: bold;
color: red;
}
</style>
The code begins by checking to see if we even have a hash in the URL. If we do, we get the value. Notice that I remove the first character since document.location.hash includes the # within it. (Seems a bit silly, but oh well.) Next I get the first character. A "p" implies we are just going to scroll to a particular paragraph while "h" implies scrolling and highlighting as well. I do a bit of parsing to get the value after the first letter and call it my pindex. This is the Nth paragraph I want to use.
Once we have that - we can then easily use the nth-child selector in jQuery to find that particular paragraph. I don't have logic to check to see if the paragraph requested is more than the actual number of paragraphs, but I figure that's ok. I grab it's Y value by calling position() on the item and getting the top value. Then I simply use the scrollTo function of the window. The final piece to this is to use an addClass() if we were doing a highlight and not just a scroll. So how well does it work? Here are a few examples.
As I said - I wrote this very quickly - and I'm sure the NYT's system is much better, but it was pretty fun to create this myself in jQuery. Tomorrow I'm going to try attacking the sentence specific selecting/highlighting.
Archived Comments
Using FF3.6 / WinXP at work, it's jumping to the appropriate paragraph, but that's it. No highlights, etc.
So all 3 work, but the 3rd one doesn't highlight? If so - could you do a quick test of just addClass? That's a fairly simple jQuery call. I'd be surprised if it didn't work right.
What I meant to say is none of your demos are highlighting anything. There's no scrolling, but it does jump to the appropriate paragraph (because that's already built into the browser, if you specify a #id and it's the same name as an id on the page, the browser will already jump to that section). Let me see if I can debug what's going on. :|
The highlighting is working now, whatever you fixed. :)
Alright, if I leave firebug open, your code works. If I close firebug, your code doesn't work. I guess all the console.logs() are screwing things up.
For me same, nothing happens in FF unless I activate Firebug. Chrome works flawlessly (winXP).
Before Firefox 4 there was no native console. You only got that via Firebug. In general haveing console calls in production or publication code is a bad idea.
What happens if the document has an element with id="p1"? An easy check if the given hash is already known to the document could be: $(':target').length
does not work in firefox
Hi Raymond,
The demos doesn't work totally and when i tried to trace your demos code, the following error message appeared "console is undefined".
I don't know if this is the main problem that prevents the code to be executed.
Thanks for your important and helpful posts, I'm using them as my tutorials :)
jQuery(function($){
var a=location.hash.length>0?location.hash.replace(/\#/g,''):null,index=0,$p=$('p'),y=0;
if(a!==null){
index=((a.replace(/\D/g,''))-1);
y=$p.eq(index).addClass('highlight').offset().top;
$('html, body').animate({"scrollTop":y}, 250);
}
})
it's probably better to use the .eq() method rather than the nth-child selector, since the nth-child selector will return ALL paragraphs that are the nth-child of their parent. In addition, I did not do all the checking you had for highlight vs. scrollto...I just simply set it to scroll the window to the paragraph and highlight it by adding the class.
Hi Ray -
Yes - I concur with Ahmed - I get the "console undefined" issue - running IE8 on XP
Guys - sorry - I thought all web developers used a 'good' browser with console support. ;) I've removed the console messages.
@rodney - hashes don't work with IDs, they only work with <a name=""></a> pairs, so an existing ID of p1 wouldn't matter (afaik).
@David Young: My thought was that there would normally be a greater div around article content. Like <div id="article"> and that you would simply add that to your main selector. I had thought about doing the animation - but I wanted it more immediate. Personal preference I guess. ;)
Ray, hashes do work with ids. You can anchor directly to an id on the page. Case in point, I have an id called 'footer' on my about page ( http://web-rat.com/about/#f... ). The browser will attempt to scroll down to footer.
If it doesn't work, don't maximize the browser to the fullest, shrink it a little bit and make sure the scrollbar is active and try it.
@Raymond
I agree there would likely be a containing parent element, but in cases where something like this might be implemented on a complex CMS where there are multiple people making edits, you would never want to take anything for granted. Even though there might be a containing div that you use as your base, nth-child will still return all p elements that are the nth-child of their container, so if your containing div #article, then had subsequent elements containing p's (such as generic divs, blockquote elements, etc), nth-child would still return all of those. You would have to assume that your containing element would be scoped only to it's immediate children #article > p:nth-child.
Another example, I can link directly to a id on your own site:
http://www.coldfusionjedi.c...
@Todd: Very cool - I didn't know that. Has it _always_ worked or only more recently?
@David: My thinking was that in the CMS, your output would be something like this (this is ColdFusion,but would apply anywhere): <cfoutput><div id="article">#fromdb#</div></cfoutput> So in other words, only the text of the article is in the main div. No other layout, etc.
But yeah - I get your point. Now I'm just being difficult. :)
@Ray: Honestly, I don't know. I do know that it's not a new addition, but I don't know when about this came to be a feature. I do know that majority of all the modern browsers support it. I stopped using <a name=""> ages ago.
@Ray
Using the hash to link to an id has worked for years and years. Neither XHTML or HTML5 have the name attribute on the anchor element. Almost all modern websites use Id, even your blog does for the direct comment linking. ;)
HTML4 specified that id should work this way (in 1999):
http://www.w3.org/TR/html4/...
Thanks @Todd, @Elliott. Every time I build a simple FAQ I end up using <a name> tags. No more!
I threw together a script that highlights paragraphs or sentences and jumps to a specified paragraph. Parts of it are ugly, but it works. It's at https://gist.github.com/727261
Nice work! How about the following to highlight sentences?
// add span tags around all sentances in all paragraphs ( . ! <end of line> are the delimiters )
$("p").each(function() {
var s = $(this).html();
s = s.replace(/(.*?)(\.|!|$)/gm,"<span>$1$2</span>") ;
$(this).html(s);
});
// highlight para2, sentence3
$("span:nth-child(3)",$("p").eq(2-1)).addClass("highlight") ;
This is very cool. I went a couple steps further and made it scroll to individual sentences and highlight them like NYT. https://github.com/drewwell...
I used kir's idea for finding out the position of a sentence in a paragraph: https://github.com/kir/js_c...
Hope you don't mind, I'll probably blog about this (not that mine gets any traffic). I found a really cool feature of regexp.replace via the Mozilla docs https://developer.mozilla.o...
Well shoot, now I don' thave to write the sentence part. ;)
I tried the NYT one and it didn't work. Yours did. So nice work. A good simple idea.
This is really cool. How would you go about getting the value of "document.location.hash" in ColdFusion (if you wanted to do something similar on the server side using CFML before returning the .cfm page)? I can't seem to find this value anywhere (URL, CGI, etc.).
Unfortunately the hash value is not available. It is only available client side. You could - of course - do a 2 step process. Step one is request foo.cfm#moo and return HTML that includes JS to get the hash. You then do an Ajax request to pass the hash to CF. I can't imagine what you would use it for server side though.
Thanks for the reply! That's odd to me. I wonder why we don't have access to it. One case where you might want to know this during the request on the CF server side is if you wanted to perform the same kind of functionality for browsers that can't support javascript and where you just return html styled with the highlights in place.
In other cases, you might have *long* list of items that have named anchors and you might want to know as you are rendering the .cfm page during the request if the incoming link contained an anchor so you could highlight the section the users is trying to see (and not just have the browser scroll to it-without javascript).
Anyway, nice work on this!
I wonder if Adobe could come up with a way to enforce access="public" or access="package" for AJAX requests.
I know that AJAX is stateless, but still...
Maybe they could come up with a standard that says "if access=package, then there's an extra parameter that has to match a _something_ found in the same folder", like a checksum of the calling routine or _something_.
@PSenn: I don't understand your request. What do you mean by 'enforce access=public'? You want access=remote for your remote (ajax/flash remoting) methods.
That's just it - I want access=package for my ajax methods.
For security purposes of course.
I suppose I can roll my own and require one of the parameters to contain a password of sorts.
Something that only the calling function knows.
I must not get what you mean. If you call method FOO on a CFC via Ajax, then FOO an call a package-access level method in another CFC in the same folder. The _initial_ method must be REMOTE. That's a good thing. But after that normal access rules apply.