Tuesday, January 31, 2017

New blog location!

This site will no longer be updated.  Please visit the new blog at http://blog.liambellows.com

Thursday, September 02, 2010

Roll back UI in Sharepoint 2010 Visual Upgrade

I looked and looked and nothing worked.  Try running this in your Powershell Sharepoint 2010 management shell:


Get-SPSite http://[site] | get-spweb $_ -limit all | Foreach {$_.UIVersionConfigurationEnabled=1;$_.UIVersion=3;$_.Update();}

Monday, July 26, 2010

Can't persist changes to SummaryLinkWebPart

Apparently there is a bug when trying to programmatically change the links in the Summary Link Web Part.  I tried checking out the page first ( pageFile.CheckOut(); ).  I tried allowing unsafe updates (spWeb.AllowUnsafeUpdates = true; ) and I also tried updating, checkingIn, and publishing:

pageFile.Update();
pageFile.CheckIn("Updated by LinkUpdateTool");
pageFile.Publish("Updated by LinkUpdateTool");

None of which worked...

I was adding the links using:  << linkWP.SummaryLinkValue.SummaryLinks.Add(linkItem);  >> where linkItem is a SummaryLink object.

I looked up a bunch of code examples online but couldn't find anything else to try so I started playing around with ordering of the function calls to no avail. I then dug deep into a google search and found a single line of code which resolves an apparent bug in the API:

linkWP.SummaryLinkValue = linkWP.SummaryLinkValue;

Do this after you add your new links but before so save the WebPartManager.  Ie...

wpm.SaveChanges(linkWP);


Frustrating but so relieved I got it working!!

Friday, April 23, 2010

List Item Ranking - Order your list items just like ordering columns in your list!

I wrote this code a while ago.  It took a lot of debugging and its not very elegant but I was pretty pressed for time.  If you have any suggestions on how to streamline, I'd like to hear them.

I also wrote code to attach to all custom lists via a feature.  This makes it easy to enable and disable.  Also, this has a transaction log functionality built to to archive changes in another list.  Ranks are 1-5 and are based on a field 'category'.  For example you can have 1-5 fruits, and 1-5 in category vegetables, so there are seperate 1-5 threads going on in the list.  This can be disabled by removing the category checks.

I can show the list attaching code as well if requested, I think its pretty useful.  This post is already long enough.


public override void ItemAdding(SPItemEventProperties properties)
    {
      try
      {
        this.DisableEventFiring();
        SPSite siteCollection = new SPSite(properties.WebUrl);
        SPWeb srcSite = siteCollection.OpenWeb();
        SPList srcList = srcSite.Lists[properties.ListId];
        //SPListItem srcItem = properties.ListItem;
        string myRank = properties.AfterProperties["Rank"].ToString();
        string myCategory = properties.AfterProperties["Category"].ToString();

        if (int.Parse(myRank) < 5)
        {
          IncrementRanks(srcList, myRank, myCategory, properties);
        }

        siteCollection.Dispose();
        srcSite.Dispose();
      }
      catch (Exception ex)
      {
        properties.ErrorMessage = ex.Message + " - " + ex.Data + " + " + ex.StackTrace;
        properties.Cancel = true;   
      }
      finally
      {
        this.EnableEventFiring();
      }
      //base.ItemAdding(properties);
    }

    public override void ItemUpdated(SPItemEventProperties properties)
    {
      base.ItemUpdated(properties);
    }

    public override void ItemUpdating(SPItemEventProperties properties)
    {
      try
      {
        this.DisableEventFiring();
        SPSite siteCollection = new SPSite(properties.WebUrl);
        SPWeb srcSite = siteCollection.OpenWeb();
        SPList srcList = srcSite.Lists[properties.ListId];
        //SPListItem srcItem = properties.ListItem;
        string myRank = properties.AfterProperties["Rank"].ToString();
        string myCategory = properties.AfterProperties["Category"].ToString();

        if (int.Parse(myRank) < 5 & !myRank.Equals(properties.ListItem["Rank"].ToString()))
        {
          IncrementRanks(srcList, myRank, myCategory, properties);
        }
 
        siteCollection.Dispose();
        srcSite.Dispose();
      }
      catch (Exception ex)
      {
        properties.ErrorMessage = ex.Message + " + " + ex.Data + " + " + ex.StackTrace;
        properties.Cancel = true;
      }

      try
      {
        //create transaction
        SPListItem newItem = properties.OpenWeb().Lists["Transaction Log"].Items.Add();

        foreach (SPField spf in properties.ListItem.Fields)
        {
          if (newItem.Fields.ContainsField(spf.Title) & !spf.Hidden & !spf.ReadOnlyField &
            !String.IsNullOrEmpty(properties.ListItem.GetFormattedValue(spf.Title)))
          {
            if (!spf.Title.Equals("Attachments"))
            {
              if (newItem.Fields.GetField(spf.Title).GetType() == spf.GetType())
              {
                newItem[spf.Title] = properties.ListItem[spf.Title];
              }
              else
              {
                newItem[spf.Title] = properties.ListItem.GetFormattedValue(spf.Title);
              }
            }
          }
        }
        newItem.Update();
      }
      catch (Exception ex)
      {
        properties.ErrorMessage = ex.Message + " - " + ex.Data + " - " + ex.StackTrace;
        properties.Cancel = true;
      }
      finally
      {
        this.EnableEventFiring();
      }
    }

    private static void IncrementRanks(SPList srcList, string myRank, string category, SPItemEventProperties properties)
    {
      bool rankExists = false;
      //check if there exists a duplicate rank
      SPListItem sameLiRank = null;
      foreach (SPListItem spli in srcList.Items)
      {
        string itemRank = spli["Rank"].ToString();
        if (itemRank == myRank & spli["Category"].ToString().Equals(category))
        {
          rankExists = true;
          sameLiRank = spli;
          break;
        }
      }

      int myRankint = int.Parse(myRank);
      
      int existingItemRank = 0;
      int myOldRank = 0;
      if(sameLiRank != null)
        existingItemRank = int.Parse(sameLiRank["Rank"].ToString());

      if (properties.ListItem != null)
        myOldRank = int.Parse(properties.ListItem["Rank"].ToString());

      if (rankExists & properties.ListItem != null & (myOldRank == existingItemRank - 1 | myOldRank == existingItemRank + 1))
      {
        sameLiRank["Rank"] = myOldRank.ToString();
        sameLiRank.SystemUpdate();
      }
      else if (rankExists)
      {
        List gList = new List();
        List rankedItems = new List();

        foreach (SPListItem spli in srcList.Items)
        {
          //set up our array of ranks
          int liRank = int.Parse(spli["Rank"].ToString());
          if (liRank < 5 & spli["Category"].ToString().Equals(category)) // filter category here 
            rankedItems.Add(spli);
        }

        //sort the items
        rankedItems.Sort(delegate(SPListItem li1, SPListItem li2) {
          return li1["Rank"].ToString().CompareTo(li2["Rank"].ToString());
        });

        foreach (SPListItem spli in rankedItems)
        {
          bool doIt = false;
          if(object.Equals(properties.ListItem, null))
          {
            doIt = true;
          }
          else if(properties.ListItem.UniqueId != spli.UniqueId)
          {
            doIt = true;
          }
          if (!string.IsNullOrEmpty(spli["Rank"].ToString()) &
            int.Parse(spli["Rank"].ToString()) >= myRankint & doIt) //fix issue so item shouldnt modify itself
          { //check for +1 and exit loop if there isnt
            
            spli["Rank"] = int.Parse(spli["Rank"].ToString()) + 1;
            spli.SystemUpdate();
          }
        }
      }
    }

Tuesday, March 23, 2010

Javascript errors - Object Expected, '_spBodyOnLoadFunctionNames', Expected ';' init.js

Another day of Sharepoint, another error.  I'm REALLY looking forward to upgrading to 2010.  Even if I have to rebuild these sites from scratch it will be worth it, because we have so much archaic code in our site that it could crash at any time and no one would know why (I've been here 6 months and all the consultants that used to work here quit/were fired/ran out of money).

Well these error have been plaguing the site intermittently for months. The first symptoms were being unable to click the site actions button (well you could click it but nothing would pop up except a javascript error), and missing site images/silverlight which would come up as red X's or just be missing completely.  Sounds like permissions, I know, but it wasn't in this case.  By the time I put a few hours in trying to diagnose this, it would "work itself out".  Also, while some people were having the problem, other could use the site fine.

And even better: the site works fine in Firefox!

Mystifying I know.  The first fix I implemented was adding this

<script type="text/javascript" language="javascript" src="/_layouts/1033/core.js?rev=CNBZRdV1h3pKuA7LsMXf3w%3D%3D;"></script>

<script type="text/javascript" language="javascript" src="/_layouts/1033/init.js?rev=VhAxGc3rkK79RM90tibDzw%3D%3D;"></script>

<script type="text/javascript" language="javascript" src="/_layouts/1033/ie55up.js?rev=VhAxGc3rkK79RM90tibDzw%3D%3D;"></script>


core.js was being added through a Sharepoint include, but the others weren't although they appeared in the source HTML. But strangely enough after adding those to the head tag, the errors cleared up.  Note the semi colon at the end of the source. That is needed.  I'm not sure why, but it is.

Then, of course, my application pages were working so I needed to add that the to application.master as well.  But it didn't work at first. Hmmm.... well turns out they were unghosted on the server so I had to go in into 12/Layouts/Templates and modify them on each WFE. Cool!  But still another error.  Oh wait, we used jquery for something (god knows what), and that was missing from the app.master so better add that as well.  Strange though that sharepoint only complained about it every month or so, for maybe 1/100 users. So I added to each WFE app.master:

<script src="/_scripts/jquery-1.3.1.js" type="text/javascript"></script>


Now the last errors to fix: _spBodyOnLoadFunctionNames.push('enableFlyoutsAfterDelay'); and '_spBodyOnLoadFunctionNames' is undefined.  The former caused by the latter.  To fix this I found a link to this article which I used method 2 to set things right. http://support.microsoft.com/kb/931509

Everything appears to be working now!!!  I'm kind of relieved but at the same time I'm worried it will come back (as it has before).  I'll be sure to update if it rears its ugly head once again.

Wednesday, January 27, 2010

AJAX and UpdatePanels and SpGridViews and Paging! O MY!!!

The hoops we jump through for usability sometimes. Its crazy!  I mean, how important can it be, that a product is convenient, fast and useful?  Why can't it just be pretty?   /sarcasm

Ok Ive been working on this specific project for over a week now.  One of the requirements was to take two (long) lists of users, and find the difference between the two, then display them in a webpart on a sharepoint site.  Obviously SPGridView comes to mind.

And since I'm a fancy pants, I also wanted to add Paging, a progress bar, and AJAX it! 

I probably should've done it in that order.  But I AJAX'd it, added a progress bar, then finally paging.  I figured that'd be hardest to easiest but turns out it may have been the reverse (paging being the most time consuming).

Now I want to mention some key points to my project, that were in place to get everything working correctly.  Obviously I needed AJAX install on the server.  I downloaded the VNTG.WebConfigModification and enabled that on my farm after adding System.Web.Ajax and AjaxControlToolkit to the GAC.  This let me use the UpdatePanel effectively, as I was having trouble with postbacks even with the ScriptManager and UpdatePanel on the page.  Make sure to have a reference to System.Web.Extensions in your project and to USE System.Web.UI.

Some of these steps *might* be able to be omitted but I am not exactly sure which yet so I will list them all.  If anyone knows which ones are extraneous, please let me know and Ill make note of that.

So lets continue with some code now, shall we?  First make sure you ovveride the OnInit event in your webpart and add your ScriptManager to the page there.  You also want it to be the first control on the page, so your update panels work correctly.  To do that try this:

ScriptManager sm = ScriptManager.GetCurrent(this.Page);
      if (sm == null)
      {
        sm = new ScriptManager();
        sm.EnablePartialRendering = true;
       
      }
      this.Page.Form.Controls.AddAt(0, sm);
      this.EnsureUpdatePanelFixups();
      if (this.spgv == null)
      {
        this.spgv = new SPGridView();
        this.spgv.AutoGenerateColumns = false;
      }
      base.OnInit(e);
I have sm and spgv as private variables in the class.  Ensurepanelfixups() is a routine which strips away an onload script that Sharepoint uses that interferes with UpdatePanel and postbacks.  You can google that and pick one as there are a lot of different versions out there.

Next would be the CreateChildControls() function that should be overridden.
base.CreateChildControls();

        this.up = new UpdatePanel();
        this.up.ID = "upPolicy";
        this.up.UpdateMode = UpdatePanelUpdateMode.Conditional;

        this.upro = new UpdateProgress();
        this.upro.AssociatedUpdatePanelID = this.up.ClientID;
        this.upro.ProgressTemplate = new UpdateProTemplate(@"images/loading.gif", "Loading...");

        this.spgv.PageIndexChanging += new GridViewPageEventHandler(Grid_PageIndexChanging);
        this.spgv.PageSize = 30;
        this.spgv.AllowPaging = true;

        this.up.ContentTemplateContainer.Controls.Add(this.spgv);

        this.Controls.Add(upro);
        this.Controls.Add(up);

        this.spgv.PagerTemplate = null;
       
        if (this.Page.Cache["dt"] != null)
          this.spgv.DataSource = this.Page.Cache["dt"];
        this.spgv.DataBind();

Important things to note:  Make sure you set the PagerTemplate to null AFTER you add the control to your UpdatePanel.  Also, cacheing (and pulling from the cache) the DataTable was the crux of my success in getting the Pager portion working.  The UpdateProgress portion can be taken from here as that tutorial helped me out in getting that working.

One thing that differed a lot from other tutorials was that I was binding my SPGridView on Load (CreateChildControls) as well as on a Button click (because I wanted some user interaction).  This proved to be formidable because I needed to NOT wipe out my DataSet but I needed an empty DataSet when the paged loaded so I didnt get the lovely null reference exception.  Ill post that code so you can easily see whats going on in the button click:
SPSite sps = new SPSite(TARGET_SITE);
        SPWeb spw = sps.OpenWeb();
        SPList spl = spw.Lists[TARGET_LIST];
        List userList = this.GetAllUsersFromList(spl);
        List ADList = this.GetAllUsersEmailFromAD();
        List nonComplianceUsers = ADList.Except(userList).ToList();
        dt = this.MakeDataTable(nonComplianceUsers);

        this.spgv.Columns.Clear();

        SPBoundField bf = new SPBoundField();
        bf.HeaderText = "Email Address";
        bf.DataField = "Email";
        this.spgv.Columns.Add(bf);

        this.spgv.DataSource = dt.DefaultView;
        this.Page.Cache["dt"] = dt.DefaultView;
        this.spgv.DataBind();

 So I make my DataTable in the first block and it gets stored in a Private DataTable: dt.  I clear my columns here so if you click twice, it doesnt duplicate all the columns.  Now I add my column (its just a unique field from our AD structure: Email)  :)   And then I assign the datasource, cache it, and bind.  I'm using constants for my destinations but I may open them up as public properties later such as to be more customizable.  Its not really a part of the requirements so perhaps it will be in V2.0.

Before I wrap up this post there is one more piece of code that is useful to have.  Its the Event handler for the pager.
    private void Grid_PageIndexChanging(object sender, GridViewPageEventArgs e)
    {
      this.spgv.PageIndex = e.NewPageIndex;
      this.spgv.DataBind();
    }
 Easy enough.  And works for my project! The cacheing makes changing pages SUPER fast, really. Try it out, its real fast.   Please let me know if you see any extra code that can be stripped away as I feel I could probably optimize a little better (cant we all?).  Also if youre interested in my ActiveDirectory routine, let me know via comment or email and I could post that as well.

Take care.

Tuesday, December 08, 2009

"There was an error retrieving data to display in this Web Part." with CQWP

I'm back with a new error... I knew it wouldn't be long.

This one is tricky because the error: "There was an error retrieving data to display in this Web Part" doesn't really tell me much of anything.  I also went into the 12 HIVE/LOGS and found little success in there as well.  We have things output as Verbose, and running Sharepoint on 3 domains makes it tough to find what you need.

So I went back to google and searched a bit more and finally came across: http://blogs.msdn.com/ecm/archive/2006/10/25/configuring-and-customizing-the-content-query-web-part.aspx

I went through the comments and found a clue to what might be the problem: "Most likely the error is caused because the lookup column allows multi choices.  Multi choices is not supported by the Cross List Query [XLQ] which is the data source of the Content Query Web Part" - Adri

Thanks Adri, I saw this on another page as well but I really didn't know where to look with an excess of 1000 sites being in existence.  I also noted, this error was hit or miss.  I would get it on certain page, or when I was filtering/grouping on certain criteria.  Most notably, filtering by 'Assigned to = Current User'.

Then another comment on that page (actually it was preceding): "I have just found that by grouping the items by another lookup column on that content type, the Portfolio lookup is displayed. As soon as I take this grouping off I get the error message as above. How strange." - Sam

That pretty much solved it for me.  Obviously, group on the filtered item that is causing the issue. And.... BOOM it worked.  Also to note, if you're displaying that field, 'assigned to' in my case, then you won't have an issue and won't have to group on it.  This can be a problem in some SQL code too, which makes sense because Sharepoint [lists] is an abstraction of SQL (more or less right?).

This error was fairly hard to solve because you don't have much info to work with.  I'm hoping someone else who happens to be blessed with this error can stumble across my blog!

One thing to note: My case is a more rare case.  The most typical cause of this error is that your XSL or outputted HTML is malformed and needs some debugging.  I knew that this had been working perfectly at one point, so there was something else going on. Definitely check the XSL first, though.