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