John
John

Reputation: 275

Rails truncate read more

I read this topic read more toggle on stackoverflow Rails truncate with a 'read more' toggle. How can I add a return to truncate or return to its original form or text in the code below without having to refresh the page?

<div>
  <% if @major.glance.length > 250 %>
    <%= truncate(@major.glance, length: 250) %>
    <%= link_to '...Read more', '', class: "read-more-#{@major.id}" %>
    <script>
      $('.read-more-<%= @major.id %>').on('click', function(e) {
        e.preventDefault()
        $(this).parent().html('<%= escape_javascript @major.glance %>')
      })
    </script>
  <% else %>
    <%= @major.glance %>
  <% end %>
<div>

Upvotes: 2

Views: 487

Answers (3)

Jussi Hirvi
Jussi Hirvi

Reputation: 725

Here is a general solution using separate methods which could be placed in a presenter. Variable str is the text input that may be long.

def readmore_item(str)
  return str if str.length < 200

  tag.div(readmore_whole(str) + readmore_part(str),
          class: 'readmore-item')
end

def readmore_whole(str)
  tag.div(str.html_safe + readmore_link('Show less'),
          class: 'readmore-whole', style: 'display:none;')
end

def readmore_part(str)
      tag.div(str.truncate(180, separator: /\s/).html_safe +
              readmore_link('Show more'),
      class: 'readmore-part')
end      

def readmore_link(link_txt)
  tag.span(link_to(link_txt, '#', class: 'readmore-link'),
           style: 'white-space:nowrap;')
end

And here is the javascript. You should make it fire on page load.

function readMore() {
  $('.readmore-item a.readmore-link').on('click', function(e) {
    e.preventDefault();
    parent = $(this).parents('.readmore-item');
    parent.find('.readmore-part').toggle(400);
    parent.find('.readmore-whole').toggle(400);
  });
};

Upvotes: 0

Sebasti&#225;n Palma
Sebasti&#225;n Palma

Reputation: 33420

You could create two divs, one with the truncated text, and one with the complete text, which won't be shown, if the user clicks on Read More, then you hide the first one, and show the second one. Same for Read Less:

<% if @major.glance.size > 250 %>
  <span class="truncated-paragraph-<%= @major.id %>">
    <%= truncate @major.glance, length: 250 %>
  </span>
  <span class="normal-paragraph-<%= @major.id %>" style="display: none;">
    <%= @major.glance %>
  </span>
  <a href="#" class="read-more-<%= @major.id %>">Read More</a>
  <a href="#" class="read-less-<%= @major.id %>">Hide</a>
<% else %>
  <%= @major.glance %>
<% end %>

<script type="text/javascript">
  $('[class^="read-more"]').click(function(element) {
    element.preventDefault()
    $(`.truncated-paragraph-${elId($(this))}`).hide()
    $(`.normal-paragraph-${elId($(this))}`).show()
  })

  $('[class^="read-less"]').click(function(element) {
    element.preventDefault()
    $(`.normal-paragraph-${elId($(this))}`).hide()
    $(`.truncated-paragraph-${elId($(this))}`).show()
  })

  function elId(element) {
    let elClassName = element.attr('class').split('-')
    return elClassName[elClassName.length - 1]
  }
</script>

Something like:

$('[class^="read-more"]').click(function(element) {
  element.preventDefault()
  $(`.truncated-paragraph-${elId($(this))}`).hide()
  $(`.normal-paragraph-${elId($(this))}`).show()
})

$('[class^="read-less"]').click(function(element) {
  element.preventDefault()
  $(`.normal-paragraph-${elId($(this))}`).hide()
  $(`.truncated-paragraph-${elId($(this))}`).show()
})

function elId(element) {
  let elClassName = element.attr('class').split('-')
  return elClassName[elClassName.length - 1]
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<span class="truncated-paragraph-1">
  Morbi non est nec mi vulputate varius vel ac mi. In efficitur bibendum nibh nec fringilla. Integer nec est blandit, ullamcorper leo ...
</span>
<span class="normal-paragraph-1" style="display: none;">
  Morbi non est nec mi vulputate varius vel ac mi. In efficitur bibendum nibh nec fringilla. Integer nec est blandit, ullamcorper leo iaculis, blandit dui. Suspendisse sem mauris, maximus quis porta elementum, fermentum in dolor. Curabitur egestas arcu ante. Praesent a efficitur leo. Proin molestie turpis in sapien porta varius. Sed nisl enim, blandit ac orci in, iaculis consectetur tellus.

Quisque sapien felis, gravida in leo eget, dictum tempus felis. Ut pulvinar ex nisi, et rutrum leo dignissim at. Integer facilisis facilisis odio. Quisque consequat, ex eu sodales posuere, orci tellus accumsan justo, vitae finibus turpis turpis et tortor. Praesent luctus consequat tortor vel egestas. Suspendisse finibus interdum varius. Curabitur facilisis aliquet diam ac aliquet. Phasellus in felis placerat, gravida velit at, pulvinar nulla. Mauris ut faucibus felis, vitae semper elit. Aenean vel tincidunt leo. Donec varius est a hendrerit eleifend. Maecenas iaculis porta tortor imperdiet blandit. Praesent fermentum mauris metus, eu pulvinar lectus euismod vitae.
</span>
<a href="#" class="read-more-1">Read More</a>
<a href="#" class="read-less-1">Hide</a>

Upvotes: 1

Martin
Martin

Reputation: 71

Change the inner if part to this:

<div class='textControl'><%= truncate(@major.glance, length: 250) %></div>
<div class='textControl' style='display:none;'><%= @major.glance %></div>
<%= link_to '...Read more', '', class: "read-more-#{@major.id} textControl" %>
<%= link_to '...Hide more', '', class: "read-more-#{@major.id} textControl" style='display:none;' %>
<script>
  $('.read-more-<%= @major.id %>').on('click', function(e) {
    e.preventDefault();
    $('.textControl').toggle();
  })
</script>

Just a side note. It's not the best way to do it. Inline javascript is a code smell. It should be extracted to a separate file and written in a universal way. You can use jQuery's siblings method to only hide siblings rather then all textControl objects.

Upvotes: 0

Related Questions