6

2/15/2017 Update

Per comment from Doug in one of the answers this is an issue with Locker service. I verified that with Locker disabled is works as expected...

In Spring 17 as of today, with locker enabled it is still not working however

Update

So, since we get an internal error when passing classes/objects to apex controllers AND if values are not set in the controller when the component requests them then there is no way to pass those properties back (see below), I am left with this mess for every non string property

    @AuraEnabled public String endDate {
        get {
            return endDate == null ? '' : endDate;
        }
        set;
    }
    private date endDateValue {
        get {
            if (string.isBlank(endDate)) return null;

            return date.valueOf(endDate);
        }
        set {
            if (value == null) endDate = null; else endDate = string.valueOf(value);

        }
    }

Should not have to go through all that to use a simple class property in a component...Ugh

Original discussion

So I have been fighting with this for a while. Maybe my mindset is wrong and what I am doing is correct but it just seems like a lot of extra work.

Scenario

  1. Component with attribute set to a type of a custom class
  2. Input boud to a property in that class

Now is it too much to as that when the data is entered in the input that it is reflected in the component? The actual input keeps the value but the attribute is never set....

It makes it extremely difficult to pass data around. Right not I am manually setting the attribute properties based on the value input in the component.

So the examples:

The first input when data is input always logs as:

{}

The second input the data logs correctly when value are input into the field.

enter image description here

The weird part is that accessing the actual property outputs as expected:

console.log(component.get('v.myCustomClass.myTestString'));

outputs

a

as shown in the second debug line in the image

enter image description here

Custom Class

public class myProblemClass {
    @AuraEnabled public string myTestString {get;set;}

    public myProblemClass(){

    }

    public myProblemClass(String setDefault){
        mytestString = setDefault;
    }
}

Apex Controller

public class myProblemController {

    @AuraEnabled
    public static myProblemClass newProblemClass(){
        return New myProblemClass();
    }

    @AuraEnabled
    public static myProblemClass newProblemClassInitial(){
        return New myProblemClass('initial');
    }

}

Lightning Component

<aura:component description="myProblemComponent" controller="myProblemController">

    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>

    <aura:attribute name="myCustomClass" type="myProblemClass"/>
    <aura:attribute name="myCustomClassInitial" type="myProblemClass"/>

                <div class="slds-form-element">
                    <label class="slds-form-element__label" for="text-input-01">Input Label</label>
                    <div class="slds-form-element__control">
                        <ui:inputText aura:id="firstInput" updateOn="keyup" keyup="{!c.logMe}" requiredIndicatorClass="slds-required"
                                      required="true" value="{!v.myCustomClass.myTestString}" placeholder="Enter Me"
                                      class="slds-input"/>
                    </div>
                </div>

                <div class="slds-form-element">
                    <label class="slds-form-element__label" for="text-input-01">Input Label</label>
                    <div class="slds-form-element__control">
                        <ui:inputText updateOn="keyup" keyup="{!c.logMe}" requiredIndicatorClass="slds-required"
                                      required="true" value="{!v.myCustomClassInitial.myTestString}" placeholder="Enter Me"
                                      class="slds-input"/>
                    </div>
                </div>

</aura:component>

Component Controller

({
    doInit: function (component, event, helper) {

        var action = component.get("c.newProblemClass");
        var actionInitial = component.get("c.newProblemClassInitial");

        action.setCallback(this, function (response){
            component.set('v.myCustomClass',response.getReturnValue());
        })

        actionInitial.setCallback(this, function (response){
            component.set('v.myCustomClassInitial',response.getReturnValue());
        })

        $A.enqueueAction(action);
        $A.enqueueAction(actionInitial);

    },
    logMe: function(component,event,helper){
        console.log(JSON.stringify(component.get('v.myCustomClass')));
        console.log(component.get('v.myCustomClass.myTestString'));
        console.log(JSON.stringify(component.get('v.myCustomClassInitial')));
    }
})

Now, I get that in the first attribute that I am not setting the value of the class property thus the Java object does not contain anything. However, when I set a value in the first Input that is Bound to the class property I would expect it to set it.

The questions:

  1. Are my expectations incorrect
  2. If #1 is true then is setting the value of the attribute as follows the correct way. Keeping in mind my real class has 20-30 properties so that is a lot of extra code to write just to set the values

     component.get('v.myCustomClass').myTestString = 
          component.find('firstInput').get('v'value');
    

So as Ashwani points out, the binding of the value on the input get set properly so you can access the property directly. So why does it still output as {} when logging the attribute.

Now if I do this:

    component.get('v.myCustomClass').myTestString = 'ABCDEF';
    console.log(JSON.stringify(component.get('v.myCustomClass')));

then JSON of the attribute outputs the correct JSON string (see 4th debug line). So I can set it in the JS but there is no way to set it in using the actual Input field?? But you can access the property directly when set by the input field v.myCustomClass.mytestString....Confusing.....

enter image description here

  • Sorry I couldn't understand your question properly. Did you mean that when the component loads, in the second input box "Initial" value is not prepopulated? – Salesforcesmarty Jan 19 '17 at 3:21
  • No, when you type values in the first box it does not get set in the attribute. When you type values in the second box the attribute updates. Would like to have the attribute update without have to initialize all properties in the class to an empty string – Eric Jan 19 '17 at 3:22
  • console.log(JSON.stringify(component.get('v.myCustomClassInitial'))); is working fine for me – Ashwani Jan 19 '17 at 3:29
  • @Ashwani - yup, the initial one works, the non initial does not. thats what I showed in my example. I do not expect to have to do it like the initial bu setting empty string for all properties in the class. – Eric Jan 19 '17 at 3:33
  • 2
    This is interesting, when I am logging "component.get('v.myCustomClass.myTestString')", it gives the value a, however when logging the class, it returns empty. Let me have a look. – Salesforcesmarty Jan 19 '17 at 3:34
4

Code is working as expected.

In the above approach there is basic problem. Lightning does not initialize any variable itself.

In controller testString is not defined so, it is not even getting set and result {}:-

public class myProblemClass {
    @AuraEnabled public string myTestString {get;set;}

    public myProblemClass(){
    }

    public myProblemClass(String setDefault){
        mytestString = setDefault;
    }
}

Define variable in no argument constructor and it will start working:-

public myProblemClass(){
   mytestString = '';
}

There is a framework logic. Only you know there is myCustomClass.myTestString but framework doesn't know.

Take the example: set the input as:

 <ui:inputText aura:id="firstInput" updateOn="keyup" keyup="{!c.logMe}" requiredIndicatorClass="slds-required"
                                      required="true" value="{!v.myCustomClass.myTestStringNOTEXIST}" placeholder="Enter Me"
                                      class="slds-input"/>

and access it as

console.log( ' 1.1 '+component.get('v.myCustomClass.myTestStringNOTEXIST'));

You will get the value but it is not the variable in controller.

| improve this answer | |
  • then why does console.log(component.get('v.myCustomClass.myTestString')); work without initialization – Eric Jan 19 '17 at 3:38
  • 1
    @Eric you are expecting result as native javascript does but here framework plays the role. You won't get myTestString even the myTestStringNOTEXIST. However, I am not much clear how framework is securing elements. I am seeing this behavior from the initially ExpenseTracker app to learn lightning was introduced. Now we have LockerService also. – Ashwani Jan 19 '17 at 3:59
  • 1
    @Eric I agree with you. My though is they are doing this to strict the data set between client and controller so no one can miss use or manipulate it. Because javascript has option to define properties at runtime even you can register functions whenever you want. This can break the secure environment of Salesforce where we can have several components from different vendors in a single page. – Ashwani Jan 19 '17 at 4:11
  • 1
    Ok, My main goal here is to understand if setting the values with 20-30 additional lines of code is the correct way of doing it. It seems like it is. So in my JS I am getting the value from the input and directly setting the properties of the attribute then sending the JSON result to the Apex controller. Thanks for the time you put into this..... – Eric Jan 19 '17 at 4:13
  • 1
    Please retry your example with Locker deactivated in your org and let me know if you see a difference in behavior. There is a long standing issue/limitation in how Locker "wraps" plain old JavaScript objects that is actively being worked on but the solution requires the use of Proxy which was not available in a number of browsers until recently so we had to use a polyfil that is not entirely accurate. The plan is to complete the current work to leverage Proxy and release it to Soring'17 as a patch. – Doug Chasman Jan 19 '17 at 12:25
6

This issue is currently scheduled to be fixed in the Summer '17 release by leveraging Proxy as Doug mentioned in the comments to another answer. I've verified the fix with a very similar scenario on the Summer '17 codeline. Unfortunately, the fix is not scheduled to be patched in to Spring '17 as initially hoped for.

| improve this answer | |
  • What kind of timeline is this going to give us for testing between the release of this fix and the force enabling of Locker Service? – dsharrison Mar 31 '17 at 17:22
  • @D.S. Safe Harbor - we are working on a new rollout plan to protect existing production customer from breaking. Details coming out in next couple of weeks. Stay tuned. – TrevorBliss Mar 31 '17 at 21:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.