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:
Simon-Pierre Plante 2017-09-01 23:46:44 -04:00
parent 4dea634f15
commit dc136bfeda
20 changed files with 279 additions and 91 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 KiB

View File

@ -28,6 +28,7 @@ Version|Date|Comments
1.0.1|July 23rd 15, 2017|Updated to GA Version
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.5|September 1st, 2017|Added a **Site Url** parameter next to the **Web Url** parameter in order to narrow down the results
## 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.**
@ -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.
<img src="Misc/allsites.gif" />
<img src="Misc/allsites_v2.gif" />
<br>
### Unlimited filters

View File

@ -2,7 +2,7 @@
"solution": {
"name": "React Content Query",
"id": "00406271-0276-406f-9666-512623eb6709",
"version": "1.0.4.0"
"version": "1.0.5.0"
},
"paths": {
"zippedPackage": "solution/react-content-query-webpart.sppkg"

View File

@ -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"
}

View File

@ -1,6 +1,6 @@
{
"name": "react-content-query",
"version": "1.0.2",
"version": "1.0.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "react-content-query",
"version": "1.0.4",
"version": "1.0.5",
"private": true,
"engines": {
"node": ">=0.10.0"

View File

@ -4,6 +4,7 @@ export class ContentQueryConstants {
/**************************************************************
* WebPart Properties
**************************************************************/
public static readonly propertySiteUrl = "siteUrl";
public static readonly propertyWebUrl = "webUrl";
public static readonly propertyListTitle = "listTitle";
public static readonly propertyOrderBy = "orderBy";

View File

@ -40,6 +40,7 @@ export class ContentQueryService implements IContentQueryService {
/***************************************************************************
* Stores the first async calls locally to avoid useless redundant calls
***************************************************************************/
private siteUrlOptions: IDropdownOption[];
private webUrlOptions: IDropdownOption[];
private listTitleOptions: IDropdownOption[];
private orderByOptions: IDropdownOption[];
@ -143,13 +144,53 @@ export class ContentQueryService implements IContentQueryService {
});
}
/**************************************************************************************************
* 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);
// Resolves an empty array if site is null
if (isEmpty(siteUrl)) {
return Promise.resolve(new Array<IDropdownOption>());
}
// Resolves the already loaded data if available
if(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
return new Promise<IDropdownOption[]>((resolve,reject) => {
let serverUrl = Text.format("{0}//{1}", window.location.protocol, window.location.hostname);
this.searchService.getWebUrlsForDomain(serverUrl)
.then((urls:string[]) => {
this.searchService.getWebsFromSite(siteUrl)
.then((urls) => {
let options:IDropdownOption[] = [ { key: "", text: strings.WebUrlFieldPlaceholder } ];
let urlOptions:IDropdownOption[] = urls.sort().map((url) => {
let serverRelativeUrl = !isEmpty(url.replace(serverUrl, '')) ? url.replace(serverUrl, '') : '/';
return { key: url, text: serverRelativeUrl };
let siteRelativeUrl = !isEmpty(url.replace(siteUrl, '')) ? url.replace(siteUrl, '') : '/';
return { key: url, text: siteRelativeUrl };
});
options = options.concat(urlOptions);
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
***************************************************************************/

View File

@ -8,7 +8,8 @@ import { IQuerySettings } from '../../webparts/contentQuer
export interface IContentQueryService {
getTemplateContext: (querySettings: IQuerySettings, callTimeStamp: number) => Promise<IContentQueryTemplateContext>;
getFileContent: (fileUrl: string) => Promise<string>;
getWebUrlOptions: () => Promise<IDropdownOption[]>;
getSiteUrlOptions: () => Promise<IDropdownOption[]>;
getWebUrlOptions: (siteUrl: string) => Promise<IDropdownOption[]>;
getListTitleOptions: (webUrl: string) => Promise<IDropdownOption[]>;
getOrderByOptions: (webUrl: string, listTitle: string) => Promise<IDropdownOption[]>;
getFilterFields: (webUrl: string, listTitle: string) => Promise<IQueryFilterField[]>;
@ -18,6 +19,7 @@ export interface IContentQueryService {
ensureFileResolves: (filePath: string) => Promise<{}>;
isValidTemplateFile: (filePath: string) => boolean;
generateDefaultTemplate: (viewFields: string[]) => string;
clearCachedWebUrlOptions: () => void;
clearCachedListTitleOptions: () => void;
clearCachedOrderByOptions: () => void;
clearCachedFilterFields: () => void;

View File

@ -79,7 +79,8 @@ export class ListService {
* 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 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> {
return new Promise<any>((resolve,reject) => {

View File

@ -19,43 +19,156 @@ export class SearchService {
/**************************************************************************************************
* Returns the web urls starting with the specified domain to which the current user has access
* @param domainUrl : The url of the web which contains the specified list
* Recursively executes the specified search query until all results are fetched
* @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[]> {
return new Promise<string[]>((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);
public getSearchResultsRecursive(webUrl: string, queryParameters: string): Promise<any> {
return new Promise<any>((resolve,reject) => {
// 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) => {
if(response.ok) {
response.json().then((data:any) => {
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); });
resolve(response.json());
}
else {
reject(response.statusText);
}
})
.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;
}
}

View File

@ -1,6 +1,5 @@
import { Text } from '@microsoft/sp-core-library';
import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';
import { SPComponentLoader } from '@microsoft/sp-loader';
import { isEmpty } from '@microsoft/sp-lodash-subset';
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'
});
});
}
}
}

View File

@ -4,7 +4,7 @@
"id": "46edf08f-95c7-4ca7-9146-6471f9f471be",
"alias": "ContentQueryWebPart",
"componentType": "WebPart",
"version": "1.0.4",
"version": "1.0.5",
"manifestVersion": 2,
"preconfiguredEntries": [{

View File

@ -38,6 +38,7 @@ export default class ContentQueryWebPart extends BaseClientSideWebPart<IContentQ
/***************************************************************************
* Custom ToolPart Property Panes
***************************************************************************/
private siteUrlDropdown: PropertyPaneAsyncDropdown;
private webUrlDropdown: PropertyPaneAsyncDropdown;
private listTitleDropdown: PropertyPaneAsyncDropdown;
private orderByDropdown: PropertyPaneAsyncDropdown;
@ -55,7 +56,7 @@ export default class ContentQueryWebPart extends BaseClientSideWebPart<IContentQ
* Returns the WebPart's 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),
onLoadTemplateContext: this.loadTemplateContext.bind(this),
siteUrl: this.properties.siteUrl,
querySettings: querySettings,
templateText: this.properties.templateText,
templateUrl: this.properties.templateUrl,
@ -107,8 +109,19 @@ export default class ContentQueryWebPart extends BaseClientSideWebPart<IContentQ
***************************************************************************/
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
let firstCascadingLevelDisabled = !this.properties.webUrl;
let secondCascadingLevelDisabled = !this.properties.webUrl || !this.properties.listTitle;
let firstCascadingLevelDisabled = !this.properties.siteUrl;
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
this.webUrlDropdown = new PropertyPaneAsyncDropdown(ContentQueryConstants.propertyWebUrl, {
@ -117,7 +130,8 @@ export default class ContentQueryWebPart extends BaseClientSideWebPart<IContentQ
errorLabelFormat: strings.WebUrlFieldLoadingError,
loadOptions: this.loadWebUrlOptions.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
@ -128,7 +142,7 @@ export default class ContentQueryWebPart extends BaseClientSideWebPart<IContentQ
loadOptions: this.loadListTitleOptions.bind(this),
onPropertyChange: this.onCustomPropertyPaneChange.bind(this),
selectedKey: this.properties.listTitle || "",
disabled: firstCascadingLevelDisabled
disabled: secondCascadingLevelDisabled
});
// Creates a custom PropertyPaneAsyncDropdown for the orderBy property
@ -139,7 +153,7 @@ export default class ContentQueryWebPart extends BaseClientSideWebPart<IContentQ
loadOptions: this.loadOrderByOptions.bind(this),
onPropertyChange: this.onCustomPropertyPaneChange.bind(this),
selectedKey: this.properties.orderBy || "",
disabled: secondCascadingLevelDisabled
disabled: thirdCascadingLevelDisabled
});
// Creates a custom PropertyPaneQueryFilterPanel for the filters property
@ -150,7 +164,7 @@ export default class ContentQueryWebPart extends BaseClientSideWebPart<IContentQ
onLoadPeoplePickerSuggestions: this.loadPeoplePickerSuggestions.bind(this),
onPropertyChange: this.onCustomPropertyPaneChange.bind(this),
trimEmptyFiltersOnChange: true,
disabled: secondCascadingLevelDisabled,
disabled: thirdCascadingLevelDisabled,
strings: strings.queryFilterPanelStrings
});
@ -159,7 +173,7 @@ export default class ContentQueryWebPart extends BaseClientSideWebPart<IContentQ
loadItems: this.loadViewFieldsChecklistItems.bind(this),
checkedItems: this.properties.viewFields,
onPropertyChange: this.onCustomPropertyPaneChange.bind(this),
disable: secondCascadingLevelDisabled,
disable: thirdCascadingLevelDisabled,
strings: strings.viewFieldsChecklistStrings
});
@ -193,7 +207,7 @@ export default class ContentQueryWebPart extends BaseClientSideWebPart<IContentQ
offText: 'Disabled',
onText: 'Enabled',
checked: this.properties.limitEnabled,
disabled: secondCascadingLevelDisabled
disabled: thirdCascadingLevelDisabled
});
// Creates a PropertyPaneTextField for the itemLimit property
@ -222,6 +236,7 @@ export default class ContentQueryWebPart extends BaseClientSideWebPart<IContentQ
{
groupName: strings.SourceGroupName,
groupFields: [
this.siteUrlDropdown,
this.webUrlDropdown,
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
***************************************************************************/
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
***************************************************************************/
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.resetOrderByPropertyPane();
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
***************************************************************************/

View File

@ -1,6 +1,7 @@
import { IQueryFilter } from "../../controls/PropertyPaneQueryFilterPanel/components/QueryFilter/IQueryFilter";
export interface IContentQueryWebPartProps {
siteUrl: string;
webUrl: string;
listTitle: string;
limitEnabled: boolean;

View File

@ -232,7 +232,8 @@ export default class ContentQuery extends React.Component<IContentQueryProps, IC
* Returns whether all mandatory fields are configured or not
*************************************************************************************/
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.viewFields) &&
(!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}>
{ 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.ListTitleFieldLabel} checked={!isEmpty(this.props.querySettings.listTitle)} />
<Checkbox label={strings.viewFieldsChecklistStrings.label} checked={!isEmpty(this.props.querySettings.viewFields)} />

View File

@ -6,6 +6,7 @@ import { IQuerySettings } from './IQuerySettings';
export interface IContentQueryProps {
onLoadTemplate: (templateUrl: string) => Promise<string>;
onLoadTemplateContext: (querySettings: IQuerySettings, callTimeStamp: number) => Promise<IContentQueryTemplateContext>;
siteUrl: string;
querySettings: IQuerySettings;
templateText?: string;
templateUrl?: string;

View File

@ -8,9 +8,13 @@ define([], function() {
QueryGroupName: "Query",
DisplayGroupName: "Display",
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",
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}",
ListTitleFieldLabel: "List Title",
ListTitleFieldPlaceholder: "Select the source list...",

View File

@ -7,6 +7,10 @@ declare interface IContentQueryStrings {
QueryGroupName: string;
DisplayGroupName: string;
ExternalGroupName: string;
SiteUrlFieldLabel: string;
SiteUrlFieldPlaceholder: string;
SiteUrlFieldLoadingLabel: string;
SiteUrlFieldLoadingError: string;
WebUrlFieldLabel: string;
WebUrlFieldPlaceholder: string;
WebUrlFieldLoadingLabel: string;