Monday, July 26, 2010

SharePoint Document Library: Add document link or Link to a Document

In SharePoint, there’s a way to add document link. Many of use not aware of it. To add the document link you need to add “Link to a Document” Content type in your library. By default content type modification is disabled in any list/library. To enable editing content type you need to go to list/library settings. Once you have enabled the content type editing, you can add/remove content type.

Enable Content Type Modification in List/Library

To enable content type editing go to list/library settings page. Then from the list settings page,  Click “Advanced Settings” and in the advanced settings page, click “Yes” for option “Allow Management of Content Types”. Enabling this option will allow you to modify content type settings. Click Ok after selecting Yes option.

After coming back to list settings page you will find the content modifying option as  shown below:

image

Add Link To Document Content Type to the library

Now from the ‘Add from Existing site content types’ link as shown in the image above, you can add the “Link to Document” content type. click “Add from existing site content types” and from the page you have landed you can the content type as shown below:

image

After adding the content type go back to document library page. You’ll find that when you’ll try to add new item, you’ll get the ‘Link to a Document’ option as shown below:

image

With this link to document item, you can add links to content not only in sharepoint but non-SharePoint content from other sites.

Programmatically Add Link to a document using SharePoint Object Model

Once you enable the ‘link to document’ support in any document library you can add the document programmatically. The following code snippet shows how to add a link to a document content in a library.

public static void AddDocumentLink(string webUrl, string libraryName,string documentPath, string documentName, string documentUrl)
{
    using (var site = new SPSite(webUrl))
    {
        using (var web = site.OpenWeb())
        {
            var contentType = web.AvailableContentTypes["Link to a Document"];
            var docLibrary = web.Lists[libraryName];
                 
            //get full path of the document to add
            var filePath = docLibrary.RootFolder.ServerRelativeUrl;
            if(!string.IsNullOrEmpty(documentPath))
            {
                filePath += "/" + filePath; 
            }
            var currentFolder = web.GetFolder(filePath);

            var files = currentFolder.Files;
            var urlOfFile = currentFolder.Url + "/" + documentName + ".aspx";

            const string format = @"<%@ Assembly Name='{0}' %>
            <%@ Register TagPrefix='SharePoint' Namespace='Microsoft.SharePoint.WebControls' Assembly='Microsoft.SharePoint' %>
            <%@ Import Namespace='System.IO' %>
            <%@ Import Namespace='Microsoft.SharePoint' %>
            <%@ Import Namespace='Microsoft.SharePoint.Utilities' %>
            <%@ Import Namespace='Microsoft.SharePoint.WebControls' %>
                <html>
                    <head> 
                            <meta name='progid' content='SharePoint.Link' /> 
                    </head>
                    <body>
                        <form id='Form1' runat='server'>
                            <SharePoint:UrlRedirector id='Redirector1' runat='server' />
                        </form>
                    </body>
                </html>";

            var builder = new StringBuilder(format.Length + 400);
            builder.AppendFormat(format, typeof(SPDocumentLibrary).Assembly.FullName);

            var properties = new Hashtable();
            properties["ContentTypeId"] = contentType.Id.ToString();

            var file = files.Add(urlOfFile, new MemoryStream(new UTF8Encoding().GetBytes(builder.ToString())), properties, false, false);
            var item = file.Item;
            item["URL"] = documentUrl + ", ";
            item.UpdateOverwriteVersion();
        }
    }
}

The above code snippet is the modified version of what SharePoint does when you add a document link from UI. I have used reflector to view the code and placed here a modified version. I have tested this against SharePoint 2010.

Wednesday, July 14, 2010

SharePoint 2010 exception: The query can not be completed because the number exceeds the contained Lookup enforced by the Administrator Lookup threshold.

SharePoint 2010 can handle very large lists with millions of items. However, Effective indexing and throttling can be a very useful technique to improve performance of large lists. Be default SharePoint provide default settings for the sites limiting how much items/lookup fields can be retrieved per query. If you get the exception “Query exceeds lookup column threshold.” or “The query can not be completed because the number exceeds the contained Lookup enforced by the Administrator Lookup threshold.” you know that you have exceeded the lookup columns limitation imposed by your administrator.

If you have administrative permission then you can modify the limitations To modify the settings go to “SharePoint Central Administrator” => Application Management (from left side navigation) and then “Manage Web Application”. You’ll find the page shown below:

image

Figure 1: Web application lists

 

Now from the web application list select the web application you want to configure and then click “Resource Throttling” from the ribbon as shown below:

image

Figure 2: Resource Throttling menu

Finally change the max number of lookup columns as shown below:

image

 

After setting the value N for the lookup threshold you can at most include N numbers of lookup field in any query.

Saturday, July 10, 2010

SharePoint 2010: Hide Recently Modified Items

In SharePoint 2010 we all are familiar with the following annoying quicklaunch menu:

image

In SharePoint 2010, the recently modified items is one of many less-used feature. In software industry we are familiar with 80-20 principal. If 80% custom wants a feature then we should go with it and if you can satisfy 80% users then you have the successful product. I guess the ‘Recently Modified’ quick link doesn’t even needed by 10% people but the link is enable by default. Maybe this is a security feature to let administrator know what files have been modified recently. But there should have an on/off option as this is less wanted feature and most of the administrators want to hide this link. Justin has already found a way to hide the option by modifying template file as described in his blog. However, we can fix the issue without modifying the shared file. Specially in case of shared hosting we don’t want to modify the files from 14 hive. The two approaches described here is implemented by modifying master page. The first approach is acceptable but may have unknown side effects. On the other hand second approach is much better and less chance of side effects.

 

First solution:

1. Open your master page (default one is V4.master) and find the content place holder ‘PlaceHolderLeftActions’ as shown below:

<asp:ContentPlaceHolder id="PlaceHolderLeftActions" runat="server">                
</asp:ContentPlaceHolder>

2. Now set the content place holder visible property to false. as shown below:

<asp:ContentPlaceHolder id="PlaceHolderLeftActions" runat="server" Visible="false">            
</asp:ContentPlaceHolder>
Why it works?

The above trick works as in the aspx files the recently changed menu is put inside the placeholder. If you open a file in SharePoint Designer  you can find that the recently modified menu is put under the placeholder with id ‘PlaceHolderLeftActions’ as shown below:

<asp:Content ContentPlaceHolderId="PlaceHolderLeftActions" runat="server">
    <SharePoint:RecentChangesMenu runat="server" id="RecentChanges" />
</asp:Content>
Warning

The content placeholder ‘PlaceHolderLeftActions’ mainly used in blog and wiki sites. So if you are using any of this kind of template then hiding the content place holder will hide others related links too. However, I have found that hiding the content placeholder in team site works perfectly.

 

Second and Better Solution

However, the above trick may have side effects, if the same place holder (‘PlaceHolderLeftActions’) is  used by other pages. Another solution (which I think less side effects or no side effects) is to apply a css. As shown in the following screen, the ‘recently modified’ quick launch uses a css class named ‘s4-recentchanges’ (shown with firebug).

image

So we can another property ‘display:none’ to the same css class by adding the following extra attribute to master page’s header section:

<style type="text/css">
    .s4-recentchanges
    {
        display:none;
    }
</style>

You can even put the above section in a css file and refer the file in master page. The following image shows the master page with the css class:

image

Wednesday, July 7, 2010

SharePoint 2010: Use ECMAScript to manipulate (Add/Delete/Update/Get) List Items

In SharePoint 2010 there are three different types of Client Object Model extension you can use. They are Managed Client Object Model, ECMAScript and silverlight extension. In one of my post I have described how to manipulate list items using Managed Client Object Model. Today I’ll go through how to use ECMAScrip to manipulate list items. Fist of all make sure your page/webpart is ready to use ECMAScript as I have described in another post.

How to Get ECMAScript Intellisence

When you’ll use ECMAScript library in Visual Studio, it’s possible to get intellisense for ECMAScript. There are three ways you may need to enable the intellisense:

  1. Get Intellisense in Application Page: In the case you want to put  your javascript in aspx file as inline, you need to add the following lines in the markup file. However, this will not work for webpart file. For webpart file you need to put your javascript in another js file as described in option 2.

    <script type="text/ecmascript" src="/_layouts/SP.debug.js" />
    <
    script type="text/ecmascript" src="/_layouts/SP.Debug.js" />
    <
    script type="text/ecmascript" src="/_layouts/SP.Runtime.Debug.js" />

    <
    script type="text/javascript">
          //you'll get intellisense here
    </script>
  2. Get Intellisense in js file: If you want to get intellisense in js file then you need to add the following lines in the top of the js file. As shown in the snippet below, the first reference is to MicrosoftAjax.js file. This is mandatory to have this js file reference at the top. Then I have added two other references. The two files (SP.Core.Debug.js and SP.debug.js) have basic SharePoint namespaces. However, if you need more functionalities try to add more js file reference from the path “C:/Program Files/Common Files/Microsoft Shared/Web Server Extensions/14/TEMPLATE/LAYOUTS”
    /// <reference name="MicrosoftAjax.js" />
    /// <reference path="file://C:/Program Files/Common Files/Microsoft Shared/Web Server Extensions/14/TEMPLATE/LAYOUTS/SP.core.debug.js" />
    /// <reference path="file://C:/Program Files/Common Files/Microsoft Shared/Web Server Extensions/14/TEMPLATE/LAYOUTS/SP.debug.js" />
    
  3. Get Intellisense in webpart: To get intellisense in webpart you need to add the following two lines in the webpart ascx file:

    <script type="text/javascript" src="/_layouts/MicrosoftAjax.js" ></script>
    <script type="text/javascript" src="/_layouts/SP.debug.js" /> 

    However, I have found the MicrosoftAjax.js file is not located in “/_layouts/MicrosoftAjax.js” and for that the above intellisense will not work. So to get intellisense you need to copy the file in the Layouts folder. You can try to find the MicrosoftAjax.js file from location “C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\ProjectTemplatesCache\VisualBasic\Web\1033\EmptyMvcWebApplicationProjectTemplatev2.0.vb.zip\Scripts” if exists. Or you can download the file from the location below where I have uploaded the file: http://cid-04d8f6d0dd4e7214.office.live.com/self.aspx/Public/MicrosoftAjax.js 

A Sample List I have used

For this blog I have used a sample product list with the properties as shown in the following image.

image

Figure 1: Product List

 

Add a new List Item

The code snippet below shows that  you need to get the current SharePoint Context first. Then get the current web from the context. And then you need to get the list from the web. Then I have used ListItemCreationInformation object. There are two important properties of ListItemCreationInforamtion:

  • ListItemCreationInformation.folderUrl: this property defines in which location you want to add the item. The url should start with forward slash(/). For example for the web myweb and list product and for the folder ‘myfolder’ in the product list the url will be  ‘/myweb/Lists/product/myfolder’.
  • ListItemCreationInformation.UnderlyingObjectType: this value identity the type of object to create. The possible values are File,Folder, Web and Invalide. The values can be found in ‘SP.FileSystemObjectType’.

The addProduct method below takes few arguments that represents the product list’s fields.

function addProduct(productName, productDesc, productLaunchDate, productAvailQty, productType) {
    try {
        var context = new SP.ClientContext.get_current();        
        var web = context.get_web();
        var list = web.get_lists().getByTitle('product');

        var listItemCreationInfo = new SP.ListItemCreationInformation();
        var newItem = list.addItem(listItemCreationInfo);
        newItem.set_item('Title', productName);
        newItem.set_item('ProductName', productName);
        newItem.set_item('ProductDescription', productDesc);
        newItem.set_item('LaunchDate', productLaunchDate);
        newItem.set_item('AvailableQuantity', productAvailQty);
        newItem.set_item('ProductType', productType);

        newItem.update();
        context.executeQueryAsync(Function.createDelegate(this, this.success), Function.createDelegate(this, this.failed));
    }
    catch (e) {
        alert('error:' + e.Message);
    }
}

If you look a bit closer in the above code snippet you can find that ClientContext.executeQueryAsync takes two function delegates. The first one will be invoked when the ECMAScript get executed successfully. The second one will be invoked otherwise. The two methods are defined below:

function success() {
    alert('success');
}
function failed(sender, args) {
    alert('failed. Message:' + args.get_message());
}

 

Delete a List Item

To delete a product by product id the following code snippet can be used:

function deleteProduct(productId) {
    var context = new SP.ClientContext.get_current();
    var web = context.get_web();
    var list = web.get_lists().getByTitle('product');
    var itemToDelete = list.getItemById(productId);
    itemToDelete.deleteObject();
    context.executeQueryAsync(Function.createDelegate(this, this.success), Function.createDelegate(this, this.failed));
}

To delete an object in Client Object Model you need to invoke the deleteObject method of that object.

 

Get Item By Id

To get an item using ECMAScript, you need to share a common variable between the method that execute the ECMAScript (getProductById method in the following code snippet) and callback method (productReceived, failed in the snippet below). Only for this reason I have defined a variable product in the first line of the code snippet below.

var product;
function getProductById(productId) {
    try {
        var context = new SP.ClientContext.get_current();
        var web = context.get_web();
        var list = web.get_lists().getByTitle('product');
        this.product = list.getItemById(productId);
        context.load(product, 'ProductName', 'ProductDescription', 'ProductType', 'LaunchDate', 'AvailableQuantity');
        context.executeQueryAsync(Function.createDelegate(this, this.productReceived), Function.createDelegate(this, this.failed));
    }
    catch (e) {
        alert(e);
    }
}
function productReceived() {
    alert('got product');
    gotProduct(this.product);
}
function failed(sender, args) {
    alert('failed. Message:' + args.get_message());
}

In the code snippet above, the Context.Load method has taken the item to load (product) as the first parameter. And a comma separated list of columns to load for this item are passed then to the load method. If you want to load all properties of the item (which is not recommended) you can just call the context.load method with only first parameter.

 

Search Items from a List

In the code snippet below Caml Query is used for searching a product by title. I have used Caml Query to search product by title. Notice here that the load takes a second parameter (wrapped with ‘include’) specifying all properties to load for items.

var productcollection;
function getProducts(title) {
    try {
        var context = new SP.ClientContext.get_current();
        var web = context.get_web();
        var list = web.get_lists().getByTitle('product');
        var query = '<View Scope=\'RecursiveAll\'>'+
                        '<Query>'+
                            '<Where>'+
                            '<Contains>'+
                                '<FieldRef Name=\'ProductName\'/>' +
                                '<Value Type=\'Text\'>' + title +'</Value>'+
                            '</Contains>'+
                            '</Where>'+
                        '</Query>'+
                             '</View>';
        var camlQuery = new SP.CamlQuery();
        camlQuery.set_viewXml(query);

        this.productcollection = list.getItems(camlQuery);
        context.load(this.productcollection, 'Include(ProductName, ProductDescription, ProductType, LaunchDate, AvailableQuantity)');
        context.executeQueryAsync(Function.createDelegate(this, this.productsReceived), Function.createDelegate(this, this.failed));
    }
    catch (e) {
        alert(e);
    }
}
function productsReceived() {
    alert('got products');
    prcessProducts(this.productcollection);
}
function failed(sender, args) {
    alert('failed. Message:' + args.get_message());
}

 

Update a list item

The code snippet below shows how to update a product item. The list item’s set_item(propertyname, propertyvalue) method is used to update the field values.

function updateProduct(productid, productName, productDesc, productLaunchDate, productAvailQty, productType) {
    var context = new SP.ClientContext.get_current();
    var web = context.get_web();
    var list = web.get_lists().getByTitle('product');
    var product = list.getItemById(productid);
    product.set_item('ProductName', productName);
    product.set_item('ProductDescription', productDesc);
    product.set_item('ProductType', productType);
    product.set_item('LaunchDate', productLaunchDate);
    product.set_item('AvailableQuantity', productAvailQty);
    product.update();
    context.executeQueryAsync(Function.createDelegate(this, this.success), Function.createDelegate(this, this.failed));

}

 

Points to Remember

1. FormDigest Control: If you write code that modifies data on server, include a FormDigest control to create a digest for security validation of the page. The formdigest control can be added in master page and then all content pages will get it automatically.

<SharePoint:FormDigest runat="server" />

2. Get and Set Property: For the scrip used, when you need to get a property, you need to add get_ as prefix. For example the web has the property title. So if you want to get the title property of web then you need to use the syntax web.get_title(). Similary, if you want to set the title value then you need to invoke web.set_title(‘title’).

2. ClietContext.Load: ClientContext’s load method is used to load object. The first parameter to the load method is the object to load. The second parameter vary depending on whether the first parameter is a single object or collection.

  • If the first parameter is a single object then the following syntax is used to load properties:

context.load(objectToLoad,’property1’,’property2’,………….,’propertyN’)

  • If the first parameter is a collection then the following syntax is used

context.load(objectCollectionToLoad,’Include(property1, property2,……….,propertyN)’)