|
<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