Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Client-side Content Editor / Script Editor include in order to extend the SharePoint 2013 content by search and search results webparts with a new token for filtering on followed sites by the active user

Client-side Content Editor / Script Editor include in order to extend the SharePoint 2013 content by search and search results webparts with a new token for filtering on followed sites by the active user.

Deployment instruction

  1. Add ce_followedsites.html to your siteassets document library
  2. Add a content editor / script editor to your webpart page / wiki page / publishing page
  3. Link this content editor to this ce_followedsites.html
  4. Use {FollowedSites} (uses '=' as the operator) or {FollowedSitesBeginsWith} (':' as the operator) as a token in your search query. Please note: the search results preview panel does not give back any results. Use the Search tool from codeplex to build your search query.

Second note: you need to configure your search webparts to execute the search queries client-side. You will find this option in the query builder options tab.

<script type="text/javascript">
(function(){
// Content Editor / Script Editor include in order to extend the SharePoint 2013 content by search and search results webparts with a new token for filtering on followed sites by the active user
// (c) 2015 Carl in 't Veld
// dependencies: jQuery
// This script works by introducing two extensions on top existing SharePoint OOTB functions:
// 1. SP.ClientRuntimeContext
// Extending the function executeQueryAsync, we create a stack in the ClientRuntimeContext instance
// that contains a range promises that must be resolved before the executeQueryAsync ultimately fires
// 2. Microsoft.SharePoint.Client.Search.Query.SearchExecutor
// Extending the function ExecuteQueries we scan on the token {FollowedSites} or {FollowedSitesBeginsWith} and replace it with the followed sites by the user
var oldexecuteQueries = Microsoft.SharePoint.Client.Search.Query.SearchExecutor.prototype.executeQueries;
var oldexecuteQueryAsync = SP.ClientRuntimeContext.prototype.executeQueryAsync;
var token = "{FollowedSites}"; // will use the '=' operator
var tokenBeginsWith = "{FollowedSitesBeginsWith}"; // will use the ':' operator
function replaceTokeninArray(token, queries, followedSites, operator) {
var joinstring = "(";
for (var i = 0; i < followedSites.length; i++) {
joinstring += " path" + operator + "\"" + followedSites[i].Uri + "\"";
}
joinstring += ")";
for (var idx in queries) {
var item = queries[idx];
var query = item.get_queryTemplate();
var split= query.split(token);
if (split.length > 1) {
var joined = split.join(joinstring);
item.set_queryTemplate(joined);
}
}
}
function replaceLogic(queries, followedSites) {
replaceTokeninArray(token, queries, followedSites, "=");
replaceTokeninArray(tokenBeginsWith, queries, followedSites, ":");
return;
}
var followedSites; // cache variable for the followed sites
Microsoft.SharePoint.Client.Search.Query.SearchExecutor.prototype.executeQueries = function() {
var activeContext = this.get_context(); // the ClientRuntimeContext that we want to catch during a executeQueryAsync so that we can chain our followed-sites call
var queryIds = arguments[0];
var queries = arguments[1];
var handleExceptions = arguments[2];
var arr = queries; // an array with Microsoft_SharePoint_Client_Search_Query_KeywordQuery items
// check if one of the queries contains the token:
var found = false;
for (var idx in arr) {
var item = arr[idx];
var query = item.get_queryTemplate();
if (query.indexOf(token) !== -1) {
found = true;
break;
}
}
// one could force the deferred construction even if the token is not present, by forcing found to true:
// found = true;
if (found) {
// our token is present; we must act
// test if followedSites was already retrieved:
if (followedSites === undefined) {
// followedSites was not retrieved before; has yet to be retrieved
var deferred = jQuery.Deferred();
activeContext.promises = activeContext.promises || [];
activeContext.promises.push(deferred.promise()); // we tell our ClientRuntimeContext extension that we want to wait on something to complete
var that = this;
var myargs = Array.prototype.slice.call(arguments);
// we create an empty results-object that we already return to the original executeQueries-call:
var results = new SP.JsonObjectResult();
// retrieve the followed sites:
getFollowedSitesAsync()
.always(function(_followedSites){
if (this.state() === "resolved") {
followedSites = _followedSites;
}
else {
// failed; most probably because the user has not yet have a social data store, i.e. no mysite yet.
// one could present an informative message but we choose to force that the query does not yield any results:
followedSites = "@@";
}
// now we have retrieved the followed sites, we can replace the token within the query:
replaceLogic(queries, followedSites)
/* copied from sp.search.debug.js:
executeQueries: function Microsoft_SharePoint_Client_Search_Query_SearchExecutor$executeQueries(queryIds, queries, handleExceptions) {
var $v_0 = this.get_context();
var $v_1;
var $v_2 = new SP.ClientActionInvokeMethod(this, 'ExecuteQueries', [ queryIds, queries, handleExceptions ]);
$v_0.addQuery($v_2);
$v_1 = new SP.JsonObjectResult();
$v_0.addQueryIdAndResultObject($v_2.get_id(), $v_1);
return $v_1;
},*/
// we have to build up the body of the original executeQueries ourselves completely
// because we have already given back an empty results-object to the originalexecuteQueries-call:
var method = new SP.ClientActionInvokeMethod(that, 'ExecuteQueries', [ queryIds, queries, handleExceptions ]);
activeContext.addQuery(method);
activeContext.addQueryIdAndResultObject(method.get_id(), results);
deferred.resolve();
});
return results;
}
else {
// use the cached result:
replaceLogic(queries, followedSites)
return oldexecuteQueries.apply(that, myargs);
}
}
else
// no token replacement necessary; just execute
return oldexecuteQueries.apply(this, arguments);
}
var execute = function (followingManagerEndpoint) {
var dfd = jQuery.Deferred();
jQuery.ajax({
url: followingManagerEndpoint + "/my/followed(types=4)",
headers: {
"accept": "application/json;odata=verbose"
},
success: function (data) {
dfd.resolve(data);
},
error: function (xhr, ajaxOptions, thrownError) {
dfd.reject({
xhr: xhr,
ajaxOptions: ajaxOptions,
thrownError: thrownError
});
}
});
return dfd.promise();
};
SP.ClientRuntimeContext.prototype.executeQueryAsync = function() {
if (this.promises !== undefined && this.promises.length > 0) {
var that = this;
var myargs = Array.prototype.slice.call(arguments);
var currlength = this.promises.length;
jQuery.when.apply(jQuery, this.promises).then(function() {
if (currlength === that.promises.length) that.promises = []; // flush them all; theoretical performance benefit
oldexecuteQueryAsync.apply(that, myargs);
});
}
else
return oldexecuteQueryAsync.apply(this, arguments);
}
var getFollowedSitesAsyncDeferred;
function getFollowedSitesAsync() {
if (getFollowedSitesAsyncDeferred && getFollowedSitesAsyncDeferred.state() !== "resolved") {
return getFollowedSitesAsyncDeferred;
}
getFollowedSitesAsyncDeferred = jQuery.Deferred();
var followingManagerEndpoint = SP.PageContextInfo.get_webAbsoluteUrl() + "/_api/social.following";
execute(followingManagerEndpoint).then(function(data) {
console.log(data);
var followedActors = data.d.Followed.results;
var query = "";
/* for (var i = 0; i < followedActors.length; i++) {
query += " path=\"" + followedActors[i].Uri + "\"";
} */
getFollowedSitesAsyncDeferred.resolve(followedActors); // one could move the AND operator to the search webpart
})
.fail(function(error) {
console.log(error);
getFollowedSitesAsyncDeferred.reject(error);
});
return getFollowedSitesAsyncDeferred.promise();
}
})(); // self-calling anonymous function
</script>

Hi,
Very nice work and thank you for sharing!
I have implemented a solution using your method, and in the beginning it was all great.
Then I added Paging in the Search Results WP, and now I get the a javascript error in "sp.search.js" and another in "sp.runtime.js".
"Object doesn't support property or method 'get_context'" and "Incorrect usage of exception handling scope.".

Does this look familer to you? Have you experienced the same issues? And are you working on a fix for this.
Or is it some where else in my setup?
Thanks!
Martin

Hey,

Could you tell me which managed property should i use with this ? I tried "Path" but it returns every items from sites i can access, not sites i'm following,

Thanks !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment