What Happened To My Office 365 Public Site?

What the heck happened?Update (Evening 7/23/2015)

Microsoft has been looking at this issue, and progress is being made! My public site looks like it has returned to normal … but I know that we’re not quite out of the woods yet.

John from Microsoft followed-up with me yesterday and said the following:

“We have pulled the flight that was impacting everyone from production.  The plan is to address these issues before turning the flight back on.  Would you be up for piloting the upgraded flight before we turn it back on for everyone? Also, you mention you know others who are having problems, would they be willing to pilot the new flight as well?  If so, please provide their contact information so I can follow up with them.”

Clearly, there’s a strong “flying vibe” in Redmond ….

I told John that I was definitely a “go” for the flight, and that I knew some others who’d experienced problems. And that’s where I’m hoping that some of you can help.

If you have been encountering problems with your Office 365 public site that are similar to mine – and you want to be part of the fix – let me know and I’ll hook you up with John. Shoot me your name, email address, and public site URL; I think that will do the job.

Stay tuned!

Original Post (below)

First of all, let me state that I’m not talking about the fact that Office 365 public sites are going the way of the dinosaur. That’s old news at this point. Instead, I’m talking about a “disruption in the force” that some of you may have observed when recently browsing to your public sites and discovering that they had … changed.

And what do I mean by “changed?” In my case, it was the observation that my public site’s branding had been altered to something I hadn’t chosen. The background image was different, the fonts weren’t the same, a number of the CSS styles I had applied to address scrollbar positioning and what-not weren’t in effect, and more. In essence, my custom branding had been completely steamrolled.

The After And The (Sort-Of Before)

Office 365 Public Site: Forcing Some Branding Elements BackOffice 365 Public Site: Busted BrandingDon’t take my word for it, though. Have a look for yourself. On the left is my public site as I discovered it a couple of weeks back (i.e., near the beginning of July, 2015). On the right is the way it’s supposed to look … sort of. The fonts and aspects of the responsive design are still off in the “corrected” version on the right, but I managed to hack the proper background, scrollbars, and a few other elements back to where they were previously. Even though I didn’t get everything corrected, differences between the two are immediately obvious.

I was confident that it had been months since I had changed anything on my public site, but I verified the branding assets in the Office 365 site against what I was tracking in source control. As expected, they were identical. The changes I was observing were not due to anything I had done to the public site.

Grumble Grumble …

Facebook Complaint About Branding IssueSince it wasn’t the first time I’d had issues with unexpected changes and behavior on my public site, I wasted little time before going to Facebook to complain aloud. Many of my friends in the SharePoint community are also friends on Facebook, so I figured I’d get some support (moral, if nothing else) there.

Shortly after posting the update seen on the left, I tagged a couple of my Microsoft friends (specifically, Jeremy Thake and Chris Johnson) who work with the Office 365 team(s) and asked if I should have known about an update or change that might have affected my public site in this fashion. Even though I was confident that I hadn’t done anything to directly impact my public site’s look-and-feel, I was not about to rule out the possibility that I had missed something that had been communicated to me. In fact, I have been consulting in information technology long enough to know that my “mental glove” doesn’t catch everything thrown to me; anymore, I just kind of assume that I am in error and work from there.

Jeremy got back to me first and indicated that he didn’t have anything specific to share, but he indicated he would take the issue to folks (internally) who should know. Shortly after that, Chris tagged Steve Walker (another Microsoftee with superhuman powers) to bring the issue to his attention. Steve told me to file a service request (SR) and that he would escalate that SR right away. So, I filed the service request with supporting screenshots and documentation … and as he had indicated he would, Steve escalated the SR in less than an hour.

In the meantime, I did what I could to get some of my site’s branding back to the way it had been. How did I do that? With the liberal application of the dreaded !important CSS directive in the custom style sheet I had created to go with my master page. The !important directive is definitely not something I use (or even like to go near) on a daily basis, but in this case, I was trying to achieve results with a minimal investment of time and effort. I needed my styling to trump whatever was being laid-down after my style sheet was being processed.

So, What Happened Next?

Following Steve’s escalation, I started working with an extremely approachable escalation engineer named John. John and I exchanged some emails, did a late-night screen-sharing session, and generally looked things up-and-down. John concurred that what we were seeing shouldn’t have been happening, and he mentioned that he had another customer or two that seemed to be having a similar problem. Some tracing in those cases seemed to implicate a recent client-side theming change that had been made and rolled-out.

Analysis Of Styles In Internet Explorer Developer ToolsAn issue with client-side theming “felt” right to me. I had used Internet Explorer’s Developer Tools to do some backtracking into the source of the errant styles that were being applied to my site, and I noticed that the background image was being served from a temporary theme directory on the server. Until I started forcing my styles to override the ones that were being applied (an example of which is shown on the right), the theme styles were trumping my own styles.

John was out for a while, but he came back to me recently with the following explanation. And his explanation makes complete sense:

So the problem you were having was the result of a bad interaction with a third-party CSS minification technique called minisp.  By adding HTML comments around our CssLink controls in the master page, the <link> tags we normally render are part of a comment and therefore not part of the DOM.

When Client-Side Theming goes to replace the CSS on these pages, it wants to put the generated <style> blocks immediately before the corresponding <link> tags. Since it can’t find the <link> tags in the DOM, it defaults to adding the <style> blocks to the end of the document head. Since these come after the custom CSS, the rules in our themed CSS take priority over the rules in the custom CSS.

Resolution?

As of July 21st, 2015, there is no official resolution. Since this has been identified as a problem in how client-side theming handles CSS <style> block insertions, though, the ultimate fix needs to come from Microsoft. In the meantime, I’ll be sticking with my hacked CSS style sheet to get back most of the look-and-feel that I need. I could expend additional (development) effort to ensure that my styles are applied after the Office 365 theme styles without using !important, but there are plenty of other more important tasks vying for my attention right now.

Thumbs UpSo, if you found this post and it’s helping you to realize that you’re not going insane (at least not because of public site branding changes you didn’t make), I’ll feel that my job is done.

As I hear more and changes take place, I’ll try to update this post accordingly. Check back every now and then if you want the play-by-play on this issue.

Parting Thoughts: A Tip Of My Hat To Microsoft

In the past, I’ve been pretty vocal about the way that Microsoft has sort of “rolled” changes onto its Office 365 customer base and failed to communicate problems in a timely and complete fashion – actions (or lack of action) that ultimately caused pain and problems. As vocal as I’ve been in those situations, I want to go on-record as saying (just as loudly) that Microsoft has definitely listened to the critical feedback it has been receiving and has acted to make changes that we have indicated we need.

Even though this public site branding issue is indeed a bug, Microsoft listened and responded quickly – without protest, without claiming that “nothing is wrong,” and without some of the problem behaviors I used to see.

Where there were previously few communications about Office 365 outages, problems, changes, and updates, we now have a boatload of information (with some of it actually being pushed) to us to keep us in-the-know on our tenants, where they stand at any given point in time, and where they are going. I can get both at-a-glance health information and deep explanations for issues using the administrative portal’s Service Health dashboard. I get push notifications whenever something happens in my tenant using the Office 365 Admin application that runs on my Windows Phone. I know when new service features and capabilities are rolling out, if changes have been cancelled, etc., by looking at the Office 365 Roadmap. And these are just some of the channels and information streams that are available.

Is Microsoft “all the way there” yet? No, but they are dramatically further along than when Office 365 first rolled-out. Outages still occur – as they do with any service – but I feel like I know what’s going on now. That’s a huge improvement in my book.

References and Resources

  1. Microsoft Support: Information about changes to the SharePoint Online Public Website feature in Office 365
  2. Office 365 Public Site: Bitstream Foundry LLC
  3. LinkedIn: Jeremy Thake
  4. LinkedIn: Chris Johnson
  5. LinkedIn: Steve Walker
  6. Stack Overflow: What are the implications of using “!important” in CSS?
  7. MSDN: Using the F12 developer tools
  8. Microsoft: Office 365 Service Health Status
  9. Windows Phone App Store: Office 365 Admin
  10. Office.com: Office 365 Roadmap

Advanced Paging with SharePoint Content Search Web Part Display Templates

Click here to download associated ZIP file with display templatesPages in a BookI’ve had an opportunity to play with SharePoint 2013’s Content Search Web Part (CSWP) on a number of occasions in the last couple of years, and I have to say that I like it a lot. The CSWP can be employed to address a whole host of different use cases; in fact, in many situations I’ve found that it can be used to solve problems that were previously addressable only through custom code.

Search-driven content in SharePoint isn’t anything new, of course, but the display templates that are used to format search results in SharePoint 2013 are a large part of the CSWP’s “special sauce.” Through the use of a Control display template and an Item display template, it is possible to select, arrange, and style the search results that are returned and shown to users in a highly customizable fashion. With some knowledge of HTML and the help of SharePoint 2013’s Design Manager, it’s possible to produce some pretty impressive looking content. And if you know JavaScript, well … the sky is the limit on what you can produce.

Control And Item Display Templates

DisplayAndPropertyMappingsBefore I dive into paging and how I’ve tried to stretch what can be done with the CSWP, I want to briefly examine some display template basics. Although this post isn’t intended to be a primer on the CSWP and its associated display templates, there are a few items worth reviewing.

On the right is a snippet of a ToolPart containing some configuration data for a particular CSWP displaying some very basic data. The examples that follow (i.e., the two images shown below) use the configuration shown in the ToolPart, so refer to it if needed.

The first example below is how a CSWP showing three results might appear to an end user when the List with Paging Control template and Two lines Item template are applied. The second example (to the right of the first) differentiates which content regions on the CSWP are being driven by the List with Paging Control template (shown with green highlighting) and which are being driven by the Two lines Item template (shown in red).

Content Search Web Part Example as Seen by End User Content Search Web Part with Template Regions Highlighted

Through these images, I’m trying to convey a very simple point: the Control template dictates how the search results’ “container” appears, and the Item template determines how each individual search result within the container is displayed. The contents of any CSWP are rendered by the output of a single Control display template and zero or more Item display templates (again, one for each search result/item shown).

Nothing To See Here

As you might have guessed from the title of this post, one functional area that seems relatively unexplored and underdeveloped (in my experience) is that of paging and how paging controls are made available to end users of the CSWP. This is unfortunate, because whenever more results are returned than can be displayed at once – a very common scenario – some form of paging is needed.

Out of the box (OOTB), SharePoint 2013 comes with only a handful of Control and Item display templates that can be used with the CSWP to format your search results. Of these display templates, only one contains paging controls: the List with Paging Control template. If all you require is the basic forward/backward paging offered by the two buttons it contains, then the List with Paging display template may be adequate for your needs.

Personally, I think the List with Paging template is bland and kind of … ugly. It works, sure, but it doesn’t display several pieces of information that I find important; for example, the total number of search results and the result page that the user is currently on. Worse is the fact that it doesn’t provide any sort of mechanism to jump to a specific page. The best that an end-user can do is page forward or backward one page at a time.

Better Paging Support

CSWP With Advanced Paging SupportOne of the talks I’ve been giving recently at various SharePoint events and conferences is titled SharePoint’s New Swiss Army Knife: The Content Search Web Part. During that talk, I demonstrate a set of CSWP display templates that I put together to generate something decidedly “non-search” in appearance – like a directory of files to which the current user has access within the current site collection. An image of that CSWP example appears on the left.

This file directory CSWP instance was generated with three simple files: two custom display templates (one for the Control, and one for each Item shown) and a cascading style sheet. The actual result data isn’t particularly remarkable, but the manner in which the paging is implemented is what tends to catch people’s attention. With the Control template I created, end-users can:

  • See the total number of documents (i.e., search results) to which they have access
  • Clearly see which page of search results they’re on through the boxed page number and the “Page xx of yy” label
  • Identify which items/results they’re viewing through the “Items xxx to yyy of zzz” label
  • Jump directly to a page of results by clicking the specified page number

Finding all of this information and displaying it through the CSWP Control template took some research and tinkering. Some of the information was available directly within the search results that were passed to the CSWP, but some of it wasn’t. In the case where some desired information wasn’t available, it was computed with some basic math.

Overview Of The CSWP HTML

By default, the CSWP implements a client-side processing and paging model. Search results that are displayed are controlled by JavaScript functions contained within the Control and Item display templates that have been assigned to the CSWP. When a set of search results is being processed for display within the client browser, the Control template (which is the results container) gets called first to render the HTML that will frame or house the search items/results. For each search result or item that is passed to the CSWP, the Item display template then gets called to create a snippet of HTML for the result/item that can be inserted into the “frame” (commonly a <div>) created by Control display template.

An example of the rendered HTML for the search results shown in the previous “Better Paging Support” section appears below.

<div id="sas-lib-listing">
	<div>Documents To Which I Have Access</div>
	<ul class="sas-table">
		<li class="sas-tablerow">
			<span class="sas-tableheader">Title of Document</span>
		</li>
		<li class="sas-tablerow">
			<div class="sas-tablecell" id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr1_SAS_Item_Template_container" data-displaytemplate="SAS_Item_Template">
				<div id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr1_SAS_Item_Template_dataContainer">
					<a title="3. Optimus Prime Part - Button" id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr1_SAS_Item_Template_displayTextLink"
					href="http://sp2013-dev:18480/SiteAssets/Forms/DispForm.aspx?ID=4">3. Optimus Prime Part - Button</a>
				</div>
			</div>
		</li>
		<li class="sas-tablerow">
			<div class="sas-tablecell" id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr2_SAS_Item_Template_container" data-displaytemplate="SAS_Item_Template">
				<div id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr2_SAS_Item_Template_dataContainer">
					<a title="4. Weather Right Now (Not Really) - Button" id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr2_SAS_Item_Template_displayTextLink"
					href="http://sp2013-dev:18480/SiteAssets/Forms/DispForm.aspx?ID=5">4. Weather Right Now (Not Really) - Button</a>
				</div>
			</div>
		</li>
		<li class="sas-tablerow">
			<div class="sas-tablecell" id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr3_SAS_Item_Template_container" data-displaytemplate="SAS_Item_Template">
				<div id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr3_SAS_Item_Template_dataContainer">
					<a title="5. Weather Right Now Page - Button" id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr3_SAS_Item_Template_displayTextLink"
					href="http://sp2013-dev:18480/SiteAssets/Forms/DispForm.aspx?ID=6">5. Weather Right Now Page - Button</a>
				</div>
			</div>
		</li>
		<li class="sas-tablerow">
			<div class="sas-tablecell" id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr4_SAS_Item_Template_container" data-displaytemplate="SAS_Item_Template">
				<div id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr4_SAS_Item_Template_dataContainer">
					<a title="6. Weather Right Now Provisioned - Button" id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr4_SAS_Item_Template_displayTextLink"
					href="http://sp2013-dev:18480/SiteAssets/Forms/DispForm.aspx?ID=8">6. Weather Right Now Provisioned - Button</a>
				</div>
			</div>
		</li>
		<li class="sas-tablerow">
			<div class="sas-tablecell" id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr5_SAS_Item_Template_container" data-displaytemplate="SAS_Item_Template">
				<div id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr5_SAS_Item_Template_dataContainer">
					<a title="8. Donuts And Drinks Pub - Button" id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr5_SAS_Item_Template_displayTextLink"
					href="http://sp2013-dev:18480/SiteAssets/Forms/DispForm.aspx?ID=9">8. Donuts And Drinks Pub - Button</a>
				</div>
			</div>
		</li>
		<li class="sas-tablerow">
			<div class="sas-tablecell" id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr6_SAS_Item_Template_container" data-displaytemplate="SAS_Item_Template">
				<div id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr6_SAS_Item_Template_dataContainer">
					<a title="WeatherRightNowScraper" id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr6_SAS_Item_Template_displayTextLink"
					href="http://sp2013-dev:18480/SitePages/WeatherRightNowScraper.aspx">WeatherRightNowScraper</a>
				</div>
			</div>
		</li>
		<li class="sas-tablerow">
			<div class="sas-tablecell" id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr7_SAS_Item_Template_container" data-displaytemplate="SAS_Item_Template">
				<div id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr7_SAS_Item_Template_dataContainer">
					<a title="DonutsAndDrinksPub" id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr7_SAS_Item_Template_displayTextLink"
					href="http://sp2013-dev:18480/Pages/DonutsAndDrinksPub.aspx">DonutsAndDrinksPub</a>
				</div>
			</div>
		</li>
		<li class="sas-tablerow">
			<div class="sas-tablecell" id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr8_SAS_Item_Template_container" data-displaytemplate="SAS_Item_Template">
				<div id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr8_SAS_Item_Template_dataContainer">
					<a title="DonutsAndDrinks" id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr8_SAS_Item_Template_displayTextLink"
					href="http://sp2013-dev:18480/SitePages/DonutsAndDrinks.aspx">DonutsAndDrinks</a>
				</div>
			</div>
		</li>
		<li class="sas-tablerow">
			<div class="sas-tablecell" id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr9_SAS_Item_Template_container" data-displaytemplate="SAS_Item_Template">
				<div id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr9_SAS_Item_Template_dataContainer">
					<a title="MakingPiTakesEffort" id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr9_SAS_Item_Template_displayTextLink"
					href="http://sp2013-dev:18480/SitePages/MakingPiTakesEffort.aspx">MakingPiTakesEffort</a>
				</div>
			</div>
		</li>
		<li class="sas-tablerow">
			<div class="sas-tablecell" id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr10_SAS_Item_Template_container" data-displaytemplate="SAS_Item_Template">
				<div id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr10_SAS_Item_Template_dataContainer">
					<a title="OptimusPrimePart" id="ctl00_ctl39_g_85d5f2f9_fbe4_4e9b_9d51_647e179b87f9_csr10_SAS_Item_Template_displayTextLink"
					href="http://sp2013-dev:18480/SitePages/OptimusPrimePart.aspx">OptimusPrimePart</a>
				</div>
			</div>
		</li>
	</ul>
	<div class="sas-paging-table">
		<div class="sas-paging-tablerow">
			<div class="sas-paging-tablecell">
				<a onclick="$getClientControl(this).page(1);return Srch.U.cancelEvent(event);" href="#">1</a>
			</div>
			<div class="sas-paging-tablecell">
				<a onclick="$getClientControl(this).page(11);return Srch.U.cancelEvent(event);" href="#">2</a>
			</div>
			<div class="sas-paging-tablecell">
				<a onclick="$getClientControl(this).page(21);return Srch.U.cancelEvent(event);" href="#">3</a>
			</div>
			<div class="sas-paging-tablecell">
				<a onclick="$getClientControl(this).page(31);return Srch.U.cancelEvent(event);" href="#">4</a>
			</div>
			<div class="sas-paging-tablecell">
				<a onclick="$getClientControl(this).page(41);return Srch.U.cancelEvent(event);" href="#">5</a>
			</div>
			<div class="sas-paging-tablecell">
				<a onclick="$getClientControl(this).page(51);return Srch.U.cancelEvent(event);" href="#">6</a>
			</div>
			<div class="sas-paging-tablecell">
				<a onclick="$getClientControl(this).page(61);return Srch.U.cancelEvent(event);" href="#">7</a>
			</div>
			<div class="sas-paging-tablecell">
				<a onclick="$getClientControl(this).page(71);return Srch.U.cancelEvent(event);" href="#">8</a>
			</div>
			<div class="sas-paging-tablecell">
				<a onclick="$getClientControl(this).page(81);return Srch.U.cancelEvent(event);" href="#">9</a>
			</div>
			<div class="sas-paging-tablecell">
				<a onclick="$getClientControl(this).page(91);return Srch.U.cancelEvent(event);" href="#">10</a>
			</div>
			<div class="sas-paging-tablecell">
				<a onclick="$getClientControl(this).page(101);return Srch.U.cancelEvent(event);" href="#">11</a>
			</div>
			<div class="sas-paging-tablecell">
				<a onclick="$getClientControl(this).page(111);return Srch.U.cancelEvent(event);" href="#">12</a>
			</div>
			<div class="sas-paging-tablecell">
				<a onclick="$getClientControl(this).page(121);return Srch.U.cancelEvent(event);" href="#">13</a>
			</div>
			<div class="sas-paging-tablecell sas-paging-currentpage">14</div>
			<div class="sas-paging-tablecell">
				<a onclick="$getClientControl(this).page(141);return Srch.U.cancelEvent(event);" href="#">15</a>
			</div>
			<div class="sas-paging-tablecell">
				<a onclick="$getClientControl(this).page(151);return Srch.U.cancelEvent(event);" href="#">16</a>
			</div>
			<div class="sas-paging-tablecell">
				<a onclick="$getClientControl(this).page(161);return Srch.U.cancelEvent(event);" href="#">17</a>
			</div>
			<div class="sas-paging-tablecell">
				<a onclick="$getClientControl(this).page(171);return Srch.U.cancelEvent(event);" href="#">18</a>
			</div>
			<div class="sas-paging-tablecell sas-paging-summary">Page
				<span>14</span>of
				<span>18</span>, items
				<span>131</span>to
				<span>140</span>of
				<span>179</span>.</div>
		</div>
	</div>
</div>

The Control template (SwissArmy_Control_Template.js) creates the top level <div> and a child <ul> element (with a CSS class of “sas-table”) for the overall HTML structure, as well as the individual <li> elements used to contain each search/result item. For each search result/item, the Item template (SwissArmy_Item_Template.js) creates a <div> block that is inserted as a child within an <li> element. Each of the individual item <div> blocks created by the Item template has a CSS class of “sas-tablecell” for easier styling.

Below the <ul> block housing the search results is another <div> with a CSS class of “sas-paging-table.” As suggested by the class name, the <div> is used to house the clickable page numbers and additional paging information seen at the bottom of the CSWP control. And like the rest of the container information, this HTML is generated within the Control template.

Fetching Search Results

Each time a set of search results is needed, either on initial page rendering or when the user moves to a new page, the CSWP calls back to its SharePoint site collection for the data it needs. By default, this call occurs asynchronously; however, the CSWP can be configured to make such calls synchronously within the normal page processing sequence.

For example, if I had a page containing a CSWP at the following URL

http://sp2013-dev:18580/DisplayTemplateStyling

… the CSWP on that page would request search results for display by posting a request to the following endpoint:

http://sp2013-dev:18580/_vti_bin/client.svc/ProcessQuery

As part of its request, the CSWP passes an XML structure to the client.svc web service that looks something like the XML that appears below. This structure contains all of the information the ProcessQuery method needs to determine which search results should be sent back:

<Request xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="15.0.0.0" LibraryVersion="15.0.0.0" ApplicationName="Javascript Library">
	<Actions>
		<ObjectPath Id="1" ObjectPathId="0" />
		<SetProperty Id="2" ObjectPathId="0" Name="QueryTemplate">
			<Parameter Type="String">(contentclass:STS_ListItem OR IsDocument:True)</Parameter>
		</SetProperty>
		<ObjectPath Id="4" ObjectPathId="3" />
		<Method Name="Add" Id="5" ObjectPathId="3">
			<Parameters>
				<Parameter Type="String">LastModifiedTime</Parameter>
				<Parameter Type="Number">1</Parameter>
			</Parameters>
		</Method>
		<SetProperty Id="6" ObjectPathId="0" Name="StartRow">
			<Parameter Type="Number">130</Parameter>
		</SetProperty>
		<SetProperty Id="7" ObjectPathId="0" Name="RowsPerPage">
			<Parameter Type="Number">10</Parameter>
		</SetProperty>
		<SetProperty Id="8" ObjectPathId="0" Name="RowLimit">
			<Parameter Type="Number">10</Parameter>
		</SetProperty>
		<SetProperty Id="9" ObjectPathId="0" Name="TotalRowsExactMinimum">
			<Parameter Type="Number">141</Parameter>
		</SetProperty>
		<SetProperty Id="10" ObjectPathId="0" Name="SourceId">
			<Parameter Type="Guid">{8413cd39-2156-4e00-b54d-11efd9abdb89}</Parameter>
		</SetProperty>
		<ObjectPath Id="12" ObjectPathId="11" />
		<Method Name="SetQueryPropertyValue" Id="13" ObjectPathId="11">
			<Parameters>
				<Parameter Type="String">SourceName</Parameter>
				<Parameter TypeId="{b25ba502-71d7-4ae4-a701-4ca2fb1223be}">
					<Property Name="BoolVal" Type="Boolean">false</Property>
					<Property Name="IntVal" Type="Number">0</Property>
					<Property Name="QueryPropertyValueTypeIndex" Type="Number">1</Property>
					<Property Name="StrArray" Type="Null" />
					<Property Name="StrVal" Type="String">Local SharePoint Results</Property>
				</Parameter>
			</Parameters>
		</Method>
		<Method Name="SetQueryPropertyValue" Id="14" ObjectPathId="11">
			<Parameters>
				<Parameter Type="String">SourceLevel</Parameter>
				<Parameter TypeId="{b25ba502-71d7-4ae4-a701-4ca2fb1223be}">
					<Property Name="BoolVal" Type="Boolean">false</Property>
					<Property Name="IntVal" Type="Number">0</Property>
					<Property Name="QueryPropertyValueTypeIndex" Type="Number">1</Property>
					<Property Name="StrArray" Type="Null" />
					<Property Name="StrVal" Type="String">Ssa</Property>
				</Parameter>
			</Parameters>
		</Method>
		<ObjectPath Id="16" ObjectPathId="15" />
		<Method Name="Add" Id="17" ObjectPathId="15">
			<Parameters>
				<Parameter Type="String">Path</Parameter>
			</Parameters>
		</Method>
		<Method Name="Add" Id="18" ObjectPathId="15">
			<Parameters>
				<Parameter Type="String">Title</Parameter>
			</Parameters>
		</Method>
		<Method Name="Add" Id="19" ObjectPathId="15">
			<Parameters>
				<Parameter Type="String">
 FileExtension</Parameter>
			</Parameters>
		</Method>
		<Method Name="Add" Id="20" ObjectPathId="15">
			<Parameters>
				<Parameter Type="String">
 SecondaryFileExtension
</Parameter>
			</Parameters>
		</Method>
		<ObjectPath Id="22" ObjectPathId="21" />
		<Method Name="Add" Id="23" ObjectPathId="21">
			<Parameters>
				<Parameter Type="String">Title</Parameter>
			</Parameters>
		</Method>
		<Method Name="Add" Id="24" ObjectPathId="21">
			<Parameters>
				<Parameter Type="String">Path</Parameter>
			</Parameters>
		</Method>
		<Method Name="Add" Id="25" ObjectPathId="21">
			<Parameters>
				<Parameter Type="String">Author</Parameter>
			</Parameters>
		</Method>
		<Method Name="Add" Id="26" ObjectPathId="21">
			<Parameters>
				<Parameter Type="String">SectionNames</Parameter>
			</Parameters>
		</Method>
		<Method Name="Add" Id="27" ObjectPathId="21">
			<Parameters>
				<Parameter Type="String">SiteDescription</Parameter>
			</Parameters>
		</Method>
		<SetProperty Id="28" ObjectPathId="0" Name="TrimDuplicates">
			<Parameter Type="Boolean">false</Parameter>
		</SetProperty>
		<Method Name="SetQueryPropertyValue" Id="29" ObjectPathId="11">
			<Parameters>
				<Parameter Type="String">TryCache</Parameter>
				<Parameter TypeId="{b25ba502-71d7-4ae4-a701-4ca2fb1223be}">
					<Property Name="BoolVal" Type="Boolean">true</Property>
					<Property Name="IntVal" Type="Number">0</Property>
					<Property Name="QueryPropertyValueTypeIndex" Type="Number">3</Property>
					<Property Name="StrArray" Type="Null" />
					<Property Name="StrVal" Type="Null" />
				</Parameter>
			</Parameters>
		</Method>
		<Method Name="SetQueryPropertyValue" Id="30" ObjectPathId="11">
			<Parameters>
				<Parameter Type="String">Scope</Parameter>
				<Parameter TypeId="{b25ba502-71d7-4ae4-a701-4ca2fb1223be}">
					<Property Name="BoolVal" Type="Boolean">false</Property>
					<Property Name="IntVal" Type="Number">0</Property>
					<Property Name="QueryPropertyValueTypeIndex" Type="Number">1</Property>
					<Property Name="StrArray" Type="Null" />
					<Property Name="StrVal" Type="String">{Site.URL}</Property>
				</Parameter>
			</Parameters>
		</Method>
		<Method Name="SetQueryPropertyValue" Id="31" ObjectPathId="11">
			<Parameters>
				<Parameter Type="String">UpdateLinksForCatalogItems</Parameter>
				<Parameter TypeId="{b25ba502-71d7-4ae4-a701-4ca2fb1223be}">
					<Property Name="BoolVal" Type="Boolean">true</Property>
					<Property Name="IntVal" Type="Number">0</Property>
					<Property Name="QueryPropertyValueTypeIndex" Type="Number">3</Property>
					<Property Name="StrArray" Type="Null" />
					<Property Name="StrVal" Type="Null" />
				</Parameter>
			</Parameters>
		</Method>
		<Method Name="SetQueryPropertyValue" Id="32" ObjectPathId="11">
			<Parameters>
				<Parameter Type="String">EnableStacking</Parameter>
				<Parameter TypeId="{b25ba502-71d7-4ae4-a701-4ca2fb1223be}">
					<Property Name="BoolVal" Type="Boolean">true</Property>
					<Property Name="IntVal" Type="Number">0</Property>
					<Property Name="QueryPropertyValueTypeIndex" Type="Number">3</Property>
					<Property Name="StrArray" Type="Null" />
					<Property Name="StrVal" Type="Null" />
				</Parameter>
			</Parameters>
		</Method>
		<Method Name="SetQueryPropertyValue" Id="33" ObjectPathId="11">
			<Parameters>
				<Parameter Type="String">ListId</Parameter>
				<Parameter TypeId="{b25ba502-71d7-4ae4-a701-4ca2fb1223be}">
					<Property Name="BoolVal" Type="Boolean">false</Property>
					<Property Name="IntVal" Type="Number">0</Property>
					<Property Name="QueryPropertyValueTypeIndex" Type="Number">1</Property>
					<Property Name="StrArray" Type="Null" />
					<Property Name="StrVal" Type="String">fb9e18d3-1d80-4ab1-8e76-bc36b0a8e22d</Property>
				</Parameter>
			</Parameters>
		</Method>
		<Method Name="SetQueryPropertyValue" Id="34" ObjectPathId="11">
			<Parameters>
				<Parameter Type="String">ListItemId</Parameter>
				<Parameter TypeId="{b25ba502-71d7-4ae4-a701-4ca2fb1223be}">
					<Property Name="BoolVal" Type="Boolean">false</Property>
					<Property Name="IntVal" Type="Number">6</Property>
					<Property Name="QueryPropertyValueTypeIndex" Type="Number">2</Property>
					<Property Name="StrArray" Type="Null" />
					<Property Name="StrVal" Type="Null" />
				</Parameter>
			</Parameters>
		</Method>
		<Method Name="SetQueryPropertyValue" Id="35" ObjectPathId="11">
			<Parameters>
				<Parameter Type="String">TermId</Parameter>
				<Parameter TypeId="{b25ba502-71d7-4ae4-a701-4ca2fb1223be}">
					<Property Name="BoolVal" Type="Boolean">false</Property>
					<Property Name="IntVal" Type="Number">0</Property>
					<Property Name="QueryPropertyValueTypeIndex" Type="Number">1</Property>
					<Property Name="StrArray" Type="Null" />
					<Property Name="StrVal" Type="String">0bfc89ed-1741-4925-9d25-828eaf74b2c8</Property>
				</Parameter>
			</Parameters>
		</Method>
		<Method Name="SetQueryPropertyValue" Id="36" ObjectPathId="11">
			<Parameters>
				<Parameter Type="String">TermSetId</Parameter>
				<Parameter TypeId="{b25ba502-71d7-4ae4-a701-4ca2fb1223be}">
					<Property Name="BoolVal" Type="Boolean">false</Property>
					<Property Name="IntVal" Type="Number">0</Property>
					<Property Name="QueryPropertyValueTypeIndex" Type="Number">1</Property>
					<Property Name="StrArray" Type="Null" />
					<Property Name="StrVal" Type="String">0f14334e-b929-42bf-ad10-ed6a93d16a4f</Property>
				</Parameter>
			</Parameters>
		</Method>
		<Method Name="SetQueryPropertyValue" Id="37" ObjectPathId="11">
			<Parameters>
				<Parameter Type="String">TermStoreId</Parameter>
				<Parameter TypeId="{b25ba502-71d7-4ae4-a701-4ca2fb1223be}">
					<Property Name="BoolVal" Type="Boolean">false</Property>
					<Property Name="IntVal" Type="Number">0</Property>
					<Property Name="QueryPropertyValueTypeIndex" Type="Number">1</Property>
					<Property Name="StrArray" Type="Null" />
					<Property Name="StrVal" Type="String">c4c58328-0e88-4937-91e9-cf7526b9b0db</Property>
				</Parameter>
			</Parameters>
		</Method>
		<SetProperty Id="38" ObjectPathId="0" Name="ResultsUrl">
			<Parameter Type="String">http://sp2013-dev:18580/DisplayTemplateStyling#k=#s=131</Parameter>
		</SetProperty>
		<SetProperty Id="39" ObjectPathId="0" Name="BypassResultTypes">
			<Parameter Type="Boolean">true</Parameter>
		</SetProperty>
		<SetProperty Id="40" ObjectPathId="0" Name="ClientType">
			<Parameter Type="String">ContentSearchRegular</Parameter>
		</SetProperty>
		<SetProperty Id="41" ObjectPathId="0" Name="EnableInterleaving">
			<Parameter Type="Boolean">false</Parameter>
		</SetProperty>
		<SetProperty Id="42" ObjectPathId="0" Name="ProcessBestBets">
			<Parameter Type="Boolean">false</Parameter>
		</SetProperty>
		<Method Name="SetQueryPropertyValue" Id="43" ObjectPathId="11">
			<Parameters>
				<Parameter Type="String">QuerySession</Parameter>
				<Parameter TypeId="{b25ba502-71d7-4ae4-a701-4ca2fb1223be}">
					<Property Name="BoolVal" Type="Boolean">false</Property>
					<Property Name="IntVal" Type="Number">0</Property>
					<Property Name="QueryPropertyValueTypeIndex" Type="Number">1</Property>
					<Property Name="StrArray" Type="Null" />
					<Property Name="StrVal" Type="String">a4e0e9b5-6dd9-4aea-b38b-26f325c67fdf</Property>
				</Parameter>
			</Parameters>
		</Method>
		<SetProperty Id="44" ObjectPathId="0" Name="ProcessPersonalFavorites">
			<Parameter Type="Boolean">false</Parameter>
		</SetProperty>
		<SetProperty Id="45" ObjectPathId="0" Name="SafeQueryPropertiesTemplateUrl">
			<Parameter Type="String">querygroup://webroot/Pages/DisplayTemplateStyling.aspx?groupname=Default</Parameter>
		</SetProperty>
		<SetProperty Id="46" ObjectPathId="0" Name="IgnoreSafeQueryPropertiesTemplateUrl">
			<Parameter Type="Boolean">false</Parameter>
		</SetProperty>
		<ObjectPath Id="48" ObjectPathId="47" />
		<ExceptionHandlingScope Id="49">
			<TryScope Id="51">
				<Method Name="ExecuteQueries" Id="53" ObjectPathId="47">
					<Parameters>
						<Parameter Type="Array">
							<Object Type="String">26103082-0e16-4ab0-a12c-bded636a7fb8Default</Object>
						</Parameter>
						<Parameter Type="Array">
							<Object ObjectPathId="0" />
						</Parameter>
						<Parameter Type="Boolean">true</Parameter>
					</Parameters>
				</Method>
			</TryScope>
			<CatchScope Id="55" />
		</ExceptionHandlingScope>
	</Actions>
	<ObjectPaths>
		<Constructor Id="0" TypeId="{80173281-fffd-47b6-9a49-312e06ff8428}" />
		<Property Id="3" ParentId="0" Name="SortList" />
		<Property Id="11" ParentId="0" Name="Properties" />
		<Property Id="15" ParentId="0" Name="SelectProperties" />
		<Property Id="21" ParentId="0" Name="HitHighlightedProperties" />
		<Constructor Id="47" TypeId="{8d2ac302-db2f-46fe-9015-872b35f15098}" />
	</ObjectPaths>
</Request>				 				 			 			 		 	 	 		 		 		 		 		 		 	  

Once the server finds the desired search results and processes them, it packages them up as a JSON object and returns that object to the browser for further action. The following is an example of a JSON structure (containing ten results) that was returned for the Swiss Army Knife CSWP as it was shown earlier in the “Better Paging Support” section:

[  
   {  
      "SchemaVersion":"15.0.0.0",
      "LibraryVersion":"15.0.4569.1501",
      "ErrorInfo":null,
      "TraceCorrelationId":"36d5029d-dcee-709e-a995-81ad013126e0"
   },
   119,
   {  
      "IsNull":false
   },
   122,
   {  
      "IsNull":false
   },
   130,
   {  
      "IsNull":false
   },
   134,
   {  
      "IsNull":false
   },
   140,
   {  
      "IsNull":false
   },
   166,
   {  
      "IsNull":false
   },
   171,
   {  
      "418e02d2-9bc5-46fd-ada2-83762ed79256Default":{  
         "_ObjectType_":"Microsoft.SharePoint.Client.Search.Query.ResultTableCollection",
         "ElapsedTime":31,
         "Properties":{  
            "RowLimit":10,
            "SourceId":"\/Guid(8413cd39-2156-4e00-b54d-11efd9abdb89)\/",
            "EnableStacking":true,
            "SerializedQuery":"<Query Culture=\"en-US\" EnableStemming=\"True\" EnablePhonetic=\"False\" EnableNicknames=\"False\" IgnoreAllNoiseQuery=\"True\" SummaryLength=\"180\" MaxSnippetLength=\"180\" DesiredSnippetLength=\"90\" KeywordInclusion=\"0\" QueryText=\"\" QueryTemplate=\"(contentclass:STS_ListItem OR IsDocument:True)\" TrimDuplicates=\"False\" Site=\"e964e9f4-d726-4c46-bda6-00367a81bb29\" Web=\"1b739834-de53-4968-b4cc-04a559306668\" KeywordType=\"True\" HiddenConstraints=\"\" \u002f>"
         },
         "QueryErrors":null,
         "QueryId":"e12d2e7f-598c-4db7-ae6e-fbf021d0b230",
         "SpellingSuggestion":"",
         "TriggeredRules":[  

         ],
         "ResultTables":[  
            {  
               "_ObjectType_":"Microsoft.SharePoint.Client.Search.Query.ResultTable",
               "GroupTemplateId":null,
               "ItemTemplateId":null,
               "Properties":{  
                  "GenerationId":9223372036854775806,
                  "ExecutionTimeMs":16,
                  "QueryModification":"(contentclass:STS_ListItem OR IsDocument:True) -ContentClass=urn:content-class:SPSPeople",
                  "RenderTemplateId":"~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fGroup_Default.js",
                  "StartRecord":0
               },
               "QueryId":"e12d2e7f-598c-4db7-ae6e-fbf021d0b230",
               "QueryRuleId":"00000000-0000-0000-0000-000000000000",
               "ResultRows":[  
                  {  
                     "Rank":0,
                     "DocId":114,
                     "Path":"http:\u002f\u002fsp2013-dev:18480\u002fSiteAssets\u002fForms\u002fDispForm.aspx?ID=4",
                     "Title":"3. Optimus Prime Part - Button",
                     "OriginalPath":"http:\u002f\u002fsp2013-dev:18480\u002fSiteAssets\u002fForms\u002fDispForm.aspx?ID=4",
                     "PartitionId":"\/Guid(0c37852b-34d0-418e-91c6-2ac25af4be5b)\/",
                     "UrlZone":0,
                     "AAMEnabledManagedProperties":"AttachmentURI;deeplinks;DefaultEncodingURL;ExternalMediaURL;HierarchyUrl;OrgParentUrls;OrgUrls;OriginalPath;ParentLink;Path;PictureThumbnailURL;PictureURL;PublishingImage;recommendedfor;ServerRedirectedEmbedURL;ServerRedirectedPreviewURL;ServerRedirectedURL;SiteLogo;SitePath;SPSiteURL;UserEncodingURL",
                     "RenderTemplateId":"~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fItem_Default.js",
                     "QueryRuleId":"\/Guid(00000000-0000-0000-0000-000000000000)\/"
                  },
                  {  
                     "Rank":0,
                     "DocId":115,
                     "Path":"http:\u002f\u002fsp2013-dev:18480\u002fSiteAssets\u002fForms\u002fDispForm.aspx?ID=5",
                     "Title":"4. Weather Right Now (Not Really) - Button",
                     "OriginalPath":"http:\u002f\u002fsp2013-dev:18480\u002fSiteAssets\u002fForms\u002fDispForm.aspx?ID=5",
                     "PartitionId":"\/Guid(0c37852b-34d0-418e-91c6-2ac25af4be5b)\/",
                     "UrlZone":0,
                     "AAMEnabledManagedProperties":"AttachmentURI;deeplinks;DefaultEncodingURL;ExternalMediaURL;HierarchyUrl;OrgParentUrls;OrgUrls;OriginalPath;ParentLink;Path;PictureThumbnailURL;PictureURL;PublishingImage;recommendedfor;ServerRedirectedEmbedURL;ServerRedirectedPreviewURL;ServerRedirectedURL;SiteLogo;SitePath;SPSiteURL;UserEncodingURL",
                     "RenderTemplateId":"~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fItem_Default.js",
                     "QueryRuleId":"\/Guid(00000000-0000-0000-0000-000000000000)\/"
                  },
                  {  
                     "Rank":0,
                     "DocId":116,
                     "Path":"http:\u002f\u002fsp2013-dev:18480\u002fSiteAssets\u002fForms\u002fDispForm.aspx?ID=6",
                     "Title":"5. Weather Right Now Page - Button",
                     "OriginalPath":"http:\u002f\u002fsp2013-dev:18480\u002fSiteAssets\u002fForms\u002fDispForm.aspx?ID=6",
                     "PartitionId":"\/Guid(0c37852b-34d0-418e-91c6-2ac25af4be5b)\/",
                     "UrlZone":0,
                     "AAMEnabledManagedProperties":"AttachmentURI;deeplinks;DefaultEncodingURL;ExternalMediaURL;HierarchyUrl;OrgParentUrls;OrgUrls;OriginalPath;ParentLink;Path;PictureThumbnailURL;PictureURL;PublishingImage;recommendedfor;ServerRedirectedEmbedURL;ServerRedirectedPreviewURL;ServerRedirectedURL;SiteLogo;SitePath;SPSiteURL;UserEncodingURL",
                     "RenderTemplateId":"~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fItem_Default.js",
                     "QueryRuleId":"\/Guid(00000000-0000-0000-0000-000000000000)\/"
                  },
                  {  
                     "Rank":0,
                     "DocId":117,
                     "Path":"http:\u002f\u002fsp2013-dev:18480\u002fSiteAssets\u002fForms\u002fDispForm.aspx?ID=8",
                     "Title":"6. Weather Right Now Provisioned - Button",
                     "OriginalPath":"http:\u002f\u002fsp2013-dev:18480\u002fSiteAssets\u002fForms\u002fDispForm.aspx?ID=8",
                     "PartitionId":"\/Guid(0c37852b-34d0-418e-91c6-2ac25af4be5b)\/",
                     "UrlZone":0,
                     "AAMEnabledManagedProperties":"AttachmentURI;deeplinks;DefaultEncodingURL;ExternalMediaURL;HierarchyUrl;OrgParentUrls;OrgUrls;OriginalPath;ParentLink;Path;PictureThumbnailURL;PictureURL;PublishingImage;recommendedfor;ServerRedirectedEmbedURL;ServerRedirectedPreviewURL;ServerRedirectedURL;SiteLogo;SitePath;SPSiteURL;UserEncodingURL",
                     "RenderTemplateId":"~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fItem_Default.js",
                     "QueryRuleId":"\/Guid(00000000-0000-0000-0000-000000000000)\/"
                  },
                  {  
                     "Rank":0,
                     "DocId":119,
                     "Path":"http:\u002f\u002fsp2013-dev:18480\u002fSiteAssets\u002fForms\u002fDispForm.aspx?ID=9",
                     "Title":"8. Donuts And Drinks Pub - Button",
                     "OriginalPath":"http:\u002f\u002fsp2013-dev:18480\u002fSiteAssets\u002fForms\u002fDispForm.aspx?ID=9",
                     "PartitionId":"\/Guid(0c37852b-34d0-418e-91c6-2ac25af4be5b)\/",
                     "UrlZone":0,
                     "AAMEnabledManagedProperties":"AttachmentURI;deeplinks;DefaultEncodingURL;ExternalMediaURL;HierarchyUrl;OrgParentUrls;OrgUrls;OriginalPath;ParentLink;Path;PictureThumbnailURL;PictureURL;PublishingImage;recommendedfor;ServerRedirectedEmbedURL;ServerRedirectedPreviewURL;ServerRedirectedURL;SiteLogo;SitePath;SPSiteURL;UserEncodingURL",
                     "RenderTemplateId":"~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fItem_Default.js",
                     "QueryRuleId":"\/Guid(00000000-0000-0000-0000-000000000000)\/"
                  },
                  {  
                     "Rank":0,
                     "DocId":688,
                     "Path":"http:\u002f\u002fsp2013-dev:18480\u002fSitePages\u002fWeatherRightNowScraper.aspx",
                     "Title":"WeatherRightNowScraper",
                     "OriginalPath":"http:\u002f\u002fsp2013-dev:18480\u002fSitePages\u002fWeatherRightNowScraper.aspx",
                     "PartitionId":"\/Guid(0c37852b-34d0-418e-91c6-2ac25af4be5b)\/",
                     "UrlZone":0,
                     "AAMEnabledManagedProperties":"AttachmentURI;deeplinks;DefaultEncodingURL;ExternalMediaURL;HierarchyUrl;OrgParentUrls;OrgUrls;OriginalPath;ParentLink;Path;PictureThumbnailURL;PictureURL;PublishingImage;recommendedfor;ServerRedirectedEmbedURL;ServerRedirectedPreviewURL;ServerRedirectedURL;SiteLogo;SitePath;SPSiteURL;UserEncodingURL",
                     "RenderTemplateId":"~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fItem_Default.js",
                     "QueryRuleId":"\/Guid(00000000-0000-0000-0000-000000000000)\/"
                  },
                  {  
                     "Rank":0,
                     "DocId":683,
                     "Path":"http:\u002f\u002fsp2013-dev:18480\u002fPages\u002fDonutsAndDrinksPub.aspx",
                     "Title":"DonutsAndDrinksPub",
                     "OriginalPath":"http:\u002f\u002fsp2013-dev:18480\u002fPages\u002fDonutsAndDrinksPub.aspx",
                     "PartitionId":"\/Guid(0c37852b-34d0-418e-91c6-2ac25af4be5b)\/",
                     "UrlZone":0,
                     "AAMEnabledManagedProperties":"AttachmentURI;deeplinks;DefaultEncodingURL;ExternalMediaURL;HierarchyUrl;OrgParentUrls;OrgUrls;OriginalPath;ParentLink;Path;PictureThumbnailURL;PictureURL;PublishingImage;recommendedfor;ServerRedirectedEmbedURL;ServerRedirectedPreviewURL;ServerRedirectedURL;SiteLogo;SitePath;SPSiteURL;UserEncodingURL",
                     "RenderTemplateId":"~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fItem_Default.js",
                     "QueryRuleId":"\/Guid(00000000-0000-0000-0000-000000000000)\/"
                  },
                  {  
                     "Rank":0,
                     "DocId":685,
                     "Path":"http:\u002f\u002fsp2013-dev:18480\u002fSitePages\u002fDonutsAndDrinks.aspx",
                     "Title":"DonutsAndDrinks",
                     "OriginalPath":"http:\u002f\u002fsp2013-dev:18480\u002fSitePages\u002fDonutsAndDrinks.aspx",
                     "PartitionId":"\/Guid(0c37852b-34d0-418e-91c6-2ac25af4be5b)\/",
                     "UrlZone":0,
                     "AAMEnabledManagedProperties":"AttachmentURI;deeplinks;DefaultEncodingURL;ExternalMediaURL;HierarchyUrl;OrgParentUrls;OrgUrls;OriginalPath;ParentLink;Path;PictureThumbnailURL;PictureURL;PublishingImage;recommendedfor;ServerRedirectedEmbedURL;ServerRedirectedPreviewURL;ServerRedirectedURL;SiteLogo;SitePath;SPSiteURL;UserEncodingURL",
                     "RenderTemplateId":"~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fItem_Default.js",
                     "QueryRuleId":"\/Guid(00000000-0000-0000-0000-000000000000)\/"
                  },
                  {  
                     "Rank":0,
                     "DocId":686,
                     "Path":"http:\u002f\u002fsp2013-dev:18480\u002fSitePages\u002fMakingPiTakesEffort.aspx",
                     "Title":"MakingPiTakesEffort",
                     "OriginalPath":"http:\u002f\u002fsp2013-dev:18480\u002fSitePages\u002fMakingPiTakesEffort.aspx",
                     "PartitionId":"\/Guid(0c37852b-34d0-418e-91c6-2ac25af4be5b)\/",
                     "UrlZone":0,
                     "AAMEnabledManagedProperties":"AttachmentURI;deeplinks;DefaultEncodingURL;ExternalMediaURL;HierarchyUrl;OrgParentUrls;OrgUrls;OriginalPath;ParentLink;Path;PictureThumbnailURL;PictureURL;PublishingImage;recommendedfor;ServerRedirectedEmbedURL;ServerRedirectedPreviewURL;ServerRedirectedURL;SiteLogo;SitePath;SPSiteURL;UserEncodingURL",
                     "RenderTemplateId":"~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fItem_Default.js",
                     "QueryRuleId":"\/Guid(00000000-0000-0000-0000-000000000000)\/"
                  },
                  {  
                     "Rank":0,
                     "DocId":139,
                     "Path":"http:\u002f\u002fsp2013-dev:18480\u002fSitePages\u002fOptimusPrimePart.aspx",
                     "Title":"OptimusPrimePart",
                     "OriginalPath":"http:\u002f\u002fsp2013-dev:18480\u002fSitePages\u002fOptimusPrimePart.aspx",
                     "PartitionId":"\/Guid(0c37852b-34d0-418e-91c6-2ac25af4be5b)\/",
                     "UrlZone":0,
                     "AAMEnabledManagedProperties":"AttachmentURI;deeplinks;DefaultEncodingURL;ExternalMediaURL;HierarchyUrl;OrgParentUrls;OrgUrls;OriginalPath;ParentLink;Path;PictureThumbnailURL;PictureURL;PublishingImage;recommendedfor;ServerRedirectedEmbedURL;ServerRedirectedPreviewURL;ServerRedirectedURL;SiteLogo;SitePath;SPSiteURL;UserEncodingURL",
                     "RenderTemplateId":"~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fItem_Default.js",
                     "QueryRuleId":"\/Guid(00000000-0000-0000-0000-000000000000)\/"
                  }
               ],
               "ResultTitle":null,
               "ResultTitleUrl":null,
               "RowCount":10,
               "TableType":"RelevantResults",
               "TotalRows":179,
               "TotalRowsIncludingDuplicates":179
            }
         ]
      }
   },
   167,
   {  
      "HasException":false,
      "ErrorInfo":null
   }
]

This JSON structure contains a lot of information – certainly more information than is being displayed by the CSWP. The trick, of course, is in figuring out exactly “what” is “where” for purposes of building a paging system.

Page-Related Processing Within The Control Template

Thankfully, the CSWP provides us with a relatively easy mechanism for getting at the search result data we care about within the JSON object that is returned from the call to client.svc. When a Control display template is invoked by the CSWP, it is passed a context object as follows:

function DisplayTemplate_2aa45a743fd94e75a3e53c940628e2ec(ctx) { ... } 

ctx Content ObjectThe ctx context object contains a number of useful methods, properties, and subordinate objects we can leverage in our attempts to manipulate search results and calculate paging information. We can use ctx to interact directly with the CSWP (which is accessible through the ClientControl property), as well as obtain the search results themselves (and information about them) through the ListData property.

Note: Even though Microsoft’s List with Paging Control template doesn’t provide anything more than relative paging forward and backward, the CSWP does appear to support some form of more advanced paging scheme through its get_pagingInfo() method. When this method is called, it returns an object that contains expanded paging information that includes information about the current page, as well as a subset of pages before and after the current page. The object does not, however, contain all of the information needed to determine the total number of items in the search result set, how to access those pages, etc.

Fortunately for us, though, the ctx.ListData object contains everything we need to implement an end-to-end paging system. Here’s how each of the critical paging information pieces is located or computed within the Control template so that the portion of the control seen below can be rendered:

Paging Line

  • ctx.ListData InspectionTotal number of all search results available. The totalRowCount variable is used within the Control display template scripting to store this value. And the value itself is readily available through the ctx.ListData.ResultTables[0].TotalRows property.
  • Maximum number of items to display per page. This value (represented within the Control display template scripting as rowsPerPageCount) is easily obtained through the ctx.ListData.Properties.RowLimit property. It identifies the maximum number of rows that may be returned in the search results table (through ctx.ListData.ResultTables[0]), and it is the value which should be used to determine the number of individual search results shown in the CSWP.
  • Number of items to display on the current page. In most cases, this value (defined through the rowsOnCurrentPageCount variable within the Control display template scripting) will be the same as the maximum number of items per page. On the last page of results, though, it isn’t uncommon for there to be fewer results available than the maximum number possible. To determine how many items should be shown on the current page of search results, the script simply determines how many rows are present in the current search results table (ctx.ListData.ResultTables[0].RowCount).
  • First page number for the result set. As my kids would say, “Easy peasy lemon squeezy.” This variable (firstPageNumber) always has the intuitive value of 1. This variable should not be confused with the firstPage variable that commonly appears in the OOTB display template scripting. The reference which is assigned to the firstPage variable is a PagingLink object that is meaningful within the context of the pagingInfo object returned from the call to the ctx.ClientControl.get_pagingInfo() – not the page calculations in the display templates associated with this post.
  • Last page number for the result set. This value (represented by the lastPageNumber variable) isn’t readily available in a way that can be “plucked” out of the search data, so the display template scripting simply does a little math to calculate it: Math.ceil(totalRowCount /rowsPerPageCount). In non-code terms: the last page of the total search results set is the total number of results in the set divided by the number of results displayed per page, rounded-down. As with the firstPageNumber and firstPage variables, don’t confuse lastPageNumber with the OOTB lastPage object reference.

In addition to the values described above, a series of calculations are performed to determine the currentPageStartItem and currentPageEndItem variables that represent the first and last item numbers on the current page. Once these values are known and assigned to their respective variables, it becomes a relatively straightforward exercise to display the paging information as is done within the SwissArmy_Control_Template.js Control template. The (not-so-heavy) lifting is accomplished with the following Javascript:

// Figure out the current page information
for (var i = 0; i< pagingInfo.length; i++)
{
	var pl = pagingInfo[i];
	if (!$isNull(pl))
	{
		if (pl.startItem == -1)
		{
			currentPage = pl;
			currentPageNumber = pl.pageNumber;
			currentPageStartItem = ((currentPageNumber - 1) * rowsPerPageCount) + 1;
			if (currentPageNumber == lastPageNumber) {
				currentPageEndItem = totalRowCount;
			} else {
				currentPageEndItem = currentPageNumber * rowsPerPageCount;
			}
		}
	}
}

// Generate page divs and their links
var getPageNumberDivs = function() {
	var currentDiv, divBlocks, pageStartItem;
	divBlocks = '';
	for (var i = firstPageNumber; i <= lastPageNumber; i++) {
		cellStyle = 'sas-paging-tablecell';
		if (i == currentPageNumber) {
			currentDiv = '<div class="sas-paging-tablecell sas-paging-currentpage">';
			currentDiv += i;
			currentDiv += '</div>';
		} else {
			pageStartItem = ((i - 1) * rowsPerPageCount) + 1;
			currentDiv = '<div class="sas-paging-tablecell">';
			currentDiv += '<a href="#" onclick="$getClientControl(this).page(' + $htmlEncode(pageStartItem) + ');return Srch.U.cancelEvent(event);">';
			currentDiv += i;
			currentDiv += '</a></div>';
		}
		divBlocks += currentDiv;
	}
	return divBlocks;
}

// Generate the block that indicates current page, item counts, etc.
var getPageSummary = function() {
	var currentDiv;
	var pageInfo = 'Page {1} of {2}, items {3} to {4} of {5}.';
	pageInfo = pageInfo.replace('{1}', '<span>' + currentPageNumber + '</span>');
	pageInfo = pageInfo.replace('{2}', '<span>' + lastPageNumber + '</span>');
	pageInfo = pageInfo.replace('{3}', '<span>' + currentPageStartItem + '</span>');
	pageInfo = pageInfo.replace('{4}', '<span>' + currentPageEndItem + '</span>');
	pageInfo = pageInfo.replace('{5}', '<span>' + totalRowCount + '</span>');
	currentDiv = '<div class="sas-paging-tablecell sas-paging-summary">' + '</span>';
	currentDiv += pageInfo + '</div>';
	return currentDiv;
}

ms_outHtml.push(''
,''
,'			</ul>'
,''
,'			<div class="sas-paging-table">'
,'				<div class="sas-paging-tablerow">'
,'					', getPageNumberDivs() ,''
,'					', getPageSummary() ,''
,'				</div>'
,'			</div>'
,'		</div>'
,'		'
);

Creating The Page Links

One area does warrant a little more explanation, though, and that is how the hyperlinks are created for each of the clickable page numbers at the bottom of the CSWP. There really isn’t a whole lot of magic behind the creation of the hyperlinks themselves; the results are achieved on line 101 of the SwissArmy_Control_Template.js file:

 
currentDiv += '<a href="#" onclick="$getClientControl(this).page(' + $htmlEncode(pageStartItem) + ');return Srch.U.cancelEvent(event);">';

The pageStartItem variable value is computed as follows …

 
pageStartItem = ((i - 1) * rowsPerPageCount) + 1; 

… where i is the value of the new result page being clicked by the user. For example, if the user clicks on “17” (to indicate a desire to move to page 17 of the search results), the value of i will be 161 when there are 10 items per page.

http://sp2013-dev:18580/DisplayTemplateStyling#k=#s=161

On the server side, SharePoint translates the request for 161 (which can also be reached directly via re-post by attaching a #s=161 to the query string portion of the URL as shown above) to package-up and send down a search results table containing all of the page 17 result items.

How Do I Use The Sample (Downloadable) Files?

Once you’ve downloaded the SwissArmyTemplates.zip file and opened it up, you’ll find that it contains five files:

  • SwissArmy_Control_Template.html
  • SwissArmy_Control_Template.js
  • SwissArmy_Item_Template.html
  • SwissArmy_Item_Template.js
  • SwissArmy_Styles.css

The two HTML files are each of the display templates in their HTML form. These HTML files can be processed through SharePoint 2013’s Design Manager to generate the corresponding JavaScript files that are actually used by the CSWP. Alternatively, the JS files that are included in the ZIP file can be used as-is and dropped directly into the Master Page Gallery > Display Templates > Content Web Parts folder within a site collection to make them available to the CSWP.

The remaining file is a CSS style sheet, and it provides the look and feel that is employed by the display templates. The style sheet itself is referenced from with the Control template files (SwissArmy_Control_Template.html and SwissArmy_Control_Template.js), and the Control template files assume that the style sheet will be available in the following location within the site collection:

~sitecollection/Style Library/en-us/SwissArmyStyles/SwissArmy_Styles.css

If you place the style sheet somewhere else in the site collection, ensure that the $includeCSS() calls in the SwissArmy_Control_Template.html file (line 25) and the SwissArmy_Control_Template.js file (line 164) are updated accordingly.

Final Request

Thanks for citing appropriatelyAs with all of the resources I make available, please feel free to use the display templates and style sheet I’ve provided within your own projects, either as-is or in a form that you’ve modified to suit your needs.

If you do share or redistribute what I’ve provided in some form, whether or not you’ve made modifications, I simply ask that you reference the original source (i.e., me and/or this blog post) in what you’re sharing. I do believe in citing sources where appropriate.

Thanks, and have fun paging through search results!

References and Resources

  1. SharePoint Interface: Files for the Swiss Army Knife CSWP example
  2. MSDN: Content Search Web Part in SharePoint 2013
  3. MSDN: SharePoint 2013 Design Manager display templates
  4. MSDN: Overview of Design Manager in SharePoint 2013
  5. SharePoint Interface: Presentation for SharePoint’s New Swiss Army Knife: The Content Search Web Part
  6. MSDN: 3.1.4.1 ProcessQuery
  7. JSON.org: Introducing JSON
Categories: Development Tags: , ,

Wrap-Up, Roll-Up, and Move-On!

March 29, 2013 12 comments

Train Series Sometimes it’s hard to believe just how quickly time flies by. At the tail end of last December, I announced that some big changes were coming for me – namely that I would be transitioning into something new from an employment perspective. Today is the last day of normal business in March 2013, and that means my time with Idera is at an end.

My last three years with Idera have been quite a whirlwind of activity. I feel very fortunate and am extremely thankful to Idera for the opportunities they’ve afforded me – especially over the last year in my role as their Chief SharePoint Evangelist. In that role, I was given the latitude to spend a significant chunk of my time focusing on an area that is very important to me personally: the SharePoint Community.

The Roll-Up

In thinking about my role and some of what I’ve done over the last three years, it occurred to me that it might be nice to summarize and link to some of the materials I assembled while at Idera. I’ve occasionally referenced these items in the past, but I don’t think I’ve ever tried to aggregate them into one post or in one place.

Blog Posts

In the last half a year or so, my regular content generation efforts were being funneled to Idera’s SharePoint “Geek Stuff” blog. Here’s a table (with associated links) to the posts I’ve written:

March 19, 2013 Maskthumb Plan Your SharePoint Farm Right with a SQL Server Alias
February 8, 2013 Strategythumb Do You Have a SharePoint Backup Strategy?
January 17, 2013 Cheating on a Test The Five Minute Cheat-Sheet on SharePoint 2013’s Distributed Cache Service
December 20, 2012 smart girlfriends smiling and looking at the laptop Why Administrators Will Giggle Like Schoolgirls About SharePoint 2013’s New App Model
November 20, 2012 IderaIceThumb1 Sean’s Thoughts on the Microsoft SharePoint Conference 2012
October 19, 2012 Broken electric cable. Getting the Permissions Wired-Up Properly When Attaching a Content Database to a SharePoint Farm
September 21, 2012   Okay, Really – What Can I Do With a SharePoint Farm Configuration Database Backup?
August 24, 2012 roots Do I Really Need to Backup Up the SharePoint Root?
June 20, 2012 Talking with John Ferringer Interview with John Ferringer
June 8, 2012 TechEd '99 Baseball Cap TechEd – Why Should You Care?

SharePoint Smarts

There was a point in the past when Idera was publishing a sort of newsletter called “SharePoint Smarts,” and I wrote a couple of articles for the newsletter before it eventually rode off into the sunset:

Whitepapers

Over the years, I’ve also written or co-authored a handful of whitepapers for Idera. At the time I’m writing this post, it appears that a couple of those whitepapers are still available:

And although it isn’t available just yet, sometime soon Idera will be releasing another whitepaper I wrote that had the working title of “SharePoint Caching Implementation Guide.” If that sounds at all interesting, keep an eye on the Whitepapers section of Idera’s Resources page.

Moving-On

Bitstream Foundry LLC Although I’m going to miss my friends at Idera and wish them the best of luck going forward, I’m very excited about some things I’ve got cooking – particularly with my new company!

A couple of months back, I launched Bitstream Foundry, LLC, with the intention of getting back into more hands-on SharePoint work. My intention is to focus initially on a combination of custom SharePoint development work and SharePoint App Store product development. In the past, I’ve been a “switch hitter” when it comes to SharePoint, and I’ve gone back and forth between development and administration roles fairly regularly. Although I’m not abandoning my admin “comrades in arms,” I have to admit that I tend to get the greatest enjoyment out of development work. Between custom solutions and App Model development, I’m pretty sure I’ll be able to keep myself busy.

Microsoft BizSpark Things are falling into place with the new company, as well. I applied for membership in Microsoft’s BizSpark program yesterday, and within hours I was accepted – much to my surprise. Why was I surprised? Well, my company website is (at the moment) being redirected to a “coming soon” page I put together on the new Microsoft Azure Web Sites offering. I’ve been waiting for an Office 365 tenant upgrade so that I can build out a proper site on SharePoint 2013, but the upgrade seems to be taking much longer than originally expected …

I also learned today that my application to get Bitstream Foundry listed in the SharePoint App Store was approved, so the way is paved for me to roll out Apps. Now I just need to write them!

One Thing That Won’t Change

Despite all of the recent changes, one aspect of my professional life that won’t be changing is my commitment to sharing with (and giving back to) the SharePoint community. My confidence in my current situation would probably be substantially lower if it weren’t for all of you – my (SharePoint) friends. Over the last several months, my belief in “professional karma” has been strongly reinforced. I’ve always tried to help those who’ve asked for my time and assistance, and I’ve seen that goodwill return to me as I’ve sought input and worked to figure out “what’s next.” To those of you who have offered advice, provided feedback, written endorsements/recommendations, and more, you have my most heartfelt thanks.

I love interacting with all of you, and I still get tremendous enjoyment out of blogging, speaking, teaching, and sharing with everyone in the SharePoint space. My “official” days as a full-time evangelist may be behind me, but that won’t really change anything for me going forward as far as community involvement goes. I’ll continue to answer emails, blog when I have information worth sharing, assemble tools/widgets, help organize events, and generally do what I can to help all of you as you’ve helped me. I’m also honored to be a part of several upcoming events, and I hope to see some of you when I’m “on tour.” If we haven’t met, please say hi and introduce yourself. Making new friends and connections is one of the most rewarding aspects of being out-and-about :-)

References and Resources

  1. Blog Post: Big Changes and Resolutions for 2013
  2. Company: Idera
  3. Yahoo! Finance: Press Release
  4. Idera: SharePoint Geek Stuff Blog
  5. Idera: Resources Page
  6. Microsoft: BizSpark
  7. Company Site: Bitstream Foundry, LLC
  8. Microsoft: Azure Web Sites
  9. Microsoft: Office 365 Enterprise E3
  10. Microsoft: SharePoint App Store
  11. SharePoint Interface: Events and Activities
Categories: News Tags: , , , ,

Custom Ribbon Button Image Limitations with SharePoint 2013 Apps

January 22, 2013 26 comments

A custom action button with image My adventures in SharePoint 2013 App Model Land have been going pretty well, but I recently encountered a limitation that left me sort of scratching my head.

The limitation applies to the creation of custom actions for SharePoint apps. To be more specific: the problem I’ve encountered is that there doesn’t appear to be a way to package and reference (using relative links) custom images for ribbon buttons like the one that’s circled in the image above and to the left. This doesn’t mean that custom images can’t be used, of course, but the work-around isn’t exactly something I’m particularly fond of (nor is it even feasible) in some application scenarios.

If you’re not familiar with the new SharePoint 2013 App Model, then you may want to do a little reading before proceeding with this post. I’m only going to cover the App Model concepts that are relevant to the limitation I observed and how to address/work-around it. However, if you are familiar with the new 2013 App Model and creating custom actions in SharePoint 2010, then you may want to jump straight down to the section titled Where the Headaches Begin.

One more warning: this post does some heavy digging into SharePoint’s internal processing of custom ribbon actions and URL tokens. If you want to skip all of that and head straight to the practical take-away, jump down to the What About the Image32by32 and Image16by16 Attributes section.

Adding a Ribbon Custom Action

First, let me do a quick run-through on custom actions. They aren’t unique to SharePoint 2013 or its new “Cloud App Model.” In fact, the type of custom action I’m talking about (i.e., extending the ribbon) became available when the Ribbon was introduced with SharePoint 2010.

With a SharePoint 2013 App, adding a new button to the ribbon is a relatively simple affair. It starts with choosing the Ribbon Custom Action option from the Add New Item dialog as shown below and to the left. Once a name is provided for the custom action and the Add button is clicked, the Create Custom Action for Ribbon dialog appears as shown below and to the right. There’s a third dialog page that further assists in setting some properties for a custom action, but I’m going to skip over it since it isn’t relevant to the point I’m trying to make.

Adding a Ribbon Custom Action

Create Custom Action for Ribbon

I want to call attention to one of the selections I made on the Create Custom Action for Ribbon dialog, though; specifically, the decision to expose the custom action in the Host Web rather than in the App Web.

Why is this choice so important? Well, the new App Model enforces a relatively strict boundary of separation between SharePoint sites and any custom applications (running under the new App Model) that they may contain. A SharePoint site (Host Web) can technically “host” applications, but those applications operate in an isolated App Web that may have components running on an entirely different server. Under the new App Model, no custom app code is running in the Host Web.

App Webs (where custom applications exist after installation) don’t have direct access to the Host Web in which they’re contained, either. In fact, App Webs are logically isolated from their Host Web parents. If App Webs want to communicate with their Host Web parent to interact with site collection data, for example, they have to do so through SharePoint’s Client-Side Object Model (CSOM) or the Representational State Transfer (REST) interface. The old full-trust, server-side object isn’t available; everything is “client-side.”

There are some exceptions to this model of isolation, and one of those exceptions is the use of custom actions to allow an App (residing in an App Web) to partially wire itself into the Host Web. The Create Custom Action for Ribbon dialog shown above, for instance, adds a new button to the ribbon for each of the Document Libraries in the Host Web. This gives users a way to navigate directly from Document Libraries (in the Host Web) to a page in the App Web, for example.

The Elements.xml file that gets generated for the custom action once the Visual Studio wizard has finished running looks something like the following:

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction Id="1470c964-6b8a-4d79-9817-4d32c898ffbe.RibbonCustomAction1"
                RegistrationType="List"
                RegistrationId="101"
                Location="CommandUI.Ribbon"
                Sequence="10001"
                Title="Invoke &apos;LibraryDetailsCustomAction&apos; action">
    <CommandUIExtension>
      <!-- 
      Update the UI definitions below with the controls and the command actions
      that you want to enable for the custom action.
      -->
      <CommandUIDefinitions>
        <CommandUIDefinition Location="Ribbon.Library.Actions.Controls._children">
          <Button Id="Ribbon.Library.Actions.LibraryDetailsCustomActionButton"
                  Alt="Examine Library Details"
                  Sequence="100"
                  Command="Invoke_LibraryDetailsCustomActionButtonRequest"
                  LabelText="Examine Library Details"
                  TemplateAlias="o1"
                  Image32by32="_layouts/15/images/placeholder32x32.png"
                  Image16by16="_layouts/15/images/placeholder16x16.png" />
        </CommandUIDefinition>
      </CommandUIDefinitions>
      <CommandUIHandlers>
        <CommandUIHandler Command="Invoke_RibbonCustomAction1ButtonRequest"
                          CommandAction="LibraryManager\Pages\LibraryDetails.aspx"/>
      </CommandUIHandlers>
    </CommandUIExtension >
  </CustomAction>
</Elements>

Deploying the App that contains the custom action markup shown above creates a new button in the ribbon of each Host Web Document Library. By default, each button looks like the following:

Custom Ribbon Button

There are a few attributes in the previous XML that I’m going to repeatedly come back to, so it’s worth taking a closer look at each one’s purpose and associated value(s):

  • Image32by32 and Image16by16 for the <Button /> element. These two attributes specify the images that are used when rendering the custom action button on the ribbon. By default, they point to an orange dot placeholder image that lives in the farm’s _layouts folder.
  • CommandAction for the <CommandUIHandler /> element. In its simplest form, this is the URL of the page to which the user is redirected upon pressing the custom ribbon button.

The Problem with the Default CommandAction

When a user clicks on a custom ribbon button in one of the Host Web document libraries, the goal is to send them over to a page in the App Web where the custom action can be processed. Unfortunately, the default CommandAction isn’t set up in a way that permits this.

CommandAction="LibraryManager\Pages\LibraryDetails.aspx"

In fact, attempting to deploy the solution to Office 365 with this default CommandAction results in failure; the App package doesn’t pass validation.

To understand why the failure occurs, it’s important to remember the isolation that exists between the Host Web and the App Web. To illustrate how the Host Web and App Web are different from simply a hostname perspective, consider the project I’ve been working on as an example:

Notice that although the /sites/dev2 relative path portion is the same for both the Host Web and App Web URLs, the hostname portion of each URL is different. This is by design, and it helps to enforce the logical separation between the Host Web and App Web – even though the App Web technically resides within the Host Web.

Looking again at the default CommandAction attribute reveals that its value is just an ASPX page that is identified with a relative URL. Rather than pointing to where we want it to point …

https://mcdonough-bc920dbeb7ecd3.sharepoint.com/sites/dev2/LibraryManager/Pages/LibraryDetails.aspx

… it ends up pointing to a non-existent destination in the Host Web:

https://mcdonough.sharepoint.com/sites/dev2/LibraryManager/Pages/LibraryDetails.aspx

And this is exactly what should happen. After all, the custom action is launched from within the Host Web, so a relative path specification should resolve to a location in the Host Web – not the location we actually want to target in the App Web.

Fixing the CommandAction

The Key! Thankfully, it isn’t a major undertaking to correct the CommandAction attribute value so that it points to the App Web instead of the Host Web. If you’ve worked with SharePoint at all in the past, then you may know that the key to making everything work (in this situation) is the judicious use of tokens.

What are tokens? In this case, tokens are specific string sequences that SharePoint parses at run-time and replaces with a value based on the run-time environment, action that was performed, associated list, or some other context-sensitive value that isn’t known at design-time.

To illustrate how this works, consider the default CommandAction attribute:

CommandAction="LibraryManager\Pages\LibraryDetails.aspx"

Modifying the attribute as follows changes the destination URL of the button so that the user is redirected to the desired page in the App Web rather than the Host Web:

CommandAction="~appWebUrl/Pages/LibraryDetails.aspx"

The ~appWebUrl token is replaced at run-time with the actual URL of the associated App Web (https://mcdonough-bc920dbeb7ecd3.sharepoint.com/sites/dev2) to build the desired destination link.

SharePoint defines a whole host of URL strings and tokens for use in Apps. As it turns out, a fairly complete list has been aggregated and defined in a handy little page on MSDN. Thanks to the always-helpful Andrew Clark for pointing this out to me; I hadn’t realized Microsoft had pulled so many tokens together in one place!

Where the Headaches Begin

Baby Crying Since tokens are the key to inserting context-dependent values at run-time, you’d think they’d have been implemented and usable anywhere a developer needs to cross the Host Web / App Web divide.

Apparently not. To be more specific (and fair), I should instead say “not consistently.”

Since this blog post is about image limitations with custom ribbon buttons, you can probably guess where I’m headed with all of this. So, let’s take a look at the Image16by16 and Image32by32 attributes.

By default, the Image16x16 and Image32by32 attributes point to a location in the _layouts folder for the farm. Each attribute value references an image that is nothing more than a little round orange dot:

Image32by32="_layouts/15/images/placeholder32x32.png"
Image16by16="_layouts/15/images/placeholder16x16.png"

Much like the CustomAction attribute, it stands to reason that developers would want to replace the placeholder image attribute values with URLs of their choosing. In my case, I wanted to use a set of images I was deploying with the rest of the application assets in my App Web. So, I updated my image attributes to look like the following:

Image32by32="~appWebUrl/Images/sharepoint-library-analyzer_32x32-a.png"
Image16by16="~appWebUrl/Images/sharepoint-library-analyzer_16x16-a.png"

Tokens Do Not Work for Image Attributes I deployed my App to my Office 365 Preview tenant, watched my browser launch into my App Web, hopped back to the Host Web, navigated to a document library, and looked at the toolbar. I was not happy by what I saw (on the left).

The image I had specified for use by the button wasn’t being used. All I had was a broken image link.

Examining the properties for the broken image quickly confirmed my fear: the ~appWebUrl token was not being processed for either of the Image32by32 or Image16by16 attributes. The token was being output directly into the image references.

I tried changing the image attributes to reference the App Web a couple of different ways (and with a couple of different tokens), but none of them seemed to work.

I did a little digging, and I saw that Chris Hopkins (over at Microsoft) covered this very topic for sandboxed solutions in SharePoint 2010. In Chris’ article, though, it was clear that tokens such as ~site and ~sitecollection were valid for use by the Image32by32 and Image16by16 attributes.

To see if I was losing my mind, I decided to try a little experiment. Although I knew it wouldn’t solve my particular problem, I decided to try using the ~site token just to see if it would be parsed properly. Lo and behold, it was parsed and replaced. ~site worked. So, ~site worked … but ~appWebUrl didn’t?

That didn’t make any sense. If it isn’t possible to use the ~appWebUrl token, how are developers supposed to reference custom images for the buttons they deploy in their Apps? Without the ~appWebUrl, there’s no practical way to reference an item in the App Web from the Host Web.

Token Forensics

When I find myself in situations where I’m holding results that don’t make sense, I can’t help myself: I pull out Reflector and start poking around for clues inside SharePoint’s plumbing. If I dig really hard, sometimes I find answers to my questions.

RegisterCommandUIWithRibbon After some poking around with Reflector, I discovered that the “journey to enlightenment” (in this case) started with the RegisterCommandUIWithRibbon method on the SPCustomActionElement type. It is in this method that the Image16by16 and Image32by32 attributes are read-in from the XML file in which they are defined. Before assignment for use, they’re passed through a couple of methods that carry out token parsing:

  • ReplaceUrlTokens on the SPCustomActionElement type
  • UrlFromPrefixedUrlCore on the the SPUtility type

Although these methods together are capable of recognizing and replacing many different token types (including some I hadn’t seen listed in existing documentation; e.g., ~siteCollectionLayouts), none of the new SharePoint 2013 tokens, like the ~appWebUrl and ~remoteWebUrl ~remoteAppUrl tokens, appear in these methods.

Interestingly enough, I didn’t see any noteworthy differences between the path of execution for processing image attributes and the sequence of calls through which CommandAction attributes are handled in the RegisterCommandUIExtension method of the SPRibbon type. The RegisterCommandUIExtension method eventually “punches down” to the ReplaceUrlTokens and UrlFromPrefixedUrlCore methods, as well.

The differences I was seeing in how tokens were handled between the CommandAction and Image32by32/Image16by16 attributes had to be originating somewhere else – not in the processing of the custom action XML.

Deployment Modifications

After some more digging in Reflector to determine where the ~appWebUrl actually showed-up and was being processed, I came across evidence suggesting that “something specialwas happening on App deployment rather than at run-time. The ~appWebUrl token was being processed as part of a BuildTokenMap call in the SPAppInstance type; looking at the call chain for the BuildTokenMap method revealed that it was getting called during some App deployment operations processing.

App Deployment Hierarchy to BuildTokenMap

If changes were taking place on App deployment, then I had a hunch I might find what I was looking for in the content database housing the Host Web to which my App was being deployed. After all, Apps get deployed to App Webs that reside within a Host Web, and Host Webs live in content databases … so, all of the pieces of my App had to exist (in some form) in the content database. 

I fired-up Visual Studio, stopped deploying to Office 365, and started deploying my App to a site collection on my local SharePoint 2013 VM farm. Once my App was deployed, I launched SQL Management Studio on the SQL Server housing the SharePoint databases and began poking around inside the content database where the target site collection was located.

Brief aside: standard rules still apply in SharePoint 2013, so I’ll mention them here for those who may not know them. Don’t poke around inside content databases (or any other databases) in live SharePoint environments you care about. As with previous versions, querying and working against live databases may hurt performance and lead to bigger problems. If you want to play with the contents of a SharePoint database, either create a SQL snapshot of it (and work against the snapshot) or mount a backup copy of the database in a test environment.

I wasn’t sure what I was looking for, so I quickly examined the contents of each table in the content database. I hit paydirt when I opened-up the CustomActions table. It had a single row, and the Properties field of that row contained some XML that looked an awful lot like the Elements.xml which defined my custom action:

<?xml version="1.0" encoding="utf-16"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
	<CustomAction Title="Invoke 'LibraryDetailsCustomAction' action" Id="4f835c73-a3ab-4671-b142-83304da0639f.LibraryDetailsCustomAction" Location="CommandUI.Ribbon" RegistrationId="101" RegistrationType="List" Sequence="10001">
		<CommandUIExtension xmlns="http://schemas.microsoft.com/sharepoint/">
			<!-- 
      Update the UI definitions below with the controls and the command actions
      that you want to enable for the custom action.
      -->
			<CommandUIDefinitions>
				<CommandUIDefinition Location="Ribbon.Library.Actions.Controls._children">
					<Button Id="Ribbon.Library.Actions.LibraryDetailsCustomActionButton" Alt="Examine Library Details" Sequence="100" Command="Invoke_LibraryDetailsCustomActionButtonRequest" LabelText="Examine Library Details" Image16by16="~site/Images/sharepoint-library-analyzer_16x16-a.png" Image32by32="~appWebUrl/Images/sharepoint-library-analyzer_32x32-a.png" TemplateAlias="o1"/>
				</CommandUIDefinition>
			</CommandUIDefinitions>
			<CommandUIHandlers>
				<CommandUIHandler Command="Invoke_LibraryDetailsCustomActionButtonRequest" CommandAction="javascript:LaunchApp('709d9f25-bb39-4e6a-97d5-6e1d7c855f38', 'i:0i.t|ms.sp.int|a441fa2c-8c5f-4152-9085-3930239ab21b@9db0b916-0dd6-4d6c-be49-41f72f5dfc02', '~appWebUrl\u002fPages\u002fLibraryDetails.aspx?ListID={ListId}\u0026SiteUrl={SiteUrl}', null);"/>
			</CommandUIHandlers>
		</CommandUIExtension>
	</CustomAction>
</Elements>

There were some differences, though, between the Elements.xml I had defined earlier and what actually appeared in the Properties field. I narrowed my focus to the differences that existed between the non-working Image32by32/Image16by16 attributes

Image16by16="~appWebUrl/Images/sharepoint-library-analyzer_16x16-a.png"
Image32by32="~appWebUrl/Images/sharepoint-library-analyzer_32x32-a.png"

… and the CommandAction attribute.

CommandAction="javascript:LaunchApp('709d9f25-bb39-4e6a-97d5-6e1d7c855f38', 'i:0i.t|ms.sp.int|a441fa2c-8c5f-4152-9085-3930239ab21b@9db0b916-0dd6-4d6c-be49-41f72f5dfc02', '~appWebUrl\u002fPages\u002fLibraryDetails.aspx', null);"

As suspected, some deployment-time processing had been performed on the CommandAction attribute but not on the image attributes. The CommandAction still contained an ~appWebUrl token, but it was wrapped as part of a parameter call to a LaunchApp JavaScript function that appeared to be handled (or rather, executed) from a client-side browser.

Jumping into my App in Internet Explorer and opening IE’s debugging tools via <F12>, I did a search for the LaunchApp function within the referenced scripts and found it in the core.js library/script. Examining the LaunchApp function revealed that it called the LaunchAppInternal function; LaunchAppInternal, in turn, called back to the SharePoint server’s /_layouts/15/appredirect.aspx page with the parameters that were supplied to the original LaunchApp method – including the URL with the ~appWebUrl token.

To complete the journey, I opened up the Microsoft.SharePoint.ApplicationPages.dll assembly back on the server and dug into the AppRedirectPage class that provides the code-behind support for the AppRedirect.aspx page. When the AppRedirect.aspx page is loaded, control passes to the page’s OnLoad event and then to the HandleRequest method. HandleRequest then uses the ReplaceAppTokensAndFixLaunchUrl method of the SPTenantAppUtils class to process tokens.

The ReplaceAppTokensAndFixLaunchUrl method is noteworthy because it includes parsing and replacement support for the ~appWebUrl token, ~remoteWebUrl ~remoteAppUrl token, and other tokens that were introduced with SharePoint 2013. The deployment-time processing that is performed on the CommandAction attribute is what ultimately wires-up the CommandAction to the ReplaceAppTokensAndFixLaunchUrl method. The Image32by32 and Image16by16 attributes don’t get this treatment, and so the new 2013 tokens (like ~appWebUrl) can’t be used by these attributes.

What About the Image32by32 and Image16by16 Attributes?

Doubt Now that some of the key differences in processing between the CommandAction attribute and image attributes have been identified, let me jump back to the original problem. Is there anything that can be done with the Image32by32 and Image16by16 attributes that are specified in a custom action to get them to reference assets that exist in the App Web? Since tokens like ~appWebUrl (and ~remoteWebUrl for all you Autohosted and Provider-hosted application builders) aren’t parsed and processed, are there alternatives?

My response is a somewhat wishy-washy “doubtful.” In my estimation, you’d need to hack SharePoint with something like a javascript: tag for an image attribute (which, interestingly enough, doesn’t appear to be expressly blocked), find some way to obtain the App Web URL base, formulate the proper path to the image, and more. If it could be done, you’d be gaming SharePoint … and I could easily see a cumulative update or service pack breaking this type of elaborate work-around.

The safest and most pragmatic way to handle this situation, it seems, is to use absolute URLs for the desired image resources and forget about deploying them to the App Web altogether. For example, I placed the images I was trying to use on the ribbon buttons here on my blog and referenced them as follows:

Image16by16="http://sharepointinterface.files.wordpress.com/2013/01/sharepoint-library-analyzer_16x16-a.png"
Image32by32="http://sharepointinterface.files.wordpress.com/2013/01/sharepoint-library-analyzer_32x32-a.png"

Working Custom Button Image I had some initial concerns that I might inadvertently bump into some security boundaries, such as those that sometimes arise when an asset is referenced via HTTP from a site that is being served up under HTTPS. This didn’t prove to be the case, however. I tested the use of absolute URLs in both my development VM environment (served up under HTTP) and through one of my Office 365 Preview site collections (accessed via HTTPS), and no browser security warnings popped up. The target image appeared on the custom button as desired (shown on the left) in both cases.

Although the use of absolute URLs will work in many cases, I have to admit that I’m still not a big fan of this approach – especially for SharePoint-hosted apps like the one I’ve been working on. Even though Office 365 entails an “always connected” scenario, I can easily envision on-premises deployment environments that are taken offline some or all of the time. I can also see (and have seen in the past) SharePoint environments where unfettered Internet access is the exception rather than the rule.

In these environments, users won’t see image buttons at all – just blank placeholders or broken image links. After all, without Internet access there is no way to resolve and download the referenced button images.

Wrapping It Up

At some point in the future, I hope that Microsoft considers extending token parsing for URL-based attributes like Image32by32 and Image16by16 to include the ~appWebUrl, ~remoteWebUrl, and other new tokens used by the SharePoint 2013 App Model. In the meantime, though, you should probably consider getting an easily accessible online location (SkyDrive, Dropbox, a blog, etc.) for images and other similar assets if you’re building apps under the new SharePoint 2013 App Model and intend to use custom actions.

Update (1/27/2013)

I need to issue a couple of updates and clarifications. First, I need to be very clear and state that SharePoint-hosted apps were the focus of this post. In a SharePoint-hosted app, what I’ve written is correct: there is no processing of “new” 2013 tokens (like ~appWebUrl and ~remoteAppUrl) for the Image32by32 and Image16by16 attributes. Interestingly enough, though, there does appear to be processing of the ~remoteAppUrl in the Image32by32 and Image16by16 attributes specifically for the other application types such as provider-hosted apps and autohosted apps. Jamie Rance mentioned this in a comment (below), and I verified it with an autohosted app that I quickly spun-up.

I double-checked to see if the ~remoteAppUrl token would even be recognized/processed (despite the lack of a remote web component) for SharePoint-hosted apps, and it is not … nor is ~appWebUrl token processed for autohosted apps. The selective implementation of only the ~remoteAppUrl token for certain app types has me baffled; I hope that we’ll eventually see some clarification or changes. If you’re building provider-hosted or autohosted apps, though, this does give you a way to redirect image requests to your remote web application rather than an absolute endpoint. Thank you, Jamie, for the information!

And now for some good news that for SharePoint-hosted app creators. Prior to writing this post, I had posted a question about the tokens over in the SharePoint Exchange forums. At the time I wrote this post, there hadn’t been any activity to suggest that a solution or workaround existed. F. Aquino recently supplied an incredibly creative answer, though, that involves using a data URI to Base64-encode the images and package them directly into the Image32by32 and Image16by16 attributes themselves! Although this means that some image pre-processing will be required to package images, it gets around the requirement of being “always-connected.” This is an awesome technique, and I’ll certainly be adding it to my arsenal. Thank you, F. Aquino!

References and Resources

  1. MSDN: How to: Create custom actions to deploy with apps for SharePoint
  2. MSDN: Apps for SharePoint overview
  3. MSDN: Customizing and Extending the SharePoint 2010 Server Ribbon
  4. MSDN: How to: Complete basic operations using SharePoint 2013 client library code
  5. MSDN: How to: Complete basic operations using SharePoint 2013 REST endpoints
  6. MSDN: URL strings and tokens in apps for SharePoint
  7. Twitter: Andrew Clark
  8. Chris Hopkins’ Visilog: Using images on your ribbon buttons from a sandboxed solution in SharePoint 2010
  9. Software: Red Gate’s Reflector
  10. Service: Microsoft’s SkyDrive
  11. Service: Dropbox

How My View of Microsoft’s Vision for SharePoint in the Cloud Has Evolved

January 10, 2013 5 comments

Pointing Out Some Clouds It was about a year and a half ago when someone dialed-up the volume on “The SharePoint Cloud Message” in my world. It’s not that I hadn’t heard people talking about SharePoint in the Cloud prior to that; I guess it’s just that I started listening more closely because Microsoft was turning into one of the Cloud’s most vocal proponents.

Around the summer of 2010, it was becoming clear to me that Cloud-based SharePoint wasn’t just a passing trend. With Microsoft clearly stating its intention to make the Cloud a cornerstone of its business, I needed to start paying attention.

How I Saw Things Before

My relationship with Microsoft and Microsoft technologies goes back to the days of MS-DOS. As a result, I’ve always seen Microsoft as a company that was primarily interested in one thing: selling software. I worked for a Microsoft managed systems integration (SI) partner – Cardinal Solutions Group – for several years. During my years with Cardinal, my goal was to help others who had purchased Microsoft software make use of that software. In many cases, customer leads came from Microsoft either directly or indirectly. Microsoft sold the software, and we setup/customized/serviced/configured that software based on what a customer was trying to accomplish. It was a symbiotic relationship, and it was pretty easy for me to grasp.

Then the whole “Cloud thing” started. Cloud-based SharePoint and other Azure-branded services seemed a somewhat confusing move for Microsoft at first – at least to me. Even before Office 365, Microsoft offered hosted SharePoint through BPOS – or the Business Productivity Online Suite. At the time when BPOS was first released, I viewed it as something of a niche market for Microsoft. I had plenty of friends who worked at places like Rackspace and Fpweb.net, so the part I found unusual wasn’t really that “someone else” was hosting SharePoint and focusing on it as a service. The fact that Microsoft itself was getting serious about SharePoint and other services was the eyebrow raiser.

For Microsoft, it wasn’t just about selling software anymore.

The Biggest Hurdle

A Hurdle Of course, when Microsoft wants to succeed at something, they invest considerable planning and resources in it. Since Microsoft is essentially betting the farm (pun intended) on Office 365 and SharePoint in the Cloud, they’re pushing it very hard on multiple fronts. Redmond’s marketing machine has been talking Office 365 frequently and loudly for at least the last year. With each new release, developer tools like Visual Studio get more Cloud-friendly. Partners have incentives to get customers onto Office 365 and Azure services. Competitive price points make it difficult to ignore Microsoft’s Cloud offerings. For me (and I’m sure for many of you), it’s a lot to process.

I’d also be remiss if I didn’t say that I think Office 365 has a very compelling value proposition, even without SharePoint. SharePoint itself is a complex platform, though, and many organizations struggle with administrative needs like data protection, performance optimization, high availability, and basic day-to-day management. The idea of turning these concerns over to someone else (or some other entity) who better-understands them makes sense to me.

After working with SharePoint 2013 for several months now, I can easily say that the platform isn’t getting any easier. SharePoint 2013 has quite a few more “moving parts” relative to SharePoint 2010, just as SharePoint 2010 demonstrated itself to be significantly more complex than SharePoint 2007.

Despite the compelling nature of Office 365, I always seemed to come back around to fixate on one thought. This thought constantly reverberated through my head anytime “SharePoint in the Cloud” became a topic of conversation:

Most companies using SharePoint have made a significant investments in hardware, software, personnel, and services to get SharePoint up-and-running. They aren’t going to simply “dump” those on-premises investments and go to the Cloud tomorrow. The Cloud will happen, but it’s going to take longer than Microsoft thinks.

In discussions with many friends and respected professionals in the SharePoint community, I knew that I wasn’t completely alone in my way of thinking. In the conversations I’d had, there was almost always agreement that a shift to the Cloud and Cloud-based services would happen over time. The greatest debate seemed to be over whether it would happen next year or if it would take the next half a decade.

Breakthrough

Old Thinking I’d say my “breakthrough moment” came after I started playing with the Office 365 Preview more extensively a few months back. I initially set up a preview tenant to familiarize myself with what was coming, how SharePoint 2013 would be exposed, how to configure Office 365 tenants, etc. The more I played with the tenant, the more I thought about how truly useful Office 365 could be, particularly for non-enterprise customers, home users, and others who didn’t fit into SharePoint’s “big deployment picture” previously.

That’s when the pieces started to click into place for me. All along I had been thinking about Office 365 and Cloud-based SharePoint deployments along the lines of the bar chart seen above and to the right. Numbers and proportions are all relative, but the key concept I’m trying to convey with the chart is this: for some reason, I had always thought that the proponents of Cloud-based SharePoint were suggesting that Cloud adoption would come at the cost of on-premises deployments; i.e., on-premises users would “convert” to the Cloud. If Cloud-based deployments grew, that meant that on-premises deployments had to shrink. In short: I was inadvertently assuming that the overall number of SharePoint deployments had hit saturation and was remaining static.

I don’t think that way at all anymore.

New Thinking After I’d done some playing with my first tenant, it wasn’t long before I was setting up another two Office 365 tenants for other side projects. In conversations with friends in the SharePoint community, I was discovering that “everyone” was setting up tenants for their families, for their spouse’s business, etc. In almost all cases where tenants were being setup, the use cases were ones that didn’t align with traditional enterprise-scale on-premises SharePoint deployment and usage. In fact, the use cases were typically the types of things that would eventually find a home on Google Apps or its equivalent because Microsoft (previously) had nothing strong to offer in that space.

The more I think about it, the more I feel that Office 365 growth – once the new 2013 Preview goes live – will be aggressive and look something more like what I’ve charted above and to the left. While Office 365 might replace some on-premises deployments, particularly for smaller organizations, I don’t see that as its primary market (initially) or its strong suit. The greatest degree of Office 365 traction is going to be obtained with users who need a Google Apps-like solution but for whom buying the required infrastructure and expertise for Exchange, SharePoint, etc., is cost-prohibitive.

So, I stopped thinking “replacement” and started thinking “complement.” That’s my assessment and working outlook for the Office 365 (Preview) right now.

Why Not Everyone?

I’m sure that plenty of folks who’ve believed in “Cloud Power” since Day One probably think that I’m still being too conservative in my outlook for SharePoint on Office 365, and that may be true. However, I still see plenty of concerns that are near-and-dear to most enterprise and larger business customers, and I believe that they will be Cloud adoption blockers until they’re addressed directly and decisively. Here are just a few that come to mind.

1. Who owns the data? Sure, it’s your tenant … but do you own the data? Common sense would seem to suggest “yes,” but this is still uncharted legal territory. Don’t believe me? Do some background reading on the Megaupload situation and see how users of that Cloud-based service are faring in their attempts to get “their data” back.

2. What about disasters? Many people point to the Cloud as a solution for business continuity and disaster recovery (DR) concerns. The Cloud can certainly help, but I’ll tell you (somewhat authoritatively) that the Cloud doesn’t make DR concerns “go away” – especially for SharePoint. For one thing, you’re locked into your provider’s terms of service; if you need more aggressive RPO and RTO windows, then you need to be looking elsewhere. Even Cloud data centers themselves go down; what’s your plan then?

3. Can I leave my provider? Everyone is quick to talk about moving to the Cloud, and many companies are happy to talk about migration strategies. What if you want to leave or change providers, though? Do those migration strategies work? What do you lose? How long would it even take? These may not seem like important questions now, but they will become increasingly more important as Cloud adoption grows and more companies get in on the action. It stands to reason that some portion of those companies will fail, close-up shop, be bought, etc. When that happens, what do you do … and what happens to your SharePoint?

Wrap Up

Of course, my perspective on Office 365 uptake in the next several years could be completely off-the-mark. After all, I don’t really have any numbers to back up my hypotheses. They’re just my opinions, but they are in-line with my gut feel.

And I’ve learned to trust my gut.

References and Resources

  1. Network World: Microsoft’s Ballmer: ‘For the cloud, we’re all in’
  2. Company: Rackspace
  3. Company: Fpweb.net
  4. Company: Cardinal Solutions Group
  5. Microsoft: Windows Azure
  6. ZDNet: The road to Microsoft Office 365: The Past 
  7. Microsoft: Office 365 Preview
  8. Google: Google Apps
  9. TorrentFreak: Megaupload Seized Data Case Will Get a Hearing, Court Rules
  10. Book: The SharePoint 2010 Disaster Recovery Guide
  11. SharePoint Interface: RPO and RTO: Prerequisites for Informed SharePoint Disaster Recovery Planning
  12. ZDNet: Amazon cloud down; Reddit, Github, other major sites affected
Categories: Cloud Tags: , ,

Big Changes and Resolutions for 2013

December 31, 2012 1 comment

Happy 2013 Fortune Cookie

2012 is coming to a close, and 2013 is just around the corner. I’ve been thinking about the year that has gone by, but I’ve been thinking even more about the year to come. 2013 promises to be a year of great personal change – for reasons that will become clear with a little more reading.

But first: I’ve got this friend, and many of you probably know him. His name is Brian Jackett, and nowadays he works for Microsoft as a member of their premier field engineering (PFE) team. For the last couple of years, I’ve watched (with envy, I might add) as Brian has blogged about his year-gone-by and assembled a list of goals for the coming year. He even challenged me (directly) to do the same at one point in the past, but sadly I didn’t rise to the challenge.

I’ve decided that year-end 2012 is going to be different. 2012 was a very busy year for me, and a lot of great things happened throughout the year. Despite these great things, I’m going into 2013 knowing that a lot is going to change (and frankly has to change).

Biggest Things First

The End ... Or Is It?Let me start with the most impactful change-up: my full-time role as Chief SharePoint Evangelist for Idera is coming to a close by the end of March 2013. I’ve been with Idera for over two and a half years now, and I’m sad to be moving on from such a great group of folks.

I’m leaving because Idera is undergoing some changes, and the company is in the process of adjusting its strategy on a few different levels. One of the resultant changes brought about by the shift in strategy involves the company getting back to more of an Internet/direct sales-based approach. Since a large part of my role involves community based activities and activities that don’t necessarily align with the strategy change, it doesn’t make a whole lot of sense for me to remain – at least in the full-time capacity that I currently operate in.

To be honest, I didn’t expect my role or position to be around forever. As many of you heard me declare publicly, though: I wanted to make the most of it while I had the role and the backing. I got a lot out of working with my friends at Idera, and I greatly appreciate the opportunities they afforded me. I hope it’s been as much fun for them as it has been for me.

What’s Next?

Even after my full-time role comes to a close, I’ve already had a couple of conversations around continuing to do some work with/for Idera. Despite my full-time focus on Idera over the last 2+ years, I have actually been operating as a contractor/consultant – not a full-time employee. This has left me free to take on other SharePoint work when it made sense (and when my schedule permitted). Going forward, my situation will probably just do a flip-flop: Idera will become the “side work” (if it makes sense), and something else will take center stage.

I don’t yet know what will be “showing on the main screen,” though. That’s been on my mind quite a bit recently, and I’ve been spending a lot of time trying to figure out what I really want to do next. Take a full-time role with a local organization? Do contract development work and continue to work from home? Wiggle my way into becoming the first Starbucks SharePoint barista? Something else entirely? If my preliminary assessment of what’s out there is accurate, there are quite a few different options. I’ll certainly be busy evaluating them and comparing them against my ever-evolving “what I want to do” checklist.

Can You Help Me Out?

Linked In Connection to Sean McDonough Many of you know that I do a lot of speaking, blogging, answering of questions/emails, etc. Giving back to the community and sharing what I’ve learned are a part of my DNA, and I’ll continue to do those things to the extent that I can going forward. I normally don’t ask for anything in return; I just like to know that I’m helping others.

As I try to figure out what’s next, I’d like to ask a favor: if you feel that I’ve helped you in some significant or meaningful way (through one of my sessions, in an email I’ve answered, etc.) over the last few years, would you be willing to endorse my skills or recommend me on LinkedIn? I see a wealth of opportunities “out there,” and sometimes an endorsement or recommendation can make the difference when it comes to employment or landing a client.

Resolutions

Employment and the ability to support my family aside, this is the first year (in quite a few) that I’ve made some resolutions for the new year. Although it’s an artificial break-point, I’ve separated my resolutions into “work-related” and “non-work” categories. And although I can think of lots of things I want to change, I’ve picked only three in each category to focus on.

Work-Related

Resolutions for a New Year1. Manage Distractions More Effectively. Working at home can be a dual-edged sword. If I were single, unmarried, and better-disciplined, I’d see working at home as the ability to do whatever I wanted without distraction. That’s not the reality in my world, though. Where I can remove distractions, I intend to.

Some of you chimed-in (positively) when I recently made a comment on Facebook about unsubscribing to a lot of junk email. Over time, I’ve come to realize that all of the extra email I’ve been getting is just a distraction. I can do something about that.

The same goes for email in general. I have multiple email accounts, and mail streams into those accounts throughout the day. Rather than constantly trying to stay on top of my inbox, I’m going to shift to a “let it sit” mentality. If I’m honest with myself, 95% of the email I receive can go unanswered for a while. I’ll attend to those items that require my attention, but some of the quasi real-time email discussions I’m known to have don’t really matter in the greater scheme of getting real work done.

Social networking tools are another great example. I think they can be a very positive and helpful force (especially for someone who’s at home all day, like me), but they can very easily become a full-time distraction. I cut down my Twitter use dramatically a couple of years back. I won’t even set foot “on” Yammer because of the huge, sucking, time-consuming noise it appears to make. Going forward, I’m going to attempt to use other tools (Facebook, LinkedIn, etc.) during specific windows rather than having them open all-day, everyday – even if I’m not “actively” on them.

For distractions that can’t be removed (e.g., children running around), my only option is to better manage the distractions. My home office has doors; I’ve already begun using them more. I’ll be wearing headphones more often. These are the sorts of things I can do to ensure that I remain better focused.

2. Thoughtfully Choose Work. I had to come clean with myself on this one, and that’s why I chose to word the resolution the way I did. Work is important to me, and it’s in my nature to always be working on something – even if that work is “for fun.” While I’d like to be the type of person who could cut back and work less, I don’t know that I’d be able to do so without incurring substantial anxiety.

Knowing this about myself, I’ve settled on trying to be more thoughtful about doing work. Make it a choice, not the default. Being a workaholic who labors from home, work became my default mode rather quickly and naturally. I remember a time when weekends were filled with fun activities – and leaving work meant “leaving” in both the physical and mental sense. Even if I can’t maintain boundaries that are quite that clear nowadays, I can be more conscientious about my choices and actually making work a conscious choice. That may sound like nothing more than semantics or babble, but I suspect other work-at-home types will get what I’m saying.

For me, this mentality needs to extend to “extracurricular” work-like activities, as well. I just went back through my 2012 calendar, and I counted 19 weekends where I was traveling or engaged in (SharePoint) community activities. That’s over a third of the weekends for the year. Many of those events are things I just sort of “fell” into without thinking too much about it. Perhaps I’d choose to do them all anyway, but again – it needs to be a choice, not the default course of action.

3. Spend Time on Impactful Efforts. Of all my work-related resolutions, this is the one that’s been on my mind the most. As I already mentioned (and many of you know), I spend a lot of time answering questions in email, speaking at and organizing SharePoint events, writing, blogging, etc. Although I originally viewed all of these activities as equally “good things,” in the past year or so I’ve begun to see that some of those activities are more impactful (and thus “more good”) to a wider audience than others.

In 2013, I intend to focus more of my time on efforts that are going to help “the many” rather than “the few.” No, that doesn’t mean I’m going to stop answering email and cease meaningful one-on-one interactions, but I do intend to choose where I spend my time more carefully.

In broader terms, I also intend to focus my capabilities on topics and areas that are generally more meaningful in nature. For example, my wife and her co-worker started a project a while back that has been gaining a lot of traction at a regional level – and the scope of the project is growing. Their effort, The Schizophrenia Oral History Project, profoundly impacts the lives of people living with schizophrenia and those caring for them, providing services to them, and others. I’ve been providing “technical support” (via an introduction to Prezi, registering domain names, etc.) for the project for a while, and I’m currently building a web site for the project using SharePoint and the Office 365 Preview. This sort of work is much more meaningful and fulfilling than some of the other things I’ve spent my time on, and so I want to do more of it.

Non-Work

1. Lose Another Ten Pounds. My weight has gone up and down a few times in the past. At the beginning of 2012, I was pretty heavy … and I felt it. I was out of shape, lethargic, and pretty miserable. Over the course of 2012, I lost close to 30 pounds through a combination of diet (I have Mark Rackley to thank for the plan) and exercise. Now at the end of the year, I’ve been bouncing around at roughly the same weight for a month or two – something I attribute primarily to the holidays and all the good food that’s been around. In 2013, I plan to lose another ten pounds to get down to (what I feel) is an optimal weight.

2. Take Up a Martial Art Once Again. This will undoubtedly help with #1 directly above. I practiced a couple of different martial arts in the past. Before and during college, I practiced Tae Kwon Do. A few years back, I had to reluctantly cease learning Hapkido after only a couple of years in. Martial arts are something I’ve always enjoyed (well, except when I was doing something like separating a shoulder), and I’ve found that life generally feels more balanced when I’m practicing. With the recent enrollment of my five year-old son into a martial arts program, I’m once again feeling the pull. I’ve wanted to learn more about Krav Maga for a while; since there’s a school nearby, I intend to check it out.

3. Prioritize My Home Life. This may be last on my list, but it’s certainly not least. With everything I’ve described so far, it’s probably no surprise to read that I do a pretty poor job of prioritizing home life and family activities. That’s going to change in 2013. Provided I make some headway with my other resolutions, it will become easier to focus on my wife, my kids, and my own interests without feelings of guilt.

Wrap-Up

I’ve written these resolutions down on a Post-It, and that Post-It has been placed on one of my monitors. That’ll ensure that it stays “in my face.”

Do you have any resolutions you’re making? Big changes?

References and Resources

  1. Blog: Brian Jackett
  2. Microsoft: Premier Field Engineering (PFE) Team
  3. Blog Post: Brian Jackett – Goals for 2010
  4. Company: Idera
  5. Company: Starbucks
  6. LinkedIn: Sean McDonough
  7. Facebook: Sean McDonough
  8. LinkedIn: Dr. Tracy McDonough
  9. LinkedIn: Dr. Lynda Crane
  10. Prezi: The Schizophrenia Oral History Project
  11. Prezi: Home Page
  12. Microsoft: Office 365 Preview
  13. Blog: Mark Rackley (The SharePoint Hillbilly)
  14. Wikipedia: Taekwondo
  15. Wikipedia: Hapkido
  16. Wikipedia: Krav Maga
Categories: News Tags: , , ,
Follow

Get every new post delivered to your Inbox.

Join 4,233 other followers

%d bloggers like this: