3

My problem is that sometimes - when a user enters and loads the lightning page for the first time, I get an error that is caused by inheritance order of execution:

enter image description here

I have one base component that has extensible="true" in it:

VisualField markup:

<aura:component extensible="true"
                controller="VisualField_CTRL"
                implements="force:hasRecordId,force:hasSObjectName"
                description="Interface for a visual component">

    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    <aura:attribute name="sObjectName" type="String" description="sObject api name to control the field. Taken from force:hasSObjectName" />
    <aura:attribute name="recordId" type="String" description="Id of the current record. Taken from force:hasRecordId" />
    <aura:attribute name="fieldName" type="String" description="Visual field api name to control." />

VisualField controller:

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

        helper.getSelectedValue(component);
    }
})

VisualField helper:

({
    getSelectedValue: function(component){

        // get the object and record data from the record home
        var sObjectName = component.get("v.sObjectName");
        var recordId = component.get("v.recordId");
        var fieldName = component.get("v.fieldName");

        // get the server side function and add params to it
        var action = component.get("c.getVisualField");
        action.setParams({
            sObjectApiName  : sObjectName,
            recordId        : recordId,
            fieldName       : fieldName
        });

        action.setCallback(this, function(response){
            var state = response.getState();
            if (component.isValid() && state === "SUCCESS") {
                var record = response.getReturnValue();
                if (record) {

                    // set the new value
                    component.set("v.fieldValue", record.fieldValue);

                    // show the selected value - call inherited method
                    component.getConcreteComponent().showSelected(record.fieldValue);
                }
                else {
                    alert("VisualField Failed to get visualField record.");
                }
            }
            else {
                alert(response.getError());
            }
        });
        $A.enqueueAction(action);
    }
})

I have also a second component that inherits the base component (extends="c:VisualField") and uses jQuery Raty - A Star Rating Plugin:

Rating Markup:

<aura:component extends="c:VisualField"
                implements="flexipage:availableForAllPageTypes"
                description="Controls a field with visual stars">

    <ltng:require styles="{!$Resource.Rating + '/css/jquery.raty.css'}"
                  scripts="{!join(',',
                          $Resource.Rating + '/js/jquery.js',
                          $Resource.Rating + '/js/jquery.raty.js')}"
                  afterScriptsLoaded="{!c.loadRaty}"/>

    <!--this method should be called from the super-component-->
    <aura:method name="showSelected" action="{!c.showSelected}">
        <aura:attribute name="selectedValue" type="String" />
    </aura:method>

    <aura:attribute type="String" name="currentValue"/>
    <aura:attribute type="String" name="newValue" default="0"/>

        <div>
            <div aura:id="starRating" />
        </div>

</aura:component>

Rating Controller:

({

    // load raty rating jquery plugin.
    loadRaty : function(component, event, helper) {
        helper.initRatingElement(component);
    },

    // comes from aura:method name="showSelected"
    showSelected: function(component, event, helper) {

        if (!event)
            return;

        // get the component and the selected value from the event
        var params = event.getParam('arguments');
        if (params) {
            // show selected stars
            var newValue = params.selectedValue;
            helper.setStars(component, newValue);
        }
     },


})      

Rating Helper:

({
    initRatingElement: function(component){

        var ratingElement = component.find("starRating").getElement();
        var helper = this;

        $( ratingElement ).raty({
            number  : 5,
            starOff : '/resource/Rating/images/star_off_darkgray--small.png',
            starOn  : '/resource/Rating/images/star_on--small.png',
            single  : false,
            // load the click event and function
            click   : function(score, evt) {
                if(score == null )
                    score = 0;
                var newValue = score.toString();
                if(component.get("v.currentValue") != newValue ){
                    component.set("v.newValue", newValue);
                }
            }
        });
    },

    setStars: function(component, selectedValue) {

        // save value as current
        component.set("v.currentValue", selectedValue);

        // show on the component
        var ratingElement = component.find("starRating").getElement();
        // check if raty has been loaded, if not - load it first:
        if (!(typeof ($(ratingElement).raty) === "function")) {
            // load raty first:
            this.initRatingElement(component);
        }
        $(ratingElement).raty('score', selectedValue);
    }
})        
  1. Rating component loads jquery and raty plugins and after they load, it initiates the raty plugin.
  2. base VisualField component on it's init gets values from server side to see and set the selected value.
  3. when the server side function in the base component finishes to load, it calls the showSelected method in the inherited component Rating.
  4. the showSelected method in the inherited component Rating executes the setStars helper method that uses the jQuery and Raty plugins $(ratingElement).raty('score', selectedValue);
  5. this call fails sometimes when the page first loads with the error message:

    Uncaught Action failed: c:Rating$controller$showSelected [$ is not defined] Callback failed: apex://VisualField_CTRL/ACTION$getVisualField throws at https://mycompany.lightning.force.com/auraFW/javascript/YKnqxnHX5EzqqKoVZYsoZQ/aura_prod.js:2:15 Object.setStars()@components/c/Rating.js:62:9 showSelected()@components/c/Rating.js:22:20

I believe this happens because the function is called from the base component and the jQuery and Raty plugins are not loaded from there and still did not load from the inherited Rating component.

Can anyone help me fix this very annoying problem?

thanks

Itai

  • Is it just me or does it require twice the code to do vs VF. Not complaining just wondering if my observation is correct – Eric May 4 '17 at 6:32
  • not sure - here I want to have a generic component for code reusing - the VisualField component, and then I use it to show visually different types of visual field - here I use rating, I also have a traffic light component that uses it, and probably more components in the future - the idea is like the Path component - to show a field visually, and the base component mainly does the connection to the server which is the same for all. @Eric do you see my problem and have a suggestion for a fix? – Itai Shmida May 4 '17 at 6:39
3

How about moving doInit() in VisualField to the helper. Then, instead of calling it from the init event of VisualField, call it from the Rating component when you're ready to do that.

Here's a generic example. Parent component:

<aura:component extensible="true">
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    <aura:attribute name="autoInit" default="true" type="Boolean" />

</aura:component>

Parent controller:

({
    doInit : function(component, event, helper) {
        if(component.get('v.autoInit')) {
            helper.doInit(component);
        }
    }
})

Parent helper:

({
    doInit : function(component) {
        console.log('Parent init');
    }
})

Child component:

<aura:component extends="c:Parent">
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    <aura:set attribute="autoInit" value="false"/>
</aura:component>

Child controller:

({
    doInit : function(component, event, helper) {
        console.log('Child init');
        helper.doInit(component);
    }
})
  • Thanks Aidan, I have implemented your suggestion and for now it looks OK!!!! Since this problem does not always happen, I am waiting for user feedback to see if this solved our problems. – Itai Shmida May 4 '17 at 12:14
  • I still have a problem, it is slightly different - I get the error [$(...).raty is not a function]. Maybe it is related to the order of execution between init() and afterScriptsLoaded? - if init loads first, it will explain my problem – Itai Shmida May 7 '17 at 6:14
  • It's because my sandbox is now summer 17. anyway - I have changed the init function to load on the afterScriptsLoaded, and the problem is fixed in my sandbox, I'll deploy to production and see it the problem is resolved – Itai Shmida May 7 '17 at 10:02
  • Yep, the only way to reliably run code which depends on scripts is to do it following afterScriptsLoaded – Aidan May 8 '17 at 12:07
  • OK, I think that this is a good answer - for now I have no more errors like this. The solution was yours & moving the init function to load from afterScriptsLoaded. – Itai Shmida May 9 '17 at 4:29

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.