[jQuery] Re: DOM friendly link creation
Thanks for the tips, but I'm working as part of a larger design team and am constrained with regards to making server-side changes, as for the inline link generation, that is what was requested and so it's what I must deliver (I will be building in a cookie check though so the option to turn it off client side will at least be in the code). I found some code at http://dossy.org/archives/000338.html which showed promise, and I did manage to build a version of the highlighter that is based around it, but I can't deploy this version because a) Doesn't work in Internet Explorer b) EXTREMELY slow! In some cases it will cause FireFox to throw up the warnign about excessive script exexution time. The only other solution I've got is to add code into the highlighter script that checks for the presence of incompatible scripts, the highlighter will just refuse to run if it finds one. Very inelegant, but it's better than what happens if the two scripts try to run together. Anyway, here's the latest version of my code, based on the highlighter from the above link: var jargonEnabled = true; var disableCaching = true; var jDivOffsetX = -10; var jDivOffsetY = 12; var jargonElems = '#content *'; var regexOptions= 'gi'; var ignoreTags = 'a'; function newJargonEntry (xmlNode) // Create an entry for this item in the jargonTerms array { var newTerm = null; if (thisDesc = xmlNode.getElementsByTagName ('description')[0]) { newTerm = new Object (); newTerm ['term']= xmlNode.getAttribute ('term'); newTerm ['href']= xmlNode.getAttribute ('href'); newTerm ['description'] = thisDesc.firstChild.data; } return (newTerm); } function insertTerm (thisTerm) { // Create a new jargon entry jargonTerms [thisTerm.getAttribute ('id')] = newJargonEntry (thisTerm); // Do Search and Replace searchTerm = new RegExp ('', ''); searchTerm.compile ('(' + thisTerm.getAttribute ('regex') + ')', regexOptions); $(jargonElems).each (function () { if ($(this).children ().size () 0) return; if (($(this).is (ignoreTags)) || ($(this).parent ().is (ignoreTags))) return; var html = $(this).html(); html = html.replace (searchTerm, 'a class=jargonbuster href=' + thisTerm.getAttribute ('href') + ' onclick=window.open (\'' + thisTerm.getAttribute ('href') + '\', \'Jargon\', \'width=647,height=680,scrollbars=yes,resizable=yes\').focus (); return (false); onmouseover=createJargonDiv (this, \'' + thisTerm.getAttribute ('id') + '\'); onmouseout=destroyJargonDiv ();$/a'); $(this).html (html); }); } function insertJargon (xmlObject) // Insert Jargon hyperlinks into content div { // Parse the returned XML if (xmlObject.getElementsByTagName('jargon')[0]) { xmlRoot = xmlObject.documentElement; // Get the jargon terms if (xmlTerms = xmlRoot.getElementsByTagName ('jargonterm')) { $(xmlTerms).each (function () { // Do search and replace on all the children of the target node insertTerm (this); }); } } return (false); } function startJargon () // Event handler for AJAX get { $.get (dataSource, function (response) { insertJargon (response); // Attach mousemove handlers to all generated links $('a.jargonbuster').mousemove (function (evt) { $(ajaxNode).css ('left',(evt.pageX + jDivOffsetX)); $(ajaxNode).css ('top', (evt.pageY + jDivOffsetY)); }); return (false); }); } function createJargonDiv (evt, selectedTerm) { // Node used for displaying help ajaxNode= document.createElement ('div'); ajaxNode.id = 'ajaxNode'; // Child divs for jargon node ajaxNodeTitle = document.createElement ('div'); ajaxNodeText= document.createElement ('div'); ajaxNodeTitle.id= 'ajaxNodeTitle'; ajaxNodeText.id = 'ajaxNodeText'; // Help title ajaxHeadTitle = document.createElement ('h3'); ajaxHeadTitle.id= 'ajaxHeadTitle'; // Associate child nodes with the main div ajaxNode.appendChild (ajaxNodeTitle); ajaxNode.appendChild (ajaxNodeText); ajaxNodeTitle.appendChild (ajaxHeadTitle); // Update div contents $(ajaxHeadTitle).html (jargonTerms
[jQuery] Re: DOM friendly link creation
Sorry, guess I should have been clearer from the start. I really do want a proper fix, something that's truely cross browser and doesn't rely on regex and hacks. But the thing is I'm developing this stuff commercially and I have deadlines to meet. In which case a sticky plaster solution to get the scripts up and running on the site in time will do, while in the meantime I can work on a updated search and replace script that does it properly that I can then add as an incremental rollout. On May 16, 1:20 am, Wizzud [EMAIL PROTECTED] wrote: Actually I thought I grasped the problem fairly well. The bit I might have misunderstood was the relative importance of the Failing that ... part, as I thought you wanted to solve the problem rather than sidestep it. Ah well ... -- View this message in context:http://www.nabble.com/DOM-friendly-link-creation-tf3733609s15494.html... Sent from the JQuery mailing list archive at Nabble.com.
[jQuery] Re: DOM friendly link creation
Gordon wrote: Now elements such as divs can contain more child nodes, or plain text, or a combination of both. What I need to know is how to get at the text of a div with a mixture of text nodes and other HTML elements. Doing $(element).text returns all the text in the child elements as well, which is not what I need. I need to do the search and replace on the plain text inside a node, then recursivly work down through each child node and do likewise (if it's a node of a type we want to do replacement on). I think you should be able to do use a function something like this: processTextNodes = function(element, fn) { var children = element.childNodes; for (var i = 0; i children.length; i++) { var node = children[i]; var type = node.nodeType; if (type == 1) processTextNodes(node, fn); if (type == 3) fn.call(this, node); } } You would call it with the element you want to update and a function which accepts a single text node. So if you wanted to replace every a in the body with an @ and every e with a 3, you could do this, probably inside a $(document).ready() function: processTextNodes(document.body, function(node) { var text = node.nodeValue; text = text.replace(/a/g, @); text = text.replace(/e/g, 3); node.nodeValue = text; }); The processTextNodes function is fairly simple, and you have to be aware that there is no requirement that browsers combine consecutive text nodes into a single nodes. That might not be a big concern, but a bigger one would be that you are receiving each text node independently, so you can't easily deal with text that spans elements. I'm not quite sure what you're going to do, but for instance, if you wanted to replace something in quotes with span class=quotesomething in quotes/span using a regex that recognized the two quotation marks, this technique would fail on embedded elements such as something emimportant/em in quotes, because you would operate on three separate text nodes from this string: something , important, and in quotes. But it's pretty simple, and might get you most of what you need. Cheers, -- Scott
[jQuery] Re: DOM friendly link creation
On May 13, 10:28 am, Gordon [EMAIL PROTECTED] wrote: I had previously written a javascript that scans a div inside a HTML document for keywords and then wraps those words in hyperlinks, as part of adynamic help system. This worked by modifying the innerHTML of the div in question. This approach worked but had a few problems, namely that you had to be careful you didn't accidentally try to wrap links around form elements, tag attributes and other such things, and that it didn't work in Safari. I did a recode recently that made use of the jQuery library. It cut the line count of the script in half but it still worked in fundamentally the same way as before, by doing regex search and replace on the .html() of the target div. I think replacing innerHTML is kludgy. Below is an example that uses a DOM traversal and replaces matching words within text nodes with wrapped words and text. It's pretty straight forward, you need to skip over some nodes (textareas, selects, etc.) that you don't want to modify. I uses a NodeList and adds nodes to it, so it goes backwards over it. Tested in Firefox and Safari, I'll leave IE to you. I'm new to jQuery so it's not jQuery-ish at all, maybe someone else can do that bit. script // Recursive traverse of DOM from node down function highLight(node, txt){ // Don't wrap content of textarea, select, option or button elements // Add any other elements that shouldn't be processed var tagName = node.tagName.toLowerCase(); if ('textarea' == tagName || 'select' == tagName || 'option' == tagName || 'button' == tagName ) { return; } if (node.nodeType == 3) return wrap(node, txt); var kid, kids = node.childNodes; var i = kids.length; while(i--) { kid = kids[i]; (kid.nodeType == 1)? highLight(kid, txt) : wrap(kid, txt); } // Remove unnecessary text nodes if (node.normalize) node.normalize(); } function wrap(txtNode, txt) { var re = new RegExp('\\b' + txt + '\\b'); var s = txtNode.data.split(re); if (s.length 2) return; var p = txtNode.parentNode; var lastEl, tempEl; // Element to wrap text in, could use em, strong, a, etc. var wrapEl = document.createElement('span'); wrapEl.style.color = 'red'; lastEl = document.createTextNode(s[0]); p.replaceChild(lastEl, txtNode); for (var i=1, len=s.length; ilen; i++) { tempEl = wrapEl.cloneNode(true); tempEl.appendChild(document.createTextNode(txt)); p.insertBefore(tempEl, lastEl.nextSibling); p.insertBefore(document.createTextNode(s[i]), tempEl.nextSibling); lastEl = tempEl.nextSibling; } } /script /head body div id=xxHere is some blah text so that I can blah test pmy new blah/pblah function to see if it blah blah works and joins blahdy blah textareaHere is blah text/textarea selectoptionblahoptionblah/select divblah/div divAnd blah. awsome. blah is blah./div tabletrtdblahtdsome blah texttdblah text blah/table /div button onclick= highLight(document.getElementById('xx'), 'blah'); Hightlight blah/button /body -- Rob
[jQuery] Re: DOM friendly link creation
After a day's coding, I have come up with this. It's closer to what I need but the final link creation phase isn't there ye.t (also each link will have attributes that point to a different URL, different event handlers, etc, so don't think they're only going to have a jargonbuser class and nothing else) var jargonEnabled = true; var disableCaching = true; var jDivOffsetX = -10; var jDivOffsetY = 12; var targetId= 'content'; var regexOptions= 'gi'; function newJargonEntry (xmlNode) // Create an entry for this item in the jargonTerms array { var newTerm = null; if (thisDesc = xmlNode.getElementsByTagName ('description')[0]) { newTerm = new Object (); newTerm ['term']= xmlNode.getAttribute ('term'); newTerm ['href']= xmlNode.getAttribute ('href'); newTerm ['description'] = thisDesc.firstChild.data; } return (newTerm); } function insertJargonLinks (thisNode, thisTerm) { switch (thisNode.nodeName.toLowerCase ()) { // If this node is plain text then do search and replace on it case ('#text') : temp = thisNode.data; temp = temp.replace (thisTerm, 'a class=jargonBuster$1/a'); thisNode.data = temp; // This only spits the raw HTML into the textnode, which is not what we really want but will do for testing. break; // If this node is one of the following types then skip it case ('!') : // IE 5.5 case ('#cdata-section') : case ('#comment') : case ('#document') : case ('#document-fragment') : case ('a') : case ('button') : case ('h1') : case ('h2') : case ('option') : case ('script') : case ('select') : case ('style') : case ('textarea') : break; // If this node has children then process them recursivly default : if (thisNode.hasChildNodes ()) { $(thisNode.childNodes).each (function () {insertJargonLinks (this, thisTerm);}); } break; } } function insertJargon (xmlObject) // Insert Jargon hyperlinks into content div { // Parse the returned XML if (xmlObject.getElementsByTagName('jargon')[0]) { xmlRoot = xmlObject.documentElement; // Get the jargon terms if (xmlTerms = xmlRoot.getElementsByTagName ('jargonterm')) { // Set up RegExp searchTerm = new RegExp ('', ''); $(xmlTerms).each (function () { // Create a new jargon entry jargonTerms [this.getAttribute ('id')] = newJargonEntry (this); // Prepare keyword search searchTerm.compile ('(' + this.getAttribute ('regex') + ')', regexOptions); // Do search and replace on all the children of the target node $(document.getElementById (targetId).childNodes).each (function () { insertJargonLinks (this, searchTerm); }); }); } } return (false); } function jargonDiv (selectedTerm) // Manage the jargon buster div { if (!document.getElementById ('ajaxNode')) { // Hide the div closeJargonDiv (); // Update div contents ajaxHeadTitle.innerHTML = jargonTerms [selectedTerm]['term']; ajaxNodeText.innerHTML = jargonTerms [selectedTerm]['description']; // Show the div document.body.appendChild (ajaxNode); } return (false); } function closeJargonDiv () // If the div is already visable, then hide it { try { document.body.removeChild (ajaxNode); } catch (e) {} return (false); } function doJargon
[jQuery] Re: DOM friendly link creation
On May 17, 1:49 am, Gordon [EMAIL PROTECTED] wrote: After a day's coding, I have come up with this. It's closer to what I need but the final link creation phase isn't there ye.t (also each link will have attributes that point to a different URL, different event handlers, etc, so don't think they're only going to have a jargonbuser class and nothing else) It goes without saying that this type of thing is much better done on the server, but anyhow I'll suggest an alternative strategy. Consider allowing the user to select text themselves. When your document.body receives a mouseup event, it looks to see if a range is selected. If so, look at the words selected and if any match your jargon dictionary, display a help button next to the selection. If the user clicks on the button, display help for the recognised words. If they click anywhere else, clear the button. This way, you can provide your own glossary for your jargon, anthing not in your dictionary could be sourced from some online dictionary or tossed into Google and presented in a new window or similar. It requires no client-side processing of the page and you don't have to find and highlight all the jargon words - I find highlighted words very distracting, especially since I only need to learn about a jargon word once (or maybe twice on a slow day), I don't want the word repeatedly highlighted just because you think I might need that. Anyhow, over to you. -- Rob
[jQuery] Re: DOM friendly link creation
On May 13, 10:21 am, Wizzud [EMAIL PROTECTED] wrote: Sounds to me like you should be considering rewriting your wrapping code to traverse the dom tree below the target div, rather than treating the whole inner html as a single element. This would solve your problem of not wrapping things like form elements, etc, and would mean that you were actually doing lots of replaces but only on text nodes, which (presumably) has less chance of clashing with your animations. Also, any changes become progressive rather than all-at-once. Since you don't give any indication of what these animations might be, or why they are needed to run from page load, its difficult to determine the level of interference. Thanks for the reply, but I don't think you've quite understood the problem I'm having. I have a senario where there are two functions attached to $ (document).ready - One function sets teh AJAX request for keywords in motion, the other one starts animations running on the page. The problem occurs when the AJAX request completes and the div innerHTML replace occurs while the animation is part way through the process of running. All kinds of nasty side-effects occur when this happens, in all my test browsers. The most obvious one is that the animations stall before completing. I also seem to get other odd side effects, like the .ready event seems to keep firing over and over and sends the browser CPU usage through the roof until I unload the page. As these problems happen in all browsers I'm assuuming it's my fault. I think messing around with the .html or .text properties of the nodes directly would just cause the above described behaviour to manifest itself again. even if it didn't then I doubt it'd work in safari. Granted the current version doesn't either but that is a problem I would love to solve. I suspect doing proper DOM traversal and node creation is the right way to go but I don't know if your posted code is the way to go. What I think I need to do is: If the node is a text node 1) Locate a keyword inside a text node. 2) If I find it, then create a new text node that contains everything up to the match. 3) Create a hyperlink node after the first text node that contains a text node rquivilant to my match. 4) create another text node after the hyperlink node that contains all the text that follows on from after the keyword to the end of the text node. 5) Set the newly created hyperlink's href, events, etc Repeat the process on the newly created text node that follows the newly created hyperlink so that I catch all occurances If the node is something other than a text node and is not a node of a type I don't want to do matching inside (hyperlink, form control, script etc) then examine the children of that node recursivly and do the hyperlink creation process on them. However, there is also time pressure and until I can rewrite the code to work in a proper DOM friendly way, a simple fix to stop the two scripts from tripping over each other will do. As the innerHTML replacement during an animation seems to trigger the problem what I need is something like this: Each script checks for the other's presence. If the animation script fails to find the innerHTML script then create your own document.ready event. If you do find the innerHTML script then do nothing. If the innerHTML script finds the animation script then create a ready that fires the ajax process and which on completion does the search and replace first, then starts the animations once this is complete. If it doesn't find the animation script then it simply does the AJAX stuff on document.ready.
[jQuery] Re: DOM friendly link creation
Actually I thought I grasped the problem fairly well. The bit I might have misunderstood was the relative importance of the Failing that ... part, as I thought you wanted to solve the problem rather than sidestep it. Ah well ... -- View this message in context: http://www.nabble.com/DOM-friendly-link-creation-tf3733609s15494.html#a10633676 Sent from the JQuery mailing list archive at Nabble.com.