Dev Daniel
Dev Daniel

Reputation: 445

Cutting down to 20 words with HTML content using JS and VueJS?

I am creating a news feed with VueJS and I have run into a bit of a problem with rendering the content. The API I am using sadly I am unable to change to suit my need properly at this time. The API gives me all the content already in HTML tags and it can also include images and lists and all the other basics. What I want to do is create a "read more" section which will render the first 20 words if just the text of the first "p" tag and stop there.

Does anyone know a quick and efficient way of doing this with JS? My current display VueJS render is the following:

<div v-for="news_item in news_items">
                        <div v-bind:class="{ 'col-md-4': display}">
                            <div class="card">
                                <div class="header">
                                    <h2>
                                        {{news_item.title}} <small>{{news_item.subtitle}}</small>
                                    </h2>
                                </div>
                                <div class="body" style="padding-top: 0">
                                    <div class="row" style="margin-right: -20px; margin-left: -20px;">
                                        <div class="col-md-12"
                                                style="padding-left: 0px; padding-right: 0px;">
                                            <img :src="news_item['thumbnail']"
                                                    class="img-responsive smaller-img" alt=""
                                                    style=" margin: 0 auto; max-height: 250px;">
                                        </div>
                                    </div>
                                    <div class="row">
                                        <div class="col-md-12">
                                            <div v-html="news_item.content"></div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>

Upvotes: 1

Views: 2640

Answers (5)

Roy J
Roy J

Reputation: 43899

You don't appear to have tried anything yet, so I'll just give you these pointers. If you run into specific problems, ask again.

  1. Make a component
  2. The component should receive the html as a prop
  3. The component should have a data item to control whether it is expanded
  4. The component should have a computed that gets the first 20 words of the first paragraph tag. You can use textContent to get text from an HTML node.

The computed is the most likely part to pose a challenge. It will look something like this

    blurb() {
      const div = document.createElement('div');
      div.innerHTML = this.content;  // this.content is the prop
      const firstP = div.querySelector('p');
      const text = firstP.textContent;
      const match = text.match(/(\S+\s*){0,20}/);
      return match[0];
    }

Upvotes: 1

tom_h
tom_h

Reputation: 905

This is the perfect time to use a directive:

https://v2.vuejs.org/v2/guide/custom-directive.html

See the codepen here: https://codepen.io/huntleth/pen/GOXaLo

Using the trim directive, you can change the content of the element. In the example above, it will show the first 5 words followed by an ellipsis.

If you're just after a pure js solution, this should do it:

    var resultString = str.split(' ').slice(0, 20).join(" ");

You could use the trim directive and search the el for any p tags, and then change their content accordingly.

Upvotes: 1

astrochoi
astrochoi

Reputation: 13

Expanding on the answers by tom_h and Roy J, here's what I'm using in my vue application to make the ellipsis clickable:

Vue.component("ellipsis", {
  template: "#ellipsis-template",
  props: ['content'],
  data: function() {
    return {
        wordLength: 3,  // default number of words to truncate
        showAll: false
    }
  }
});

<script type="text/x-template" id="ellipsis-template">
  <span v-if="content.split(' ').length>wordLength && showAll">{{content}}
    <a href="#" onclick="return false" @click="showAll=false"> (less)</a>
  </span>
  <span v-else-if="content.split(' ').length>wordLength && !showAll"> 
    {{content.split(" ").slice(0,wordLength).join(" ")}}
    <a href="#" onclick="return false" @click="showAll=true"> ...</a>
  </span>
  <span v-else>{{content}}</span>
</script>

To call it:

<ellipsis :content="someData"></ellipsis>

Upvotes: 0

dagalti
dagalti

Reputation: 1956

You can write a vue directive to solve this.

  1. Set max-height to the div.
  2. count the words and append "Read more.." link to the content.
  3. Add a click event to 'read more' to expand the DIV to full height.

For example see this codepen

 let handler = ""

Vue.directive("viewmore", {
    inserted: function (el, binding){
      let maxlines = binding.value
      let lineheight = parseFloat(getComputedStyle(el).lineHeight)
      let paddingtop =  parseFloat(getComputedStyle(el).paddingTop) 
      let lines = (el.clientHeight) / lineheight ; 
      let maxheight = (lineheight * maxlines) + paddingtop + (lineheight/5)

      if(lines>maxlines){
          el.classList.add('vmore')
          el.style.maxHeight = maxheight + 'px'
          el.addEventListener('click', handler = ()=> {
          el.style.maxHeight = ""
          el.scrollIntoView({behavior: "smooth"})
          el.removeEventListener('click', handler)
          el.classList.remove('vmore')
        })
      }
    },
    unbind: function (el, binding) {
          el.removeEventListener('click', handler)
          handler = ""
    }
});

https://codepen.io/dagalti/pen/vPOZaB .

it works based on the lines in the content.

Code : https://gist.github.com/dagalti/c8fc86cb791a51fe24e5dc647507c4a3

Upvotes: 0

Rajkumar Somasundaram
Rajkumar Somasundaram

Reputation: 1270

Rough implementation, Pure Js approach

document.getElementById("addContent").onclick = display;
document.getElementById("ellipsAnchor").onclick = hideEllipsis;

function display() {
  document.getElementById("instruction").classList+= " hide";
   let content = document.getElementById("inputbox").value;
   if(content.length > 30) {
     let sliced = content.slice(30);
     let unsliced = content.substring(0,29);
     let spantag = document.createElement("span");
     spantag.className = "toReplace hide"
     let text = document.createTextNode(sliced);
     spantag.appendChild(text);
     let spantag1 = document.createElement("span");
     let text1 = document.createTextNode(unsliced);
     spantag1.appendChild(text1);
     let contentTag =document.getElementById("content");
     contentTag.appendChild(spantag1)
     contentTag.appendChild(spantag)
     document.getElementById("ellipsis").classList -= "hide";
   }
}

function hideEllipsis(){
   document.getElementById("ellipsis").classList += " hide";   
   document.querySelectorAll("span.hide")[0].classList -= " hide"
}
.hide {
  display : none;
}
<textarea type="text" id="inputbox"></textarea>
<button id="addContent">
Show content
</button>
<div id="content">
</div>
<div class="hide" id="ellipsis">
<a href="#" id="ellipsAnchor">Read More..</a>
</div>

<div id="instruction">
Type more than 30 characters and click show content
</div>

Upvotes: 0

Related Questions