0

I am working on a project where users can work around with a list of information. I'll explain with the code below.

<div>
  <div id="factmarker1" onmouseup="factFunction(1)">
     <b class="fact-num" id="fact-num1">1</b><mark id="fact1">The earth is spherical</mark>
  </div>
  <div id="factmarker2" onmouseup="factFunction(2)">
     <b class="fact-num" id="fact-num2">2</b><mark id="fact2">There is no gravity in space</mark>
  </div>
  <div id="factmarker3" onmouseup="factFunction(3)">
     <b class="fact-num" id="fact-num3">3</b><mark id="fact3">The earth is made up of more water</mark>
  </div>
  <div id="factmarker4" onmouseup="factFunction(4)">
     <b class="fact-num" id="fact-num4">4</b><mark id="fact4">There is a lunar year every four years</mark>
  </div>
  <div id="factmarker5" onmouseup="factFunction(5)">
     <b class="fact-num" id="fact-num5">5</b><mark id="fact5">Every lunar year has 366 days</mark>
  </div>
  <div id="factmarker6" onmouseup="factFunction(6)">
     <b class="fact-num" id="fact-num6">6</b><mark id="fact6">The earth is spherical</mark>
  </div>
  <div id="factmarker7" onmouseup="factFunction(7)">
     <b class="fact-num" id="fact-num7">7</b><mark id="fact7">The earth is spherical</mark>
  </div>
</div>
<div id="previewFact"></div>

Now this is pretty straightforward. If a user clicks or highlights any of the facts, an options tab pops up enabled by javascript where the user has options to do a lot of things like copying, saving or sharing each fact. If a user highlights say fact 5 and chooses to save it, the preview prompt shows something like this:

/*
FACT 5

Every lunar year has 366 days

Now here's my problem: I want to be able to allow a user highlight multiple facts at the same time and I can't find a way to do it. If a user finds fact 1 through 4 interesting and highlights it, I want the preview to be something like this:

/*
FACT 1 to 4

1 The earth is spherical
2 There is no gravity in space
3 The earth is made up of more water
4 There is a lunar year every four years
*/

The user should also be able to choose any multiple facts. Also, the facts are called out from a database using PHP, so, I don't know how many facts there are or what the contents are.

I have tried using the window.getSelection() method. I tried using "fact-num" class so that when I got the content of the highlighted area, I could search for the classes inside it - like so:

function factFunction(factNum) {
  var sel = window.getSelection();
  var factNumLength = sel.querySelectorAll('.fact-num').length;
  if(factNumLength > 1){ //checking if it is a multiple highlight or not
    var factStart = sel.querySelectorAll('.fact-num')[0].innerHTML;
    var factStop = sel.querySelectorAll('.fact-num')[factNumLength - 1].innerHTML;
    var preview = "Fact "+factStart+" to "+factStop;
    preview += "<br/>"+sel.toString();
    document.getElementById('previewFact').innerHTML = preview;
  }else{
    var preview = "Fact "+factNum;
    preview += "<br/>"+sel.toString();
    document.getElementById('previewFact').innerHTML = preview;
  }
}

This isn't the entire block of code as it is much longer, but this is the general structure of the entire thing and it's not working. I don't know if I am supposed to use the value of the window.getSelection() as I am using it.

I would really appreciate any help as I have spent the whole of yesterday and most of today looking for answers.

Thank you

Edit

Just so I'm properly understood, when highlighted like this: enter image description here It should show this: enter image description here

  • Just so that I am clear: You wish that a user click on any number of "facts" and that fact is then copied to the preview area? If that is so, are the facts to retain their original numbers regardless of the sequence in which they are selected or to be renumbered? – Professor Abronsius Sep 18 '21 at 13:40
  • @ProfessorAbronsius - If I understand your question correctly, my answer will be that when the facts are "highlighted" (not clicked), the facts are copied to the preview area but the facts are to retain their original numbers regardless of whether the facts where highlighted top to bottom or bottom to top. I hope this answers your question – Michael David Sep 18 '21 at 13:44
  • How are they highlighted? By swiping a selection with the mouse? – Professor Abronsius Sep 18 '21 at 13:52
  • @ProfessorAbronsius - Exactly – Michael David Sep 18 '21 at 13:56
  • I'd have a look at this answer for calling an event after selecting text: https://stackoverflow.com/a/3731367/1969888 – dave Sep 18 '21 at 14:12
  • Is the above HTML structure required to be quite like that or is there scope to modify things? – Professor Abronsius Sep 18 '21 at 14:47
  • Presumably also the fact that you are making a selection with a mouse means that the selection must be of contiguous facts rather than an assortment - such as 1,3,5 etc? – Professor Abronsius Sep 18 '21 at 15:37

2 Answers2

3

I wasn't able to find anywhere in the specification that Selection provides querySelectorAll but even if it would exist, containsNode with true for partial might be the better choice.

Furthermore, you want to utilize event delegation instead of attaching the event handler to every element. That way you are more flexible about the logic you want to apply.

With data- attributes you can give the elements some additional semantic information like your facts id.

I cloned the facts elements in displayFactsResult but you can for sure change that again to match your desired output.

The final code could look something like this (with further comments what each part of the code does):

function displayFactsResult(array) {
  const previewFact = document.getElementById("previewFact");

  // empty the facts preview
  previewFact.innerHTML = '';

  // create the info how many slected
  const infoNode = document.createElement('div')
  if (array.length == 1) {
    infoNode.textContent = 'Fact ' + array[0].dataset.fact
  } else if (array.length > 1) {
    infoNode.textContent = 'Facts ' + array[0].dataset.fact + ' to ' + array[array.length-1].dataset.fact
  }
  previewFact.appendChild(infoNode)

  // clone each of the selected facts and append them to the preview
  array.forEach(node => {
    previewFact.appendChild(node.cloneNode(true));
  })

}

// use event delegation instead of attaching the event handler to each element
document.querySelector('.facts').addEventListener('mouseup', (e) => {

  // traverse the DOM up until on element with `data-fact` or `.facts` element is reached
  let element = e.target
  while (element != e.currentTarget && !element.dataset.fact) {
    element = element.parentNode;
  }

  // get the selection
  var sel = window.getSelection();

  // get all elements, and filter by those in the selection
  var factsInSelection = Array.from(document.querySelectorAll('[data-fact]'))
    .filter(node => {
      return sel.containsNode(node, true)
    });

  if (factsInSelection.length > 0) {
    // if there is at least one fact selected pass it to the display function
    displayFactsResult(factsInSelection)
  } else if (element.dataset.fact) {
    // if the element at which we stopped traversing has `data-fact` use this to display
    displayFactsResult([element])
  } else {
    displayFactsResult([])
  }
})
<div class="facts">
  <div id="factmarker1" data-fact="1">
    <b class="fact-num" id="fact-num1">1</b><mark id="fact1">The earth is spherical</mark>
  </div>
  <div id="factmarker2" data-fact="2">
    <b class="fact-num" id="fact-num2">2</b><mark id="fact2">There is no gravity in space</mark>
  </div>
  <div id="factmarker3" data-fact="3">
    <b class="fact-num" id="fact-num3">3</b><mark id="fact3">The earth is made up of more water</mark>
  </div>
  <div id="factmarker4" data-fact="4">
    <b class="fact-num" id="fact-num4">4</b><mark id="fact4">There is a lunar year every four years</mark>
  </div>
  <div id="factmarker5" data-fact="5">
    <b class="fact-num" id="fact-num5">5</b><mark id="fact5">Every lunar year has 366 days</mark>
  </div>
  <div id="factmarker6" data-fact="6">
    <b class="fact-num" id="fact-num6">6</b><mark id="fact6">The earth is spherical</mark>
  </div>
  <div id="factmarker7" data-fact="7">
    <b class="fact-num" id="fact-num7">7</b><mark id="fact7">The earth is spherical</mark>
  </div>
</div>
<hr>
<div id="previewFact">
</div>
t.niese
  • 39,256
  • 9
  • 74
  • 101
0

I've updated this to make an array of objects containing the fact ID and text that checks against the fact ID to avoid duplicates, and saves all clicked facts into the previewFact div.

let savedFacts = [];

$('.fact').click(function() {

  let factText = $(this).children('mark').text();
  let factId = $(this).children('.fact-num').text();
  
  if (savedFacts.findIndex(x => x.id === factId) == -1) {

    savedFacts.push({"id":factId,"fact":factText})

  }
  
  $("#previewFact").html(savedFacts.map(function(elem){
        return `${elem.id} ${elem.fact}`;
    }).join("<br />"));
  
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
  <div id="factmarker1" class="fact">
     <b class="fact-num" id="fact-num1">1</b><mark id="fact1">The earth is spherical</mark>
  </div>
  <div id="factmarker2" class="fact">
     <b class="fact-num" id="fact-num2">2</b><mark id="fact2">There is no gravity in space</mark>
  </div>
  <div id="factmarker3"  class="fact" >
     <b class="fact-num" id="fact-num3">3</b><mark id="fact3">The earth is made up of more water</mark>
  </div>
  <div id="factmarker4"  class="fact" >
     <b class="fact-num" id="fact-num4">4</b><mark id="fact4">There is a lunar year every four years</mark>
  </div>
  <div id="factmarker5"  class="fact">
     <b class="fact-num" id="fact-num5">5</b><mark id="fact5">Every lunar year has 366 days</mark>
  </div>
  <div id="factmarker6"  class="fact" >
     <b class="fact-num" id="fact-num6">6</b><mark id="fact6">The earth is spherical</mark>
  </div>
  <div id="factmarker7"  class="fact">
     <b class="fact-num" id="fact-num7">7</b><mark id="fact7">The earth is spherical</mark>
  </div>
</div>
<br />
<span>Saved facts:</span>
<div id="previewFact"></div>
dave
  • 2,750
  • 1
  • 14
  • 22