Thursday, 8 July 2010

Colour code a RAG column in Sharepoint 2007 list view using Jquery

To see the best selling SMART televisions visit Yngoo!
I'm a big fan of Microsoft Sharepoint, even though it really annoys me sometimes. It's amazing that no matter what question a client asks of it, it never can quite answer the question straight out of the box. However, this is not a problem, because Sharepoint is virtually infinately customisable by a competent programmer with enough time to become emersed in the "Sharepoint Way" and no fear of learning new techniques.
An example of this came up for me recently when a client requested that a column on a list would be colour coded depending on the value of the data in that column. The data would be a RAG rating (i.e. a "choice" field containing a set of values that relate to red, amber or green), and therefore it would be nice to colour code the column in eiter red, amber or green to match the rating. Sharepoint does not give you any easy way of doing this unfortunately.

This is where Jquery comes in. This terrific library of javascript offers developers a new and consise way of dealing with the internet document object model (DOM). With it developers can quickly and easily scan the object model of the page returned in the browser, and change the page before it is rendered. There is far more to it than this of course, but this is the trick I used to colour code the columns.

To colour (color) code a coloumn, follow these steps:



  1. Download the jquery.js library from teh link above, and upload it to your Sharepoint site in a convenient location (I use the Site Documents Collection in my top level publishing site, but anywhere will do)
  2. Edit the Sharepoint page that the holds the list, and add a Content Editor Web Part (CEWP).






  3. Edit the CEWP, choose the "edit source" option, and paste in something like the following code








<script type="text/javascript" src="http://<root site>/SiteCollectionDocuments/jquery-1.4.2.js"></script>

<script type="text/javascript">

$(document).ready(function(){
$(".ms-vb2:contains('1: No Assurance')").each(function(){
$(this).css("background-color", "#FF0000");
//$(this).css("filter", "alpha(opacity=50)"); 
});

$(".ms-vb2:contains('2: Limited Assurance')").each(function(){
$(this).css("background-color", "#FF9900");
});

$(".ms-vb2:contains('3: Significant Assurance')").each(function(){
$(this).css("background-color", "#FFFF00");
});

$(".ms-vb2:contains('4: Full Assurance')").each(function(){
$(this).css("background-color", "#00CC00");
});

});
<script>

Note that in this example, the Jquery library is located at: http://<root site>/SiteCollectionDocuments/jquery-1.4.2.js. Change this to the URL of your own Jquery library.

In my example, the RAG rating column can contain the following values:
  • 1: No Assurance  (RGB #FF0000)
  • 2: Limited Assurance (RBG #FF9900)
  • 3: Significant Assurance  (RGB #FFFF00)
  • 4: Full Assurance (RGB #00CC00)

Now to explain what this is actually doing: "ms-vb2" is the standard Sharepoint site template CSS class that is used to identify columns in a list view web part.
What this Jquery script is effectively doing is looking for a DOM item with the class "ms-vb2", and in each case that it this
DOM item contains a specific piece of text ("1: No Assurance", "2: Limited Assurance" etc.), it modifies the CSS of this
particular section of the DOM to add a "background-color" CSS tag.

Of course your list will have different values, so remember to change the strings in bold to match your own list values!
The colour codes are standard RGB, so you may find this RGB table useful to decide your colours.


Your results will look something like this:
Best of luck!

Carissima Gold 9 ct Yellow Gold Two-Row Diamond Cut Curb Bracelet of 21 cm 8.5-inch on www.yngoo.co.uk
Carissima Gold 9 ct Yellow Gold Two-Row Diamond Cut Curb Bracelet of 21 cm/8.5-inch

Tuesday, 11 May 2010

Write lines to a RichTextBox control in different colours (VB .net example)

I create a lot of back end "processing" software, for example migration or upgrade tools that process documents as they find them on a server. As a standard, I tend to write their progress out to a text file, but I also like the program to show me what is happening as it processes files by writing the log file to the screen. This is particularly useful when debugging the application, but it's also far more satisfying to see something happening on the screen.

In this situation it is nice to write out warnings or non critical errors in different colours in order to attract the eye.

To achieve this, create a RichTextBox control on your main form (in my example it is called "fInfoBox"), and write to it using the code in the sub routine below.






Sub ShowLog(ByVal qFore as Drawing.Color, ByVal sMesg as string)
   Dim sDispMess As String
   sDispMess = Date.Now()& ": " & sMesg & vbCrLf 

   With fInfoBox 'This is the name of you RichTextBox control on your form
       .SelectionStart = Len(.Text)
       .SelectionBullet = True  ' This shows the line in bulleted form, comment out if not required

       .SelectionColor = qFore
       .SelectedText = sDispMess.ToString()
    End With

    fInfoBox.ScrollToCaret()  ' This scrols to the last line of the text box
End Sub

To display a message with a font colour of blue, call the routine like this:
ShowLog(Drawing.Color.Blue, "This message will appear in a font color of blue")
For a red message, try this:
ShowLog(Drawing.Color.Red, "ERROR: This message will display in a font color of red!!")
This is a very simple example, but I hope it helps someone get started with this sort of thing and gives you some ideas.

Remember that if you use threading or the backgroundworker class to remove your time consuming processes from the UI (which you should, especially since this is so easy after .Net 2.0), then you will need to use a callback technique for your worker thread to be able to talk to your UI thread.

Wednesday, 21 April 2010

VBA code to iterate through the results of GetListCollection web service from Sharepoint 2007


In a previous post I discussed how to set meta-data in Sharepoint 2007 directly from VBA.  I mentioned that the Microsoft documentation for the Sharepoint web services does not appear to be correct, insofar as you cannot address a Sharepoint "list" in the web service using its name, but rather you are forced to use the GUID.

I have now had to write a function to retrieve the GUID for the list from teh Sharepoint site.  This proved to be much harder in practice than I had expected it to be, partly at least because of the problems in working in the VBA IDE with its poor debugging functionality, partly because VBA is a much more limited language than .Net and I have got used to using all the nice constructs such as generics that make life so much easier,  and partly because I still find MSXML very "brain" hard to work with.  I don't know why I find it so hard, it just always seems that everything ends up being far more complicated than I think it should be.  I find the .Net XML model much easier to deal with.

Anyway, after playing about with XSLT and XPATH in order to extract the ID and VersionNumber attributes from the wsm_GetListCollection web service (and failing miserable), I finally decided to just iterate through the nodes and attributes till I found what I wanted.  The code below is a simple function that you can call with three parameters:  the name of the list that you need the GUID for (sListName),  a string variable in which the GUID will be placed (sListID), and an integer that the list version number will be placed (iListVersion).

Note: You may also be interested in this post: VBA code to check in a document to Sharepoint and set meta data.


Function GetListCollection(ByVal sListName As String, ByRef sListID As String, ByRef iListVersion As Integer)
    ' Class created by toolkit to connect to the Web service
    Dim ws As New clsws_Lists
    ' The collection is returned as an XML node list
    Dim ListCollectionNodeList As MSXML2.IXMLDOMNodeList
    'Root node of the returned list
    Dim nod As MSXML2.IXMLDOMNode
    ' Output string for the XML transformation
    Dim strOutput As String
 
    On Error GoTo GetListCollection_OnError
 
    ' Retrieve the collection of lists
    Set ListCollectionNodeList = ws.wsm_GetListCollection
 
    ' Get the root node from the list
    Set nod = ListCollectionNodeList.Item(0)
 
 
    ' Iterate nodelist to find details for our named list
    Dim bGotNode
    bGotNode = False
 
 
    Dim procNode As MSXML2.IXMLDOMNode
    Dim TitleNodeList As MSXML2.IXMLDOMNodeList
    Dim IDNodeList As MSXML2.IXMLDOMNodeList
    Set TitleNodeList = nod.SelectSingleNode("//Title")
    Set IDNodeList = nod.SelectSingleNode("//ID")
 
    Dim ListNode As MSXML2.IXMLDOMNode
    Dim ListChildNodes As MSXML2.IXMLDOMNode
    Dim oAttr As MSXML2.IXMLDOMAttribute
 
    Dim retID As String
    Dim listVersion As Integer
    Dim ListName As String
 
    ' Iterate through list nodes returned (should only be one)
    For Each ListNode In ListCollectionNodeList
        'Iterate through the child nodes (each one represents a single Sharepoint list)
        For Each ListChildNodes In ListNode.ChildNodes
            'See if we have any attributes (we should have!)
            If ListChildNodes.Attributes.Length > 0 Then
                ' For each list, reset our variables
                retID = ""
                listVersion = 0
 
                ' Iterate through the attributes
                For Each oAttr In ListChildNodes.Attributes
                    Debug.Print "Name= " & oAttr.Name
                    Debug.Print "Value= " & oAttr.nodeTypedValue
 
                    'Look for the ID attribute
                    If oAttr.Name = "ID" Then
                        ' FOund it, so remember it in case this is our list
                        retID = oAttr.nodeTypedValue
                    End If
 
                    ' Look for the "Title" attribute
                    If oAttr.Name = "Title" Then
                        If oAttr.nodeTypedValue = sListName Then
                            ' Found our list, so remember it
                            ListName = oAttr.nodeTypedValue
                            bGotNode = True
                        End If
                    End If
 
                    ' Look for the "Version" attribute
                    If oAttr.Name = "Version" Then
                            ' Found our list, so remember it
                            listVersion = oAttr.nodeTypedValue
                    End If
 
                    ' Once we have found a title, ID and version, break
                    If retID <> "" And listVersion <> 0 And bGotNode = True Then
                       Exit For
                    End If
 
                Next oAttr
            End If
            ' Once we have found the data, break out of iteration
            If bGotNode = True Then
                Exit For
            End If
        Next
    Next
 
    If bGotNode = False Then
        Err.Raise 65000, "", "ERROR: Cannot find list named <" & sListName & "> in Sharepoint site.", "", ""
    End If
 
    sListName = ListName
    sListID = retID
    iListVersion = listVersion
 
    Exit Function
 
GetListCollection_OnError:
    MsgBox ("ERROR: Retrieving list collection from Sharepoint." + vbCrLf + _
           Err.Number + " - " + Err.Description)
End Function

Remember, you will have to have installed the Microsoft Web Services Toolkit before this will work, and add a web reference to your Sharepoint site to your VBA project. This has been tested with Office 2003 linking to Sharepoint 2007 and works :).

Wednesday, 10 March 2010

Set Sharepoint meta-data from VBA using updatelist web service

For the top ten best selling Rolex watches, visit Yngoo!
I've been working on a problem trying to integrate a legacy Microsoft Office 2003 VBA application with Sharepoint 2007. In theory, I should be able to use the SOAP toolkit provided my Microsoft to talk to the Sharepoint (WSS) web services, but in practice there was very little documentation of how to do this, and I could find no resources on the internet aside from the MSDN definitions of the web services, and only a little attached to the SOAP toolkit (available here: Microsoft Office 2003 Web Services Toolkit ).

The basic problem I was trying to solve was: how do I set meta-data held on a document in Sharepoint from Office 2003 using VBA?

After much head scratching I finally solved this by making some assumptions based on the MSDN reference documentation, then using the Fiddler tool until I had worked out what I was doing wrong.  Fiddler allows you to inspect the HTTP calls and responses that you are issuing/receiving.  This is very helpful when debugging web services, and well worth looking into if you are doing anything in this area.

Anyway, through Fiddler I could see the response from the web service, including the error message.  This meant that my initial best guess on how to structure the XML was at least nearly correct, because the web service was giving me a proper error message.  Eventually I had a properly structured call and everything worked!


Remember to install the SOAP toolkit before you try this.  You will then be able to add this to your VBA project and add a web reference to your project to the WSS Lists.asmx service as you would if your using .Net.  This is all explained in the MSDN link above but drop a comment if you would like me to go into more detail.

The actual VBA code used structure the call to the web service looks like this:

'Define a new list service web service class
Dim listws As New clsws_Lists

' Set up the batch command used to set the meta data. 
' Note that FileRef is set to URL of the file you want to change
'ListVersion is from my test site, you may not need that
' First set up DOM document containing fields
Dim xmlDoc As New MSXML2.DOMDocument30
xmlDoc.async = False
Dim xmlText As String
xmlText = "<root>" + _
  "<Batch OnError='Continue' ListVersion='59' PreCalc='TRUE' xmlns=''>" + _
      "<Method ID='1' Cmd='Update'>" + _
        "<Field Name='ID' />" + _
        "<Field Name='FileRef'>http://myservername:1080/sites/mysite/shared documents/TestWebService.doc</Field>" + _
        "<Field Name='Title'>Uploaded from VBA</Field>" + _
        "<Field Name='Surname'>" + sLastName + "</Field>" + _
        "<Field Name='Forename'>" + sFirstName + "</Field>" + _
        "<Field Name='Date_x0020_of_x0020_Birth'>" + sDOB + "</Field>" + _
      "</Method>" + _
    "</Batch>" + _
  "</root>"
xmlDoc.LoadXml (xmlText)

' This is a bit of debug that checks the XML is well formed and show you it if it is. 
If xmlDoc.parseError.ErrorCode <> 0 Then
    Dim myErr
    Set myErr = xmlDoc.parseError
    MsgBox (myErr.reason)
Else
    MsgBox xmlDoc.XML
End If

' Set up IXMLDOMNodeList object
Dim myXMLNodeList As MSXML2.IXMLDOMNodeList
Dim root As MSXML2.IXMLDOMElement
' Now the the XML node list to the batch command we have just constructed above
Set root = xmlDoc.documentElement
Set myXMLNodeList = root.ChildNodes

' Now run the web service to update the meta-data.
' Note that the first parameter is the GUID of the list that holds the data you want to change.  I can't get this to take a list name - it only seems to wrok with a GUID.
Dim updateReturn As IXMLDOMNodeList
Set updateReturn = listws.wsm_UpdateListItems("{B768B024-8B98-4918-990A-ECE34691DBBC}", myXMLNodeList)

If you need to find out the GUID or ListVersion values for your list, use the "GetListCollection" web service and iterate through the XML it returns:

Dim listCollection As IXMLDOMNode
Set listCollection = listws.wsm_GetListCollection
' You can iterate this to find the GUID of our list
I can post further examples if anybody wants any.

*Edit* - I have written another blog post on how to find the GUID of lists in VBA here: http://the-simple-programmer.blogspot.com/2010/04/vba-code-to-iterate-through-results-of.html
This simple function will return the version number and the GUID of the Sharepoint list from its name.  

You may also be interested in teh following post: :  VBA code to check in a document to Sharepoint and set meta data.



Sunday, 21 February 2010

SQL Server - MySQL 4 integration - openquery does not work for tables with datetime values of 0000-00-00

I have been trying copy the contents of an old MySQL 4.0.12 database to SQL Server 2008 in order to archive its contents.

To archive this, I followed these steps:
  1. Create an database within SQL Server 2008 to hold the data called ARCHIVE_DB.
  2. Download and install ODBC driver for MySQL (available here).  Note that due to the version of the MySQL database I was unable to use the newer driver (version 5.1.xx), since this requires version 4.1 of MySQL.
  3. Set up a linked server in SQL Server 2008 using this driver to point to the data source.  I called this data source MYSQL_ls.
  4. I can now select data from this data source using the "openquery" TSQL syntax, eg:


    SELECT *
    FROM openquery([NADT-SQLHOST], 'select * from webdb.episode')
    

  5. I can extend this query using the TSQL "INTO" clause, which will build the SQL table from the source data, eg:


    SELECT * INTO ARCHIVE_DB
    FROM openquery([MYSQL_ls], 'select * from mydatabase.mytable')
    

  6. In theory the above will copy all the data from the MySQL database table called (in the MySQL database called ) into the SQL Server table (SQL Server database ).  I do not have to create the table schema, this will be created automatically by the INTO clause.  
  7. I can now create an SSIS package that loops through the tables in the MySQL database and runs the SQL statement show in number 5 against each table.  This should in theory copy each table across.
This technique works very well until you come to tables that include datetime values.

MySQL allows datetime values in the format of "0000-00-00", which is the equivalent of a NULL date in SQL server.  Unfortunately the "Select * Into From Openquery..." trick above does not deal with this situation, and you will recieve a version of the following error:

Msg 7342, Level 16, State 1, Line 4
An unexpected NULL value was returned for column "[MSDASQL].enddate" from OLE DB provider "MSDASQL" for linked server "MYSQL_ls". This column cannot be NULL.

This error is caused by using "openquery".  Fortunately there is a very easy work around for this:  where tables have datetimes in "0000-00-00" format, you must use an SSIS data flow task to copy the information, rather than the "openquery" described above:

  1. Set the data pump source to the MySQL table pointed to in the Linked server and click on "preview" and you will notice that it can return data.  This is not possible by running a query in the SQL Management studio, so clearly we are getting somewhere.  
  2. Now set the data pump target to "" and click on "Mappings" and you will notice that each column in the source table will be mapped to an identically named table in the destination table.
  3. Save the data pump and run.

If you run this data flow, the data will be copied across without reporing the datetime mismatch error.

Wednesday, 17 February 2010

Use a variable as a table name in an SSIS Execute SQL Task

I spent quite a while today trying to build an SSIS package that iterated through a dataset that contained a set of table names usign a for-each container, then run a piece of SQL against each table.

Setting up the for-each loop was relatively simple, but I couldn't get the Execute SQL task to run a simple query of the type:
Select count(*) from @table
Where @table is the name of the parameter generated from the for each loop.

It appears that this is not possible in SSIS.  Eventually I found a solution:  use another variable to store the SQL statement and set up the SQL task to use this variable as its input source.

Assuming that the input parameter is already set up and called [User::TableName],  then the steps to achive this are:

  1. Set up a new variable (say called "SQLStatement"), make this a string type.
  2. In the properties box of the paremeter, Set EvaluateAsExpression to True
  3. In the properties box of the paremeter, click on the elipses (...) of the "Expression" property to build an expression.  Type in the SQL you need and drag the variable you want to use from the "variables" tree.
  4. Escape you SQL in this expression wwithin quotation marks.  e.g. the count statement above would look like:
  5. "Select count(*) from " + @[User:TableName] 
  6.  You can use the "Evaluate expression" button to make sure that this evaluates as real SQL.  If you are using a "for-each" container then you may want to give your [User::TableName] variable a sensible default value in order to see the results of the evaluated expression in a format you could use for testing.
  7. Drag an "Execute SQL Task" object into your control flow and open its properties page
  8. Set the "SQL Source Type" property to "Variable" to tell it that the SQL will come from a variable, rather from a typed in SQL statement.
  9. Set the "SourceVariable" property to the name of the variable you created in step 1 (in this example it would be "SQLStatement".
That's it!  

    Monday, 8 February 2010

    Blogger.com code syntax highlighting, or always remember to save your code before you start playing around with it!

    I've decided to re-vamp the look of this blog to give it more width and make the code fragments easier to read. I hope you like the new look!

    When doing this I made one of the oldest mistakes in the programmer's book: I didn't save my work!

    This meant I had to add all the site specific code to render the code in syntax-highlighted format, and add Google analytics tracking code back into the HTML, which whilst not the worst job in the world, did take me some time to remember what I had done. REMEMBER TO SAVE YOUR WORK!

    This does give me the opportunity to thank the author of the fabulous syntax highlighter for Blogger.com that I have used, which can be found here: http://alexgorbatchev.com/wiki/SyntaxHighlighter. Thank you very much Alex, it's a superb bit of code!

    The code I used to enable the nifty highlighter for C# should be pasted into the HEAD section of your blog:


    <link href='http://alexgorbatchev.com/pub/sh/current/styles/shCore.css' rel='stylesheet' type='text/css'/>
    <link href='http://alexgorbatchev.com/pub/sh/current/styles/shThemeDefault.css' rel='stylesheet' type='text/css'/>
    <script src='http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js' type='text/javascript'></script>
    <!-- add brushes here -->
    <script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJScript.js' type='text/javascript'></script>
    
    <script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushBash.js' type='text/javascript'></script>
    <script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushSql.js' type='text/javascript'></script>
    <script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushXml.js' type='text/javascript'></script>
    <script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCpp.js' type='text/javascript'></script>
    <script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCSharp.js' type='text/javascript'></script>
    <script type='text/javascript'>
      SyntaxHighlighter.config.bloggerMode = true;
      SyntaxHighlighter.all();
    </script>
    
    

    To use it, make sure that the code you want to display has been HTML escaped (which can be done using the utility available here: http://www.string-functions.com/htmlencode.aspx.

    Now, to use this, just paste your escaped code into your blog and wrap it in the following HTML tags:
    <pre class="brush:csharp">
       ...your code here...
    </pre>
    

    Note that this will work for C Sharp code. For other types of code highlighting, simply change the brush type in the <pre> tag, and make sure the appropriate brush is included in the list of brish types defined in the code above in the "!-- add brushes here --" section.