Question

I am trying to update a list item using Rest API. Following is my code. It works and it updates all fields except "RichMessage" which is a Note field with RichText="TRUE" and readonly. I have tested with PATCH also.

 var properties = {
     '__metadata': { "type": "SP.Data.MessageListListItem" },
     "Title": itemTitle,
     "MessageUpdatedById": userId,
     "Last_x0020_Updated": myDate,
     "RichMessage": newMessage
   };
var listItemUri = webUrl + "/_api/web/lists/GetByTitle('MessageList')/items(1)";
   $.ajax({
       url: listItemUri,
       type: "POST",
       contentType: "application/json;odata=verbose",
       data: JSON.stringify(properties),
       headers: {
             "Accept": "application/json;odata=verbose",
             "X-RequestDigest": $("#__REQUESTDIGEST").val(),
             "X-HTTP-Method": "MERGE",
             "If-Match": "*"
         },
         success: function (data) {
              alert("updated");
         },
         error: function (data) {
            console.log(data.responseText);
            alert("Failed");
        }
   });
Was it helpful?

Solution

Explanation

Based on your affirmative response to my comment, I've gone ahead and wrote this using the JSOM API. The script works by disabling the ReadOnly property on the RichMessage field, updating the values on your target item, and re-enabling the ReadOnly field. Up until now, this is the only working strategy I've found to change read-only fields from the SharePoint client APIs.

The REST API is based on the Client Object Model, so it can also use the this functionality with calls to the the SP.Field endpoint. For this use case, however, it's a bad idea to use it for the implementation.

Potential Issues

Because your code is making changes to the list's Field collection, disabling the ReadOnly property on the RichMessage field will disable it for all items in MessageList until it's re-enabled at the end of the process. This can easily result in issues if the script is started by multiple users at the same time, since the slightly faster first user's script could re-enable the ReadOnly property before the second user's script gets a chance to update their item's values.

The REST API would require 3 consecutive requests to implement this functionality, making it a lot easier for this sort of overlap issue to occur.

The JSOM approach doesn't send its changes to the server until the SP.ClientContext.executeQueryAsync call, and at that point, sends all three changes in a single request. (to be executed sequentially)

Source Code

/** updateReadOnlyItem - JSOM implementation */
(function(listTitle, readOnlyField, _itemId, _fieldValues) {

    // First grab our client context.
    var oCtx = SP.ClientContext.get_current(),

    // Cache stuff to prevent needlessly recomputing stuff.
    oList = oCtx.get_web().get_lists().getByTitle(listTitle),
    oField = oList.get_fields().getByInternalNameOrTitle(readOnlyField);

    // Entrypoint
    updateReadOnlyItem(_itemId, _fieldValues, function() {
        alert('Item successfully updated!');
    });

    /// #region Actual Logic

    function updateReadOnlyItem(id, fieldValues, onSuccess) {
        // If no values are given, ignore and return.
        if(!fieldValues) { return; }

        // Verify whether we need to make changes to the field.
        var needsFieldUpdate = hasMember(fieldValues, readOnlyField);

        // Update field read only
        if(needsFieldUpdate) { allowFieldUpdate(true); }

        // Since we didn't opt to execute the query yet, there was no need
        // to wait for updateItem to finish.
        updateItemFieldValues(id, fieldValues);

        // All done. Revert previous update, and then call update in order to 
        // register another change.
        if(needsFieldUpdate) { allowFieldUpdate(false); }

        // Finally, execute our query so that all three steps are done in a 
        // single request to the server. (disable field read-only, update list
        // item values, re-nable field's read-only property)
        return executeQuery(this, onSuccess, 'ExecuteQuery');
    }

    function allowFieldUpdate(canUpdate, onSuccess) {
        // Used to set the readOnly property on the target field. Since
        // we only need to use this on `oField`, we cached the field instance
        // above.
        var readOnly = !canUpdate,
        onError =  'setting Field.ReadOnly = ' + (readOnly?'TRUE':'FALSE');
        oField.set_readOnlyField(readOnly);

        // Register the changes.
        oField.update();
        oCtx.load(oField);
        // 
        // Instead of the two lines above, I originally used the code below. We
        // don't need to load the item for the update, but if you find yourself
        // needing to debug, and would like each action to perform in a separate
        // request, comment the two lines above and uncomment the line below.
        //
        // loadUpdates(oField, onSuccess, 'update field read-only', this);
    }

    function updateItemFieldValues(id, fieldValues, onSuccess) {
        // Does the actual work of updating the list item's values. fieldValue
        // refers to a object mapping field names to field values.
        var fieldNames = Object.keys(fieldValues),
        oItem = oList.getItemById(id);

        for(var i = 0; i < fieldNames.length; i++) {
            var name = fieldNames[i];
            oItem.set_item(name, fieldValues[name]);
        }

        // Same as line 100.
        oItem.update();
        oCtx.load(oItem);
        //
        // loadUpdates(oItem, onSuccess, 'update oItem fieldValues', this);
    }

    /// #endregion


    /// #region Utility Functions

    function isDefined(subject) {
        // Self-explanatory
        return typeof(subject[member]) !== 'undefined';
    }

    function hasMember(subject, member) {
        // subject has a specific member (value of member can be null, just
        // so long as it exists)
        return subject &&
               Object.prototype.hasOwnProperty.call(subject, member) || 
               isDefined(subject[member])
    }

    function showError(description, args) {
        // Show error message prefixed with the "description" parameter.
        alert('Error occurred ' + description + ': '
                                + args.get_message() + '\n' 
                                + args.get_stackTrace()); // lazy
    }

    function executeQuery(self, onSuccess, onError) {
        // If onError is a string value, generate a call to showError, using 
        // the string for its description field.

        if(typeof(onError) === 'string' || (onError instanceof String)) {
            var description = onError;
            onError = function(sender, args) {
                showError.call(this, description, args);
            };
        }

        return oCtx.executeQueryAsync(
            Function.createDelegate(self, onSuccess),
            Function.createDelegate(self, onError)
        );
    }

    function loadUpdates(spObject, onSuccess, onError, self) {
        // Call update on spObject, load it, and if a success handler is given,
        // execute a query with onSuccess as the success handler.
        spObject.update();
        oCtx.load(spObject);
        if(onSuccess) {
            executeQuery(self || this, onSuccess, onError);
        }
    }

    /// #endregion

})(
    /* listTitle=     */ 'MessageList',
    /* readOnlyField= */ 'RichMessage',
    /* itemId=        */ 1,
    /* fieldValues=   */
    {
        "Title": itemTitle,
        "MessageUpdatedById": userId,
        "Last_x0020_Updated": myDate,
        "RichMessage": newMessage
    }
);
Licensed under: CC-BY-SA with attribution
Not affiliated with sharepoint.stackexchange
scroll top