How can I wrap the previous, current, and next word inside a tag using jQuery?
Asked Answered
V

2

9

Not sure if the title is well chosen...

I am trying to simulate text-selection in HTML/JS/CSS to get rid of the action bubble on mobile device when truly selecting texts.

To be more specific, I'm trying to avoid this: enter image description here

The visual:

enter image description here

The way I built it and it may change because it doesn't matter, is that the text selected is wrapped inside a span.selection and inside that tag, there are also two caret used as handlers:

Lorem ipsum dolor                            <!-- Unselected Text -->

<span class="selection">                     <!-- Start selection wrapper -->

  <span rel="previous" class="caret"></span> <!-- The left-side caret -->

  sit amet, consectetur                      <!-- The selected texts -->

  <span rel="next" class="caret"></span>     <!-- The right-side caret -->

</span>                                      <!-- End selection wrapper -->

adipiscing elit.                             <!-- Unselected Text -->

Ideally, it would be fun to use a drag-n-drop to select more or less texts but I believe this would be darn hard to do and in that case, maybe using click on the carets to select either the previous or the following word and wrap it inside the .selection wouldn't be that bad.

Here is the jsfiddle: http://jsfiddle.net/m6Qx4/

The surrounding texts may contains HTML tags too such as: <i>, <b>, <span> and <ul>/<li> may be present making the parsing harder.

Any ideas how this can be done?

Status Update:

I have actually managed to make it work with .click(); event listener using my custom jQuery methods.

Eventually, I will replace the click events with jQuery UI draggable to select surrounding words so long as it's useable for mobile devices.

My current bug consists of the re-position of the red carets. I have tried destroying them and prepend/append them back and it's still not working. It might be a bug with Firefox that can't reload the DOM properly after changes made to Text Nodes?

Reference: jsFiddle

To check operating condition of jsFiddle.net due to recent outages, visit their Tweets.

Varied answered 28/6, 2012 at 18:1 Comment(9)
Below console.info in your jsFiddle, you may need to use variable lastWordArray instead of lastWord since that was a previous correction. Not to sure what needs to be changed.Larynx
I can't see why you're trying to do this. What's the point? That said, the rangy library might help you.Vapid
I edited the question. It must be more abvious now with the image. And Rangy being a nice plugin indeed, but useless in my case because text selection under Android is simulated and not really "happening" in the WebView.Varied
Here is a update: jsfiddle.net/sNpLw/1 you start by double clicking the <p>. Only tested in FirefoxVaried
In Firefox, it works with a single click that will show two red carets at the extreme ends. Then clicking each caret will make it move towards the center. Pressing the button at any time returns the contents. Glitch seen where one word disappears during process, but it's "captured" correctly via alert box confirmation. The button pressing also changes the format of the original text. That said, your jsFiddle would make a great answer that you can select if you want to, or you can include this as a update in your Question and see if others can help with fine tuning it (be clear of whats wrong).Larynx
Status Update: Here's my custom version, almost complete but not quite there yet. jsFiddle New Method. Also, here's the tutorial version of this work-in-progress with more comments than markup.Larynx
@arttronics, I just tested with my phone and your solution is simply awesome!Varied
@arttronics, At the end, I would like to wrap the fake "selection" with a <span> because that is needed for a bigger project where I prompt the user with options after to gives the choice to either remove, edit or highlight that selection.Varied
@arttronics, and yes I am aware that I have some questions still pending.Varied
K
2

A few thoughts:

I can't see how wrapping everything in a <span> is a viable solution. What happens when you have something like this?

<p>The <b>important terms</b> will be bolded in this text</p>

When you select The important, you'll have some tag mayhem where the <span> tag's body wants to overlap the <b> tag's body.

I think a better solution would be to have multiple selection <span> tags with the same class. Each time an HTML tag is encountered, you skip over the tag and create a new selection <span>. Then, when you reposition your carets, you simply do this:

$(".selection:first").prepend(startCaret);
$(".selection:last").append(endCaret);

I played around with this idea and it was working fine for me. If you want to get really fancy, you could try and merge selection <span>s when you find a closing tag after you've encountered its corresponding start tag, but that would be a lot of work.


Your carets aren't moving correctly because you specify a style of absolute positioning. Change that to relative and give each caret a body of &nbsp;. You'll have to play around with a few other positioning CSS settings to get it to look just right, but it moves as you'd expect with those changes (example here).


As I was playing with some ideas for this, I tried doing absolute positioning on the carets so they didn't have to be moved around inside the selection <span>. That was a mistake. I ran into problems with paragraphs that overflowed to a new line without any markup. Stay with your idea of having the carets inside of your selection <span>.


Lastly, a few thought questions:

  • Are you going to limit the scope? (i.e., if you start selecting inside a <p> tag, don't allow selection to spill outside of that tag to the next <p> or whatever follows)
  • Are certain tags going to be unselectable? (i.e., <table>, <script>, <select>, etc)

I think this idea is pretty cool, but it could be a pretty massive project. I'll post some of my prototypes that I tried when I get them a bit more polished.

Kiona answered 3/7, 2012 at 0:9 Comment(7)
I do not want to avoid scope. Because that would also mean that we can't select through strong tag either. My code, so far only limits itself to the surrounding text nodes. I'm trying to figure out a way to detect and regenerate any encountered tags inside my .selection without duplicating those tags too.Varied
+1 @RustyTheBoyRobot, interesting idea of multiple selection <span> tags with the same class which can allow for individual words to have it's background-color set simulating text-selection.Larynx
@Larynx - Obviously, it could cause some headaches if you ended up with two spans that weren't next to each other. The component would act like everything between the spans was selected, but it wouldn't be. (If that makes sense....)Kiona
Humm, that in itself sounds like another method to me... to have the spans onset and offset "act" as the gatekeeper so to speak.Larynx
Does that mean we have to wrap all words with a span and only the selected ones would contain the .selected?Varied
@Cybrix, there are a few SO Answers I've come across for what you just said.Larynx
Although an Answer is not yet found, you received the Bounty because you participated during the Bounty period. The purpose of this Sponsored Bounty is now complete, which was to attract attention that may have lead to a viable answer. Cheers!Larynx
A
0

To cover Rusty's issue:

Split the tags out first:

var textBlock = textContainer.html(),
taglessArray = textBlock.split(/<[^>]*>/),
arrayOfTags = textBlock.match(/<[^>]*>/g),
tagsFirst = (textBlock[0] === '<') ? true : false, //parens for legibility only
//tagsFirst is a switch that tells us which to start splicing back in later

Now let's wrap all of our non-whitespace in span tags we can use

var i = taglessArray.length;
while(i--){
    taglessArray[i].replace(/([^\s]*)/g,'<span class="selectableWord">$1</span>');
}
//not 100% sure I got that right - haven't tested - but the idea is , wrap all non
//whitespace/word boundary blocks in 'selectableWord' classed span tags

Now stick 'em back together. Original text formatting tags should retain position. Obviously 'selectableWord' classed spans should avoid layout/formatting impact

var addToArray, adderArray;
if(tagsFirst){ addToArray = arrayOfTags; adderArray = taglessArray; }
else { addToArray = taglessArray; adderArray = arrayOfTags; }

var oLen, //used to retain 'original length'
i2 = oLen = (tagsFirst.length + arrayOfTags.length),
rejoinArray = [];

while(i2--){ //not 100% sure I got this right - hope you get the general idea
    var currentKey = oLen - 1 - i2;
    //if first or alternating from first 0,2,4,etc...
    if(currentKey === 0 || ( currentKey % 2 === 0) {
        rejoinArray[currentKey] = addToArray.shift(); //remove first element and return
    }
    //should be keys 1,3,5,etc...
    else {
        rejoinArray[currentKey] = adderArray.shift();
    }
}

All of the above of course is hugely untested and probably buggy/wrong but I think the basic idea is pretty cool. Rip the tags out. Wrap the array of untagged words in spans you can use. Then splice all the silly tags back in. They should still wrap all the same words. This assumes basic HTML integrity. Your text blocks only contain inline-display tags that would go around words or sets of words. Improper nesting might not be an issue unless there are improperly nested spans.

Amphimixis answered 8/7, 2012 at 1:20 Comment(1)
I will try to implement it in a jsfiddle so we can debug it.Varied

© 2022 - 2024 — McMap. All rights reserved.