<CharlieDigital/> Programming, Politics, and uhh…pineapples

30May/11Off

Bumblebees

Posted by Charles Chen

One of my favorite things about summer is watching the bumblebees at work on my Salvias — they love the stuff.

18May/11Off

Introducing GameTime – Real Time Collaboration for SharePoint

Posted by Charles Chen

Background

Since 2005, I've been thinking about building a web-based, real-time collaboration solution.  Back in those days, I had just discovered AJAX.NET (before ASP.NET provided an implementation of AJAX) and I had drawn up a design for a chat-centric collaboration platform.  My friend and co-worker Dan Chawner would sit in adjacent cubicles and exchange IMs over MSN Messenger as we worked on projects.  I thought: "wouldn't it be great if I could actually do things with these IMs instead of copying/pasting them?"

Over the years, that design languished as I moved on to other interests.  When I first saw Groove (before Microsoft purchased it) and Wave, it brought back memories of those designs.  Wave, in particular, was very close to what I had imagined building (albeit without all of the crazy in-line edits and what not).  I had kind of given up the idea after not being able to find any direction myself on how to make such a tool useful.

Experience

It turns out what I needed was more experience -- both technically and professionally -- to finally put it all together.  One thing that I've learned in the last few years of working with SharePoint is that it's generally a really cumbersome platform for collaboration when left to it's own devices.  It's great for:

  1. Storing documents
  2. Finding documents you've stored
  3. Storing lists of things
  4. ???

Everything else?  I guess it's kind of mediocre.

And yet, organizations -- multi-billion dollar organizations -- depend on SharePoint as a platform for collaboration, communication, sharing information, and in general, getting things done.

This is what experience has taught me as I sat through scrums watching folks update list items, as I dealt with the deluge of emails sent "Reply All" trying to figure out the status of tasks, and as I dealt with communicating effectively as a part of a team of remote consultants.

There are real inefficiencies when you try to use out-of-the-box SharePoint for scenarios which it was not designed and it's not a terribly useful platform for collaboration so much as it is for storage and retrieval of information (and even some would debate how well it's designed for those purposes....).

The question we set out to answer is how can we make the SharePoint platform more efficient for collaboration?  How can we help teams that work with remote members collaborate and communicate effectively?  How can we make SharePoint more than just a document and information repository?  How can we enable SharePoint to deliver notifications and updates in real-time?

Opportunity

Right before Christmas, my wife was put on strict bed rest at home carrying our daughter, Charlotte (she was deemed a high risk pregnancy as we've lost three other fetuses in two prior pregnancies).  At first, I considered taking the 6 week unpaid family leave.  But our due date was at the end of April; that would hardly get me through February with my vacation days.  I knew I had to quit and tough it out for at least these 4 months to make sure that we carried this baby to term.

This is when I finally put two-and-two together: I had to use this one opportunity to take a risk, go all-in and try to manifest this idea that I've been carrying around with me for years.

The Result

We've been "dogfooding" it for over a month now!

What came out of this process is GameTime, a real-time collaboration solution built on SharePoint and the same underlying technology in Google Wave, XMPP.  In one sentence?  It's Campfire for SharePoint.

At the core of GameTime is the concept of a "Huddle" where team members come together around a web-based chat interface.  But it's more than that; we've integrated it with SharePoint document libraries and lists to create a context for real-time collaborative efforts right in SharePoint.  Each Huddle is composed of collaborators, documents, milestones, and tasks -- the essentials of any collaborative effort and it's all wired up to react in real-time.

When a document is checked out in SharePoint, a real-time notification shows up in the chat stream and the document is updated in the Huddle.  When a new task is created and assigned, a real-time notification shows up in the chat stream and the task is added to the Huddle.  When a user comes into the Huddle, a real-time presence notification is sent and the user's status is updated immediately in the Huddle.

GameTime finally gives SharePoint users an actual reason to be in the SharePoint environment outside of point interactions (for example: trying to find a document); it gives SharePoint a central role in day-to-day collaboration instead of being just a storage repository that is called upon once in a while.  But even more importantly, perhaps, is that it adds a real-time element to SharePoint.  No more waiting for email notifications.  No more playing email-tag to get the status of tasks.  No more waiting for someone to check documents in/out.  You can see SharePoint activity in real-time right from your Huddle.

This short demo video should give you an idea of the functionality and capabilities of the product (this video represents about 60% of the current functionality):

Now the Hard Part

It's taken the small team of John Peterson (and his alter ego "Tyrone Engels") and myself nearly 4 months of work to get GameTime to this point and just this week, we've started our first AdWords campaign -- a great milestone.  The challenge of spreading the word and getting our first sale is now before us so indulge me with this shameless plug!

If your organization runs SharePoint 2010 (Server or Foundation) and you're interested in trying out that real-timey goodness of GameTime, fill out our contact form and get your first 10 licenses, free.  You can also use the form to schedule a live demo in our hosted environment.  I truly believe that you'll be sold once you experience it, live.

The Future

While we're focused on getting our first sale, we've started to plan for upcoming tradeshows and we've started to develop our next set of features.  These include:

  • Higher level, real-time dashboards built off of the same platform
  • Mobile integration for Android, Blackberries, iPhones, and Windows Phone
  • Chat and real-time notifications everywhere in the SharePoint environment -- get immediate notification of changes anywhere you are SharePoint.

So head over to our web site: http://thinktastic.com and contact us to get a fully featured trial license!

Filed under: News, SharePoint, XMPP No Comments
11May/11Off

When Life Gives You Lemons…

Posted by Charles Chen

I love this photograph from the The Big Picture blog:

The caption for this photograph:

Cyril Forck, 90, catches a small perch fish from his backyard deck, which is usually 50 feet away from the edge of the Mississippi River, on Mud Island in Memphis, Tenn. May 4. (Lance Murphey/AP)

The flooding, loss of lives, destruction of homes and livelihoods is certainly terrible, but I guess it's important to keep your spirits about you!

11May/11Off

Disability, Morality, and Family

Posted by Charles Chen

As a newly minted father, I found an interview with Ian Brown, regarding his son's cardiofaciocutaneous syndrome, fascinating from a variety of angles.

When he was 8 months old, Walker Brown was diagnosed with cardiofaciocutaneous syndrome (CFC), a rare disorder that left him with severe cognitive, developmental and physical disabilities. By the time he was 3 years old, his father says, his medical chart was 10 pages long.

Now 15, Walker wears diapers and an apparatus on his wrists that prevents him from hitting and scratching himself. Developmentally, his age is between 1 and 3, and he will require constant care for the rest of his life.

"He can't speak," his father, Ian Brown, tells Fresh Air's Terry Gross. "He can't do a lot of things — he can't swallow, so he's fed through a tube. We don't know how well he sees or hears. We know he sees and we know he hears, and I think it might be getting a bit better, but because he can't talk, he just has no way of rationally communicating — so we spent a long time trying to figure out other ways to connect."

Brown has spent years trying to learn about his son's condition, a rare genetic mutation that affects only 300 people in the world. He writes about his journey raising Walker — and his mission to find the answers to both medical and philosophical questions — in his new memoir, The Boy in the Moon.

A very deep, emotional, and thought-provoking interview -- especially for parents.

3May/11Off

SharePoint Content Type Lifecycle Management

Posted by Charles Chen

That's just a fancy term for updating content types via feature upgrades.

In SharePoint 2010, there's a new UpgradeActions element and a handy new AddContentTypeField element as well.

There are a couple of good blog posts that discuss this topic and an unusually useful MSDN article on the topic of application lifecycle management in SharePoint 2010.  These more than cover the topic of adding new fields to existing content types via upgrades.

One key point from this article is the following section of text:

This section describes at a high level how you can put these feature-versioning and upgrading capabilities to work. When you create a new version of a feature that is already deployed on a large SharePoint 2010 farm, you must consider two different scenarios: what happens when the feature is activated on a new site and what happens on sites where the feature already exists. When you add new content to the feature, you must first update all of the existing definitions and include instructions for upgrading the feature where it is already deployed.

For example, perhaps you have developed a content type to which you must add a custom site column named City. You do this in the following way:

  1. Add a new element file to the feature. This element file defines the new site column and modifies the Feature.xml file to include the element file.
  2. Update the existing definition of the content type in the existing feature element file. This update will apply to all sites where the feature is newly deployed and activated.
  3. Define the required upgrade actions for the existing sites. In this case, you must ensure that the newly added element file for the additional site column is deployed and that the new site column is associated with the existing content types. To achieve these two objectives, you add the ApplyFeatureManifests and the AddContentTypeField upgrade actions to your Feature.xml file.

It would seem that this would be a great mechanism for managing your metadata upgrades.  As bullet number 2 implies, one simply adds the new field to your existing element manifest file for new installs and add a new element manifest file plus a AddContentTypeField element to your feature XML for upgrades.

After trying this out for almost two days now, I can say with some certainty that this does not work as documented or designed.  You cannot add the field to both your original element manifest XML (for new installs) and to a separate element manifest file for upgrades.  It does not work.  After performing an upgrade, the symptom is that the field does not get pushed down to the list level content types that inherit from the site level content type.  If you go to add the field manually, you will see the field appear twice:

You can see the result of two passes of testing here.  At the site level, the content type is fine and dandy; it's only at the list level where things go wrong.  Obviously, this is less than ideal.  I tried numerous variations of feature setup to see if I could get it to work without code and none of them worked.

Ultimately, the solution that worked for me was to add a CustomUpgradeAction as well in addition to the AddContentTypeField.

The upgrade action has the following code in it:

// Delete the field link from the content type at the site level.
contentType.FieldLinks.Delete(fieldId);

contentType.Update(true);

// Add it again and force it to push down.
contentType.FieldLinks.Add(fieldLink);

contentType.Update(true);

It's a lot of redundancy, but ultimately, the only way I could get the upgrade to work with the field in both the original element manifest (desired for new installs) and in a new element manifest for upgrades.  I should note that if you do NOT add the field to the original element manifest, everything works as documented.  But this is a less desirable scenario as it means that every clean install would also require all of the updates to be run afterwards.

Here is the full listing

My feature.xml file:

<?xml version="1.0" encoding="utf-8"?>

<Feature Id="0C4C1210-B056-4AFC-B556-A8BB30E1A9F1"
         Title="Zaang Labs GameTime Content Types"
         Description="Installs the content types used by Zaang Labs GameTime."
         Hidden="FALSE"
         Scope="Site"
         DefaultResourceFile="core"
         ReceiverAssembly="ZaangLabs.Server.Framework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4df39f66bb4e1d17"
         ReceiverClass="ZaangLabs.Server.Framework.Components.FeatureReceivers.MetadataPrimer"
         Version="1.0.0.1"
         xmlns="http://schemas.microsoft.com/sharepoint/">
    <ElementManifests>
        <ElementManifest Location="zl_gametime_contenttypes.xml" />
    </ElementManifests>
    <Properties>
        <Property Key="zaanglabs.prime.if.contains" Value="ZaangLabs,Zaang Labs"/>
        <Property Key="zaanglabs.mark.for.removal" Value="GameTime Configuration,GameTime Huddles"/>
        <Property Key="zaanglabs.config.assembly" Value="ZaangLabs.GameTime.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4df39f66bb4e1d17"/>
        <Property Key="zaanglabs.config.resource.path" Value="ZaangLabs.GameTime.Core.zaanglabs.custom.web.config.xml"/>
    </Properties>
    <UpgradeActions
        ReceiverAssembly="ZaangLabs.Server.Framework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4df39f66bb4e1d17"
        ReceiverClass="ZaangLabs.Server.Framework.Components.FeatureReceivers.ContentTypeUpdater">
        <VersionRange BeginVersion="1.0.0.0" EndVersion="1.0.0.1">
            <ApplyElementManifests>
                <ElementManifest Location="zl_upgrade_1_0_0_1.xml"/>
            </ApplyElementManifests>
            <AddContentTypeField ContentTypeId="0x01002167C000000000000000000000000002" FieldId="{0bc82141-5fa4-4948-b7b9-888f10010020}" PushDown="TRUE" />
            <CustomUpgradeAction Name="AddFields">
                <Parameters>
                    <Parameter Name="add.field.1">0x01002167C000000000000000000000000002,{0bc82141-5fa4-4948-b7b9-888f10010020}</Parameter>
                </Parameters>
            </CustomUpgradeAction>
        </VersionRange>
    </UpgradeActions>
</Feature>

A snippet of the original element manifest with the new field added to the content type for new deployments:

<!-- Other fields omitted -->
<Field DisplayName="Share Milestones With"
    Name="Share_Milestones_With"
    StaticName="Share_Milestones_With"
    ID="{0bc82141-5fa4-4948-b7b9-888f10010020}"
    Description="Leave this blank to use a new set of milestones for this Huddle.  Otherwise, select an existing Huddle to share Milestones with."
    Type="Lookup"
    ShowField="Title"
    List="Self"
    SourceID="schema.zaanglabs"
    Group="Zaang Labs" />
<ContentType Name="Huddle"
    ID="0x01002167C000000000000000000000000002"
    Description="Zaang Labs Huddle."
    Group="Zaang Labs">
    <FieldRefs>
        <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000010}" Name="Huddle_Description" Required="TRUE" />
        <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000011}" Name="Huddle_Collaborators" Required="TRUE" />
        <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000012}" Name="Huddle_Documents" Required="FALSE" ShowInNewForm="FALSE" ShowInEditForm="FALSE"/>
        <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000013}" Name="Huddle_Milestones" Required="FALSE" ShowInNewForm="FALSE" ShowInEditForm="FALSE" />
        <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000014}" Name="Huddle_Tasks" Required="FALSE" ShowInNewForm="FALSE" ShowInEditForm="FALSE" />
        <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000015}" Name="Remove_Artifacts" />
        <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000016}" Name="Huddle_Documents_List_ID" />
        <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000017}" Name="Huddle_Tasks_List_ID" />
        <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000018}" Name="Huddle_Milestones_List_ID" />
        <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000019}" Name="Huddle_Owner" ShowInNewForm="FALSE" ShowInEditForm="FALSE"/>
        <FieldRef ID="{0bc82141-5fa4-4948-b7b9-888f10010020}" Name="Share_Milestones_With"/>
    </FieldRefs>
    <!-- Omitted -->
</ContentType>

The new upgrade element manifest:

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <Field DisplayName="Share Milestones With"
        Name="Share_Milestones_With"
        StaticName="Share_Milestones_With"
        ID="{0bc82141-5fa4-4948-b7b9-888f10010020}"
        Description="Leave this blank to use a new set of milestones for this Huddle.  Otherwise, select an existing Huddle to share Milestones with."
        Type="Lookup"
        ShowField="Title"
        List="Self"
        SourceID="schema.zaanglabs"
        Group="Zaang Labs" />
</Elements>

And the custom feature receiver (with portions omitted):

using System;
using System.Collections.Generic;
using Microsoft.SharePoint;
using ZaangLabs.Server.Framework.Components.Deployment;

namespace ZaangLabs.Server.Framework.Components.FeatureReceivers
{
    /// <summary>
    ///     Updates
    /// </summary>
    public class ContentTypeUpdater : FeatureReceiverBase
    {
        /// <summary>
        ///     Triggered when a feature is updated.
        /// </summary>
        /// <param name = "properties">The properties.</param>
        /// <param name = "upgradeActionName">Name of the upgrade action.</param>
        /// <param name = "parameters">The parameters.</param>
        public override void FeatureUpgrading(SPFeatureReceiverProperties properties, string upgradeActionName, IDictionary<string, string> parameters)
        {
            if(upgradeActionName.ToUpperInvariant() != "ADDFIELDS")
            {
                base.FeatureUpgrading(properties, upgradeActionName, parameters);
                return;
            }

            try
            {
                SPSite site = GetSite(properties);

                using (SPWeb web = site.RootWeb)
                {
                    web.AllowUnsafeUpdates = true;

                    foreach (string key in parameters.Keys)
                    {
                        // Iterate and split each content type, field value.
                        string value = parameters[key];

                        string[] parts = value.Split(',');

                        SPContentTypeId contentTypeId = new SPContentTypeId(parts[0]);
                        Guid fieldId = new Guid(parts[1]);

                        SPField field = web.Fields[fieldId];
                        SPFieldLink fieldLink = new SPFieldLink(field);
                        SPContentType contentType = web.ContentTypes[contentTypeId];

                        Log("Adding field \"{0}\" to content type \"{1}\".", field.Title, contentType.Name);

                        contentType.FieldLinks.Delete(fieldId);

                        contentType.Update(true);

                        contentType.FieldLinks.Add(fieldLink);

                        contentType.Update(true);
                    }

                    web.AllowUnsafeUpdates = false;
                }
            }
            catch(Exception exception)
            {
                Log("Failed to execute the custom upgrade action: {0}", exception);
            }

            base.FeatureUpgrading(properties, upgradeActionName, parameters);
        }
    }
}
Filed under: SharePoint No Comments