Thursday, April 24, 2014

Enable or disable custom ribbon button in SharePoint 2013 based on List Item Property [ Field Value ]

You would come to some scenarios in SharePoint where you want to create a ribbon custom action to perform some custom tasks to meet your business needs.

Creating a custom action in SharePoint 2013 is not different than SharePoint 2010, and there are a lot of articles explaining how to create a custom action.

Examples:

  1. SharePoint 2010 Custom Ribbon Button
What about disabling this button when a specific condition is true, The most common example you will find is disabling the ribbon button while more than on list item is selected.
  1. CommandUIHandler Element
  2. Enable or disable custom ribbon button in SharePoint 2010
But what about disabling the custom action based on a field value in the currently selected list item, For example in the Check-in & Check-out buttons in the ribbon is disabled/Enabled based on the Document [Item] Status.

Ok, Then how would you apply the same idea based on your own custom field.

Here comes the magic of using CSOM and ECMA scripts to communicate asynchronously with current list, Getting the current list item fields, then deciding based on the field value if you will Enable/Disable the button.

In the Custom action elements.xml definition you will find a section with the following tag "CommandUIHandler", This tag has "EnabledScript" attribute, Where you can right javascript to return true if enabled, False if the button is disabled.

First you need to check if only one item is selected:


function singleStatusEnable() {
    try{
        var selecteditems = SP.ListOperation.Selection.getSelectedItems();
        var ci = CountDictionary(selecteditems);

        if (ci == 1) {
            return CheckStatus(selecteditems);
        }
        else {
            return false;
        }
    }
    catch (ex) {
        alert('Error occurred: ' + ex.message);
        return false;
    }
}


Then we will have the following plan, We will create a global window variable of array type, To maintain the values of the EnabledScript.

We will use the array index as the ItemID and the value will be either true or false, Why we will do this ?? Simply to not have to check the Item Field value each type the user check/uncheck the item, as each time the item is checked or unchecked the method "RefreshCommandUI()" is called which re-validates all the ribbon buttons to decide wither to enable or disable them according to current selected Item.

If the global window variable is not defined we will initialize it - This will happen only with first selected item - Then we will check if the current item ID already exists in our array if yes we will return the value if not we will check the value asynchronously, after we get the response back from the server we will call the "RefreshCommandUI()" method to re-validate the ribbon buttons


function CheckStatus(selectedItems) {
    //Get Current Context
    var clientContext = SP.ClientContext.get_current();
    //Get Current List
    var currentList = clientContext.get_web().get_lists().getById(SP.ListOperation.Selection.getSelectedList());
    //Get Selected List Item
    var ItemId = selectedItems[0].id;

    //Check if the window global array variable was initialized or not 
    if(window.FolderStatusValue === undefined) {
        window.FolderStatusValue = new Array();
    }
    
    //Check if the current selected ID was previously saved if not Get the Item status and refresh the UI
    if (window.FolderStatusValue[ItemId] === undefined) {
        singleItem = currentList.getItemById(ItemId);
        clientContext.load(singleItem);
        clientContext.executeQueryAsync(Function.createDelegate(this, OnSucceeded), Function.createDelegate(this, OnFailed));
        return false;
    }
    
    //Return the saved value
    return window.FolderStatusValue[ItemId];
}

//When the Async request is completed save the Item value in the array and re-call RefreshCom//mandUI() method
function OnSucceeded() {
    
    var selecteditems = SP.ListOperation.Selection.getSelectedItems();
    var ItemId = selecteditems[0].id;

    var ItemStatus = singleItem.get_item('YOUR-CUSTOM-COLUMN-STATIC-NAME');
    
    
    if (ItemStatus) {
        window.FolderStatusValue[ItemId] = true; //Enable Ribbon button
        RefreshCommandUI();
    }
    else {
        window.FolderStatusValue[ItemId] = false; //Disable Ribbon button
    }
}


function OnFailed(sender, args) {
    alert('Error occurred: ' + args.get_message());
    return false;
}


Here is the full XML definition for the Custom Action  :


<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction Id="8d2b47c5-9e1a-4ff2-9a90-071632a0e9db.ShareFolderExternal"
                RegistrationType="ContentType"
                RegistrationId="0x0120001D4A61CCFCF04620B4F487A48EABBD52"
                Location="CommandUI.Ribbon"
                Rights="AddListItems,DeleteListItems,EditListItems">
    <CommandUIExtension>
      <CommandUIDefinitions>
        <CommandUIDefinition
         Location="Ribbon.Documents.Share.Controls._children">
          <Button
           Id="8d2b47c5-9e1a-4ff2-9a90-071632a0e9db.ShareFolderExternal.Button"
           Command="ShareFolderExternally"
           Image16by16="/_layouts/15/images/Share16x16.png"
           Image32by32="/_layouts/15/images/Share32x32.png"
           LabelText="$Resources:DocumentSharing,ShareFolderCA;"
           TemplateAlias="o1"
           Sequence="11" />
        </CommandUIDefinition>
      </CommandUIDefinitions>
      <CommandUIHandlers>
        <CommandUIHandler   Command="ShareFolderExternally"
                            CommandAction="Javascript:
                                            function Operation(dialogResult, returnValue)
                                            {
                                              SP.UI.Notify.addNotification('Successfully Done!');

                                              SP.UI.ModalDialog.RefreshPage(SP.UI.DialogResult.OK);
                                            }
                
                                            var webURL = _spPageContextInfo.webServerRelativeUrl;
                                            var selecteditems = SP.ListOperation.Selection.getSelectedItems();
                                            
                                            var ItemId = selecteditems[0].id;
                                            
                                            var options = {
                                                            url: webURL + '/_layouts/15/Progress.aspx?FolderId=' + ItemId + '&amp;ListId={ListId}',
                                                            title: 'Share Folder Externally',
                                                            allowMaximize: false,
                                                            showClose: true,
                                                            width: 400,
                                                            height: 100,
                                                            dialogReturnValueCallback: Operation
                                                          };
                                            SP.UI.ModalDialog.showModalDialog(options);"
                              EnabledScript="javascript:
                              
var singleItem;

function singleStatusEnable() {
    try{
        var selecteditems = SP.ListOperation.Selection.getSelectedItems();
        var ci = CountDictionary(selecteditems);

        if (ci == 1) {
            return CheckStatus(selecteditems);
        }
        else {
            return false;
        }
    }
    catch (ex) {
        alert('Error occurred: ' + ex.message);
        return false;
    }
}

function CheckStatus(selectedItems) {
    var clientContext = SP.ClientContext.get_current();
    var currentList = clientContext.get_web().get_lists().getById(SP.ListOperation.Selection.getSelectedList());

    var ItemId = selectedItems[0].id;

    if(window.FolderStatusValue === undefined) {
        window.FolderStatusValue = new Array();
    }
    
    if (window.FolderStatusValue[ItemId] === undefined) {
        singleItem = currentList.getItemById(ItemId);
        clientContext.load(singleItem);
        clientContext.executeQueryAsync(Function.createDelegate(this, OnSucceeded), Function.createDelegate(this, OnFailed));
        return false;
    }
    return window.FolderStatusValue[ItemId];
}

function OnSucceeded() {
    
    var selecteditems = SP.ListOperation.Selection.getSelectedItems();
    var ItemId = selecteditems[0].id;

    var ItemStatus = singleItem.get_item('YOUR-CUSTOM-COLUMN-STATIC-NAME');
    
    
    if (ItemStatus) {
        window.FolderStatusValue[ItemId] = true;
        RefreshCommandUI();
    }
    else {
        window.FolderStatusValue[ItemId] = false;
    }
}


function OnFailed(sender, args) {
    alert('Error occurred: ' + args.get_message());
    return false;
}

singleStatusEnable();
" />
      </CommandUIHandlers>
    </CommandUIExtension>
  </CustomAction>
</Elements>


Hope you find this article useful, Happy SharePointing :)

Sunday, April 20, 2014

SharePoint 2013 - Document Library and Custom Field Type

We created a very simple custom field control to filter some custom data from SQL database, and the code behind is only the necessary constructors and one overload. We created a custom content type that inherits from Folder, and associate our site column/field type to the new content type. We then add that content type to a Document Library, and when we attempt to create the new 'folder' (our content type), it has only two columns: Name and My Custom Field. When we try to save it, Name blanks out and doesn't give us any information, validation error or exception. It just won't allow us to save. Weird !!!!

After some googling and search I've found that great blog Custom field types and rendering templates correlation with the new “Server Render” property of the ListFormWebPart .

In this article you will find that the problem is that my custom field type is using Server Rendering template, While SharePoint 2013 document library ListFormWebPart is using CSR [Client-Side Rendering] .

Then you have a solution, by following the Sridhar's article by editing the New & Edit form for your library changing the ListFormWebPart , find the “CSR Render Mode” option under “Miscellaneous” section.  Just choose “Server Render (ServerRender)” for the “CSR Render Mode” option .

But what about automating this process, In  my scenario I had a SharePoint Project including the field type, content type, Document library Template and List definition. All connected together

I had a web scoped  feature to provision this document library on containing this Field type.

So what I've to do to make all this work, Is to specify  Sridhar's solution in the feature activated event receiver, The below code snippet is what you have to do to get that done:




private void ChangeSentDocumentListFormWebPart(SPList SentDocLibList)
        {
            SPDocumentLibrary SentDocLib = (SPDocumentLibrary)SentDocLibList;
 
                        // Update forms
            foreach (SPForm spForm in SentDocLib.Forms)
            {
                if (spForm.Url.Contains("DispForm.aspx") || spForm.Url.Contains("EditForm.aspx") || spForm.Url.Contains("Upload.aspx"))
                {
                    string fileURL = SentDocLib.ParentWeb.Url + "/" + spForm.Url;
                    SPFile page = SentDocLib.ParentWeb.GetFile(fileURL);
 
                    using (SPLimitedWebPartManager lwpm = page.GetLimitedWebPartManager(PersonalizationScope.Shared))
                    {
                        try
                        {
                            // Enable the Update
                            lwpm.Web.AllowUnsafeUpdates = true;
 
                            // Check out the file, if not checked out
                            SPFile file = lwpm.Web.GetFile(fileURL);
                            if (file.CheckOutType == SPFile.SPCheckOutType.None)
                                file.CheckOut();
 
                            // Find the ListFormWebPart and Update the Template Name Property
                            foreach (System.Web.UI.WebControls.WebParts.WebPart wp in lwpm.WebParts)
                            {
                                if (wp is Microsoft.SharePoint.WebPartPages.ListFormWebPart)
                                {
                                    Microsoft.SharePoint.WebPartPages.ListFormWebPart lfwp =
                                        (Microsoft.SharePoint.WebPartPages.ListFormWebPart)wp.WebBrowsableObject;
                                    lfwp.CSRRenderMode = CSRRenderMode.ServerRender;
                                    lwpm.SaveChanges(lfwp);
                                }
                            }
 
                            // Update the file
                            file.Update();
                            file.CheckIn("System Update");
 
                            // Disable the Unsafe Update
                            lwpm.Web.AllowUnsafeUpdates = false;
                        }
                        finally
                        {
                            if (lwpm.Web != null)
                            {
                                lwpm.Web.AllowUnsafeUpdates = false;
 
                                lwpm.Web.Dispose(); // SPLimitedWebPartManager.Web object Dispose() called manually
                            }
                        }
                    }
                }
            }
        }