Upgraded from 1.9 to 2.0.5 and I now get an error on create.

Upgraded from 1.9 to 2.0.5 and I now get an error on create.

washuit-iammwashuit-iamm Posts: 84Questions: 32Answers: 1

I am sure you will need more info, but I am only able to debug so much into the DataTables code without understanding what I am looking at.

After updating, my create results in "Cannot read property 'Id' of null". I am using the idSrc: "Id" option. When my create POSTs to the server, it returns the newly created object, and I have verified that it DOES have an Id set.

I noticed in the changelog, the create on server-side should now highlight a row? I assume this fix/feature resulted in the breaking change I am now seeing.

Please advise on any further troubleshooting or information I can provide. My codebase is large, and my table is all server side with custom .NET code (not the editor .NET library), so providing an example is difficult. I was hoping this is a known issue.

This question has an accepted answers - jump to answer

Answers

  • washuit-iammwashuit-iamm Posts: 84Questions: 32Answers: 1
    {
        table: tableId,
        template: formId,
        idSrc: 'Id',
        formOptions: {
            main: {
                onBackground: 'none',
                submitOnReturn: false
            }
        },
        ajax: {
        create: {
            url: editorRestURL,
            type: 'POST',
            contentType: 'application/json',
            error: dataTableAjaxErrorFn,
            complete: dataTableAjaxCompleteFn,
            data: function (d) {
                // The server expects the payload and nothing more
                // Create only creates a single record at a time.
                var itemToSave = d.data[0];
                // NOTE: If a property value is undefined, JSON.stringify will exclude the key/value pair.
                return JSON.stringify(itemToSave);
            }
        },
        // ... edit/delete
    }
    

    This is my custom ajax create function. I share this across a few tables. Also, I found out some of my other tables work. So I am still digging.

  • washuit-iammwashuit-iamm Posts: 84Questions: 32Answers: 1

    Still digging, but I have narrowed the issue further. The error only happens if I submit JSON to my server with a property that has a null value.

    The full stack trace:

    datatables.js?v=BGz3GUGvJ2lM3bEgP_MEd1StcZ_PRreDWCIgx9W44vg:1918 Uncaught TypeError: Cannot read property 'Id' of null
        at datatables.js?v=BGz3GUGvJ2lM3bEgP_MEd1StcZ_PRreDWCIgx9W44vg:1918
        at Editor.id (datatables.js?v=BGz3GUGvJ2lM3bEgP_MEd1StcZ_PRreDWCIgx9W44vg:16229)
        at datatables.js?v=BGz3GUGvJ2lM3bEgP_MEd1StcZ_PRreDWCIgx9W44vg:16402
        at Function.map (jquery-3.5.1.js:473)
        at Editor.prep (datatables.js?v=BGz3GUGvJ2lM3bEgP_MEd1StcZ_PRreDWCIgx9W44vg:16401)
        at Editor._dataSource (datatables.js?v=BGz3GUGvJ2lM3bEgP_MEd1StcZ_PRreDWCIgx9W44vg:19410)
        at Editor._submitSuccess (datatables.js?v=BGz3GUGvJ2lM3bEgP_MEd1StcZ_PRreDWCIgx9W44vg:20449)
        at datatables.js?v=BGz3GUGvJ2lM3bEgP_MEd1StcZ_PRreDWCIgx9W44vg:20320
        at Object.opts.complete (datatables.js?v=BGz3GUGvJ2lM3bEgP_MEd1StcZ_PRreDWCIgx9W44vg:19124)
        at fire (jquery-3.5.1.js:3496)
    
  • allanallan Posts: 61,322Questions: 1Answers: 10,023 Site admin

    I isn’t something we’ve had a report of already I’m afraid (at least, not that I recall). So no quick fix sadly.

    The error only happens if I submit JSON to my server with a property that has a null value.

    Can you show me the data that is being sent to the server, the response from the server and also the Editor Javascript configuration object?

    Thanks,
    Allan

  • washuit-iammwashuit-iamm Posts: 84Questions: 32Answers: 1

    Request to Server:

    POST

    {
      "Name": "Test1234",
      "Description": "Test1234",
      "Type": "",
      "Notes": "Test1234",
      "LicenseNotes": "Test1234",
      "DocumentationLink": "Test1234",
      "Vendor": "Test1234",
      "Distributor": "Test1234",
      "Version": "Test1234",
      "RequiresLicense": false,
      "OffPremiseHost": false,
      "OnPremise": false,
      "WashuITSupported": false,
      "VendorEndService": null,
      "TargetEndService": null,
      "NextLifecycleTransition": null,
      "ProductTypeId": 2,
      "LifecycleMaturityId": null,
      "ServiceOptionIds": [],
      "Timestamp": "",
      "UpdatedByDisplayName": "",
      "UpdatedOn": ""
    }
    

    Response from Server:

    {
      "Id": 63,
      "ProductTypeName": "Books",
      "CreatedByWUPeopleId": "Me",
      "CreatedOnUtc": "2021-09-10T13:23:37.2065024Z",
      "UpdatedByWUPeopleId": "Me",
      "UpdatedByDisplayName": "Me",
      "UpdatedOnUtc": "2021-09-10T13:23:37.2065065Z",
      "CreatedByDisplayName": "Me",
      "CorrelationId": "dae6bf27-7f76-4fa8-9e08-fda899b7e477",
      "LifecycleMaturityId": null,
      "ProductTypeId": 2,
      "Description": "Test1234",
      "Notes": "Test1234",
      "LicenseNotes": "Test1234",
      "DocumentationLink": "Test1234",
      "Name": "Test1234",
      "Vendor": "Test1234",
      "Distributor": "Test1234",
      "Version": "Test1234",
      "RequiresLicense": false,
      "OnPremise": false,
      "OffPremiseHost": false,
      "WashuITSupported": false,
      "VendorEndService": null,
      "TargetEndService": null,
      "NextLifecycleTransition": null,
      "ServiceOptionIds": [],
      "Timestamp": "AAAAAAACeNE="
    }
    

    Editor Configuration:

    var editorConfig = {
      table: tableId,
      template: formId,
      idSrc: "Id",
      formOptions: {
        main: {
          onBackground: "none",
          submitOnReturn: false,
        },
      },
      ajax: {
        create: {
          url: editorRestURL,
          type: "POST",
          contentType: "application/json",
          error: dataTableUtils.dataTableAjaxError,
          complete: DataTableAjaxComplete,
          data: function (d) {
            // The server expects the payload and nothing more (no action or data properties from DataTables Editor)
            var itemToSave = d.data[0];
            return JSON.stringify(itemToSave);
          },
        },
        edit: {
          url: editorRestURL,
          type: "PUT",
          contentType: "application/json",
          error: dataTableUtils.dataTableAjaxError,
          complete: DataTableAjaxComplete,
          data: function (d) {
            let result = [];
            for (var itemToSaveId in d.data) {
              var itemToSave = d.data[itemToSaveId];
              itemToSave.Id = parseInt(itemToSaveId);
              result.push(itemToSave);
            }
            return JSON.stringify(result);
          },
        },
        remove: function (method, url, data, success, error) {
          editorRestURL = editorRestURL.replace(/\/$/, "");
          var deleteUrl = new URL(editorRestURL);
          deleteUrl.searchParams.append("Id", Object.keys(data.data).join(","));
          $.ajax({
            url: deleteUrl.toString(),
            type: "DELETE",
            contentType: "application/json",
            success: function (xhr) {
              DataTableAjaxComplete(xhr);
              // Endpoint returns a 204 no content. DataTables internally does not like that.
              success({});
            },
            error: function (xhr) {
              dataTableUtils.dataTableAjaxError(xhr);
              error.apply(null, arguments);
            },
          });
        },
      },
    };
    
    // dataTableAjaxComplete
    function DataTableAjaxComplete(xhr) {
        // 422 unprocessable entity (Our custom FluentValidation Core Middleware Response)
        // Editor success will not fire on a 422. The responseJSON is already in a state where DataTables will be happy.
        if (xhr.status === 422) {
        xhr.status = 200;
        }
    
        // jQuery does not respect a response with a status 204 with a json content type and an empty body.
        if (
        (xhr.status === 200 || xhr.status === 204) &&
        xhr.responseText === ""
        ) {
        xhr.responseText = "{}";
        }
    }
    
  • washuit-iammwashuit-iamm Posts: 84Questions: 32Answers: 1

    I think I got it.

    I had a postSubmit function doing this:

        // The server response on an edit is `[{},{},{}]` editor wants `{ data: [ {}, {}, {} ] }`
        function PostSubmitMapper(e, json, data, action) {
          if (action === "create" || action === "edit") {
              json.data = json;
           }
        }
    

    My fix is:

        // The server response on an edit is `[{},{},{}]` editor wants `{ data: [ {}, {}, {} ] }`
        function PostSubmitMapper(e, json, data, action) {
          if (action === "create" || action === "edit") {
              clonedJson = JSON.parse(JSON.stringify(json));
              json = {
                data: clonedJson
              };
            }
        }
    

    Is there a better way to do that?

    Not sure why this worked in DT 1.9.6 and not in 2.0.5

  • allanallan Posts: 61,322Questions: 1Answers: 10,023 Site admin

    Interestingly, your new code doesn't change the json object that Editor sees at all. Rather it is assigning the local variable json a new object. That is not being returned, and so Editor won't make use of it at all.

    What you'd actually need to do in this case is put a modifier into the Ajax success callbacks that you have. What does DataTableAjaxComplete contain?

    Allan

  • washuit-iammwashuit-iamm Posts: 84Questions: 32Answers: 1
        function DataTableAjaxComplete(xhr) {
    
            // 422 unprocessable entity (Our custom FluentValidation Core Middleware Response)
            // Success will not fire on a 422. The responseJSON is already in a state where DataTables will be happy.
            if (xhr.status === 422) {
                xhr.status = 200;
            }
    
            // jQuery does not respect them 204 with a json content type and an empty body.
            if ((xhr.status === 200 || xhr.status === 204) && xhr.responseText === "") {
                xhr.responseText = "{}";
            }
        }
    

    If it works without me changing the json object, am I good to go? I guess it just wont be able to figure out the id of the newly created row, but it does not throw any errors in the console.

  • washuit-iammwashuit-iamm Posts: 84Questions: 32Answers: 1

    @allan did some code cleanup, I just removed the postSubmit mapper and moved the code you suggested over into the success callback of the create ajax option. Same error though:

    Error

    datatables.js?v=BGz3GUGvJ2lM3bEgP_MEd1StcZ_PRreDWCIgx9W44vg:1918 Uncaught TypeError: Cannot read property 'Id' of null
        at datatables.js?v=BGz3GUGvJ2lM3bEgP_MEd1StcZ_PRreDWCIgx9W44vg:1918
        at Editor.id (datatables.js?v=BGz3GUGvJ2lM3bEgP_MEd1StcZ_PRreDWCIgx9W44vg:16229)
        at datatables.js?v=BGz3GUGvJ2lM3bEgP_MEd1StcZ_PRreDWCIgx9W44vg:16402
        at Function.map (jquery-3.5.1.js:473)
        at Editor.prep (datatables.js?v=BGz3GUGvJ2lM3bEgP_MEd1StcZ_PRreDWCIgx9W44vg:16401)
        at Editor._dataSource (datatables.js?v=BGz3GUGvJ2lM3bEgP_MEd1StcZ_PRreDWCIgx9W44vg:19410)
        at Editor._submitSuccess (datatables.js?v=BGz3GUGvJ2lM3bEgP_MEd1StcZ_PRreDWCIgx9W44vg:20449)
        at datatables.js?v=BGz3GUGvJ2lM3bEgP_MEd1StcZ_PRreDWCIgx9W44vg:20320
        at Object.success (site.js?v=UsYYgKNwB33W7hG0nlpIw_Twe5oXKFq62wpHwhCqrbw:90)
        at fire (jquery-3.5.1.js:3496)
    

    Editor Options

    let editorOpts = {
        table: tableId,
        template: formId,
        idSrc: "Id",
        formOptions: {
            main: {
            onBackground: "none",
            submitOnReturn: false,
            },
        },
        ajax: {
            create: function (method, url, data, success, error) {
            $.ajax({
                url: editorRestURL,
                type: "POST",
                contentType: "application/json",
                // The server expects the new row as an object
                // The API only supports creating a single row at a time, unlike editing
                data: JSON.stringify(data.data[0]),
                success: function (json, status, xhr) {
                dataTableUtils.dataTableAjaxComplete(xhr);
                // Send back the new row in the shape DataTables expects
                success({
                    data: json,
                });
                },
                error: function (json, status, xhr) {
                dataTableUtils.dataTableAjaxError(xhr);
                error.apply(null, arguments);
                },
            });
            },
        },
    };
    

    dataTableAjaxComplete function

    function DataTableAjaxComplete(xhr) {
        // 422 un-processable entity (Our custom FluentValidation Core Middleware Response)
        // Editor success will not fire on a 422. The responseJSON is already in a state where DataTables will be happy.
        if (xhr.status === 422) {
        xhr.status = 200;
        }
    
        // jQuery does not respect a response with a status 204 with a json content type and an empty body.
        if (
        (xhr.status === 200 || xhr.status === 204) &&
        xhr.responseText === ""
        ) {
        xhr.responseText = "{}";
        }
    }
    

    Request

    {
        "Name": "sdfsd464tyfghdg",
        "Description": "sdfsd464tyfghdg",
        "Type": "",
        "Notes": "sdfsd464tyfghdg",
        "LicenseNotes": "sdfsd464tyfghdg",
        "DocumentationLink": "sdfsd464tyfghdg",
        "Vendor": "sdfsd464tyfghdg",
        "Distributor": "sdfsd464tyfghdg",
        "Version": "sdfsd464tyfghdg",
        "RequiresLicense": false,
        "OffPremiseHost": false,
        "OnPremise": false,
        "WashuITSupported": false,
        "VendorEndService": null,
        "TargetEndService": null,
        "NextLifecycleTransition": null,
        "ProductTypeId": 3,
        "LifecycleMaturityId": null,
        "ServiceOptionIds": [],
        "Timestamp": "",
        "UpdatedByDisplayName": "",
        "UpdatedOn": ""
    }
    

    Response

    {
        "Id": 75,
        "ProductTypeName": "Garden",
        "CreatedByWUPeopleId": "Me",
        "CreatedOnUtc": "2021-09-13T18:18:56.2538126Z",
        "UpdatedByWUPeopleId": "Me",
        "UpdatedByDisplayName": "Me",
        "UpdatedOnUtc": "2021-09-13T18:18:56.2538152Z",
        "CreatedByDisplayName": "Me",
        "CorrelationId": "e6b351f7-2e6b-4110-808e-91fb4397ca76",
        "LifecycleMaturityId": null,
        "ProductTypeId": 3,
        "Description": "sdfsd464tyfghdg",
        "Notes": "sdfsd464tyfghdg",
        "LicenseNotes": "sdfsd464tyfghdg",
        "DocumentationLink": "sdfsd464tyfghdg",
        "Name": "sdfsd464tyfghdg",
        "Vendor": "sdfsd464tyfghdg",
        "Distributor": "sdfsd464tyfghdg",
        "Version": "sdfsd464tyfghdg",
        "RequiresLicense": false,
        "OnPremise": false,
        "OffPremiseHost": false,
        "WashuITSupported": false,
        "VendorEndService": null,
        "TargetEndService": null,
        "NextLifecycleTransition": null,
        "ServiceOptionIds": [],
        "Timestamp": "AAAAAAADU5I="
    }
    
  • allanallan Posts: 61,322Questions: 1Answers: 10,023 Site admin

    Unfortunately, that all looks quite sensible and like it should work. Could you give me a link to your page so I can debug it directly please?

    One possible thing you might need to add to the $.ajax call is dataType: 'json', but jQuery should really detect JSON automatically.

    Allan

  • washuit-iammwashuit-iamm Posts: 84Questions: 32Answers: 1

    @allan I figured it out

    Works in 2.0.5

    success({
        data: [{ Id: 42 }] // Note the array!
    })
    

    No longer works in 2.0.5

    success({
        data: { Id: 42 } // Must be an array!
    })
    

    it would take me a while to get you a copy of the site since its behind our VPN, so I just downloaded the nodejs sample and started hacking away.

    If you want to reproduce this quickly, download the nodejs sample, edit any page, say fieldDefaults.html, remove ajax: "/api/staff", and instead change it to:

    ajax: {
        create: function (method, url, data, success, error) {
            success({
                "cancelled": [],
                "data": {
                    "DT_RowId": "row_58",
                    "first_name": "A",
                    "last_name": "A",
                    "position": null,
                    "office": "Edinburgh",
                    "extn": null,
                    "age": null,
                    "salary": null,
                    "start_date": null
                    },
                "fieldErrors": []
            });
        }
    },
    

    You will see the error. I used sqlite to quickly populate the DB and test.

    Also, regarding "it only works when I do not send any null value properties" this is true:

    Works

    success({
        "cancelled": [],
        "data": { // <----------------- Note this is not an array!!!
                "DT_RowId": "row_58",
                "first_name": "A",
                "last_name": "A",
                "position": "Software developer",
                "office": "Edinburgh",
                "extn": "0000",
                "age": 42,
                "salary": 85000,
                "start_date": "2021-09-14"
            },
        "fieldErrors": []
    });
    

    Fails

    success({
        "cancelled": [],
        "data": { // <----------------- Note this is not an array!!!
                "DT_RowId": "row_58",
                "first_name": "A",
                "last_name": "A",
                "position": "Software developer",
                "office": "Edinburgh",
                "extn": "0000",
                "age": null, // <----------------- Note that I am null!!
                "salary": 85000,
                "start_date": "2021-09-14"
            },
        "fieldErrors": []
    });
    
  • allanallan Posts: 61,322Questions: 1Answers: 10,023 Site admin
    Answer ✓

    Ah! Very interesting - thank you for spotting that and letting me know about it. That's an artefact of how Editor worked in v1.0-1.4 where only a single row at a time could be edited. With 1.5 we introduced multi-row editing which meant an array must be returned.

    With v2 I removed the support for the old style single row, and it has impacted this as well.

    I can add a check and correction into Editor if that is going to be a problem for you to change in your scripts?

    Allan

  • washuit-iammwashuit-iamm Posts: 84Questions: 32Answers: 1

    @allan No need to add that. I have one global editor configuration object that I can override the ajax data property for all CRUD operations on my end.

Sign In or Register to comment.