Archive

Archive for the ‘SharePoint 2010’ Category

SharePoint: SPLongOperation inside a Modal Dialog Window

September 10th, 2012 No comments

    In one of my posts I wrote about using SPLongOperation. When a lengthy operation has been accomplished the usual behavior of the SPLongOperation is to redirect user to another web page. In SharePoint 2010, however, the web page containing long operation could be opened in a Modal Dialog Window. In this case, instead of redirection, we often need to close the dialog right after the long operation is completed.

SPLongOperation.EndScript method

Fortunately, the SPLongOperation class provides us with a new useful method, EndScript, which notifies server that the operation has been completed and sends a specified JavaScript into the user browser. A typical use is presented below:

using (SPLongOperation operation = new SPLongOperation(this.Page))
{ 
    operation.LeadingHTML  = "Long operation";
    operation.TrailingHTML = "Please wait while the long operation is in progress";
 
    operation.Begin();
 
    // the code of the lengthy operation
    ...
 
    // check if the current page is opened in dialog
    if(SPContext.Current.IsPopUI)
        // signal that the lengthy operation is completed and 
        // send a script to close the current dialog
        operation.EndScript("window.frameElement.commitPopup();");
    else
        // signal that the lengthy operation is completed and 
        // redirect to another page
        operation.End("anotherPage.aspx");
}

The window.frameElement.commitPopup() script allows to close the dialog and pass SP.UI.DialogResult.OK as the result. The SPContext.Current.IsPopUI flag indicates if the current page is going to be opened in dialog and should be rendered in an appropriate way.

Wrapper around SPLongOperation

In the same old post I demonstrated a handy wrapper around SPLongOperation. Taking into account the dealing with dialogs, the updated version of the wrapper looks as follows:

public class SPLongOperationExecutionParams
{
    public string LeadingHtml  { get; set; }
    public string TrailingHtml { get; set; }
    public string RedirectUrl  { get; set; }
    public bool   CloseDialog  { get; set; }
}

public static void DoInSPLongOperationContext(SPLongOperationExecutionParams executionParams, Action action)
{
    if (HttpContext.Current.CurrentHandler is Page)
    {
        using (SPLongOperation operation = new SPLongOperation(HttpContext.Current.CurrentHandler as Page))
        {
            operation.LeadingHTML  = !string.IsNullOrEmpty(executionParams.LeadingHtml)  ? executionParams.LeadingHtml  : "Long operation";
            operation.TrailingHTML = !string.IsNullOrEmpty(executionParams.TrailingHtml) ? executionParams.TrailingHtml : "Please wait while the long operation is in progress";

            operation.Begin();

            if (action != null)
                action();

            try
            {
                if (executionParams.CloseDialog)
                    operation.EndScript("try { window.frameElement.commitPopup(); } catch (e) {}");
                else
                    operation.End(executionParams.RedirectUrl, SPRedirectFlags.DoNotEndResponse | SPRedirectFlags.Trusted, HttpContext.Current, "");
            }
            catch (ThreadAbortException)
            {
                // This exception is thrown because the SPLongOperation.End
                // calls a Response.End internally
            }
        }
    }
    else
        throw new ApplicationException("Couldn't find a host page!");
}

Below is an example of how to use it:

void startLongOperationButton_Click(object sender, EventArgs e)
{
    SPLongOperationExecutionParams executionParams = new SPLongOperationExecutionParams() 
    { 
        LeadingHtml  = "Creation of a new list item", 
        TrailingHtml = "Please wait while the item is being created", 
        CloseDialog  = SPContext.Current.IsPopUI, // if the page is in a dialog, the latter will be closed
        RedirectUrl  = "anotherPage.aspx" // if it's not a dialog, user will be redirected to that page
    };

    DoInSPLongOperationContext(executionParams, delegate()
    {
        // the code of the lengthy operation
        Thread.Sleep(5000);
    });
}

SharePoint: How to pass parameters into a Modal Dialog Window and then access them

August 3rd, 2012 No comments

Passing parameters into a Modal Dialog Window

Opening a web page in a Modal Dialog Window we can pass parameters to it through the args property of dialog options. A typical JavaScript might look like

function OpenDialog() {
  var opt = {
    url     : 'somePage.aspx',
    autoSize: true,
 
    /* additional parameters */
    args: { arg1: 'The second argument is ', arg2: 12345 },
 
    dialogReturnValueCallback:
      function (res, retVal) {
        if (res === SP.UI.DialogResult.OK) { /* do something */ }
        else { /* do something else */ }
      }
  };
  SP.UI.ModalDialog.showModalDialog(opt);
}

Now let’s take a look how these parameters can be accessed from within the Parent-page and the page opened in the dialog.

Accessing the arguments from within the page opened in the dialog

In general case, when opening a SharePoint-based page, the passed arguments could be reached using a script similar to the following:

function DoSomethingWithArgs() {
  var passedArgs = SP.UI.ModalDialog.get_childDialog().get_args(); /* get access to the passed parameters */
  alert(passedArgs.arg1 + passedArgs.arg2);
}

The key method here is SP.UI.ModalDialog.get_childDialog, which allows to get the current dialog, or, to be more precise, the current SP.UI.Dialog object. The retrieved object, in turn, exposes the get_args method to access the passed parameters.

A possible use of the DoSomethingWithArgs function from the sample above is

<a href="#"
  onclick="ExecuteOrDelayUntilScriptLoaded(DoSomethingWithArgs, 'sp.ui.dialog.js'); return false;">
Show me the passed arguments
</a>

In case the web page opened in the dialog knows nothing about SharePoint and doesn’t include appropriate SharePoint JavaScript files, use the following version of the code:

function DoSomethingWithArgs() {
  var passedArgs = window.frameElement.dialogArgs; /* get access to the passed parameters */
  alert(passedArgs.arg1 + passedArgs.arg2);
}

Where the dialogArgs as a property of the window.frameElement object is provided by SP Dialog framework and returns the passed arguments.

Assuming the SharePoint‘s ExecuteOrDelayUntilScriptLoaded function isn’t available as well, the possible use of the DoSomethingWithArgs is quite straightforward:

<a href="#" onclick="DoSomethingWithArgs(); return false;">
Show me the passed arguments
</a>

All other conditions being equal, I recommend the approach with the window.frameElement object as it works always and doesn’t require the page in the dialog to be bound to SharePoint somehow.

Accessing the passed arguments from within the Parent-page

The Parent-page is a place where we define parameters and pass them to the dialog. So, the most direct way is to store the required values into some variable(s) to access them later. Another way is to rely on Dialog framework and use the code already mentioned in the previous section, namely

//...
var passedArgs = SP.UI.ModalDialog.get_childDialog().get_args();
//...

The more interesting case, however, is to access the passed arguments inside the dialogReturnValueCallback function. The listing below contains an example of one of such dialogReturnValueCallback definitions:

function OpenDialog() {
  var opt = {
    url     : 'somePage.aspx',
    autoSize: true,

    /* additional parameters */
    args: { arg1: 'The second argument is ', arg2: 12345 }, 

    dialogReturnValueCallback:
      function (res, retVal) {
        if (res === SP.UI.DialogResult.OK) {                    
          var passedArgs = this.get_args(); /* get access to the passed parameters */
          alert(passedArgs.arg1 + passedArgs.arg2);
        }
      }
  };
  SP.UI.ModalDialog.showModalDialog(opt);
}

The dialogReturnValueCallback function will be called in the context of the SP.UI.Dialog object associated with the current Modal Dialog Window. Therefore, inside the function the passed arguments can be accessed using this-keyword.

A possible use of the OpenDialog is shown below:

<a href="#" 
  onclick="ExecuteOrDelayUntilScriptLoaded(OpenDialog, 'sp.ui.dialog.js'); return false;">
Open Modal Dialog Window
</a>

ps There is a quite detailed article about dealing with Dialogs in SharePoint 2010. So, read to learn more about Dialog framework.

SharePoint: How To Maximize a Modal Dialog Window

July 17th, 2012 No comments

    In SharePoint 2010 by default a list item is opened in a Modal Dialog Window. The dialog adjusts its size depending on the size of the contained content and provides users with buttons to maximize and close itself. So, I was asked to maximize the dialog when opening some list items.

Creating and Initializing of a Modal Dialog Window

The JavaScript responsible for the dialog rendering is in the SP.UI.Dialog.js file (usually located in C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS). Clicking on a link-title, for example, in a list view leads to creating and displaying a new Modal Dialog Window in a manner similar to the following:

var options = SP.UI.$create_DialogOptions();

options.url           = 'display-page corresponding to the current list item';
options.title         = 'list item title';
options.allowMaximize = true;
options.showClose     = true;

// possible option, but it's not used in the place in question
//options.showMaximized = true;

var modalDialog = SP.UI.ModalDialog.showModalDialog(options);

The commented line demonstrates one of the possible settings, showMaximized, which apparently does exactly what we need. So, it would be a good solution if we had control under creation of the dialog. Unfortunately, we don’t have. Modifying scripts in SharePoint system js-files runs counter to all known “best practices”, therefore I don’t even consider it. Another possible way is override the client-side OnClick-events of the link-title fields everywhere (in list views, web parts and so on) so that your own script would create and display the dialog in a required manner. Obviously, it’s a very time-consuming solution. Moreover, every time when creating a list view, web part or something else you always have to keep in mind the possible need to rewrite their OnClick-events. In my opinion the only acceptable approach is allow a web page itself to maximize the dialog window it’s placed in.

Script to Maximize a Modal Dialog Window

For that, first of all, we need to have a JavaScript to maximize the current dialog. As SP.UI.Dialog.js doesn’t provide a function/method intended for use outside, we have to consume undocumented internal functions. A suitable script is described in the blog post – “How to maximize a Modal Dialog in JavaScript?“. With slight changes the JavaScript looks like the following

function _maximizeWindow() {
    var currentDialog = SP.UI.ModalDialog.get_childDialog();
    if (currentDialog != null && !currentDialog.$S_0)
        currentDialog.$z();    
    }
    ExecuteOrDelayUntilScriptLoaded(_maximizeWindow, 'sp.ui.dialog.js');

_maximizeWindow gets the current dialog window instance and tries to maximize it if it hasn’t been done before. ExecuteOrDelayUntilScriptLoaded, in turn, is applied to ensure the _maximizeWindow is called after the sp.ui.dialog.js has been completely loaded.

Script Applying

Having the script, we can insert it into the end of a web page to be maximized. It could be done through SharePoint Designer or by modifying a physical aspx-file defined as a display/new/edit form for a list or a content type. However, I decided to add the script dynamically in a page’s code-behind. So, the resultant code is shown below:

using System;
using System.Web.UI;
using Microsoft.SharePoint;

//...

public class DisplayListItem : Page
{
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        if(!IsPostBack)
            AddMaximizeWindowScript();
    }

    //...

    protected virtual void AddMaximizeWindowScript()
    {
        if (SPContext.Current.IsPopUI)
        {
            const string scriptKey = "MaxWinwScript";
            if (!ClientScript.IsClientScriptBlockRegistered(GetType(), scriptKey))
            {
                const string jsScript =
                    @"function _maximizeWindow() {
                        var currentDialog = SP.UI.ModalDialog.get_childDialog();
                        if (currentDialog != null && !currentDialog.$S_0)
                            currentDialog.$z();    
                        }
                        ExecuteOrDelayUntilScriptLoaded(_maximizeWindow, 'sp.ui.dialog.js');";

                ClientScript.RegisterClientScriptBlock(GetType(), scriptKey, jsScript, true);
            }
        }
    }
    
    //...
}

You may ask why I use RegisterClientScriptBlock instead of RegisterStartupScript or window.onload, or jQuery‘s $(document).ready (the latest two arise on client side). The reason is that initially the dialog window is created with the autoSize option. So, it’s very reasonable to maximize the window as soon as possible to prevent the content’s size calculating, which apparently happens after the page has been loaded entirely.

SharePoint: How to Delete a List Field/Column programmatically

July 13th, 2012 No comments

    Sometimes I need to remove a list field, which is not in use anymore. To remove the field we need just to call SPField.Delete method. However, there are situations when the field cannot be deleted due to some conditions, for example, when the field is read-only and etc. Because of that you might get such exceptions as

"The field cannot be deleted because it is a read only field in the list."
"The field cannot be deleted because it is a sealed field in the list."
"The field cannot be deleted because it is a hidden field in the list."

and other. So, let’s consider how these difficulties can be overcome (if it’s possible at all).

Inside the SPField.Delete method

The SPField.Delete does nothing except calling the Delete method of the SPFieldCollection class. The listing below demonstrates the short version of the SPFieldCollection.Delete:

public void Delete(string internalFieldName)
{
    SPField fld = ... // get the field from the current collection
    ...	

    if (!fld.CanBeDeleted)
        ... // throw an eception

    SPFieldLookup lookup = fld as SPFieldLookup;
    if (((lookup != null) && !lookup.IsDependentLookup) && (lookup.GetDependentLookupInternalNames().ToArray().Length != 0))
        ... // throw an eception     

    ... // delete the field
}

Where the CanBeDeleted property of the field defined as the following:

public bool CanBeDeleted
{
    get
    {
        if (this.AllowDeletion.HasValue)
            return this.AllowDeletion.Value;
        return (!this.FromBaseType && !this.Sealed);
    }
}

*Note: this code is true for both SharePoint 2007 and SharePoint 2010.

As we can see, deleting a list field, SharePoint explicitly analyzes such properties of the field as AllowDeletion, Sealed and FromBaseType. Additionally, experiments show that the ReadOnlyField and Hidden properties are being examined as well (likely it happens somewhere in the unmanaged SharePoint modules).

Workaround

An obvious workaround is, before calling SPField.Delete, change the above properties so that the field would be allowed for deletion. So, taking that into account, I implemented the following method(s) to delete list fields:

public static bool RemoveField(SPField spField)
{
    if (spField == null)
    {
        WriteErrorToLog("spField is null! Please, provide a valid one");
        return false;
    }

    bool res = false;
    try
    {
        // check if it's a ReadOnly field.
        // if so, reset it
        if (spField.ReadOnlyField)
        {
            spField.ReadOnlyField = false;
            spField.Update();
        }

        // check if it's a Hidden field.
        // if so, reset it
        if (spField.Hidden)
        {
            spField.Hidden = false;
            spField.Update();
        }

        // check if the AllowDeletion property is set to false.
        // if so, reset it to true
        if (spField.AllowDeletion == null || !spField.AllowDeletion.Value)
        {
            spField.AllowDeletion = true;
            spField.Update();
        }

        // If the AllowDeletion property is set, 
        // the Sealed property seems not to be examined at all.
        // So the following piece of code is commented.
        /*if(spField.Sealed)
        {
            spField.Sealed = false;
            spField.Update();
        }*/

        // If the AllowDeletion property is set, 
        // the FromBaseType property seems not to be examined at all.
        // So the following piece of code is commented.
        /*if(spField.FromBaseType)
        {
            spField.FromBaseType = false;
            spField.Update();
        }*/

        // finally, remove the field
        spField.Delete();
        spField.ParentList.Update();

        res = true;
    }
    catch (Exception ex)
    {
        WriteErrorToLog(ex.Message);
    }

    return res;
}

public static bool RemoveField(SPList spList, string displayNameOrInternalNameOrStaticName)
{
    SPField spField = GetFieldByName(spList, displayNameOrInternalNameOrStaticName);
    if(spField == null)
    {
        WriteErrorToLog(string.Format("Couldn't find field {0}!", displayNameOrInternalNameOrStaticName));
        return false;
    }

    return RemoveField(spField);
}

public static void WriteErrorToLog(string errorMsg)
{
    // write error into log
}

*Note: the GetFieldByName method is described here – Getting SPField with no exceptions to be thrown.

According to the code of the CanBeDeleted, the Sealed and FromBaseType properties are not being examined at all while the AllowDeletion is set. Thus the dealing with them is commented, but retained just in case.

Below is a sample of use:

SPSecurity.RunWithElevatedPrivileges(delegate
{
    using (SPSite spSite = new SPSite("some site url"))
        using (SPWeb spWeb = spSite.OpenWeb())
        {
            SPList spList = GetListByUrl(spWeb, "Lists/Products");
            RemoveField(spList, "product name");
        }
});

*Note: the GetListByUrl method is described here – Getting SPList with no exceptions to be thrown.

The RemoveField method isn’t a panacea. For example, you still will have problems if the field going to be deleted is a part of a multiple column lookup.

Be very careful when deleting fields, because it can lead to severe issues in your SharePoint applications. If you are uncertain, don’t delete anything, especially if it concerns a live application on a production server. Remember that you are always able to just make the field hidden instead.

SharePoint: Getting a SPField with no exceptions to be thrown

June 29th, 2012 No comments

    As you probably know, a SharePoint field has a few names: display name, internal and static. The display name usually differs from the internal and static ones. In some exotic cases, the internal and static names differ from each other too. List’s fields can be reached through the SPList.Fields collection of the SPFieldCollection class.

Get SPField by different names in SharePoint 2007

In SharePoint 2007 the SPFieldCollection exposes a few methods to get a field by its known display or internal name, but not the static name. These methods are the indexer of the SPFieldCollection that accepts the field’s display name, the GetFieldByInternalName method accepting the internal name, and the GetField method accepting both display and internal names. Unfortunately, all these methods are case sensitive and throw an exception if the field with the passed display or internal name wasn’t found. I don’t like to wrap every piece of code into try-catch, so, for SharePoint 2007 applications I’m involved in, when it’s possible I use the simple method shown below:

public static SPField GetFieldByName(SPList spList, string displayNameOrInternalNameOrStaticName)
{
    displayNameOrInternalNameOrStaticName = displayNameOrInternalNameOrStaticName.ToLower();

    foreach (SPField spField in spList.Fields)
    {
        if (spField.Title.ToLower() == displayNameOrInternalNameOrStaticName)
            return spField;
        if (spField.InternalName.ToLower() == displayNameOrInternalNameOrStaticName)
            return spField;
        if (spField.StaticName.ToLower() == displayNameOrInternalNameOrStaticName)
            return spField;
    }

    return null;
}

// how to use
// ...
    using (SPSite spSite = new SPSite("some site url"))
        using (SPWeb spWeb = spSite.OpenWeb())
        {
            SPList  spList  = GetListByUrl(spWeb, "Lists/Products");
            SPField spField = GetFieldByName(spList, "product name"); // the field's real display name is Product Name
            // do something
        }
// ...

*Note: find the GetListByUrl method in the previous blog post – SharePoint: Getting SPList with no exceptions to be thrown

The GetFieldByName accepts a field’s all possible names including the static name, it’s not case sensitive and returns null if the sought-for field doesn’t exist.

Of course, I’m aware that enumerating fields takes more time than retrieving them from the SPFieldCollection’s internal hashtables so as the built-in methods do. But when time isn’t so crucial for a particular piece of code, I prefer using the GetFieldByName method. In addition I don’t have an alternative for the GetFieldByName when I know only the field’s static name.

Get SPField by different names in SharePoint 2010

In SharePoint 2010 the new TryGetFieldByStaticName method has been added to the SPFieldCollection class. So, as the method’s name implies, we get a field by its static name, and no one exception even will be thrown in case the field doesn’t exist. Thus, for SharePoint 2010 I’ve modified the GetFieldByName as follows:

public static SPField GetFieldByName(SPList spList, string displayNameOrInternalNameOrStaticName)
{
    SPField spFieldByStaticName = spList.Fields.TryGetFieldByStaticName(displayNameOrInternalNameOrStaticName);
    if (spFieldByStaticName != null)
        return spFieldByStaticName;

    displayNameOrInternalNameOrStaticName = displayNameOrInternalNameOrStaticName.ToLower();

    foreach (SPField spField in spList.Fields)
    {
        if (spField.Title.ToLower() == displayNameOrInternalNameOrStaticName)
            return spField;
        if (spField.InternalName.ToLower() == displayNameOrInternalNameOrStaticName)
            return spField;
        if (spField.StaticName.ToLower() == displayNameOrInternalNameOrStaticName)
            return spField;
    }

    return null;
}

Check whether a field exists

To check whether a field exists, we can use the following method based on the GetFieldByName:

public static bool FieldExist(SPList spList, string displayNameOrInternalNameOrStaticName)
{
    return GetFieldByName(spList, displayNameOrInternalNameOrStaticName) != null;
}
Related posts: