Quantcast
Channel: Charlie Holland » SilverLight
Viewing all articles
Browse latest Browse all 2

Silverlight Field control using HTMLBridge

$
0
0

Have you ever thought “it would be really cool if I could create a field control for my publishing page that would interact with all the other field controls”? I’ll bet you have, I know you don’t want to admit it because the idea is madness but I’ll bet you have! (No? I guess I’m out on my own with that one then?)

I’ve only really gotten into the latest version of Silverlight in the past few weeks and I have to say I’m impressed. The potential for integration with SharePoint is huge and that’s before we consider the possibilities that come with the new Client Object Model in SharePoint 2010. Hopefully this post will give you a few ideas for how you could use it.

It’s all about the binding

If you’re anything like me, you’ll be bitterly disappointed by the design-time experience in VS2008 for Silverlight. While I accept the reasons for this, it’s still a bit of a pain having to jump between VS and Blend to get the job done. However, one of the good things about this forced separation is that it prompts you to think a bit more about how to structure your control. Sure you could still slap all your logic in the code-behind for your page.xaml and a lot of the examples I’ve still seem to suggest that the way to go, but this pony’s found a new trick, data binding. Yeah, yeah, I know you’ve heard it all before Data binding’s been around for years, but here’s the thing: If you’re creating a control in Silverlight that has any kind of business logic, styling it in Blend would be a nightmare without data binding. For a start, there would be no content to style and you’d have to keep running the thing just to see what it looked like, follow that up by having to synchronise your event hook-ups between VS and Blend and you’ll quickly see the nightmare that I was referring to.

My general approach has been to create business objects that encapsulate the entire logic of my control and have them all generated as members of a single ‘MyApplication’ object as appropriate. I then data bind the business objects to the user interface in Blend and voila, an instant design-time rendering of my control with no event hook-ups to think about. (I’m working on a few articles for a CodePlex project that will demonstrate this approach, hopefully I’ll post it this week)

What’s this go to do with field controls?

Since I’ve been banging on about having business objects that represent everything that our control cares about, we need a collection of ‘field’ objects that represents the other SPField objects on our page, and building this collection is where the first challenge comes in. How does Silverlight know what field controls are on the page?

The short answer is “You tell it”, here’s how:

1. Create a new class that inherits from BaseTextField (We’re only building the rendering control in this example, it can be manually hooked up to anything using SharePoint Designer as we’ll see later)

[ToolboxData("<{0}:MyInteractiveControl runat="server"></{0}:MyInteractiveControl>"),
    ParseChildren(false)]
    public class MyInteractiveControl : BaseTextField
    {

      protected override void Render(HtmlTextWriter output)
        {
            base.RenderFieldForInput(output);
        }

     public override string Text
        {
            get
            {
                if (this.ItemFieldValue != null)
                {
                    return this.ItemFieldValue.ToString();
                }
                else
                {
                    return base.Text;
                }
            }
            set
            {
                base.Text = value;
            }
        }

        public override void UpdateFieldValueInItem()
        {
            this.Value = field.Value;
            this.ItemFieldValue = field.Value;
        }

}

 

2. Add a reference to System.Web.Silverlight.(You’ll need to install the Silverlight SDK for this to work since you need the server-side Silverlight objects). Add a protected Silverlight field to your class, I’ve also added a HiddenField, my Silverlight control uses this to store the value that will ultimately be saved into the SPField object when the publishing page is saved:

        protected Silverlight sl;
        protected HiddenField field;

 

3. Add some code to CreateChildControls to render the Silverlight control. I’ve embedded my Silverlight control as a resource in my SharePoint assembly, of course you could just link to an XAP in a SharePoint document library.

      protected override void CreateChildControls()
        {
            ScriptManager sm = ScriptManager.GetCurrent(this.Page);

            if (sm == null)
            {
                this.Controls.AddAt(0, new ScriptManager());
            }

            //Insert a hidden field
            field = new HiddenField();

            if (!this.Page.IsPostBack)
            {
                field.Value = this.Text;
            }

            this.Controls.Add(field);

            if ((this.ControlMode == SPControlMode.Display))
            {
                Literal lit = new Literal();
                lit.Text = this.Text;
                this.Controls.Add(lit);
            }
            else
            {
                //insert a silverlight control
                sl = new Silverlight();

                string url = Page.ClientScript.GetWebResourceUrl(this.GetType(), SILVERLIGHT_RESOURCE);
                sl.ID = this.ID;
                sl.Source = url;
                sl.Width = this.Width;
                sl.Height = this.Height;

                string mode = this.ControlMode.ToString();

                string properties = HttpUtility.UrlEncode(SerializeProperties());

                sl.InitParameters = string.Format(CultureInfo.InvariantCulture,
                                                  "ElementName={0},DisplayMode={1},Properties={2}",
                                                  field.ClientID,
                                                  mode,
                                                  properties);

                this.Controls.Add(sl);
            }
        }

 

4. You’ll notice in the CreateChildControls code, there’s a call to SerializeProperties. The SerializeProperties method builds an XML string that contains the names of all the fields attached to the current SPListItem. The Xml string is passed into the Silverlight control as a parameter.

    private string SerializeProperties()
        {
            XmlWriterSettings settings = new XmlWriterSettings()
            {
                OmitXmlDeclaration = true,
                Indent = true
            };

            StringBuilder sb = new StringBuilder();
            StringWriter writer = new StringWriter(sb);
            XmlWriter xmlWriter = XmlWriter.Create(writer, settings);
            xmlWriter.WriteStartElement("Properties");

            string identifier = string.Empty;
            string tagName = string.Empty;
            string value = string.Empty;

            foreach (SPField field in this.ListItem.Fields)
            {
                if (!field.Hidden)
                {
                    Debug.WriteLine(field.InternalName + ":" + field.TypeAsString);
                    switch (field.TypeAsString)
                    {
                        case "Text":
                        case "Number":
                        case "Currency":
                            identifier = "TextField";
                            tagName = "input";
                            break;
                        case "Boolean":
                            identifier = "BooleanField";
                            tagName = "input";
                            break;
                        default:
                            break;
                    }

                    if (this.ListItem[field.Id] != null)
                    {
                        value = this.ListItem[field.Id].ToString();
                    }
                    else
                    {
                        value = string.Empty;
                    }

                    xmlWriter.WriteStartElement("Property");
                    xmlWriter.WriteAttributeString("key", field.Title);
                    xmlWriter.WriteAttributeString("identifier", identifier);
                    xmlWriter.WriteAttributeString("tagName", tagName);
                    xmlWriter.WriteAttributeString("value", value);
                    xmlWriter.WriteEndElement();

                }
            }

            xmlWriter.WriteEndElement();
            xmlWriter.Flush();
            writer.Flush();
            writer.Close();

            return sb.ToString();

        }

 

Ok so now it knows what it’s looking for. How does it find it?

When I first came across this, it seemed a bit of a fudge but after some investigation it seems that this code is used quite a bit in SharePoint and is partly the reason for each built-in field rendering a title attribute in the browser. (Something to bear in mind of you’re creating custom fields since it isn’t mentioned in the SDK anywhere)

function getTagFromIdentifierAndTitle(tagName, identifier, title) {

  var len = identifier.length;

  var tags = document.getElementsByTagName(tagName);

  for (var i=0; i < tags.length; i++) {

    var tempString = tags[i].id;

    if (tags[i].title == title && (identifier == "" || tempString.indexOf(identifier) == tempString.length - len)) {

      return tags[i];

    }

  }

  return null;

}

 

So we convert this piece of JavaScript to C# so that we can use it in Silverlight:

   private HtmlElement GetHtmlElement()
        {
            string title = this.Key;
            string identifier = this.Identifier;
 string tagName = this.TagName;
            int len = identifier.Length;

            ScriptObjectCollection tags = HtmlPage.Document.GetElementsByTagName(tagName);

            for (var i = 0; i < tags.Count; i++)
            {

                string tempString = tags[i].GetProperty("id").ToString();

                if (tags[i].GetProperty("title").ToString() == title &&
                    (identifier == "" || tempString.IndexOf(identifier) == tempString.Length - len))
                {

                    return tags[i] as HtmlElement;
                }

            }

            return null;

        }

 

Take it to the bridge

HTMLBridge is a very powerful feature in Silverlight, especially when it comes to SharePoint. Now you can embed a whole load of client functionality that would have required the pain of JavaScript before. Here’s how we use it:

In our Silverlight control we create a class called ‘Property’ to represent the value of the SPField that we’re linking to. The class is pretty simple and makes use of Xml serialization to pick up the data from our SerializeProperties method above. All we’re doing here is picking up a reference to the SPField’s rendered html control object using an HtmlPage object. We’re then surfacing an OnChanged event which can be picked up anywhere in our Silverlight control and used to handle changes in the other field controls on the page:

public class Property
    {

        public event EventHandler<HtmlEventArgs> OnChanged
        {
            add
            {
                try
                {
                    HtmlElement e = GetHtmlElement();
                    if (e != null)
                    {
                        e.AttachEvent("onchange", value);
                    }
                }
                catch (Exception)
                {

                }
            }
            remove
            {
                try
                {
                    HtmlElement e = GetHtmlElement();
                    if (e != null)
                    {
                        e.DetachEvent("onchange", value);
                    }
                }
                catch (Exception)
                {

                }
            }
        }

        [XmlAttribute("key")]
        public string Key
        {
            get;
            set;
        }

        [XmlAttribute("tagtName")]
        public string TagName
        {
            get;
            set;
        }

        [XmlAttribute("identifier")]
        public string Identifier
        {
            get;
            set;
        }

        [XmlAttribute("value")]
        public string Value
        {
            get;
            set;
        }

        private HtmlElement GetHtmlElement()
        {
            string title = this.Key;
            string identifier = this.Identifier;

            int len = identifier.Length;
            string tagName =this.TagName;

            ScriptObjectCollection tags = HtmlPage.Document.GetElementsByTagName(tagName);

            for (var i = 0; i < tags.Count; i++)
            {

                string tempString = tags[i].GetProperty("id").ToString();

                if (tags[i].GetProperty("title").ToString() == title &&
                    (identifier == "" || tempString.IndexOf(identifier) == tempString.Length - len))
                {

                    return tags[i] as HtmlElement;
                }

            }

            return null;

        }

    }

 

To respond to any changes in the SPFIeld objects we can simply hook up the OnChanged event in one of our Silverlight business objects (in this example _propertyValue represents a reference to a Property object as defined above that’s hooked up to a particular SPFIeld):

_propertyValue.OnChanged += new EventHandler<System.Windows.Browser.HtmlEventArgs>(PropertyValue_OnChanged);

 

By adding an event handler in our custom business object we can do whatever we need to do with the new value:

        private void PropertyValue_OnChanged(object sender, System.Windows.Browser.HtmlEventArgs e)
        {
            //Update the property value and notify change
            string newValue= e.Source.GetProperty("value").ToString();            

             _propertyValue.Value = newValue;
             NotifyPropertyChanged("EvaluationOrWheteverYourPropertyIsCalled");

        }

 

To stick with my stated approach of using databinding to hookup the front end with the engine – In order to trigger a new data binding operation we need to implement INotifyPropertyChange on our business object then raise a new PropertyChanged event:

      private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

 

To make all of this work we need to deserialize the collection of Property objects from the parameter that we passed in from the SharePoint BaseFieldControl. We can do that using the following code, this needs to be hooked up to our Application_Startup method because the parameters are picked up in the StartupEventArgs object that’s passed into that method:

       private static void DeserializeProperties(string propertiesXml)
        {
            using (TextReader rdr = new StringReader(propertiesXml))
            {
                XDocument properties = XDocument.Parse(rdr.ReadToEnd());

                _properties = new Dictionary<string, Property>();

                //iterate through each property node
                foreach (XNode typeNode in properties.Descendants("Property"))
                {
                    //attempt to deserialize the property collection
                    XmlSerializer s = new XmlSerializer(typeof(Property));

                    Property p = s.Deserialize(typeNode.CreateReader()) as Property;

                    //Don't allow duplicates
                    if (!_properties.ContainsKey(p.Key))
                    {
                        _properties.Add(p.Key, p);
                    }
                }
            }
        }

 

A grand idea but how to I see it on my page?

You’ll notice above, when we defined the control class that derived from BaseTextField that we added a ToolBoxData attribute. The benefit of this (as it’s name suggests), is that out new control will now appear on the toolbox in SharePoint designer. (Assuming you’ve deployed the compiled assembly to the GAC). All you need to do to see your new control is drag it onto a page in SharePoint designer and set the field property to the name of a text field on your publishing page.

Conclusion

I hope this post gives some idea of how SIlverlight can be used to enhance the client experience in SharePoint. I appreciate that I’ve skipped a few broad strokes and assumed the reader is already familiar with most of the processes and procedures involved in creating a custom field controls so if there is anything that you’d like me to expand upon please feel free to post a comment and I’ll respond as soon as possible.


Viewing all articles
Browse latest Browse all 2

Latest Images

Trending Articles





Latest Images