Introduction:
Sometimes we need to display user the activities or changes made on any item of a SharePoint list. For example, there can be a list to store settings for any application and we need to display Admin users about the changes done on settings list items. In that case we can easily fetch the data from version history and display in our application.
Ensure Version Settings is enabled:
To get version history of any SharePoint list item, version settings for that list should be enabled.
Version settings is available in list settings
Version history can be viewed from SharePoint list as follows-
If we need to display this version history of a SharePoint list item in SPFx application, there are multiple ways we can do that. Depending on the requirement we can choose any one of these.
PnP JS library function:
The following PnP JS function can be called to get versions history of a SharePoint list item, though it is not mentioned in the PnP JS library documentation.
let versionHistory = await sp.web.lists.getByTitle(listName)
.items.getById(listItemId).select('Versions').expand('Versions')
.get();
But from the response of this function, we do not get the data like which fields are modified and by whom. It only returns version number and id.
By calling API to get versions:
We can call the following API and get version history data of a SharePoint list item: –
//get version history by list name and item id
public getVersionForListItem(listName: string, listItemId: number, fields?: string[]): Promise<any[]> {
let versionHistory: any[] = [];
try {
return sp.web.lists.getByTitle(listName).get().then(listDetails => {
console.log("listDetails: ", listDetails);
let listId = listDetails["Id"];
let url = `${this._webAbsoluteUrl}/_api/Web/Lists(guid'${listId}')/Items(${listItemId})/versions`;
let headers = {
'accept': 'application/json; odata=verbose'
};
return fetch(url, {
headers: headers,
}).then((res) => {
return res.json();
}).then((json) => {
console.log("getVersionForItemUrl", json);
let results = json.d && json.d.results ? json.d.results : [];
versionHistory = results.length > 0 ? results.map(item => {
let verObj = {
VersionLabel: item.VersionLabel,
IsCurrentVersion: item.IsCurrentVersion,
VersionId: item.VersionId,
CreatedDate: moment(item.Created).format("MM/DD/YYYY hh:mm:ss A"),
CreatedBy: item.Author && item.Author.Email && item.Author.LookupValue ? item.Author.LookupValue + "(" + item.Author.Email + ")" : "",
ModifiedDate: moment(item.Modified).format("MM/DD/YYYY hh:mm:ss A"),
ModifiedBy: item.Editor && item.Editor.Email && item.Editor.LookupValue ? item.Editor.LookupValue + "(" + item.Editor.Email + ")" : "",
};
if (fields && fields.length > 0) {
fields.forEach(elm => {
verObj[elm] = item[elm];
});
}
return verObj;
}) : [];
console.log("versionHistory", versionHistory);
return versionHistory;
}).catch(err => {
console.log('fetch error - ', err);
return err;
});
});
} catch (err) {
console.log('error - ', err);
return err;
}
}
The API mentioned above will return value of all the fields of that list along with created date, created by, modified date, modified by, version label, version id and a Boolean flag to indication that the version is current version or not etc. But from the response of this API, we don’t get exactly which fields are modified.
Using ‘iframe’:
We can add an iframe in SPFx application and add following URL as ‘src’-
<site_absolute_url>/_layouts/15/Versions.aspx?FileName=<site_relative_url>/Lists/<listTitle>/<listItemId>_.000&IsDlg=1&list={<listId>}
e.g.: –
https://mytenant.sharepoint.com/sites/demosite/layouts/15/Versions.aspx?FileName=/sites/demosite/Lists/productList/5.000&IsDlg=1&list={C10CBFBA-86DC-4F7C-BC92-36B45FC6BD69}
let iframeSRC =
`${siteAbsoluteURL}/_layouts/15/Versions.aspx?FileName=${siteRelativeURL}/Lists/${listName}/${listItemId}_.000&IsDlg=1&list={${listId}}`;
<iframe src={iframeSRC} height="400" width="500"/>
In this way we can display the exact content of the version history page of SharePoint as displayed from list item. But this will include all the links available in the page, which redirects users from our application to SharePoint pages or some links with action like delete all versions, which we may not prefer to display.
Fetching html of version history page:
We can fetch html of versions.aspx page and display in a div after removing all links. Following is the function to fetch the html code.
public getVersionHtml(listName: string, listItemId: number): Promise<string> {
try {
return sp.web.lists.getByTitle(listName).get().then(listDetails => {
console.log("listDetails: ", listDetails);
let listId = listDetails["Id"];
let url = `${this._webAbsoluteUrl}/_layouts/15/Versions.aspx?FileName=${this._webserverRelativeUrl}/Lists/${listName}/${listItemId}_.000&IsDlg=1&list={${listId}}`;
let headers = {
'accept': 'text/html; charset=UTF-8'
};
return fetch(url, {
headers: headers,
}).then((res) => {
return res.text();
}).then((htmlText) => {
console.log("getVersionHtml", htmlText);
return htmlText;
}).catch(err => {
console.log('fetch error - ', err);
return err;
});
});
} catch (err) {
console.log('error - ', err);
return err;
}
}
Now to get the html of version history page call the above function with list name and item id as follows –
let versionHtml = await this.getVersionHtml(
this.state.listName,
this.state.listItemId
);
Once we get the html, we can call the following function to get the required part and remove links and images as follows –
public getRequiredHTML = (versionHtml) => {
try {
const divEl = document.createElement('div');
divEl.innerHTML = versionHtml;
let tableElements = divEl.getElementsByClassName("ms-settingsframe");
let tableHtml = tableElements && tableElements.length > 0?
tableElements[0].outerHTML : "";
console.log("tableHtml",tableHtml);
let re = new RegExp("<a([^>]* )href=\"([^\"]+)\"", "g");
let result = tableHtml.replace(re, "<a$1href=\"#\"");
let reg = new RegExp("/<([a-z][a-z0-9]*)[^>]*?(\/?)>/si","g");
result = result.replace(reg,'<$1$2>');
result = result.split("<a ").join("<span ");
result = result.split("</a>").join("</span>");
result = result.split(` href="#"`).join("");
//hide images from ms-imnSpan
result = result + `<style>.ms-imnSpan img {display:none}</style>`;
console.log("frame content : " + result);
return result;
} catch(e){
console.log("getRequiredHTML",e);
return "";
}
}
Call the above function to get required html. With the required html you can append the style as required or you can add global CSS to modify the appearance of the content.
let versionHtml = this.getRequiredHTML(versionHtml);
Now set the html in a div as follows-
<div dangerouslySetInnerHTML={this.createMarkup(versionHtml)}
style={{"height" : "400px", "width" : "500px"}}/>
public createMarkup(htmlText) {
return {__html: htmlText};
}
Conclusion:
These are the ways we can display the activities made on any SP list item in SPFx application. We can choose the one as per projects requirement. If the complete detail of an item in each version needs to be displayed, the API approach can be chosen. If the links doesn’t matter, then the iframe approach is time saving. If the UI should match the application and only the modifieds fields of the item need to be displayed, the last approach suits best.