Medium has been a very popular blog for quite some time and it still is. I have been introduced to this blog 2 months ago. Since then, I found this blog to be more interesting in terms of user experience and for some of its very unique features. One of my favorite feature is text highlighting & taking private notes on highlighted text. Today, we are going to see how we can implement text highlighting.
As a starter, you just need to have basic knowledge of javascript and know how we can access DOM elements using javascript.
Now, from the looks of it, this is a very simple feature to implement. You just have to get the user selected text by simple javascript DOM function (window.getSelection()). After that, you just have to wrap a div having a dark background color (Predefined jQuery function). Simple Right ?
Well, Sadly this is not that simple. The above technique will only work if we have plain text enclosed in a single div. In blogs like medium, the text is enclosed in a nested structure of DOM. So, the problem here is quite complex.
Now, before arbitrarily approaching different solutions, we have to critically analyze our problem.
Problem Definition:
Our end goal is to highlight/unhighlight the text that a user select from within the blog post. For this, we have to address the following questions in our desired solution.
- How to get the text selected by the user?
- How to highlight the selected text? Given that blog text is enclosed in the nested DOM structure.
- How to restrict user from selecting already highlighted text?
- How to remove highlight from already highlighted text?
Our problem doesn’t end here. We don’t know the pattern of nested DOM structure because every blog writer has its own way to write a blog article. So, we have to come up with a generic solution that can handle any type of nested structure.
If we see, question 1 and 3 are quite simple. For question 1, the solution is using the plain javascript DOM function (window.getSelection()). For question 3, there is a simple CSS property that prevent user selection ( user-select : none ).
The main problem we have is question 2 and 4. As i have mentioned before that we can’t predict the nested DOM structure of a given blog. A paragraph can have italic sections, bold sections, hyperlinks sections etc. Example is shown below:
<p>
Lorem Ipsum <a href=”abc.com”>has been the</a> industry’s standard dummy text ever since the 1500s, when an unknown printer <b> took a galley </b>of type and scrambled <i>it to</i> make a type specimen book. It has survived not only <a href=”abc.com”>five centuries</a>
</p>
Looking at the above example, a user can select text from anywhere. For example:
It can be:
Lorem Ipsum <a href=”abc.com”>has
OR
when an unknown printer <b> took a
OR
and scrambled <i>it to</i> make a type specimen book. It has survived not only <a href=”abc.com”>five cen
Looking at the situation, we see that we have keep a track of every opening and closing tag within the selected text. Otherwise, we will end up messing up the whole DOM structure.
In reality, we can face a lot more complex structures. There can be a situation where we have to select multiple paragraphs, half hyperlinks and so on. The possibilities are endless. And even if we succeed to highlight a selected text, we also have to keep a reverse path open. It means that if we have to remove a highlight from a text, we can be able to do so.
Now, i think the problem we have to solve is quite understood and we should move on towards the possible solutions.
Possible Solutions:
We will use the javascript range object in construction of the solution. So, before proceeding towards the solution, we must understand the javascript RANGE object. A range object contains the whole DOM tree information for the current selected text. The information contains its parent, child elements, its position in the DOM etc. We can access this range object by using selection object i-e (window.getSelection().getRangeAt(0)).
Now, moving on towards the solutions. Suppose we have the given blog post:
<p>
Lorem Ipsum <a href=”abc.com”>has been the</a> industry’s standard dummy text ever since the 1500s, when an unknown printer <b> took a galley </b>of type and scrambled <i>it to</i> make a type specimen book. It has survived not only <a href=”abc.com”>five centuries</a>
</p>
Now, Suppose i want to highlight the following:
Lorem Ipsum <a href=”abc.com”>has been
We know that how we can get the range object. Through this object we can access the whole tree information.
Get Muhammad Ahmed’s stories in your inbox
Join Medium for free to get updates from this writer.
Solution # 1:
We can start wrapping our selected text range and if we have any non-closed child at the end of range. We will close it first and then close our highlight wrapper. The resulting blog post will look like this:
<p>
<span class=”highlight”>Lorem Ipsum <a href=”abc.com”>has been</a></span> the</a> industry’s standard dummy text ever since the 1500s, when an unknown printer <b> took a galley </b>of type and scrambled <i>it to</i> make a type specimen book. It has survived not only <a href=”abc.com”>five centuries</a>
</p>
Now, if we see carefully, we have a problem. We have a anchor tag ( <a> ) after span who has no parent. To solve this problem, we have to add a starting tag of the same child element present in the highlight <span> element.
Now, the correct output shall look like this:
<p>
<span class=”highlight”>Lorem Ipsum <a href=”abc.com”>has been</a></span><a href=”abc.com”> the</a> industry’s standard dummy text ever since the 1500s, when an unknown printer <b> took a galley </b>of type and scrambled <i>it to</i> make a type specimen book. It has survived not only <a href=”abc.com”>five centuries</a>
</p>
Hurray!!! Looks like we have solved the problem. Well, thats is not quite true. Let me explain, We have highlighted the text successfully, but what about remove the highlight from the text? . Other than removing the highlight span tag ( <span class=”highlight”> ) we also have to remove all the manually altered child element tags that we added to avoid DOM destructuring. i-e (<a href=”abc.com”> )
This can become very costly task in terms of computation. This is because that other than the selected text range, we also have to extract the outer text range object to access its tree structure for manipulation.
The illustrated example is a very simple. If we think of situations where we have 10 to 12 incomplete child tags, even highlighting can be a very costly task.
The above solution is very much appropriate but not efficient for large DOM structures. So, i have to discard this solution.
Solution # 2:
Let’s take a look on the text portion we need to highlight:
Lorem Ipsum <a href=”abc.com”>has been
Initially, we have only one text selection range for this selection. We will divide this range into several safe ranges. Safe ranges are those ranges who have no incomplete closing child elements.
To get safe ranges, we have to traverse the full DOM tree in the current selected text range. We have the find all the incomplete child element nodes and then split the range on the incomplete child nodes.
In this particular example, we get 2 safe ranges in our selected text.
Lorem Ipsum AND has been
Now we can wrap these safe ranges separately with highlighted span. The resulting blog post will look like this:
<p>
<span class=”highlight”>Lorem Ipsum </span><a href=”abc.com”><span class=”highlight”>has been</span> the</a> industry’s standard dummy text ever since the 1500s, when an unknown printer <b> took a galley </b>of type and scrambled <i>it to</i> make a type specimen book. It has survived not only <a href=”abc.com”>five centuries</a>
</p>
Now, to remove highlight from text we just need to remove the highlighted span tag (<span class=”highlight”>) . Nothing else!
Now, In my opinion this solution is more suitable for all kinds of nested DOM structures. There is only one time computation effort to find the safe ranges. After that, there is no expensive effort. We just need to wrap and unwrap the safe ranges.
Atlast! We have a winner. It is none other than Solution # 2 ( Safe Ranges Solution )
BONUS ! Persisting the highlighted text in the browser local storage
Well this is quite easy, there is a javascript library called “rangy”. It is used to serialize and deserialize the Range object. We just need to serialize our safe ranges and save it in the localstorage. Upon page reload, we just need to fetch them and deserialize them. After that you just need to wrap the safe ranges by highlighted span tag (<span class=”highlight”>)
Link to live demo:
https://wikimedium-server.firebaseapp.com/wiki/Stack_Overflow
Just Open this link and select a text to highlight it. Enjoy!