Thursday, September 25, 2014

SharePoint Online [Office 365] - Enable Anonymous users to Add items to list

I was recently implementing on SharePoint Online, which is a very good escape for content and publishing portals and Internet facing sites for Small & Medium enterprises which cannot afford the SharePoint Server licensing, with two alternative plans starting from 4$/month which is affordable for such business types.

We’ll have a series of articles of some challenges faced during the implementation to spread this knowledge, Specially that the term SaaS “Software as a Service” is trending and Microsoft is pushing towards Apps, cloud and for sure devices J.

The first Site Collections you have after registering for an Office 365 plan in SharePoint, SharePoint Public Site [Publishing Site], Team Site and search center with so many and many service applications configured and ready to go.

Mostly we will have interest for the SharePoint Public Site, as this is the site type that you can go online with, and enable anonymous users to access it, But in SharePoint online enabling anonymous access is just a button “Make this site online” J, You do not have the ability to set the anonymous access policy as you can do on SharePoint Server version, Even you cannot grant [Add Items], Even if you tried using SPO PS “SharePoint Online Power Shell”.

So a simple task like “Contact Us” form, creating a list with a form enabling users to add some data to the list would look like it is impossible. Then here comes the power of Apps J.

In SharePoint online the first thing you do before implementing any customization, have this Question “Is there an App for that??!” for most common tasks you will find an app for what you want to implement, Do not invest effort even time reinventing the wheel J.

Let’s get back on rails, What if the data we want to collect is a custom data, let’s get introduced to “Office 365 Anonymous Access / SharePoint 2013 Sandbox Solution” it is a Sandbox solution to manage anonymous access on Office 365 / SharePoint 2013.

OK. This is simple, Get the wsp, upload it to solution gallery then ……….. No , There is no solution gallery link under Site Settings J, Microsoft is hiding the link , But they did not prevent access to the page , So navigate to your Gallery by “ https://yourOffice365Domain-public.sharepoint.com/_catalogs/solutions/Forms/AllItems.aspx “ and yup you will be able to Upload the solution and activate it.

Note:  By default you will find this message “Your resource quota is 0 server resources. Solutions can consume resources and may be temporarily disabled if your resource usage exceeds your quota. “ message, So you need to allocate some server resources to the Public Site to be able to execute your packages:
  1. Go to the SharePoint Administration Panel
  2. Then select your public site collection, and select Server Resource Quota.
  3. Then assign some quota to the site collection
After activating the sandboxed solution, Navigate to your list and in the List tab, click on Anonymous Access

Then select Allow anonymous users to add items to this list

Now you can develop a form using SharePoint Apps, or Sandboxed solution to add the data to the list and anonymous users will be able to submit the data.

Note: I’ve tried to Using ECMA script for the form but it was not working with anonymous users, after having a call with Microsoft Support Engineer I was informed that is not allowed by Design !! , I am still investigating for it J



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
                            }
                        }
                    }
                }
            }
        }

Monday, January 27, 2014

SharePoint 2013 - Update Search Navigation Nodes for all Sitecollections & subwebs in your Web Application

Search navigation links represents the search result pages in SharePoint Search center. By default we have four search results pages in SharePoint search center, "Everything", "People", "Conversations", and "Videos" as shown in image below.

Those options are Shown as tabs in your search center



or as a drop down in your search box




But if you added your own result pages that are linked to specific result sources or query rules, You have to add the new pages to your search navigation settings per each webapplication

What if you have a Mulit-Site collection , Mullti-Webs Structure hierarchy … In the subweb search settings you will find that search settings have an option “Use the same results page settings as my parent”, But this will inhirit the search settings only not the search navigation links as the links are SPNavigationNode object,


So you will have to do this task manually…. Or just code it


I have written a powershell script file to update all the webs "DOWNLOAD FILE HERE", sites within your web application, In my case we have added new search results pages “Events”, “File Share” …etc…. So manipulate the URLs the titles to meet your needs

The Code:


function Update-SearchNav([string]$Identity)
{
 Write-Host -ForegroundColor Red "============================================="
 Write-Host -ForegroundColor Green "Updating Search Navigation at URL " -NoNewline;
 Write-Host -ForegroundColor Green $Identity

 $s = Get-SPSite $Identity
 $w = $s.RootWeb

 foreach ($w in $s.AllWebs) { 
  Write-Host -ForegroundColor Red "============================================="
  Write-Host -ForegroundColor Green "Updating Search Navigation at URL " -NoNewline;
  Write-Host -ForegroundColor Green $w.Url
  
  $SearchNav = $w.Navigation.SearchNav
  
  IF ($SearchNav -ne $NULL)
  {
   Write-Host -ForegroundColor Red "This Site Search Navigation Already containing values";
  }
  ELSE
  {
   Write-Host -ForegroundColor Red "Search Navigation was not found";
   
   Write-Host -ForegroundColor Green "Adding Search Navigation Everything";
   $Title = "Everything"
   $RelativeUrl = "/sites/SearchCentre/pages/results.aspx"
   $node = new-object -TypeName "Microsoft.SharePoint.Navigation.SPNavigationNode" -ArgumentList $Title, $RelativeUrl, $true
   $w.Navigation.SearchNav.AddAsLast($node)

   Write-Host -ForegroundColor Green "Adding Search Navigation Events";
   $Title = "Events"
   $RelativeUrl = "/sites/SearchCentre/Pages/events.aspx"
   $node = new-object -TypeName "Microsoft.SharePoint.Navigation.SPNavigationNode" -ArgumentList $Title, $RelativeUrl, $true
   $w.Navigation.SearchNav.AddAsLast($node)

   Write-Host -ForegroundColor Green "Adding Search Navigation People";
   $Title = "People"
   $RelativeUrl = "/sites/SearchCentre/Pages/peopleresults.aspx"
   $node = new-object -TypeName "Microsoft.SharePoint.Navigation.SPNavigationNode" -ArgumentList $Title, $RelativeUrl, $true
   $w.Navigation.SearchNav.AddAsLast($node)

   Write-Host -ForegroundColor Green "Adding Search Navigation Conversation";
   $Title = "Conversation"
   $RelativeUrl = "/sites/SearchCentre/Pages/conversationresults.aspx"
   $node = new-object -TypeName "Microsoft.SharePoint.Navigation.SPNavigationNode" -ArgumentList $Title, $RelativeUrl, $true
   $w.Navigation.SearchNav.AddAsLast($node)

   Write-Host -ForegroundColor Green "Adding Search Navigation File Share";
   $Title = "File Share"
   $RelativeUrl = "/sites/SearchCentre/Pages/FileShare.aspx"
   $node = new-object -TypeName "Microsoft.SharePoint.Navigation.SPNavigationNode" -ArgumentList $Title, $RelativeUrl, $true
   $w.Navigation.SearchNav.AddAsLast($node)

   Write-Host -ForegroundColor Green "Adding Search Navigation Videos";
   $Title = "Videos"
   $RelativeUrl = "/sites/SearchCentre/Pages/videoresults.aspx"
   $node = new-object -TypeName "Microsoft.SharePoint.Navigation.SPNavigationNode" -ArgumentList $Title, $RelativeUrl, $true
   $w.Navigation.SearchNav.AddAsLast($node)

   Write-Host -ForegroundColor Green "Adding Search Navigation This Section";
   $Title = "This Section"
   $RelativeUrl = $w.ServerRelativeUrl + "/_layouts/15/osssearchresults.aspx?u={contexturl}"
   $node = new-object -TypeName "Microsoft.SharePoint.Navigation.SPNavigationNode" -ArgumentList $Title, $RelativeUrl, $true
   $w.Navigation.SearchNav.AddAsLast($node)

  }
  Write-Host -ForegroundColor Red "============================================="
    } 
 
 $w.Dispose()
 $s.Dispose()
 Write-Host -ForegroundColor Red "============================================="
}

#TODO Add Your Web Application URL

$WebApplication = Get-SPWebApplication http://webapplicationurl


Foreach ($Sites in $WebApplication.Sites)
{ 
 Update-SearchNav($Sites.url.trim())
 Write-Host "Press any key to continue ..."

 $x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")

}

Sunday, January 26, 2014

Custom Sitemap provider in SharePoint does not show n-level :@

Another genius SharePoint  limitation or issue , If you want to implement your own custom site map provider, To implement your own Sitemap provider on SharePoint you will have to inherit from the following class :
  • -          Microsoft.SharePoint.Publishing.Navigation. PortalSiteMapProvider
  • -          Then you will have to override the following method
    • public override SiteMapNodeCollection GetChildNodes(SiteMapNode node)
  • -          After implementing your custom logic with a static or recursive algorithm (In my case was a recursive algorithm to get all navigation terms from Managed Metadata Service in a specific term set), You will find that only 2 levels are showing in the top navigation menu ….weiiiiird
  • -          Ok google it… you will find that this maybe an issue in the <SharePoint:Aspmenu> control in your masterpage which contains a property called MaximumDynamicDisplayLevels,,, OOOOH this maybe my life saver…. :@ but no SharePoint will not make your life that easy.
  • -          Playing around with this property and other properties like StaticDisplayLevels but in vain .
  • -          So what is happening in the background … Time to reflect some code
  • -          Reflecting the  Microsoft.SharePoint.Publishing.Navigation. PortalSiteMapProvider class … Here you will have following  surprise



-          Yes you are seeing it right, Microsoft engineers for some UNKNOWN reason had decided to HARDCODE the MaximumSupportedNodeDepth as a PROTECTED property to be UNACCESSABLE .
-          Now implement your logic but this time inherit for the classic System.Web.StaticSiteMapProvider implement the following methods:
o   public override SiteMapNode BuildSiteMap()
o   protected override SiteMapNode GetRootNodeCore()
-          You now have your own customized top navigation site map provider supporting n-level hierarchy


-          Happy SharePointing :)

Tuesday, April 23, 2013

SharePoint 2013 Multilingual User Interface (MUI) Switcher

In SharePoint Foundation 2010, when someone navigates to a multilingual website, the website uses the Accept-Language header that the client browser sends with the HTTP request to determine the language in which to render the user interface. If the website does not support any of the languages specified by the browser, the default language is used as the display language
A multilingual website also displays a drop-down menu in the upper-right corner of the page, next to the user's name, where users can select a display language. When someone selects a language that is different from the current display language, the website switches to the new language. The user's preference is persisted in a cookie that is dropped on the client computer. The website gets the user's language preference from the cookie on subsequent visits to the site.



But In SharePoint 2013  the language switcher drop down is removed ... So We have tried to implement our own switcher with same concept in SharePoint 2010 - Reference the Changing the Display Language  - But trying to add the JavaScript was useless.

So we tried another approach ... Using an HTTP Module which interrupts the request in a very early stage - Reference this Page to see how to create and apply an HTTPModule to SharePoint  - .

The trick is the new SharePoint model for changing the display language  is by checking the user's language preferences ... Then adding those languages to the request header in the Accept-Language tag i.e. ar-SA,en-US or buy the language preferences the user configures in his SharePoint user profile. 
  
English Preference Only

Arabic then English Preference 

OK ... we will not reinvent the wheel ... The game plan is to interrupt the request and check for the cookies value - Assuming we have a cookie  preserving the current selected language - Adding the selected language at the to the request header using an HTTP Handler , Then setting the current thread culture to the selected language, By implementing the PreSendRequestHeaders event handler.

UPDATE: Thanks to Suleman, many people was facing some issues in this approach, He found using the PreRequestHandlerExecute event handler solves all issues

e.g context.PreRequestHandlerExecute +=context_PreRequestHandlerExecute;


Following is the code we used for achieving the above scenario by setting the language to arabic:



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Threading.Tasks;
using System.Threading;
 
namespace MUISwitcher
{
    class HTTPSwitcherModule : IHttpModule
    {
        #region IHttpModule Members
 
        public void Dispose()
        {
        }
 
        public void Init(HttpApplication context)
        {
            context.PreRequestHandlerExecute +=context_PreRequestHandlerExecute;
        }
 
        void context_PreRequestHandlerExecute(object sender, EventArgs e)
        {
            HttpApplication httpApp = sender as HttpApplication;
            HttpContext context = httpApp.Context;
            string httpUrl = context.Request.Url.ToString();
 
            //TODO:Get the selected value for the current culture form the cookie i.e. ar-SA and 
            //set the Header and the CurrentCulture to the aquired value
 
            var lang = context.Request.Headers["Accept-Language"];
 
            if (!lang.Contains("ar-SA"))
                context.Request.Headers["Accept-Language"] = "ar-SA," + context.Request.Headers["Accept-Language"];
 
 
            var culture = new System.Globalization.CultureInfo("ar-SA");
 
            Thread.CurrentThread.CurrentCulture = culture;
            Thread.CurrentThread.CurrentUICulture = culture;
        }
 
        #endregion
    }
}

Sunday, April 14, 2013

Publishing Page Layout Image Field Disappears when the page is Published

I was facing this problem that when Publishing Page Layout the Image Field Disappears when the page is Published and I found out that the page layout I was using was not associated with the custom content type, as I was using SharePoint designer to copy the layout from one environment to another one, So I opened the master page gallery and edited the properties for this layout and re-associated it with the content type , and everything is working OK right now