Skip to main content

Promoted Links - Wrap and size tiles with Client Side Rendering


SharePoint 2013 introduces Promoted Links list and the web part is unbelievable hit with my client users. Anyone who has seen it wants it in their team/portal sites.

With increase in usage comes new requirements. And so the requirement did come, for reducing the size of promoted link tiles just so it fits into a web part zone of a custom page layout that was being used. User was adding 3 tiles and the third tile was displayed only partially and a header with scroll buttons was displayed for navigation. Users would prefer to see full 3 tiles in the row. If there are more than 3 items in the list, then they would prefer that the tiles be wrapped to the next row.  

Picture below shows out of the box Promoted Links output. There are 6 items in the list. Notice that the Green tile is truncated:











To display the 3 full tiles in a row within the web part zone required that the tile size be reduced. Promoted links are rendered using Client Side Rendering Template (CSR): SP.UI.TileView.js. The tile size is hard coded as 150px within the script. The script has the logic to display the header div with the navigation if there are more items than that can fit. Research on the Internet brought up couple of different implementations for wrapping and sizing the tiles. While these posts gave me good lot of information, on analysis, were limiting in one way or other. I wanted an approach that would let TileView.JS render the HTML and later modify that HTML using a CSR Template. My quest resulted in the CSR code below. Using the CSR gives me the advantage of the context, which, I can use to find the WPQ value, and build selectors with respect to that WPQ. This way I do not have to hard code for a WPQ, making the script reusable without having to modify and I can support multiple instances of the list/web part on a single page.

To learn about Client side rendering with JS Link feature of SharePoint 2013 see: SharePoint2013 JS Link Tutorial.

JavaScript template code is as below. Notice that JavaScript file does not render the content like, hmm, may be other CSR JS files that you might have come across. Instead, I want SharePoint to do the rendering and then manipulate the HTML.

The JavaScript starts with CSR overrides to take care of MDS enabled sites. MDS feature stores a list of JavaScript files that are already executed and on subsequent page refresh MDS will not execute it again. This will cause the web part to look different from the one when the page was first loaded. For more information on MDS enabled sites and the effect it would have on JS file, see Sridhara's blog "RegisterCSR-override on MDS enabled SharePoint 2013 site".

Override registration section is  pretty standard except that there is no template generating HTML markup. I have a "post render handler" and that is where all the actions happen. I register for ListTemplateType of 170 which represents Promoted Links.

CSR JS Template code:
RegisterModuleInit("/_catalogs/masterpage/MyJSFiles/PromotedLinksTileManagement.js", RegisterPromotedLinksOverride); // Let us take care of MDS enabled site RegisterPromotedLinksOverride(); function RegisterPromotedLinksOverride() { if (typeof SPClientTemplates === 'undefined' || SPClientTemplates === null) return; // Setup the template override var overrideCtx = {}; overrideCtx.Templates = {}; overrideCtx.BaseViewID = 1; // 170 for PromotedLinks overrideCtx.ListTemplateType = 170; // Register endHTML render handler overrideCtx.OnPostRender = PostRenderHandler; // Register for template override SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideCtx); } function PostRenderHandler(ctx) { try { // The number of tiles to be displayed in a row var tilesPerRow = 3; var rowPosition = 1; // Initialize row position var tilesInCurrentRow = tilesPerRow; var startHTML = "<tr><td><div class='ms-promlink-body' id='promlink_row_"; var endHTML = "'></div></td></tr>"; // Get the wpq of the web part. This will be used to identify the node with id var wpq = ctx.wpq; // Selector for header element var selector = "#promotedlinksheader_" + wpq + " .ms-promlink-headerNav"; // Remove the header for we do not need it $(selector).empty(); selector = "#script" + wpq + " .ms-promlink-root > table > tbody:last"; var linksBody = "#promotedlinksbody_" + wpq; // Get the total number of list items var numberOfTiles = ctx.ListData.LastRow; // BEGIN: Rearrange the tiles ... if necessary if (numberOfTiles > tilesPerRow) { for (i = tilesPerRow + 1; i <= numberOfTiles; i++) { // Add a new row , if we have reached the maximum number of links to show per row // Handle the first row if (tilesInCurrentRow == tilesPerRow) { rowPosition++; // Create a new row of links $(selector).append(startHTML + rowPosition + endHTML); // Reset the number of links for the current row tilesInCurrentRow = 0; } // Move the Nth (numberOfLinksPerRow + 1) div to the current table row $(linksBody + ' > .ms-tileview-tile-root:nth-child(' + (tilesPerRow + 1) + ')').appendTo($('#promlink_row_' + rowPosition)); // Increment the number of links in the current row tilesInCurrentRow++; } } // END: Rearrange the tiles // BEGIN: Reducing tile size // This is optional. Use only if the tile size needs to be fit into a particular size // Resize the tiles to 140px. Resize few different places. Also need to adjust the line spacing and // paddings and margins var newTileSize = 140; var newTileSizeString = newTileSize.toString() + "px"; var tileRoot = newTileSize + 10; var tileRootString = tileRoot.toString() + "px"; $(".ms-tileview-tile-root").css("width", tileRootString).css("height", tileRootString); $(".ms-tileview-tile-content").css("width", newTileSizeString).css("height", newTileSizeString); $(".ms-tileview-tile-detailsBox").css("width", newTileSizeString).css("height", newTileSizeString); $(".ms-tileview-tile-content").find("img").css("width", newTileSizeString); $(".ms-tileview-tile-detailsListMedium").css("height", newTileSizeString) // Reduce the margins and padding so the collapsed and details display show up aligned $(".ms-tileview-tile-detailsListMedium").css("margin-right", "5px") $(".ms-tileview-tile-titleTextMediumCollapsed").css("padding-left", "0px"); $(".ms-tileview-tile-titleMedium.ms-tileview-tile-titleMediumCollapsed").css("padding-left", "0px"); // When the tile is resized, a line of text is partially visible. We can address that by reducing the line spacing $(".ms-tileview-tile-titleTextMediumCollapsed").css("line-height", "18px"); // END: Reducing tile size } catch (err) { console.error(err); } }
 
JavaScript template property settings:



















 

Advantage of CSR is the context that is passed along. I make use of the context within "post render handler" to get the WPQ number of the web part. The selectors are built with respect to that WPQ. This way I do not have to hard code for a WPQ, making the script reusable without having to modify and I can support multiple instances of the list/web part on a single page.  Selectors are used to get a reference to appropriate elements and adding additional rows if need be. If your requirement is to wrap the tiles to new rows, then stop after the if block (//END: Rearrange the tiles).

The tiles effect is brought about by overlapping with details box. In my case I had to reduce the tile size to 140px to fit 3 tiles into a single row of the web part zone to display all 3 tiles completely. "Tile root" sets up the spacing between the 2 tiles. By default this is 10px more than the tile width. And so I have made necessary adjustments to tile root, padding, margin etc., so the text and the details box fits nicely with the readjusted tile size. Fire bug or IE developer tools comes very handy to investigate and make necessary adjustment to get the final result as desired.

Now if my JS file is not rendering the content then what is! As I mentioned earlier, I wanted the tiles to be rendered by SP.UI.TileView.js. The key to make this work is piping the JS Link files. Edit the Promoted Links web part and set the JS Link property as below:
sp.ui.tileview.js | ~sitecollection/_catalogs/masterpage/MyJSFiles/PromotedLinksTileManagement.js

Notice the piping, which is supported by JS Link. This will download and run SP.UI.TileView.js before downloading and applying the custom CSR JS file for the promoted links web part. In my case, I created a folder "MyJSFiles" within Master page gallery to store all of my JS Templates. Change this per your needs and reflect the changes within the script as well.

Enjoy your newly arranged promoted links!



Promoted Links Web Part with custom CSR JS Link:






 


















About Me: I am an Architect/Consulting Manager at Lighthouse Computer Services - Microsoft Technology Group. I have over 20 years of experience in Software Development and recently wrapped up Architecture/Designing an Intranet Portal with SharePoint 2013 in O365 for a large retail client. Lighthouse Computer Services -  Microsoft Technology Group specializes in Microsoft Technology with extensive experience in Information Architecture, SharePoint Governance, Architecture, Design, Implementation, Installation, and Deployment.

Comments

  1. If I wanted to wrap the Promoted Link boxes based on browser size (via media queries or otherwise), would there be a way to make the "var tilesPerRow = 3;" responsive?

    ReplyDelete
    Replies
    1. Paul, my initial thought was that this might not work well because of the way MS is rendering Promoted Links. While writing my reply, I thought of a possibility so I will try this out this week and update here. Interesting thought!

      Delete
  2. Hi, thanks for the post. I've uploaded the js file to my site collection specifically
    /sites/NS/_catalogs/masterpage/MyJSFiles/PromotedLinksTileManagement.js. I have successfully linked this file to a promoted links list that contains 5 items which are displayed in tile view.

    The js file is affecting the display of the promoted link items which is good as it's a sign that the two are linked up correctly. However, instead of getting the tiles to wrap, the js file seems to be causing the tiles to be displayed as list items.

    Do you have any idea why this might be happening? I'm using SarePoint Online. Thanks in advance.

    ReplyDelete
    Replies
    1. Andrew, check if "Server Render" option is checked (Edit page -> Edit Promoted Links web part -> Miscellaneous section -> Server Render.
      Let me know how that goes, may be we can do a screen share and see what is happening. Thank you.

      Delete
    2. thanks for the reply Suresh. If I uncheck the "Server Render" option I do get a tiled view which is good. The thing is I still get no wrapping.

      On further inspection I found that in the console I was getting the following error:

      ReferenceError {stack: (...), message: "$ is not defined", popStackFrame: function}

      I don't suppose you know what's going on here. I was thinking that maybe the firewall at work was stopping the JavaScript files from loading and that's why I was getting this error... Next step is to check if I get the same error connecting from home.

      Thanks for the offer of a screen share, I may need to take you up on this.

      Delete
    3. The $ sign is a jQuery syntax. In the script, I have used JQuery selectors to access the DOM elements. You could add a reference to jQuery in your master page. If your implementation makes use of a different java script library then you could follow that syntax. Hope that helps.

      Delete
    4. Andrew, How did it work out for you? Do you need help?

      Delete
  3. I seem to have the same problem as Andrew.
    When I am in edit mode of the page, everything is rendered correctly. When I press SAVE to save the page, the list view is loaded. If I press EDIT PAGE again, the tiles are shown. I can't seem to get the tiles to show when I save the page. Any idea?

    ReplyDelete
    Replies
    1. That is interesting. Have you unchecked the "Server Render" property of the web part?
      Run the browser debugger (and/or Fiddler) and see if "sp.ui.tileview.js" as well as your .js file is coming down successfully.

      Delete
    2. Justin, we used to have this problem during our implementation of a intranet portal in Office 365. We could not find a reason why it would seem ok in the edit mode but not in the view mode. We used to delete the web part from the page, add it back and configure it again! And that used to work.
      You might want try that. Have you customized your master page? If so, is other web parts working fine?

      Delete

Post a Comment

Popular posts from this blog

AADConnect: Attribute-based Filtering

As a hands-on practice area lead, I get to deliver projects both directly as an architect, and indirectly as an Engagement Manger. In one recent project, one of my Higher Ed clients wanted to setup attribute-based filtering. The matter got escalated to me and I helped the client in setting this up. I documented the process to my client and thought there may be others who may find this helpful as well. In this article I will not be going into what  AADConnect  is and how to deploy the same. I will assume that you already know about  AADConnect  and possibly have deployed the same as well. One of the features of  AADConnect  is the ability to filter objects that are synched to Azure AD. The default and the recommended configuration are to sync all objects in all domains in the configured forest. There are cases, however, that requires us to filter the objects to be synched.  AADConnect  provides the following filtering options: Group-base...

Office 365 access: Enforcing VPN with ADFS

Recently, I was asked for possible solutions to enforce VPN connection to access Office 365. This seems odd at first, for this is against one of the tenets of Office 365, accessing service from anywhere and on any device. But then there is always a certain use case that needs to be addressed. In this case, the customer had deployed Office 365 and federated using ADFS, a textbook deployment with 2 ADFS server farm, and 2 WAPs in the DMZ. Within the Microsoft 365 world, Intune and Conditional access would enable for enforcing policies. However, that will also require the customer to acquire additional licenses beyond O365 E3, which my customer did not want to do. I compiled some of the possibilities with ADFS to enforce VPN connectivity. Although I do not recommend anyone to bypass the features, I want to share this out to get some feedback from the community, to see if this is such a common scenario, or if anyone implemented any of these or other cost-effective solutions...