When Salesforce is life!

Tag: JavaScript

[Salesforce] Handle encryption and decryption with Apex Crypto class and CrypoJS

One of the easiest Javascript libraries for encryption I usually adopt is CryptoJS, quick setup and good support for most algorithms.

But I got an headache trying to make it talk with Salesforce, this was due to my relatively low encryption-topics training but also to a specific way Salesforce handles encryption.

I was surprised that none has ever had the same need before.

I’m not going to explain how I came up to this solution (one of the reasons is that I already forgot it…as I always say, my brain is a cool CPU but with a low amount of storage), but I’ll just give you the way I solved encrypted data exchange between a Javascript script (whether it is client or server side) and Salesforce.

In Apex encrypting and decrypting a string is quite easy:

//encrypt
String algorithmName = 'AES256';
Blob privateKey = Crypto.generateAesKey(256);
Blob clearText = Blob.valueOf('Encrypt this!');
Blob encr = Crypto.encryptWithManagedIV(algorithmName, privateKey, clearText);
system.debug('## ' + EncodingUtil.base64encode(encr));
//decrypt
Blob decr = Crypto.decryptWithManagedIV(algorithmName, privateKey, encr );
System.debug('## ' + decr.toString());

This could be an example of the output:

## Lg0eJXbDvxNfLcFMwJm6CkFtxy4pWgkmanTvKLcTttQ=
## Encrypt this!

For encryption noobs out there, the encrypted string changes every time you run the script.

The string if first encrypted with the AES256 algorithm and then decrypted using the same secret key (generated automatically by Salesforce).

All is done through Crypto class’ methods:


Valid values for algorithmName
are:

– AES128
– AES192
– AES256

These are all industry standard Advanced Encryption Standard (AES) algorithms with different size keys. They use cipher block chaining (CBC) and PKCS5 padding.

Salesforce HELP

PKCS5 padding is a subset of the more general PKCS7, that is supported by CryptJS, so it still works.

The only thing that is not clearly stated here (at least for my low storage brain) is that this method uses an Initialization Vector (IV, that is used together with the private key to generate the proper encryption iterations) which has a fixed 16 Bytes length.

Also, the IV is included within the encrypted string: this is the key point.

To encrypt and decrypt using the following method the CryptoJS must be aware of the first 16 Bytes of the IV and append it to (if we are encrypting from JS to Salesforce) or extract it from (if we are decrypting in JS from a Salesforce encrypted string) the encrypted string.

This is what I came up with after a bit of research (you have to deal with binary data when encrypting, that’s why we use Base64 to exchange keys and encrypted strings).

//from https://gist.github.com/darmie/e39373ee0a0f62715f3d2381bc1f0974
var base64ToArrayBuffer = function(base64) {
    var binary_string =  atob(base64);
    var len = binary_string.length;
    var bytes = new Uint8Array( len );
    for (var i = 0; i < len; i++)        {
        bytes[i] = binary_string.charCodeAt(i);
    }
    return bytes.buffer;
};
//from //https://gist.github.com/72lions/4528834
var appendBuffer: function(buffer1, buffer2) {
    var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
    tmp.set(new Uint8Array(buffer1), 0);
    tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
    return tmp.buffer;
};
//from //https://stackoverflow.com/questions/9267899/arraybuffer-to-base64-encoded-string
var arrayBufferToBase64 = function( arrayBuffer ) {
    return btoa(
        new Uint8Array(arrayBuffer)
            .reduce(function(data, byte){
                 return data + String.fromCharCode(byte)
            }, 
        '')
    );
},
//Encrypts the message with the given secret (Base64 encoded)
var encryptForSalesforce = function(msg, base64Secret){
    var iv = CryptoJS.lib.WordArray.random(16);
    var aes_options = { 
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7,
        iv: iv
    };
    var encryptionObj  = CryptoJS.AES.encrypt(
        msg,
        CryptoJS.enc.Base64.parse(base64Secret),
        aes_options);
    //created a unique base64 string with  "IV+EncryptedString"
    var encryptedBuffer = base64ToArrayBuffer(encryptionObj.toString());
    var ivBuffer = base64ToArrayBuffer((encryptionObj.iv.toString(CryptoJS.enc.Base64)));
    var finalBuffer = appendBuffer(ivBuffer, encryptedBuffer);
    return arrayBufferToBase64(finalBuffer);
};
//Decrypts the string with the given secret (both params are Base64 encoded)
var decryptFromSalesforce = function(encryptedBase64, base64Secret){
    //gets the IV from the encrypted string
    var arrayBuffer = base64ToArrayBuffer(encryptedBase64);
    var iv = CryptoJS.enc.Base64.parse(arrayBufferToBase64(arrayBuffer.slice(0,16)));
    var encryptedStr = arrayBufferToBase64(arrayBuffer.slice(16, arrayBuffer.byteLength));

    var aes_options = { 
        iv: iv,
        mode: CryptoJS.mode.CBC
    };

    var decryptObj  = CryptoJS.AES.decrypt(
        encryptedStr,
        CryptoJS.enc.Base64.parse(base64Secret),
        aes_options
    );

    return decryptObj.toString(CryptoJS.enc.Utf8);
};

By sharing the Base64 of the Salesforce generated secret (using the method Crypto.generateAesKey(256) ) between your JS client and Salesforce, you can store and exchange encrypted data with a blink of an eye.

[Javascript / Chrome / EasyPeasy] Blocking form autocomplete after Chrome Canary (version 65)

Recent Google Chrome update (65) brought something that developers are not liking too much: the autocomplete=”off” attribute on forms and inputs is no more considered.

Read in depth this Stack Overflow thread.

The solution (not so clear) is to give a random autocomplete value to the autocomplete attribute of each input of the form:

$('form input').each( function(){
   $(this).attr('autocomplete','no-autocomplete-'+(Math.random()*Math.random()));
});

Also apparently Chrome uses the name attribute (at least for email and password values), in my use case the name attribute was not necessary in the form so this script worked like a charm.

[Salesforce / Javascript] Visualforce tips for Javascript nerds

No secret I love Javascript.

It’s messy to the right point, it can be quick and dirty, it can be elegant, it can be wathever you are: your Javascript code can be a mirror of your personality.

And what’s better that putting together 2 things I like the most?

Yes, I’m talking about Salesforce and Javascript.

Javascript is the core of the new Lightning framework but since the birth of S-Controls (now deprecated) and Visualforce it’s been an importanto tool to expand the standard Salesforce platform.

What do you have to know before starting your Javascript journey with Visualforce?

Use zipped static resources for your scripts

Put all your JS files into a zipped static resource, this way you have aboslute control over the place where files are stored and have no problem finding them.

Use the apex:includeScript

If you use the HTML script tag the resources can be loaded more than once and this can lead to unexpected errors.

Think you are using jQuery and Bootstrap and your page loads them in sequence.

In the same Visualforce you also use a custom Visualforce component which loads jQuery itself: what’s the outcome? Bootstrap is simply erased from jQuery plugins.

The apex:includeScript component loads all the JS files only once, avoiding this kind of conflicts.

Use only one version per library

This is the same scenario of the previous paragraph.

If you load, e.g., jQuery 2 in one point and jQuery 3 on another place, this could lead to conflicts and strange behavior.

Keeping you library version on a static resouces allow you to get the right library version across all Visualforce page and components.

Use jQuery as much as you can

You’ll be using Javascript because you’ll want to create a slick, modern and responsive UI, so why reinvent the wheel?

Some good programmers still lack in jQuery knowledge, if you are one of them keep some time to study the bases of the library and I assure you won’t regret it…and you can be sure you’ll have really few problems for compatibility on different browsers.

Give all your components an ID / class name

When you develop a new Visualforce page take few minutes more that necessary to give all your components a recognizable html ID.

<page id="thePage">
 <pageBlock id="mainPB">
  <apex:pageBlockSection id="customerDataPBS" rendered="{!showCustomerData}">
   <apex:pageBlockSectionItem id="csDataNamePBSI">
    <apex:outputLabel id="csDataNameLBL">Name</apex:outputLabel>
    <apex:outputPanel id="csDataNameVAL">
     <apex:inputField id="csDataNameINP" value="{!account.Name}" />
     <apex:commandButton id="refreshCsNameBTN" 
          value="Refresh Data..." 
          action="{!refreshAccountName}"
          rerender="csDataNameVAL" />
    </apex:outputPanel>
   </apex:pageBlockSectionItem>
  </apex:pageBlockSection>
 </pageBlock>
</page>

This will ease your life if in the near future you decide to use Javascript to access the page’s markup.

Remember that IDs are automatically calculate by the Visualforce render engine, so if you want the exact ID you can use the $Component global variable, so if you want to access the apex:inputField on the previous code:

document.getElementById("{!$Component.thePage.mainPB.customerDataPBS.csDataNamePBSI.csDataNameINP}");

But I recommend not to use this way.

This is not incorrect but as you continue your Visualforce development (sometimes the customers you work with don’t know what they actually want till they want it!) you can change the UI, moving sections on the page, so the ID chain can change over time.

That’s why you can use the “ends with” operator with jQuery to get an HTML component:

var inputField = $('[id$="csDataNameINP"]');

That is easier. The only thing is that you must be sure that you only have one and only one component with that ID.

Sometimes I use class names rather that ID, so for example:

<apex:inputField class="csDataNameINP" value="{!account.Name}" />

This way I don’t care about ID generation and go directly with searching for a specific class name.

var inputField = $('.csDataNameINP');

That is even clearer (this is something I usually do and I like the syntax but it is not a best practice).

Keep coding and may the Force.com be with you!

[Salesforce/ Lightning] inputLookup: the missing component

Or quick tips on how to implement your own inputLookup Salesforce ligthning component

This is a repost of the article I published on Salesforce Developer Blog.

Salesforce Spring ’15 release brought some brand new components ready to be used in your lightning apps. One of the missing components that could be useful for your apps is the input lookup component. The idea is to use a typeahead input field. We are going to user Bootstrap and the Twitter typeahead component.

The idea behind this implementation is that the user will type a search on the input field and the typeahead shows a list of the first 20 items for the given object and search string. On load, the component should also load the initial value of the “name” field for the given object, if an ID value is selected.

This is how the component will look:

For those of you who don’t want to read more and just use the component, jump to the inputLookup github repo.

To ensure proper script loading we will use RequireJS with the trick proposed in this blog post.

Let’s first have a look at the lookupInput component’s markup (github):

<aura:component controller="InputLookupController">
    
    <ltng:require scripts="/resource/RequireJS" afterScriptsLoaded="{!c.initTypeahead}" />
    <aura:handler name="init" value="{!this}" action="{!c.setup}"/>

    <!-- ... -->

    <!-- public attributes -->
    <aura:attribute name="type" type="String" required="true"/>
    <aura:attribute name="value" type="String" />
    <aura:attribute name="className" type="String" />
    <!-- PRIVATE ATTRS -->
    <aura:attribute name="nameValue" type="String"
                    access="PRIVATE"/>
    <aura:attribute name="isLoading" type="Boolean"
                    default="true"
                    access="PRIVATE"/>

    <div class="has-feedback">
        <input id="{!globalId+'_typeahead'}" type="text" 
               value="{!v.nameValue}" class="{!v.className}" 
               onchange="{!c.checkNullValue}" readonly="{!v.isLoading}"/> 
        <span class="glyphicon glyphicon-search form-control-feedback"></span>
    </div>
</aura:component>

It uses the “InputLookupController” Apex class (with required @RemoteActions).

The RequireJS components triggers “c:requireJSLoaded” event that actually makes the component load its bootstrapped Typeahead input field: note the absence of any custom namespace, that is replaced with the “c” default namespace (this is a Spring ’15 feature).

We also have an init method that ensures that all required parameters are set.

This component simply uses a “type” parameters (with the actual API name of the needed SObject) and a “value” parameter that stores the selected ID (or the loading ID).

At first we want to load on initialization (or when the value parameter changes) the content of the input (we want to see the “Name” of the pre-selected SObject and not its ID).

The initialization loading is done using the following “inputLookupController.js” method:

initTypeahead : function(component, event, helper){
        try{
            //first load the current value of the lookup field and then
            //creates the typeahead component
            helper.loadFirstValue(component);
        }catch(ex){
            console.log(ex);
        }
    }

This is the helper part:

loadFirstValue : function(component) {
        //this is necessary to avoid multiple initializations (same event fired again and again)
        if(this.typeaheadInitStatus[component.getGlobalId()]){ 
            return;
        }
        this.typeaheadInitStatus[component.getGlobalId()] = true;
        this.loadValue(component);

    },

    loadValue : function(component, skipTypeaheadLoading){
        this.typeaheadOldValue[component.getGlobalId()] = component.get('v.value');
        var action = component.get("c.getCurrentValue");
        var self = this;
        action.setParams({
            'type' : component.get('v.type'),
            'value' : component.get('v.value'),
        });

        action.setCallback(this, function(a) {
            if(a.error && a.error.length){
                return $A.error('Unexpected error: '+a.error[0].message);
            }
            var result = a.getReturnValue();
            component.set('v.isLoading',false);
            component.set('v.nameValue',result);
            if(!skipTypeaheadLoading) self.createTypeaheadComponent(component);
        });
        $A.enqueueAction(action);
    }

This piece of code simply calls the “InputLookupController.getCurrentValue” remote action that simply preload the selected record’s Name field (if any).

The “typeaheadInitStatus” property (see the full file code) is used to avoid multiple initialization of the component (the “createTypeaheadComponent” method is the method that is used to create the typeahead component: this must be executed only once!).

On the other side the “typeaheadOldValue” stores the current value, in order to understand if the value parameter is changed: this is extremely useful when some other part of the application changes the value parameters and so the component should refresh it’s inner value.

To achieve this simply see the “inputLookupRenderer.js” file:

rerender : function(component, helper){
        this.superRerender();
        //if value changes, triggers the loading method
        if(helper.typeaheadOldValue[component.getGlobalId()] !== component.get('v.value')){
            helper.loadValue(component,true);
        }
    }

This method is called every time the component is rerendered (e.g. when one of its parameters changes), and it constantly evaluates if the “value” parameter has changed since the last set; if this is true then we load again the component, without re-contructing it (“skipTypeaheadLoading” parameters of the loadValue function).

The typeahead is a simple porting of the Twitter Typeahead component with some changes, the most important of which is the call to the remote action that search (using SOSL) for all SObjects given the search term (“substringMatcher” function of the helper file):

// . . . 
    substringMatcher : function(component) {
        //usefull to escape chars for regexp calculation
        function escapeRegExp(str) {
          return str.replace(/[-[]/{}()*+?.^$|]/g, "$&");
        }

        return function findMatches(q, cb) {
            q = escapeRegExp(q);
            var action = component.get("c.searchSObject");
            var self = this;

            action.setParams({
                'type' : component.get('v.type'),
                'searchString' : q,
            });

            action.setCallback(this, function(a) {
                if(a.error && a.error.length){
                    return $A.error('Unexpected error: '+a.error[0].message);
                }
                var result = a.getReturnValue();

                var matches, substrRegex;

                // an array that will be populated with substring matches
                var matches = [];

                // regex used to determine if a string contains the substring `q`
                var substrRegex = new RegExp(q, 'i');
                var strs = JSON.parse(result);
                // iterate through the pool of strings and for any string that
                // contains the substring `q`, add it to the `matches` array
                $.each(strs, function(i, str) {
                    if (substrRegex.test(str.value)) {
                        // the typeahead jQuery plugin expects suggestions to a
                        // JavaScript object, refer to typeahead docs for more info
                        matches.push({ value: str.value , id: str.id});
                    }
                });
                if(!strs || !strs.length){

                    $A.run(function(){
                        component.set('v.value', null);
                    });
                }
                cb(matches);
            });
            $A.run(function(){
                $A.enqueueAction(action);
            });
        };
    }
    // . . .

This “complex” code is used to push the results from the “searchSObject” remote action to the typeahead result set picklist.

Finally you can add the component in your lightning app:

<aura:application >

    <aura:attribute name="id" type="String" default="" access="GLOBAL"/>
    <aura:attribute name="objNew" type="Contact" default="{'sobjectType':'Contact',  
                                                       'Id':null}" />

    <!-- ... -->

    <div class="well">
        <div class="panel panel-primary">
            <div class="panel-heading">Existent sobject</div>
            <div class="panel-body">
                <div class="form-horizontal" >
                    <div class="form-group">
                        <label class="col-sm-2 control-label">Contact</label>
                        <div class="col-sm-8">
                            <c:inputLookup type="Contact"  
                                         value="{!v.id}"
                                         className="form-control "/>
                        </div>
                    </div>
                    <div class="form-group has-feedback">
                        <label class="col-sm-2 control-label">Loaded contact</label>
                        <div class="col-sm-8 ">
                            <ui:inputText value="{!v.id}" 
                                          class="form-control"
                                          placeholder="Change id value"/>
                         </div>
                    </div>
                </div>
            </div>
        </div>

        <div class="panel panel-primary">
            <div class="panel-heading">New sobject</div>
            <div class="panel-body">
                <div class="form-horizontal" >
                  <div class="form-group">
                    <label class="col-sm-2 control-label">Contact</label>
                    <div class="col-sm-8">
                      <c:inputLookup type="{!v.objNew.sobjectType}"
                                    value="{!v.objNew.Id}"  
                                    className="form-control "/>
                      </div>
                  </div>
                 <div class="form-group has-feedback">
                    <label class="col-sm-2 control-label">New contact</label>
                    <div class="col-sm-8 ">
                        <ui:inputText value="{!v.objNew.Id}" 
                                      class="form-control"
                                      placeholder="Change id value"/>
                     </div>
                  </div>
                </div>
            </div>
        </div>
    </div>

</aura:application>

We have two components, the first one ‘s value is grabbed directly from the “?id=XXXX” query string parameters, while the second one has no value on load:

Finally you can easily test that everytime you change the “value” input text, the inputLookup component loads it’s new “name” field, while if you search for a Contact on the component and select a record, the value input is set with the new value.

You brand new custom input lookup component is served!

[Salesforce / RemoteAction] Using Date fields on a RemoteAction (PITA ALERT!)

I’ve just come through a bug on the Date field handling for Visual Force @RemoteAction.

We want to pass to a remote action a list of SObjects to be handled correctly.

This is the controller that hosts the @RemoteAction method:

public class MyController(){
   @RemoteAction
   public String doAction(List<contact> contacts){
      return 'All is ok!';
   }
}

This is the JS on the VF page:

function action(){
   var contact1 = {"LastName":"Smith",
                   "FirstName":"John",
                   "Phone" : "999-999-999"};
   var contact2 = {"LastName":"Brown",
                   "FirstName":"Mike",
                   "Phone" : "123-456-789"};
   var contactList= [contact1, contact2];
   Visualforce.remoting.Manager.invokeAction(
              '{!$RemoteAction.MyController.doAction}', 
              accList || [],
                function(result, event){
                    if(event.status){
                        alert(result);
                    }else{
                        //generic event error
                        alert(event.message);
                    }
                },{escape: false, timeout: 120000});
}

This runs perfectly.

But let’s add a Date field on the JS part:

function action(){
   var contact1 = {"LastName":"Smith",
                   "FirstName":"John",
                   "BirthDate":"1980-01-01",
                   "Phone" : "999-999-999"};
   var contact2 = {"LastName":"Brown",
                   "FirstName":"Mike",
                   "BirthDate":"1982-11-21",
                   "Phone" : "123-456-789"};
   var contactList= [contact1, contact2];
   Visualforce.remoting.Manager.invokeAction(
              '{!$RemoteAction.MyController.doAction}', 
              contactList || [],
                function(result, event){
                    if(event.status){
                        alert(result);
                    }else{
                        //generic event error
                        alert(event.message);
                    }
                },{escape: false, timeout: 120000});
}

And all messes up, stating that you are passing an incorrect type to MyController.doAction(List).

The problem is related to how the Date type serialization is done when invoking the Remote Action (it is apparently a bug not fixed yet).

The solution is to pass a serialized version of the JS array and deserialize it with Apex:

public class MyController(){
   @RemoteAction
   public String doAction(String serializedContacts){
      List<contact> contacts = (List<contact>)JSON.deserialize(objList,List<contact>.class);
      return 'All is ok!';
   }
}
function action(){
   var contact1 = {"LastName":"Smith",
                   "FirstName":"John",
                   "BirthDate":"1980-01-01",
                   "Phone" : "999-999-999"};
   var contact2 = {"LastName":"Brown",
                   "FirstName":"Mike",
                   "BirthDate":"1982-11-21",
                   "Phone" : "123-456-789"};
   var contactList= [contact1, contact2];
   Visualforce.remoting.Manager.invokeAction(
              '{!$RemoteAction.MyController.doAction}', 
              JSON.stringify(contactList || []),
                function(result, event){
                    if(event.status){
                        alert(result);
                    }else{
                        //generic event error
                        alert(event.message);
                    }
                },{escape: false, timeout: 120000});
}

Remember: don’t mess with JSON serialization!

[Salesforce / JS] Download automatically files from apex (using href link and base64)

This post is a recap of this Salesforce Developer Forum thread.

We want to trigger a download from an attachment but the running user don’t have access to the object (think of Community User for instance).

In the controller read the Attachment’s body in a String getter coded in Base64:

public String base64Value{get;set;}
public String contentType{get;set;}
public void loadAttachment(){
   Attachment att = [Select Id, Body, ContentType From Attachment limit 1];
   base64Value = EncodingUtil.base64Encode(att.Body);
   contentType = att.ContentType;
}

In the page:

<a href="data:{!contentType};content-disposition:attachment;base64,{!base64Value}">Download file</a>

[Salesforce / Lightning] Loading scripts

a{ color: red !important;}

UPDATE

Due to the introduction of the ltng:require, this post is no more a valid solution. Refer to the official Lightning documentation.

This post has been more like a request for help, rather than a technical blog post, but it came to be an awesome way to see Salesforce community in action and ready to help!In the last Dreamforce 14 big Mark presented the Lightning framework for fast development of reusable components (see details here and the awesome Topcoder’s track).Click here for the “Lightning Components Developer’s Guide”, well written and clear.I’ve noticed a strange behavior regarding external javascript libraries loading.These are the requirements regarding external script loading:

  • You can only load external libraries got from a static resource
  • You cannot use the {!$Resource.resourceName} expression because we are not inside a VisualForce page, so you have to simply refer to “/resource/[resourceName]” in your <script> tags
  • From page 100: “If you want to use a library, such as jQuery, to access the DOM, use it in afterRender().

Apparently the last sentence is not true.The problem arose because I loaded jQuery + Bootstrap and sometimes (and randomly) the Bootstrap plugin did not load because of jQuery was not yet loaded: the cause was certanly the fact that libraries were not loaded sequentially!TLDR; click here for the solution!This is what I’m trying to do:

BlogScriptApp.app

<aura:application>
    <aura:handler name="init" value="{!this}" action="{!c.doInit}" />
    <aura:handler event="aura:doneRendering" action="{!c.doneRendering}"/>
    <script src="/resource/BlogScripts/jquery.min.js" ></script>    
    <div id="afterLoad">Old value</div>
</aura:application>

BlogScriptAppController.js

({
 doInit : function(component, event, helper) {
        try{
   $("#afterLoad").html("VALUE CHANGED!!!");
            console.log('doInit: Success');
        }catch(Ex){
            console.log('doInit: '+Ex);
        }
 },
    doneRendering : function(component, event, helper) {
        try{
   $("#afterLoad").html("VALUE CHANGED!!!");
            console.log('doneRendering: Success');
        }catch(Ex){
            console.log('doneRendering: '+Ex);
            
            setTimeout(function(){
                try{
                    $("#afterLoad").html("VALUE CHANGED!!!");
                    console.log('doneRendering-Timeout: Success');
                }catch(Ex){
                    console.log('doneRendering-Timeout: '+Ex);
                }
            }, 100);
        }
 }
})

BlogScriptAppRenderer.js

({
 afterRender : function(){
        this.superAfterRender();
  try{
   $("#afterLoad").html("VALUE CHANGED!!!");
            console.log('afterRender: Success');
        }catch(Ex){
            console.log('afterRender: '+Ex);
        }
  
    }
})

This is what I get in the console

doInit: ReferenceError: $ is not defined
afterRender: ReferenceError: $ is not defined
doneRendering: ReferenceError: $ is not defined
doneRendering-Timeout: Success 

This means that in the last app event (aura:doneRendering) we don’t have the libraries loaded, and that the only way to do it is to detach from the current execution and use the “setTimeout” method to call asynchronously the needed code.No surprise that this could not work if the jQuery library took too long to loadOne of the suggestions was to use RequireJS on the app, but the problem is the same: if the external scripts are not loaded, the “require” method does not exists and you cannot load its configuration.In this case RequireJS would allow to load in the correct order all the libraries (for instance jQuery, than bootstrap, then another lib …), like in this example:

BlogRequireJSApp.app

<aura:application>
    <aura:handler event="aura:doneRendering" action="{!c.doneRendering}"/>
    <script src="/resource/RequireJS" ></script>    
    <div id="afterLoad">Old value</div>
</aura:application>

BlogRequireJSAppController.js

({
 doneRendering : function(component, event, helper) {
        try{
            helper.loadRequire(component);
            console.log('doneRendering: Success');
        }catch(ex){
            console.log('doneRendering: '+ex);
            setTimeout(function(){
                try{
                    helper.loadRequire(component);
                    console.log('doneRendering-Timeout: Success');
                }catch(ex){
                 console.log('doneRendering-Timeout: '+ex);
                }
            }, 100);
        }
 }
})

BlogRequireJSAppHelper.js

({
    loadRequire : function(component) {
        require.config({
            paths: {
                "jquery": "/resource/BlogScripts/jquery.min.js?",
                "bootstrap": "/resource/BlogScripts/boostrap.min.js?"
            }
        });
        
        require(["jquery"], function($) {
            require(["bootstrap"], function(bootstrap, chartJS) {
                $("#afterLoad").html("VALUE CHANGED!!!");
            });
        });
    }
})

This is what I get in the console

doneRendering: ReferenceError: require is not defined
doneRendering-Timeout: Success 

This way you’ll have the scripts loaded in the correct order usign the RequireJS library: anyway if the RequireJS library is not yet loaded (it depends by your data connection) you’ll see another exception at the end of the log

Here comes the Salesforce community!

I posted a question on the SF developer forums and got a super cool response.From that response I came up with a simple solution that uses dynamic script loading and one event fired: this solution is a simpler reinterpretation of the forum’s one to make it easier to understand.

BlogRequireJSDinamic.app

<aura:application>
        <aura:handler event="forcelogic2:BlogRequireJSEvent" action="{!c.initJS}"/>
        <aura:registerEvent type="forcelogic2:BlogRequireJSEvent" name="requireJSEvent"/>
        <aura:handler name="init" value="{!this}" action="{!c.doInit}" />
        <div id="afterLoad">Old value</div>
    </aura:application>

BlogRequireJSDinamicController.app

({
    /*
        Sets up the RequireJS library (async load)
    */
    doInit : function(component, event, helper){
        
        if (typeof require !== "undefined") {
            var evt = $A.get("e.forcelogic2:BlogRequireJSEvent");
            evt.fire();
        } else {
            var head = document.getElementsByTagName('head')[0];
            var script = document.createElement('script');
            
            script.src = "/resource/RequireJS"; 
            script.type = 'text/javascript';
            script.key = "/resource/RequireJS"; 
            script.helper = this;
            script.id = "script_" + component.getGlobalId();
            var hlp = helper;
            script.onload = function scriptLoaded(){
                var evt = $A.get("e.forcelogic2:BlogRequireJSEvent");
                evt.fire();
            };
            head.appendChild(script);
        }
    },
    
    initJS : function(component, event, helper){
        require.config({
            paths: {
                "jquery": "/resource/BlogScripts/jquery.min.js?",
                "bootstrap": "/resource/BlogScripts/boostrap.min.js?"
            }
        });
        console.log("RequiresJS has been loaded? "+(require !== "undefined"));
        //loading libraries sequentially
        require(["jquery"], function($) {
            console.log("jQuery has been loaded? "+($ !== "undefined"));
            require(["bootstrap"], function(bootstrap) {
                console.log("bootstrap has been loaded? "+(bootstrap !== "undefined"));
                $("#afterLoad").html("VALUE CHANGED!!!");
            });//require end
        });//require end
    }
})

This is what I get in the console

RequiresJS has been loaded? true
jQuery has been loaded? true
bootstrap has been loaded? true 

What has happened?When the app loads (init event) the doInit function tries to understand if the RequireJS library has been loaded. If so fires a BlogRequireJSEvent event. If not yet loaded, dynamically creates a <script> tag with the path to RequireJS, attaching an onload handler, which in fact will inform that the library has been loaded with the same event.The same app is also an handler for the BlogRequireJSEvent event trhough the initJS function: it will load sequentially jQuery and Bootstrap libraries: this way you are pretty sure libraries will be loaded in the correct order.The next step in the solution given in the forums is to make a component that does all the work and fires an event that can be handled from anywhere in your app / components set.All the code above has been packaged into a GitHub repository.Enjoy!

Powered by WordPress & Theme by Anders Norén