Wednesday, 20 February 2013

CreateTaskWithContentType (02)

Create Custom Column(s)

Why would we make a custom site column? Why would you use the CreateTaskWithContentType activity if we didn't need custom columns. If you do not need a custom column, use the CreateTask activity.

1.) If you're still with me let's make a custom field/column whichever you prefer to call it. right click your project->Add->New Item->Empty Element

Make sure you have a SharePoint 2010 template selected in the left hand pane and pick the Empty Element Type in the centre pane, give it a name and click Add.

2.) Once you've added an empty element Item to your project define your column(s) as needed. I'm creating just one since this is not an exercise in defining site columns.
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Field ID="{8F1F2378-C7F1-4D1B-BD4B-8F3951ECCB7D}"
         Name="spm_KiwiMango" 
         Group="Custom Workflow" 
         DisplayName="Kiwi Mango"
         Type="Text" 
         Sealed="FALSE" 
         ReadOnly="FALSE" 
         Hidden="FALSE"
         DisplaceOnUpgrade="TRUE"/>
</Elements>
that's mine make yours however you like.

Create Content Type for tasks

1.) Now you've probably made lots of content types, but for the love of god do not skip this part. To get started add a content type. Right click your project->Add->New Item
again make sure to have the SharePoint 2010 Template selected, pick the content type Object type give it a name  and click Add.

2.) When prompted which base type should this content type inherit from? select task

3.) This is what you get out of the box.
what you want it to look like is the following
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <!-- Parent ContentType: Task List (0x01080100) -->
  <ContentType ID="0x01080100d6b5580b746b4e68b032f3cdc88b6062"
               Name="spm_WorkFlow_CT"
               Group="Custom Content Types"
               Description="My Content Type"
               Inherits="TRUE"
               Version="0"
               Hidden="FALSE">
    <FieldRefs>
      <FieldRef ID="{8F1F2378-C7F1-4D1B-BD4B-8F3951ECCB7D}" Name="spm_KiwiMango" />
    </FieldRefs>
    <XmlDocuments>
      <XmlDocument NamespaceURI="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url">
        <FormUrls xmlns="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url">
          <New>_layouts/WrkTaskIP.aspx‌​</New>
          <Display>_layouts/WrkTaskIP.aspx</Display>
          <Edit>_layouts/WrkTaskIP.aspx</Edit>
        </FormUrls>
      </XmlDocument>
    </XmlDocuments>
  </ContentType> 
</Elements>
Not exactly your run of the mill content type. Things of note are
  • Inherit from the base type 0x01080100
  • Set the hidden attribute to false
  • Add your field ref
  • Specify a generic place holder for the forms, do this so that when we make the list definition the workflow doesn't use the OOTB (out of the box) forms. (FormUrls Schema Overview)
Wowzers looks like we're done with the content type. Next lets make us a list definition(again there's a trick to it, so don't stop paying attention.

It's List Definition Time

1.) Start like you would with any list definition from content type, right click your project->Add->New Item
Now as always make sure you have SP2010 template selected in the left hand pane, in the centre pane pick "List Definition From Content Type", give it a much more descriptive name then I did and hit Add.

2.) Next you'll be prompted to enter in a couple of details about your list definition a display name, which content type to use and whether to include a list instance.
So give your list definition a name, pick your content type, and well I never like to include my list instance with my list definition so I guess do what I'm doing or don't...

3.) Now open up your List Definitions elements file.
change the Type property to 107, this will define it as a task list

4.) Now with that complete you can open up the schema to inspect the difference you're content type made
It's not all that interesting but hey why not right?

5.) Declare a List Instance from the list Definition you just created. As always, right click your project-> Add Item->New
SP2010 template, List instance item, Descriptive name, Hit Add.

6.) Next you're going to have some list instance details to fill out.
Give it a name, make sure to select your List Definition, clean up the URL, if you want add it to the quick launch that's you're call or should be in your governance (whatever that is).

7.) Next deploy your solution, navigate to your root web and check on your list. if there's nothing there, then open up your feature and make sure that you include your columns, content type, list definition and list instance, the work flow is not necessary just yet.

http://[your site]:[your port number]/_layouts/viewlsts.aspx

now you should see something along the lines of

If you see a magical task list, having the clipboard icon with the check mark you where successful, if you have the list but the wrong icon go back to your list definition and change the type to 107, then go to your list instance and change the TemplateType attribute to 107, redeploy and check again. if you don't have the list start over I guess....

Continued

Tuesday, 19 February 2013

CreateTaskWithContentType (01)

Creating a Task with a custom content type turned out to be a nightmare... I'm putting this together so that it doesn't become a reoccurring one. There are seven steps to pushing through this kerfuffle.

Create a Workflow Project

It doesn't really matter whether you make a sequential or state machine workflow; a State Machine workflow will add it's own level of complexity, but since this post is focusing on the CreateTaskWithContentType activity I'm going to create a sequential to simplify this example as much as possible.

1.) Add a Sequential workflow project to your solution. Right click your solution->Add->New Project.

2.) Next, pick your site with port number and you shouldn't have a choice but too deploy as a farm solution.

3.) Now you have to name your workflow and select whether it's a list or a site, I'm going with List though I'd imagine it shouldn't make a difference, but don't quote me on that.

4.) At this final junction you'll basically have two choices, either specify all your details or do it manually later, since the whole point of this series of posts is to create your own custom task list with custom content type it doesn't  make too much sense to define them now. Disable the automatic association.


WOOT WOOT, you've just created your sequential workflow project, feel great? you shouldn't it's only going to get worse from here on in.

Continued in Part 02; creating Site Columns, Content Type, List Definition, List Instance.

Thursday, 31 January 2013

List Page Layout Properties


function LoadSharePointPowerShellEnviroment
{
                write-host
                write-host "Setting up Powershell enviroment for Sharepoint" -foregroundcolor Blue
                Add-PSSnapin "Microsoft.Sharepoint.PowerShell" -ErrorAction SilentlyContinue
                Write-host "Sharepoint PowerShell Snapin loaded." -foregroundcolor Green
}
LoadSharePointPowerShellEnviroment

$site = new-object Microsoft.SharePoint.SPSite("http://ssrap7:88")
$spWeb = $site.OpenWeb("ETSforSuccess")
$spFile = $spWeb.GetFile("Pages/default.aspx")
             

$i = 0

foreach($key in $spFile.Properties.Keys)
{
    write-host $i "File Key: " $key
    $i +=1
}

$i = 0
foreach($val in $spFile.Properties.Values)
{
    write-host $i "File value: " $val
     $i +=1
}

Get Status GUID

When dealing with workflows certain columns must be referenced via their GUID, this is a quick Powershell script to do just that

function LoadSharePointPowerShellEnviroment
{
write-host
write-host "Setting up Powershell enviroment for Sharepoint" -foregroundcolor Blue
Add-PSSnapin "Microsoft.Sharepoint.PowerShell" -ErrorAction SilentlyContinue
Write-host "Sharepoint PowerShell Snapin loaded." -foregroundcolor Green
}

write-host
LoadSharePointPowerShellEnviroment

$site = Get-SPSite ["http://YourUrl:80"]

$web = $site.rootweb
$pages = $web.lists["Item Approval Task"]
$statusField = $pages.Fields["Status"]

write-host $statusField.id

Friday, 21 December 2012

Workflow email task Link

So you've created a work flow in SharePoint Designer and now you want to let someone know they have a task, but you don't want to just tell them that they have a task and go find it you want to send them a link to the task. Let's face it odds are they're a middle tier manager who's responsibility extends to approving business card request and paid time off; a B.Com degree paying off.

So let's pretend that you have a workflow already set up and all you want to do is create this assignment email: 
In SharePoint Destroyer Designer navigate to your workflows

once you've selected your work flow, edit it.


now that you're ready to edit your work flow, add a step and inside that step create a "start custom task process" action. In the picture below the task is called "Business Card request Task" this is a name I gave it, originally it should start as "Task".


once you've opened the task, under Customization click "Change the behaviour of a single task"


this will load 5 default steps
  1. Before a task is assigned
  2. when a task is pending
  3. when a task expires
  4. when a task is deleted
  5. when a task is completed
You are concerned with the second one "Pending". Add the action email user.


Once you select the user add the appropriate fields


To Create the link address click the link button circled in green above.


Click the fx button to build the URL for your link

Select the current task, and pick the Form_URN, this will give you the task item url

Keep hitting ok followed by a publish and give it a try.

Wednesday, 19 December 2012

Find Text on Publishing Page

Sometimes you need to find a specific text string on your site, maybe it's a common typo, or it could be a name change lets say mr Chooch turned into Dr Chooch and you want to know all the pages that Mr Chooch appears on.


param($web_app_url=$(read-host "Please provide web app url of the list to be delete (EG:'http://SSRAP1'):"))

$TextToFind = $(read-host "Please Provide the text to find")
 
if($ListToDelete -eq $null -or $ListToDelete -eq '')
{
    throw "You must specify a text string to search for"
}

function LoadSharePointPowerShellEnviroment
{
write-host "Setting up Powershell enviroment for Sharepoint" -foregroundcolor Blue
Add-PSSnapin "Microsoft.Sharepoint.PowerShell" -ErrorAction SilentlyContinue
Write-host "Sharepoint PowerShell Snapin loaded." -foregroundcolor Green
}

function ScrubPages($nodeWeb2)
{
    $pubWeb = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($nodeWeb2)
   
    try
    {
        $pubPages = $pubWeb.GetPublishingPages($pubWeb)
        foreach($pp in $pubPages)
        {
            $content = $pp.ListItem["Page Content"]
       
            if($content -ne $null)
            {
                if ($content.Contains($TextToFind))
                {
                    $data = @{"url" = $pubWeb.url.ToString() + "/pages/" + $pp.name.ToString() }
                    New-Object PSObject -Property $data         
                }
            }
        }
    }
    catch [Exception]
    {
         #It wasn't a publishing page
    } 

}

function RecurseNav([Microsoft.SharePoint.SPWeb]$nodeWeb)
{
    $subWebs = $nodeWeb.GetSubwebsForCurrentUser()
    ScrubPages($nodeWeb)

    foreach ($nextweb in $subWebs)
    {
        RecurseNav($nextWeb)
    }
}


try
{
    if($web_app_url -eq $null -or $web_app_url -eq '')
    {
        throw "You must specify your server name. Value provided was null/empty."
    }
  
    $web = Get-SPWeb $web_app_url

    RecurseNav($web) | Out-GridView
}
catch [Exception]
{
    Write-Host -ForegroundColor Red "Error: $_"
} 


Saturday, 1 December 2012

Validate & Verify a Date

When I say Verify, I mean make sure that it's in a correct format; this can very easily be accomplished using regex; if you don't know what that is, it's basically an expression to check formatting of a string against. I suggest reading the following site:

Zytrax this is an excellent source for regex knowledge, I use it regularly.

The following JavaScript function takes in a string and checks it against the following pattern ##/##/####. you may notice it doesn't make sure that the month is between 1-12 or the day is less then 31, it just makes sure that you entered 3 numbers and that they're separated by forward-slashes.

function validdateDate(dateString)
{
    var datePattern = /^\d{2}[/]\d{2}[/]\d{4}$/;
    return datePattern.test(dateString);
}

Now Validate, well that's a bit more tricky. First lets talk about what I mean by validate, if you run the date 02/29/2011 against a simple date Regex it will come back as valid, but it's clearly not since that date never occurred.

At first I thought I could just load the individual month day year into a constructor for a JavaScript Date object and just get an invalid date exception, but no such luck, lovely JavaScript  will just bump the date up to 03/01/2011. Good bad it doesn't really matter, what matters is that I still haven't validated my Date. It's high school programming time.

To resolve this issue first we need a function to check if we're dealing with a leap year, simple enough you can grab the algorithm from numerous sites; I got this one from Wikipedia or if your super lazy it's below.

function isLeapYear(year)
{
    if(year%400==0)
        return true;
    else if(year%100==0)
        return false;
    else if(year%4==0)
        return true;
    return false;
}

Now that we can tell if we're dealing with a leap year lets check to make sure that our date is an actual date. that has occurred.

function verifyDate(dateString)
{
    var daysInMonth = [31,28,31,30,31,30,31,31,30,31,30,31];

    if(dateString.length != 10)
        return false;

    var da = dateString.split("/");
  
    if(da[2] <= new Date().getFullYear())
    {
        if(da[0] > 0 && da[0] < 13)
        {
            if(da[0] == 2 && isLeapYear(da[2]))
                return (da[1] > 0 && da[1] < 30);
            else
                return (da[1] > 0 && da[1] < daysInMonth[da[0]-1]);
        }
    }
    return false;
}

Well there you have it, are there other much more robust ways to check date, sure there are, could I have written this function to take in some sort of pattern to check against rather then just month/day/year, absolutely, but i'm not writing an API, I'm just making a one off function to filter out shitty data before it hits my server.