MOSS MVP

I've moved my blog to http://blog.falchionconsulting.com!. Please update your links. This blog is no longer in use--you can find all posts and comments at my new blog; I will no longer be posting to this site and comments have been disabled.

Tuesday, October 2, 2007

Move a Web Part on a Page

This particular command was one that I didn't actually need but rather I needed a starting place for a couple other commands that I did need (to be documented) and moving a web part seemed a simple enough starting place for manipulating web parts. So this one was really just a template for my other commands but it works rather well and who knows, maybe someone will benefit from it. Moving a web part is actually quite simple - you just call the SPLimitedWebPartManager's MoveWebPart method passing in the web part to move, the zone ID to move to, and the zone index (position in the zone). And then you just call SaveChanges passing in the web part.

All the real work is in locating the web part to move which is a little trickier than it should be because the Title field is not unique but it's a lot more convenient for the user to use the title rather than the cryptic ID. The other thing is the the zone ID is a string and could theoretically be any value so the caller needs to know what values to provide (this is where the gl-enumpagewebparts command comes in handy along with retrieving the web parts ID if necessary). To facilitate getting the web part I created a couple helper methods, one gets the web part by ID the other by Title:

   1: /// <summary>
   2: /// Gets the web part by id.
   3: /// </summary>
   4: /// <param name="web">The web.</param>
   5: /// <param name="url">The URL.</param>
   6: /// <param name="id">The id.</param>
   7: /// <param name="manager">The web part manager.</param>
   8: /// <returns></returns>
   9: internal static WebPart GetWebPartById(SPWeb web, string url, string id, out SPLimitedWebPartManager manager)
  10: {
  11:  manager = web.GetLimitedWebPartManager(url, PersonalizationScope.Shared);
  12:  WebPart wp = manager.WebParts[id];
  13:  if (wp == null)
  14:  {
  15:   manager = web.GetLimitedWebPartManager(url, PersonalizationScope.User);
  16:   wp = manager.WebParts[id];
  17:  }
  18:  return wp;
  19: }
  20:  
  21: /// <summary>
  22: /// Gets the web part by title.
  23: /// </summary>
  24: /// <param name="web">The web.</param>
  25: /// <param name="url">The URL.</param>
  26: /// <param name="title">The title.</param>
  27: /// <param name="manager">The web part manager.</param>
  28: /// <returns></returns>
  29: internal static WebPart GetWebPartByTitle(SPWeb web, string url, string title, out SPLimitedWebPartManager manager)
  30: {
  31:  manager = web.GetLimitedWebPartManager(url, PersonalizationScope.Shared);
  32:  List<WebPart> foundParts = new List<WebPart>();
  33:  WebPart wp = null;
  34:  foreach (WebPart tempWP in manager.WebParts)
  35:  {
  36:   if (tempWP.DisplayTitle.ToLowerInvariant() == title.ToLowerInvariant())
  37:   {
  38:    foundParts.Add(tempWP);
  39:    wp = tempWP;
  40:   }
  41:  }
  42:  if (foundParts.Count == 0)
  43:  {
  44:   manager = web.GetLimitedWebPartManager(url, PersonalizationScope.User);
  45:   foreach (WebPart tempWP in manager.WebParts)
  46:   {
  47:    if (tempWP.DisplayTitle.ToLowerInvariant() == title.ToLowerInvariant())
  48:    {
  49:     foundParts.Add(tempWP);
  50:     wp = tempWP;
  51:    }
  52:   }
  53:  }
  54:  if (foundParts.Count > 1)
  55:  {
  56:   string msg = "Found more than one web part matching the specified title.  Use the ID instead:\r\n\r\n";
  57:   XmlDocument xmlDoc = new XmlDocument();
  58:   string tempXml = null;
  59:   foreach (WebPart tempWP in foundParts)
  60:   {
  61:    tempXml += EnumPageWebParts.GetWebPartDetailsMinimal(tempWP, manager);
  62:   }
  63:   xmlDoc.LoadXml("<MatchingWebParts>" + tempXml + "</MatchingWebParts>");
  64:   throw new SPException(msg + GetFormattedXml(xmlDoc));
  65:  }
  66:  return wp;
  67: }

Once you've got the web part the rest is fairly easy - just need to provide a bit of logic to figure out which method to call and then handle the check in/out of the web part page so that the move operation can work:

   1: public override int Run(string command, StringDictionary keyValues, out string output)
   2: {
   3:  output = string.Empty;
   4:  
   5:  InitParameters(keyValues);
   6:  
   7:  SPBinaryParameterValidator.Validate("id", Params["id"].Value, "title", Params["title"].Value);
   8:  if (!Params["zone"].UserTypedIn && !Params["zoneindex"].UserTypedIn)
   9:   throw new SPSyntaxException("You must specify at least the zone or zoneindex parameters.");
  10:  
  11:  string url = Params["url"].Value;
  12:  
  13:  using (SPSite site = new SPSite(url))
  14:  using (SPWeb web = site.OpenWeb()) // The url contains a filename so AllWebs[] will not work unless we want to try and parse which we don't
  15:  {
  16:   SPFile file = web.GetFile(url);
  17:  
  18:   if (!Utilities.IsCheckedOut(file.Item) || !Utilities.IsCheckedOutByCurrentUser(file.Item))
  19:    file.CheckOut(); // If it's checked out by another user then this will throw an informative exception so let it do so.
  20:  
  21:   string displayTitle = string.Empty;
  22:   try
  23:   {
  24:    WebPart wp;
  25:    SPLimitedWebPartManager manager;
  26:    if (Params["id"].UserTypedIn)
  27:    {
  28:     wp = Utilities.GetWebPartById(web, url, Params["id"].Value, out manager);
  29:    }
  30:    else
  31:    {
  32:     wp = Utilities.GetWebPartByTitle(web, url, Params["title"].Value, out manager);
  33:     if (wp == null)
  34:     {
  35:      throw new SPException(
  36:       "Unable to find specified web part.  Try specifying the -id parameter instead (use enumpagewebparts to get the ID)");
  37:     }
  38:    }
  39:  
  40:    if (wp == null)
  41:    {
  42:     throw new SPException("Unable to find specified web part.");
  43:    }
  44:  
  45:    string zoneID = manager.GetZoneID(wp);
  46:    int zoneIndex = wp.ZoneIndex;
  47:  
  48:    if (Params["zone"].UserTypedIn)
  49:     zoneID = Params["zone"].Value;
  50:    if (Params["zoneindex"].UserTypedIn)
  51:     zoneIndex = int.Parse(Params["zoneindex"].Value);
  52:  
  53:    // Set this so that we can add it to the check-in comment.
  54:    displayTitle = wp.DisplayTitle;
  55:  
  56:    manager.MoveWebPart(wp, zoneID, zoneIndex);
  57:    manager.SaveChanges(wp);
  58:   }
  59:   finally
  60:   {
  61:    file.CheckIn("Checking in changes to page layout due to moving of web part " + displayTitle);
  62:  
  63:    if (Params["publish"].UserTypedIn)
  64:     file.Publish("Publishing changes to page layout due to moving of web part " + displayTitle);
  65:   }
  66:  }
  67:  
  68:  return 1;
  69: }
The syntax of the command I created can be seen below.

C:\>stsadm -help gl-movewebpart

stsadm -o gl-movewebpart

Moves a web part on a page.

Parameters:
        -url <web part page URL>
        {-id <web part ID> |
         -title <web part title>}
        [-zone <zone ID>]
        [-zoneindex <zone index>]
        [-publish]

Here's an example of how to move a web part to a different zone on a page:

stsadm -o gl-movewebpart -url "http://intranet/hr/pages/default.aspx" -title "Grouped Listings" -zone MiddleLeftZone -zoneindex 1 -publish

7 comments:

Montanan Missing her Mountains said...

Gary,

Can this be used to put a webpart on a site if it is not there already?

thanks!

Gary Lapointe said...

Not this one - gl-setwebpartstate will though.

Frank said...

Hi Gary,

When trying this command, I keep on getting this error message:

An ErrorWebPart cannot be saved during browse.

Any idea?

Thanks,
Frank

Anonymous said...

Hi. I have a problem. When I execute the MoveWebPart method, the others webparts index of the same zone doesn't update.
Somebody know why this happens?
Thanks!

Anonymous said...

Hi Gary,

Love the SharePoint page!

I was wondering if you know of a way to output what is normally outputted when someone appends ?contents=1 on the querystring but for all users?

Currently I can replicate this but only for the current user and I’m interested in outputting the data for all usres.


string url = "http://spsite";
using (SPSite site = new SPSite(url))
using (SPWeb page = site.OpenWeb())
{
//give relative path of the webpartpage
SPLimitedWebPartManager wm = page.GetLimitedWebPartManager("default.aspx", System.Web.UI.WebControls.WebParts.PersonalizationScope.User);

foreach (System.Web.UI.WebControls.WebParts.WebPart wp in wm.WebParts)
{
Response.Write(wp.Title.ToString() + "
");
}
}

Thanks in advance!!

Gary Lapointe said...

I've not really looked into this so can't say for sure but if you crack open that page and look at it in reflector you should be able to find what you need (that's what I'd do to find the answer).

Sharepoint-WIZ said...

Awesome post! I was searching for this for quite some time. Thanks for sharing the knowledge.