12/08/2018, 15:46

Tạo breadcrumbs đơn giản bằng content_for trong Rails

Breadcrumbs là gì chắc ai cũng biết, các trang web bây giờ hầu như đều có breadcrumbs, nó giúp người dùng dễ hình dung mình đang ở đâu trong trang web, giả dụ mình muốn tạo một breadcrumbs như thế này: Và đây là code HTML để tạo ra nó: <nav class="breadcrumb"> <a class="breadcrumb-it ...

Breadcrumbs là gì chắc ai cũng biết, các trang web bây giờ hầu như đều có breadcrumbs, nó giúp người dùng dễ hình dung mình đang ở đâu trong trang web, giả dụ mình muốn tạo một breadcrumbs như thế này: Và đây là code HTML để tạo ra nó:

<nav class="breadcrumb">
  <a class="breadcrumb-item" href="/"><i class="fa fa-home"></i> Home</a>
  <span class="breadcrumb-item ">Users</span>
  <span class="breadcrumb-item active">Edit</span>
</nav>

Có link đến trang Home, gạch chéo giữa các trang và ở trang hiện tại thì add thêm class active cho dễ nhìn. Rất đơn giản và không có gì đặc biệt, nhưng nếu cứ lặp đi lặp lại đoạn code này ở mỗi trang dùng breadcrumbs thì chắc chắn không phải cách hay và tối ưu. Thay vì thế mình sẽ tạo ra một file partial tên _breadcrumbs.html.erb đặt trong thư mục layouts. Tuy nhiên vẫn còn một vấn đề là breadcrumbs ở mỗi trang sẽ khác nhau, nên mình sẽ tìm cách truyền vào partial tên các trang mà mình muốn hiển thị trang breadcrumbs, tất nhiên phải theo đúng trình tự. Mình sẽ thêm vào đầu mỗi trang dòng code tương tự như thế này:

<%= content_for :breadcrumbs, ['Users', 'Edit'] %>

Giờ mình đã có mảng các trang muốn hiển thị và cần hiển thị nó trong breadcrumbs

<nav class="breadcrumb">
  <%= link_to icon('home', 'Home'), root_path, class: 'breadcrumb-item' %>
  <%= yield(:breadcrumbs) if content_for?(:breadcrumbs) %>
</nav>

Có vẻ như đã hoàn thành. Và đây là thành quả: ['Users', 'Edit'] không phải một mảng mặc dù nhìn nó khá là giống. Vậy ta cần đưa nó về đúng dạng mảng bằng cách sử dụng JSON.parse

<nav class="breadcrumb">
  <%= link_to icon('home', 'Home'), root_path, class: 'breadcrumb-item' %>
  <% breadcrumbs = JSON.parse(sanitize yield(:breadcrumbs)) if content_for?(:breadcrumbs) %>
  <%= breadcrumbs %>
  <%= breadcrumbs.class %>
</nav>

Và đây là kết quả: Class của breadcrumbs giờ đã đúng dạng mảng. Giờ ta chỉ cần chạy vòng lặp với mỗi phần tử của mảng:

<nav class="breadcrumb">
  <%= link_to icon('home', 'Home'), root_path, class: 'breadcrumb-item' %>
  <% breadcrumbs = JSON.parse(sanitize yield(:breadcrumbs)) if content_for?(:breadcrumbs) %>
  <% breadcrumbs.each do |breadcrumb| %>
    <%= content_tag :span, breadcrumb, class: "breadcrumb-item" %>
  <% end if content_for?(:breadcrumbs) %>
</nav>

Kết quả: Breadcrumbs gần như đã hoàn thiện, còn thiếu class active ở tên trang hiện tại. Giờ chúng ta sẽ thêm class active cho phần tử cuối cùng.

<nav class="breadcrumb">
  <%= link_to icon('home', 'Home'), root_path, class: 'breadcrumb-item' %>
  <% breadcrumbs = JSON.parse(sanitize yield(:breadcrumbs)) if content_for?(:breadcrumbs) %>
  <% breadcrumbs.each do |breadcrumb| %>
    <%= content_tag :span, breadcrumb, class: "breadcrumb-item #{breadcrumb.equal?(breadcrumbs.last) ? 'active' : nil}" %>
  <% end if content_for?(:breadcrumbs) %>
</nav>

Tèn tén ten, kết quả cuối cùng: Breadcrumbs giờ đã hoàn thiện đúng theo mong muốn! Có vẻ mục đích đã hoàn thành và nên kết thúc bài viết, nhưng sẽ tiện lợi hơn nếu có thể click vào các thành phần trong breadcrumbs và liên kết đến một trang khác. Đôi khi không phải tất cả thành phần trong breadcrumbs đều có một trang liên quan để hiển thị, vì vậy cần mở rộng để có thể hiển thị chữ không hoặc liên kết đến một trang khác. Ý tưởng sẽ là thay vì truyền vào một mảng các phần tử là tên các trang như vừa nãy, ta sẽ truyền vào các mảng bên trong mảng vừa nãy với phần tử thứ nhất là tên trang, phần tử thứ hai là link đến trang đó, nói không có lẽ hơi khó hiểu, ví dụ như thê này:

<%= content_for :breadcrumbs, ['Static 1',['Dynamic 1', root_path], ['Dynamic 2', 'https://google.com'], 'Static 2'] %>

Trong partial _breadcrumbs.html.erb ta sẽ thêm một case để check xem phần tử truyền vào là mảng hay chuỗi. Nếu phần tử đó là mảng, ta sẽ tách nó ra, tạo thành liên kết và hiển thị nó. Nếu chỉ là một chuỗi thì sẽ hiển thị bằng thẻ span như bình thường.

<nav class="breadcrumb">
  <%= link_to icon('home', 'Home'), root_path, class: 'breadcrumb-item' %>
  <% breadcrumbs = JSON.parse(sanitize yield(:breadcrumbs)) if content_for?(:breadcrumbs) %>
  <% breadcrumbs.each do |breadcrumb| %>
    <% case breadcrumb.class.to_s %>
      <% when 'Array' %>
        <% string, link = breadcrumb %>
        <%= link_to string, link, class: "breadcrumb-item #{breadcrumb.equal?(breadcrumbs.last) ? 'active' : nil}" %>
      <% when 'String' %>
        <%= content_tag :span, breadcrumb, class: "breadcrumb-item #{breadcrumb.equal?(breadcrumbs.last) ? 'active' : nil}" %>
    <% end %>
  <% end if content_for?(:breadcrumbs) %>
</nav>

Kết quả: Trông đẹp hơn nhiều đúng không? Và giờ khi muốn hiển thị breadcrumbs trên trang nào, mình chỉ cần sử dụng content_for: breadcrumbs và truyền vào mảng các phần tử muốn hiển thị. Ví dụ như:

<%= content_for :breadcrumbs, [['Users', users_path],'Edit'] %>

Đơn giản lại dễ sử dụng, chúc mọi người thành công!

Nguồn tham khảo: https://blog.driftingruby.com/hacking-content_for-to-create-a-simple-display-helper/

0