"Politics is the art of looking for trouble, finding it whether it exists or not, diagnosing it incorrectly, and applying the wrong remedy." - Ernest Benn
Scott Valentine
Los Alamos, NM
USA
Michael A. Vickers
Portland, CT
USA
Thursday, February 14, 2008
Calculated Fields, XSL Sums, and Commas in SharePoint

I find that, programmatically, SharePoint is a witches brew of different technologies which work together as long as you don't need to do things your way.

In this instance I'm creating a list of Invoices which will serve as the parent item to a list of Invoice Items. Each Invoice Item row contains (among other fields) a field for quantity, the amount for each item, and the total. The total field is a calculated field, displaying the product of the amount and quantity fields.

Initially I had the total field set up as a number, which seemed to make sense considering I was working with an equation. Where it did not make any sense is when I put together a form which found all the Invoice Items for an Invoice and summed up the total field values to create an overall total for the Invoice.

In my initial tests everything actually ran fine until I tried putting in an amount of 1000 for a particular Invoice Item. Or, rather, put in an amount and quantity whose calculated total was 1000 or more. It turns out that by the time SharePoint does the calculation and hands off the value to the XSL for transformation, there is a comma in the value which the XSL sum function chokes on. I either received nothing on the web page or NaN, and I could not find a simple way to format the total field through the SharePoint interface.

I switched the total field to text format and that didn't help. I even switched the amount field to text and that didn't help. My original total field looked like this:

=Quantity*Amount

What I ended up doing after a mere 18 hours of research was keeping the amount field as a number, the total field as text, and using the following formula to calculate the total field:

=TEXT(VALUE(Quantity)*VALUE(Amount),"0.00")

The calls to VALUE are probably overkill, but in essence I told it to get the product and format it as a number with two decimal places. And, even though the total field is technically a text field, the sum function in the XSL operates on it properly.

This technique could also help out in a worst case scenario where you do an XSL calculation on a numeric field in SharePoint -- create a calculated field whose sole purpose is to transform the numeric field into a plain vanilla version.

Labels: ,


Tuesday, December 18, 2007
Lookup List DropDowns in SharePoint

For today's absurdity in SharePoint, I give you lookup list dropdown handling in the SharePoint UI.

Try this little exercise. Create three custom lists. In the third list create a lookup field connected to the title column of the first list. Create a second lookup field connected to the title column of the second list. Create 19 records in your first list, then create 20 records in your second list. Try putting "one, two, three, etc" in the title field for the records you are creating in these first two lists.

Now, create a record in the third list and notice what the dropdowns look like:

Notice how they are different? This is what it looks like when you click down on the first one vs. clicking down on the second one:

I'm guessing the thought process was that once the number of records in a list gets to be large (read: 20 or more) then the display of a lookup field based on that list is going to look unruly on the screen. It's actually not a bad looking dropdown or a bad idea, except it's a total PITA when trying to read/manipulate the contents of the control in JavaScript.

In a nutshell, the steps to programmatically select an item in the prettied-up dropdown through JavaScript is to emulate a click event on the down arrow image causing the list to appear, and then to emulate a double-click on the item you want. That's right, when you are selecting an item in this prettied-up dropdown through the UI, you have to double-click.

That's actually a simplified version of what you have to do. The in-between steps are so obnoxious between figuring out if the control has been changed, finding all the control pieces it's using and changing their states that I went about about finding a shortcut and present it to you here.

I've created a JavaScript wrapper object I call the SharePoint LookupDropDown. Here is the code:

var SP = function() {}
SP.LookupDropDown = function(cn) // cn = "cell name"
{
    var cll = ydom.getElementsBy(function(el) { return(el.id.indexOf(cn) > -1); }, "td")[0];
    var btn, fld, sel, IsFarged = false;
    this.Value;

    if (typeof(yelm) == "undefined")
        throw("Error in SP.LookupDropDown: \'yelm\' is missing.");
    if (typeof(cll) == "undefined")
        throw("Error in SP.LookupDropDown: invalid CellName specified."); else cll = new yelm(cll);
    if (cll.getElementsByTagName("input").length == 1) IsFarged = true;
    if (IsFarged)
    {
        btn = cll.getElementsByTagName("img")[0];
        fld = cll.getElementsByTagName("input")[0];
        this.Value = fld.value;
        try { btn.click(); } catch(x) { }; // trapping -- btn may be hidden
        sel = ydom.get(fld.opt);
        try { sel.style.display = "none"; } catch(x) { } // trapping -- control may be hidden
    }
    else
    {
        sel = cll.getElementsByTagName("select")[0];
        this.Value = sel.options[sel.selectedIndex].value;
    }
    this.GetSelectId = function()
    {
        return(sel.id);
    }
    this.GetValue = function()
    {
        return(this.Value);
    }
    this.SetValue = function(val)
    {
        this.Value = val;
        if (IsFarged)
        {
            try { ShowDropdown(fld.id); } catch(x) { }
                // ShowDropDown is a function baked into SharePoint;
                // will throw an error when the controls are hidden
            sel = ydom.get(fld.opt);
            for (var i = 0; i < sel.options.length; i++)
            {
               if (sel.options[i].value == val) sel.selectedIndex = i; continue;
            }
            try { OptLoseFocus(sel); } catch(x) { }
                // OptLoseFocus is a function baked into SharePoint;
                // will throw an error when the controls are hidden
        }
        else
        {
            for (var i = 0; i < sel.options.length; i++)
            {
               if (sel.options[i].value == val) { sel.options[i].selected = true; continue; }
            }
        }
    }
}

This function sits on top of a namespace I've created called "SP". You can also see that I have a couple objects called "ydom" and "yelm". These are friendly names to two components of the Yahoo User Interface Library (which I use a ton and highly recommend), YAHOO.util.Dom and YAHOO.util.Element. I've left them here because it saves me a ton of time from coding their equivalent functions, but if you want to code around it manually or substitute your own equivalent functions from your favorite library be my guest.

To use this script include it on your page where you have one of these prettied-up dropdowns. In SharePoint Designer (or your favorite html editor) find the cell/<td> element holding the lookup dropdown and give it an ID, such as "LookupCell". Make sure you haven't added any other controls into this cell, either. In your own JavaScript code pass in this ID when you instantiate an instance of this object:

var MyLookupDropDown = new SP.LookupDropDown("LookupCell");

I elected to instantiate the wrapper this way (by using the ID of the enclosing cell/<td> element) because the ID of the actual dropdown is somewhat random and can change. So now you'll be able to get/set the value of the dropdown by simply calling the GetValue or SetValue methods:

MyLookupDropDown.SetValue("blahblahblah");
var TheValue = MyLookupDropDown.GetValue();

There is another method in there called GetSelectId which I don't use anymore, but you may find it helpful. It returns the SharePoint-assigned ID of the core select control for the prettied-up dropdown.

Where might you use this? Well, I'll tell you where I've used it. I tend to assign lookup fields to the ID field of the source list, which means a lookup dropdown doesn't look too hot on a page unless you're some sort of savant who knows which item they want to select based on their ID number alone.

So, for a custom edit form I'll add the lookup field to the page as I normally would, but then hide the row containing it by adding a "style='display:none'" attribute in the <tr> element. I then assign an ID to the enclosing cell/<td> element holding this lookup field and instantiate the wrapper object using this cell ID. I call this the "source" dropdown.

Then I'll add an old fashioned select element to the page and populate it either through some onerous JavaScript or binding it to a data source. I call this the "surrogate" dropdown. I then attach an "onchange" attribute to the surrogate, which calls the SetValue method of the source. Now, when I save the record the correct information is being saved and the dropdown lists looks the way I want it to look.

A couple notes here:

  1. This dropdown list tweaking only occurs in IE.
  2. Yes, this wrapper object does detect whether or not the dropdown has been tweaked by SharePoint and sets the value of the"source" object accordingly.

Labels:


Wednesday, December 12, 2007
Deploying Linked Lists in MOSS 2007

This post contains a process for re-linking lists you've already deployed to SharePoint, which to your chagrin may have un-linked themselves from their previous state in your development environment. You can skip the pre-ramble and read the detailed version I've brewed up or the terse version.

One of the insanely stupid things about developing in SharePoint is how little thought there was given to the process of creating and maintaining discreet development, testing, and deployment environments. For myself, I like to have a development environment where I am currently changing the code (I use a VM for this these days), and test environment where I can push what I think is good code and let the customer hack away at it, and then the production environment where the code is in a "live" state.

For developing regular web sites in .Net this is fairly trivial. Just copy the files from place to place. SharePoint is a completely different beast. For one, most (if not all) of the files you will be working on are handled by http handlers built into the SharePoint namespace (no code-behind for you!). Second, those files are not tangible entities you can browse around for on the server hard disk. They are "hosted" inside the content database for the site you are working on.

That's the biggest WTF you run into when learning to develop in SharePoint. What I really want to grump on today is the process of deploying custom linked lists or document libraries from a dev/test environment to a production environment.

When you think of a list in SharePoint, you think of something akin to a table in a database with columns of different variable types. That's good from an understanding perspective.

All list data for a site in SharePoint is stored in one physical table in SQL Server, though, with an insane number of columns with generic names (nvarchar1, nvarchar2, ..., nvarchar64 and repeat a few more times with a couple other variable types) to store the list data. Further, in order to figure out which of those generic column names correspond to the field names you created through the SharePoint UI, you have to find the list definition in another table and pull the contents of one of the columns out into a text editor. The contents are in XML format. What an abomination.

Where this abomination starts ruining your day is when you have a handful of lists that are linked to each other via a lookup field you've created through the SharePoint environment. When you get ready to push these lists to another server you typically save the lists as templates (with the content included), download the templates to your local machine, upload the templates to the production machine, and then create new lists based on those templates.

Because of the way SharePoint stores the list data it depends on a system of identifying lists with GUIDs, and when you create a new list in SharePoint based on a template you do not get the same GUID identified with the new list as you had on your dev/test environment. What that ultimately means is that any lookup fields you had on a list are hosed -- they point to nowhere.

Todd Bleeker, who has multiple black belts in SharePoint development and administration and who is well known in the SharePoint community, has a process for dealing with this. It involves cracking open the template prior to uploading to the production server and editing the one of the files contained within. It works, but involves uploading each list one at a time, finding out the generated GUID for that list, cracking open the next template, editing the file inside, reassembling the template, uploading, etc.

I've come up with an alternate way of re-linking your lists together after you've uploaded all of them to your production server. It involves opening SQL Studio or Query Analyzer and editing the column holding the field definition for your just-created lists. This alternate way also assumes that you are deploying your new list/library set to the same site. If not, you'll have to find another way yourself.

A caveat here -- everybody in the SharePoint community will tell you not to ever edit any of the data in a SharePoint content database outside of the SharePoint UI. Actually, I agree with that sentiment but it was 3 in the morning when I came up with this scheme and I liked it better than the Bleeker method. So, do this at your own risk and only if you are comfortable with using Microsoft's SQL Server tools and writing SQL. If you hose your content database, I don't know you but I do hope you have good backups.

So, create your templates, upload them to your production server, and create your list/document libraries from them. Then, crack open SQL Studio or Query Analyzer, open a query window to your content database and follow along.

The Detailed, Getting-Paid-By-The-Hour-To-Type-This Version

  1. You need to find your custom list and the field definitions for it in the content database. To do this run this piece of SQL:

    select
        Webs.Id WebId,
        Webs.SiteId,
        Webs.FullUrl SiteUrl,
        Lists.tp_ID ListId,
        Lists.tp_Title ListName,
        Lists.tp_Fields FieldDefs
    from
        Webs
        left join AllLists Lists on Lists.tp_WebId = Webs.Id
    where
        Webs.FullUrl = '[ your relative site url ]' and
        ((Lists.tp_Title = '[ list name 1 ]' and Lists.tp_DeleteTransactionId = 0x) or
         (Lists.tp_Title = '[ list name 2 ]' and Lists.tp_DeleteTransactionId = 0x) or
         (Lists.tp_Title = '[ list name 3 ]' and Lists.tp_DeleteTransactionId = 0x))

    What this SQL does is find the exact lists/libraries you've just created in the content database. You need some pieces of information here -- the relative url to the site where your list is located, and the name you've given to each of your new lists/libraries. These are put into the where clause.

    The first line of the where clause references the relative url to the site where your lists have been deployed. Your relative URL should look something like "sites/TheSite" if you've create a site collection at /sites, or it could look something like "thesite" if your site collection is at the root level of the site.

    The second, third and fourth lines of the where clause finds your lists/libraries based on the names you gave them. I've included three lines here, but you'll want to include a line for each list/library, whether it's referencing another list/library or is being referenced itself. You'll notice here that the where clause on each of these lines filters using the field "tp_DeleteTransactionId." This is because if you create a list/library in the past with the same name, SharePoint doesn't actually delete it's row from the database (at least, not right away). If you have created a list with the same name in the past but deleted it, this additional filter makes sure you are grabbing the list that has not been deleted.

    This query should return exactly one row for each list/library you've specified. If not, abort and try again.
  2. Next we're going to put the field definition info into something we can easily manipulate. For this you are going to need your favorite text editor. In your query results, find the list which needs to have it's external references fixed, copy the contents of the "FieldDefs" column to the clipboard, and paste it into your text editor. It would not be a bad idea to save a copy of what you pasted into your text editor as a backup somewhere should you need to restore the column content to its original state.

  3. Now we're going to find the lookup fields which need adjusting. What you'll get in your text editor is something that starts off resembling this:

    12.0.0.4518.0.0<FieldRef Name="ContentTypeId"/>
    <FieldRef Name="_ModerationComments" ColName="ntext1"/>
    <FieldRef Name="FileLeafRef"/>
    <FieldRef Name="Modified_x0020_By" ColName="nvarchar1"/>
    <FieldRef Name="Created_x0020_By" ColName="nvarchar2"/>
    <FieldRef Name="File_x0020_Type" ColName="nvarchar3"/>

    [ ... so on and so forth ... ]

    I'm sorry, but you'll have to ignore that first bit of creative Microsoft xml mangling at the very beginning (I think it's a version number they stick in there). What you're looking for in this xml are the definitions for your lookup field(s). You can do this in your text editor by doing a search for the word "lookup." You should find an xml node that looks something like this:

    <Field
       Type="Lookup"
       DisplayName="[ display name ]"
       Required="FALSE"
       List="[ a guid string ]"
       ShowField="ID"
       UnlimitedLengthInDocumentLibrary="FALSE"
       ID="{9f437a3e-5507-41e9-ab68-36beb3bd0822}"
       SourceID="{f3296739-dd99-4fd5-b756-020c359d9fbb}"
       StaticName="[ static name ]"
       Name="[ field name ]"
       ColName="int2" RowOrdinal="0" Group="" Version="4"
       WebId="[ a guid string ]"
    />

    I've replaced some attribute values of this node with placeholders, but you'll see that some of the attributes contain the name you've given to the lookup field. Of particular interest here are two attributes containing GUID values -- "List" and "WebId." These help SharePoint define the list being referenced to by this lookup field.
  4. Next we'll replace the GUID values in the xml with the proper ones from the database Go back to the query you ran and find the list that this lookup value is referencing. I'm assuming you know which list this should be, and that it was included in the where clause of the SQL statement you ran. When you find the list in the query results, copy the contents of the "ListId" column and paste it over the contents of the "List" attribute value in the xml node you are currently editing.

    Once you've done that, repeat this process with the WebId attribute by copying the contents of the WebId column in the query results and pasting it over the contents of the WebId attribute value in the xml node1.

  5. Repeat steps 3 and 4 for the other lookup fields in your list, if there are any more.

  6. Now we're going to update the field definition for this list in the content database. Go back to your query editor and stamp out this piece of SQL in a new window pointing at the same content database:

    update Lists set tp_Fields = '[ FieldDefs ]' where Lists.tp_Id = '[ ListId ]'

    Replace the "[ ListId ]" placeholder with the contents of the ListId column (a GUID value) of the list item you are editing in the original query.

    Go to your text editor and copy the entire xml text to the clipboard, then come back to your query editor and replace the "[ FieldDefs ]" placeholder with the contents of the clipboard. It's going to look real ugly, but don't fret it. Just make sure the contents of the clipboard are between the apostrophes.

    Run this SQL, and you should get 1 row update.

  7. You can now go in through the SharePoint UI and examine the lookup field definition for the list. The "Get Information From:" should read with the correct list name, whereas before it was blank.

  8. Repeat steps 2 - 7 for your other lists containing lookup fields.

This seems pretty complicated, but I was intentionally long-winded so that you'll understand what's going on with each step. The less verbose version of this process is here in the event that you don't want to weed through the explanations.

The Terse, Would-You-Please-Get-To-The-Point-Already Version

  1. Open Query Analyzer or SQL Studio and run this piece of SQL:

    select
        Webs.Id WebId,
        Webs.SiteId,
        Webs.FullUrl SiteUrl,
        Lists.tp_ID ListId,
        Lists.tp_Title ListName,
        Lists.tp_Fields FieldDefs
    from
        Webs
        left join AllLists Lists on Lists.tp_WebId = Webs.Id
    where
        Webs.FullUrl = '[ your relative site url ]' and
        ((Lists.tp_Title = '[ list name 1 ]' and Lists.tp_DeleteTransactionId = 0x) or
         (Lists.tp_Title = '[ list name 2 ]' and Lists.tp_DeleteTransactionId = 0x) or
         (Lists.tp_Title = '[ list name 3 ]' and Lists.tp_DeleteTransactionId = 0x))

    Make sure to a) replace the placeholders as appropriate and b) add/subtract lines from the where clause as appropriate for each created list which is referencing another list or is being referenced.
  2. Find the first list in the query results which contains lookup fields and copy the contents of the FieldDefs field to the clipboard and paste into a text editor.

  3. Find the first lookup field which needs to be changed. Do a search on the word "Lookup" and you should find a node which looks like this:

    <Field
       Type="Lookup"
       DisplayName="[ display name ]"
       Required="FALSE"
       List="[ a guid string ]"
       ShowField="ID"
       UnlimitedLengthInDocumentLibrary="FALSE"
       ID="{9f437a3e-5507-41e9-ab68-36beb3bd0822}"
       SourceID="{f3296739-dd99-4fd5-b756-020c359d9fbb}"
       StaticName="[ static name ]"
       Name="[ field name ]"
       ColName="int2" RowOrdinal="0" Group="" Version="4"
       WebId="[ a guid string ]"
    />

  4. Go back to the query and find the list that is being referenced in this xml node. Copy the contents of the ListId column (should be a GUID value) and paste it over the existing value in the "List" attribute.

    Repeat this process using the "WebId" column value from the query and pasting it over the "WebId" attribute value in the xml node.

  5. Repeat steps 3 and 4 for the other lookup values contained in the xml.

  6. Go back to your query editor and open a new window to the same content database:

    update Lists set tp_Fields = '[ FieldDefs ]' where Lists.tp_Id = '[ ListId ]'

    Replace the "[ ListId ]" placeholder with the contents of the ListId column (a GUID value) of the list item you are editing in the original query. Replace the "[ FieldDefs ]" placeholder with the xml from your text editor. Run the SQL and you should get 1 row updated.

  7. Examine the just-editing field through the SharePoint UI. The "Get Information From" should now read with the correct list reference rather than being blank.

  8. Repeat steps 2 - 7 for the other lists containing lookup fields.


Updates, Post-Scripts and What-Not

  1. I'm finding that there are times when the WebId attribute is missing in the XML. Skipping this seems to still glue the lookup field properly as long as you paste the correct ListId in.

Labels:


Thursday, November 29, 2007
Note To Self: SharePoint Queries

I've just spent the last, uh, 6 hours trying to figure out why a query I've been writing in SharePoint would not filter the returned data properly. It would always return more data than I was anticipating. Consider the following snippet:

sopv = "{" + soplib.Views["WS_Browse"].ID.ToString().ToUpper() + "}";
sopq = new CamlQuery(CAML.Where(yaddi yaddi yadda)).CreateQuery();
sopmtch = soplib.GetItems(sopq, sopv);

In the first line I'm fetching a friendly handle to the name of a view I've built in a document library. I had constructed the view inside SharePoint and specified the columns I wanted returned as well as including a filter so it would weed some of the gunk out before my own filter ran over it in the query.


In the second line I'm using John Holliday's CAML.net to build the query xml, and in the last line I'm running the query, specifying the query object and the view name in the method call.


For the life of me I couldn't figure out why in the Sam Hill it was returning more items than I was expecting. It wasn't returning everything, but just one or two more than I was expecting. And the one or two extra were definitely outside the range I was yaddi yaddi yadda'ing about.


So my next step was to crack open U2U's CAML Query Builder application and reconstruct the query in what I knew was a working a environment. Voila, the query worked perfectly. And theeeeeen, I outputted the actual query xml from my SPQuery object (sopq) and compared it to the query xml generated by U2U's tool. Identical!


At this point I was really getting creeped out, thinking that there was some bug in using the SharePoint object model to run queries vs. using the SharePoint web services (which is what I was doing with the U2U tool). As a last ditch effort I removed the view specification from the GetItems method. BINGO.


So, you know what? I learned something today! If you specify a view to use in your query the SharePoint query processor happily ignores the CAML you've whipped up. Not very nice considering the effort it takes to to put one together that doesn't get stuck in SharePoint's throat.


And while I have your attention, I'd like to make the following public service announcements:



  1. John Holliday's CAML.Net is a great shortcut for creating CAML, although even a somewhat simple query can lose you if you put it all on one line. I'll have to check out his Visual Studio plugin.

  2. The U2U CAML Query Builder rocks. I would actually use it more often and just store the raw, generated queries in text files, but I wanted the flexibility of making queries up on the fly. At the very least it's a good debugging tool.

  3. The existence of CAML is hella-silly. I mean, it's just dreadful. XML and SQL, two not-so-great tastes that taste like [ expletive deleted ] together.

  4. Programming data operations in SharePoint, in general, is an abomination. In fact, at the end of a day of programming data operations in SharePoint, I can't wait to stop and ram shards of glass in my eyes. For you DBA types, just consider writing queries as I have described in the previous point. Now consider doing that plus doing data joins manually. Hallelujah... where's the Tylenol!

Labels:


Thursday, July 26, 2007
MOSS Install Issue on Windows Server w/SP2
Due to the persistence of my cranial memory being more of the RAM than ROM variety these days, I'm regurgitating this post from SharePoint blogs (I had it starred in Google Reader) because the original post was destroyed in some sort of crash. If I run into this issue again hopefully I will have the presence of mind to search my bookmarks for this post.



MOSS Install Issue on Windows Server w/sp2
by gmoser

I did a MOSS install this week on a brand new box with Windows Server sp2 installed and ran into an unexpected and rather frustrating problem. If this post saves someone else the 5 or 6 hours I lost on it I'll be happy.

So I show up at the client site to instal MOSS on the freshly built box that one of our engineers built for me. The first thing I did was install IIS and Asp.net from Add/Remove Programs as per normal with any SharePoint install. During the IIS install I was prompted for an IIS file in the sp2 i386 folder which I surfed to on the file system and installed. All seemed well.

Next I install SQL Standard, which will run on the same server for this relatively small company. I proceed to install MOSS using a domain account set up on their SBS Active Directory. Nothing particulary unusual happens as I go through the install and Configuration Wizard. But, when I click the link to bring up the SharePoint Admin site I am prompted for a logon. This is unusual. When I enter my process account credentials for the Admin site, I am repeatedly prompted, whether I click OK or Cancel. I cancel a few more times and eventually get to the Admin site home page.

Long story longer, just about anything I mouse over or click on in the Admin stie kicks up multiple logon prompts, which I can cancel and eventually get to the requested page. Javascipt errors in i.e. indicate missing objects and bad syntax in the code. Odd, and a major pain. I am allowed access to the site and page, but seem to not have access via IIS to some images, css or other resources behind the scenes.

I check the various WSS, SQL and Event logs, but there is nother there. I go thourgh the IIS an file system permissions and can find nothing missing. I am suspicious of sp2 so I uninstall it, but that does not solve the problem (remember this part). I uninstall and reinstall MOSS and WSS a number of different ways, using local accounts instead of domain accounts etc, but I get the same logon behavior regardless of the configuration.

After a wasted afternoon of this I give up the next day and call Microsoft Support and spend a couple of hours on the phone trying many of the same things I had tried on my own the day before, plus a number of other tools Support had me download. No clear answer presents itself.

Finally someone in the room with my support guy says he had a similiar issue they traced back to Windows Server sp2. Turns out that installing IIS on a server that already has sp2 installed can cause a problem becuase the dlls from RTM/sp1 are not compatible with sp2. Now in my case I was prompted to install the sp2 IIS components, but that did not seem to help me. Support (after suggesting we may have to completely reimage the box, which did not make me happy) suggested we try reinstalling sp2 to see if that helps. So I spend 10 minutes doing that (I had already downloaded it), do an IISRESET, and voila, logon prompts are gone, and IIS and MOSS seem to be happy. Today I configured my SSP and created a default site and things are looking good.

If I had only tried reinstalling sp2 early in the process, I would have saved myself a few hours and a call to PSS. But you never know what is going to do it ... you just keep trying what seems to make sense and hope to get lucky.

Okay, this got longer than I expected. To summarize:

Problem: After installing MOSS/WSS on a new server that had sp2 installed (but not IIS), you get inexplicable login prompts in the Admin site.

Apparent fix: Uninstall Windows Server sp2 and reinstall it.

Labels:


Friday, June 15, 2007
Not-So-Global Navigation In SharePoint
This is another fabulous adventure in nerdville. I'm posting this mostly for my own edification, so the three people who read this blog and are not finding themselves here via the General Lee, feel free to move along.

I am still getting my head wrapped around the paradigm that is SharePoint. From one perspective, it's a great platform to starts or build your intranet on if you have a lot of money (I call it "intranet-in-a-box") and not a tremendous amount of know-how. You can start creating portals and sites in no time at all, create rudimentary data models to hold information, set permissions on everything, and then start pumping information into it.

From the other perspective (that would be mine, or if I may be so presumptuous to call my perspective "the developer at large") it can be a complete PITA. I'm finding that even the slightest modification to the system which can not be handled via the web-based tools or even the new SharePoint Designer software requires time, patience, and a deep swear jar.

The latest incident involves trying to use the same global navigation bar in the "MySite" portal as the parent portal. I would think this would be a fairly trivial task, but not really. Actually, it kind of is after googling the entire Internet and finally finding a solution (it's located in the comments).

Of course, the solution didn't quite work as-is, so I'll regurgitate.

In the masterpage of the offending MySite portal there is a "SharePoint:AspMenu" tag, which is where all the action is at. There are at least a couple, so find the one specifically with the "TopNavigationMenu" ID. Mine looks something like this:
<SharePoint:AspMenu
ID="TopNavigationMenu"
Runat="server"
DataSourceID="topSiteMap"
EnableViewState="false"
blah blah blah />
Note the DataSourceID. This tells the control where to get the info it needs (namely the titles on the menu and the links they go to).

This object containing the DataSourceID is located a little further down the page:
<asp:SiteMapDataSource
ShowStartingNode="False"
SiteMapProvider="SPNavigationProvider"
id="topSiteMap"
runat="server"
StartingNodeUrl="sid:1002"/>
OK, now we're getting somewhere. According to the aforementioned post and comment, this little bit of code provides a contextual data source for the menu at run-time. The way to force it to use a data source of my choosing (there are a handful of them defined in the web.config for the application) is to wipe that piece of code out altogether and replace it with something resembling the following:

<PublishingNavigation:PortalSiteMapDataSource
ID="topSiteMap"
Runat="server"
SiteMapProvider="CombinedNavSiteMapProvider"
StartFromCurrentNode="true"
ShowStartingNode="false"
TreatStartingNodeAsCurrent="true"
TrimNonCurrentTypes="Heading"
/>
And for this tag to be recognized on the page there must be an accompanying registration for it at the top:
<%@ Register Tagprefix="PublishingNavigation"
Namespace="Microsoft.SharePoint.Publishing.Navigation"
Assembly="Microsoft.SharePoint.Publishing, Version=12.0.0.0,
Culture=neutral, PublicKeyToken=94de0004b6e3fcc5" %>
So far, so good, except after doing this I received the following error message on the page:
Could not load file or assembly 'Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=94de0004b6e3fcc5' or one of its dependencies. The system cannot find the file specified
Huh?

Turns out the problem here is simple -- the "PublicKeyToken" didn't seem to match correctly for me as quoted in the post. Along side that registration I put in were other registrations which had matching PublicKeyToken values. I simply copied that value over and voila, it worked.

So far the only menu I can get it to display is the custom one you create through the process of customizing the site navigation for the specific MySite portal. I can't figure out how to set the site navigation to the same one as the parent portal, but as long as I have some control over it for the time being I'm fine with having to set the navigation in two different areas.

Labels: ,






People We Know


People We Keep Up With


Categories of Interest


Ye Olde Archives

December 2004
January 2005
February 2005
March 2005
April 2005
May 2005
June 2005
July 2005
August 2005
September 2005
October 2005
November 2005
December 2005
January 2006
February 2006
March 2006
April 2006
May 2006
June 2006
July 2006
August 2006
September 2006
October 2006
November 2006
December 2006
January 2007
February 2007
March 2007
April 2007
May 2007
June 2007
July 2007
August 2007
September 2007
October 2007
November 2007
December 2007
January 2008
February 2008
March 2008
April 2008

Useful Stuff


www.flickr.com
This is a Flickr badge showing public photos from Michael A. Vickers.

Subscribe to Idiotsyncrasies RSS Feed