[jQuery] Re: DOM friendly link creation

2007-05-17 Thread Gordon

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

2007-05-16 Thread Gordon

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

2007-05-16 Thread Scott Sauyet


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

2007-05-16 Thread RobG



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

2007-05-16 Thread Gordon

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

2007-05-16 Thread RobG


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

2007-05-15 Thread Gordon

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

2007-05-15 Thread Wizzud


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.