Added a Site Url property next to the Web Url property
Added a Site Url property next to the Web Url property in order to narrow down the results that were previously all mixed within the same dropdown. Also added a recursive promise mecanism that allows the WebPart to perform multiple search requests until all the results are returning, bypassing the max rowLimit of 500 items. The site url property can now fetch unlimited site collections, and the web url property can also fetch unlimited web urls.
This commit is contained in:
parent
4dea634f15
commit
dc136bfeda
Binary file not shown.
After Width: | Height: | Size: 498 KiB |
|
@ -28,6 +28,7 @@ Version|Date|Comments
|
||||||
1.0.1|July 23rd 15, 2017|Updated to GA Version
|
1.0.1|July 23rd 15, 2017|Updated to GA Version
|
||||||
1.0.3|August 12, 2017|Added external scripts functionnality
|
1.0.3|August 12, 2017|Added external scripts functionnality
|
||||||
1.0.4|August 31, 2017|Fixed a bug where tenant sites/subsites were missing from the **Web Url** dropdown
|
1.0.4|August 31, 2017|Fixed a bug where tenant sites/subsites were missing from the **Web Url** dropdown
|
||||||
|
1.0.5|September 1st, 2017|Added a **Site Url** parameter next to the **Web Url** parameter in order to narrow down the results
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
||||||
|
@ -38,7 +39,7 @@ Version|Date|Comments
|
||||||
|
|
||||||
The WebPart uses the search in order to get all sites under the current domain, which makes it possible to query not only subsites but other site collections and their subsites as well.
|
The WebPart uses the search in order to get all sites under the current domain, which makes it possible to query not only subsites but other site collections and their subsites as well.
|
||||||
|
|
||||||
<img src="Misc/allsites.gif" />
|
<img src="Misc/allsites_v2.gif" />
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
### Unlimited filters
|
### Unlimited filters
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"solution": {
|
"solution": {
|
||||||
"name": "React Content Query",
|
"name": "React Content Query",
|
||||||
"id": "00406271-0276-406f-9666-512623eb6709",
|
"id": "00406271-0276-406f-9666-512623eb6709",
|
||||||
"version": "1.0.4.0"
|
"version": "1.0.5.0"
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
"zippedPackage": "solution/react-content-query-webpart.sppkg"
|
"zippedPackage": "solution/react-content-query-webpart.sppkg"
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"cdnBasePath": "https://publiccdn.sharepointonline.com/spptechnologies.sharepoint.com/110700492eeea162ee5bad0f35b1f0061ded8bf436ce0199efe2a4d24109e1c0df1ec594/react-content-query-1.0.4"
|
"cdnBasePath": "https://publiccdn.sharepointonline.com/spptechnologies.sharepoint.com/110700492eeea162ee5bad0f35b1f0061ded8bf436ce0199efe2a4d24109e1c0df1ec594/react-content-query-1.0.5"
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "react-content-query",
|
"name": "react-content-query",
|
||||||
"version": "1.0.2",
|
"version": "1.0.4",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "react-content-query",
|
"name": "react-content-query",
|
||||||
"version": "1.0.4",
|
"version": "1.0.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
|
|
Binary file not shown.
|
@ -4,6 +4,7 @@ export class ContentQueryConstants {
|
||||||
/**************************************************************
|
/**************************************************************
|
||||||
* WebPart Properties
|
* WebPart Properties
|
||||||
**************************************************************/
|
**************************************************************/
|
||||||
|
public static readonly propertySiteUrl = "siteUrl";
|
||||||
public static readonly propertyWebUrl = "webUrl";
|
public static readonly propertyWebUrl = "webUrl";
|
||||||
public static readonly propertyListTitle = "listTitle";
|
public static readonly propertyListTitle = "listTitle";
|
||||||
public static readonly propertyOrderBy = "orderBy";
|
public static readonly propertyOrderBy = "orderBy";
|
||||||
|
|
|
@ -40,6 +40,7 @@ export class ContentQueryService implements IContentQueryService {
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
* Stores the first async calls locally to avoid useless redundant calls
|
* Stores the first async calls locally to avoid useless redundant calls
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
private siteUrlOptions: IDropdownOption[];
|
||||||
private webUrlOptions: IDropdownOption[];
|
private webUrlOptions: IDropdownOption[];
|
||||||
private listTitleOptions: IDropdownOption[];
|
private listTitleOptions: IDropdownOption[];
|
||||||
private orderByOptions: IDropdownOption[];
|
private orderByOptions: IDropdownOption[];
|
||||||
|
@ -143,13 +144,53 @@ export class ContentQueryService implements IContentQueryService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**************************************************************************************************
|
/**************************************************************************************************
|
||||||
* Gets the available webs for the current user
|
* Gets the available webs for the current user
|
||||||
**************************************************************************************************/
|
**************************************************************************************************/
|
||||||
public getWebUrlOptions(): Promise<IDropdownOption[]> {
|
public getSiteUrlOptions(): Promise<IDropdownOption[]> {
|
||||||
|
Log.verbose(this.logSource, "Loading dropdown options for toolpart property 'Site Url'...", this.context.serviceScope);
|
||||||
|
|
||||||
|
// Resolves the already loaded data if available
|
||||||
|
if(this.siteUrlOptions) {
|
||||||
|
return Promise.resolve(this.siteUrlOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, performs a REST call to get the data
|
||||||
|
return new Promise<IDropdownOption[]>((resolve,reject) => {
|
||||||
|
let serverUrl = Text.format("{0}//{1}", window.location.protocol, window.location.hostname);
|
||||||
|
|
||||||
|
this.searchService.getSitesStartingWith(serverUrl)
|
||||||
|
.then((urls) => {
|
||||||
|
let options:IDropdownOption[] = [ { key: "", text: strings.SiteUrlFieldPlaceholder } ];
|
||||||
|
let urlOptions:IDropdownOption[] = urls.sort().map((url) => {
|
||||||
|
let serverRelativeUrl = !isEmpty(url.replace(serverUrl, '')) ? url.replace(serverUrl, '') : '/';
|
||||||
|
return { key: url, text: serverRelativeUrl };
|
||||||
|
});
|
||||||
|
options = options.concat(urlOptions);
|
||||||
|
this.siteUrlOptions = options;
|
||||||
|
resolve(options);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**************************************************************************************************
|
||||||
|
* Gets the available webs for the current user
|
||||||
|
* @param siteUrl : The url of the site from which webs must be loaded from
|
||||||
|
**************************************************************************************************/
|
||||||
|
public getWebUrlOptions(siteUrl: string): Promise<IDropdownOption[]> {
|
||||||
Log.verbose(this.logSource, "Loading dropdown options for toolpart property 'Web Url'...", this.context.serviceScope);
|
Log.verbose(this.logSource, "Loading dropdown options for toolpart property 'Web Url'...", this.context.serviceScope);
|
||||||
|
|
||||||
|
// Resolves an empty array if site is null
|
||||||
|
if (isEmpty(siteUrl)) {
|
||||||
|
return Promise.resolve(new Array<IDropdownOption>());
|
||||||
|
}
|
||||||
|
|
||||||
// Resolves the already loaded data if available
|
// Resolves the already loaded data if available
|
||||||
if(this.webUrlOptions) {
|
if(this.webUrlOptions) {
|
||||||
return Promise.resolve(this.webUrlOptions);
|
return Promise.resolve(this.webUrlOptions);
|
||||||
|
@ -157,14 +198,13 @@ export class ContentQueryService implements IContentQueryService {
|
||||||
|
|
||||||
// Otherwise, performs a REST call to get the data
|
// Otherwise, performs a REST call to get the data
|
||||||
return new Promise<IDropdownOption[]>((resolve,reject) => {
|
return new Promise<IDropdownOption[]>((resolve,reject) => {
|
||||||
let serverUrl = Text.format("{0}//{1}", window.location.protocol, window.location.hostname);
|
|
||||||
|
|
||||||
this.searchService.getWebUrlsForDomain(serverUrl)
|
this.searchService.getWebsFromSite(siteUrl)
|
||||||
.then((urls:string[]) => {
|
.then((urls) => {
|
||||||
let options:IDropdownOption[] = [ { key: "", text: strings.WebUrlFieldPlaceholder } ];
|
let options:IDropdownOption[] = [ { key: "", text: strings.WebUrlFieldPlaceholder } ];
|
||||||
let urlOptions:IDropdownOption[] = urls.sort().map((url) => {
|
let urlOptions:IDropdownOption[] = urls.sort().map((url) => {
|
||||||
let serverRelativeUrl = !isEmpty(url.replace(serverUrl, '')) ? url.replace(serverUrl, '') : '/';
|
let siteRelativeUrl = !isEmpty(url.replace(siteUrl, '')) ? url.replace(siteUrl, '') : '/';
|
||||||
return { key: url, text: serverRelativeUrl };
|
return { key: url, text: siteRelativeUrl };
|
||||||
});
|
});
|
||||||
options = options.concat(urlOptions);
|
options = options.concat(urlOptions);
|
||||||
this.webUrlOptions = options;
|
this.webUrlOptions = options;
|
||||||
|
@ -448,6 +488,15 @@ export class ContentQueryService implements IContentQueryService {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* Resets the stored 'list title' options
|
||||||
|
***************************************************************************/
|
||||||
|
public clearCachedWebUrlOptions() {
|
||||||
|
Log.verbose(this.logSource, "Clearing cached dropdown options for toolpart property 'Web Url'...", this.context.serviceScope);
|
||||||
|
this.webUrlOptions = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
* Resets the stored 'list title' options
|
* Resets the stored 'list title' options
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
|
|
@ -8,7 +8,8 @@ import { IQuerySettings } from '../../webparts/contentQuer
|
||||||
export interface IContentQueryService {
|
export interface IContentQueryService {
|
||||||
getTemplateContext: (querySettings: IQuerySettings, callTimeStamp: number) => Promise<IContentQueryTemplateContext>;
|
getTemplateContext: (querySettings: IQuerySettings, callTimeStamp: number) => Promise<IContentQueryTemplateContext>;
|
||||||
getFileContent: (fileUrl: string) => Promise<string>;
|
getFileContent: (fileUrl: string) => Promise<string>;
|
||||||
getWebUrlOptions: () => Promise<IDropdownOption[]>;
|
getSiteUrlOptions: () => Promise<IDropdownOption[]>;
|
||||||
|
getWebUrlOptions: (siteUrl: string) => Promise<IDropdownOption[]>;
|
||||||
getListTitleOptions: (webUrl: string) => Promise<IDropdownOption[]>;
|
getListTitleOptions: (webUrl: string) => Promise<IDropdownOption[]>;
|
||||||
getOrderByOptions: (webUrl: string, listTitle: string) => Promise<IDropdownOption[]>;
|
getOrderByOptions: (webUrl: string, listTitle: string) => Promise<IDropdownOption[]>;
|
||||||
getFilterFields: (webUrl: string, listTitle: string) => Promise<IQueryFilterField[]>;
|
getFilterFields: (webUrl: string, listTitle: string) => Promise<IQueryFilterField[]>;
|
||||||
|
@ -18,6 +19,7 @@ export interface IContentQueryService {
|
||||||
ensureFileResolves: (filePath: string) => Promise<{}>;
|
ensureFileResolves: (filePath: string) => Promise<{}>;
|
||||||
isValidTemplateFile: (filePath: string) => boolean;
|
isValidTemplateFile: (filePath: string) => boolean;
|
||||||
generateDefaultTemplate: (viewFields: string[]) => string;
|
generateDefaultTemplate: (viewFields: string[]) => string;
|
||||||
|
clearCachedWebUrlOptions: () => void;
|
||||||
clearCachedListTitleOptions: () => void;
|
clearCachedListTitleOptions: () => void;
|
||||||
clearCachedOrderByOptions: () => void;
|
clearCachedOrderByOptions: () => void;
|
||||||
clearCachedFilterFields: () => void;
|
clearCachedFilterFields: () => void;
|
||||||
|
|
|
@ -79,7 +79,8 @@ export class ListService {
|
||||||
* Returns a sorted array of all available list titles for the specified web
|
* Returns a sorted array of all available list titles for the specified web
|
||||||
* @param webUrl : The web URL from which the specified list is located
|
* @param webUrl : The web URL from which the specified list is located
|
||||||
* @param listTitle : The title of the list from which to load the fields
|
* @param listTitle : The title of the list from which to load the fields
|
||||||
* @param selectProperties : Optionnaly, the select properties to narrow down the query scope
|
* @param selectProperties : Optionnaly, the select properties to narrow down the query size
|
||||||
|
* @param orderBy : Optionnaly, the by which the results needs to be ordered
|
||||||
**************************************************************************************************/
|
**************************************************************************************************/
|
||||||
public getListFields(webUrl: string, listTitle: string, selectProperties?: string[], orderBy?: string): Promise<any> {
|
public getListFields(webUrl: string, listTitle: string, selectProperties?: string[], orderBy?: string): Promise<any> {
|
||||||
return new Promise<any>((resolve,reject) => {
|
return new Promise<any>((resolve,reject) => {
|
||||||
|
|
|
@ -19,43 +19,156 @@ export class SearchService {
|
||||||
|
|
||||||
|
|
||||||
/**************************************************************************************************
|
/**************************************************************************************************
|
||||||
* Returns the web urls starting with the specified domain to which the current user has access
|
* Recursively executes the specified search query until all results are fetched
|
||||||
* @param domainUrl : The url of the web which contains the specified list
|
* @param webUrl : The web url from which to call the REST API
|
||||||
|
* @param queryParameters : The search query parameters following the "/_api/search/query?" part
|
||||||
**************************************************************************************************/
|
**************************************************************************************************/
|
||||||
public getWebUrlsForDomain(domainUrl: string): Promise<string[]> {
|
public getSearchResultsRecursive(webUrl: string, queryParameters: string): Promise<any> {
|
||||||
return new Promise<string[]>((resolve,reject) => {
|
return new Promise<any>((resolve,reject) => {
|
||||||
let endpoint = Text.format("{0}/_api/search/query?querytext='Path:{0}/* AND (contentclass:STS_Site OR contentclass:STS_Web)'&selectproperties='Path'&trimduplicates=false&rowLimit=500", domainUrl);
|
|
||||||
|
// Executes the search request for a first time in order to have an idea of the returned rows vs total results
|
||||||
|
this.getSearchResults(webUrl, queryParameters)
|
||||||
|
.then((results: any) => {
|
||||||
|
|
||||||
|
// If there is more rows available...
|
||||||
|
let relevantResults = results.PrimaryQueryResult.RelevantResults;
|
||||||
|
let initialResults:any[] = relevantResults.Table.Rows;
|
||||||
|
|
||||||
|
if(relevantResults.TotalRowsIncludingDuplicates > relevantResults.RowCount) {
|
||||||
|
|
||||||
|
// Stores and executes all the missing calls in parallel until we have ALL results
|
||||||
|
let promises = new Array<Promise<any>>();
|
||||||
|
let nbPromises = Math.ceil(relevantResults.TotalRowsIncludingDuplicates / relevantResults.RowCount);
|
||||||
|
|
||||||
|
for(let i = 1; i < nbPromises; i++) {
|
||||||
|
let nextStartRow = (i * relevantResults.RowCount);
|
||||||
|
promises.push(this.getSearchResults(webUrl, queryParameters, nextStartRow));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once the missing calls are done, concatenates their results to the first request
|
||||||
|
Promise.all(promises).then((values) => {
|
||||||
|
for(let recursiveResults of values) {
|
||||||
|
initialResults = initialResults.concat(recursiveResults.PrimaryQueryResult.RelevantResults.Table.Rows);
|
||||||
|
}
|
||||||
|
results.PrimaryQueryResult.RelevantResults.Table.Rows = initialResults;
|
||||||
|
results.PrimaryQueryResult.RelevantResults.RowCount = initialResults.length;
|
||||||
|
resolve(results);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
// If no more rows are available
|
||||||
|
else {
|
||||||
|
resolve(results);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**************************************************************************************************
|
||||||
|
* Recursively executes the specified search query using batches of 500 results until all results are fetched
|
||||||
|
* @param webUrl : The web url from which to call the search API
|
||||||
|
* @param queryParameters : The search query parameters following the "/_api/search/query?" part
|
||||||
|
* @param startRow : The row from which the search needs to return the results from
|
||||||
|
**************************************************************************************************/
|
||||||
|
public getSearchResults(webUrl: string, queryParameters: string, startRow?: number): Promise<any> {
|
||||||
|
return new Promise<any>((resolve,reject) => {
|
||||||
|
|
||||||
|
queryParameters = this.ensureSearchQueryParameter(queryParameters, 'StartRow', startRow);
|
||||||
|
let endpoint = Text.format("{0}/_api/search/query?{1}", webUrl, queryParameters);
|
||||||
|
|
||||||
// Gets the available webs for the current domain with a search query
|
|
||||||
this.spHttpClient.get(endpoint, SPHttpClient.configurations.v1).then((response: SPHttpClientResponse) => {
|
this.spHttpClient.get(endpoint, SPHttpClient.configurations.v1).then((response: SPHttpClientResponse) => {
|
||||||
if(response.ok) {
|
if(response.ok) {
|
||||||
response.json().then((data:any) => {
|
resolve(response.json());
|
||||||
try {
|
|
||||||
let urls:string[] = [];
|
|
||||||
let pathIndex = null;
|
|
||||||
|
|
||||||
for(let result of data.PrimaryQueryResult.RelevantResults.Table.Rows) {
|
|
||||||
// Stores the index of the "Path" cell on the first loop in order to avoid finding the cell on every loop
|
|
||||||
if(!pathIndex) {
|
|
||||||
let pathCell = result.Cells.filter((cell) => { return cell.Key == "Path"; })[0];
|
|
||||||
pathIndex = result.Cells.indexOf(pathCell);
|
|
||||||
}
|
|
||||||
urls.push(result.Cells[pathIndex].Value);
|
|
||||||
}
|
|
||||||
resolve(urls);
|
|
||||||
}
|
|
||||||
catch(error) {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => { reject(error); });
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
reject(response.statusText);
|
reject(response.statusText);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => { reject(error); });
|
.catch((error) => { reject(error); });
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**************************************************************************************************
|
||||||
|
* Recursively searches for all site collections with a path which starts by the specified url
|
||||||
|
* @param startingUrl : The url of the domain from which to find the site collections
|
||||||
|
**************************************************************************************************/
|
||||||
|
public getSitesStartingWith(startingUrl: string): Promise<string[]> {
|
||||||
|
return new Promise<string[]>((resolve,reject) => {
|
||||||
|
let queryProperties = Text.format("querytext='Path:{0}/* AND contentclass:STS_Site'&selectproperties='Path'&trimduplicates=false&rowLimit=500", startingUrl);
|
||||||
|
|
||||||
|
this.getSearchResultsRecursive(startingUrl, queryProperties)
|
||||||
|
.then((results: any) => {
|
||||||
|
resolve(this.getPathsFromResults(results));
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**************************************************************************************************
|
||||||
|
* Recursively searches for all site collections with a path which starts by the specified url
|
||||||
|
* @param siteUrl : The url of the site collection from which to find the webs
|
||||||
|
**************************************************************************************************/
|
||||||
|
public getWebsFromSite(siteUrl: string): Promise<string[]> {
|
||||||
|
return new Promise<string[]>((resolve,reject) => {
|
||||||
|
let queryProperties = Text.format("querytext='SiteName:{0} AND (contentclass:STS_Site OR contentclass:STS_Web)'&selectproperties='Path'&trimduplicates=false&rowLimit=500&filter=", siteUrl);
|
||||||
|
|
||||||
|
this.getSearchResultsRecursive(siteUrl, queryProperties)
|
||||||
|
.then((results: any) => {
|
||||||
|
resolve(this.getPathsFromResults(results));
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**************************************************************************************************
|
||||||
|
* Recursively executes the specified search query using batches of 500 results until all results are fetched
|
||||||
|
* @param queryParameters : The search query parameters following the "/_api/search/query?" part
|
||||||
|
* @param parameterName : The name of the parameter that needs to be ensured
|
||||||
|
* @param parameterValue : The value of the parameter that needs to be ensured
|
||||||
|
**************************************************************************************************/
|
||||||
|
private ensureSearchQueryParameter(queryParameters: string, parameterName: string, parameterValue: any): string {
|
||||||
|
if(parameterValue) {
|
||||||
|
let strParameter = Text.format("{0}={1}", parameterName, parameterValue);
|
||||||
|
queryParameters = queryParameters.replace(new RegExp('StartRow=\\d*', 'gi'), strParameter);
|
||||||
|
|
||||||
|
if(queryParameters.toLowerCase().indexOf(parameterName) < 0) {
|
||||||
|
queryParameters += ('&' + strParameter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return queryParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**************************************************************************************************
|
||||||
|
* Gets the paths out of the specified search results
|
||||||
|
* @param results : The url of the domain from which to find the site collections
|
||||||
|
**************************************************************************************************/
|
||||||
|
private getPathsFromResults(results: any): string[] {
|
||||||
|
let urls:string[] = [];
|
||||||
|
let pathIndex = null;
|
||||||
|
|
||||||
|
for(let result of results.PrimaryQueryResult.RelevantResults.Table.Rows) {
|
||||||
|
// Stores the index of the "Path" cell on the first loop in order to avoid finding the cell on every loop
|
||||||
|
if(!pathIndex) {
|
||||||
|
let pathCell = result.Cells.filter((cell) => { return cell.Key == "Path"; })[0];
|
||||||
|
pathIndex = result.Cells.indexOf(pathCell);
|
||||||
|
}
|
||||||
|
urls.push(result.Cells[pathIndex].Value);
|
||||||
|
}
|
||||||
|
return urls;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
import { Text } from '@microsoft/sp-core-library';
|
import { Text } from '@microsoft/sp-core-library';
|
||||||
import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';
|
import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';
|
||||||
import { SPComponentLoader } from '@microsoft/sp-loader';
|
|
||||||
import { isEmpty } from '@microsoft/sp-lodash-subset';
|
import { isEmpty } from '@microsoft/sp-lodash-subset';
|
||||||
|
|
||||||
export class TaxonomyService {
|
export class TaxonomyService {
|
||||||
|
@ -84,39 +83,4 @@ export class TaxonomyService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*************************************************************************************
|
|
||||||
* Ensures SP.js and its dependencies in order to be able to do JSOM later on
|
|
||||||
*************************************************************************************/
|
|
||||||
private ensureJSOMDependencies(): Promise<{}> {
|
|
||||||
if(window['SP']) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return SPComponentLoader.loadScript('/_layouts/15/init.js', {
|
|
||||||
globalExportsName: '$_global_init'
|
|
||||||
})
|
|
||||||
.then((): Promise<{}> => {
|
|
||||||
return SPComponentLoader.loadScript('/_layouts/15/MicrosoftAjax.js', {
|
|
||||||
globalExportsName: 'Sys'
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then((): Promise<{}> => {
|
|
||||||
return SPComponentLoader.loadScript('/_layouts/15/SP.Runtime.js', {
|
|
||||||
globalExportsName: 'SP'
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then((): Promise<{}> => {
|
|
||||||
return SPComponentLoader.loadScript('/_layouts/15/SP.js', {
|
|
||||||
globalExportsName: 'SP'
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then((): Promise<{}> => {
|
|
||||||
return SPComponentLoader.loadScript('/_layouts/15/SP.Taxonomy.js', {
|
|
||||||
globalExportsName: 'SP.Taxonomy'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -4,7 +4,7 @@
|
||||||
"id": "46edf08f-95c7-4ca7-9146-6471f9f471be",
|
"id": "46edf08f-95c7-4ca7-9146-6471f9f471be",
|
||||||
"alias": "ContentQueryWebPart",
|
"alias": "ContentQueryWebPart",
|
||||||
"componentType": "WebPart",
|
"componentType": "WebPart",
|
||||||
"version": "1.0.4",
|
"version": "1.0.5",
|
||||||
"manifestVersion": 2,
|
"manifestVersion": 2,
|
||||||
|
|
||||||
"preconfiguredEntries": [{
|
"preconfiguredEntries": [{
|
||||||
|
|
|
@ -38,6 +38,7 @@ export default class ContentQueryWebPart extends BaseClientSideWebPart<IContentQ
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
* Custom ToolPart Property Panes
|
* Custom ToolPart Property Panes
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
private siteUrlDropdown: PropertyPaneAsyncDropdown;
|
||||||
private webUrlDropdown: PropertyPaneAsyncDropdown;
|
private webUrlDropdown: PropertyPaneAsyncDropdown;
|
||||||
private listTitleDropdown: PropertyPaneAsyncDropdown;
|
private listTitleDropdown: PropertyPaneAsyncDropdown;
|
||||||
private orderByDropdown: PropertyPaneAsyncDropdown;
|
private orderByDropdown: PropertyPaneAsyncDropdown;
|
||||||
|
@ -55,7 +56,7 @@ export default class ContentQueryWebPart extends BaseClientSideWebPart<IContentQ
|
||||||
* Returns the WebPart's version
|
* Returns the WebPart's version
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
protected get dataVersion(): Version {
|
protected get dataVersion(): Version {
|
||||||
return Version.parse('1.0.4');
|
return Version.parse('1.0.5');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -89,6 +90,7 @@ export default class ContentQueryWebPart extends BaseClientSideWebPart<IContentQ
|
||||||
{
|
{
|
||||||
onLoadTemplate: this.loadTemplate.bind(this),
|
onLoadTemplate: this.loadTemplate.bind(this),
|
||||||
onLoadTemplateContext: this.loadTemplateContext.bind(this),
|
onLoadTemplateContext: this.loadTemplateContext.bind(this),
|
||||||
|
siteUrl: this.properties.siteUrl,
|
||||||
querySettings: querySettings,
|
querySettings: querySettings,
|
||||||
templateText: this.properties.templateText,
|
templateText: this.properties.templateText,
|
||||||
templateUrl: this.properties.templateUrl,
|
templateUrl: this.properties.templateUrl,
|
||||||
|
@ -107,8 +109,19 @@ export default class ContentQueryWebPart extends BaseClientSideWebPart<IContentQ
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||||
|
|
||||||
let firstCascadingLevelDisabled = !this.properties.webUrl;
|
let firstCascadingLevelDisabled = !this.properties.siteUrl;
|
||||||
let secondCascadingLevelDisabled = !this.properties.webUrl || !this.properties.listTitle;
|
let secondCascadingLevelDisabled = !this.properties.siteUrl || !this.properties.webUrl;
|
||||||
|
let thirdCascadingLevelDisabled = !this.properties.siteUrl || !this.properties.webUrl || !this.properties.listTitle;
|
||||||
|
|
||||||
|
// Creates a custom PropertyPaneAsyncDropdown for the siteUrl property
|
||||||
|
this.siteUrlDropdown = new PropertyPaneAsyncDropdown(ContentQueryConstants.propertySiteUrl, {
|
||||||
|
label: strings.SiteUrlFieldLabel,
|
||||||
|
loadingLabel: strings.SiteUrlFieldLoadingLabel,
|
||||||
|
errorLabelFormat: strings.SiteUrlFieldLoadingError,
|
||||||
|
loadOptions: this.loadSiteUrlOptions.bind(this),
|
||||||
|
onPropertyChange: this.onCustomPropertyPaneChange.bind(this),
|
||||||
|
selectedKey: this.properties.siteUrl || ""
|
||||||
|
});
|
||||||
|
|
||||||
// Creates a custom PropertyPaneAsyncDropdown for the webUrl property
|
// Creates a custom PropertyPaneAsyncDropdown for the webUrl property
|
||||||
this.webUrlDropdown = new PropertyPaneAsyncDropdown(ContentQueryConstants.propertyWebUrl, {
|
this.webUrlDropdown = new PropertyPaneAsyncDropdown(ContentQueryConstants.propertyWebUrl, {
|
||||||
|
@ -117,7 +130,8 @@ export default class ContentQueryWebPart extends BaseClientSideWebPart<IContentQ
|
||||||
errorLabelFormat: strings.WebUrlFieldLoadingError,
|
errorLabelFormat: strings.WebUrlFieldLoadingError,
|
||||||
loadOptions: this.loadWebUrlOptions.bind(this),
|
loadOptions: this.loadWebUrlOptions.bind(this),
|
||||||
onPropertyChange: this.onCustomPropertyPaneChange.bind(this),
|
onPropertyChange: this.onCustomPropertyPaneChange.bind(this),
|
||||||
selectedKey: this.properties.webUrl || ""
|
selectedKey: this.properties.webUrl || "",
|
||||||
|
disabled: firstCascadingLevelDisabled
|
||||||
});
|
});
|
||||||
|
|
||||||
// Creates a custom PropertyPaneAsyncDropdown for the listTitle property
|
// Creates a custom PropertyPaneAsyncDropdown for the listTitle property
|
||||||
|
@ -128,7 +142,7 @@ export default class ContentQueryWebPart extends BaseClientSideWebPart<IContentQ
|
||||||
loadOptions: this.loadListTitleOptions.bind(this),
|
loadOptions: this.loadListTitleOptions.bind(this),
|
||||||
onPropertyChange: this.onCustomPropertyPaneChange.bind(this),
|
onPropertyChange: this.onCustomPropertyPaneChange.bind(this),
|
||||||
selectedKey: this.properties.listTitle || "",
|
selectedKey: this.properties.listTitle || "",
|
||||||
disabled: firstCascadingLevelDisabled
|
disabled: secondCascadingLevelDisabled
|
||||||
});
|
});
|
||||||
|
|
||||||
// Creates a custom PropertyPaneAsyncDropdown for the orderBy property
|
// Creates a custom PropertyPaneAsyncDropdown for the orderBy property
|
||||||
|
@ -139,7 +153,7 @@ export default class ContentQueryWebPart extends BaseClientSideWebPart<IContentQ
|
||||||
loadOptions: this.loadOrderByOptions.bind(this),
|
loadOptions: this.loadOrderByOptions.bind(this),
|
||||||
onPropertyChange: this.onCustomPropertyPaneChange.bind(this),
|
onPropertyChange: this.onCustomPropertyPaneChange.bind(this),
|
||||||
selectedKey: this.properties.orderBy || "",
|
selectedKey: this.properties.orderBy || "",
|
||||||
disabled: secondCascadingLevelDisabled
|
disabled: thirdCascadingLevelDisabled
|
||||||
});
|
});
|
||||||
|
|
||||||
// Creates a custom PropertyPaneQueryFilterPanel for the filters property
|
// Creates a custom PropertyPaneQueryFilterPanel for the filters property
|
||||||
|
@ -150,7 +164,7 @@ export default class ContentQueryWebPart extends BaseClientSideWebPart<IContentQ
|
||||||
onLoadPeoplePickerSuggestions: this.loadPeoplePickerSuggestions.bind(this),
|
onLoadPeoplePickerSuggestions: this.loadPeoplePickerSuggestions.bind(this),
|
||||||
onPropertyChange: this.onCustomPropertyPaneChange.bind(this),
|
onPropertyChange: this.onCustomPropertyPaneChange.bind(this),
|
||||||
trimEmptyFiltersOnChange: true,
|
trimEmptyFiltersOnChange: true,
|
||||||
disabled: secondCascadingLevelDisabled,
|
disabled: thirdCascadingLevelDisabled,
|
||||||
strings: strings.queryFilterPanelStrings
|
strings: strings.queryFilterPanelStrings
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -159,7 +173,7 @@ export default class ContentQueryWebPart extends BaseClientSideWebPart<IContentQ
|
||||||
loadItems: this.loadViewFieldsChecklistItems.bind(this),
|
loadItems: this.loadViewFieldsChecklistItems.bind(this),
|
||||||
checkedItems: this.properties.viewFields,
|
checkedItems: this.properties.viewFields,
|
||||||
onPropertyChange: this.onCustomPropertyPaneChange.bind(this),
|
onPropertyChange: this.onCustomPropertyPaneChange.bind(this),
|
||||||
disable: secondCascadingLevelDisabled,
|
disable: thirdCascadingLevelDisabled,
|
||||||
strings: strings.viewFieldsChecklistStrings
|
strings: strings.viewFieldsChecklistStrings
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -193,7 +207,7 @@ export default class ContentQueryWebPart extends BaseClientSideWebPart<IContentQ
|
||||||
offText: 'Disabled',
|
offText: 'Disabled',
|
||||||
onText: 'Enabled',
|
onText: 'Enabled',
|
||||||
checked: this.properties.limitEnabled,
|
checked: this.properties.limitEnabled,
|
||||||
disabled: secondCascadingLevelDisabled
|
disabled: thirdCascadingLevelDisabled
|
||||||
});
|
});
|
||||||
|
|
||||||
// Creates a PropertyPaneTextField for the itemLimit property
|
// Creates a PropertyPaneTextField for the itemLimit property
|
||||||
|
@ -222,6 +236,7 @@ export default class ContentQueryWebPart extends BaseClientSideWebPart<IContentQ
|
||||||
{
|
{
|
||||||
groupName: strings.SourceGroupName,
|
groupName: strings.SourceGroupName,
|
||||||
groupFields: [
|
groupFields: [
|
||||||
|
this.siteUrlDropdown,
|
||||||
this.webUrlDropdown,
|
this.webUrlDropdown,
|
||||||
this.listTitleDropdown
|
this.listTitleDropdown
|
||||||
]
|
]
|
||||||
|
@ -288,11 +303,19 @@ export default class ContentQueryWebPart extends BaseClientSideWebPart<IContentQ
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* Loads the dropdown options for the webUrl property
|
||||||
|
***************************************************************************/
|
||||||
|
private loadSiteUrlOptions(): Promise<IDropdownOption[]> {
|
||||||
|
return this.ContentQueryService.getSiteUrlOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
* Loads the dropdown options for the webUrl property
|
* Loads the dropdown options for the webUrl property
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
private loadWebUrlOptions(): Promise<IDropdownOption[]> {
|
private loadWebUrlOptions(): Promise<IDropdownOption[]> {
|
||||||
return this.ContentQueryService.getWebUrlOptions();
|
return this.ContentQueryService.getWebUrlOptions(this.properties.siteUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -394,7 +417,14 @@ export default class ContentQueryWebPart extends BaseClientSideWebPart<IContentQ
|
||||||
* Resets dependent property panes if needed
|
* Resets dependent property panes if needed
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
private resetDependentPropertyPanes(propertyPath: string): void {
|
private resetDependentPropertyPanes(propertyPath: string): void {
|
||||||
if(propertyPath == ContentQueryConstants.propertyWebUrl) {
|
if(propertyPath == ContentQueryConstants.propertySiteUrl) {
|
||||||
|
this.resetWebUrlPropertyPane();
|
||||||
|
this.resetListTitlePropertyPane();
|
||||||
|
this.resetOrderByPropertyPane();
|
||||||
|
this.resetFiltersPropertyPane();
|
||||||
|
this.resetViewFieldsPropertyPane();
|
||||||
|
}
|
||||||
|
else if(propertyPath == ContentQueryConstants.propertyWebUrl) {
|
||||||
this.resetListTitlePropertyPane();
|
this.resetListTitlePropertyPane();
|
||||||
this.resetOrderByPropertyPane();
|
this.resetOrderByPropertyPane();
|
||||||
this.resetFiltersPropertyPane();
|
this.resetFiltersPropertyPane();
|
||||||
|
@ -453,6 +483,21 @@ export default class ContentQueryWebPart extends BaseClientSideWebPart<IContentQ
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* Resets the List Title property pane and re-renders it
|
||||||
|
***************************************************************************/
|
||||||
|
private resetWebUrlPropertyPane() {
|
||||||
|
Log.verbose(this.logSource, "Resetting 'webUrl' property...", this.context.serviceScope);
|
||||||
|
|
||||||
|
this.properties.webUrl = null;
|
||||||
|
this.ContentQueryService.clearCachedWebUrlOptions();
|
||||||
|
update(this.properties, ContentQueryConstants.propertyWebUrl, (): any => { return this.properties.webUrl; });
|
||||||
|
this.webUrlDropdown.properties.selectedKey = "";
|
||||||
|
this.webUrlDropdown.properties.disabled = isEmpty(this.properties.siteUrl);
|
||||||
|
this.webUrlDropdown.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
* Resets the List Title property pane and re-renders it
|
* Resets the List Title property pane and re-renders it
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { IQueryFilter } from "../../controls/PropertyPaneQueryFilterPanel/components/QueryFilter/IQueryFilter";
|
import { IQueryFilter } from "../../controls/PropertyPaneQueryFilterPanel/components/QueryFilter/IQueryFilter";
|
||||||
|
|
||||||
export interface IContentQueryWebPartProps {
|
export interface IContentQueryWebPartProps {
|
||||||
|
siteUrl: string;
|
||||||
webUrl: string;
|
webUrl: string;
|
||||||
listTitle: string;
|
listTitle: string;
|
||||||
limitEnabled: boolean;
|
limitEnabled: boolean;
|
||||||
|
|
|
@ -232,7 +232,8 @@ export default class ContentQuery extends React.Component<IContentQueryProps, IC
|
||||||
* Returns whether all mandatory fields are configured or not
|
* Returns whether all mandatory fields are configured or not
|
||||||
*************************************************************************************/
|
*************************************************************************************/
|
||||||
private areMandatoryFieldsConfigured(): boolean {
|
private areMandatoryFieldsConfigured(): boolean {
|
||||||
return !isEmpty(this.props.querySettings.webUrl) &&
|
return !isEmpty(this.props.siteUrl) &&
|
||||||
|
!isEmpty(this.props.querySettings.webUrl) &&
|
||||||
!isEmpty(this.props.querySettings.listTitle) &&
|
!isEmpty(this.props.querySettings.listTitle) &&
|
||||||
!isEmpty(this.props.querySettings.viewFields) &&
|
!isEmpty(this.props.querySettings.viewFields) &&
|
||||||
(!isEmpty(this.props.templateUrl) || !isEmpty(this.props.templateText));
|
(!isEmpty(this.props.templateUrl) || !isEmpty(this.props.templateText));
|
||||||
|
@ -291,6 +292,7 @@ export default class ContentQuery extends React.Component<IContentQueryProps, IC
|
||||||
<div className={styles.cqwpValidations}>
|
<div className={styles.cqwpValidations}>
|
||||||
{ this.props.strings.mandatoryProperties }
|
{ this.props.strings.mandatoryProperties }
|
||||||
|
|
||||||
|
<Checkbox label={strings.SiteUrlFieldLabel} checked={!isEmpty(this.props.siteUrl)} />
|
||||||
<Checkbox label={strings.WebUrlFieldLabel} checked={!isEmpty(this.props.querySettings.webUrl)} />
|
<Checkbox label={strings.WebUrlFieldLabel} checked={!isEmpty(this.props.querySettings.webUrl)} />
|
||||||
<Checkbox label={strings.ListTitleFieldLabel} checked={!isEmpty(this.props.querySettings.listTitle)} />
|
<Checkbox label={strings.ListTitleFieldLabel} checked={!isEmpty(this.props.querySettings.listTitle)} />
|
||||||
<Checkbox label={strings.viewFieldsChecklistStrings.label} checked={!isEmpty(this.props.querySettings.viewFields)} />
|
<Checkbox label={strings.viewFieldsChecklistStrings.label} checked={!isEmpty(this.props.querySettings.viewFields)} />
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { IQuerySettings } from './IQuerySettings';
|
||||||
export interface IContentQueryProps {
|
export interface IContentQueryProps {
|
||||||
onLoadTemplate: (templateUrl: string) => Promise<string>;
|
onLoadTemplate: (templateUrl: string) => Promise<string>;
|
||||||
onLoadTemplateContext: (querySettings: IQuerySettings, callTimeStamp: number) => Promise<IContentQueryTemplateContext>;
|
onLoadTemplateContext: (querySettings: IQuerySettings, callTimeStamp: number) => Promise<IContentQueryTemplateContext>;
|
||||||
|
siteUrl: string;
|
||||||
querySettings: IQuerySettings;
|
querySettings: IQuerySettings;
|
||||||
templateText?: string;
|
templateText?: string;
|
||||||
templateUrl?: string;
|
templateUrl?: string;
|
||||||
|
|
|
@ -8,9 +8,13 @@ define([], function() {
|
||||||
QueryGroupName: "Query",
|
QueryGroupName: "Query",
|
||||||
DisplayGroupName: "Display",
|
DisplayGroupName: "Display",
|
||||||
ExternalGroupName: "External",
|
ExternalGroupName: "External",
|
||||||
|
SiteUrlFieldLabel: "Site Url",
|
||||||
|
SiteUrlFieldPlaceholder: "Select the source site...",
|
||||||
|
SiteUrlFieldLoadingLabel: "Loading available site collections...",
|
||||||
|
SiteUrlFieldLoadingError: "An error occured while loading site collections : {0}",
|
||||||
WebUrlFieldLabel: "Web Url",
|
WebUrlFieldLabel: "Web Url",
|
||||||
WebUrlFieldPlaceholder: "Select the source web...",
|
WebUrlFieldPlaceholder: "Select the source web...",
|
||||||
WebUrlFieldLoadingLabel: "Loading webs from current site...",
|
WebUrlFieldLoadingLabel: "Loading webs from selected site...",
|
||||||
WebUrlFieldLoadingError: "An error occured while loading webs : {0}",
|
WebUrlFieldLoadingError: "An error occured while loading webs : {0}",
|
||||||
ListTitleFieldLabel: "List Title",
|
ListTitleFieldLabel: "List Title",
|
||||||
ListTitleFieldPlaceholder: "Select the source list...",
|
ListTitleFieldPlaceholder: "Select the source list...",
|
||||||
|
|
|
@ -7,6 +7,10 @@ declare interface IContentQueryStrings {
|
||||||
QueryGroupName: string;
|
QueryGroupName: string;
|
||||||
DisplayGroupName: string;
|
DisplayGroupName: string;
|
||||||
ExternalGroupName: string;
|
ExternalGroupName: string;
|
||||||
|
SiteUrlFieldLabel: string;
|
||||||
|
SiteUrlFieldPlaceholder: string;
|
||||||
|
SiteUrlFieldLoadingLabel: string;
|
||||||
|
SiteUrlFieldLoadingError: string;
|
||||||
WebUrlFieldLabel: string;
|
WebUrlFieldLabel: string;
|
||||||
WebUrlFieldPlaceholder: string;
|
WebUrlFieldPlaceholder: string;
|
||||||
WebUrlFieldLoadingLabel: string;
|
WebUrlFieldLoadingLabel: string;
|
||||||
|
|
Loading…
Reference in New Issue