Reading InfoPath template's default values in code

 

String xml = "";
FormTemplate template = this.Template;
using (Stream s = template.OpenFileFromPackage("template.xml"))
{
    XPathDocument reader = new XPathDocument(s);
    XPathNavigator nav = reader.CreateNavigator();
    XPathNavigator repeat = nav.SelectSingleNode("/my:myFields/my:Repeats/my:Repeat[1]", this.NamespaceManager);
    if (repeat == null)
    {
        return;
    }
    xml = tender.OuterXml;
}
if (!String.IsNullOrEmpty(xml))
{
    XPathNavigator destination = this.CreateNavigator().SelectSingleNode("/my:myFields/my:Repeats", this.NamespaceManager);
    destination.AppendChild(xml);
}

 

The top part of the code is particularly useful if you want to use the Default Values for repeating sections in InfoPath.  Your code will read the xml for the default values and insert them into the repeating section.  I've previously hardcoded these XML segments for insert, but that's extremely error prone when you inevitably update your XML template with new and more exciting child elements and attributes.

IE11 (+Win8.1.1) F12 Developer Tools for the SharePoint Dev

 

This blog post is about all the new nifty features I'm finding in the latest IE11 F12 developer tools.  I updated my Windows to 8.1 update 1, and IE11 was updated.  I started seeing a few cool new features, and went on Twitter to find the official documentation.

http://msdn.microsoft.com/en-us/library/ie/dn641599(v=vs.85).aspx

Was supplied by @AdamTReineke

https://twitter.com/AdamTReineke/status/454678702169677824

 

Rather than bore you with a list of features, which is on MSDN.  I want to just quickly share how I'm using some of them.

Disclaimer - I had just watched LEGO movie.  So EVERYTHING IS AWESOME!

 

DOM Explorer

 

1. CSS Changes

 

  • When you "touch up" CSS in SharePoint to get the exact look you want.  You often forget which rule you had applied.
  • The DOM Explorer's "Changes" tab tracks all the individual changes, and you can revert an individual rule, or copy them all and paste to your CSS file.
  • Copy All.  Awesome!

image

 

2. Pseudo Rules

 

  • You know those pesky :hover and :visited CSS rules in SharePoint that can never find to eliminate? 
  • Now you can apply :hover or :visited and see the effect rule without actually trying to catch your mouse hovering.  Haha.  Awesome!

image

 

The super cool updated Console object

 

3. Console info, warning, error

  • My "warnings" are currently filtered. 
  • use console.info() console.warn() and console.error() to write to these.

image

image

  • No ribbon button but you can right-click to filter Log messages too.  For those really spammy libraries, which is pretty awesome!

 

4. Console handles objects, multiple objects and HTML

 

  • Chrome and Firefox both were able to log objects and inspect them.  IE11 used to just log the [object].tostring which was pretty useless.
  • The update now fixes that, and allow multiple arguments to be logged at the same time.

 

image

 

5. Console always available for dev. 

 

image

 

  • So you can have all your logs happening without trying to start the debugger before you load the page
  • Remember your end users won't have this on, so TEST before you deploy code.

 

6. Console can switch target to an iFrame. 

 

  • Note, I couldn't get this to work in IE8-Compat mode (which my SP2010 runs on).  This works fine for IE9, IE10, Edge.
  • This is awesome for debugging objects in the SP.UI.Dialog

image

 

Debug

 

7. Debugger can be attached without reloading the page

 

  • Not sure if we need a picture to describe how awesome this is.  I imagine the picture will involve unicorns, rainbows and kittens.  AWESOME!

 

8. Just My code

 

image

  • Debugger only stops on my code.
  • Note, some libraries can throw error when you call it wrong - so sometimes not so awesome.

 

9. Pretty Print

 

  • Oh crap.  Something in sp.runtime.js don't know how to read this...

image

  • Not anymore in 2014!
  • Hit pretty-print - the sp.runtime.js becomes actually readable, and you can set line-based debugging too!

image

  • I didn't switch to sp.runtime.debug.js - this is awesome!

 

10. Source Maps

 

  • Now finally we have source map support.  Here is me debugging Typescript in IE11

 

image

 

 

Summary

  • The developer story on IE11 (after this update) is awesome!

Building a 2013 No Code webpart for XKCD.com/now

Last week, XKCD (of nerd comic fame) produced this most excellent comic http://xkcd.com/now/

Now

 

This complex looking image describes essentially an outer ring and an inner ring.  The outer ring is the current time on your machine.  The inner ring is the regions of the world.  The chart tells you quickly what time it is for someone living in another part of the world.

Being the SharePoint lover that I am, you know what I'm thinking.

Time for a fun toy Web Part

Step 0.  Environment.
Step 1.  Create a SharePoint Sandbox Solution, add Client Web Part (Host Web)
Step 2.  Create the assets (images, HTML and CSS)
Step 3.  Plug in the javascript code.
Step 4.  Permissions.
Step 5.  Create a test page.  Add the App Part to play.  Change web.regional settings - see webpart change.

 

Step 0.  Environment

My Environment setup is very simple:

  • Office 365 with Developer site, at: https://sharepointgurus365.sharepoint.com/sites/Developer/
  • Visual Studio 2013
  • Running on Windows 8.1
  • I do not have SharePoint installed on this client PC
  • I have a paint program Paint.NET which is an excellent developer's tool when you don't have Photoshop

 

Step 1. Create a SharePoint Sandbox Solution, add Client Web Part (Host Web)


image

Select SharePoint-Hosted.  This App Part will be hosted by Office 365.

 

image

 

Step 2.  Create the images.

imageimage

 

I split the image into two layers.  Because I want to rotate them separately.  I also change the text labels for the time 6PM, Midnight and 6AM on the Time-Ring.  Since when it's upside down it still needs to be readable.

I add these to the Project, under Images

image

I also modify the App Part html

image

I added CSS for the two rings.  Position:fixed so that they overlap. 

<body>
    <style>
        .timezone-ring {
            background: url(../Images/xkcd-now-land.png) no-repeat top left;
            width:706px;
            height:705px;
            position:fixed;
            top:0px;
            left:0px;
        }
        .time-ring {
            background: url(../Images/xkcd-now-time.png) no-repeat top left;
            width: 706px;
            height: 705px;
            position:fixed;
            top: 0px;
            left: 0px;
        }
    </style>
    <div style="position:relative;">
        <div class="timezone-ring">
        </div>

        <div class="time-ring">
        </div>
    </div>
</body>

image

<ClientWebPart Name="xkcd-clock" Title="xkcd-clock" Description="Clock based on xkcd.com/now the outer ring rotates to current machine time.  The inner ring rotates to web region." DefaultWidth="720" DefaultHeight="720">

  • Note: tidy up the ClientWebPart definition.  Set the default width and height.

 

Step 3.  Plug in the javascript code.

 

There is very little JavaScript,  So I wrote them inline.  In practice, this made the debugging a lot more difficult.  I recommend always writing your Javascript in a separate file.

 

<script type="text/javascript">
    //'use strict';  // have to turn off 'use strict' because I use eval() later :-P

    // Set the style of the client web part page to be consistent with the host web.
    (function () {
        var hostUrl = '';
        if (document.URL.indexOf('?') != -1) {
            var params = document.URL.split('?')[1].split('&');
            for (var i = 0; i < params.length; i++) {
                var p = decodeURIComponent(params[i]);
                if (/^SPHostUrl=/i.test(p)) {
                    hostUrl = p.split('=')[1];
                    document.write('<link rel="stylesheet" href="' + hostUrl + '/_layouts/15/defaultcss.ashx" />');
                    break;
                }
            }
        }
        if (hostUrl == '') {
            document.write('<link rel="stylesheet" href="/_layouts/15/1033/styles/themable/corev15.css" />');
        }

/*** John's code starts here ***/

        function turn() {
            var hour = (new Date()).getHours() + 12; // clock faces up so need 12hr offset
            var degrees = -((hour % 24) * 15); // turn anti-clockwise so negative
            var rotate = "rotate(" + degrees + "deg)";

            $(".time-ring").css("transform", rotate);

            setTimeout(turn, 1000 * 60 * 15); // 15mins
        }

        var ctx = SP.ClientContext.get_current();
        var hostCtx = new SP.AppContextSite(ctx, hostUrl);
        var timezone = hostCtx.get_web().get_regionalSettings().get_timeZone();
        ctx.load(timezone);
        ctx.executeQueryAsync(function () {
            // has current timezone

            var description = timezone.get_description();
            var m = /UTC(.*\d+):/.exec(description);
            if (m) {
                var offset = eval(m[1]) -2;
                // map is aligned with Egypt (UTC+2) so need a 2hr offset.

                var degrees = -(offset * 15); // turn anti-clockwise so negative
                var rotate = "rotate(" + degrees + "deg)";

                $(".timezone-ring").css("transform", rotate);
            }

            turn();  // call turn

        }, function () {
            // failed.
        })

/*** JOHN'S CODE ENDS HERE ***/

    })();
</script>

 

Few quick notes here:

  • turn() function calculates the rotation degree for the outer ring, based on the current machine time.  See comments for all the offsets.
  • the clientContext query needs to get the App Host Web settting.  Otherwise it will return you the regional setting for the App Web which would be pretty pointless.
  • finally, once we have the timezone information from the regional setting, it looks like (UTC+10: Sydney).  I take the +10 and do more offset for the inner ring.
  • I only request region information once on page load.  But I put turn() on a timeout call every 15minutes.  So if you leave the webpart on a screen it'll keep checking and turn every 15mins.

 

Step 4.  Permissions.

 

image

By default, the App has no permissions to read from the Host Web.  We need to set up the permission to Read from the Host Web.

image

When deployed via VS.NET you'll see this pop up in the browser.  You'll also get this when you activate the App.

 

Step 5.  Create a test page.  Add the App Part to play. 

 

image

 

Results

 

image

I'm in Australia (AEST UTC+10).  Which is pointing up.  My current time is 11:40pm

image

Change the Host Web's region to (Pacific Time UTC-8).

image

Web Part still shows 11.45PM - that's my current time.  But the map has rotated to point to the West Coast.

 

Downloads

I did not test this on any other machine but it should work.  Let me know if it works for you.

And if you really enjoy using this in your company, please buy something from the XKCD gift shop.

Using spservices to create discussion and reply in a Discussion List

This updated blog post describes how I go about building a threaded, inline comments system for any page, using a SharePoint Discussion List as the backend storage for threaded data.

I choose to use SPServices because originally, the webpart is built for a SharePoint 2007 environment.  

I've rebuilt the webpart using the JavaScript code and confirm it runs happily in SharePoint 2013, and this time, with much better screenshots!
In SharePoint 2010 and 2013, you should be able to talk directly to the REST endpoint to perform read and updates.

Enter the Discussion List

 

Many users have a love-hate relationship with SharePoint's Discussion list.  Perhaps more hate.  The main culprit is the inflexible UI.  Lack of many modern forum needs:

  • Can't show all discussions and show threaded replies together
  • Can't do inline new discussion
  • Can't do inline replies
  • Poor support cross browsers
  • Can't easily filter for a subset

But still, the list itself is very functional.

  • Does support threaded discussions
  • Calculates total replies and last discussion updated
  • Has email notification through SharePoint alerts
  • Has tweaked permission model where users can create but not modify their own posts, or can edit.

 

Image888

Figure: A basic SharePoint Discussion List

 

Let's ignore the problems with the native Discussion List UI, and build our own with JavaScript

Read from the Discussion List

 

/*
This code queries using SPServices' GetListItems on list and populates a UL.discussions dom element on the page
*/
var $ul = $("ul.discussions");
var list = "My Discussions";

function getDiscussionList(list, $ul) {
var promise = $().SPServices({
  operation: "GetListItems",
  listName: list,
  CAMLViewFields: "<ViewFields><FieldRef Name='Title' /><FieldRef Name='Body' /><FieldRef Name='Author' /><FieldRef Name='Modified' /><FieldRef Name='DiscussionLastUpdated' /><FieldRef Name='ItemChildCount' /></ViewFields>",
  CAMLQuery: "<Query><OrderBy><FieldRef  Name='DiscussionLastUpdated' Ascending='FALSE' /></OrderBy></Query>"
});

promise.done(function(){
  var items = $(promise.responseXML).SPFilterNode("z:row").SPXmlToJson({
    mapping: {},
    includeAllAttrs: true,
    removeOws: true
  });
  $ul.empty(); // clear old list

  $.each(items, function(index, item) {
    // render each discussion
    var $li = $("<li class=discussion' />");
    $li.append(item.Title);
    $li.append(item.Body);
    $li.append(item.Author);
    $li.append(item.Modified);
    $li.append(item.DiscussionLastUpdated);
    $li.append(item.ItemChildCount);
    $li.append(item.FileRef);
    $li.append("<div class='replies'>replies</div>");
    $ul.append($li);

  });  // end items.each
}); // end promise done
promise.fail(function(xhr, status, error) {
  alert(xhr.responseText);
}); // end promise fail

} // end getDiscussionList


Key points:

  • List.asmx - GetListItems
  • This returns the top level items in the list, which are Discussions

 

Figure out the threaded discussions


In SharePoint discussion lists, the top level "Discussion" items are folders.  The subsequent "Message" reply items are list items within that folder.
So to query for the replies to a discussion, we query the list with the filepath of the top level Discussion item as the query filter.

/*
This second function figure out the threaded replies when you expand one discussion
*/

Note: The FileRef usually has a value that looks like this:
// 15;#Company/Site1/Web1/Lists/My Discussions/15_.000
You need to clean the URL and use only the file path:

function getFilePath(fileRef) {
  if (!fileRef) return;
  var m = /;#(.*)$/.exec(fileRef);
  if (m) {
    return m[1];
  }
}

This will give you:
Company/Site1/Web1/Lists/My Discussions/15_.000

function getDiscussionReplies(list, filepath) {
var options = {
  operation: "GetListItems",
  listName: list,
  CAMLViewFields: "<ViewFields><FieldRef Name='Title' /><FieldRef Name='Body' /><FieldRef Name='Author' /><FieldRef Name='Modified' /><FieldRef Name='DiscussionLastUpdated' /><FieldRef Name='ItemChildCount' /></ViewFields>",
  CAMLQueryOptions: "<QueryOptions><ViewAttributes Scope='RecursiveAll' IncludeRootFolder='True' /></QueryOptions>",
  CAMLQuery: "<Query><Where><Contains><FieldRef Name='FileRef' /><Value Type='Text'>" + filepath + "</Value></Contains></Where><OrderBy><FieldRef  Name='FileRef' Ascending='TRUE' /></OrderBy></Query>"
};
var promise = $().SPServices(options);

promise.done(function(){
  var items = $(promise.responseXML).SPFilterNode("z:row").SPXmlToJson({
    mapping: {},
    includeAllAttrs: true,
    removeOws: true
  });

  $.each(items, function(index, item) {
    // render each reply
    // snipped

  });  // end items.each
}); // end promise done

} // end getDiscussionReplies

 

Key points:

  • Use CAMLQueryOptions for RecursiveAll
  • Use FileRef contains FilePath to find all threaded (Message) replies

Create a Discussion in the Discussion List

To create a new discussion, you just need to create a new item in the Discussion List

var list = "My Discussions";
var title = $("input.title").text();
var body = $("textarea.body").text();

function newDiscussion(list, title, body) {

var promise = $().SPServices({
  operation: "UpdateListItems",
  batchCmd: "New",
  listName: "Team Discussion",
  updates: "<Batch OnError='Continue' >" +
       "<Method ID='1' Cmd='New'>" +
        "<Field Name='ContentType'>Discussion</Field>" +
       "<Field Name='FSObjType'>1</Field>" +   // Important: FSObjType = 1 means that this is a folder.  If this isn't specified SharePoint sometimes create the wrong root level item.
        "<Field Name='Title'>" + escapeColumnValue(title) + "</Field>" +
        "<Field Name='Body'>" + escapeColumnValue(body) + "</Field>" +
       "</Method>" +
      "</Batch>"
});
   
promise.done(function(){
  var $ul = $("ul.discussions");
  getDiscussionList(list, $ul);

}); // end promise done

}

 

Image893

Figure: Start a new discussion inline

Key points:

  • List.asmx - UpdateListItems
  • Set FSObjType = 1, this is necessary or there were head-scratching bugs

 

(Optional) Filter a Discussion List to only related item

 

In the Discussion List settings, you can add a lookup column RelatedItem to an external List (or Document Library).
Then when you use the JavaScript above, you can filter your CAML Queries so that they only return elements that are related to the current page.

For getDiscussionList:

CAMLQuery: "<Query><Where><Eq><FieldRef Name='RelatedItem' /><Value Type='Number'>" + related + "</Value></Eq></Where><OrderBy><FieldRef  Name='DiscussionLastUpdated' Ascending='FALSE' /></OrderBy></Query>"

And for creating discussion threads:

updates: "<Batch OnError='Continue' >" +
     "<Method ID='1' Cmd='New'>" +
      "<Field Name='ContentType'>Discussion</Field>" +
      "<Field Name='FSObjType'>1</Field>" +
      "<Field Name='Title'>" + escapeColumnValue(title) + "</Field>" +
      "<Field Name='Body'>" + escapeColumnValue(body) + "</Field>" +
      "<Field Name='RelatedItem'>" + related + "</Field>" +
     "</Method>" +
    "</Batch>"


In my project, I linked my discussion list to a Video Library, and each Video has an area for creating comments and reply to threaded discussions.  All the conversation are saved in one single Discussions List, but filtered on each page as required.

Understand the nested format for Discussion List items

In a SharePoint Discussion List:

  • Discussion (content type) is a Folder.  FSObjType = 1
  • Message (content type) is a List Item.  FSObjType = 0

 

Reply in a Discussion List.


var list = "My Discussions";
var filepath = "Company/Site1/Web1/Lists/My Discussions/15_.000";
var body = $("textarea.body").text();

function replyDiscussion(list, filepath, body) {

// RootFolder needs to start with /

var promise = $().SPServices({
  operation: "UpdateListItems",
  batchCmd: "New",
  listName: list,
  updates: "<Batch OnError='Continue' RootFolder='" + "/" + filepath + "'>" +  // RootFolder needs to start with /
     "<Method ID='1' Cmd='New'>" +
      "<Field Name='ContentType'>Message</Field>" +
      "<Field Name='FSObjType'>0</Field>" +
      "<Field Name='Body'>" + escapeColumnValue(body) + "</Field>" +
     "</Method>" +
    "</Batch>"
});
promise.fail(function(xhr, status, error) {
  alert(xhr.responseText);
});
promise.done(function(){
  getDiscussionReplies(list, filepath);
});

} // end replyDiscussion

 

Image882

Figure: Threaded Discussions, with inline reply.


Key points:

  • List.asmx - UpdateListItems
  • Set FSObjType = 0 (for List Item), this is necessary or there were head-scratching bugs
  • Set RootFolder to the Filepath of the parent Discussion (folder) item.  This way, UpdateListItems creates a new list item within that folder.

 

 

Conclusion: here we are.  Threaded, inline, replies.


Combine everything together, the javascript lets me now do:

  • Tie a Discussion List to a Picture Library
  • Start one or more discussion threads on any Picture
  • See replies inline in one single UI.
  • Inline comment-creation
  • Inline comment-reply

 

  • Inline Edit and Inline Delete is possible, but the end-user will need to follow SharePoint permission settings on the Discussion List.  I have not written code to do those operations. 

SPSSYD 2013 and special thanks to Brian Farnhill

I wanted to thank Brian Farnhill for organizing SharePoint Saturday (SPS) events in Australia faithfully for the last few years.  SharePoint Saturday Sydney 2013 was the last one where he is the official organizer, chief, keynoter, label-printer, sponsor-chaser, etc. etc.

The event had a lot of highlights for me:

  • A lot of people showed up early and was ready at the keynote. 
  • Coffee Cart showed up on time in the morning
  • The Clifton venue (they moved since last year) was amazing - I really liked the layout of the sofa and the meal table-benches were great for conversation
  • Lunch was hopefully just enough - I think right at the end we might have just ran out of sandwiches
  • Adam got rick rolled'
  • Ross' session had a memory moment (I heard second hand)
  • My session had a major projector fail (more on this later)

 

Presentation - Typescript PowerPoint and demo project

 

Explanation - What happened to the projector / your laptop?!

 

I was using a USB-3 display link adapter for the last month.  But I didn't realize it would behave very badly with the HDMI-VGA dongle for the projector at the event.  A quick fumbling got the display to work.  But it was black and white but I was going to run out of time so I just ran with it.

I'm glad you guys had laughs at my expense.  It made me feel a little bit less miserable.

Love you guys.