3

I have a web application that, after clicking any node in the HTML, needs to retrieve the index of that node in its parent's childNodes array. However, I am having trouble getting the currently selected node through an onclick event. The returned target of the event is the containing element rather than the specific node inside the element. This difference is important when text nodes exist, such as:

<div>This is Node 1<span>node 2</span>, node 3, and <span>node 4</span></div>

If you click on the spans for Node 2 or Node 4, it's straightforward to know where you are. However, if you click on the text for Node 1 and Node 3, I can't seem to find where the event would help you figure out which part of the actual content was clicked on.

This happens to be important because a later operation needs to check for certain properties either forward or backward through the document until the first match. So, if both Node 2 and Node 4 are a match for the search, I need to know if I am in Node 1 or Node 3 in order to know which one to return. For example, if searching rightwards, starting in Node 1 means that Node 2 should be returned, and starting in Node 3 means that Node 4 should be returned. Obviously, this is a simplification, but it demonstrates the issue. Does anyone know the canonical solution for this? If I can get the node object or the index, that should be sufficient. jquery is fine, but not necessary.

Namey
  • 1,172
  • 10
  • 28
  • In fiddling with this during and for a while after getting this question out, it seems like the jquery onclick event may have this info via: event.originalEvent.explicitOriginalTarget. However, I'm not sure if anyone uses this for that purpose. – Namey Dec 22 '14 at 08:37
  • So far, this actually looks like it might work, though I have only tested it on FireFox so far. Any input on cross-browser compatibility for this would be valuable. – Namey Dec 22 '14 at 08:51
  • I was looking at event.originalEvent in chrome dev tools for something like that but couldn't see anything. Just checked again and it's definitely not present in Chrome (google-chrome for linux, build 36.0.1985.125) – Joel Cox Dec 22 '14 at 08:58
  • Looks like the explicitOriginalTarget param is definitely FireFox (Gecko) specific, and generally intended to be internal. However, if I can find comparable functionality for IE 9+ and Chrome, that would cover my use cases. – Namey Dec 22 '14 at 09:06

5 Answers5

1

Maybe somthing like this demo could help you out a bit:

document.getElementsByTagName('div')[0].addEventListener('click', function () {
    var fullStr = this.innerHTML.replace(/<[^>]*>/g, ''),
        sel = window.getSelection(),
        str = sel.anchorNode.data,
        clickPos = sel.focusOffset,
        wordPosLeft = str.slice(0, clickPos + 1).search(/\S+$/),
        wordPosRight = str.slice(clickPos).search(/\s/),
        wordClicked,
        nextWordRegex,
        nextWordPosLeft,
        nextWord;

    if(wordPosRight < 0) {
        wordClicked = str.slice(wordPosLeft);
    } else {
        wordClicked = str.slice(wordPosLeft, wordPosRight + clickPos);
    }
    nextWordRegex = new RegExp(wordClicked);
    nextWordPosLeft = fullStr.search(nextWordRegex) + wordClicked.length;
    nextWord = fullStr.slice(nextWordPosLeft).match(/^\s*(\S*)\s*.*$/)[1];    

    console.log('wordClicked: ' + wordClicked);
    console.log('nextWord: ' + nextWord);
});

See this fiddle.

Ferret
  • 1,440
  • 2
  • 12
  • 17
  • That is pretty awesome - had no idea that was possible. Not sure if it solves OPs problem as I can't see how you could get the actual text node that was clicked, but still awesome. – Joel Cox Dec 23 '14 at 01:38
  • Interesting note: If you click on a space just a pixel or two after the end of the last character, it thinks the next word is "That" (start of sentence) regardless of which space you clicked on, but clicking a few more pixels into the space character it detects the clicked word as the one following the space. It's easiest to test and reproduce this if you zoom in to ~250%+. (google-chrome for Linux 39.0.2171.71) – Joel Cox Dec 23 '14 at 01:45
  • Yes it has some bugs ;) fullStr.search(nextWordRegex) finds the first occurrence of it. So "is" would be found in "this" and so on. But i still hoped it could help the OP. – Ferret Dec 23 '14 at 06:54
0

You need to get Your nodes in some containers. If You would click on "Node 1" text, function will return You a <div> element. But, if You would change Your code on this:

<div>
    <span>This is Node 1</span>
    <span>node 2</span>
    <span>, node 3, and </span>
    <span>node 4</span>
</div>

it would work and return <span> container. Not possible in other way, I think.

You can eventually make some JavaScript split() or regex operations.

Jazi
  • 6,569
  • 13
  • 60
  • 92
  • Unfortunately, due to the arbitrary nature of the underlying HTML, it's impossible to wrap it all in that manner. Ironically, I am also replacing some stuff that relied heavily on very fragile regex operations to do similar stuff. It broke a lot of the time. – Namey Dec 22 '14 at 08:40
  • Unfortunately, this HTML is live-editable and is eventually stored away, then used elsewhere. Every single span added using that method would later need to be stripped (but without removing any other spans). I looked into this by declaring my own subtype of spans using webcomponents.js, but it is really clunky and will be a bear to maintain. – Namey Dec 22 '14 at 09:20
  • Reviewing the different approaches discussed - I think it's beginning to look like this might be one of your only options. Although this would be incredibly hacky, if there's a certain simple tag that you can expect to not be in the output you could use that. e.g . It ruins semantics, but would be an easy thing to find/replace both the start and end tag of. – Joel Cox Dec 23 '14 at 01:09
  • I made a jsfiddle that shows a way to toggle labels on/off for only the nessecary elements. http://jsfiddle.net/wgrov28t/1/ Although if it's live-editable for your users, I guess this might cause some issues with whatever editor you're using? Also finding the right time to run that logic would be difficult since the content could change anytime... maybe this will help though? – Joel Cox Dec 23 '14 at 01:29
0

If you're just trying to work out the text of the element you clicked, minus child nodes text, I have a solution:

$('body').on('click', function(e) {
    alert('Node Text: '+$(e.target).clone().children().remove().end().text());
});

http://jsfiddle.net/xoegujqu/1/

Essentially, delegate the click event to the highest-level element you want this to run for (in this example it just used body, but you'll probably want to be more specific). use $(e.target) to get the element that was actually clicked, .clone() to clone it so you can modify it without affecting the actual page content, .children().remove() to remove all it's descendant elements, .end() to go back to the previous jQuery selector object, then finally .text() to get the remaining text content.

Joel Cox
  • 3,289
  • 1
  • 14
  • 31
  • Unfortunately, I need to know the actual element reference or index itself, not just the text. – Namey Dec 22 '14 at 08:38
  • then just `$(e.target)` within the function body :) Edit: Never mind - I see you mentioned that in your question. – Joel Cox Dec 22 '14 at 08:39
  • Can I ask what your use case is for this? It might help us offer a different approach. – Joel Cox Dec 22 '14 at 08:40
  • A very simplified version of the use case is actually noted in the original question. Basically, after clicking on a part of the page, a search starts to look for a particular type of tag either before or after it. By and large, it's a pretty basic traversal, except that the first part of the traversal may start in a text node and the position of that text node can impact the outcome. – Namey Dec 22 '14 at 08:48
  • I should also note that "before" and "after" are considered entirely inclusive, in that it traverses anything inside the current object, then any siblings, then the parent and any of the parent's earlier/later children (and their children), etc. – Namey Dec 22 '14 at 08:50
  • If that's your game, then it may be possible to get the elements before and after the position using a positioning scheme. e.g. Try utilizing the tools in [this answer](http://stackoverflow.com/a/27588908/1166904) while looping through the childNodes. You can get the x and y positions from the click event to compare to. – Makaze Dec 22 '14 at 09:27
  • Unfortunately, I don't think that will work consistently because the elements need to be DOM-previous/next, while a coordinate-based approach will be based on the display order. Due to CSS styling (e.g., absolute positioning), those two will not necessarily be identical. – Namey Dec 23 '14 at 00:54
0

check out even bubbling / propagation

Also: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.addEventListener useCapture section

Community
  • 1
  • 1
  • Can you be a bit more specific? Is there any browser where useCapture will mean that the text node is captured as the target, rather than its parent element? – Namey Dec 22 '14 at 09:27
0

It is not possible to do this as far as I know. You cannot:

  1. Detect events on text nodes.
  2. Detect the position of the text node relative to window or page.

This answer gives an idea with some good insight, but does not do what you want (return the index of the node).

I believe you are out of luck, unless you can find a way to use the solution above to determine index.

Community
  • 1
  • 1
Makaze
  • 1,076
  • 7
  • 13
  • Ugh. I sure hope that's not the only solution, though it's possible. The solution down that path is to: 1) wrap every character in a span, 2) find the index of the clicked one, 3) find the boundary indices of every node in the parent element, 4) report which bin the index fell into. Possible, though very contorted. – Namey Dec 22 '14 at 09:33
  • @Namey See [this comment](http://stackoverflow.com/questions/27599018/get-html-node-not-necessarily-element-on-click/27599416?noredirect=1#comment43622615_27599161): I think it provides a much easier solution by polling element positions. – Makaze Dec 22 '14 at 10:24