3

I have: Simple block of html text:

<p>
The future of manned space exploration and development of space depends critically on the
creation of a dramatically more proficient propulsion architecture for in-space transportation.
A very persuasive reason for investigating the applicability of nuclear power in rockets is the 
vast energy density gain of nuclear fuel when compared to chemical combustion energy...
</p>

I want: wrap word into span when user click on it.

I.e. User clicked at manned word, than I should get

<p>
The future of <span class="touched">manned</span> space exploration and development of space depends critically on the
creation of a ....

Question: How to do that? Is there way more efficient that just wrap all words into span at loading stage?

P.S. I'm not interested in window.getSelection() because I want to imply some specific styling for touched words and also keep collection of touched words

Special for @DavidThomas: example where I get selected text, but do not know how to wrap it into span.

VB_
  • 45,112
  • 42
  • 145
  • 293
  • You could try with jQuery dot dot dot. http://dotdotdot.frebsite.nl/ – ianaya89 Nov 14 '14 at 13:42
  • you can get clicked word only, it's position, then use string slice + concat. – dfsq Nov 14 '14 at 13:43
  • http://stackoverflow.com/questions/7563169/detect-which-word-has-been-clicked-on-within-a-text – dfsq Nov 14 '14 at 13:44
  • Have you tried [`window.getSelection()`](https://developer.mozilla.org/en-US/docs/Web/API/Selection)? – Ja͢ck Nov 14 '14 at 13:44
  • @Jack I know how to get selected text. I want to wrap it with span. (To be able to do something with the collection of touched words afterwards) – VB_ Nov 14 '14 at 13:45
  • @dfsq the task is to wrap html node but not to get the text. I also need to imply some styling for touched words – VB_ Nov 14 '14 at 13:45
  • Once you know the node in which the selection took place and the offset, wrapping that into a span shouldn't be so hard; your question doesn't seem to suggest you had already cracked the first part. – Ja͢ck Nov 14 '14 at 13:46
  • @Jack the problem is that I have only one node - `

    `. And a thousands of words inside it. Sorry, but how do you say you wanna wrap a word?

    – VB_ Nov 14 '14 at 13:48
  • Rather than adding a 'P.S. I don't want...' try adding an excerpt of your code that demonstrates successfully retrieving the correct text, and shows your attempts at replacing that text with a `` element. – David Thomas Nov 14 '14 at 13:49
  • @DavidThomas one minute pls – VB_ Nov 14 '14 at 13:49
  • While I'm touched by your generosity, you realise that your demo doesn't seem to alert any selected/clicked text? – David Thomas Nov 14 '14 at 14:11

4 Answers4

6

I were you, I'd wrap all words with <span> tags beforehand and just change the class on click. This might look like

$( 'p' ).html(function( _, html ) {
    return html.split( /\s+/ ).reduce(function( c, n ) {
        return c + '<span>' + n + ' </span>'
    });
});

and then we could have a global handler, which listens for click events on <span> nodes

$( document.body ).on('click', 'span', function( event ) {
    $( event.target ).addClass( 'touch' );
});

Example: http://jsfiddle.net/z54kehzp/


I modified @Jonast92 solution slightly, I like his approach also. It might even be better for huge data amounts. Only caveat there, you have to live with a doubleclick to select a word.

Example: http://jsfiddle.net/5D4d3/106/

jAndy
  • 231,737
  • 57
  • 305
  • 359
  • 2
    Yeah, you might want to be more careful with your ``-wrapping though: [a not-unrealistic demo](http://jsfiddle.net/davidThomas/81d6Lzxe/). – David Thomas Nov 14 '14 at 13:52
  • @jAndy yeah, thanks. That was my first though when I started with this problem. But, suppose I have 100 000 words. How many time it'd take to wrap all the words into `span`'s? – VB_ Nov 14 '14 at 13:54
  • @V_B performance might be an issue of course when you're dealing with huge data amounts. However, I don't see any viable option in HTML/JS besides doing it this way (with click events). – jAndy Nov 14 '14 at 13:57
  • 1
    Pretty nice solution. – Jonast92 Nov 14 '14 at 14:00
2

I modified a previous answer to almost get what you're looking for, as demonstrated in this demo.

It finds the currently clicked word and wraps a span with that specific class around the string and replaced the content of the paragraph with a new content which's previously clicked word is replaced with the newly wrapped string.

It's limited a bit though because if you click on a substring of another word, let's say 'is' then it will attempt to replace the first instance of that string within the paragraph.

You can probably play around with it to achieve what you're looking for, but the main thing is to look around.

The modified code:

$(document).ready(function()
{
    var p = $('p');
    p.css({ cursor: 'pointer' });
    p.dblclick(function(e) {
        var org = p.html();
        var range = window.getSelection() || document.getSelection() || document.selection.createRange();
        var word = $.trim(range.toString());
        if(word != '')
        {
            var newWord = "<span class='touched'>"+word+"</span>";
            var replaced = org.replace(word, newWord);
            $('p').html(replaced);
        }
        range.collapse();
        e.stopPropagation();
    });    
});

Then again, @jAndy's answer looks very promising.

Community
  • 1
  • 1
Jonast92
  • 4,964
  • 1
  • 18
  • 32
  • it's a good approach, but very unreliable. Lets say you have a text like "This is" and you select the "is" .. – jAndy Nov 14 '14 at 14:01
  • What do you think about a somewhat adapted version to my solution: http://jsfiddle.net/5D4d3/106/ – jAndy Nov 14 '14 at 14:11
  • @Jonast92 `org.replace(word, newWord)` returns undefined. But that isn't an issue. Really - what happens if that word occur multiple times at the paragraph? – VB_ Nov 14 '14 at 14:14
  • @V_B you're right, multiple words are still problematic. It would require some more sugar and checking whats the previous and/or next character to a match for instance (to figure out `<` `>` characters) – jAndy Nov 14 '14 at 14:26
  • @jAndy yes, I think that's all about implementation of `getPosition` method. Could you look at my answer below pls? – VB_ Nov 14 '14 at 14:27
  • @Jonast92 can we implement `getPosition` with help of `range`s? – VB_ Nov 14 '14 at 14:32
0

Your answers inspired me to the next solution:

$(document).ready(function()
{
    var p = $('p');
    p.css({ cursor: 'pointer' });
    p.dblclick(function(e) {
        debugger;
        var html = p.html();
        var range = window.getSelection() || document.getSelection() || document.selection.createRange();
        var startPos = range.focusOffset; //Prob: isn't precise +- few symbols
        var selectedWord = $.trim(range.toString());            
        var newHtml = html.substring(0, startPos) + '<span class=\"touched\">' + selectedWord + '</span>' + html.substring(startPos + selectedWord.length);
        p.html(newHtml);
        range.collapse(p);
        e.stopPropagation();
    });    
});

We haven't there wrap each word in span. Instead we wrap word only on click.

VB_
  • 45,112
  • 42
  • 145
  • 293
0

use

range.surroundContents(node)

  $('.your-div').unbind("dblclick").dblclick(function(e) {
    e.preventDefault();
    // unwrap .touched spans for each dblclick.
    $(this).find('.touched').contents().unwrap();
    var t = getWord();
    if (t.startContainer.nodeName == '#text' && t.endContainer.nodeName == '#text') {
      var newNode = document.createElement("span");
      newNode.setAttribute('class', 'touched');
      t.surroundContents(newNode);
    }
    e.stopPropagation();
  });

  function getWord() {
    var txt = document.getSelection();
    var txtRange = txt.getRangeAt(0);
    return txtRange;
  }
Gregory R.
  • 1,815
  • 1
  • 20
  • 32