Docstoc

Microsoft Word Cookbook Template

Document Sample
Microsoft Word Cookbook Template Powered By Docstoc
					Sharepoint Portal
Server Cookbook




                          James O'Neill
        UK Platforms Consulting Practice
                      Microsoft Services
Contents
Contents ...................................................................................................................................... 2
Preface ........................................................................................................................................ 3
On Taxonomy … ........................................................................................................................ 4
Bulk uploading to Sharepoint ..................................................................................................... 6
Setting Categories (and other properties) in bulk....................................................................... 9
Getting Sharepoint to help with formatting: using its style sheets and built-in icons. ............. 12
Providing "Minimize Web Part" functionality using DHTML with the styles and icons
provided by SPS ....................................................................................................................... 14
More on the built-in styles, and querying for all the instances of a class – part 1 ................... 17
Moving away from ADO ......................................................................................................... 21
Querying for categories, folders and other types (querying for all the instances of a class part
2)............................................................................................................................................... 28
Parameters – and re-using the default announcements, news and links Web Parts ................. 31
Using the fields of the Web Part: parameterizing or hiding your Web Parts ........................... 32
Testing folder security .............................................................................................................. 33
Paths in Web Parts, using substitution, and ways to discover the path .................................... 34
One last thing on paths – the Document Library ..................................................................... 36
Using the Portal's own forms with "Placeholder documents" in your own applications ......... 37
Exploiting properties and profiles ............................................................................................ 41
Web links and place holder documents .................................................................................... 43
Another web links part … Contribute ...................................................................................... 47
The XMLHTTP object and "Chainsaw" development of Web Parts ....................................... 49
Working with the Digital Dashboard Services Component. .................................................... 53




Sharepoint Portal Server Cookbook                                                                                            Page 2
Preface
Why a Cookbook? One reason has to do with cookery - I am a competent cook, but generally
I don‟t follow recipes: I read books about cookery for ideas, which I try to follow. My mother
has an ancient exercise book – it's at least 42 years old - which contains various useful scraps
of information she has picked up over the years (often cut from somewhere else and glued in).
She doesn‟t need to refer to it very often, but it‟s a valuable source. The other reason is a nod
to Chris StValentine‟s Access Basic Cookbook which helped me out of a couple of really
tricky problems One was, in Access 2.0 how did you fill a list box with the user's Schedule
Plus tasks (or any other dynamic list which wasn‟t in a database) I found the answer in
Chris‟s book. I'm trying to put together a "Go to" resource for Sharepoint Portal server like
those

The best dedication I have ever seen in a book goes like this.
"I am sending you a present which, if it does not come up to the obligations I owe you, is at
any rate the best that [I am] able to send you. For in it I have set down all that I know and
have learnt from a long experience of, and from constantly reading about, [this subject]. And
since neither you nor anyone else can expect more from me than this you will not be
disappointed that I am not sending you more. You may perhaps lament my lack of skill should
my narratives be thin, and also errors of judgement should I, in discussing things, have in
many places made mistakes. If this be so, I know not which of us is the less obliged to the
other, I to you for having forced me to write what I should never have written of my own
accord, or you to me if what I have written fails to satisfy you. Accept it then in the manner in
which things are accepted amongst friends, by whom the intention of the giver is always more
esteemed than the quality of the gift”

So there you are, this is in the same class as my mother's cookery scrapbook; though since I
have today‟s software to work with instead of scissors and glue I can be a little more
organised. Forgive me my shortcomings, and accept it as gift between friends.

A quick note on embedded files

In a number of places I have embedded zip files in this document. If you have problems
extracting any of the files, right click them, and choose "Package object" and "Edit Package",
then choose file save from the object packager.




Sharepoint Portal Server Cookbook                                                 Page 3
On Taxonomy …
I‟ve distilled what I‟ve learnt about this subject in these basic rules:
    1. Gather useful meta-data. The meta-rule is "It‟s all about gathering and using meta-
        data".
    2. Do not add any field to a profile unless you are sure it will be useful. Authors want
        to write their documents, save them and get on with the next job: generally they do not
        want to spend time entering lots of profile. Remember that you need their co-
        operation.
    3. It is better to search for unique information in the document body than in a
        meta-data field. For example, getting the author to enter a serial number field allows
        readers to search for documents with that serial number. But in reality they will
        probably do a free text search for it, not a property search. The free text search has the
        side effect of cross referencing documents e.g. a search for DOC71077345 turns up a
        document which contains "This document supersedes DOC71077345".
    4. Store data in the data, not in the field name. Do not create a long list of properties
        with yes/no answers. Not only is this awkward for users, but the sequence “Relates to
        product A, Relates to product B” stores Yes and No as the data. A multi select box
        “Relates to products...” stores the information where you can search it.
    5. The most important meta-data items are Title, Categories and Description: put
        these first in your profiles; other fields are a bonus. From a pure SPS point of view:
        if readers won't use it in a property search, then don't ask authors to enter it.
        SPS-based applications can be exceptions to this, but generally see rule 1 and rule 2:
        don't burden authors with requests for information that readers won't use.
    6. Authors should ALWAYS set a meaningful title: if a document has a short
        abstract at the start, then authors can (and should) copy and paste it into the
        description. Remember that the results of a search or category browse show the
        document title and description. Readers won't open a document to find out if is useful.
    7. Ensure that authors understand the importance of title and description. Yes we
        know people don't want to fill in properties in word, but remember that Sharepoint
        gives extra weight to words it finds in the title and description – a document with these
        filled in will come nearer the top in searches where they it is relevant.
    8. Where authors have to make choices make them easy:
        a) Don't make lots of similar profiles e.g. If you deal with software specifications,
             don‟t create profiles called, User interface Spec, Database spec, Search spec, etc:
             instead use "Specification" with an "area" field with these choices,
        b) Don't make very fine grain categories (e.g. I use "Windows" and not Windows 95,
             Windows 98, Windows NT Server, Windows NT Workstation, Windows 2000
             Pro... etc. A document for Windows 2000 might well apply to XP, and to Pro,
             Server etc. Authors would be unlikely to file their documents correctly and the
             categories would go out of date. I use a “product version” field to qualify them).
    9. Remember folders are for the benefit of authors and administrators, not for
        readers. You only need to create new folders for different security, different approval,
        and different document profiles. You will want other folders for authors' convenience.
        If you find you've got lots of folders you're probably using the folder path to imply
        something about the content of the document which should be in the meta-data.
    10. Readers will find documents by browsing categories or by searching. If readers
        are exposed to your folder hierarchy you are doing it wrong!!
        Although big, complex, category hierarchies can work (see Yahoo), remember every
        time authors assign a category they are going be picking from a list of all of them.


Sharepoint Portal Server Cookbook                                                 Page 4
One discussion I was involved in was about a system which would store résumés. The client
wanted to track skills a person had and the market sectors they had worked in – 150 in all. For
each skill they wanted a pull down box which offered Basic, Intermediate or Advanced. Not
only is the list much too long, but Sharepoint‟s use of a pull down box rather than radio
buttons means two mouse clicks for every selection. Worse: the data searches badly, because
the information we want – the skill – is in the field name, not the data.
I proposed using three (long) multi-select pull down boxes, named "Skills at Advanced
Level", "Skills at Intermediate Level", "Skills at Basic Level " and a fourth for "Industry
sectors worked in". This would make it far easier for the person putting the résumés into the
system to enter correct data. The client objected that the default SPS search Web Part would
require the person entering the search to know what the skills were. But there were other
problems with the default search page:
        (a) The default Web Part for search only allows three properties.
        So the client could not search for “Has Skill A and has Skill B and has worked in
        Sector X and has worked in Sector Y
        (b) The default search page only allows ANDed conditions .
        So it does not allow search for (Skill A = Advanced or Skill A = Intermediate),
        nor can you search for (has Skill A or has Skill B) etc
        (c) When a list of matching résumés is returned users will want to see all the skills and
        industry experience of the consultant.

It is not hard to write your own custom search page, and your own display page which shows
the results with these four fields. Since the skills are now in the contents of a few fields it is
easy to do a keyword search on the document which adds extra weight to these fields. The
customer was having a problem with search, so it made sense to fix the search.

Here‟s another example of the wrong way to do it.

       Can I tell Sharepoint to create a category for every folder name under a
       certain path?
       I have a file server with docs from hundreds of projects. I want to have a
       category for every project name. Which is the quickest way?
It is very unlikely that you want a category for each of hundreds of projects. It is possible, but
thinking about the poor author scrolling down a huge list of categories to select theirs, one
quickly realizes that the categories you want are more likely to be the types of project. It
would be far better to leave the projects out of the Category structure and have a “project”
field. You can create categories programmatically, by the way, although there is a maximum
of about 500-600 items that can appear in the dictionary which sharepoint uses to build the list
in for authors to choose from. But if you‟re using a script to bulk upload documents then it
can pick up the project folders' names and put them into either a Project property, or the
Categories property.




Sharepoint Portal Server Cookbook                                                  Page 5
Bulk uploading to Sharepoint
Sharepoint's Object model (PKM CDO) allows you to manipulate documents from scripts
which run a client or a sharepoint server.
In particular you can
     Create files and folders
     Upload documents to the Web Storage System
     Save text as a file in the Web Storage System
     Set properties on new or existing documents
     Check documents out, check them in, and publish them.

Let's have a look at the process to upload a file, to a folder with versioning enabled. On page
10 you can see some code which allows these routines to adapt depending on the folder to
which it finds it is uploading.
Code Fragment 1 Subroutine to upload and check in files.
sub upload (sourcePath, DestinationURL)
    Dim oDoc      ' As PKMCDO.KnowledgeDocument
    Dim oStream ' As ADODB.Stream
    Dim oVer      ' As PKMCDO.KnowledgeVersion
    Set oDoc     = CreateObject("CDO.KnowledgeDocument")
    Set oStream = oDoc.OpenStream
    oStream.Type = 1                    ' 1 = adTypeBinary , 2 = adTypeText
    oStream.SetEOS
    wscript.echo "Uploading " & sourcePath & " to " & DestinationURL
    oStream.LoadFromFile sourcePath
    oStream.Flush
    oDoc.DataSource.SaveTo DestinationURL, , , &H4000000
    wscript.echo "Checking in and publishing"
    Set oVer = createobject("CDO.KnowledgeVersion")
    oVer.Checkin oDoc
    oVer.Publish oDoc
end sub

It is a simple process – for the moment we are not going to set any properties.
We create a KnowledgeDocument object: we're going to use two objects that it refers to: the
Stream and DataSource Objects.
The knowledge document's Stream object allows us to send binary files or text to the server.
In this case we use a four step process to upload the file: specify the Stream type, ensure we
are at the end of the stream, call the LoadFromFile method and then flush the stream to make
sure the data is committed.
In Code Fragment 31 we use the Text version of Stream – we just set the type to text and use
the "WriteText" method to set the text. Incidentally the Stream object also has ReadText and
WriteToFile methods, which you can use to get data out.
Having finished with the Stream object, we use the knowledge document's dataSource object
to save the document. It is only at this point that PKMCDO learns where the file is to go. The
transfer process can take a few seconds with large files – so my script displays a message to
tell the user what is happening. Windows provides two version of the scripting host,
command line (cscript) and Windowed – (Wscript). If you display lots of messages, make
sure you run the script using cscript, not the default Wscript – which will display a dialog
box for each message, and wait for you to press OK. You can use the command line cscript
Upload.vbs to run a single script using cscript or cscript /h cscript to make cscript the
default.




Sharepoint Portal Server Cookbook                                                Page 6
If you are uploading to a folder which doesn't use versioning, then you can omit the
references to oVer, but if you want to check the document in and publish it, you create a
KnowledgeVersion object, and call its checkin and Publish methods with the document you
want to publish.

You may also want to set properties during the upload process. A knowledge document
object has built-in properties for Author, Categories, Keywords, BestBetCategories,
BestBetKeywords, Description and Title.
You can set the fields like this
    oDoc.Author = "James O'Neill"
    oDoc.Categories = array(":products:sharepoint", ":teams:collaboration")


Any other fields that you want to set need to be set through the document's fields collection
e.g.
    odoc.fields("urn:schemas-microsoft-com:publishing:ShortcutTarget")=TheUrl
    odoc.fields.update


There are two things to note here. Firstly failing to call the update method on the fields
collection will lose any changes you have made. Secondly fields that you define will usually
be named like this:
       urn:schemas-microsoft-com:office:office#Your Field Name
if you need to know the name for the field you are trying to set use the PLEX tool provided in
the support\tools folder of the Sharepoint CD. This will show you all the fields for an
existing item.
Often the only information we have about the documents we are uploading comes from the
folder where they stored. It would be useful to create a property "Old path" and add these two
lines to before the DataSource.SaveTo line in Code Fragment 1.
    odoc.fields("urn:schemas-microsoft-com:office:office#Old Path")= sourcePath
    odoc.fields.update


Having got a subroutine to upload our files, we just need some logic which calls it e.g.
Code Fragment 2 Sample code to call the Upload subroutine
   Dim oFileSysObj, oFolder, oFile,oFilesCol, shref, sDocumentPath
   Set oFileSysObj = CreateObject("Scripting.FileSystemObject")
   Set oFolder = oFileSysObj.GetFolder("E:\DOCS")
   Set oFilesCol = oFolder.Files
   For Each oFile in oFilesCol
      shref = "http://binky/workspace/documents/gems/" & oFile.name
      sDocumentPath = ofile.parentFolder & oFile.name
      if     ucase(right(sdocumentPath,4)) = ".PPT" _
         or ucase(right(sdocumentPath,4)) = ".DOC" then
               upload sdocumentPath , Shref
      else
               wscript.echo "Ignoring " & sdocumentPath
      end if
   Next


This uses the scripting "File System Object", this allows us to request a folder object – in
this case hard coded to "E:\docs". The folder object has a files collection (and a subfolders
collection), so the logic just works through each file in the files collection and uploads it. You
could make recursive calls to a function to process each sub folder of the starting folder.
In this case we are just working through one folder and the code ignores anything which is not
a Word or PowerPoint file.
Sharepoint does not allow file names to contain the ~ character, so the script I use doesn't use
the raw name when it builds the sHref string, but cleans the name first.


Sharepoint Portal Server Cookbook                                                 Page 7
It is sometimes useful to be able to create folders during the upload process and this is even
simpler than creating files: it takes three lines

dim oKF
set oKF= createObject("Cdo.knowledgeFolder")
oKF.datasource.saveTo("http://MyServer/MyWorkspace/documents/MyFolder/NewFolder")


This could be called recursively like this
Code Fragment 3 Recursively creating Sharepoint folders to match file system folders
sub traverseFolderTree(ofldr,SavePath)
   set oKF= createObject("Cdo.knowledgeFolder")
   oKF.datasource.saveTo(SavePath & ofldr.name )
   For Each sf in ofldr.subFolders
        traverseFolderTree sf, savePath & ofldr.name                & "/"
   Next
end sub

Set oFileSysObj = CreateObject("Scripting.FileSystemObject")
Set oFldr = oFileSysObj.GetFolder("C:\myfiles")
traverseFolderTree oFldr, "http://myServer/myworkspace/documents/myfolder/"


You could insert the following code before the For Each line in Code Fragment 3 to upload
documents from the folder, before processing its subfolders.
     For Each oFile in oFldr.files
        shref = savePath & oFldr.name & "/" & oFile.name
        sDocumentPath = ofile.parentFolder & oFile.name
        upload sdocumentPath , Shref
     Next


Folders created in this way will inherit the settings of its parent – which is usually what we
want in bulk upload scenarios, but if you want to enable or disable versioning; you need to
set the content class before adding documents to the folder, as follows
oKF.contentClass = "urn:content-classes:smartfolder"
or
oKF.contentClass = " urn:content-classes:knowledgefolder"




Sharepoint Portal Server Cookbook                                                      Page 8
Setting Categories (and other properties) in bulk
There are two problems to solve when using a script to set a category on an existing
document. The first is that if the document is in a folder where versioning is enabled then
properties can only be set if the document is checked out – so the script must check the
document out and then check it back in again. The second is that "categories" is just an array
property, and we must check to see (a) if it exists at all, and (b) if it already contains the
desired category.
The code below works with versioned documents,
Code Fragment 4 A VBScript subroutine to set a category for a document
sub SetCategory (theDocUrl, theCategory)
  Dim oDoc     ' As PKMCDO.KnowledgeDocument
  Dim over     ' As PkmCDO.KnowledgeVersion
  Dim rs       ' As RecordSet
..Set oVer = createobject("CDO.KnowledgeVersion")
  Set oDoc = CreateObject("CDO.KnowledgeDocument")
  set rs= over.checkout (theDocURL)
  sWorkingCopyUrl = rs.fields("urn:schemas-microsoft-com:publishing:workingCopy")
  oDoc.datasource.open SworkingCopyUrl ,, 3          '3 = adModeReadWrite

  on error resume next
  cat = odoc.categories
  if err then
        err.clear
        odoc.categories = array(theCategory)
  else
        insert = true
        U= ubound(cat)
        L = Lbound(cat)
        for i = L to U
            if cat(i) = theCategory then insert=false
        next
        if insert = true then
            redim preserve Cat(U+1)
            cat (U + 1) = TheCategory
            odoc.categories = cat
        end if
   end if

  odoc.datasource.save

  over.checkin(TheDocURL)
  over.publish(theDocURL)
  set over = nothing
  set oDoc = Nothing
end sub


Let's work through the code.
For non versioned documents we could begin with
  Dim oDoc     ' As PKMCDO.KnowledgeDocument
  Set oDoc = CreateObject("CDO.KnowledgeDocument")
  Odoc.datasource.open TheDocUrl ,, 3
This would give us a knowledge document object, which points to an open copy of the
document we want. However, for versioned documents we need to declare a knowledge
version object and tell it to checkout the document we want. This will return a recordset
object containing 1 record. This record contains the properties of the checked out document
and we need to tell our Knowledge document to open the working copy of the document we
checked out - which will be in Sharepoint's "Shadow "folder.



Sharepoint Portal Server Cookbook                                               Page 9
The next step is to pickup the Categories property. This will generate an error if we have no
categories, so the first thing to do is to catch that error (that's the on error resume next line):
if we had an error then we set the categories property: it needs to be an array with 1 element
(that's the odoc.categories = array(theCategory) line )
If the categories property already existed then we need to look at each item in the array and
see if the category has already been assigned to the document (the for … next loop does
that). If the category is not set already we extend the array (with redim), and set the extra item
to the new category. Then we can save the document (with datasource.save). For non
versioned documents we can stop here, for versioned documents we probably want to check
the document back in and publish the new version, which we do with the Knowledge version
object.
Code Fragment 5 Adapting the SetCategory subroutine to work with versioned or Non-versioned folders
Normally these scripts are written to a custom job and we can adapt them for versioned or non
versioned as needs dictate. But the following code would allow a single code base to be used
sub SetCategory (theDocUrl, theCategory)
  Dim over     ' As PkmCDO.KnowledgeVersion
  Dim rs       ' As RecordSet
  Dim oDoc     ' As PKMCDO.KnowledgeDocument
  Dim ofld     ' As PkmCdo.KnowledgeFolder
  Set oDoc = CreateObject("CDO.KnowledgeDocument")
  set ofld = createobject("Cdo.knowledgeFolder")
  pos = 1
  while instr(pos, theDocUrl ,"/") > 0
     pos = instr(pos, theDocUrl , "/") +1
  wend
  ofld.datasource.open (left(theDocUrl , pos -1 ))
  bIsSmart = (ofld.contentClass= "urn:content-classes:smartfolder")
  if bIsSmart then
    Set oVer = createobject("CDO.KnowledgeVersion")
    Set rs= over.checkout (theDocURL)
    sWorkingCopyUrl = rs.fields("urn:schemas-microsoft-com:publishing:workingCopy")
    Odoc.datasource.open SworkingCopyUrl ,, 3         '3 = adModeReadWrite
  else
    Odoc.datasource.open TheDocURL ,, 3        '3 = adModeReadWrite
  end if

same processing ending with oDoc.DataSource.Save

  if IsSmart then
    over.checkin(TheDocURL)
    over.publish(theDocURL
  End if
end sub


Having built a subroutine which sets a category (potentially any other property) for an item all
we then need some logic to drive it. Here is an example of looking for documents which
contain a value in a given field. For matching documents it calls the subroutine and sets the
category.




Sharepoint Portal Server Cookbook                                                  Page 10
Code Fragment 6 Sample code to call the set category subroutine
FieldToLookIn = "urn:schemas-microsoft-com:office:office#Title"
TextToLookFor = "Windows 2000"
CategoryName = ":Windows 2000"
Servername = "binky"
WrkSp ="/workspace"

'Create ADO objects adoConnection, adoRecordset
set adoRecordSet = CreateObject ("adodb.recordSet")
set adoConnection = createObject("adodb.connection")
adoConnection.Open "Provider=MSDAIPP.DSO;Data Source=http://" & servername & WrkSp
'build the query.
strCommand = ""
strCommand = strCommand & "Select ""DAV:href"" "
strCommand = strCommand & " FROM Scope('deep traversal of """& WrkSp &"""') "
strCommand = strCommand & " WHERE contains(""" & FieldToLookIn & """, _
                                                '"""& TextToLookFor&"""')"

adoRecordset.Open strCommand, adoConnection
count = 0
While Not adoRecordset.EOF
    count = count + 1
    SetCategory   adoRecordSet("DAV:href").value , CategoryName
    adoRecordset.MoveNext
wend

wscript.echo Count & " items set."


This code makes a connection to the workspace using the MSDAIPP provider and runs a
simple SQL query. In this case
Select "DAV:href"
From   Scope ('Deep Traversal of '/workspace')
Where Contains ("urn:schemas-microsoft-com:office:office#Title", "Windows 2000")


This will find any document in the workspace named "workspace" where the title contains
"Windows 2000". For each matching document, the script calls the SetCategory routine and
passes the URL of the document and the category name – in this case ":windows 2000" note
the leading colon, Sharepoint uses : in category paths, in the way that we normally use / or \
in paths. If you use a name that doesn't begin with : then it will save, but it won't work as a
category.

These two sections have provided the building blocks for you to build your own scripts, here
are three of mine (if you have trouble getting the embedded files out of Word, right click
them and go to edit packed object, then save the file from the object packager.)




Sharepoint Portal Server Cookbook                                               Page 11
Getting Sharepoint to help with formatting: using its style
sheets and built-in icons.
If you load up a dashboard page in Internet Explorer, right click in it and choose view source,
you will find three lines like these near the start of the HTML code.

<BASE id="bseHref" href="http://srv/workspace/Portal/resources/" />
<LINK rel="stylesheet" href="global.css" />
<LINK rel="stylesheet" href="arctic.css" />


The third one depends on the style sheet selected for the dashboard.

The first one tells the HTML page to go to the portal/resources folder for anything where
the path isn‟t specified explicitly. You can cheat by putting files that your Web Parts need into
this folder (although they really should go in a sub-folder of the dashboard‟s folder, using the
name WebPartName_Files which is covered in the section Paths in Web Parts, using
substitution, and ways to discover the path on page 34.)
One useful folder within the resources folder is DocTypeIcons. This contains 16x16 Pixel icons
for each type of document. The normal format is <extension>16.gif, e.g. doc16.gif,
html16.gif and so on. There are some useful icons for sharepoint folders, so for example
<img src="DocTypeIcons/urn-schemas-microsoft-com-Dashboard16 ">
Will display a folder with a globe on it; you can use
       urn-schemas-microsoft-com-categoryfolder16
       urn-schemas-microsoft-com-dashboard16.gif
       urn-schemas-microsoft-com-folder16
       urn-schemas-microsoft-com-knowledgefolder16
       urn-schemas-microsoft-com-smartfolder16
       urn-schemas-microsoft-com-ManagementFolder16

There are some styles that are recommended in the "Building Web Parts.doc". which is part of
the digital dashboard resource kit.
As a guideline, don‟t hard code any colours, fonts or anything else style-related into your Web
Parts, they will stick out like a sore thumb if the style sheet is changed in the future.
If you‟re writing HTML to be displayed in an Iframe in the dashboard, you can include a link
to the style sheet.
Code Fragment 7 VB Script to get the style sheet for a free standing ASP pages
Dashboards are folders within the Web Storage System. They have properties for each
dashboard configuration option. This code picks up the style sheet name for the home
dashboard in the portal– you could use any dashboard. It relies on “theSource” being the
workspace name, and leaves a variable called “the styleSheetHTML.”
set Ofldr= server.createObject("CDO.KnowledgeFolder")
Ofldr.datasource.open(theSource & "/portal")
TheStyleSheetHTML = "<html><head><BASE id=""bseHref"" href=""" & _
        TheSource & "/Portal/resources/"" />" & vbcrlf & _
       "<LINK rel=""stylesheet"" href=""" & _
       Ofldr.fields("urn:schemas-microsoft-com:webpart:StylesheetLink") & _
       """/>" &vbcrlf & "<LINK rel=""stylesheet"" href=""global.css"" />"
set Ofldr = Nothing




Sharepoint Portal Server Cookbook                                                Page 12
Code Fragment 8 HTML to draw a dashboard frame
Sometimes you want to build a frame round your Web Part or a title bar for it from inside the
Web Part code, (instead of letting the dashboard do it). The key to drawing the frame is the
DashPartFrame class in the style sheet, which can be used like this:
<table border="0" cellspacing="0" cellpadding="0" width="100%">
    <tr><td class="DashPartFrame">
        Your Web Part HTML goes here
    </td></tr>
</table>
If you want a Title bar you can put it in the section Your Web Part HTML goes here

Code Fragment 9 HTML to draw a dashboard title
In the following Web Part, I wanted a blank header, so I‟ve used a non breaking space
( &nbsp; ) - without that it didn‟t seem to render properly. This HTML builds a Web Part title
bar. The key class here is DashPartTitle
<table cellspacing="0" Width="100%" cellpadding="0" border="0">
       <tr><td class="DashPartTitle">
              &nbsp;
       </td></tr>
</table>
You can use multiple cells in the table provided that you set the class of each one to
DashPartTitle


Code Fragment 10 HTML using the dashpart styles, and built-in icons to build the “view as web folder
part”
I used DashpartFrame, and DashpartTitle with one of the built-in icons in a Web Part where I
wanted a short cut to open a web folder view, the HTML is built by a script, but it ends up
like this.
<table border="0" cellspacing="0" cellpadding="0" width="100%">
   <tr><td class="DashPartFrame">
      <table cellspacing="0" Width="100%" cellpadding="0" border="0">
         <tr><td class="DashPartTitle">&nbsp;</td></tr>
      </table>
      <table width="100%">
         <tr><td valign = "top" align="right">
            <img src="DocTypeIcons/urn-schemas-microsoft-com-dashboard16.gif">
            <a href="/gotowf.asp?url=http://mysrv/wksp">
              View as Web folder
            </a>
         </td></tr>
         <tr><td>&nbsp;</td></tr>
      </table>
   </td></tr>
</table>
A code version of the Web Part appears as Code Fragment 19. This functionality was added to
the default folder Web Parts in Service Pack One, but there still situations where it is useful to
put it in your Web Parts.




Figure 1 The "View as Web Folder" Web Part                      And the files to implement it




Sharepoint Portal Server Cookbook                                                     Page 13
Providing "Minimize Web Part" functionality using DHTML
with the styles and icons provided by SPS
One frequent complaint about the SPS dashboard stems from these two check boxes



Figure 2 The remove and minimize checkboxes
SPS inherited these options from the SQL Server based dashboards. But SQL Server is a
relational database, and it can store user settings and join them to the defaults. The Web
Storage System is not relational, so the SPS dashboard factory doesn't implement
personalization. However the icons for minimize and close still appear if (and only if) the user
is able to modify the Web Part – they change the Web Part for ALL users, and that means if a
coordinator hides a Web Part, then users with read only access can't make the part visible
again.

So here is a Web Part which implements minimize, and stores the user's preference on the
client. The setting doesn't follow the user between machines unless they have a roaming
profile, but I can live with that.

The part uses dynamic HTML, and the example below is not the finished Web Part but shows
the framework. In particular if this were a code Web Part it should discover its title and other
properties (see the section "Using the fields of the Web Part: parameterizing or hiding your
Web Parts" on page 32 for the methods used to achieve this). Also if two or more Web Parts
use the same function name or use the same IDs for their components then errors will result.
(See the section "Paths in Web Parts, using substitution, and ways to discover the path" on
page 34 for the solution to this problem)

Normally I do only two things with DHMTL. One is to create a section which will receive
some HTML. Some code on the page builds the HTML and then uses the innerHTML property
to set it. The HTML looks like this:
         <DIV ID=PlaceHolder1> &nbsp; </DIV>

and the line of script is simply
Placeholder1.innerHTML = Mycontent

The other thing I do is to hide a section or reveal it and the script is either
         Placeholder1.style.display = "none"                 ' to hide it

Or
         PlaceHolder1.style.display = "block" ' to show it

Trying to discover how to save a user preference was a problem because there isn't a way to
do it from script, and the Windows Scripting Host objects which support doing this from a
.VBS file at the command line do not work on HTML pages. Eventually I came upon a
HTML behaviour which would allow me to do what I wanted1.


1
  One of the many great things about working for Microsoft is the community feeling around some of the internal
distribution list. Sharepoint Portal Server Discussion is a case in point. I was really stuck with this problem and
posted a message to the DL, a couple of hours later I had a message from Todd Foust – who works for Microsoft
in Charlotte. He gave me the reference I needed.


Sharepoint Portal Server Cookbook                                                              Page 14
Adding behavior:url('#default#userData')" to an object's style attribute allows code on the
page to use "SetAttribute" and "GetAttribute" to set arbitrary things and Save and load to
read or write them to a file in the user's profile directory. The filename is based on the name
used in the SAVE command and it is in XML format with sections for the attributes.
This will only work with IE 5 or better but in my case this is not a problem.
Code Fragment 11 The client side minimize Web Part code
The explanation follows the code
<SCRIPT>
function DoMin(){
   document.all.Content.style.display="none";
   document.all.PlusPart.style.display="block";
   document.all.MinusPart.style.display="none" ;
   document.all.MinusPart.setAttribute("PartDisplay","none");
   document.all.MinusPart.save("WebPartPrefs");
}
function DoMax(){
   document.all.Content.style.display="block"
   document.all.PlusPart.style.display="none"
   document.all.MinusPart.style.display="block"
   document.all.MinusPart.setAttribute("PartDisplay","block");
   document.all.MinusPart.save("WebPartPrefs");
}
function fnLoad(){
  document.all.MinusPart.load("WebPartPrefs");
  var blockOrNone = document.all.MinusPart.getAttribute("PartDisplay");
  if (blockOrNone == "none")
       {
          document.all.Content.style.display="none"
          document.all.PlusPart.style.display="block"
          document.all.MinusPart.style.display="none"
       }
}
</SCRIPT>

<table cellspacing="0" Width="100%" cellpadding="0" border="0"><tr>
  <td class="dashpartFrame">
    <table cellspacing="0" Width="100%" cellpadding="0" border="0"><tr>
      <td class="DashPartTitle" Width="100%">A Part you can hide</td>
      <td class="DashPartTitle" onclick="DoMin();" ID=MinusPart
        STYLE="display:block;behavior:url('#default#userData')">
          <img src="minus.gif">
      </td>
      <td class="DashPartTitle" onclick="DoMax();" ID=PlusPart
        STYLE="display:none;"><img src="plus.gif"></td>
    </tr></table>
    <div ID=Content><h1>Hello world </h1></div>
  </td></tr>
</table>

<script>
  var Loading = fnLoad();
</script>


Let's start with the HTML and move on to the code. The first line (after the first </script>
tag) puts the frame round the part. The next line ( the second <table>) begins to insert the title
bar. This table has one row of three cells. The first cell contains the title "A part you can
hide". It is set 100% width, which will get the title to use all space available after the plus and
minus symbols have been inserted. The other two cells in this table hold these symbols. There
are three things to notice here, which are picked out in bold.




Sharepoint Portal Server Cookbook                                                 Page 15
       The minus symbol has the Behaviour part added to its style attribute, and is visible
        (display is set to block) – the part is initially visible, so we want the minimize symbol
        available
     The plus symbol is hidden (display is set to none) – we only want to see the maximize
        symbol if the part is currently minimized
     On clicking the minus symbol we call DoMin which will hide the body of the part, and
        change which icon is visible (and save this as our preference) clicking the plus symbol
        calls DoMax which will make the body of the part visible, swap the icons back, and save
        that as the preference
The next section of the web part is the body (the <DIV ID=CONTENT>) which is the part we want
to hide or show.
The final thing to do is to call the function "fnload" which looks at the saved settings and
hides the part if necessary.

The DoMin and DoMax functions simply set the icons and the body either visible or invisible as
required, and use the SetAttribute and Save methods that the behaviour provides.
The fnLoad function re-loads the information, and looks at the attribute if it is set to "none"
then the body and minus icon are hidden and the plus sign is revealed, exactly as they are for
DoMin.


Here are two copies of the Web Part: one is plain HTML which can pasted into an HTML
Web Part and the other is a VB Script Web Part




Sharepoint Portal Server Cookbook                                               Page 16
More on the built-in styles, and querying for all the
instances of a class – part 1
Three of the other styles that I have found useful are DashNavArea, DashNavDashArea, and
DashNavLink which are used to create the navigation header which lists the sub-dashboards
accessible from the current dashboard. In the settings page for a dashboard you can turn off
the display of sub-dashboards, and use a part like the one below to replace it.
Code Fragment 12 HTML to display a Navigation header bar
In its simplest form the sub dashboard navigation header looks something like this.
<table cellspacing="0" cellpadding="0" width="100%" class="DashNavArea">
  <tr valign="bottom">"
    <td class="DashNavDashArea">
      <A class="DashNavLink" href="http://MyServer/workspace/portal">
         <nobr>Home</nobr>
      </A>
    </td>
  </tr>

</table>


From my own dashboard I wanted to be able to access the dashboards linked to the home page
like this


Figure 3 The Dashboard navigation header Web Part
You may find other places where you want to replace the default navigation bar with your
own web part. On the dashboard settings page, under advanced settings, there is a checkbox
labelled "Show subdashboards in the navigation bar" by un-checking this box you can
remove the navigation bar an insert your own web part.
You can go a stage further and remove the title bar, since this hides the links to get to the
dashboards content, layout and settings, it not exposed through the portal UI. It is still
possible to get to these options, by adding cmd=content, cmd=layout or cmd=open to the URL
of the dashboard. The process for hiding the file is to set a property on the dashboard folder –
urn:schemas-microsoft-com:webpart:DisplayTitle . This can be set via the Plex tool on the
SPS CD, but it is easier using Microsoft Office Developer




Figure 4 Turning the title bar on or off in Microsoft Office Developer




Sharepoint Portal Server Cookbook                                               Page 17
Code Fragment 13 VBScript Web Part to build a dashboard navigation header
This is the Web Part I wrote. You can see how it builds up HTML like the piece above. The
other parts of it are worth looking at, line by line.

function getcontent(nod)
TheSource = GetWorkspaceUrl()
set adoRecordSet = CreateObject ("adodb.recordSet")
set adoConnection = createObject("adodb.connection")
AdoConnection.Open "Provider=MSDAIPP.DSO;Data Source=" & TheSource

SQL = "SELECT     ""DAV:href"", ""DAV:displayname""    "& _
      "FROM    SCOPE ('SHALLOW TRAVERSAL OF """& TheSource &"/portal""') " & _
      "WHERE   (""DAV:contentclass"" = 'urn:schemas-microsoft-com:dashboard') " & _
      "ORDER BY   ""urn:schemas-microsoft-com:webpart:DashboardOrder""

adoRecordset.Open sql, adoConnection
c = "<table cellspacing=""0"" cellpadding=""0"" width=""100%""
class=""DashNavArea"">" &_
    "<tr valign=""bottom"">"

C = C & "<td class=""DashNavDashArea""><A class=""DashNavLink"" href=""" & _
         TheSource & """ ><nobr>Home</nobr></A></td>"

While Not adoRecordset.EOF
    C = C & "<td class=""DashNavDashArea""><A class=""DashNavLink"" href=""" & _
             adoRecordSet("DAV:href") & """><nobr>"&
adoRecordSet("DAV:displayname") & _
             "</nobr></A></td>"
    adoRecordset.MoveNext
wend
getContent = C & "<td width=""100%"">&nbsp;</td></tr></table>"
end function
Let's break this function down line by line.
The function used in a Web Part is always named "GetContent" and takes one argument,
which allows the code to find out details about the part. The typical code goes like this:
Function GetContent(nod)

       Build up results in a variable, C

getContent = C
End function.


Here we're using an ADO to query for dashboards. We first saw ADO in Code Fragment 6.
and a lot of early web parts I wrote used the same logic as scripts. Subsequently I've learnt
that ADO is not really a good choice for web parts because the MSDAIPP provider is not
properly server safe. If you're familiar with ADO then you might want to use it to do some
quick prototyping – and this is the only reason I have left the code in. In fact MSDAIPP uses
the HTTP-DAV extensions to get XML data back from the server, and present it as familiar
ADO objects. You'll get better performance and safety if you use the XMLHTTP object to
send the same SQL statement and get the data natively and this is covered later in Moving
away from ADO starting on page 21

So let's have a look at the code in Code Fragment 13:
GetWorkSpaceUrl is a function which is declared in the ASP file TahoeUtils.asp, which has
some useful routines in it.
The next three lines are there to setup the ADO connection using the Internet access provider
(formerly code named „Rosebud‟) pointing at the workspace. Then we build a SQL statement
and send it to the server – in the case by using it with the OPEN method of a Recordset object
- we‟ll look at the SQL in a moment.


Sharepoint Portal Server Cookbook                                              Page 18
Then we have to process each item returned by the query, which will be represented by a row
in the recordset, And then it is the case of processing the item, moving to the next one, doing
the same thing and so on in a loop, until we have worked though all of them.
Many web parts follow the same pattern the only things we need to determine is the SQL
statement we need line and what are we going to inside the processing loop.

So let‟s look at the SQL
SELECT "DAV:href", "DAV:displayname"
  FROM SCOPE('SHALLOW TRAVERSAL OF “http://myserver/myworkspace/portal"')"
WHERE ("DAV:contentclass" = 'urn:schemas-microsoft-com:dashboard')
ORDER BY "urn:schemas-microsoft-com:webpart:DashboardOrder"


Normally a SQL statement goes like this:
SELECT some fields FROM some source WHERE Some Condition ORDER BY some field(s)


And the free text search flavour of SQL is just the same, the only difference is the names are a
little unusual - for example using field names of “DAV:href” , “DAV:displayname” ,
“DAV:ContentClass”. These are the qualified schema names of the fields in the Web Storage
System. You can see what these are using the “Plex” tool found in the tools folder on the SPS
CD.
In this query href is the URL for the folder, and displayname is the name displayed.
Contentclass tells the store what the item is. In this case we are using content class to ask for
items which are Dashboards. These three properties are defined within the "DAV" XML
namespace, we use a fourth one from the "urn:schemas-microsoft-com:webpart" name space
and that is "DashboardOrder" which gives the dashboard its place in the header (you can set
this in the advanced part of Settings for the dashboard)
The remaining unusual part is the source – which in a normal database context would be a
table, or a set of joined tables. Here we specify a folder where the search should start, and by
specifying a shallow traversal (as opposed to a deep one) we have done two things. The
obvious thing is to limit the scope of the search, but the less obvious one is to direct the query
against the folder itself and not against the index. This is important, because not all items are
indexed. As it happens a dashboard folder is not indexed, so if we used
       From Scope (' DEEP TRAVERSAL OF "http://myserver/workspace" ')
it wouldn‟t find any dashboards!

Having retrieved the recordset, all that is left to do is to work out the code to put in the while
loop. It is just one (long) line.
    C = C & "<td class=""DashNavDashArea""><A class=""DashNavLink"" href=""" & _
             adoRecordSet("DAV:href") & """> <nobr>"& _
             adoRecordSet("DAV:displayname") & "</nobr></A></td>"


We pickup the URL and the name from the recordset , and drop them into a single table cell,
like this
<td class="DashNavDashArea">
    <A class="DashNavLink" href="http://mysrv/myworkspace/portal/search">
        <nobr>Search</nobr>
    </A>
</td>




Sharepoint Portal Server Cookbook                                                  Page 19
That‟s all there is to it. Well … you keep finding ways to enhance Web Parts. As you‟ll see
later I decided I wanted the Web Part to build the menu bar for any folder, not just the portal
home page, so I decided to add the logic to get the name of folder from a parameter, rather
than hard coding the portal folder. You could also add links to your own folders, in the same
way that my part includes a link to the home dashboard.
In the next section I've re-worked this part as a demonstration of how produce XMLHTTP
based parts instead of ADO based ones.




Sharepoint Portal Server Cookbook                                               Page 20
Moving away from ADO
The Web Storage System used in Exchange 2000 and Sharepoint Portal Server is a Web
Service which allows applications to access rich properties of a stored item using
HTTP-DAV. Among the verbs which DAV adds to HTTP is SEARCH. Search requests are
made using a form of SQL, which is sent to the server in a simple XML wrapper; they return
data as an XML document.
To save developers from working with XML and low level HTTP commands, the MSDAIPP
provider for ADO gives a familiar database style interface. Beneath the surface the provider is
making the same DAV requests and processing the returned XML into a recordset. Working
with the XML does not require code to be much more complex than using ADO – in effect
ADO is just adding overhead, so cutting it out should make for more efficient web parts.
I decided that changing the parts was easy, and they would be more efficient. Some people are
concerned about using this provider on the server because it uses the WinInet API and I have
had reports of problems ADO based web parts hanging servers under heavy load. Therefore I
would recommend ADO as the easiest way to work in scripts, and it can be useful to produce
a quick web part but it should not be used in production (although you'll probably get away
with it if your server is lightly loaded.)

There are three parts to the ADO code which need to be changed; they are
1. Retrieving the data as a recordset – becomes retrieving the data as an XML document
2. Working through the records of the recordset – which turns into working through the
   nodes of the XML document
3. Accessing the fields of the records – which becomes accessing sub nodes of the top level
   XML nodes

1. This code to create the "recordset" turns from this:
        set adoRecordSet = CreateObject ("adodb.recordSet")
        set adoConnection = createObject("adodb.connection")
        AdoConnection.Open "Provider=MSDAIPP.DSO;Data Source=" & strWorkspaceURL
        adoRecordset.Open strSQL, adoConnection
Into this
        Set xhr = CreateObject("MSXML2.serverXmlHttp")
        xhr.Open "SEARCH", strWorkspaceURL, False
        xhr.setRequestHeader "content-type", "text/xml"
        theXML = "<searchrequest xmlns=""DAV:"">" & vbCrLf & _
                      "<sql>" & vbCrLf & _
                             StrSQL & vbCrLf & _
                      "</sql>" & vbCrLf & _
                 "</searchrequest>"
        xhr.send (theXML)
        Set oXMLDOM = xhr.responseXML.documentElement
As you can see in the ADO case the process is to create a connection object which refers to
the workspace, and use it to open a recordset which holds the result of query.
With the DAV version, we create a "ServerXMLhttp" object which will send the DAV
command to the server. This command is an XML search request which contains the query:
the result comes back in an XMLdocument object instead of a recordset.
In most development situations this code is cut and pasted from an existing part: the variables
StrWorkspaceURL and StrSQL are set to the same values for both the ADO and DAV methods.




Sharepoint Portal Server Cookbook                                              Page 21
2. This code which works through the recordset:
       While Not adoRecordset.EOF
              Do stuff with the data(in adoRecordset)
              adoRecordset.MoveNext
       wend
Becomes:
       For Each n In oXMLDOM.childNodes
              Set oXMLNode = n.selectSingleNode(propString)
              Do stuff with thr data (in oXMLNode)
       Next
(I'll look at Propstring in a moment; and there is more than one way to loop through the
results)

3. Instead of working through Records in a recordset we are now working through the nodes
in an XML document, and the final step is to replace references to fields which start as this:
       adoRecordSet.fields("DAV:href").value
into something like this
       oXMLNode.selectSingleNode("DAV:href").nodeTypedValue
- But not in this exact form… it is time to explain about propstring

The results from this query come back as XML similar to this:
<a:multistatus     xmlns:a=   "DAV:"
                   xmlns:d=   "urn:schemas.microsoft.com:fulltextqueryinfo:"
                   xmlns:e=   "urn:schemas-microsoft-com:office:office"
                   xmlns:f=   "urn:schemas-microsoft-com:publishing:" >
  <a:response>
    <a:href>
      http://EPL-02-SRV/eplatform/Portal/Project%20DashBoard
    </a:href>
    <a:propstat>
      <a:status>
        HTTP/1.1 200 OK
      </a:status>
      <a:prop>
        <a:href>
           http://EPL-02-SRV/eplatform/Portal/Project%20DashBoard
        </a:href>
        <a:displayname>
           Project DashBoard
        </a:displayname>
      </a:prop>
    </a:propstat>
  </a:response>
  <a:response>
    <a:href>
      http://EPL-02-SRV/eplatform/Portal/Calendar
    </a:href>
    <a:propstat>
      <a:status>HTTP/1.1 200 OK</a:status>
      <a:prop>
        <a:href>
          http://EPL-02-SRV/eplatform/Portal/Calendar
        </a:href>
        <a:displayname>
          Calendar
        </a:displayname>
      </a:prop>
    </a:propstat>
  </a:response>
</a:multistatus>
The document element is the part between the <Multistatus> and </Multistatus> tags: the
individual responses are its "child nodes". Each response (and there are only two in the
example above) is between a pain of <response> and </response> tags. Each response


Sharepoint Portal Server Cookbook                                              Page 22
contains the href (URL) of the matching resource, and one or more "PropStat"
(Property/Status pair) entries. Propstat groups fields together using the HTTP status code
generated when they were requested - a field may be return "Not found" or "OK" (if it is
found), and these codes appear between the <Status> and </Status> tags, of the Propstat
section. The properties themselves are found between the <prop> and </prop> tags.
Sometimes it helps to think of this as a tree structure.

Multistatus
|______Response 1
|      |______Href of first match
|      |______PropStat
|      |      |______Status – 200 OK
|      |      |______PROP
|      |      |      |_____Fields found for first match
|      |______PropStrat
|             |______Status 404 Not found
|             |______PROP
|                    |______Fields not found for first match
|______Response 2
|      |______etc
Figure 5 the XML returned by a query depicted as a tree
The loop
 For Each n In oXMLDOM.childNodes,
sets n to subsets of the document - the two response sections in this case.
By calling SelectSingleNode, we can ask for the PROPSTAT node, and its PROP child.
SelectSingleNode is handed a path – such as "DAV:propstat/DAV:prop" : however when the
data comes back from the Web Storage System all the namespaces are replaced with short
hand versions – in the example above the DAV namespace is reduced to "a:". DAV always
seems to come back as "a:" but it is dangerous practice to rely on the fact. The job of
propString is to hold "x:Propstat/x:Prop" with X set to whatever shorthand name is used for
DAV.To do this there is one extra piece of code to fit in after the XML data is returned.

For Each attr In oXMLDOM.Attributes
       If attr.nodeTypedValue = "DAV:" Then
              propString = Replace("DAV:propstat/DAV:prop", "DAV", attr.baseName)
              Exit For
       End If
Next
This looks through all the xmlns entries in the opening <MultiStatus> tag, until it finds the
one whose value is "DAV" and adapts the string accordingly.

Namespaces cause a further problem, because the properties look like this:
       <a:prop>
         <a:href>
           http://EPL-02-SRV/eplatform/Portal/Calendar
         </a:href>
         <a:displayname>
           Calendar
         </a:displayname>
       </a:prop>
The fieldnames (href and displayname) are also qualified with a namespace, our query began:
        Select "DAV:hef" , "DAV:displayname"
but the fields could have lots of different namespaces – for example most sharepoint fields use
        "urn:schemas-microsoft-com:office:office".




Sharepoint Portal Server Cookbook                                               Page 23
To solve this we can store all the prefixes, and insert them every time we use
selectSingleNode , and this is what I have done in the MK2 web part embedded below, but
there is an alternative: we could take the name space out altogether; if the query begins
       Select "DAV:href" as href, "DAV:displayname" as displayname
then the prop part will look like this
       <a:prop>
         <href>
           http://EPL-02-SRV/eplatform/Portal/Calendar
         <href>
         <displayname>
           Calendar
         <displayname>
       </a:prop>


This means we can use
       oXMLNode.selectSingleNode("displayname").nodeTypedValue
without worrying about the namespace, be warned though, names are case sensitive. Because
this code is shorter I have used it in the sample code I walk through on the next page, which
you can think of as Mark 2.1.

Here is mark 2 of the header web part. Mark 1 is in the section beginning on page 17, but
we're about to build a mark 3 version.




Sharepoint Portal Server Cookbook                                             Page 24
For comparison here are two versions of the header part
Version 1 – ADO                                                  Version 2 – XML
Step 1 is to get the data – SQL already contains the query string with "Select "DAV:href" as href"
                                                                  theXML = "<searchrequest xmlns=""DAV:""> <sql>"& _
                                                                           vbCrLf & SQL & vbCrLf & _
                                                                           "</sql> </searchrequest>"
set adoRecordSet = CreateObject ("adodb.recordSet")               Set xhr = CreateObject("MSXML2.serverXmlHttp")
set adoConnection = createObject("adodb.connection")              xhr.Open "SEARCH", TheSource , False
AdoConnection.Open "Provider=MSDAIPP.DSO;Data Source=" & _        xhr.setRequestHeader "content-type", "text/xml"
                     TheSource                                    xhr.send (theXML)
adoRecordset.Open sql, adoConnection                              Set oXMLDOM = xhr.responseXML.documentElement

                                                                 For Each attr In oXMLDOM.Attributes
                                                                   If attr.nodeTypedValue = "DAV:" then
                                                                      propString = Replace("DAV:propstat/DAV:prop", _
                                                                                             "DAV", attr.baseName)
                                                                      exit for
                                                                   end if
                                                                 Next
Step 2 is to build the opening part of the HTML
                                 c = "<table cellspacing=""0"" cellpadding=""0"" " & _
                                     "width=""100%"" class=""DashNavArea"">" & _
                                     "<tr valign=""bottom"">"
                                 C = C & "<td class=""DashNavDashArea"">" & _
                                   "<A class=""DashNavLink"" href=""" & TheSource & """ > " & _
                                   "<nobr>Home</nobr></A></td>"

Step 3 is to build the main part of the html body
While Not adoRecordset.EOF                                       For Each n In oXMLDOM.childNodes
                                                                     Set oXMLNode = n.selectSingleNode(propString)
    C = C &"<td class=""DashNavDashArea""> " & _                     C = C & "<td class=""DashNavDashArea""> " & _
     "<A class=""DashNavLink"" href=""" & _                           "<A class=""DashNavLink"" href=""" & _
      adoRecordSet.fields("href").value & _                           oXMLNode.selectSingleNode("href").nodeTypedValue & _
      """><nobr>" & _                                                 """><nobr>" & _
       adoRecordSet("DAV:displayname")                                oXMLNode.selectSingleNode("displayname").nodeTypedValue& _
&amp;"&lt;/nobr&gt;&lt;/A&gt;&lt;/td&gt;"                             "</nobr></A></td>"
    adoRecordset.MoveNext
wend                                                             Next
Step 4 is to build the closing part of the HTML
                                 getContent = C & "<td width=""100%"">&nbsp;</td></tr></table>"




Sharepoint Portal Server Cookbook                                    Page 25
This method of working through the XML data is makes for easy porting from ADO, and it is
the only way to do some complex web parts like the Category Tree introduced on page 28 -
but in many more cases it would be more efficient to use an XSL style sheet to transform the
data.
Now, I have to confess that I'm far from being an expert in XSL, fortunately it doesn't require
a very complex style sheets. Occasionally we need to pre-process the XML before letting the
style sheet do its work. We can build the style sheet as a string in the code.
The string looks like this:
Code Fragment 14 A simple XSL style sheet
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<xsl:template xmlns:xsl="uri:xsl"
              xmlns:e="urn:schemas-microsoft-com:publishing:"
              xmlns:a="DAV:"
              xmlns:d="urn:schemas.microsoft.com:fulltextqueryinfo:"
              xmlns:c="xml:"
              xmlns:f="urn:schemas-microsoft-com:office:office" >
  <xsl:for-each select="a:multistatus/a:response">
    <TD Class="DashNavDashArea">
      <xsl:element name="a">
        <xsl:attribute name="Class">DashNavLink</xsl:attribute>
        <xsl:attribute name="href">
          <xsl:value-of select="a:propstat/a:prop/a:href"/>
        </xsl:attribute>
        <nobr>
          <xsl:value-of select="a:propstat/a:prop/a:displayname" />
        </nobr>
      </xsl:element>
    </TD>
  </xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The for-each select construct picks up each of the responses. As it loops, it outputs the <TD>
and </TD> tags: the Element and attribute construct builds an <A> tag and the <value-of
select> construct outputs a single field from the XML


We can build an XSL template like this one as a string and load it into an XML       DomDocument
object, which can be used to transform the data with the following code.

Code Fragment 15 Creating a style sheet object and using it to convert XML to HTML
Set DomSTYLESHEET = CreateObject("MSXML2.DOMDocument")
X = DomSTYLESHEET.loadXML(theXsl)
c = c & XHR.responseXML.transformNode(DomSTYLESHEET.documentElement)


As we see in a moment this is still not the best way … but I want to look at another
improvement first .We are still manually creating the HTML for the "HOME" entry on the
header menu.
We could move this into the XML for the style sheet to transform, like this




Sharepoint Portal Server Cookbook                                                    Page 26
Code Fragment 16 Building and inserting a node into an XML document
NodeXML = "<a:response xmlns:a=""DAV:""> " & _
              "<a:href>" & TheSource & TheDashboardFolder & "</a:href>" & _
              "<a:propstat>" & _
                "<a:status>HTTP/1.1 200 OK</a:status><a:prop> " & _
                  "<a:href>" & TheSource & TheDashboardFolder & "</a:href>" & _
                  "<a:displayname>Home</a:displayname>" & _
                "</a:prop>" & _
              "</a:propstat>" & _
            "</a:response>"
Set newnode = CreateObject("MSXML2.DOMDocument")
x = newnode.loadXML(nodeXML)
Set z = xhr.responseXML.documentElement.insertBefore(newnode.documentElement, _
                            xhr.responseXML.documentElement.childNodes(0))


As you can see we build a piece of XML which follows the same pattern as the responses
which come back from our query. We then load that into an XML document object, and insert
it before the first childnode in the XML which came back from the server. So the build
process for the content is reduced to:
C =    "<Table><TR>" & _
       XHR.responseXML.transformNode(DomSTYLESHEET.documentElement) & _
       "</TR></TABLE>


It would be simple to put the <table><tr> and </tr></table> tags into the style sheet, before
the <for-each> and after the </for-each> respectively.

So now we've got a web part which looks like this.

Code Fragment 17 The XSL version of the header web part
'build three strings: the Search request in TheXML. the extra node in nodeXML
'and the style sheet in theXSL
Set xhr = CreateObject("MSXML2.serverXmlHttp")
xhr.Open "SEARCH", TheSource , False
xhr.setRequestHeader "content-type", "text/xml"
xhr.send (theXML)

Set newnode = CreateObject("MSXML2.DOMDocument")
x = newnode.loadXML(nodeXml)
Set z = xhr.responseXML.documentElement.insertBefore(newnode.documentElement, _
                    xhr.responseXML.documentElement.childNodes(0))
GetContent = XHR.responseXML.transformNode(DomSTYLESHEET.documentElement)

But wait.! Why are we building the style sheet in code and then applying it? The dashboard
factory will handle this for us - which saves syntax checking a lot of text constants. There are
situations where a web part might want to build a style sheet dynamically – for example to list
the fields passed as a parameter - but these cases are rare. So the last line in that code
fragment becomes is reduced to GetContent = XHR.responseXML.xml

We can have the get content function return XML and store the XSL in the "Embedded XSL"
field of the Web Part and check the box "Use XSL to transform the content", this will then
take the building of the string out of the VB Script which is more efficient.
So here is the final version




Sharepoint Portal Server Cookbook                                               Page 27
Querying for categories, folders and other types (querying
for all the instances of a class part 2)
One of the things I wanted to create early on was a “tree” view of categories like this one




Figure 6 The category Tree Web Part
I get the data for this web part using the same code I outlined on page 21, here I want to get
all the categories: so the SQL becomes:

SELECT "DAV:href", "DAV:displayname", "DAV:ParentName"
FROM SCOPE('DEEP TRAVERSAL OF "http://myServer/myWorkspace/categories" ')
WHERE ("DAV:contentclass" = 'urn:content-classes:categoryfolder')
ORDER BY "DAV:href"


This will return a list of all the categories in the workspace. I then set about processing this
into a dynamic HTML page which provides my tree. I found a suitable piece of DHTML on
another site on our intranet. Unfortunately for me this site was in Hebrew, so I had to reverse
everything on it. Not an easy task.
As soon as I showed this part to colleagues, someone asked if it could do folders as well as
categories, well folders are either members of the class knowledgefolder or Smartfolder so
this was just a matter of changing the scope to /documents and where clause to this:
WHERE (""DAV:contentclass"" = 'urn:content-classes:knowledgefolder'
     or ""DAV:contentclass"" = 'urn:content-classes:smartfolder')


This query gives me a list of the URLs for the folders and their parents. Next to each folder I
have to draw one of four symbols, which you can see in Figure 6: a „t‟ – (next to Management
info), a „t plus‟ (next to Products), an “L” (next to RAP) and an “L Plus” (next to
Technologies). Clicking on „L plus‟ or „t plus‟ changes them to „L minus‟ or „t minus‟ (next to
Methodologies) and expands the section below them.




Sharepoint Portal Server Cookbook                                                Page 28
The code in the Web Part must decide which of the symbols to insert with only a flat list to
work from. We get the following data:
 Display name           Path                                        Parent
 Case Studies –         /Categories/Case Studies - Lessons learnt /Categories
 Lessons learnt
 Management Info        /Categories/Management Info                 /Categories
 Methodologies          /Categories/Methodologies                   /Categories
 MOF                    /Categories/Methodologies/MOF               /Categories/Methodologies
 MSF                    /Categories/Methodologies/MSF               /Categories/Methodologies
 RAP                    /Categories/Methodologies/RAP               /Categories/Methodologies
 Products               /Categories/Products                        /Categories
 Sharepoint             /Categories/Products/Sharepoint             /Categories/Products
 Social                 /Categories/Social                          /Categories
 Technologies           /Categories/Technologies                    /Categories
 XML                    /Categories/Technologies/XML                /Categories/Technologies

In order to insert the symbol the part must examine the next row in the table, so for example
before it can insert a „t Plus‟ next to Methodologies, it must look at MOF, and see that its
parent is Methodologies. But you can see in Figure 6 that Methodologies is a „t plus‟ and
Technologies is an „L plus‟ The difference ? "Technologies" doesn‟t have any siblings –
categories with the same parent.

The only way to discover this information is to look ahead in the recordset .The process for
each node is as follows:
Category                Action
Case Studies –          Look at Management info, it is a sibling of case studies, draw a „t‟
Lessons learnt
Management info         Look at Methodologies, it is a sibling of Management info, draw a „t‟
Methodologies           Look at MOF, it is a child of Methodologies, draw one of the „pluses‟
                        Look at MSF, another child
                        Look at RAP, another child
                        Look at Products, it is a sibling of Methodologies, draw „t Plus‟
MOF                     Look at MSF, it is a sibling of MOF, draw a „t‟
MSF                     Look at RAP, it is a sibling of MSF, draw a „t‟
RAP                     Look at Products, it is neither a sibling, nor a child of MSF,
                        draw an „L‟
Products                Processed in the same way as Methodologiess
Technologies            Look at the each row until XML: they are all children of Technologies
                        XML is the last row in the recordset, there are no rows left to be
                        siblings. Therefore draw an „L plus‟
XML                     It is the last row in the recordset therefore draw an L




Sharepoint Portal Server Cookbook                                              Page 29
 The comparisons are not quite as easy as I have described them because Sharepoint is not
consistent about how it escapes characters in URLs that it stores. The href value might be set
to http://./categories/management info but a child folder might refer to the same folder in
its parentName field as http://./categories/management%20info so some extra code is
needed to handle this.

Having developed this as two web parts I developed a "version 2" which has more flexibility
this is covered in the section which starts on page 53 – the web part files appear there. One of
the first things I decided to do was take the where condition and place to search out of the
code and make it a parameter.




Sharepoint Portal Server Cookbook                                               Page 30
Parameters – and re-using the default announcements,
news and links Web Parts
The examples to date have managed to avoid using parameters, but there are many cases
where you want to pass parameters to a part instead of hard coding something which will need
to be changed later. The default editing process for Web Parts in the portal is not a nice one;
so it is good to avoid editing the content. As a developer you might well use Microsoft Office
Developer to make the editing process easy, but you don't want others to have to delve into
your code to change paths and so on. A Web Part can pick up all the information which is
normally available to an ASP page, so you can use forms to pass information to a part if you
want to. More often you want to store information with the part, and there is a field in each
part‟s advanced settings especially for this: it is labelled “Store this information for this part.”
If you look at the settings for the default news part you will see that this field contains 3 lines




Figure 7 The default parameters for quick links
The name PORTAL_QUICKLINKS_FOLDER_PATH_HOLDER is one of several "path holders" which is
substituted to a pre-set value. You can see the code to do this in the Web Part content but you
could put a path like /Portal Content/QuickLinks in there.
The next line "DAV:getlastmodified" DESC specifies the sort order – a frequently asked
question is how to change the sort order, here it is sort by the last modified date, in
DESCending order.
The 10 specifies how many records to show.

To create a team news part on your own dashboard, create a folder in the workspace and
specify that it uses the News Item profile (you can use the Announcement or Web link
profiles as well). Then export the Web Part from the default home page, and re-import it into
the target dashboard. Finally replace the folder path holder in the Web Part with the name of
your folder.

While we're looking at this area of the Web Part settings, it is worth just pointing out the
caching settings. For all but the most dynamic Web Parts it is worth making sure that you turn
caching on. Here it is set for 15 minutes, and the factory uses a single cached copy for all
users – if the part contains user specific information you can keep a per user cache, but the all
user caching is more valuable because the work that is done building the part for one user is
not repeated for subsequent users – but each user will probably load the part only once. If you
set the same cache timeout for every part on the dashboard it Service Pack 1 allows you to tell
the dashboard to cache the whole of the resulting HTML page.




Sharepoint Portal Server Cookbook                                                  Page 31
Using the fields of the Web Part: parameterizing or hiding
your Web Parts
Remember that the getcontent function is passed one parameter which is traditionally called
'nod'. This contains an XML representation of the Web Part‟s data.
Code Fragment 18 VBScript to return information stored for a Web Part.
In your own Web Parts you can pickup parameter data from 'nod' with the following line of
code
X = nod.SelectSingleNode("PartStorage").Text


Code Fragment 19 The view as web folder part, re-worked as VBscript with a parameter
Here is the View as Web Folder part re-written as a VBscript Web Part, although all but one
line is just building up a string to return. You can see the parameter being introduced.
Function GetContent(nod)
  C = C & "<table border=""0"" cellspacing=""0"" cellpadding=""0"" width=""100%"">"
  C = C & "<tr><td class=""DashPartFrame"">"
  C = C & "<table cellspacing=""0"" Width=""100%"" cellpadding=""0"" border=""0"">"
  C = C & "<tr><td class=""DashPartTitle"">&nbsp;</td></tr></table>"
  C = C & "<table width=""100%""><tr><td valign = ""top"" align=""right"">"
  C = C & "<img src=""DocTypeIcons/urn-schemas-microsoft-com-dashboard16.gif"">"
  C = C & "<a href=""/gotowf.asp?url=" & nod.SelectSingleNode("PartStorage").Text
  C = c &""">View as Web folder</a></td></tr>"
  C = C & "<tr><td>&nbsp;</td></tr></table></td></tr></table>"
  getContent = c
end function


You should realise that 'nod' contains every parameter of the Web Part, and they are dynamic
i.e. you can change them but the change doesn't get written back to the store.
Code Fragment 20 dynamically hiding a Web Part.
I can add a line to the code above which looks like this:
        If user_has_no_access nod.SelectSingleNode("IsVisible").Text = "0"
Code Fragment 21 Listing all the properties of a Web Part
If you want to see all the fields in the Web Part, insert this code into the VBScript content of a
new Web Part.
function getContent(nod)
  gc="<table width=""100%"">"
  for each n in nod.childnodes
       gc = gc & "<TR><TD><b>" & n.nodeName & "</b></TD>"
       gc = gc & "<TD>" & server.htmlencode(n.text) & "</TD></TR>"
  next
  getcontent =gc & "</table>"
end function




Sharepoint Portal Server Cookbook                                                  Page 32
Testing folder security
In Code Fragment 20 we looked at hiding a Web Part if the user has no access to it. In the
supporting asp files for Sharepoint there are some functions and constants defined which
allow us to test the access the current user has to a folder.

I found the original version of this code in a part a colleague had written to jump to a user's
personal dashboard2. If the user didn't have a personal dashboard then the part could create
one – assuming they had permission to do so.
Code Fragment 22 Checking permissions on a folder
StrDashboardsPath = GetWorkspaceURL & "/dashboards/"
iRights = ToLong(GetMAPIprop(strDashboardsPath, PR_RIGHTS_PROP))
if (iRights and CREATESUBFOLDERS) = CREATESUBFOLDERS)) then
       Create_a_dashboard
end if


Notice that CreateSubfolders is defined as a constant, the other constants reflect values from
Exchange.The list of constants available to your code is as follows:

CONST   READITEMS = 1
CONST   CREATEITEMS = 2
CONST   EDITOWN = 8
CONST   DELETEOWN = 16
CONST   EDITANY = 32
CONST   DELETEANY = 64
CONST   CREATESUBFOLDERS = 128
CONST   FOLDEROWNER = 256
CONST   FOLDERVISIBLE = 1024


By testing for folderVisible + ReadItems (for example) we could decide whether or not to
show the View as Web folder part.
Code Fragment 23 The View as Web Folder part modified to check security
Function GetContent(nod)
fldrPath = nod.SelectSingleNode("PartStorage").Text
if right(fldrPath,1) <> "/" then fldrpath = fldrPath & "/"
rightsneeded = folderVisible + ReadItems
iRights = ToLong(GetMAPIprop(fldrPath, PR_RIGHTS_PROP))
if (iRights and RightsNeeded) = RightsNeeded)) then
  C = C & "<table border=""0"" cellspacing=""0"" cellpadding=""0"" width=""100%"">"
  C = C & "<tr><td class=""DashPartFrame"">"
  C = C & "<table cellspacing=""0"" Width=""100%"" cellpadding=""0"" border=""0"">"
  C = C & "<tr><td class=""DashPartTitle"">&nbsp;</td></tr></table>"
  C = C & "<table width=""100%""><tr><td valign = ""top"" align=""right"">"
  C = C & "<img src=""DocTypeIcons/urn-schemas-microsoft-com-dashboard16.gif"">"
  C = C & "<a href=""/gotowf.asp?url=" & nod.SelectSingleNode("PartStorage").Text
  C = c &""">View as Web folder</a></td></tr>"
  C = C & "<tr><td>&nbsp;</td></tr></table></td></tr></table>"
Else
.. nod.SelectSingleNode("IsVisible").Text = "0"
.. c= ""
End if
getContent = c
end function




2
  It's that Sharepoint Discussion list again. This time it was Ron Tielke who did the work to find out how to do
this.


Sharepoint Portal Server Cookbook                                                              Page 33
Paths in Web Parts, using substitution, and ways to
discover the path
The last thing the dashboard factory does when it finishes building a part is to replace some
tokens. These are _WPC_ , _WPQ_ , _WPID_ , _DashboardID_ and _WPC_

When you write a Web Part you can't be sure which other Web Parts will be in dashboards
with it. So there is a possibility that the names used in one part clash with the names in
another. You could take huge care with the names you choose, but even then if there are two
instances of the same part on the same dashboard it will not do any good. Fortunately the
dashboard factory has a solution to this: any time it encounters the string _WPQ_ in the
HTML pieces which are being stitched together to form the dashboard, it replaces it with a
unique "Web Part Qualifier" for the Web Part which produced it. So by prefixing names with
this token we can avoid clashes.

The other useful substitution is for the Web Part Resources folder. Any instances of
_WPR_ are changed to a folder named WebPartName_files

You can use this in the content part of an HTML Web Part, or in the output of the GetContent
function in a script Web Part. But you can also use it as the path to the content. I have an
Instant Messenger HTML page which does not sit very well on a true dashboard page but
works nicely in an Iframe. So initially I placed the files for it in the root of the web site and
had a part like the one below with the link which was http://mysite/im.htm

This worked, but I then had to tell others who wanted a Web Part, "Here is the HTML file and
the gifs that you will need, put them anywhere, and then create a Web Part". So as an
experiment I put them into the folder named Instant Messenger_files and tried using _WPR_
in the path, and it worked! Now I can export my Web Part, zip up the DWP and folder
containing the files and I have easy distribution and installation




Figure 8 The structure of the Instant messenger part and its files. Note that web parts are always 0 bytes
in size. Their content is stored as a property not in the file itself.




Figure 9 Creating the IM Web Part
Here are the files for the IM Web Part




Sharepoint Portal Server Cookbook                                                        Page 34
And here is the part in action




Figure 10 The IM Web Part in action
There is another way of getting data into an Iframe. Looking at someone else's page I came
upon a Web Part which simply had _WPC_ in the "Get content from the following link"
box. So I put _WPC_ into the content field of a part to see what I got back and it was this:
http://epl-02-srv/eplatform/Portal/resources/store.asp?Cmd=GetWebPartContent
&WebPartID=http://epl-02-srv/eplatform/Dashboards/test/Test%20Paramaters.VBS


Calling store.asp with CMD = GetWebPartContent and a Web Part URL gives the content part
of the Web Part. A quick look inside Store.asp reveals that you can call it with some other
variations:

Cmd=GetWebPart&webpartID=WebPartURL                         Returns the XML for the Web Part
                                                            (DWP file)
Cmd=GetDashboard&DashboardID=DashboardURL                   Returns an XML representation of the
                                                            dashboard
Cmd=GetWebpartResource&webPartID=webPartURL                 Returns a resource from the
&ResourceName=Resource
                                                            WebPart_Files folder


One script Web Part I produced needed to contain a large block of fixed HTML. Outputting it
from code seemed laborious, so I wanted to read it from a file. Using _WPR_ would not work as
that is replaced after the script is run, not before. The XML passed to the GetContentFunction
doesn't contain the path, but there is a similar object – which is not available to GetContent –
which has the information. To get the data, I need a single line which sits outside the
getcontent function to store the data, and a corresponding line to retrieve it, as in the
following code fragment:
Code Fragment 24 Discovering the path to the _files folder and loading a file from it in a script Web Part
function GetContent(nod)

        urlName = StoreGetResourceDir(GetPerPageCachedItem("my-Path")) & _
                     "/Preamble.htm"
        urlname = mid(urlname, instr(8,urlName,"/"))
        Preamble = LoadStringFromFile( replace(server.mapPath(urlname) ,"%20"," "))

Build page

       GetContent = preamble & the_page_that_was_built
end function

SetPerPageCachedItem "my-Path", nodeDAV.selectSingleNode("a:href").text




Sharepoint Portal Server Cookbook                                                        Page 35
One last thing on paths – the Document Library
According to things I have read, the product team debated whether the Document Library
dashboard should start in the Documents folder or one level above that so co-ordinators could
get into the Portal Content folder where the News, Announcements and Quick Links are
stored. They opted for the latter and several people I know have complained about this, so I
wrote a simple Web Part which doesn't display any data. It checks to see where it is running
and if it is at the Portal/Document library dashboard it sends the browser a single line of
script which re-directs it to the /documents folder. If it is anywhere else it sends nothing back.
Code Fragment 25 The code for the "Jump to documents folder" web part
function GetContent(nod)

    If UCase(Request.ServerVariables("REQUEST_METHOD")) = "GET" Then
        strRet = Request.QueryString("DataUrl")
             If Err.Number <> 0 Then Exit Function

    ElseIf UCase(Request.ServerVariables("REQUEST_METHOD")) = "POST" Then
             strRet = CStr(Request.Form("DataUrl"))
             If Err.Number <> 0 Then Exit Function
    End If
if Ucase(StrRet) = ucase(getWorkspaceURL) & "/PORTAL/DOCUMENT LIBRARY/" then
getcontent = "<script LANGUAGE=""VBScript"">" & vbcrlf & "window.navigate(""" &_
              getWorkspaceUrl & "/documents"")" & vbcrlf & "</script>"
else
  getcontent = ""
end if
end function


You could use similar logic to re-direct any dashboard page to an alternate destination.




Sharepoint Portal Server Cookbook                                                 Page 36
Using the Portal's own forms with "Placeholder
documents" in your own applications
I wanted to use Sharepoint Portal Server to provide similar functionality to the lists in
Sharepoint Team Services. On the dashboard in Figure 11, the Web Part at the top does just
that by showing a selection of the fields found in the folder's default profile. The other part on
the dashboard allows files to be uploaded to a pre-defined folder. Both parts use parameters to
define the folder to use.
The list part builds its own title bar which includes a "View as Web Folder" icon, built using
the same techniques that I showed in Code Fragment 10 on page 13. The Upload part transfers
a file to server and collects profile information for the document.
As well as displaying the items in the list, the List Web Part needs to allow the user to create
new items, and edit or delete existing ones – these are the icons on the right of the Web Part.




Figure 11 The upload and list Web Parts
The upload Web Part was built by reverse engineering the Add a document page (which you
can reach via the dashboard page for any document folder) it invokes a page named
HandleUploadedFile.asp in the /Portal/resources/ folder. It needs four parameters:
Parameter           Value                  Explanation
PortalCmd           "uploadnewdoc"         Identifies which of 4 commands is required,
                                           others are UploadOverWrite, Checkin and Publish
CheckInOrPublish "Publish"                 For uploads, this can be set to "Checkin" or
                                           "Publish" or omitted
DataUrl             Destination Folder     The folder where the file should go
UrlToReturnTo"      This Page              Page to go back to when the process is complete



Sharepoint Portal Server Cookbook                                                Page 37
The Upload part uses a simple HTML form – if you have not used a form which uploads files
before, the enctype field in the <form> tag, and the input type of "file" will probably be new
to you. The form uses four hidden fields for data described in the table above, and a file field
to control the upload.
Code Fragment 26 HTML form for uploading files to a Sharepoint smart folder
<FORM name="WebPartEditForm" enctype="multipart/form-data" method="post"
     action="http://epl-02-srv/eplatform/Portal/resources/HandleUploadedFile.asp" >
  <INPUT type="hidden" name="PortalCmd" value="uploadnewdoc">
  </INPUT>
  <INPUT type="hidden" name="DataUrl"
          value="http://epl-02-srv/eplatform/documents/projects">
  </INPUT>
  <INPUT type="hidden" name="UrlToReturnTo"
          value="Http://epl-02-srv/eplatform/Dashboards/test/">
  </INPUT>
  <INPUT type="hidden" name="CheckInOrPublish" value="Publish">
  </INPUT>
  <INPUT type=file style="width: 100%" maxlength="4096"
         id="FileUpload" name="FileUpload"/>
  <INPUT class="CustButton" TYPE="submit" value="Upload" id=sbmOK name=sbmOK >
  </INPUT>
</FORM>
In practice this is a code based Web Part which determines the current page and uses that as
the "URL to return to" and takes a parameter to specify the destination (DataURL), if the folder
does not use versioning then it omits the checkInOrPublish line.

I took these ideas a little further in the List Web Part, which needed to create and delete items,
and edit their properties. If you probe a little round the document actions page in the portal
you find that a lot of things that are done to the document rely on opening it with some
parameters. The way the Web Storage System works is to give every item in it a class and use
this to determine how to display the item. You can see this at work in Outlook Web Access –
that's how it knows to open one URL in a message form, and another in an appointment form.
In Sharepoint, Dashboard folders, Document folders and Category folders all have classes
which lead to a dashboard being opened. When you click the contents link at the top right
corner of a dashboard you'll notice that the URL has ?cmd=contents appended to it. The ?cmd=
tells the Web Storage System to take some non-default action with the item (such as modify
the dashboard's contents)
For document actions, the command which is appended is in the form
?cmd=tahoePortal&PortalCmd=<command>&UrlToReturnTo=<someUrl>
The commands are as follows
&PortalCmd=Inspect        Called by the "Show actions" link. Invokes the "document inspection" page
&PortalCmd=checkout       Check a document out
&PortalCmd=undocheckout   Undoes the check out
&PortalCmd=checkin        Checks a document in – which invokes handleuploadedFile.asp.
&PortalCmd=publish        Publishes a document (checking in if necessary)
&PortalCmd=Approve        Approves a document (if folder requires approval for publication)
&PortalCmd=Reject         Rejects a document (if folder requires approval for publication)
&PortalCmd=history        Shows the version history for a document
&PortalCmd=rename         Changes the file name (not the displayed title) for a document
&Portalcmd=Delete         Deletes the document
&PortalCmd=EditProfile    Allows the profile to be edited, and can take an additional parameter
                              &CheckInOrPublish=Publish|CheckIn
                              This only works if the document is checked out, and is called by
                              HandleUploadedFile (see above)



Sharepoint Portal Server Cookbook                                                Page 38
The list items in my Web Part are place holder documents with suitable properties. A place
holder document doesn't have any "real" content, and exists only to hold the properties. To
create list items I built an ASP page "Create.ASP". Create takes a folder name (which was a
parameter for the Web Part), and the ubiquitous UrlToReturnTo – which is the dashboard page
where the part is running. The ASP script uses the date and time together with the user's logon
name to generate a unique file name for the place holder which it creates. Once the
placeholder is created the ASP re-directs the user's browser to open the place holder with the
"edit profile" portal command – telling it to go back to the dashboard page when it is done.
Code Fragment 27 ASP to Create a place holder file
<%
Dim oDoc       '     As PKMCDO.KnowledgeDocument
Dim ofld       '     As PKMCDO.KnowledgeFolder
Dim oStream    '     As ADODB.Stream
strFldrURL           = request.queryString("folder")
strUrlToReturnTo     = request.queryString("UrlToReturnTo")
strUserName          = request.serverVariables("LOGON_USER")
strUserName          = mid(strUserName, instr(strUserName,"\")+1)

set ofld = createobject("Cdo.knowledgeFolder")
ofld.datasource.open(strFldrURL)
ThisIsAnAdvancedFolder = ("urn:content-classes:smartfolder" = ofld.contentClass )

strSaveToURL     =   cstr(now())
strSaveToURL     =   replace (strSaveToURL ,":","")
strSaveToURL     =   replace (strSaveToURL," ","")
strSaveToURL     =   replace (StrSaveToURL,"/","")
strSaveToURL     =   strFldrURL & "/" & strSaveToURL & strUserName & ".htm"

Set oDoc = CreateObject("CDO.KnowledgeDocument")
Set oStream = oDoc.OpenStream
oStream.Type = 2                         ' 1 = adTypeBinary , 2 = adTypeText
oStream.SetEOS
oStream.writeText "<HTML></HTML>"
oStream.Flush
oDoc.DataSource.SaveTo StrSaveToURL, , , &H4000000 '= adCreateOverwrite

    destinationUrl = strSaveToURL & "?Cmd=TahoePortal&PortalCmd=EditProfile"
    if ThisIsAnAdvancedFolder then destinationUrl = destinationUrl & _
                                                   "&CheckInOrPublish=Publish"
    destinationUrl = destinationUrl & "&UrlToReturnTo=" & strUrlToReturnTo

Response.Redirect destinationUrl
%>


The first seven lines declare variables and pick up the parameters that will be needed. Because
the edit profile page needs to be handed the parameter "CheckInOrPublish=Publish" if this is
an advanced folder, the next three lines check the folder type.

The next five lines in the ASP create the unique file name – it strips colons, slashes and
spaces out of the date and time, prepends the folder name and appends username.htm to the
end.

The next 7 lines create the place holder, the process is
   1. Create a knowledge document object,
   2. Open the "stream" part of the object, which is the file itself, define the file to be text,
   3. Move to the end of the stream, and write some text to it,
   4. Save the stream and then save the knowledge document




Sharepoint Portal Server Cookbook                                                 Page 39
Up to this point nothing has been sent back to the user's browser. Finally it is redirected to the
newly created file with the &cmd=TahoePortal&PortalCmd=EditProfile etc appended to the end
of it, this brings up the dialog for the user to enter the "list item" information

Editing the item's profile uses a very similar process. An item in an advanced folder must to
be checked out before its profile can be edited, so I needed another ASP file
(edititem.asp).This is given a path to a document, checks to see if it is in a smart folder – if
it is it checks it out before redirecting to the edit profile page (where the document will be
re-published).
Code Fragment 28 The Edit item asp page
<%
Dim oVer        ' As PKMCDO.KnowledgeVersion
Dim ofld        ' As PKMCDO.KnowledgeVersion
set ofld         = createobject("Cdo.knowledgeFolder")
theDocUrl        = request.queryString("Item")
strUrlToReturnTo = request.queryString("UrlToReturnTo")

pos = 1
while instr(pos, theDocUrl ,"/") > 0
 pos = instr(pos, theDocUrl , "/") +1
wend

on error resume next
ofld.datasource.open (left(theDocUrl , pos -1 ))
if "urn:content-classes:smartfolder" = ofld.contentClass then
       Set oVer = createobject("CDO.KnowledgeVersion")
       set rs= over.checkout (theDocURL)
       Strcheckin = "&CheckInOrPublish=Publish"
end if

response.redirect theDocUrl &
"?Cmd=TahoePortal&PortalCmd=EditProfile&UrlToReturnTo=" & strUrlToReturnTo              &
strCheckIn
%>


With edit item and create functionality in place it was easy to call them from the Web Part.
The delete icon links to the document with ?Cmd=TahoePortal&PortalCmd=Delete appended to
it. Since this does not prompt the user, it is necessary to put some code into the page to check
the user intended to click delete!

Here are the files for the two Web Parts, note that the code assumes the ASP files are in the
root of web site.




The list part has a number of options which you can enable in the code. You can turn on a
display of document type icons beside the items in the list. You can remove the delete option
and change the edit option into show actions (which will allow check in and check out, show
history, edit properties, and delete according to the users permissions). By adding the word
Deep as a third line to part storage ,you can make the search a deep traversal of the whole
documents folder or you remove the restriction of documents matching just one profile (don't
do both!)




Sharepoint Portal Server Cookbook                                                Page 40
Exploiting properties and profiles
In the previous section I showed the list Web Part (see Figure 11), and how it made use of the
built-in pages that can be called up with ?cmd= parameters.
It is worth looking at how the title bar knows these are Project Document items, and how the
columns are selected.

The Web Part takes its parameters on two lines like this:
/documents/projects
Author,Title,Customer Name,Sector,Product version,Description,Closed


The following code parses the stored information into a folder name and a collection of field
names using the split function. The first call to split divides the information in partStorage
wherever it meets a line feed character (n.b. this is a line feed, not a carriage return/linefeed),
and the third line of code takes the second line of the parameter and splits it into an array of
field names where ever it sees a comma.
     aRows = split(nod.SelectSingleNode("PartStorage").Text                , vblf)
     sFldrUrl = sWsURL & arows(0)
     afields = split(arows(1) , ",")


The next step is to connect a knowledgeFolder object to the specified folder and look at its
defaultcontentclass property. This holds URN style name for the folder's default document
profile. We can then get objects to represent the profile and each of the properties within it.
Armed with that information we can build a SQL statement which gets all the fields in the
profile and refers to them by their "friendly" names.
Code Fragment 29 Getting the content class object and using its properties to build a SQL statement
The code starts by getting an object to represent the folder, and then gets an object that
represents the folders default content class (a.k.a. profile) by using the getObject method of a
KnowledgeWorkspace object. The content class includes a propertyOrder field –which
combines the properties defined specifically for the class, and those inherited from the class
on which it is based. Those properties are listed as URN names which are what we normally
use in a SQL query. More calls to GetObject return an object for each property (including its
display name, data type, and dictionary of allowed values), and the URN and display name
are used to build a SQL query.
  set ofld = createobject("Cdo.knowledgeFolder")
  ofld.datasource.open(SFldrURL)

  sWsURL = getWorkspaceURl()
  Set oWS = CreateObject("CDO.KnowledgeWorkspace")
  oWS.DataSource.Open sWsURL

  Set OKcc = oWS.GetObject(ofld.DefaultContentclass)
  PropList = OKcc.PROPERTY("urn:schemas-microsoft-com:publishing:propertyorder")

  Sql = "Select ""DAV:href"" as ""href"", ""DAV:displayname"" as ""DisplayName"","
  FOR Each prop in Proplist
       set oke = ows.getobject(prop)
       sql = SQL & vbcrlf & " """ & prop & """ as """ & oke.title & """ ,"
  next
  sql = left(sql,len(sql) -1) & vbcrlf & "FROM SCOPE('Shallow TRAVERSAL OF """& _
                                                      sfldrUrl &"""') "
  Sql = SQL & vbcrlf &    "WHERE ( ""DAV:contentclass"" = '" & _
                                        ofld.DefaultContentclass &"')"




Sharepoint Portal Server Cookbook                                                      Page 41
This is the SQL that results from this code
SELECT
   "DAV:href" as "href", "DAV:displayname" as "displayname" ,
   "urn:schemas-microsoft-com:office:office#Author"           as "Author" ,
   "urn:schemas-microsoft-com:office:office#Title"            as "Title" ,
   "urn:schemas-microsoft-com:office:office#Customer Name"    as "Customer Name",
   "urn:schemas-microsoft-com:publishing:Categories"          as "Categories" ,
   "urn:schemas-microsoft-com:office:office#Description"      as "Description" ,
   "urn:schemas-microsoft-com:office:office#Keywords"         as "Keywords" ,
   "urn:schemas-microsoft-com:office:office#Product Version" as "Product Version",
   "urn:schemas-microsoft-com:office:office#Sector".          as "Sector" ,
   "urn:schemas-microsoft-com:office:office#Closed".          as "Closed"
FROM SCOPE('Shallow TRAVERSAL OF "http://epl-02-srv/eplatform/documents/projects"')
WHERE ( "DAV:contentclass" = 'urn:content-classes:Project Document')


By this point you may be wondering why I have gone to all this trouble to build a SQL query
like this. There are two reasons, firstly SELECT * doesn't work in this type of query, and
secondly I have to build the output of the Web Part using the field name specified in the
parameter. This output is a HTML Table, so to build the first row, a for … next loop can
output the array of field names. The for each row in the recordset I can add a row to the
HTML table, looping through that same array of field names to output the desired fields in the
correct order.
Code Fragment 30 Building up the output table for the list part
For Each ChNode in oXMLDOM.childNodes
  Set oxmlnode = chNode.selectSingleNode(propString) 'the current "record"
  theHref = oxmlnode.selectSingleNode("href").nodeTypedValue
  for each f in Afields
       If InStr(oxmlnode.XML, "<" & f) Then ' the field has been returned
         set xmlField = oxmlnode.selectSingleNode(f)
         TheField = xmlField.nodeTypedValue
       Else ' if there was nothing returned
         TheField = "&nbsp;"
       End If
       strContent = strContent & vbcrlf & "<td valign=""top"">"
         if f="Title" then ' Title needs special handling
         If TheField <> "&nbsp;" then theDName = TheField
              strContent = strContent & "<A HREF=""" & TheHref & """><B>" & _
                                                      TheDName & "</B></A>"
         else
              strContent = strContent & vbcrlf & TheField
         end if
         strContent = strContent & vbcrlf & "</td>"
  next 'field
strContent = strContent & vbcrlf & "</tr>"
nextd


Note that this code outputs only the data returned by the query. In the real Web Part there are
also links for deleting and editing the file, as described in the previous section.

Something else that you should note here is the use of on error resume next. Normally you
can use this in your code and rely on execution to blunder through to the end no matter what.
Not so in a Web Part. When Web Part code is run in the dashboard factory, the factory itself
checks to see if the code finished with an error. If you use on error resume next on its own,
the factory will report the error that occurred and not show the part's output – even though the
part completed successfully. The solution is to make sure you call err.clear in any part that
uses on error resume next.




Sharepoint Portal Server Cookbook                                               Page 42
Web links and place holder documents
Web links are a great feature of Sharepoint and not at all well understood.

If you create an entry in the Web Storage System and set a property
"urn:schemas-microsoft-com:publishing:ShortcutTarget" - which profile forms refer to as
"Link", then the body of the document will be ignored. When the portal lists a document it
normally creates a clickable link to the document in the Web Storage System. But if the link
field is present, it becomes the target of the link instead. Importantly the indexing and
categorization services follow the link- so the file at the end of the link is indexed. All the
other properties of the document are respected, so if the placeholder is assigned to a category
the document can be found through the category folder




Figure 12 A web link place holder document - note the PDF file does not have show actions - and its URL
is on the status bar
The category assistant will use documents which you link to in this way as training
documents. Once enabled, the assistant tries to place documents outside SPS into categories -
that's easy. But this route allows you to train the assistant without copying sample documents
into the Web Storage System, which is very useful indeed.




Sharepoint Portal Server Cookbook                                                     Page 43
I wrote a Web Part to create the place holder documents so that users could easily add links to
their favourite external documents to a "pot" of links.




Figure 13 The Share web links Web Part
One thing which is unusual about this Web Part is that is a conventional HTML form which
links back to itself – i.e. it goes back to the dashboard page with the data entered by the user.
I've kept this part in the cookbook for that reason, although it has been replaced by another
part. When the Web Part's script runs it picks up the data and processes it like a traditional
ASP.
Code Fragment 31 The Share links Web Part
function getContent(nod)
TheURL = request.form("URL")
if theUrl > "" then
       If left(ucase(theUrl) , 7) <> "HTTP://" then theURL = "http://" & theURL
end if

TheWorkspace = request.form("Workspace")
If theWorkspace = "" then TheWorkspace = GetWorkspaceUrl()
end if

TheCategories = split(request.form("categories"), ", ")
TheDescription = request.form("Description")
TheName = request.form("Name")
if theName > "" then
       TheSavePath = nod.SelectSingleNode("PartStorage").Text
       if left (TheSavePath,1) <> "/" then TheSavePath = "/" & TheSavePath
       if right(TheSavePath,1) <> "/" then TheSavePath = TheSavePath & "/"
       TheSavePath = TheWorkspace & TheSavePath & theName
       if right(UCASE(thesavePath) , 4) <> ".URL" then
              theSavePath = theSavePath & ".URL"
       end if
end if
if (TheWorkspace > "") and (theName > "") and (TheUrl > "") then
    Set oDoc = Server.CreateObject("CDO.KnowledgeDocument")
    Set oStream = oDoc.OpenStream
    oStream.Type = 2                        ' 1 = adTypeBinary , 2 = adTypeText
    oStream.SetEOS
    oStream.writeText "[InternetShortcut]" & vbcrlf & "URL=" & TheUrl
    oStream.Flush
    oDoc.Title = TheName
    if ubound(TheCategories) > -1 then     ODoc.categories = TheCategories
    if theDescription > "" then oDoc.Description = TheDescription
    odoc.fields("urn:schemas-microsoft-com:publishing:ShortcutTarget")=TheUrl
    odoc.fields("DAV:contentclass")="urn:content-classes:weblink"
    odoc.fields.update
    oDoc.DataSource.SaveTo TheSavePath, , , &H4000000 '= adCreateOverwrite
    set oStream = nothing
    set oDoc = Nothing



Sharepoint Portal Server Cookbook                                                 Page 44
    C = C & "<script language=""VBScript"">" & vbcrlf & _
.            "MSGBOX ""Link added"",,""Thank you"" " & vbcrlf & "</script>"
elseif (theName > "") or (theUrl >"") then C = "<script language=""VBScript"">" & _
             vbcrlf & "MSGBOX ""You must supply a name and a URL"" " & vbcrlf &
"</script>"
end if

C = C  & "<Form METHOD=""Post""><Table width=""100%"">" & vbcrlf
C = C  & "<TR><TD COLSPAN=""2"">Add the document at this URL:    " & _
         "<i><font color=""#FF0000"">(required) </font> </i> </TD></TR>" & vbcrlf
C = C & "<TR><TD COLSPAN=""2""><Input Type=""Text"" name=""URL"" " & value="""
If TheUrl <> "" Then C = C & theUrl
C = C & """ style=""width: 100%""></TD></TR>" & vbcrlf
C = C & "<TR><TD COLSPAN=""2"">Use this name for the link:       " & _
              "<font color=""#FF0000""><i>(required)</i></font></TD></TR>" & vbcrlf
C = C & "<TR><TD COLSPAN=""2""><Input Type=""Text"" name=""Name"" " & "value="""
If TheName <> "" Then C = C & theName
C = C & """ style=""width: 100%""> </TD></TR>" & vbcrlf
C = C & "<TR><TD width=""50%"">Put link in these categories</TD>" & vbcrlf
C = C & "<TD width=""50%"">Display this description</TD></TR>" & vbcrlf
C = C & "<TR><TD><Input Type=""Hidden""     name=""Workspace"" value="""&_
                                                 TheWorkspace & """>" & vbcrlf

C = C  & "<select name=""Categories"" multiple style=""width: 100%"" " &_
                                 " size=""4"">" & vbcrlf
Set oWS = CreateObject("CDO.KnowledgeWorkspace")
oWS.DataSource.Open TheWorkspace
Set oKD = oWS.GetObject("urn:dictionary:categories")
v = oKD.Property("urn:schemas-microsoft-com:publishing:dictionaryvalues")
for x = lbound(v) to UBound(v)
       C = C & "<option>" & v(x) & "</option>" & vbcrlf
next

C = C  & "</select></TD>"
C = C  & "<TD><TEXTAREA name=""Description"" MAXLENGTH=""2000"" " & _
         "style=""width: 100%"" ROWS=""4"" WRAP=""Virtual"">"
If TheDescription <> "" Then C = C & theDescription
C = C & "</TEXTAREA ></TD></tr>" & vbcrlf
C = C & "<TR><TD><Input type=""submit"" value=""Create link"" " & _
                                  "style=""width: 100%"" name=""GOButton"" ></td>" &
vbcrlf
C = C & "<TD><Input type=""Reset"" value=""Clear form"" " & _
                           "style=""width: 100%"" name=""ClearButton"" >
</TD</TR></Table>"
getcontent = C
end function


The code checks to see if it was passed a URL and a workspace. It makes sure the URL
begins http:// although you might allow other URL types. If no workspace was passed it
assumes the current workspace. It then gets the categories from the form and turns them from
a comma separated list into an array. It gets the description and the file name from the form,
and gets the path to store the place holder document from PartStorage

The next part is to build the place holder: it is given an extension of .URL and the correct data
for a shortcut file is written to it. This allows a user to click on the place holder in a web
folder and have it open correctly. The title, description, categories, content class, and shortcut
target for the place holder are all set and it is saved. If this is successful the dashboard page
displays a pop up a message saying "Thank you", but if required parameters are missing it a
message tells the user what is required.




Sharepoint Portal Server Cookbook                                                Page 45
The bulk of the rest of the code is involved in creating the HTML form, but one interesting
part to pick out is the following section which is another way to get a category listing
Set oWS = CreateObject("CDO.KnowledgeWorkspace")
oWS.DataSource.Open TheWorkspace
Set oKD = oWS.GetObject("urn:dictionary:categories")
v = oKD.Property("urn:schemas-microsoft-com:publishing:dictionaryvalues")
for x = lbound(v) to UBound(v)
       C = C & "<option>" & v(x) & "</option>" & vbcrlf
next


The code creates a knowledge workspace object and asks it to get the categories dictionary –
using its URN name (in the same way that the list Web part obtained the ContentClass object
in Code Fragment 29). It then gets the dictionaryValues field for the dictionary object – the
values are in an array, and the for … next loop outputs each of the values.

When categories are added to the system they are simply placed at the end of the dictionary
there is a tool in the SPS resource kit to sort the dictionary into alphabetical order. In theory
you can remove some of the items from the dictionary, e.g. on my server I have a category
:Products with sub categories :Products:Exchange; :Products:Sharepoint and
:Products:Windows If I did not want :Products to be presented to authors when they assign
their documents to categories I could modify the dictionary to remove it. So in this Web Part
it is better to get the information form the dictionary than from the category folder structure.




Sharepoint Portal Server Cookbook                                                 Page 46
Another web links part … Contribute
In Figure 11 I showed the upload web part, and a link to a page which creates a new item. The
"share links web part" which appears in Figure 13 takes up more space on the dashboard than
the ideal, so it made sense to me to combine the two functions into a single part, and to add
the code from the "Testing folder security" section to make sure that users were only offered
the chance to upload files or create links if they had create permissions on the folders that the
part connected to; if the user has permissions to neither folder, the part hides itself.




Figure 14 The Contribute web part
Most of the code in the part has appeared before:
It starts with some code to determine which folders it should use from Part Storage, and
checks the user's rights to these folders. If the user has rights to neither it hides the web part
completely.
Code Fragment 32 Checking permissions for the folders in the contibute web part
wsurl=getworkspaceurl()
aRows = split(nod.SelectSingleNode("PartStorage").Text                 , vblf)
  destFldr = WsURL & arows(0)
  sFldrUrl = WsURL & arows(1)
iRightsNeeded = folderVisible + CreateItems

sFldrPath = sFldrUrl
if right(sFldrPath,1) <> "/" then sFldrPath = sFldrPath & "/"
iRights = ToLong(GetMAPIprop(sFldrPath, PR_RIGHTS_PROP))
bUserHasRightsToLinks = ((iRights and iRightsNeeded) = iRightsNeeded)

sFldrPath = DestFldr
if right(sFldrPath,1) <> "/" then sFldrPath = sFldrPath & "/"
iRights = ToLong(GetMAPIprop(sFldrPath, PR_RIGHTS_PROP))
bUserHasRightsToDocs = ((iRights and iRightsNeeded) = iRightsNeeded)

if (not bUserHasRightsToDocs ) and (not bUserHasRightsToLinks ) then
       nod.SelectSingleNode("IsVisible").Text = "0"
       getcontent = ""
       exit function
else


Having decided that we have something to display, the else clause contains the following code
Code Fragment 33 Creating the clickable link to create a quick-link item in the store
If bUserHasRightsToDocs then
       C = the Html form used in the orginal upload part
End if
If buserHasRightsToLinks Then
       c = c &"<IMG src=""DocTypeIcons/htm16.gif"" border=""0"" " & _
              " width=""15"" height=""15""/> " & _
              <a Title=""New item"" href=""/create.asp?folder=" & sFldrURL & _
               "&UrlToReturnTo=" & ThisPage & _
              """> I want to add a link to a single page on another site</a>"
end if
GetContent = c




Sharepoint Portal Server Cookbook                                                       Page 47
Here is the complete web part




Sharepoint Portal Server Cookbook   Page 48
The XMLHTTP object and "Chainsaw" development3 of Web
Parts
Sometimes we need to fetch dynamic data from a web site and process it before displaying it
to the user. This would be so easy if sites used XML for their data, but most of the time we
need to slice up HTML. We do this in two ways.
1. Client side. An HTML page runs a script, which fetches the data, processes it and
    displays it in the page. Here's a little code to do that
         <DIV ID=NewsDIV> &nbsp </DIV>
         <script language="VBScript">
         getNews
         Sub GetNews
             ' do stuff to build the Html we want
             newsDiv.innerHtml = MyHtml
         end sub
      </script>
      When the page loads, getNews is called, this builds the HTML and using a simple
      piece of dynamic HTML inserts it between the <DIV> and </DIV> tags.
2. Server side: here an ASP runs to build the HTML and sends it back to the client. The
   HTML generation is identical.

With Web Parts the client side HTML can go in an HTML Web Part, or ASP code can go in a
VBscript or JScript Web Part.

Running at the server can give us the ability to support a broader range of clients, and take
advantage of caching, running at the client puts less load on the server – but generates more
network traffic. Since my server has capacity to spare, I have reworked everything to be
server side.

In both cases if the site holding the data has a major re-design, you will need to re-work your
Web Part. There is a huge amount you can do just by fetching HTML, and cutting it up with
left, mid, right, and instr then sticking the results together. I hope it goes without saying
that you need to show a proper respect for other people's intellectual property when
developing this type of Web Part.

I'm going to work through the development of stock quote part,4 shown here:


Figure 15 The Stock quote Web Part.
All these parts, client side or server work in the same way,
1. Fetch data from a known URL
2. Use a knowledge of the format of the page to cut it up and stick it back together.
3. Return the results to the client.


3
  This is taken from another document I wrote entitled Guerilla Web Part Development
4
  Why not use a stock ticker? I .We use flashing lights or waved flags for things you really need to see because
movement attracts the eye. Marquee text, tickers and animated GIFs are bad neighbors for other things on the
screen. An animation that runs once and stops can sometimes be forgiven but things that never stop merely
distract. The ticker model can work when we want a lot of information to go by, and we don't want it all at once.
But in dashboards we want a little information and we want all of it NOW. For small amounts of information
(e.g. a few stock prices) tickers need more pixels than static text


Sharepoint Portal Server Cookbook                                                             Page 49
These Web Parts use one of the two versions of XmlHTTP object; one, invoked as
“Microsoft.XmlHTTP”, is intended only for client use, and although it will work on a server,
it is not safe in a multi-user environment – which is the purpose of the other object – invoked
as “MSXml2.XmlHTTP”.

Here's a run down of the major parts of the object
Method/Property            Description
Object.open                Specifies
                           1. The method to be used such as Get or Post – or the DAV
                               extensions Propfind and Search.
                           2. The URL to send the request to
                           3. Whether the request should run asynchronously – i.e.
                               should execution wait for response or continue
                           4. (Optionally) logon credentials
Object.send                Sends the item. If the method is a POST (for example) Send
                           specifies the data to go the server
Object.responsetext or     Contains the response back from the server
Object.responseXML
Object.SetRequestHeader Specifies a header and its value, e.g.
                           Object.SetReponseHeader "Pragma","No-Cache"
Object.WaitForResponse Pauses until an asynchronous operation has finished

This code fragment will get the page held at theUrl and store it as one large string in
StrIResponse
set inet1 = createObject("MSXML2.ServerXmlhttp")
inet1.open "GET" , theUrl , false
inet1.send
StrIresponse = inet1.responsetext


Once the Web Part has fetched the page, it need to slice it up. The MSN investor page was
formatted like this




Figure 16 The MSN Investor page – actually it has changed since this screen shot , but it didn't break the
Web Part




Sharepoint Portal Server Cookbook                                                        Page 50
You can see that this is formatted as a table and the price I am interested in is next to a cell
marked containing the word last. So I‟ll do view source and look for “Last”
Reading down the html I get to this
<TABLE WIDTH=100% BORDER=1 CELLPADDING=1 CELLSPACING=0 RULES=ROWS FRAME=BELOW
BORDERCOLOR=#CCCC99 BORDERCOLORDARK=WHITE BGCOLOR=FFFFFF>
  <TR><TD>Last</TD><TD ALIGN=RIGHT NOWRAP><B>&nbsp;70.75</B></TD></TR>
  <TR><TD>Change</TD><TD ALIGN=RIGHT NOWRAP>&nbsp;+2.22</TD></TR>
  <TR><TD>% Change</TD><TD ALIGN=RIGHT NOWRAP>&nbsp;+3.24%</TD></TR>
  <TR><TD>Day's Low</TD><TD ALIGN=RIGHT NOWRAP>&nbsp;67.96</TD></TR>
  <TR><TD>Day's High</TD><TD ALIGN=RIGHT NOWRAP>&nbsp;71.05</TD></TR>
  <TR><TD>Volume</TD><TD ALIGN=RIGHT NOWRAP>&nbsp;59.78 Mil</TD></TR>
</TABLE>
<TABLE WIDTH=100% CELLPADDING=0 CELLSPACING=0><TR><TD></TD></TR></TABLE>
</td></tr><tr><td>
<span class=smallprompt>Last trade 05/04/01 04:00 PM ET</span>
etc etc


I can throw away everything before the first cell with “last” in it, and then trim a down to just
after the non-breaking space which follows it, and trim after the final span.
StrIresponse = mid(StrIresponse ,instr(StrIresponse , "<TR><TD>Last</TD>"))
StrIresponse = mid(StrIresponse ,instr(StrIresponse , "&nbsp;")+6)
StrIresponse = left(StrIresponse ,instr(StrIresponse , "</SPAN>"))


StrIRepsone now begins 70.75</B></TD></TR>. I use the part up to the </B> tag to start
building up my response.
StrStock= _
  "<a href=""http://moneycentral.msn.com/scripts/webquote.dll?iPage=qd&Symbol=" & _
  Strsymbol & """ target=_new><B>"& Strsymbol & "</a>&nbsp;" & _
  left(StrIresponse ,instr(StrIresponse, "</B>")-1)


Then I can repeat the process to get the % change and get some HTML like this.
<a href=http://moneycentral.msn.com/scripts/webquote.dll?iPage=qd&Symbol=MSFT"
target=_new><B>MSFT</B></a>&nbsp;70.75&nbsp;(+3.24%)


I pass a list of stocks to the Web Part as a parameter and use the VB function "Split" to turn it
into an array: to enable me to display a name instead of a symbol I check each symbol to see
if it is followed by description in square brackets, and process accordingly.

You can see how the whole thing goes together in the listing below.
Here is the Web Part file




Sharepoint Portal Server Cookbook                                                  Page 51
Code Fragment 34 The Stock quote Web Part
I create an array of XMLHTTP objects and send one to get each piece of data I need (the first
for…next loop): the controls run in asynchronous mode - that way I don't have to wait for the
first one to come back before sending the second and so on.
Once all the GET commands are in progress I then wait – until they are all complete (the
second for … next loop) and run through the processing cycle described above.

function GetContent(nod)
symbols = split( nod.SelectSingleNode("PartStorage").Text , ",") symbolCount =
ubound(symbols)
redim Symbolinet(SymbolCount+1)
for x = 0 to SymbolCount
       TheUrl = "http://investor.msn.com/scripts/webquote.dll?iPage=qq&Symbol="
       p = instr(symbols(x), "[")
       if p > 0 then
              TheUrl = TheUrl & left(symbols(x),p-1)
       else
              Theurl = TheUrl & symbols(x)
       end if
       set SymbolInet(x) = createObject("MSXML2.serverXmlHttp")
       SymbolInet(x).Open "GET", TheUrl , true
       SymbolInet(x).send
next

for x = 0 to symbolCount
       SymbolInet(x).WaitForResponse()
next

for x = lbound(symbols) to ubound(symbols)
    p = instr(symbols(x), "[")
    StrIresponse = SymbolInet(x).responsetext
    StrIresponse = mid(StrIresponse ,instr(StrIresponse , "<TR><TD>Last</TD>"))
    StrIresponse = left(StrIresponse ,instr(StrIresponse , "</SPAN>"))
    StrIresponse = mid(StrIresponse ,instr(StrIresponse , "&nbsp;")+6)
    StrStock = StrStock & _
                "<a href=""http://moneycentral.msn.com/scripts/webquote.dll"
     if p = 0 then
       strStock = StrStock & "?iPage=qd&Symbol=" & symbols(x)
       StrStock = StrSrock & """ target=_new><B>"& symbols(x) & "</B></a>"
    else
       Strsymb = mid(symbols(x),P+1)
       Strsymb = left(strSymb, Instr(strSymb,"]") -1)
       strStock = strStock & "?iPage=qd&Symbol=" & left(symbols(x),p-1)
       strStock = StrStcok & """ target=_new><B>" & strSymb & "</B></a>"
    end if

    strStock =  StrStock & _
               "&nbsp;"& left(StrIresponse ,instr(StrIresponse, "</B>")-1)
    StrIresponse = mid(StrIresponse ,instr(StrIresponse , "% Change"))
    StrIresponse = mid(StrIresponse ,instr(StrIresponse , "&nbsp;")+6)
    StrStock = StrStock & _
               "&nbsp;(" & left(StrIresponse ,instr(StrIresponse, "</TD>")-1) &") "
next

getcontent = strStock
end function




Sharepoint Portal Server Cookbook                                             Page 52
Working with the Digital Dashboard Services Component.
The DDSC provides four easy-to-use services for Web Parts:
    Discovery and Part State: code in a part can request an object representing the
      dashboard via DDSC.Dashboard(). Using this object – and particularly its Parts()
      collection – a part can learn which other parts are present on the dashboard and
      examine their properties
    Session State the DDSC object provides three functions to allow information to be
      held as the user navigates between dashboards.
    Notification: provides a mechanism for one Web Part to tell another that something
      has happened. This is often triggered by a user making a selection in one web part, and
      the result is that a second part does something.

I'm going to concentrate on notification here.

There is a stock demonstration of the DDSC which has two web parts - one has a list of three
car manufacturers and the other displays a photo of their cars. When the car is manufacturer is
chosen in the manufacturers part, an event fires via the DDSC to notify the photo display part,
which shows the right car. This is not exactly a real world use but its job is to provide
inspiration. For me the inspiration came via Microsoft's internal Sharepoint discussion list…
Someone made a simple request on the DL: could we replicate explorer functionality inside
the dashboard. I had three building blocks needed to do this
    1. A Web Part that would provide a folder tree , the one I describe in Moving away from
        ADO on page 21
    2. An ASP page (gotowf.asp) which would take a URL and display it in a web
        folder.(Used in the "View as web folder" part in Figure 1 on page 13)
    3. A variation on the car photographs demo to show a list of countries: when one was
        chosen that countries version of MSN opened in an in line frame (or iframe).-The
        display part of this would become the folder part of the explorer display

Starting with these, building the prototype meant
    1. Changing the Folders Tree Web Part, so that clicking on a folder did not navigate to
        the folder but raised a DDSC event.
    2. Changing of the "Local MSN" web part to invoke my ASP with the folder‟s URL and
        the display the result in the iframe.

And the result looked like this




Sharepoint Portal Server Cookbook                                              Page 53
Figure 17 The Explorer web parts
The first prototype hard coded the call to “gotoWf.asp” in the folder content pane, but then
someone else wanted to display the portal view in content frame. So I moved the control over
what was displayed to the calling web part – i.e. the folder tree part.

The logic remains simple enough. The folder content web part contains an <iframe> tag and a
one line subroutine to set the SRC parameter of the iframe to a value which is passed to it as a
parameter.
This part registers for notifications for a particular for an event named urn:UKmcs-microsoft-
com:Explorer:onSelect When another part wants something to be displayed in the iframe, all
it has to do is to fire the event and pass it the URL for the iframe to display.
Here is the full code for the Content web part:
Code Fragment 35 The Explorer content web part
<iframe frameborder="0" id=iframe_WPQ_ name="iframe_WPQ" style="width:100%;
height:400px;" src=""> Your browser does not support IFrames. </iframe>

<SCRIPT language="VBScript">
  DDSC.RegisterForEvent "urn:UKmcs-microsoft-com:Explorer", "onSelect", GetRef
("Navigate_WPQ_")

  Sub navigate_WPQ_ (ByVal param)
   document.all.iframe_WPQ_.src =       Param
  end sub
</SCRIPT>




Sharepoint Portal Server Cookbook                                               Page 54
To perform the navigation, the folder tree needs to raise the event and pass it a single
parameter. To make this easy I inserted a one-line function into the HTML: this function
simply raises the event - it is passed a parameter, and passes it on to the DDSC, which in turn
passes it on to any function which has been registered for the event

Function _WPQ_select(U)
DDSC.RaiseEvent "urn:UKmcs-microsoft-com:Explorer", "onSelect", _
                                 "http://ridcully/gotowf.asp?url=" & U
end function

The function is invoked from the links in the folder view web part, so these become
<A href=Vbscript:_WPQ_SELECT(
           "http://ridcully/workspace/Documents/Employee%20Handbook")>
Employee Handbook</A>



Since the folder tree web part is server side VBScript, which returns code to run on the client,
I saw a chance to make the final version of the part more flexible …Why hard code the
DDSC.RaiseEvent line? If I stored everything before the folder's URL in web part's “part
storage” field then a single part could be told to navigate the whole window to the folder URL
(as in the original web part) by just storing “window.navigate ” (with a trailing space) or I
could call DDSC.RaiseEvent with just the URL . So if I store
    DDSC.RaiseEvent "urn:UKmcs-microsoft-com:Explorer", "onSelect",
It navigates the to the dashboard view of the folder
Or I can store
DDSC.RaiseEvent "urn:UKmcs-microsoft-com:Explorer", "onSelect", _
                                 "http://ridcully/gotowf.asp?url=" &
This passes the URL of the folder to the   gotowf.asp   page which gives the web folder view.

There was one more thing I could do with parameters. The original folder web part and the
categories part it was based on , both took the root folder for their search as a parameters, but
they hard coded the folder types to look for and the icons to display. I changed the icons to
display to be calculated from folder‟s class, and moved where condition which selects the
folder types to be a parameter. This is the category tree version of the web part




The settings under Store the following data for this Web Part: are configured so it acts
as the original category tree part but with the following parameters it becomes the folder part.
/documents
("DAV:contentclass" = 'urn:content-classes:knowledgefolder' or "DAV:contentclass" =
'urn:content-classes:smartfolder')
Window.navigate


And with these, it functions as half of explorer
/documents
("DAV:contentclass" = 'urn:content-classes:knowledgefolder' or "DAV:contentclass" =
'urn:content-classes:smartfolder')
DDSC.RaiseEvent "urn:UKmcs-microsoft-com:Explorer", "onSelect",
"http://ridcully/gotowf.asp?url=" &




Sharepoint Portal Server Cookbook                                                Page 55
The subscribe Web Part, which you can also see in Figure 17, works in much the same way as
the folder content part above it. It is built by server side script to put the workspace name into
the part and the result looks like this
Code Fragment 36 The HTML in the subscribe to this folder DDSC part
<DIV ID=DIVWPQ1>&nbsp;</DIV>
<SCRIPT language="VBScript">
  DDSC.RegisterForEvent "urn:UKmcs-microsoft-com:Explorer", "onSelect", GetRef
("SetSubWPQ1")

  Sub SetsubWPQ1 (ByVal param)
    pos = instr(param, "url=")
    if pos > 0 then
      fldr = mid(param,pos + 4)
    else
      fldr = param
    end if
    document.all.DIVWPQ1.innerhtml = "<A
href=""http://ridcully/workspace/portal/resources/createSubscription.asp?Subscripti
onType=Folder&DataUrl=" & fldr & """ target=new>Subscribe to " & fldr & "</A>"
  end sub
</SCRIPT>


You can see the same logic at work, here as in The Explorer content web part on page 54, i.e.

    1. Register for the event.
    2. When it fires it calls code which sets the innerHTML of the DIV to be a link to one
       Sharepoint's management pages - this one creates a subscription.

You can see that the management page takes parameters for the type of subscription and the
URL of the folder. Although it is possible to tell this page where to return to, coming back to
this page would lose the folder we had navigated to, so I open the page in a new Window. On
completing the subscription, the user sees a list of all the subscriptions and can close the
window.
Most of the code in the sub is to cope with the fact the event may just refer to a folder URL or
may call gotoWf?url=folderURL so it looks for that and trims it off.

Here are the files for all three parts in explorer – the folder tree, the folder content and the
subscribe parts. If you use this remember to set the name of your server in part storage for the
folder tree. I have also included a simple example of DDSC notify in action – the country
demo allows you to load a part which lists countries in Europe, there are two other web parts
which will show weather for that country or its local MSN home page. Also included here is
a part I found on the Microsoft public site which demonstrates part discovery – note the way
this part registers for the On Load event – if it was the first part on the dashboard, when it ran
the other parts would not be the loaded, so it waits for DDSC to tell it that everything has
completed before it runs.




                .
If you have problems extracting the files, right click the icon and choose package object, then
edit package and save the file from Object Packager.



Sharepoint Portal Server Cookbook                                                 Page 56
Sharepoint Portal Server Cookbook   Page 57

				
DOCUMENT INFO
Description: Microsoft Word Cookbook Template document sample