Rendering JSON from an XSLTListViewWebPart

I recently built a site on SharePoint 2010 that used lots of JavaScript for custom form validations.  The JavaScript made ajax calls back to listdata.svc to get the data to perform the validations. Things worked fine locally, but when a user from the other side of the globe accessed the site, things were slow.  A quick test using Fiddler’s ‘TimeLine’ tab showed me that after the initial page load, seconds were being spent making  multiple  ajax calls to get the validation data.

My first thought was to switch from rest to csom so that I could batch all the requests together and get the validation data in a single call. But then I began to think ‘Why not just send it down with the initial page?’ I thought about writing a custom webpart that could get data from an SPDataSource and serialize it to JSON and write it to the output stream.

Then it occurred to me that this could probably be done using an XSLTListViewWebPart and some custom XSL.  I found several pages that discussed serializing XML to JSON (Most notably http://controlfreak.net/xml-to-json-in-xslt-a-toolkit/ ).  Tweaking one of the samples in that article gave me the following XSLT which renders an XSLTListViewWebPart’s data into the browser:

<xsl:stylesheet xmlns:xsl=”http://www.w3.org/1999/XSL/Transform&#8221;
version=”1.0″ xmlns:ddwrt2=”urn:frontpage:internal”>
<xsl:param name=”webpartTitle”></xsl:param><!– need to add parameter binding <ParameterBinding Name=”webpartTitle” Location=”WPProperty(Title)” /> –>
<!– Turn off auto-insertion of <?xml> tag and set indenting on –>
<xsl:output method=”text” encoding=”utf-8″ indent=”yes”/>

<!– strip whitespace from whitespace-only nodes –>
<!–CAUSES ERROR Error while executing web part: System.Xml.Xsl.XslTransformException: White space cannot be stripped from input documents that have already been loaded. Provide the input document as an XmlReader instead. –>
<!– <xsl:strip-space elements=”*”/> –>

<xsl:template match=”/dsQueryResponse”>
<xsl:value-of select=”$webpartTitle” />
<xsl:text>&lt;script type=&quot;text/javascript&quot; &gt; debugger;</xsl:text>
<xsl:value-of select=”$webpartTitle” />
<xsl:text>=</xsl:text>

<xsl:apply-templates></xsl:apply-templates>
<xsl:text>&lt;/script&gt;</xsl:text>
</xsl:template>

<!– handles elements –>
<xsl:template match=”*”>
<!– element name –>
<xsl:text>{ &quot;e&quot; : &quot;</xsl:text>
<xsl:value-of select=”name()”/>
<xsl:text>&quot;</xsl:text>
<xsl:variable name=”ctr” select=”count(*)”/>
<xsl:variable name=”actr” select=”count(@*)”/>
<xsl:variable name=”tctr” select=”count(text())”/>
<!– there will be contents so start an object –>
<xsl:if test=”$actr > 0″>
<xsl:text>, &quot;values&quot; : { </xsl:text>
<xsl:apply-templates select=”@*”/>
<xsl:text>}</xsl:text>
</xsl:if>
<!– handle element nodes –>
<xsl:choose>
<xsl:when test=”$ctr = 1″>
<xsl:text>, &quot;data&quot; : </xsl:text>
<xsl:apply-templates select=”*”/>
</xsl:when>
<xsl:when test=”$ctr > 1″>
<xsl:text>, &quot;rows&quot; : [ </xsl:text>
<xsl:apply-templates select=”*”/>
<xsl:text> ]</xsl:text>
</xsl:when>
</xsl:choose>
<!– handle text nodes –>
<xsl:choose>
<xsl:when test=”$tctr = 1″>
<xsl:text>, &quot;$&quot; : </xsl:text>
<xsl:apply-templates select=”text()” />
</xsl:when>
<xsl:when test=”$tctr > 1″>
<xsl:text>, &quot;$&quot; : [ </xsl:text>
<xsl:apply-templates select=”text()” />
<xsl:text> ]</xsl:text>
</xsl:when>
</xsl:choose>
<xsl:text> }</xsl:text>
<xsl:if test=”position() != last()”>
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:template>

<!– this template handle text nodes –>
<xsl:template match=”text()”>
<xsl:variable name=”t” select=”.” />
<xsl:choose>
<!– test to see if it is a number –>
<xsl:when test=”string(number($t)) != ‘NaN'”>
<xsl:value-of select=”$t”/>
</xsl:when>
<!– deal with any case booleans –>
<xsl:when test=”translate($t, ‘TRUE’, ‘true’) = ‘true'”>
<xsl:text>true</xsl:text>
</xsl:when>
<xsl:when test=”translate($t, ‘FALSE’, ‘false’) = ‘false'”>
<xsl:text>false</xsl:text>
</xsl:when>
<!– must be text –>
<xsl:otherwise>
<xsl:text>&quot;</xsl:text>
<xsl:value-of select=”$t”/>
<xsl:text>&quot;</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:if test=”position() != last()”>
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:template>

<!– this template handles attribute nodes –>
<xsl:template match=”@*”>
<!– attach prefix to attribute names –>
<xsl:text>&quot;</xsl:text>
<xsl:value-of select=”name()”/>
<xsl:text>&quot; : </xsl:text>
<xsl:variable name=”t” select=”.” />
<xsl:choose>
<xsl:when test=”string(number($t)) != ‘NaN'”>
<xsl:value-of select=”$t”/>
</xsl:when>
<xsl:when test=”translate($t, ‘TRUE’, ‘true’) = ‘true'”>
<xsl:text>true</xsl:text>
</xsl:when>
<xsl:when test=”translate($t, ‘FALSE’, ‘false’) = ‘false'”>
<xsl:text>false</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>&quot;</xsl:text>
<xsl:value-of select=”$t”/>
<xsl:text>&quot;</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:if test=”position() != last()”>
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:template>

</xsl:stylesheet>

The XSLT creates a JavaScript global variable with the same name as the webpart and writes the json data into that variable. Note that I needed to add an additional Parameter Binding:

<ParameterBinding Name=”webpartTitle” Location=”WPProperty(Title)” />

to the webpart so that the JavaScript variable could be named the same thing as the Webpart Title.  Also note that after the webpart is added to the page the ‘Enable Asynchronous Load’ option must be turned off, otherwise the JSON object will be written into an IFrame, and not easily accessible by the scripts on the main page.

Now I can just place these modified XSLTListViewWebParts on my forms page and the validation data I was previously retrieving via ajax calls is delivered with the initial page load and can be accessed using my custom JavaScript. No more ajax callbacks!

As an added benefit I can now filter the data send to the browser by editing the XSLTListViewWebParts, adding/removing columns, setting filter criteria etc.

Other possible uses of this technique could be to create views completely in JavaScript or the XSLT could easily be modified to write out Knockout observables. Also, if you Group the data in the Webpart, group data is even written to the JSON so you could render the groups on the client!

Below is the full source code for a webpart that serializes its data to JSON (Note that you should probably store the XSL in a library rather than inline)

<WebPartPages:XsltListViewWebPart runat=”server” Description=”mapping for contracts” PartOrder=”2″ HelpLink=”” AllowRemove=”True” IsVisible=”True” AllowHide=”True” UseSQLDataSourcePaging=”True” ExportControlledProperties=”False” IsIncludedFilter=”” DataSourceID=”” Title=”MAP_Contracts” ViewFlag=”8388613″ AllowConnect=”True” DisplayName=”JSON” FrameState=”Normal” PageSize=”-1″ PartImageLarge=”” AsyncRefresh=”False” ExportMode=”NonSensitiveData” Dir=”Default” DetailLink=”/sites/testing/sitenamehere/Lists/MAP_Contracts” ShowWithSampleData=”False” ListId=”7f481096-9449-48bd-b02d-e3b8dc62ef95″ ListName=”{7F481096-9449-48BD-B02D-E3B8DC62EF95}” FrameType=”Default” PartImageSmall=”” IsIncluded=”True” SuppressWebPartChrome=”False” AllowEdit=”True” ViewGuid=”{A3EE0A01-B7EB-407E-9FC3-4572CB34B207}” AutoRefresh=”False” AutoRefreshInterval=”60″ AllowMinimize=”True” WebId=”00000000-0000-0000-0000-000000000000″ ViewContentTypeId=”0x” InitialAsyncDataFetch=”False” GhostedXslLink=”main.xsl” MissingAssembly=”Cannot import this Web Part.” HelpMode=”Modeless” ID=”g_a3ee0a01_b7eb_407e_9fc3_4572cb34b207″ ConnectionID=”00000000-0000-0000-0000-000000000000″ AllowZoneChange=”True” TitleUrl=”/sites/testing/sitenamehere/Lists/MAP_Contracts” ManualRefresh=”False” __MarkupType=”vsattributemarkup” __WebPartId=”{A3EE0A01-B7EB-407E-9FC3-4572CB34B207}” __AllowXSLTEditing=”true” __designer:CustomXsl=”fldtypes_Ratings.xsl” WebPart=”true” Height=”” Width=””><ParameterBindings>
<ParameterBinding Name=”webpartTitle” Location=”WPProperty(Title)” />
<ParameterBinding Name=”dvt_sortdir” Location=”Postback;Connection” />
<ParameterBinding Name=”dvt_sortfield” Location=”Postback;Connection” />
<ParameterBinding Name=”dvt_startposition” Location=”Postback” DefaultValue=”” />
<ParameterBinding Name=”dvt_firstrow” Location=”Postback;Connection” />
<ParameterBinding Name=”OpenMenuKeyAccessible” Location=”Resource(wss,OpenMenuKeyAccessible)” />
<ParameterBinding Name=”open_menu” Location=”Resource(wss,open_menu)” />
<ParameterBinding Name=”select_deselect_all” Location=”Resource(wss,select_deselect_all)” />
<ParameterBinding Name=”idPresEnabled” Location=”Resource(wss,idPresEnabled)” />
<ParameterBinding Name=”NoAnnouncements” Location=”Resource(wss,noXinviewofY_LIST)” />
<ParameterBinding Name=”NoAnnouncementsHowTo” Location=”Resource(wss,noXinviewofY_DEFAULT)” />
</ParameterBindings>
<XmlDefinition>
<View Name=”{A3EE0A01-B7EB-407E-9FC3-4572CB34B207}” MobileView=”TRUE” Type=”HTML” DisplayName=”JSON” Url=”/sites/testing/GeraldFixPortal/Lists/MAP_Contracts/JSON.aspx” Level=”1″ BaseViewID=”1″ ContentTypeID=”0x” ImageUrl=”/_layouts/images/generic.png”>
<Method Name=”Read List”/>
<Query>
<GroupBy Collapse=”TRUE” GroupLimit=”30″>
<FieldRef Name=”Exchange”/>
</GroupBy>
<Where>
<Eq>
<FieldRef Name=”Category”/>
<Value Type=”Text”>CME_Futures</Value>
</Eq>
</Where>
</Query>
<ViewFields>
<FieldRef Name=”ID”/>
<FieldRef Name=”Category”/>
<FieldRef Name=”Cname”/>
</ViewFields>
<RowLimit Paged=”TRUE”>2000000</RowLimit>
<Aggregations Value=”Off”/>
<Toolbar Type=”Standard”/>
</View>
</XmlDefinition>
<DataFields>
</DataFields>
<Xsl>
<xsl:stylesheet xmlns:xsl=”http://www.w3.org/1999/XSL/Transform&#8221;
version=”1.0″ xmlns:ddwrt2=”urn:frontpage:internal”>
<xsl:param name=”webpartTitle”></xsl:param><!– need to add parameter binding <ParameterBinding Name=”webpartTitle” Location=”WPProperty(Title)” /> –>
<!– Turn off auto-insertion of <?xml> tag and set indenting on –>
<xsl:output method=”text” encoding=”utf-8″ indent=”yes”/>

<!– strip whitespace from whitespace-only nodes –>
<!–CAUSES ERROR Error while executing web part: System.Xml.Xsl.XslTransformException: White space cannot be stripped from input documents that have already been loaded. Provide the input document as an XmlReader instead. –>
<!– <xsl:strip-space elements=”*”/> –>

<xsl:template match=”/dsQueryResponse”>

<xsl:text>&lt;script type=&quot;text/javascript&quot; &gt; debugger;</xsl:text>
<xsl:value-of select=”$webpartTitle” />
<xsl:text>=</xsl:text>

<xsl:apply-templates></xsl:apply-templates>
<xsl:text>&lt;/script&gt;</xsl:text>
</xsl:template>

<!– handles elements –>
<xsl:template match=”*”>
<!– element name –>
<xsl:text>{ &quot;e&quot; : &quot;</xsl:text>
<xsl:value-of select=”name()”/>
<xsl:text>&quot;</xsl:text>
<xsl:variable name=”ctr” select=”count(*)”/>
<xsl:variable name=”actr” select=”count(@*)”/>
<xsl:variable name=”tctr” select=”count(text())”/>
<!– there will be contents so start an object –>
<xsl:if test=”$actr > 0″>
<xsl:text>, &quot;values&quot; : { </xsl:text>
<xsl:apply-templates select=”@*”/>
<xsl:text>}</xsl:text>
</xsl:if>
<!– handle element nodes –>
<xsl:choose>
<xsl:when test=”$ctr > 1″>
<xsl:text>, &quot;rows&quot; : [ </xsl:text>
<xsl:apply-templates select=”*”/>
<xsl:text> ]</xsl:text>
</xsl:when>
</xsl:choose>
<xsl:text> }</xsl:text>
<xsl:if test=”position() != last()”>
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:template>

<!– this template handle text nodes –>
<xsl:template match=”text()”>
<xsl:variable name=”t” select=”.” />
<xsl:choose>
<!– test to see if it is a number –>
<xsl:when test=”string(number($t)) != ‘NaN'”>
<xsl:value-of select=”$t”/>
</xsl:when>
<!– deal with any case booleans –>
<xsl:when test=”translate($t, ‘TRUE’, ‘true’) = ‘true'”>
<xsl:text>true</xsl:text>
</xsl:when>
<xsl:when test=”translate($t, ‘FALSE’, ‘false’) = ‘false'”>
<xsl:text>false</xsl:text>
</xsl:when>
<!– must be text –>
<xsl:otherwise>
<xsl:text>&quot;</xsl:text>
<xsl:value-of select=”$t”/>
<xsl:text>&quot;</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:if test=”position() != last()”>
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:template>

<!– this template handles attribute nodes –>
<xsl:template match=”@*”>
<!– attach prefix to attribute names –>
<xsl:text>&quot;</xsl:text>
<xsl:value-of select=”name()”/>
<xsl:text>&quot; : </xsl:text>
<xsl:variable name=”t” select=”.” />
<xsl:choose>
<xsl:when test=”string(number($t)) != ‘NaN'”>
<xsl:value-of select=”$t”/>
</xsl:when>
<xsl:when test=”translate($t, ‘TRUE’, ‘true’) = ‘true'”>
<xsl:text>true</xsl:text>
</xsl:when>
<xsl:when test=”translate($t, ‘FALSE’, ‘false’) = ‘false'”>
<xsl:text>false</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>&quot;</xsl:text>
<xsl:value-of select=”$t”/>
<xsl:text>&quot;</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:if test=”position() != last()”>
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:template>

</xsl:stylesheet></Xsl>
</WebPartPages:XsltListViewWebPart>

The Image below shows a sample JSON object called MAP_Contracts that was generated using this XSLT. Note that the generated object has an array called rows that contains field called values that contains the data. In this example I have grouped the view on Exchange so the fields Exchange.COUNT.group, Exchange.groupindex, Exchange.newGroup and exchange.urlencoded were added by the webpart and could be used to do grouping on the client side.

jaonvariable

Advertisements
This entry was posted in ajax, javascript, sharepoint. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s