Using WebApi example - spreadsheet
IMPORTANT
Help UKRAINE
! Your action matters!
Donate to support Ukrainian Army!
Donate to charity funds!
Organize/join street protests in your city to support Ukraine and condemn Russian aggression!
Expose and report Russian disinformation! #StandWithUkraine
Help UKRAINE

Some time ago, in my previous post about Web API I mentioned the example of its usage - creating Excel-like experience on the Portal. The time has come to show that example. In my code, I am using a third-party library called jExcel. Below you can find a video of how it looks like and sample code. The code may not reveal the best practices for jExcel library and provided only as a reference.
IMPORTANT
As the date of the article Web API is still in Preview and is not suitable for the production environment.
As the date of the article Web API is still in Preview and is not suitable for the production environment.
<script src="https://bossanova.uk/jexcel/v4/jexcel.js"></script>
<link rel="stylesheet" href="https://bossanova.uk/jexcel/v4/jexcel.css" type="text/css" />
<script src="https://bossanova.uk/jsuites/v2/jsuites.js"></script>
<link rel="stylesheet" href="https://bossanova.uk/jsuites/v2/jsuites.css" type="text/css" />
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Material+Icons" />
<div class="container">
<h1>Sample spreadsheet</h1>
<div id="spreadsheet"></div>
</div>
<!-- Fetching list of contacts -->
{% fetchxml contactList %}
<fetch version="1.0" mapping="logical">
<entity name="contact">
<attribute name="firstname"></attribute>
<attribute name="lastname"></attribute>
<attribute name="emailaddress1"></attribute>
<attribute name="telephone1"></attribute>
<attribute name="contactid"></attribute>
<filter type="and">
<condition attribute="fullname" operator="like" value="%(sample)%" />
</filter>
</entity>
</fetch>
{% endfetchxml %}
<script>
//Web API ajax wrapper
(function (webapi, $) {
function safeAjax(ajaxOptions) {
var deferredAjax = $.Deferred();
shell.getTokenDeferred().done(function (token) {
// Add headers for ajax
if (!ajaxOptions.headers) {
$.extend(ajaxOptions, {
headers: {
"__RequestVerificationToken": token
}
});
} else {
ajaxOptions.headers["__RequestVerificationToken"] = token;
}
$.ajax(ajaxOptions)
.done(function (data, textStatus, jqXHR) {
validateLoginSession(data, textStatus, jqXHR, deferredAjax.resolve);
}).fail(deferredAjax.reject); //ajax
}).fail(function () {
deferredAjax.rejectWith(this, arguments); // On token failure pass the token ajax and args
});
return deferredAjax.promise();
}
webapi.safeAjax = safeAjax;
})(window.webapi = window.webapi || {}, jQuery)
function saveContact(contactFieldsArr) {
let contactObj = {
"firstname": contactFieldsArr[0],
"lastname": contactFieldsArr[1],
"emailaddress1": contactFieldsArr[2],
"telephone1": contactFieldsArr[3],
}
let requestURL = `/_api/contacts(${contactFieldsArr[4]})`;
webapi.safeAjax({
type: "PATCH",
url: requestURL,
contentType: "application/json",
data: JSON.stringify(contactObj),
success: function (res) {
console.log(res);
}
});
}
function saveContacts() {
for (let index = 0; index < changedRows.length; index++) {
const rowIndex = changedRows[index];
let rowData = table.getRowData(rowIndex);
saveContact(rowData);
}
}
// list of row indexes that were changed
var changedRows = [];
// function that will be called when data is changed in table
var onDataChange = function(instance, cell, colNum, rowNum, value) {
let findIndex = changedRows.findIndex(elem => elem == rowNum);
if(findIndex == -1) {
changedRows.push(rowNum);
}
}
var data = [
{% for entity in contactList.results.entities %}
[
"{{ entity.firstname }}",
"{{ entity.lastname }}",
"{{ entity.emailaddress1 }}",
"{{ entity.telephone1 }}",
"{{entity.contactid}}",
] {% unless forloop.last %}, {% endunless %}
{% endfor %}
];
var table = jexcel(document.getElementById('spreadsheet'), {
data: data,
columns: [{
type: 'text',
title: 'First Name',
width: 220
},
{
type: 'text',
title: 'Last Name',
width: 220
},
{
type: 'text',
title: 'Email',
width: 220
},
{
type: 'text',
title: 'Phone',
width: 150
},
{
type: 'text',
title: 'Id',
width: 120,
readOnly: true
}
],
toolbar: [{
type: 'i',
content: 'undo',
onclick: function () {
table.undo();
}
},
{
type: 'i',
content: 'redo',
onclick: function () {
table.redo();
}
},
{
type: 'i',
content: 'save',
onclick: function () {
saveContacts();
}
}
],
onchange: onDataChange
});
</script>