28 Sept 2010

Sharepoint 2010 Global Navigation Hide First Item (Collection Tab)

I'm branding up a Sharepoint 2010 site at the moment and one thing that stuck in my craw was the Global Navigation. It shows the subsites and pages fine and dandy, but it also sticks the root site collection as the first item in the menu.

Now, you can probably remove it programatically with a solution like this, but I just wanted to do it with some style tweaks.

So just stick this in your custom css file:

#s4-topheader2 a.static.menu-item[accesskey="1"] { display: none !important; }


That works for most browsers but not IE6 (spit) cos of it's CSS2 failitude. For that you can use some sweet jQuery in a jiffy:

<script type="text/javascript">
$(function () {
$('#s4-topheader2 a.static.menu-item:first').hide();
});
</script>

27 Sept 2010

SharePoint 2010 - Deploy a Custom WebPart

Build a release version of your web part to create a .WSP file

Copy the WSP file to the target server

On the target machine, ensure you have correct SPShellAdmin permissions for the Sharepoint Management PowerShell:
Add-SPShellAdmin –UserName DOMAIN\YOUR_USER

As per this excellent article:

- Add-SPSolution c:\your.WSP

- Install-SPSolution –Identity your.WSP –WebApplication http://yourSPInstance -GACDeployment

- Go to the Site Settings page, choose the Web Parts gallery and Add a New Web Part. Your custom web part should be there and you can Edit it to set the title, description and group.


To do an update later if you've amended your web part code:

Update-SPSolution -Identity your.WSP -LiteralPath c:\your.wsp -GACDeployment

24 Sept 2010

Develop a custom editable Visual Web Part (WebPart) for SharePoint 2010

I wanted to create a web part for Sharepoint 2010 that would let an editor add a block of free-form html to a page, but wrapped up in a nicely formatted HTML container of my choosing. I also wanted the web part to include some custom properties to allow the user to select some permutations for the HTML container (colour, position etc.). The key thing was that when the editor is amending the content, I wanted to be able to use the standard ribbon controls instead of having to hook in a 3rd party rich text control like Telerik RadEditor.

So, I assumed I could just take the build-in Content Editor Web Part (CEWP) and extend it. No dice - cos it's sealed!

So I ended up whipping out Reflector and digging into the CEWP code, ripping the guts out of it to hack a new webpart. The following Web Part does what I set out to do, and maybe it will be a good base for you too.

Step 1: Create a new Visual Web Part called DCContentBlock.

Step 2: In the ASCX template, paste this:
<div class="DCContentBlock">
 <div class="DCContent">
  <asp:Panel ID="plhContentEdit" runat="server" />
  <asp:Panel ID="plhContentDisplay" runat="server" />
  <asp:Panel ID="plhNoContent" runat="server" />
 </div>
</div>


Step 3: In the UserControl class in the ASCX.CS file, paste this:
  public Panel BodyContentEdit { get { return plhContentEdit; } }
  public Panel BodyContentDisplay { get { return plhContentDisplay; } }
  public Panel BodyNoContent { get { return plhNoContent; } }


Step 4: In the web part CS file, here is the main code to make this sucker work:
 [ToolboxItemAttribute(false)]
 public class DCContentBlock : Microsoft.SharePoint.WebPartPages.WebPart
 {
  // Visual Studio might automatically update this path when you change the Visual Web Part project item.
  private const string _ascxPath = @"~/_CONTROLTEMPLATES/YOUR_PROJECT_NAME_HERE/DCContentBlock/DCContentBlockUserControl.ascx";

  private string _content;
  private DCContentBlockUserControl control;

  private HtmlGenericControl editableRegion = new HtmlGenericControl();
  private HtmlGenericControl emptyPanel = new HtmlGenericControl();

  private bool IsInEditMode
  {
   get
   {
    SPWebPartManager currentWebPartManager = (SPWebPartManager)WebPartManager.GetCurrentWebPartManager(this.Page);
    return (((currentWebPartManager != null) && !base.IsStandalone) && currentWebPartManager.GetDisplayMode().AllowPageDesign);
   }
  }

  protected override void OnInit(EventArgs e)
  {
   base.OnInit(e);
   if (this.IsInEditMode)
   {
    SPRibbon current = SPRibbon.GetCurrent(this.Page);
    if (current != null)
    {
     current.MakeTabAvailable("Ribbon.EditingTools.CPEditTab");
     current.MakeTabAvailable("Ribbon.Image.Image");
     current.MakeTabAvailable("Ribbon.EditingTools.CPInsert");
     current.MakeTabAvailable("Ribbon.Link.Link");
     current.MakeTabAvailable("Ribbon.Table.Layout");
     current.MakeTabAvailable("Ribbon.Table.Design");
     if (!(this.Page is WikiEditPage))
     {
      current.TrimById("Ribbon.EditingTools.CPEditTab.Layout");
      current.TrimById("Ribbon.EditingTools.CPEditTab.EditAndCheckout");
     }
    }
   }
  }

  protected override void OnLoad(EventArgs e)
  {
   base.OnLoad(e);

   // Prevent default display of webpart chrome in standard view mode
   this.ChromeType = PartChromeType.None;

   control = (DCContentBlockUserControl)Page.LoadControl(_ascxPath);
   Controls.Add(control);
   control.BodyContentDisplay.Controls.Add(new LiteralControl(this.Content));
   control.BodyContentEdit.Controls.Add(this.editableRegion);
   control.BodyNoContent.Controls.Add(this.emptyPanel);
   
   string strUpdatedContent = this.Page.Request.Form[this.ClientID + "content"];
   if ((strUpdatedContent != null) && (this.Content != strUpdatedContent))
   {
    this.Content = strUpdatedContent;
    try
    {
     SPWebPartManager currentWebPartManager = (SPWebPartManager)WebPartManager.GetCurrentWebPartManager(this.Page);
     Guid storageKey = currentWebPartManager.GetStorageKey(this);
     currentWebPartManager.SaveChanges(storageKey);
    }
    catch (Exception exception)
    {
     Label child = new Label();
     child.Text = exception.Message;
     this.Controls.Add(child);
    }
   }
   if (this.IsInEditMode)
   {
    this.Page.ClientScript.RegisterHiddenField(this.ClientID + "content", this.Content);

    control.BodyContentDisplay.Visible = false;

    this.emptyPanel.TagName = "DIV";
    this.emptyPanel.Style.Add(HtmlTextWriterStyle.Cursor, "hand");
    this.emptyPanel.Controls.Add(new LiteralControl("Click here to Edit"));
    this.emptyPanel.Style.Add(HtmlTextWriterStyle.TextAlign, "center");
    base.Attributes["RteRedirect"] = this.editableRegion.ClientID;
    ScriptLink.RegisterScriptAfterUI(this.Page, "SP.UI.Rte.js", false);
    ScriptLink.RegisterScriptAfterUI(this.Page, "SP.js", false);
    ScriptLink.RegisterScriptAfterUI(this.Page, "SP.Runtime.js", false);
    this.editableRegion.TagName = "DIV";
    this.editableRegion.InnerHtml = this.Content;
    this.editableRegion.Attributes["class"] = "ms-rtestate-write ms-rtestate-field";
    this.editableRegion.Attributes["contentEditable"] = "true";
    this.editableRegion.Attributes["InputFieldId"] = this.ClientID + "content";
    this.editableRegion.Attributes["EmptyPanelId"] = this.emptyPanel.ClientID;
    this.editableRegion.Attributes["ContentEditor"] = "True";
    this.editableRegion.Attributes["AllowScripts"] = "True";
    this.editableRegion.Attributes["AllowWebParts"] = "False";
    string script = "RTE.RichTextEditor.transferContentsToInputField('" + SPHttpUtility.EcmaScriptStringLiteralEncode(this.editableRegion.ClientID) + "');";
    this.Page.ClientScript.RegisterOnSubmitStatement(base.GetType(), "transfer" + this.editableRegion.ClientID, script);
   }
  }

  // Properties
  [WebPartStorage(Storage.Shared)]
  public string Content
  {
   get
   {
    return this._content;
   }
   set
   {
    _content = value;
   }
  }
 }


STEP 5: Your CS file will also need the following using statements at the top:
using System;
using System.ComponentModel;
using System;
using System.Globalization;
using System.Collections.Generic;
using System.ComponentModel;
using System.Xml.Serialization;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebPartPages;
using Microsoft.SharePoint.WebControls;
using Microsoft.Web.CommandUI;


STEP 6: The use of Microsoft.Web.CommandUI means we need to add a project reference to the appropriate dll.
  • Right click on the project and Add Reference.
  • Click Browse and find: C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI\Microsoft.Web.CommandUI.dll


That's all! Build it, edit the page, insert a web part and the part will be in the custom folder. Edit the project's .webpart file and the Elements.xml file if you want to change the name, description and group that the webpart belongs to.

After this, you can extend and style the control to your heart's content. Some ideas:

1. Use CSS to style the DCContentBlock and DCContent classes
2. Add your own HTML to the webpart's ASCX template
3. Add editor properties to allow the user to select from some preset styling options.

I went to the additional trouble of adding editor properties by following this excellent article by Sahil Malik. When the user selects from my custom editor property drop-down, I store the preference value. I then refer to this value to append an appropriate css class to the DCContentBlock div in the WebPart's OnPreRender. Let me know if you'd like me to do a separate post on how to do that.

12 Sept 2010

Change / Remove Audi A4 Wheel

Although a CODING GOD, I am admittedly a mechanical weenie. So I just spent about an hour changing a wheel on my Audi cos it had a puncture on my one of my new front tyres, grrrrr. Here's what I learnt.

1. All the kit is located in under the boot/trunk floor, on either side of the spare wheel.
2. Take careful note of the layout of the stowed jack and tools, because they are impossible to put back in any other configuration - it's like a bloody chinese puzzle.
3. The Wheel is held on by 5 nuts (may be more or less on your alloys!) around a central hub. YOU ONLY HAVE TO REMOVE THE NUTS AROUND THE HUB - the rusty hub nut in the middle is not for you to touch! ;)
4. The nuts may be covered by cosmetic hexagonal covers. In the toolkit you'll find a plastic pair of tweezers which are designed to remove these covers.
5. The plastic centre cover of the wheel comes out easily by prising it out carefully with a screwdriver. Allegedly you should instead use the twisty wire thing in the toolkit to do this, but my centre cover did not have an appropriate hole to use it with!? No matter.
6. Loosen the nuts using the breaker bar in the toolkit WHEN THE WHEEL IS STILL ON THE GROUND. Jumping on your breaker bar while the car is up on the jack may end in tears.
7. After the nuts have been removed, it seemed like the wheel was still stuck on (hence me worrying about whether the centre nut had to be removed). But no, the wheel was just seized onto the rusty hub. I couldn't break the seal with my own force - instead i let the jack down and allowed the weight of the car to break the seal instead, then jacked it up again. The wheel came right off then.

10 Sept 2010

CampTune vs WinClone for BootCamp Partition Resize

Hello! I have a Mac Mini with Snow Leopard and Windows 7 running from the same drive via BootCamp. I made a 32Gb windows partition originally, planning to keep all my big windows files on a separate drive. Sadly, 32Gb is still not enough for the windows C drive when you factor in the pagefile, windows update / install files etc.

Therefore, I had to increase the size of the Windows partition without losing either the Mac or the Windows installation. 2 solutions were considered, WinClone and CampTune.

To cut to the chase, WinClone didn't work, as it's been abandoned for a while around Leopard time. I tried it, but the initial backup failed with errors, which gave me the willies. So I shelled out 13 quid for CampTune, which burns a boot CD from which you perform the partition resizing.

It worked like a champ, taking about 20 minutes to do it's thang, leaving both Mac OSX and Windows running just like before, only now with 200Gb for my Win C drive :)

29 Aug 2010

The Girl Who Wrote A Shopping List

It was raining as Mikael Blomkvist took the first stop off the tunnelbana at Likstoerp and visited the late-night AftonMarket. He bought pickled herring, appelsaúce, two pakkenpanes of wholegrain bread, a strudel, some milk, Sverigen cheese, a packet of raisins, three pepparsausage Billy Pan pizzas, a sponge, some shoelaces, a coathanger, some poptarts, a tube of Sensodyne Pro-Enamel toothpaste, a new washbag and a carton of Thor-brand extra strong condoms.

He paid by American Express, and then walked into the Apple Store opposite where he treated himself to a new Apple MacBook Pro with 4Gb ram, a 3Ghz Intel processor, Nvidia graphics card, a 250Gb solid state disk drive and a WUXGA OLED screen with a 60Hz refresh rate. He also bought an extended warranty, an offical Apple MacPack storage case and some 2Gb SanDisk USB memory sticks.

Then he went home, shagged someone else's wife and solved the murder.

19 Aug 2010

ASP.NET MVC 2 - Force Password Change

How to force the user to change their password when they log on? Here's how. This assumes you're not using the Membership model's User Comments field, because we're going to use it to store a flag. You're going to want to put 'using System.Web.Security;' at the top of your files cos we use the Membership classes a lot.

First define a nice enum somewhere as I'm scared of string literals.

public enum MembershipFlagType
{
RequirePasswordChange
}

Next we'll put in the code that sets the flag - e.g. in an ActionMethod where the user password is reset:

MembershipUser user = Membership.GetUser(User.Identity.Name);
user.Comment = MembershipFlagType.RequirePasswordChange.ToString();
Membership.UpdateUser(user);
string newPassword = user.ResetPassword();

Now we define a new Action Filter Attribute to perform a check for the RequirePasswordChange flag:

public class EnforcePasswordPolicy : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
MembershipUser user = Membership.GetUser(filterContext.HttpContext.User.Identity.Name);
if (user.Comment == MembershipFlagType.RequirePasswordChange.ToString())
filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(new {controller = "account", action = "changepassword"}));

base.OnActionExecuting(filterContext);
}
}

Then all you have to do is decorate your ActionMethods with the new attribute:

[Authorize]
[EnforcePasswordPolicy]
public ActionResult Index(IndexViewModel viewModel)
{
...
}

Don't forget to clear the comment field after the user has changed their password, otherwise they'll be stuck in a loop.

ASP.NET MVC Email Validation Attribute using Regular Expression

You want MVC 2 / 3 to automatically validate user-submitted email addresses? Stick the following class in your project:
using System.ComponentModel.DataAnnotations;

public class EmailValidationAttribute: RegularExpressionAttribute
{
    public EmailValidationAttribute() : base(@"^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-zA-Z0-9]{1}[a-zA-Z0-9\-]{0,62}[a-zA-Z0-9]{1})|[a-zA-Z])\.)+[a-zA-Z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$") {  }
}

Then you can use it on your model / model-metadata like this:
[EmailValidation(ErrorMessage="Not a valid Email Address")]
public string Email { get; set; }

NOTE: The regex is STRONG AND GOODLY because I found a definitive source. Scott Gu has a similar solution but for once THE MIGHTY GU HAS LET US DOWN. :p

1. His regex is incomplete and wrong (doesn't allow upper-case first part to the address)
2. His blogpost comments are disabled so I can't let him know about it.

NAUGHTY GU!

17 Aug 2010

JS Framework Roundup #834543

If you're a jaded Ajax monkey like me you'll have spent an age fighting the likes of Duffle, jSeraph and DOMinic trying to get cross-domain block blending right but with unsatisfactory results, either because of unspeakably low-grade filter belling or just the usual HIB/JSTFU squirrel poll chronlocks. I'll save you the hassle of surfing gitbox, mashStash or dumplocker for a better solution, because I have just been using it and it rocks: Cherub for SquirrelNuts.

We've seen stuff from Slutw4re before, anyone remember Shizbazz Tourette or the mNugget KPlaya extension? Paragons of REST/CHEST, and unlike other Web 4.0 JS hiblibs, they always just "felt right" when you decamped them into whatever framework is your homeboy, from MicMacMock to jQuirk. Even H-Christ got a look in, and that wasn't even at Alpha yet (What's before Alpha? Answers on an eCard...)

Anyway, Cherub is their latest baby, and of course it's GPL'd with the NeoMoose caveats so you're fine on uBert, Chuzzle or Medley but might have problems if you have Bonob:OO up to patch 59.9.7.9. But who's rocking Bonob:OO anymore, anyway? What is this, late march 2009?

But I digress! Okay, so you've decamped Cherub and slipstreamed with Mon2WonTon as usual. Now what? Well, look at the new chops and grooves like so:

$.fn.Cherub(bs.mode.throb);
var myCherub *= this.Cherub({Cherubim: Seraphim});
myCherub.Blend('rubicon/framework');

I know what you're thinking. Exactly! YAY!

More info here: http://dailyjs.com/

11 Aug 2010

ASP.NET MVC 2 Controller - Use Custom Attribute to intercept call to Action

I was thinking that it would be good to have certain MVC Actions run some common code every time they are called. Naturally we could just call a common method on the first line of our Action methods, but that would not be MVC sexeh. After all, we use special Attributes such as [Authorize] and [HttpPost] to intercept the Action, so why not roll our own?

Microsoft have done the hard work for you - all you have to do is extend an attribute called ActionFilterAttribute (a type of FilterAttribute) and override its calls to the following events as required:

- OnActionExecuting
- OnActionExecuted
- OnResultExecuting
- OnResultExecuted

For example, I wanted to update the User's LastActivity timestamp whenever certain Actions were called, so I created the following:


public class LogUserActivityAttribute: ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
... Do your log activity stuff here ...

base.OnActionExecuted(filterContext);
}
}


I could then have that code executed on a per-Action basis like so...


[HttpPost]
[LogUserActivity]
public ActionResult DeleteItem(int id)
{
...
}


HTH! Incidentally, don't forget that instead of this funky MVC apprioach, you can set the Application_BeginRequest method in the global.asax file, which runs at the start of every web request to the application, e.g.

protected void Application_BeginRequest(Object sender, EventArgs e)
{
... Do your per-request stuff here ...
}
If I helped you out today, you can buy me a beer below. Cheers!