>
Blog
Book
Portfolio
Search

7/3/2013

4609 Views // 0 Comments // Not Rated

SharePoint 2013 Web Parts And The App Model, Part 3: The Web Part "Client" Architecture

In part one of this four part series, I started with a discussion about the new development paradigms SharePoint 2013 presents. Then, part two completed the data access layer for our SharePoint 2013 web parts. Now it's time to take a look at the web part itself. I'll finish these posts with the deployment story in part four.

Like I said in those previous posts, our web parts are built starting from the out-of-the-box visual web part template (which implies they are part of a WSP, which will be important in part three) in Visual Studio 2012. After adding a new web part, the first thing I do is pretty up the Elements.xml and whatever.webpart files as follows:

Elements.xml

  • Line #5: Set the "Group" property to the same value for all web parts, which will almost always be something like "(Name of Project) Web Parts."

Whatever.webpart

  • Line #10: Set the "Title" to a pretty version of what the template barfs out.
  • Line #11: Set the "Description" to something, you know, descriptive.

Next, let's look at the code behind. All of my web parts inherit from a base one named, creatively, "BaseWebPart." I still consider this to be kosher given Microsoft's new guidance against using the SharePoint server object model, since we're not referencing Microsoft.SharePoint.dll. Everything here is ASP.NET code that could never be blamed for botching a SharePoint upgrade or migration.

Code Listing 1

  1. public class BaseWebPart : WebPart
  2. {
  3. #region Events
  4. protected override void OnInit(EventArgs e)
  5. {
  6. //initialization
  7. base.OnInit(e);
  8. }
  9. protected override void Render(HtmlTextWriter writer)
  10. {
  11. //initialization
  12. base.Render(writer);
  13. //write properties to page
  14. foreach (PropertyInfo pi in this.GetType().GetProperties())
  15. {
  16. //only get our properties
  17. if (pi.GetCustomAttributes<CategoryAttribute>().Any(a => a.Category.Equals("2013WebPart")))
  18. {
  19. //get value
  20. object value = pi.GetValue(this);
  21. //write hidden input for each property value
  22. writer.WriteBeginTag("input");
  23. writer.WriteAttribute("name", pi.Name);
  24. writer.WriteAttribute("type", "hidden");
  25. writer.WriteAttribute("webPartId", this.ID);
  26. writer.WriteAttribute("value", value == null ? string.Empty : value.ToString());
  27. writer.Write(HtmlTextWriter.SelfClosingTagEnd);
  28. }
  29. }
  30. }
  31. #endregion
  32. }

The Init method on Line #'s 4-8 are part of the Visual Studio 2012 visual web part template (with Office 2013 Tools installed). All I do here is override the Render method, pass through to our base's rendering logic, and then have a little fun with our properties. Line #14 uses reflection to grab all properties decorated with an attribute of type "CategoryAttribute" and writes a hidden input to the client with information containing the property name, value, and id of the web part that owns it. CategoryAttribute defines a single string named "Category" and Line #17 will grab the property if the category is set to "2013WebPart." Who doesn't think reflection is fun?

This way, all of our web parts will automatically have their properties and current values thereof serialized to the DOM for us. As you are probably guessing, this is the first of a two-step process, where the second is grabbing these values and sending them up to the Web API controller. So before finishing the web part discussion, let's take a look at the AJAX call that performs this work. This JavaScript is located in a "common" JS file that I usually call "[NameOfProject].js." It's referenced by the master page and lives in _layouts (by way of being deployed via the WSP project).

Code Listing 2

  1. function LoadWP(target, id, api, doneCallback)
  2. {
  3. //call server
  4. $.ajax
  5. ({
  6. method: 'GET',
  7. dataType: 'jsonp',
  8. url: 'http://urltomvcsite.com/api/' + api,
  9. data:
  10. ({
  11. //assemble args
  12. args: GetProperties(id),
  13. page: window.location.href
  14. }),
  15. success: function (response, status)
  16. {
  17. //process result
  18. if (response.Error != null)
  19. {
  20. //error
  21. alert(response.Error);
  22. }
  23. else if (response.Model != null)
  24. {
  25. //json
  26. ko.applyBindings(response.Model, $(target)[0]);
  27. }
  28. else if (response.HTML != null)
  29. {
  30. //html
  31. $(target).html(response.HTML);
  32. }
  33. //done
  34. if (doneCallback != null)
  35. doneCallback(response);
  36. },
  37. error: function (response, error, ex)
  38. {
  39. //error
  40. alert(error);
  41. }
  42. });
  43. }
  44. function GetProperties(id)
  45. {
  46. //check for no id
  47. if (id == null)
  48. return null;
  49. //initialization
  50. var model = new Object();
  51. model.Args = new Array();
  52. //get all properties for this web part
  53. $('input[webPartId="' + id + '"]').each(function (i)
  54. {
  55. //assemble object
  56. model.Args[i] = new Object();
  57. model.Args[i].Value = $(this).val();
  58. model.Args[i].Name = $(this).attr('name');
  59. });
  60. //return
  61. return JSON.stringify(model, null, 2);
  62. }

LoadWP, starting at the top on Line #1, takes in four parameters:

  1. target (string): the id of the DOM element who's HTML should be set or the id of the Knockout script tag to which the model should be bound. We'll be revisiting Knockout a bit later.
  2. id (string): the id of the web part who owns the property.
  3. api (string): the name of the Web API controller that we'll be "getting" data from.
  4. doneCallback (optional function): will be invoked with the result from the MVC call so that you can perform additional logic after the UI is updated.

Line #4 performs the jQuery AJAX call. Line #6 specifies the get and Line #7 specifies jsonp as the data type. This is very important, as it allows communication between the SharePoint domain and our app domain over in the MVC site (which should be different URLs, and therefore a no-no with conventional JSON). It's also very awesome, because we don't have to do any extra work to de/re-serialize the model we get back from MVC; it comes down from the server pre-giftwrapped as a JSON object ready for databinding.

Line #'s 12 and 13 set the query string parameters that the controller expects as part of the request. The former calls the method (called "GetProperties" that we'll look at next) which reads in the values of the hidden inputs that the base web part generates, builds a client-side model, and serializes it; the latter is simply the current page's URL.

Line #15 defines the AJAX "success" callback's anonymous method that implements the following logic: if there's an error, alert that; if there's a model, Knockout bind that; if there's a value for the HTML, set that. Then if there's a callback, do it. The Knockout binding on Line #26 is annoying, as we have to hard-code in the zero index to the jQuery selector that specifies which data template to use; .first() doesn't work, as it appears that Knockout wants an actual DOM element, not a jQuery selector.

Finally, let's look at GetProperties. Line #'s 50 and 51 new up the web part args model, which matches MVC's WebPartArgsModel exactly. The magic happens on Line #53, when we use the web part Id passed into the method to feed a jQuery selector that gives us all its property values. I "each" my way through them, adding a new web part arg model at each index. Finally, Line #61 serializes this object for query string transmission.

Now that our data access layer is wired up, we can discuss the web part's UI. Like I said, "conventional" web parts in Visual Studio 2012 / SharePoint 2013 behave, at development time, just like user controls. The only annoyance is the fact that we can't quick deploy our ASCX files independent of their WSP installation cycle. But other that, Microsoft as really streamlined web part development.

Let's take a look at a sample web part's HTML:

Code Listing 3

  1. <script type="text/javascript">
  2. $(function ()
  3. {
  4. //load web part
  5. LoadWP('#tiles', '<% =this.ID %>', 'PageTiles', function (response)
  6. {
  7. //do more stuff
  8. var whatever = response.Model.Whatever;
  9. ...
  10. });
  11. });
  12. </script>
  13. <script type="text/html" id="tiles-template">
  14. <td valign="top">
  15. <div class="bordered-box" style="width: 144px;">
  16. <span class="title" data-bind="text: Title" />
  17. <img data-bind="attr: { src: ImageUrl }" />
  18. <p class="description" data-bind="text: Description" />
  19. <a class="rnd-5 boxed-btn" data-bind="text: Command, attr: { href: Url }" />
  20. </div>
  21. </td>
  22. <td style="width: 17px;" valign="top" />
  23. </script>
  24. <table cellpadding="0" cellspacing="0" width="100%" style="float: left; margin-top: 17px;">
  25. <tr id="tiles" data-bind="template: { name: 'tiles-template', foreach: Tiles }" />
  26. </table>

The first block, Line #'s 1-12, perform the AJAX call to get the page's data from the server. Phonetically, Line #5 says "Call the Get method on the PageTiles controller and bind the resulting model to the element with an Id of "tiles." This shoots us down to Line #25, which uses the Knockout "foreach" binding against the "Tiles" array on the model and renders it with the "tiles-template" template.

This template is defined on Line #13 and identified via an Id that matches the template name in the binder element. Knockout templates are defined within script tags (of type "text/html") so that their fragmented markup isn't displayed on the UI before the binding is complete. It's actually pretty easy to use once you get a one or two displays working. Really my only complaint about Knockout is that the data binding attributes aren't stripped from the DOM. I don't think this causes any harm, but it's sort of like a painter leaving the blue tape up between the top of a wall and the ceiling. It just seems a bit sloppy.

For completeness sake, here's what a property declaration in the web part's code behind looks like:

Code Listing 4

  1. [WebBrowsable(true)]
  2. [CategoryAttribute("2013WebPart")]
  3. [Personalizable(PersonalizationScope.Shared)]
  4. public string WebUrl { get; set; }

Line #'s 1 and 3 basically wire this property into the standard tool part for configuration when the page the web part is on is in edit mode. Line #2 is our "CategoryAttribute" which I use, again, to inform the base web part that this property belongs to this particular control. This is important to allow multiple web parts of the same type to live on the same page. Finally, we have the property itself on Line #4, which will always be of type string.

Like I said, other than the boilerplate initialization code that comes with the visual web part template and the specification that the class should inherit from our base, these properties are the onlty code we'll see in the file. And since there's absolutely no traces of Microsoft.SharePoint.anything.dll, these puppies should upgrade nice and clear to next version of SharePoint (a most displeasing thought, as I'm still learning this one). That does it for the web part. Read on to part four, where we'll look at the deployment aspect of this architecture.

No Tags

No Files

No Thoughts

Your Thoughts?

You need to login with Twitter to share a Thought on this post.


Loading...