What is the correct way to send an Apex type containing DateTime members to RemoteActions?

Update: this is only an issue with a concrete type in a custom class. I implemented the same pattern with the Contact sObject containing a DateTime field, no exception thrown. Code below and the question title updated to demonstrate this difference in behavior.


I am using @RemoteAction methods to expose a custom Apex Type to the page. When performing the retrieve operation, the DateTime members on the class are serialized into the unix epoch format. Like so:

{
    name: "Item Name",
    theDate: 1440797362525
}

However, when performing a commit back to the controller with this exact same data, the unix epoch format is not able to be deserialized back into the concrete type and it throws an exception.

Visualforce Remoting Exception: Unable to convert date ‘1440797362525’ to Apex type Datetime.

This is “JS Remoting: 101” and it frustrates me that it does not work properly.

Questions:

  1. What is the correct way to implement this very simple pattern?
  2. Why does this work properly with an sObject but not with an apex type?

Controller

public with sharing class DateTimeSerializationController {

    public class MyItem {
        public String name      { get; set; }
        public DateTime theDate { get; set; }
    }

    @RemoteAction
    public static MyItem fetchTheItem() {
        MyItem item = new MyItem();
        item.name = 'Item Name';
        item.theDate = DateTime.now();
        return item;
    }

    @RemoteAction
    public static MyItem storeTheItem(MyItem item) {
        // DML or something similar could happen here
        system.debug(item);

        return item;
    }

    @RemoteAction
    public static Contact fetchTheContact() {
        Contact item = new Contact();
        item.FirstName = 'Joe';
        item.LastName = 'Bob';
        item.My_Date__c = DateTime.now();
        return item;
    }

    @RemoteAction
    public static Contact storeTheContact(Contact item) {
        // DML or something similar could happen here
        system.debug(item);

        return item;
    }
}

VF Page

<apex:page controller="DateTimeSerializationController">
    <script>
        window.myApp = window.myApp || {};

        window.myApp.fetchItem = function() {

            Visualforce.remoting.Manager.invokeAction(
                'DateTimeSerializationController.fetchTheItem',
                function(result, event){
                    if (event.status) {
                        console.log(result);
                        myApp.myItem = result;    
                    } else {
                        console.log(event.message);
                    }
                }, 
                {escape: true}
            );

        }

        window.myApp.storeItem = function() {
            Visualforce.remoting.Manager.invokeAction(
                'DateTimeSerializationController.storeTheItem',
                myApp.myItem,
                function(result, event){
                    if (event.status) {
                        console.log(result);
                    } else {
                        console.log(event.message);
                    }
                }, 
                {escape: true}
            );
        }

        window.myApp.fetchContact = function() {

            Visualforce.remoting.Manager.invokeAction(
                'DateTimeSerializationController.fetchTheContact',
                function(result, event){
                    if (event.status) {
                        console.log(result);
                        myApp.myContact = result;    
                    } else {
                        console.log(event.message);
                    }
                }, 
                {escape: true}
            );

        }

        window.myApp.storeContact = function() {
            Visualforce.remoting.Manager.invokeAction(
                'DateTimeSerializationController.storeTheContact',
                myApp.myContact,
                function(result, event){
                    if (event.status) {
                        console.log(result);
                    } else {
                        console.log(event.message);
                    }
                }, 
                {escape: true}
            );
        }
    </script>

    <p>Open the browser console and then click the buttons.</p>

    <button onclick="myApp.fetchItem();">Fetch Item</button>
    <button onclick="myApp.storeItem();">Store Item</button>

    <button onclick="myApp.fetchContact();">Fetch Contact</button>
    <button onclick="myApp.storeContact();">Store Contact</button>
</apex:page>

Answer

To send a DateTime field defined on a custom Apex class from JavaScript to Apex using Visualforce Remoting you need to use toUTCString().

You can create a JavaScript Date object from a Unix time value using the Date(value) constructor.

If you change your storeItem function to the following then everything should work without you needing complicated parsing logic in your controller or additional fields on your object.

It’s not ideal, especially if you have a large number of DateTime fields on your object but it gives you a way forward at least.

window.myApp.storeItem = function() {
    myApp.myItem.theDate = new Date(myApp.myItem.theDate).toUTCString();

    Visualforce.remoting.Manager.invokeAction(
       'DateTimeSerializationController.storeTheItem',
        myApp.myItem,
        function(result, event){
            if (event.status) {
                console.log(result);
            } else {
                console.log(event.message);
            }
        }, 
        {escape: true}
    );
}

As to the second part of your question, I have no idea why this behaviour is different for Apex classes vs. sObject fields.

That said, sObject fields have a number of special properties (such as being able to be used with apex:inputField) so until I have a better answer I’d have to consider this a ‘special’ feature/property of sObjects.

Attribution
Source : Link , Question Author : Mark Pond , Answer Author : Alex Tennant

Leave a Comment