4

I'm currently trying to refactor the Discover Lightning Actions source code into something which wouldn't make my eyes bleed on a professional project.

At present, my CreateUser.cmp looks like this:

<aura:component implements="force:lightningQuickActionWithoutHeader,force:hasRecordId" controller="LTNG_CaseStudyAuraCtrl">
    <!-- Public attributes -->
    <aura:attribute name="testUser" type="Test_User__c" default="{sobjectType: 'Test_User__c'}" access="global"/>
    <aura:attribute name="hasErrors" type="Boolean" description="Indicate whether there were failures or not" />
    <aura:attribute name="caseStudyName" type="String" />
    <aura:attribute name="recordId" type="String" description="Case Study id"/>

    <!-- "Private" attributes, for binding with input componets -->
    <aura:attribute name="firstName" type="String" />
    <aura:attribute name="lastName" type="String" />
    <aura:attribute name="nickName" type="String" />
    <aura:attribute name="email" type="String" />

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

    <div class="slds-page-header" role="banner">
        <p class="slds-text-heading--label">Case Study</p>
        <h1 class="slds-page-header__title slds-m-right-small slds-truncate slds-align-left" title="Case Study Title">{!v.caseStudyName}</h1>
    </div>
    <br/>

    <aura:if isTrue="{!v.hasErrors}">
        <div class="userCreateError">
            <ui:message title="Error" severity="error" closable="true">
                Please review the error messages.
            </ui:message>
        </div> 
    </aura:if>

    <div class="slds-form--stacked">
        <c:LTNG_SldsInput label="first name" fieldName="firstName" value="{!v.testUser.firstName}" required="true" updateForm="{!c.updateNickname}" />
        <c:LTNG_SldsInput label="last name" fieldName="lastName" value="{!v.testUser.lastName}" required="true" />
        <c:LTNG_SldsInput label="nickname" fieldName="nickName" value="{!v.testUser.Nickname__c}" />
        <c:LTNG_SldsInput label="user's email" fieldName="email" value="{!v.testUser.Email__c}" required="true" />
        <c:LTNG_SldsInput label="user's password" fieldName="password" value="{!v.testUser.Password__c}" required="true" secret="true" />

        <div class="slds-form-element">
            <ui:button class="slds-button slds-button--neutral" press="{!c.cancel}" label="Cancel" />
            <c:LTNG_SaveTestUserButton testUser="{!v.testUser}" />
        </div>
    </div>
</aura:component>

Everything works as expected (except dynamically setting aura:id="{!v.fieldName}", as discussed here) UNTIL the user saves the form with correct data.

At that point, then everything gets stuck in an infinite loop and I need to kill the browser. Experiments with the console log suggest that saving is somehow getting a "SUCCESS" response, but the test user is not actually saved. But I've tested the save method on the relevant Apex controller and it seems to work correctly.

Any ideas what might be wrong?

Here is my save button component and the Test User controller:

LTNG_SaveTestUserButton.cmp:

<aura:component implements="force:hasRecordId" controller="LTNG_TestUserAuraCtrl">
<aura:attribute name="testUser" type="Test_User__c" default="{sobjectType: 'Test_User__c'}" />
<aura:attribute name="recordId" type="String" description="Case Study id"/>

<aura:registerEvent name="testUserInputErrorsEvent" type="c:LTNG_TestUserInputErrorsEvent" />

<ui:button class="slds-button slds-button--neutral" press="{!c.save}" label="Save User" />

LTNG_SaveTestUserButtonController.js:

({
    save: function(component, event, helper) {
        var isValid = helper.validatePassword(component, event, helper)
                        && helper.validateEmail(component, event, helper)

        if (isValid) {
            component.set('v.hasErrors', false);
            helper.save(component, helper);
        }
        else {
            component.set('v.hasErrors', true);
        }
    }
})

LTNG_SaveTestUserButtonHelper.js:

({
    save : function(component, helper) {
        var testUser = component.get('v.testUser');

        var action = component.get('c.save');
        action.setParams({
            'name' : testUser.firstName + ' ' + testUser.lastName,
            'password' : testUser.password,
            'email': testUser.email,
            'nickname': testUser.nickName,
            'caseStudyId' : component.get('v.recordId')
        });
        action.setCallback(this, function(response){
            helper.saveResponseHandler(component, helper, action, response);
        });
        $A.enqueueAction(action);
    },

    saveResponseHandler: function(component, helper, action, response) {
        var returnValue = response.getReturnValue();

        var isValid = component.isValid();
        var state = action.getState();

        if (isValid && state === 'SUCCESS') {
            helper.toastSuccess();
        }
        else {
            console.error('There was a problem and the state is: ' + action.getState());
        }
    },

    toastSuccess: function() {
        var closeAction = $A.get('e.force:closeQuickAction');
        if (closeAction) {
            closeAction.fire();
        }

        var toastEvent = $A.get('e.force:showToast');
        toastEvent.setParams({title: 'Success!', message: 'The test user has been created.'});
        toastEvent.fire();
        $A.get('e.force:refreshView').fire();
    },

    fireInputErrorEvent: function(inputField, message, value) {
        var testUserInputErrorsEvent = $A.get('e.c:LTNG_TestUserInputErrorsEvent');
        testUserInputErrorsEvent.setParams({
            fieldName: inputField,
            errorMessage: message ? (message + ' (value: ' + value + ')') : null
        });
        testUserInputErrorsEvent.fire();
    },

    validate: function(component, helper, inputField, testUserField, requirement, message) {
        var value = component.get('v.testUser.' + testUserField);

        var isValid = requirement(value);
        if (!isValid) {
            helper.fireInputErrorEvent(inputField, message, value);
        }
        return isValid;
    },

    validatePassword: function(component, event, helper) {
        var isValid = validate(helper.valueIsDefined, 'You must enter a password.')
            && validate(isCorrectLength, 'The password is the wrong length.')
            && validate(hasAtLeastOneNumber, 'The password must contain at least one number.')
            && validate(hasAtLeastOneLetter, 'The password must contain at least one letter.');

        if (isValid){
            helper.fireInputErrorEvent('password');
        }

        return isValid;

        function isCorrectLength(value) {
            return value.length >= 7 || value.length <= 15;
        }

        function hasAtLeastOneNumber(value) {
            return value.search(/[0-9]+/) !== -1;
        }

        function hasAtLeastOneLetter(value) {
            return value.search(/[a-zA-Z]+/) !== -1
        }

        function validate(requirement, message) {
            return helper.validate(component, helper, 'password', 'Password__c', requirement, message);
        }
    },

    validateEmail: function(component, event, helper) {
        var isValid = validate(helper.valueIsDefined, 'You must enter an email.')
            && validate(hasCorrectFormat, 'Email is not in the correct format.')
            && validate(isGmail, 'Email must be a gmail account.');

        if (isValid){
            helper.fireInputErrorEvent('email');
        }

        return isValid;

        function hasCorrectFormat(value) {
            var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
            return re.test(value);
        }

        function isGmail(value) {
            var parts = value.split('@');
            var domain = parts[parts.length - 1];
            return domain.toLowerCase() === 'gmail.com';
        }

        function validate(requirement, message) {
            return helper.validate(component, helper, 'email', 'Email__c', requirement, message);
        }
    },

    valueIsDefined: function isDefined(value) {
            return value !== undefined;
    }
})

LTNG_TestUserAuraCtrl.cls

({
    save : function(component, helper) {
        var testUser = component.get('v.testUser');

        var action = component.get('c.save');
        action.setParams({
            'name' : testUser.firstName + ' ' + testUser.lastName,
            'password' : testUser.password,
            'email': testUser.email,
            'nickname': testUser.nickName,
            'caseStudyId' : component.get('v.recordId')
        });
        action.setCallback(this, function(response){
            helper.saveResponseHandler(component, helper, action, response);
        });
        $A.enqueueAction(action);
    },

    saveResponseHandler: function(component, helper, action, response) {
        var returnValue = response.getReturnValue();

        var isValid = component.isValid();
        var state = action.getState();

        if (isValid && state === 'SUCCESS') {
            helper.toastSuccess();
        }
        else {
            console.error('There was a problem and the state is: ' + action.getState());
        }
    },

    toastSuccess: function() {
        var closeAction = $A.get('e.force:closeQuickAction');
        if (closeAction) {
            closeAction.fire();
        }

        var toastEvent = $A.get('e.force:showToast');
        toastEvent.setParams({title: 'Success!', message: 'The test user has been created.'});
        toastEvent.fire();
        $A.get('e.force:refreshView').fire();
    },

    fireInputErrorEvent: function(inputField, message, value) {
        var testUserInputErrorsEvent = $A.get('e.c:LTNG_TestUserInputErrorsEvent');
        testUserInputErrorsEvent.setParams({
            fieldName: inputField,
            errorMessage: message ? (message + ' (value: ' + value + ')') : null
        });
        testUserInputErrorsEvent.fire();
    },

    validate: function(component, helper, inputField, testUserField, requirement, message) {
        var value = component.get('v.testUser.' + testUserField);

        var isValid = requirement(value);
        if (!isValid) {
            helper.fireInputErrorEvent(inputField, message, value);
        }
        return isValid;
    },

    validatePassword: function(component, event, helper) {
        var isValid = validate(helper.valueIsDefined, 'You must enter a password.')
            && validate(isCorrectLength, 'The password is the wrong length.')
            && validate(hasAtLeastOneNumber, 'The password must contain at least one number.')
            && validate(hasAtLeastOneLetter, 'The password must contain at least one letter.');

        if (isValid){
            helper.fireInputErrorEvent('password');
        }

        return isValid;

        function isCorrectLength(value) {
            return value.length >= 7 || value.length <= 15;
        }

        function hasAtLeastOneNumber(value) {
            return value.search(/[0-9]+/) !== -1;
        }

        function hasAtLeastOneLetter(value) {
            return value.search(/[a-zA-Z]+/) !== -1
        }

        function validate(requirement, message) {
            return helper.validate(component, helper, 'password', 'Password__c', requirement, message);
        }
    },

    validateEmail: function(component, event, helper) {
        var isValid = validate(helper.valueIsDefined, 'You must enter an email.')
            && validate(hasCorrectFormat, 'Email is not in the correct format.')
            && validate(isGmail, 'Email must be a gmail account.');

        if (isValid){
            helper.fireInputErrorEvent('email');
        }

        return isValid;

        function hasCorrectFormat(value) {
            var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
            return re.test(value);
        }

        function isGmail(value) {
            var parts = value.split('@');
            var domain = parts[parts.length - 1];
            return domain.toLowerCase() === 'gmail.com';
        }

        function validate(requirement, message) {
            return helper.validate(component, helper, 'email', 'Email__c', requirement, message);
        }
    },

    valueIsDefined: function isDefined(value) {
            return value !== undefined;
    }
})
9

The reason for the cause of the recursion is your apex method and the lightning component helper method have same name "save"

I would change the apex method to different name than save

save : function(component, helper) {
var testUser = component.get('v.testUser');

var action = component.get('c.serverSave');

Using separate name for controller and helper is also recommended in the doc here

Use unique names for client-side and server-side actions in a component. A JavaScript function (client-side action) with the same name as a server-side action (Apex method) can lead to hard-to-debug issues.

  • I've a new problem (to post of shortly), but it works! :-) – Brian Kessler Mar 4 '17 at 21:10

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.