Building a Masonry Grid View with Bootstrap in Drupal 8

 

I’m in the process of moving my photography site into my current Drupal 8 site.  After a little thought I decided that I wanted to have my photos in a grid using a masonry layout (you know, the layout made famous by Pinterest).   The image attached to this article gives a quick demonstration.   On many sites this is accomplished using David DeSandro’s masonry.js.  

Drupal has a Masonry Api and a companion Masonry Views Module.  I installed the modules and they mostly worked but didn't quite meet my needs.  I wanted a little more control of the grid.  I wanted to have a responsive design with different number of columns based on screen size (1 or 2 columns on phones, 3 columns on tablets, 4 on large desktops).  Although my current design doesn't require it, I suspect  that down the road I might want certain images to span multiple columns.  I use bootstrap on my sites to make things simple so I didn't want to write custom CSS to manage column sizes.  I knew I could get the results I wanted with some work but I got to wondering, "how much work would it take to just integrate the library and write some JS".  I found the answer was under 10 lines of code and a little configuration work.

A quick google search led me to this codepen built by the developer of masonry.js which made things seem simple enough. 

So let's break down what I did:

  1. I already have a theme built which is using Bootstrap 3.  The theme's machine name and directory is seanreiser (replace seanreiser with your theme's machine name)
  2. Download the masonry library from here.
  3. Place masonry.pkgd.min.js in themes/seanreiser/libraries/masonry (so the file is located at themes/seanreiser/libraries/masonry/masonry.pkgd.min.js).
  4. Add the masonry library to the theme.  Add this code to my theme's seanreiser.libraries.yml :
    1. masonry:
    2.   js:
    3.   libraries/masonry/masonry.pkgd.min.js: {}
  5. Create a view:
    1. View Name : Photo Gallery
    2. Style : Unformatted List
    3. Settings -> Row Class -> col-xs-12 col-sm-6 col-md-4 col-lg-3
    4. Show: Fields
    5. Add the image to the field list.
  6. Attach the library to the views the view template  (themes/seanreiser/templates/view/views-view-unformatted--photo_gallery.html.twig)
    1. {#
    2. /**
    3.   * @file
    4.   * Default theme implementation to display a view of unformatted rows.
    5.   *
    6.   * Available variables:
    7.   * - title: The title of this group of rows. May be empty.
    8.   * - rows: A list of the view's row items.
    9.   * - attributes: The row's HTML attributes.
    10.   * - content: The row's content.
    11.   * - view: The view object.
    12.   * - default_row_class: A flag indicating whether default classes should be
    13.   * used on rows.
    14.   *
    15.   * @see template_preprocess_views_view_unformatted()
    16.   *
    17.   * @ingroup themeable
    18.   */
    19.  #}
    20.  
    21. {{ attach_library('seanreiser/masonry') }}
    22.  
    23. {% for row in rows %}
    24. {%
    25. set row_classes = [
    26. default_row_class ? 'views-row',
    27. loop.first ? 'views-row-first',
    28. loop.last ? 'views-row-last',
    29. ]
    30. %}
    31. <div {{ row.attributes.addClass(row_classes) }}>
    32. {{- row.content -}}
    33. </div>
    34. {% endfor %}
  7. To my site's themes/seanreiser/js/script.js I added:
    1. $('.views-row').masonry({
    2. itemSelector: '.views-row',
    3. percentPosition: true
    4. });
  8. Clear cache and rock and roll.

And it worked... mostly.  I had an issue where image overlapped occasionally.  The issue was that the grid would be initialized before the images were loaded.  Thankfully, David DeSandro has another library, ImagesLoaded, which detects if all your images have been loaded.  This required a couple of changes:

  1. I downloaded the library and put it in: themes/seanreiser/libraries/imagesloaded
  2. Add the imagesLoaded library to seanreiser.libraries.yml:
    1. imagesloaded:
    2.   js:
    3.   libraries/imagesloaded/imagesloaded.pkgd.min.js: {}
  3. Attach the library to the view (themes/seanreiser/templates/view/views-view-unformatted--photo_gallery.html.twig):
    1. {#
    2. /**
    3.   * @file
    4.   * Default theme implementation to display a view of unformatted rows.
    5.   *
    6.   * Available variables:
    7.   * - title: The title of this group of rows. May be empty.
    8.   * - rows: A list of the view's row items.
    9.   * - attributes: The row's HTML attributes.
    10.   * - content: The row's content.
    11.   * - view: The view object.
    12.   * - default_row_class: A flag indicating whether default classes should be
    13.   * used on rows.
    14.   *
    15.   * @see template_preprocess_views_view_unformatted()
    16.   *
    17.   * @ingroup themeable
    18.   */
    19.  #}
    20.  
    21. {{ attach_library('seanreiser/masonry') }}
    22. {{ attach_library('seanreiser/imagesloaded') }}
    23.  
    24. {% for row in rows %}
    25. {%
    26. set row_classes = [
    27. default_row_class ? 'views-row',
    28. loop.first ? 'views-row-first',
    29. loop.last ? 'views-row-last',
    30. ]
    31. %}
    32. <div {{ row.attributes.addClass(row_classes) }}>
    33. {{- row.content -}}
    34. </div>
    35. {% endfor %}
  4. Modify your JS to hold off on initially the grid until the images are loaded (themes/seanreiser/js/script.js)
    1. var $grid = $('.view-photo-gallery');
    2. $grid.imagesLoaded( function() {
    3. $grid.masonry({
    4. itemSelector: '.views-row',
    5. percentPosition: true
    6. });
    7. });

Now it's working just as I want.  Answers to some questions that I've been asked:

  • Why did you include the library manually instead of using the Masnory API module?
    Since I was using the library on one view in one theme, I didn't see the benefit in having the overhead of the module.  If, in the future I use masonry in other ways, I'll change this.
  • You mentioned photos spanning columns where is that?
    I haven't implemented that.  If I decide to do it I'll add a link here to the todo.
  • Where is the photography site?
    I'm in the middle of a major rebuild of a number of my sites.  Stay Tuned.
  • Update 4/24/2019 1:00AM EDT - You said less then 10 lines, there's a lot more there?
    My bad. views-view-unformatted--photo_gallery.html.twig is a copy of the default views-view-unformatted.html.twig with 2 lines added (the attach library). I am counting that as 2 lines.

Share and Enjoy!

    Posted: Apr 23, 2019
Posted: Apr 21, 2019

Clients are often surprised when I tell them we should make sure we change passwords before they future endeavor (aka fire) people. I'll be using this as an example in the future.

Posted: Apr 20, 2019

New rule: Documentarians are only allowed to use the "Ken Burns Effect" in their videos if David McCullough is the narrator.

Posted: Apr 20, 2019

You know at the beginning of the season, I was looking forward to a Yankee outfield of Fraizer, Tauchman, and Wade.  If Bernie didn't put in his retirement papers last year, he'd be next on the depth charge. #yankees #injuryBlues

Posted: Apr 20, 2019

I now answer the home phone "Hello, if this is about Solar Panels, this is an apartment take me off your list"

Posted: Apr 18, 2019

Thank you for coming to my TED talk on entry level social engineering and hacking.

Posted: Apr 18, 2019

I'm more interested in the trial of Lando and Wedge for he destruction of the Endor Death Star. As was pointed out in Kevin Smith's documentary, Clerks, unlike the Tattooine Death Star, the Endor Death Star was populated by mostly civilian workmen and other non-combatants.

Posted: Apr 18, 2019

Cast Iron Chicken with Spinach, Mushrooms and Tomatoes

INGREDIENTS

  • 4 Chicken Thighs
  • 10 oz. Fresh Leaf Spinach
  • 1/4 cup Marsala Wine
  • 1/3 cup Chicken Stock
  • 1/4 cup Heavy Whipping Cream
  • 3 tbsp. Parmesan Cheese
  • 1/4 tsp. Lemon Juice
  • 1 tbsp. and 1/2 tsp. Minced Garlic, Separated
  • 1/2 tsp. Paprika
  • 3/4 tsp. Ground Thyme
  • Dash Garlic Powder
  • Dash Sea Salt
  • Fresh Ground Black Pepper
  • 8 oz. Fresh White Mushrooms, chopped up.
  • 5 tbsp. Butter
  • 8 oz Cherry Tomatoes

INSTRUCTIONS

  1. Set oven to 400 degrees to pre-heat.
  2. Pre-heat a 12" cast iron pan to medium heat
  3. Season chicken with paprika, salt and pepper
  4. Add 3 tbsp of butter and 1/2 tsp of the minced garlic into heated pan.
  5. Add chicken quarters and brown the skin side for 7 minutes then the back side for 5 minutes. Once done, remove chicken from stove top and set aside.
  6. Remove most of the excess liquid from pan
  7. Add the other 2 tbsp of butter. Add 1 tbsp of minced garlic, marsala wine, pepper and chicken stock to pan and allow to cook for 1 to 2 minutes. Scrape off any burnt on pieces of chicken while it cooks.
  8. Add heavy whipping cream, Parmesan cheese, lemon juice, ground thyme and mushrooms (optional) then allow to cook for 2 minutes. Add fresh spinach and dash of garlic powder and allow the spinach to cook down for 2 minutes or until wilted.
  9. Place the chicken thighs back into the pan with ingredients with the skin side up and remove from heat. Add the tomatoes between the pieces of chicken. Place pan into the oven for 20 minutes.
  10. Remove from oven, allow to cool then serve
  11. Share and Enjoy!
    Posted: Apr 17, 2019