0

I want to make a text clickable and by this I mean every word in it. Now I know that you can wrap every Word with a <span> tag. That works well for sentences and paragraphs, but I want to be able use long texts (Articles to books) and I guess that this mechanism wont work for this.

So in a way I want to add the specific eventlistener, when I click on it and not beforehand, because wrapping each word in a whole book with a <span> would just cause to much overhead.

So I was wondering if there is something like a package that can do that for me?

The other way I could think of, is to put the text and event Listeners into an Object like this:

[{"word: 'Hello'", event:EventListenerFunction},{"word":'World!', event: EventListerenerFunction},...].

This way I can compute the whole thing on the server and not on the client and would create a lot more information needed. It also has advantages for other stuff I want to do with the texts too.

Now the problem with this approach is the displaying. Its easy to display the text in vue.js like so: {{ textObject.text }} (if the object is structured in the right way of course. Otherwise you need to filter the words beforehand). But How can I display the words without losing the information in the Object and make the event listeners work?

My third theoretical Idea was to give the parent html-tag an eventlistener that somehow gets the clicked word as argument. That would work with the wrapping span around the words approach. But without it?

Do you have any other ideas?

telion
  • 834
  • 1
  • 9
  • 34
  • `I guess that this mechanism wont work for this` Why not? It's exactly what I would do. –  Nov 30 '17 at 13:38
  • Maybe you can take a look [here](https://stackoverflow.com/questions/8609170/how-to-wrap-each-word-of-an-element-in-a-span-tag) – Ko2r Nov 30 '17 at 13:43
  • Are you showing whole book at once? Without pagination? –  Nov 30 '17 at 14:41
  • I planned to load the content as I scroll down like on Facebook. – telion Nov 30 '17 at 15:46

2 Answers2

2

With spans:

var page = 'Lorem ipsum dolor sit amet, cu eum eros vitae persequeris, laudem possit nam ex. Labore eloquentiam per an. Sit ex omnesque interpretaris, habeo tantas eos ad, ea eos ludus inciderint. Facete tritani pro ei, vim evertitur liberavisse ex. Ridens indoctum duo cu, est utamur aliquando expetendis ne. Cum nusquam definiebas ex, id esse neglegentur cum, eu libris bonorum volumus vis. Ius et quis omnis graeco, no his nullam perpetua dissentiet. No vix possim scripserit consequuntur, te mnesarchum philosophia sed. Ne mea putent iudicabit, in eam ipsum viris dicunt. Eum amet accommodare ex, sint malis adversarium at qui.'

new Vue({
  el: '#app',
  data: {
    page: ''
  },
  computed: {
    pageContent () {
      return this.page.replace(/\b(\w+?)\b/g, '<span>$1</span>')
    }
  },
  methods: {
    doSomething (e) {
      if (e.target.nodeName === 'SPAN')
        console.log(e.target.textContent)
    }
  },
  created () {
    // load external data
    this.page = page
  }
})
<div id="app">
  <p @click="doSomething" v-html="pageContent"></p>
</div>

<script src="https://unpkg.com/vue@2.5.9/dist/vue.min.js"></script>

Without spans:

var book = 'Lorem ipsum dolor sit amet, cu eum eros vitae persequeris, laudem possit nam ex. Labore eloquentiam per an. Sit ex omnesque interpretaris, habeo tantas eos ad, ea eos ludus inciderint. Facete tritani pro ei, vim evertitur liberavisse ex. Ridens indoctum duo cu, est utamur aliquando expetendis ne. Cum nusquam definiebas ex, id esse neglegentur cum, eu libris bonorum volumus vis. Ius et quis omnis graeco, no his nullam perpetua dissentiet. No vix possim scripserit consequuntur, te mnesarchum philosophia sed. Ne mea putent iudicabit, in eam ipsum viris dicunt. Eum amet accommodare ex, sint malis adversarium at qui.'

new Vue({
  el: '#app',
  data: {
    book: ''
  },
  methods: {
    doSomething (e) {
      var content = e.target.textContent
      var pos = window.getSelection().anchorOffset
      content = content
        .substring(0, content.indexOf(' ', pos))
        .trim()
      content = content
        .substr(content.lastIndexOf(' ') + 1)
        .replace(/[.,:;!?()+-]/g, '')
        
      console.log(content)
    }
  },
  created () {
    // load external data
    this.book = book
  }
})
<div id="app">
  <p @click="doSomething" v-html="book"></p>
</div>

<script src="https://unpkg.com/vue@2.5.9/dist/vue.min.js"></script>
  • I guess I will use that for now. Thank you. – telion Nov 30 '17 at 15:56
  • @telion Done. I found a solution without need to wrap every word with span elements. Example added. –  Nov 30 '17 at 17:42
  • @Waldemarlce Nice. Great answer!! Thank you very much. – telion Nov 30 '17 at 21:07
  • For the sake of completion: There is the edge-case of inline tags like . I looked analysed your answer and was a little confused on why it didnt work since e.target.textcontent should actually get all text in a tag even if there are other tags inside it. It however causes wierd behavior. – telion Dec 14 '17 at 14:09
  • Second example improved - you can now use text with mark tags. –  Dec 14 '17 at 15:31
1

You can use window.getSelection() to get your position in the text, then find the word characters around it.

One corner case: if you have formatting tags (like the bold in my example) that break a word, you will get the part of the word you clicked on, but not the whole word.

var page = `Lorem ipsum dolor sit amet, cu eum eros vitae persequeris, laudem possit nam ex.
Labore eloquentiam per an.
Sit ex omnesque interpretaris, habeo tantas eos ad, ea eos ludus inciderint.
<i>Facete tritani pro ei</i>, vim evertitur liberavisse ex.
Ridens indoctum duo cu, est utamur aliquando expetendis ne. <b>Cum nusquam defin</b>iebas ex, id esse neglegentur cum, eu libris bonorum volumus vis. Ius et quis omnis graeco, no his nullam perpetua dissentiet. No vix possim scripserit consequuntur, te mnesarchum philosophia sed. Ne mea putent iudicabit, in eam ipsum viris dicunt. Eum amet accommodare ex, sint malis adversarium at qui.`

new Vue({
  el: '#app',
  data: {
    page
  },
  methods: {
    doSomething (e) {
      const sel = window.getSelection();
      const text = sel.anchorNode.textContent;
      const lmatch = text.substr(0, sel.anchorOffset).match(/[\s\S]*\s/);
      const offset = lmatch ? lmatch[0].length : 0;
      const match = text.substr(offset).match(/\w+/);
      
      console.log(match && match[0]);
    }
  }
})
<div id="app">
  <p @click="doSomething" v-html="page"></p>
</div>

<script src="https://unpkg.com/vue@2.5.9/dist/vue.min.js"></script>
Roy J
  • 42,522
  • 10
  • 78
  • 102
  • Great Answer. I dont know now who posted the solution first but thank you very much for your answer. I really appreciate it. – telion Nov 30 '17 at 21:08
  • For the sake of completion: there is a second edge-case which is utf-8. If there is a char that isnt part of \w, the word will only display all characters till the char that isnt defined. – telion Dec 14 '17 at 14:05
  • Fair point. UTF-8 is ugly in JavaScript Regexps. https://stackoverflow.com/questions/6381752/validating-users-utf-8-name-in-javascript – Roy J Dec 14 '17 at 14:13