Monday, June 24, 2013

Office 365: Recreate cross site publishing


This is a very to-the-point description of how I made a setup similar to the new Cross site Publishing features in SharePoint 2013 but in the current version of Office 365 that does not yet have those standard features.

The goal is to pull in content that is tagged with a certain Term from one site collection in another, based on a property of the current user's profile (such as Country = NL). This allows from complete de-coupling of the authoring and the publishing sites, which means you can publish a piece of content once and then have it appear on different devices with custom branding in the desired presentation style.

Currently the standard SharePoint 2013 to do this are not yet enabled in Office365. That is why I created this javascript in order to create the same functionality but with client-side scripting and the REST API's.

General:
1. Create Managed metadata term set (in this case HRS > Country ).
2. Add managed property to User Profiles in SharePoint Administration > Profiles. Type is text and the property is connected to the Country Termset, same as your site column.
3. Set your own user profile property value to a Term that you will create content for at step 9.
4. Create two site collections: Author and Reader.

Authoring site collection:
4. In the Author site collection setup variations in the normal way: Add a few variation labels and locales.
5. Create Site Column attached to managed metadata term set (single valued). In this case HRS_Country.
6. Create content type based on Publishing Page. Add Site Column HRS Country.
7. Create a page layout (download the webpart page layout from the masterpage gallery, rename it and upload it again). Attach it to the content type you created during upload.
8. Make the page layout available in the variation subsites.
9. Create some content in the source variation. When the variation timer job has run, update the page's tagging and content in the target variations.
10. Turn off automatic propagation of changes in the variation settings, because they're annoying as hell.
11. Wait for a crawl of the content.

In the Reader site collection:
12. Create custom masterpage.
13. Add script references to custom masterpage.
14. Set masterpage of the site to your custom masterpage.
15. Add javascript/jquery in a script editor webpart on the homepage of the 'Reader' site.

The javascript will wait until the correct JS libraries have been loaded. After this it gets the current user's profile properties and then runs a query on the search API with the current user's Country value.
It will return all items that are tagged with the same country as the current user's profile. It will get three fields Url, PublishingImage and Title.

In the Itemsfound method the html will be built that places the values of those three columns of the found items in a UL (ala the content query webpart but across site collections.

Great Success! You've just rebuilt the new Cross site Publishing functionality and Content by Search webparts but with the REST API's.


This means it will also work in Office365 right now (which should receive the cross site publishing feature later this year).

Javascript for 'Cross-site Publishing'

<script>
// initialize profile properties
var g_CurrentUserProfileProperties = "";
var g_CurrentUserName = "";
var g_CurrentUserCountry = "";
var g_CurrentUserLanguage = "";
var g_CurrentUserAccountName = "";
var g_CurrentUserWorkEmail = "";
</script>

<!-- div that will be appended with the found items -->
<div id="divContentContainer"></div>

<!-- script that will do the deed -->
<script type="text/javascript">
    $(document).ready(function ($) {
 var e = ExecuteOrDelayUntilScriptLoaded(DisplaySearchResults, "sp.js");      
    });

function DisplaySearchResults()
{
CallloadCurrentUserDataWhenScriptIsloaded();
}


function CallloadCurrentUserDataWhenScriptIsloaded()
{
SP.SOD.executeOrDelayUntilScriptLoaded(loadCurrentUserData, 'SP.UserProfiles.js');
}

function loadCurrentUserData()
{
//Get Current Context
var clientContext = new SP.ClientContext.get_current();

//Get Instance of People Manager Class
var peopleManager = new SP.UserProfiles.PeopleManager(clientContext);

//Get properties of the current user
userProfileProperties = peopleManager.getMyProperties();

clientContext.load(userProfileProperties);

//Execute the Query.
clientContext.executeQueryAsync(onloadCurrentUserDataSuccess, onloadCurrentUserDataFail);

}
 
function onloadCurrentUserDataSuccess() {  

// Get ProfileProperties object
g_CurrentUserProfileProperties = userProfileProperties.get_userProfileProperties();

// Get specific properties
g_CurrentUserName = userProfileProperties.get_displayName();
g_CurrentUserCountry = g_CurrentUserProfileProperties['Country'];
g_CurrentUserLanguage = g_CurrentUserProfileProperties['Language'];
g_CurrentUserAccountName = g_CurrentUserProfileProperties['AccountName'];
g_CurrentUserWorkEmail = g_CurrentUserProfileProperties['WorkEmail'];

console.log(g_CurrentUserName);
console.log(g_CurrentUserCountry);
console.log(g_CurrentUserAccountName);
console.log(g_CurrentUserWorkEmail);

if (g_CurrentUserCountry != null && g_CurrentUserCountry != undefined)
{
var basePath = "https://jwiersem.sharepoint.com/sites/hrs-content/_api/";
var aFullQuery = "search/query?Querytext='HRS_Country=" + g_CurrentUserCountry + "'&selectproperties='Url,Title,PublishingImage'";

$.ajax({
url: basePath + aFullQuery,
type: "GET",
headers: { "Accept": "application/json;odata=verbose" },
success: function (data) {
ItemsFound(data);
},
error: function (data) {
//output error HERE
alert('Error api call 1: ' + data.statusText);
}
});
}
}

function onloadCurrentUserDataFail(sender, args) {
alert("Error: " + args.get_message());
}

function ItemsFound(data)
{
//script to build UI HERE
//get the details for each item
var listData = data.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results;

if (listData != null && listData != undefined)
{
var itemCount = listData.length;
var processedCount = 0;
var ul = $("<ul>");
for (i = 0; i < listData.length; i++) {
var aCurrentItem = listData[i].Cells.results;
var aCurrentItemURI = GetValueFromArray(aCurrentItem, "Url");
var aCurrentItemTitle = GetValueFromArray(aCurrentItem, "Title");
var aCurrentItemImageHTML = GetValueFromArray(aCurrentItem, "PublishingImage");
aCurrentItemImageHTML = unescape(aCurrentItemImageHTML);

// Show only the pages with the same language as the user's preferences
var aIsCurrentUserLanguageItem = IsCurrentUserLanguageItemOrGlobal(aCurrentItemURI, g_CurrentUserLanguage);

if (aIsCurrentUserLanguageItem)
{
var htmlStr = "";
var aLink = "<a href='" + aCurrentItemURI + "'>" + aCurrentItemImageHTML + "</a>";
var aTitleSpan = "<span>" + aCurrentItemTitle + "</span>";
var aFullDiv = "<div>" + aLink + aTitleSpan + "</div>";

htmlStr += "<li>" + aFullDiv + "</li>";

ul.append($(htmlStr));
}
} // end for all items in listData
ul.append("</ul>");
$("#divContentContainer").append(ul);
}
}

function IsCurrentUserLanguageItemOrGlobal(aCurrentItemURI, aCurrentUserLanguage)
{
var Yesitis = ((aCurrentItemURI.toLowerCase().indexOf(aCurrentUserLanguage) >= 0) || (aCurrentItemURI.toLowerCase().indexOf("global") >= 0));

return Yesitis;
}

function GetValueFromArray(aArray, aKey)
{
var aReturnValue = "";
var aCounter = 0;

if (aArray != null && aArray != undefined && aArray.length > 0)
{
if (aKey != null && aKey != undefined)
{
$.each(aArray, function(key, value)
{
var bKey = aArray[key]["Key"];
if (bKey == aKey)
{
aReturnValue = aArray[key]["Value"];
aCounter++;
}
});
}
}

if (aCounter > 1)
{
console.log('Error in GetValueFromArray: Multiple entries foudn in array with the same key');
}

if (aReturnValue == undefined || aReturnValue == null || aReturnValue == "")
{
console.log('Error in GetValueFromArray: Key not found in array');
}

return aReturnValue;
}
</script>