Skip Ribbon Commands
Skip to main content
Jul 21
How do I convert my web parts into tabs?

Tabify.jpg

Here is a neat trick that can make your team pages a lot more efficient.  Using jQuery and jQuery UI's tabs feature, we can automatically "tabify" all web parts contained within a web part zone. 

 

Prerequisites for this solution to work:

  1. This solution has been tested to work for SharePoint 2013 and SharePoint within Office 365 only.
  2. All web parts that you want to "tabify" should be placed within the same web part zone on your page.

 

List of Steps:

  1. Create a new .txt. file with the following code in it.  Upload the file somewhere on your web site.  For the sake of this example, I placed the file on my site's /siteassets/ folder.  Therefore, the URL will be /samples/siteassets/tabify.txt/

    <script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
    <script type="text/javascript" src="https://code.jquery.com/ui/1.12.0/jquery-ui.min.js"></script>
    <link rel="stylesheet" type="text/css" type="text/javascript" href="https://code.jquery.com/ui/1.12.0/themes/base/jquery-ui.css" >
    
    
    <script type="text/javascript">
    
    var inDesignMode = document.forms[MSOWebPartPageFormName].MSOLayout_InDesignMode.value ? document.forms[MSOWebPartPageFormName].MSOLayout_InDesignMode.value == "1" : false;
    var wikiInEditMode = document.forms[MSOWebPartPageFormName]._wikiPageMode ? document.forms[MSOWebPartPageFormName]._wikiPageMode.value == "Edit" : false;
    
    function tabify() {
    
       if (!inDesignMode && !wikiInEditMode) {
          
          $('.tabifyMe').each(function(){
             
                var tabifyMeDiv = $(this);
                var theZone =    tabifyMeDiv.closest(".ms-webpart-zone").length > 0 ?
                            tabifyMeDiv.closest(".ms-webpart-zone") :
                            tabifyMeDiv.closest(".ms-rte-layoutszone-inner");
                var WebPartDivIDs = [];   
                var tabsWebPartIDSuffix = tabifyMeDiv.closest(".ms-webpartzone-cell").attr('id').replace('MSOZoneCell_WebPart','');   //This will return something like WPQ2
                
                //Put the Web Part Title for all the Web Parts you wish to exclude from being tabbed into the array below.
                //These web parts will be moved above or below the tabs area (depending on the existance of the data-location attribute of your .tabifyMe div)
                //The excluded web parts will display in the same order specified in the array below
                var excludedWebParts = [];
                
                if($.trim(tabifyMeDiv.text()) != "") {
                   excludedWebParts = tabifyMeDiv.text().trim().split(",");
                   
                   //Finds webparts whose titles match the comma-separated list of titles inside the .tabifyMe div and pushes thir IDs to the array excludedWebParts
                   for (i = 0; i < excludedWebParts.length; i++) {
                      excludedWebParts[i] = theZone.find('.ms-webpart-titleText span').filter(function() {   
    return $(this).text() === excludedWebParts[i].trim() || $(this).text().indexOf('tabify.txt') > -1;
                      }).closest('div[id^=MSOZoneCell_WebPart]').attr('id');            
                   }
                }
                
                // A true value indicates the tabs will be placed ABOVE all the excluded web parts within zone.
                // A false value indicates the tabs will be placed BELOW all the excluded web parts within zone.
                var tabsOnBottom = tabifyMeDiv.attr('data-location') == "bottom";
                   
    
                //hide the webpart containing the '.tabifyMe' div
                $('#MSOZoneCell_WebPart' + tabsWebPartIDSuffix).hide();
                
                //for all web parts inside the web part zone, put them into the WebPartDivIDs array excluding the web parts inside the excludedWebParts array
                theZone.find('.ms-webpartzone-cell').each(function () { 
                   if($(this).attr('id') != 'MSOZoneCell_WebPart' + tabsWebPartIDSuffix &&
                   (($.inArray($(this).attr('id'),excludedWebParts)) < 0)) {
                      WebPartDivIDs.push($(this).attr('id').replace('MSOZoneCell_WebPart',''));
                   }
                 });
                
                tabifyZone(theZone, WebPartDivIDs);
                
                //Move the excluded webparts to above/below the tabs
                if(tabsOnBottom) {
                   for (var i = excludedWebParts.length; i-- > 0; )
                      $('#' + excludedWebParts[i]).prependTo(theZone);
                }
                else {
                   for (i in excludedWebParts)
                      $('#' + excludedWebParts[i]).appendTo(theZone);            
                }
          })
       }
    }
    
    function tabifyZone(zone, webPartIDs) {
       zone.wrapInner(   "<div class='tabsContainer'>" + 
                      "<ul class='tabsContainerInner'></ul>" +
                   "</div>");   
    
       for (index = webPartIDs.length - 1; index > -1; index--) {
          var title = webPartIDs[index];
          
          /*if($('#MSOZoneCell_WebPart' + title + ' div.ms-hide').length > 0)
             continue;*/
          zone.find(".tabsContainerInner").prepend('<li><a href="#Tab' + title + '" id="TabHead' + title + '">' +
          $('span#WebPartTitle' + title + ' .ms-webpart-titleText').text() + '</a></li>');
          $('div#MSOZoneCell_WebPart' + title).wrap('<div class="TabContent" id=Tab' + title + '></div>');
          $('#WebPart' + title + '_ChromeTitle').remove();
       }
       zone.find(".tabsContainer").tabs();
       zone.find('.tabsContainer #' + $('.ui-tabs-active').attr('aria-controls')).show();
    }
    
    _spBodyOnLoadFunctionNames.push("tabify");
    
    </script>
    <style type="text/css">
    .tabsContainer .ms-WPBorder {border:none;}
    .TabContent {margin-top:40px;}
    </style>
    
    Note: As you can see, the script already references the jQuery and jQuery UI libraries.  If your current website already references them elsewhere, then lines 1 and 2 of this script can be omitted.

  2. Reference the file you created in Step 1 in your page so that the code runs.  You can do this in one of two ways:

    • Create a new Content Editor Web Part on your oage that points to the file.  Please note that you must place this Content Editor Web Part in a separate zone than the one you wish to "tabify."  This solution works best if you only wish to "tabify" web parts in this page, and no where else in your site.

      OR

    • Change the .txt file extension to a .js file and reference in your master page.  This solution works best if you wish to "tabify" multiple pages within your site and don't want to create Content Editor Web Parts referencing the script for every page.

  3. Create a new Content Editor Web Part in the same zone as the other web parts you wish to "tabify."  This new CEWP should be located as the first web part in the zone.   This content CEWP would contain the following HTML code in it:
          
          
          <div class="tabifyMe"></div>
       
       

    For the sake of this example, we will call this div the .tabifyMe div.

  4. If there are web parts within the zone that you do not want to be included in the "tabify" process, simply list their web part titles inside the .tabifyMe div, in a comma separated list.  Doing this will leave the listed web parts alone and not include them within the tabs
          
          
          <div class="tabifyMe">Images</div>
       
       
    Tabify2.jpg

  5. If you are excluding web parts from being "tabified", but would like these excluded web parts to be shown above the tabs, simply include the attribute data-location=bottom in the .tabifyMe div
          
          
          <div class="tabifyMe" data-location="bottom">Images</div>
       
       
    Tabify3.jpg

Voilà!  I hope you enjoy making this work and feel free to leave a comment below!

Apr 04
How do I retrieve log files from a multi-server farm using the correlation ID and Merge-SPLogfile?

The dreaded Correlation ID! It is fine when you have a few servers in your farm. However, if you manage a huge farm finding the error can be time consuming Instead of searching every server in the farm logs to match the correlation ID, the Merge-SPLogfile cmdlet allows an admin to create one log file from the correlation ID. The cmdlet will search every server in the farm and match the correlation ID and make one single log file.

 

Here is an example:​

Merge-SPLogfile –Path "C:\SP2013\ErrorLogs\mergedlogs.log" –Correlation <Correlation ID>

 

When you receive the Correlation ID from a user outside the twenty minute window, you need to do provide a general start time/date and end time/date. In this example, the user tells me that Correlation ID was received around today at 1pm to 3pm.

Here is an example:

Merge-SPLogfile –Path "C:\SP2013\ErrorLogs\mergedlogs.log" –Correlation <Correlation ID>

-StartTime "03/18/2016 13:00" –EndTime "03/18/2016 15:00"

Mar 31
Creating Unique Identifiers in SharePoint 2013 Lists via Workflow
Screen Shot 2016-03-31 at 6.27.24 PM.png

Throughout many of our customer projects there have been multiple requests to deliver a unique identifier for lists and libraries.  These projects have been for internal help desk solutions, Goods and Services Request Centers, Case Management solutions, etc.  ​​I'd like to share one of many methods that we've used to deliver such capabilities with success.

​The Scenario

​For the purposes of this walkthrough let's assume that we're working on a Request list that takes various types of requests from employees and allows them to input some very basic information about their request.  Our goal is to allow members of our company to submit requests to a centralized repository and then to have their requests categorized and uniquely identifiable by way of the Title of the request.  We're going to implement this solution by developing a SharePoint 2013 workflow that updates the Task Name field on item creation with a concatenation of data from our item.  Specifically, we'll concatenate the Request Type, Created date, and the list ID number for our item.  The end result should look something like this example: [Purchase] - 3/31/2016 - 2 .


​​The Prep

To prep our Request list for the workflow and to provide a very basic level of demonstration content, we've configured our list with the following specs:

  1. ​​​​Create a Task list titled Requests
  2. Add Choice column titled Request Type
    • Choices are: Purchase, Support and Leave
  3. Edit the default view to Group By our field Request Type

Optional

I like to hide the Task Name field from my forms so that users don't feel that they can change the name of our list items. Instead, our workflow will generate this for us after it completes.​  Even though I hide this from our forms, the field is still easily accessible to list views and at the top of the forms as the title.

  1.  Go to your list settings > Advanced Settings and Allow Management of Content Types
  2. Then back on your list settings page, click on the Task content type and then on the Task Name column.
  3. Set the column to Hidden and click ok.

​The Nitty Gritty

​First thing we'll do is open up SharePoint Designer 2013 and connect to our site that contains or will contain the Requests list we'll be working with.  I've gone ahead and created a site called Workflow Demo Site and created a list called Requests based on the Task list template.

​Once you've connected Designer to your site, open up the list that you'll be working in.  i.e. Requests list.  


Screen Shot 2016-03-31 at 6.33.45 PM.png


​​Create The Workflow

​Create a new List Workflow associated with the Requests list.

The workflow designer will open up and you can begin entering the steps you want your workflow to walk through.  

  1. ​Let's begin with renaming the "Stage" of your workflow that may be something more meaninful than "Stage 1".  I've renamed the stage to Create Unique ID.  
    Screen Shot 2016-03-31 at 10.14.56 PM.pngStages can help you identify what stage of your workflow an item currently resides in if you have many.​

  2. Next let's establish some variables for our workflow.  In the ribbon you'll find a button labeled Local Variables.  Add the following
    1. Name: Date
      Type: Date/Time
    2. Name: SPID
      Type: Number

    Screen Shot 2016-03-31 at 11.15.46 PM.png
  3.  Then in the first row of your workflow you can insert an action to Set Workflow Variable.  Set Workflow Variable for Date and SPID.  Set:
    1. Date = Current Item : Created
    2. SPID =  Current Item : ID

    Screen Shot 2016-03-31 at 6.37.13 PM Edited.png
  4. Once you've set the variables, on the next line of your workflow, insert an action to Update List Item.
  5. Click on the highlighted "this list" text in your new workflow step and in the modal, make sure that Current Item is selected in the dropdown and click Add.
  6. In the Value Assignment dropdown you can select the field that you would like to make the container for your unique identifier.  We're using Task Name as the field to update.
  7. Then in the To This Value field, click the ellipsis and we'll begin creating our concatenation string of item data.
  8. In the modal window (String Builder) we'll click the Add or Change Lookup button and select Current Item : Request Type.  

  9. Then we'll add our variables as we created in the beginning.  Click on Add or Change Lookup again and this time change the Data Source dropdown to the Workflow Variables and Parameters choice.  The Field from source dropdown below will now be populated with your two variables created in step two.  Go ahead and add both of your variables to the String Builder window.  I selected Short Date for the format of my Date Variable and String for the format type of my SPID variable.
    Screen Shot 2016-03-31 at 6.38.21 PM.png Within the String Builder window you can type in any formatting you may want to build your unique identifier with.  For instance, I've wrapped the Request Type with brackets such that in my list view, the Request Type is easily identifiable.  You'll notice in the example, i've also formatted the space between each field being concatenated with dashes to add some seperation of data visually.
  10. In the Transition to stage section of your first workflow stage, go ahead and insert an action to Go To A Stage. 
  11. Now click outside the first stage box and you can add a new stage from the ribbon.  Give your new stage a name.  i.e. Complete.
  12. For the Create Unique ID stage, in the transition dropdown, choose to go to a stage: Complete.
  13. In the last stage (Complete), I've only added one action and that was to Log a message.  I've instructed it to log the message: "Updated Task Name: and inserted the Task Name field.  

    This allows us to review workflow history and identify possible breaking points if things go wrong.
  14. Finally, add the End Workflow to the transition to stage dropdown for our Complete stage.
  15. SAVE the workflow!

Pheww!  Much easier to do than type, I promise.  We have created all of the necessary steps and now we just need to ensure that the settings for the workflow trigger are set correctly.  

  1. Go back to the Workflow Details tab for our newly created workflow and ensure that the box is checked to Start workflow automatically when an item is created.
    Screen Shot 2016-03-31 at 6.34.38 PM.png​​
  2. Save... and Publish.

​The Test

Let's add a few sample items to our list and test the workflow.  Add a few items, one for each Request Type so that you can verify your view is working to group the requests by type and your Unique ID (Task Name) includes your Request Type in the resultant concatenated string.

​Conclusion

​We've built a unique identifier for SharePoint list items by way of a SharePoint Designer 2013 workflow.  Like I mentioned before, there a lot of methods for accomplishing this same functionality but I believe that working through this solution, has in my experience in the past, opened the eyes of young SharePoint designers to the potential that workflows and the functionality within designer contain.  Working with the workflow designer, setting variables, using workflow context and actions can quickly take a budding SharePoint user to Power user.  We hope that you find some good information in this post and look forward to any questions you may have regarding this or any other SharePoint features and solutions.


Mar 30
How do I create a parent/child relationship between list items?

The parent/child architecture is a very common pattern when creating new SharePoint projects.  It is often the case when you need to relate a list item as a "parent" of another list item.  Common examples include relating an employee to a company, or a car make to a car model.  Whatever the case, we can use some neat JavaScript/jQuery tricks to make these relations in SharePoint.

We will use the car make and car model  scenario for this example.

Parent-Child Form

Prerequisites for this solution to work:

  1. "Parent" and "children" entities are represented as either custom lists or libraries on your site.

    pc-lists.png
    Notice the "Car Make" and "Model" lists

  2. All lists/libraries where your "children" entities reside need to have a column named "PID" of type text.

    For the sake of this example, the parent list is Car Make and the Children list is Model.


List of Steps:

  1. Automatically display the listing of Models that belong to a given Car Make in its DISPLAY and EDIT forms.

    1. Create a new txt file containing the code blow.  Upload that text file anywhere you wish on your site.  For this example, I will name this text file loadForm.txt.
      <script type="text/javascript" src="/pathToYourjQueryLibrary/jquery.min.js"></script>
      <script type="text/javascript">
      
      _spBodyOnLoadFunctionNames.push("loadForm");
      
      function loadForm() {
      	$("#idHomePageNewItem").unbind().attr("href",$("#idHomePageNewItem").attr("href") + "&pid=" + getURLParameter("ID"));
      }
      
      //Retrive the value of a query string variable
      function getURLParameter(name) {
      	var match = RegExp('[?&]' + name + '=([^&]*)')
      				.exec(window.location.search);
      	return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
      }
      	
      </script>
      
      Note: Be sure to reference the correct path to your jQuery library on the first line.
      Note: This script ensures that when the user clicks on the "+ New Item" link to add a new child, the link passes the ID of this parent in a URL variable named "pid."

    2. Navigate to the DISPLAY item form of any Car Model record and edit the page
      pc-EditPage.png

    3. Click Add a Web Part, then under the Apps category choose the Model  list. 
      pc-addApp.png

    4. On the newly-added Model  web part, choose Connections > Get Filter Values From > Car Make.   
      pc-addConn.png

    5. For Provider Field Name, choose "ID."  For  Consumer Field Name, chose "PID"
      pc-Provider.png

    6. Click "Add a Web Part" once again, and add a Content Editor Web Part pointing to the loadForm.txt file you created in step 1. Ensure this web part has a Chrome Type value of "None" so that you don't get an unnecessary heading for it.

    7. Repeat steps 2 – 6 for the EDIT form of any record in the Car Make list.


  2. When creating a new "child" item, we need to automatically relate it to it's parent.

    1. Create another new txt file containing the code blow.  Upload that text file anywhere you wish on your site.  For this example, I will name this text file loadChildForm.txt.

      <style type="text/css">
      	#onetIDListForm {visibility: hidden;}
      </style>
      <script type="text/javascript" src="/samples/test/SiteAssets/jquery-git.js"></script>
      <script type="text/javascript">
      
      _spBodyOnLoadFunctionNames.push("loadChildForm");
      
      function loadChildForm() {
      	$('input[title="PID"]').val(getURLParameter('pid'));
      	$('input[title="PID"]').closest('tr').hide();
      	$('#onetIDListForm').css('visibility','visible');
      	
      }
      
      //Retrive the value of a query string variable
      function getURLParameter(name) {
      	var match = RegExp('[?&]' + name + '=([^&]*)')
      				.exec(window.location.search);
      	return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
      }
      	
      </script>
      
      Note: This script auto-populates the "PID" field with the value passing on via the "pid" URL parameter.  It also automatically hides the "PID" field in the form so that users don't tamper with it.

    2. Navigate to the NEW form of the Model list.

    3. Edit the page and add a new Content Editor Web Part that will point to the loadChildForm.txt file you created in step 1.  Ensure this web part has a Chrome Type value of "None" so that you don't get an unnecessary heading for it.
      pc-addCEWP.png

Final Thoughts

By using out-of-the-box features such as adding List View Web Parts and Web Part Connections, we are able to get the right list of children to display under a parent's form. With the assistance of URL parameter-passing and jQuery, we ensured that the "+ New Item" link on the children views relates the newly-created children to its parent via the "PID" column.

Mar 30
How do I hide the ribbon and/or the suite bar on my SharePoint 2013 or SharePoint Online site?

Screen Shot 2016-03-30 at 12.28.10 AM.png

The suite bar and ribbon in your SharePoint site holds some very useful functionalities, but there are cases where you either do not need it, or would like to hide it from your users for one reason or another.  Luckily there are several ways you can either hide them, or not have them render at all depending on who is logged in.

 

  1. Approach 1: Ommit the suite bar and ribbon from all pages on your site by using security trimming.
    This approach will only display the suite bar and ribbon for site admins.  That is, all users who have ManageWeb access to your site.
    (You must have access to edit to the site's master page to use this approach).

    1. Open the site master page

    2. Find the code block for the suite bar and contain it within a SPSecurityTrimmedControl tag

      • If using SharePoint 2013:

        <SharePoint:SPSecurityTrimmedControl ID="AnyIDYouChoose1" runat = "server" PermissionsString="ManageWeb" >
           <div id="suiteBar" class="ms-dialogHidden noindex">
               ......
           </div>
        </SharePoint:SPSecurityTrimmedControl>

      • If using SharePoint Online

        <SharePoint:SPSecurityTrimmedControl ID="AnyIDYouChoose2" runat = "server" PermissionsString="ManageWeb" >

        <SharePoint:AjaxDelta runat="server" id="suiteBarDelta" BlockElement="true" CssClass="ms-dialogHidden ms-fullWidth noindex">
            ......
        </SharePoint:AjaxDelta>
        </SharePoint:SPSecurityTrimmedControl>

    3. Find the code block for the ribbon and contain it within a SPSecurityTrimmedControl tag

      • <SharePoint:SPSecurityTrimmedControl ID="AnyIDYouChoose" runat = "server" PermissionsString="ManageWeb" >
            <div id="s4-ribbonrow">

                 ......
            </div>
        </SharePoint:SPSecurityTrimmedControl>


  2. Approach 2: Using CSS to hide the suite bar and ribbon
    This approach does not require access to the site's master page code.  The following lines of CSS code should do the trick:
      • If using SharePoint 2013

        #suiteBar {display:none;}
        #s4-ribbonrow {display:none;}

      • If using SharePoint Online

        #suiteBarDelta {display:none;}
        #s4-ribbonrow {display:none;}

        You can apply this CSS to your page(s) by wrapping them around <style></style> tags, saving the document as a .txt file, and referencing that documents in content editor web parts in whichever pages you want to apply this CSS too.

        Screen Shot 2016-03-30 at 12.29.22 AM.png
        Reference your CSS file in a new Content Editor Web Part

        Screen Shot 2016-03-30 at 12.30.43 AM.png
        Viola! The suite bar and ribbon are gone!
Feb 11
Validate that column contains a valid email address?
Tovalidate an email address using SharePoint field validation use this formula:
 
=NOT(AND( [E-Mail Address] <> "", ISERROR(FIND("@",[E-mail Address])<>0),ISERROR(FIND(".",[E-mail Address])<>0) ) )
 
This avoids common pitfalls and will still allow the field to be optional. Note that the column name in this case is "E-Mail Address" you need to change the to the name of the column you are validating.


1 - 4Next
© SharePointXperts . Powered by Office 365! Sign In