Build a Better Photo Gallery for SharePoint using REST and Handlebars

Working with SharePoint’s REST API is pretty cool. Lately, I’ve been writing plenty of applications where the entire CRUD is involved; all using SharePoint lists behind the scenes. Meaning, the entire front end is entirely up to me to build. This way, I can use different JavaScript libraries and frameworks when building these interfaces.

Note that this will require the following files:

  • bootstrap.css
  • magnific-popup.css
  • jquery-1.12.0.min.js
  • jquery.magnific-popup.min.js

The plugin already includes the above files via CDN. The Plugin JavaScript and CSS is also included via RawGit – so changes may affect your working files.

View in Github


Today, let’s look at something pretty simple. Let’s build a photo gallery using SharePoint Picture library as the backend, together with Handlebars.js – a good Javascript templating system. With this knowledge, you will gain good understanding on how to select items with SharePoint’s REST API, which I happen to have a post on.


bspg2

Ready to get started? Roll up your sleeves and let’s write some code. Note that I will not go over the entire code base in this tutorial – just the meat of the logic.

Setup the Gallery

First we need to make sure we have a gallery to connect to. In SharePoint, you do this by going to “View All Site Content” and adding a new Picture Library. Note that for this demo I’m using SharePoint 2010 – I believe in newer versions you have to “Add an App” > “Photo Library“.

Be sure to note the name of your library:
sp-photo-gallery-3
Upload some images to your newly created library. Now we can continue and pull our images in the front end.

The Javascript

Create a Javasript file and name it sp-gallery.js. You can place it inside any SharePoint document library – and simply map to it using Windows Explorer so you can write to it just like you would as if it was a local file.

Let’s setup the wrapper for our gallery. We’re using the “Revealing Module” pattern so we can have methods that are public and private, as well as we keep our logic real tidy. We’re adding private variables inside our class so we can use it all over our code.

var gallery = function(list){
  var webUrl = _spPageContextInfo.webAbsoluteUrl;
  var listName = list;
  var limit = 20;
  var skip = 0;
  var total = 0;
}
Get the List Count

Our first method is to grab the number of photos in our library. This is important so we can decide whether to show our “show more” button or not. Note the use our our “webUrl” and “listName” variables in our method. Add this code inside our gallery wrapper. This method is internal, so we create a function declaration:

function getListCount(webUrl,listName) {
    var url = webUrl + "/_vti_bin/listdata.svc/" + listName + '/$count';
    var ajax = $.ajax({
        url: url,
        method: "GET",
        headers: { "Accept": "application/json;odata=verbose,text/plain" },
        error: function (data) {
          console.log('error in fetching list count');
          console.log(data.responseJSON.error);
        }
    });
    return ajax;
}

The above will return a “promise” object that we can use to manipulate later on. In the case above, it’s simply a number.

Get the Gallery Items

This method is the call to grab our list items. Again, we’re using jQuery’s “Ajax” method, returning a promise object.

function getListItems(webUrl,listName, itemId) {
    var url = webUrl + "/_vti_bin/listdata.svc/" + listName;
    if(itemId !== null){
      url += "(" + itemId + ")";
    }
    url += '?$top='+limit+'&$skip='+skip;
    var ajax = $.ajax({
        url: url,
        method: "GET",
        headers: { "Accept": "application/json; odata=verbose" },
        error: function (data) {
          console.log(data.responseJSON.error);
        }
    });
    return ajax;
  }

Compile with HandleBars

This method is taking the data that is returned from our ajax promise above, and compiling it with handlbars. We haven’t built our HTML yet – which contains the actual handebars template. Note that this is a public function, so we’re creating the function as a “function expression”:

var buildGallery = function(data){
      var source   = $("#photogallery").html();
      var template = Handlebars.compile(source);
      $('.photo-gallery-wrap').append(template(data));
      $('.popup').magnificPopup({
          type: 'image',
          gallery:{
            preload: [0,5],
            enabled:true
          },
          removalDelay: 300,
          mainClass: 'mfp-fade'
      });
  }

Also note that I’ve added a “popup” functionality which we’ve instantiated in the code above. We’re using magnificpopup.js for this awesome feature.

The “Show More” button

We’re only loading a certain subset of photos in our gallery. We will have a button in the bottom of our images that when clicked, will do another fetch to our list and add them below. Again, this is public, so it’s a “function expression”.

var showmore = function (){
      $('#showmore').addClass('fetching').html('Loading images...');
      skip = skip+limit;
      var ajax2 = getListItems(webUrl,listName, null);
      ajax2.done(function(data){
        buildGallery(data);
        $('#showmore').removeClass('fetching').html('Show More');
        if($('.thumbnail-wrap').length >= total){
            $('#showmore').hide();
        }
      });
      return false;
  }

This sort of acts like an “infinite scroll” functionality – but with a button. I’m not particularly fond of that automatic loading.

Initializing the Gallery

Now that our methods are in place, it is time to tie them all together and returning them so we can call them outside of our class. We do this by creating an “init” function.

var gallery = function(list){
//our private vars here...
var init = function(){
      var ajax1 = getListCount(webUrl,listName);
      var ajax2 = getListItems(webUrl,listName, null);
      ajax1.done(function(data){
        total = data;
        if(total <= limit){
          $('#showmore').hide();
        }
      });
      ajax2.done(function(data){
        buildGallery(data);
      });
  }
//the rest of the methods here...
//below returns our public methods for use:
 return {
    init : init,
    showmore : showmore,
    buildGallery : buildGallery
  }

Notice that we are only returning the public functions above. So we are keeping our variables and private functions inaccessible from the outside world. Let’s move on to our helpers:

HandleBar Helpers

These methods doesn’t have to be our gallery code. This can live outside, so it can be accessed by Handlebars. The code is namespaced using the Handlebars object. I’ll explain what the do below:

Handlebars.registerHelper('findGroup', function(data) {
    var remove = _spPageContextInfo.webServerRelativeUrl;
    var str = data.Path;
    return str.replace(remove+'/','');
});
Handlebars.registerHelper('imgSrc', function(data) {
    var url = _spPageContextInfo.siteAbsoluteUrl;
    url += data.Path;
    url += '/' + data.Name
    return url;
});
Handlebars.registerHelper('imgSrcThumb', function(data) {
    var thumb = data.Name;
    thumb = thumb.replace('.jpg', '_jpg.jpg');
    var url = _spPageContextInfo.siteAbsoluteUrl;
    url += data.Path;
    url += '/_t/' + thumb;
    return url;
});

We will use a “findGroup” helper inside our templates so we can “group” our photos together. This is what our “Magnificpopup” plugin requires – so when you click “next” or “previous” – it knows which photo to show.

The “imgSrc” helper simply returns the source of the images, while the “imgSrcThumb” returns the thumbnail path. Note that this helper only supports jpegs at the moment.

The CSS

We are simply displaying the thumbnails in a grid fashion, so our styles are quite basic. I’m using this inside a Bootstrap wrapper, so you can see the “.col” classes in our HTML later. Create a file and name it sp-gallery.css and add the code below:

.photo-gallery-wrap {
  overflow: hidden;
  margin-right: -30px;
}
.thumbnail {
  display: inline-block;
  width: 100%;
  height: 100px;
  background-position: center center !important;
  text-indent: -9999px;
}
.thumbnailHidden {
  display:none;
}
.showMoreWrap {
  clear:both;
  display:block;
}
#showmore {
  color:#fff;
}
#showmore.fetching {
  background:#AAD3AA;
  border:#9AC39A;
}
.thumbnail-wrap {
  margin-bottom: 25px;
}
.thumbnail-wrap.col-lg-3 {
  padding-left:0;
  padding-right: 30px;
}
@media(max-width:387px){
  .thumbnail-wrap.col-lg-3 {
    padding-right: 15px;
  }
  .col-xxs-6 {
    width:50%;
  }
  #showmore {
    display:block;
  }
}
@media(max-width:240px){
  .col-xxxs-12 {
    width:100%;
  }
}
/* overlay at start */
.mfp-fade.mfp-bg {
  opacity: 0;
  -webkit-transition: all 0.15s ease-out;
  -moz-transition: all 0.15s ease-out;
  transition: all 0.15s ease-out;
}
/* overlay animate in */
.mfp-fade.mfp-bg.mfp-ready {
  opacity: 0.8;
}
/* overlay animate out */
.mfp-fade.mfp-bg.mfp-removing {
  opacity: 0;
}
/* content at start */
.mfp-fade.mfp-wrap .mfp-content {
  opacity: 0;
  -webkit-transition: all 0.15s ease-out;
  -moz-transition: all 0.15s ease-out;
  transition: all 0.15s ease-out;
}
/* content animate it */
.mfp-fade.mfp-wrap.mfp-ready .mfp-content {
  opacity: 1;
}
/* content animate out */
.mfp-fade.mfp-wrap.mfp-removing .mfp-content {
  opacity: 0;
}

I’ve also added extra “col” classes for my own grid styles when the viewport is decreased. Also notice the .mfp classes are necessary for our magnific popup to work “magnificently”.

The HTML

Finally, this is the file that we add to any page of our SharePoint site. Create a text file – name it “sp-gallery.txt” and add the code below:

<script src="https://code.jquery.com/jquery-1.12.0.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/magnific-popup.js/1.0.1/jquery.magnific-popup.min.js"></script>
<script src="ADD-YOUR-PATH-HERE/sp-gallery.js"></script>
<script>
$(document).ready(function(){
  var photos = gallery('TestPhotoLib'); //change to your document library name
  photos.init();
  $('#showmore').on('click', photos.showmore);
})
</script>
<div class="photo-gallery-wrap"></div>
<div class="showMoreWrap">
<a id="showmore" class="btn btn-sm btn-success" href="#">Show More</a></div>

 
The above contains the references to the files we need.

We went ahead and create our gallery and pass in the name of our Photo Gallery above. We then initialize and add the “showmore” handler when our button is clicked.

Add our Handlebars template to the text file:

<script id="photogallery" type="text/x-handlebars-template">
{{#each d}}
<div class="col-lg-3 col-md-3 col-sm-3 col-xs-4 col-xxs-6 col-xxxs-12 thumbnail-wrap">
  <a href="{{imgSrc this}}" group="{{findGroup this}}" class="thumbnail popup" style="background:url('{{imgSrcThumb this}}');" >
    {{Name}}
  </a>
</div>
{{/each}}
</script>

 
The above template is what we need for our gallery to work. This code represents each tile that we will produce in our gallery.

Save this text file (along with the .js and .css) and add it to any SharePoint page. Add a Content Editor WebPart, and add a link to an external file – to our .txt file.


sp-photo-gallery-2
I usually edit the webpart to “NOT” show the chrome.

Final Result

Save the page and if everything works well, you should see something like below:

bspg

A simple photo gallery with popup and paging functionality. It surely looks better than the “out of the box” photo gallery that SP provides. Furthermore, you have complete ability to change the look as much as you want.

But the real value is the introductory lesson to working with SharePoint’s REST api. Be sure to stay tuned for more stuff like this.

Leave your comments below.

47 Comments

  1. Hi Michael,
    Thank you for this wonderful! This is my first rest app and was able to use it in SharePoint Online.
    Thanks a lot.

    Reply
  2. Hey Michael,
    Thanks for the awesome post. Can you please let me know how can I create a photo gallery which shows only black color as thumbnail and after clicking on it, it redirects to respective document library with different color icon.
    Thanks,
    Rishabh

    Reply
  3. It shows ‘gallery’ is undefined. Had queries in sequence of code and braces. Will it be correct or the brace for var gallery needs to be closed before??
    var gallery = function(list){
    //our private vars here…
    //init function
    //the rest of the methods here…
    }
    //below returns our public methods for use:
    return {
    init : init,
    showmore : showmore,
    buildGallery : buildGallery
    }

    Reply
  4. Hi Micheal,
    After I setup all the things in the sharepoint – changing the js,css and sp-gallery.js path.
    and put the text file to the CEWP, the only thing that came out was the square outline of the picture box (got 4 picture in the gallery). Therefore, the web show 4 square box with no image in it.
    when i check the box url link, it not the path of the image where it should be,
    Here what is the box url link: sharepoint.com/sites/subsite01/sites/subsite01/subsite02/Gallery/car.jpg
    Here is what I think it should be : sharepoint.com/sites/subsite01/subsite02/Gallery/car.jpg
    When I try to change the sp-gallery.js at the handlebars.registerHelper(‘imgSrc’,function(data)
    well nothing change..it still show the same url with that duplicate subsite.
    Thanks.

    Reply
    • Hi Ikki,
      view your webpage using Chrome and open the console. this will show you what’s going on – usually for broken images – it will show you where it’s looking, and you figure out how to fix.

      Reply
      • Michael I am getting a similar problem. The Chrome console is telling me it can’t find the thumbnail in the “_t” subfolder – do I have to generate thumbnails some how first?

        Reply
        • The document library you’re using is probably not a “Picture Library”. Try creating a “Picture Library” and add the photos there and point to it. SharePoint generates the thumbnails inside a “_t” subfolder

          Reply
    • can you go inside the same handlebars helper (imgSrc) and do a console log on what “data.Path” is? That is what’s causing the duplicate “subsite01/subsite02/”

      Reply
      • can you go inside the same handlebars helper (imgSrc) and do a console log on what “data.Path” is? Just write this line:
        console.log(data.Path);
        inside the helper, then do a refresh on the webpage and see the console.
        I think that’s what’s causing the duplicate “subsite01/subsite02/”

        Reply
      • Yes, data.path is always “/sites/mySite/myPictures” thus adding the doublicate site.
        Another question: Are you planning to add folder support? Would be awesome to use that clean gallery together with folder navigation.
        Great work, cheers

        Reply
  5. This doesn’t work for me unfortunately. Using SharePoint 2010 and followed instructions… all that appears on my page is the Show more button. Any ideas

    Reply
    • Use chrome so you can debug easier. Press F12 and check out the “console” tab. You will see JavaScript errors in there. Post your findings here and I’ll help you.

      Reply
  6. Hi Michael
    Thanks for your great work for this beautiful Gallery.
    Sadly I got the same problem on our SP2013 platform: “shows only squares on the page and after clicking on it,’The Image could not be loaded’ appears”. Press F12 debug console displayed “SCRIPT5: Access is denied.”
    Microsoft Dev Network:
    https://msdn.microsoft.com/query/dev12.query?appId=Dev12IDEF1&l=DE-CH&k=k(VS.WebClient.Help.SCRIPT5)
    Advise: “If you’re trying to call a REST API, refactor this call to your server-side code, then expose a new REST endpoint for your client-side scripts.”
    How can I create this REST-endpoint script including in your script?
    Every other resource is available locally.
    Thanks in advance

    Reply
    • What they’re referring to is writing it in c# – which is not covered in this tutorial. This is all front end code. Or you add more permissions to your gallery.

      Reply
  7. I replaced the line “return url” in .JS to “return url.replace(‘/sites/photos’,”);” (photos is my sitecollection) and now it works.
    I also added to Handlebars line #4 this “background-repeat: no-repeat;”
    And disabled SharePoint Minimal Download Strategy that can make troubles.
    Now it is working fine.

    Reply
  8. Getting this error for the popup:
    sp-gallery.js:46 Uncaught TypeError: $(…).magnificPopup is not a function
    at buildGallery (sp-gallery.js:46)
    at Object. (sp-gallery.js:20)
    at c (jquery.min.js:3)
    at Object.fireWith [as resolveWith] (jquery.min.js:3)
    at k (jquery.min.js:5)
    at XMLHttpRequest.r (jquery.min.js:5)
    Seems to work after a page reload but not at first post. Any ideas?
    Thanks

    Reply
  9. Hi i am able to generate gallery. But when I click the thumbnail image . The image could not be loaded message is coming . Please help

    Reply
  10. HI, i have this problem: Uncaught TypeError: Cannot read property ‘replace’ of undefined sp-gallery.js:104
    at Object. (sp-gallery.js:104)
    at eval (eval at createFunctionContext (handlebars.min.js:28), :8:111)
    at h (handlebars.min.js:27)
    at c (handlebars.min.js:27)
    at Object. (handlebars.min.js:27)
    at Object.eval [as main] (eval at createFunctionContext (handlebars.min.js:28), :6:31)
    at c (handlebars.min.js:27)
    at d (handlebars.min.js:27)
    at e (handlebars.min.js:28)
    at buildGallery (sp-gallery.js:47)
    shellplusg2m_73d3a900.js:29 Failed to execute ‘postMessage’ on ‘DOMWindow’: The target origin provided (‘https://portal.office.com’) does not match the recipient window’s origin (‘https://login.microsoftonline.com’).

    Reply
    • Hi All
      I’m getting the same problem as Andreas, take a look:
      Uncaught TypeError: Cannot read property ‘replace’ of undefined
      at Object. (sp-gallery.js:104)
      at eval (eval at createFunctionContext (handlebars.min.js:28), :8:111)
      at h (handlebars.min.js:27)
      at c (handlebars.min.js:27)
      at Object. (handlebars.min.js:27)
      at Object.eval [as main] (eval at createFunctionContext (handlebars.min.js:28), :6:31)
      at c (handlebars.min.js:27)
      at d (handlebars.min.js:27)
      at e (handlebars.min.js:28)
      at buildGallery (sp-gallery.js:47)
      Any suggestion to fix it? I’m using SharePoint Online.
      Thanks!

      Reply
      • this is an error in handlebars – so try to debug even before compiling the template. My hunch is some of the items do not have a value.

        Reply
  11. Failed to load resource: the server responded with a status of 404 ()
    /sites/myngn/_catalogs/masterpage/NGN/js/sp-gallery.js:64 error in fetching list count
    /sites/myngn/_catalogs/masterpage/NGN/js/sp-gallery.js:65 Object
    /sites/myngn/_vti_bin/listdata.svc/stockphotos?$top=20&$skip=0 Failed to load resource: the server responded with a status of 404 ()
    /sites/myngn/_catalogs/masterpage/NGN/js/sp-gallery.js:85 Object
    Any ideas?

    Reply
  12. Hi Michael,
    It is me again 😀 With more problems with new functions.
    So your code is generating an all white page where there are white scuares that are tumbnails, I can see the urls to images when hovering. When trying to click on any area that is supposed to be an tumbnail (not screated but area is) the /site/sitename/site/sitename issue is thrown at me.
    /sites/Site_name_Here/sites/Site_name_Here/List_name/img.jpg
    There is one /sites/Site_name_here/ to much…
    I tried to get around this but the images are linked that way no mather what.

    Reply
    • Press F12 – and look under “console” – you should see where the code is trying to pull the images. Just verify if this path is correct.

      Reply
  13. Hello, I just need to put this script twice in same page to show two galleries with different picture library, but it shows images from two picture library in same gallery. Kindly provide steps to do this

    Reply
  14. Did you not forget to link to your css file somewhere (in sp-gallery.txt for example: there is already reference to the sp-gallery.js file there) ?

    Reply
    • I found that the sp-gallery.txt on your github page does reference all the necessary files…just not the code on this page.

      Reply
  15. Hi Michael,
    This is really great, could you help me generating the pictures please, right now it’s generating blank thumbnail because the path is incorrect, I’m getting this /site/sitename/site/sitename then then the filename. I’m also missing the .jpg extention on the thumbnail part, I’m seeing undefined instead of .jpg.
    Thanks,
    Reggie

    Reply

Leave a Reply to Michael Soriano Cancel reply