Sunday 31 January 2016

Freezing Users with Lightning Components

Freezing Users with Lightning Components



Back in November 2013 I wrote a blog post about a new feature in the Winter 14 release of Salesforce - freezing users. At the time this was only available via the API, so I came up with a Visualforce solution that leveraged the Ajax Toolkit. This post presents the Lightning Components equivalent.

Screen Shot 2016 01 31 at 08 29 11

One Does Not Simply Call the API

Not from a Lightning Component, at least. The docs make this quite clear: 

"Make API calls from an Apex controller. You can’t make API calls from JavaScript code."

Luckily the sObject that encapsulates the frozen status of a user, UserLogin, is now available and updatable in Apex, so the Lightning Component will be responsible for allowing an admin to choose which users to freeze/unfreeze, while the actual database changes will be handled by its Apex controller.



The full code is available at the following Gists:

The Lightning Component

The key part of the Lightning Component is the iterator that outputs user information and a checkbox so that the user’s frozen status can be updated. This makes use of a wrapper class that encapsulates the User and UserLogin sObjects for a specific user : 

<aura:iteration items="{!v.UserDetails}" var="ud">
    <td id="{!ud.user.Id}">
      <ui:inputCheckbox change="{!c.frozenChanged}"
                        value="{!ud.userLogin.IsFrozen}" />

Note the input checkbox has a handler for when the value is changed - I found I couldn’t just bind the value from the underlying UserLogin field. I suspect this is because I am binding to a field from a complex object rather than directly to a primitive. This meant that I had to fire an event when the value changed, then figure out which user the checkbox was associated with. Ideally I’d have given the inputCheckboxComponent an aura Id that included the user id, but the aura Id for this component must be a string literal, so if I use something like aura:id=“{!’cb-‘ + ud.user.Id}”, then the aura id generated is exactly that - {!’cb-‘ + ud.user.Id}, which isn’t very useful.

Where I can use a dynamically generated id is in the containing element - the table cell in this case. My event handler that is invoked when the value is changed can then use the id of the parent element to determine the user id, and invert the current value of the IsFrozen field:

updateCheckbox : function(cmp, ev) {
    var value=ev.getSource().get('v.value');
    var userId=ev.getSource().getElement();
    var userDetails=cmp.get('v.UserDetails');
    for (var idx=0; idx<userDetails.length; idx++) {
        if (userDetails[idx].user.Id==userId) {
    cmp.set('v.UserDetails', userDetails);

The final line once again demonstrates the power of Lightning Components - by updating the component attribute containing the wrapper classes, the component rerenders the user information table with the latest details.

When the changes have been made on the client side, the collection of wrapper classes is then sent back to the server so that the UserLogin records can be updated. Even though I know there is a bug around sending an array as a parameter to the Apex controller, I still spent 15 minutes figuring out why doing exactly this threw an internal server error! Once I remembered I used the now familiar workaround, converting the collection of wrapper classes to a JSON string:

var userDetails=cmp.get('v.UserDetails');
var udAsJSON=JSON.stringify(userDetails);
var params={
          "toProcessAsJSON" : udAsJSON

 and turning this back into the wrapper class collection server side:

Type udArrType=Type.forName('List<UserFreezer.UserDetails>');
List<UserDetails> toProcess =
    (List<UserDetails>)JSON.deserialize(toProcessAsJSON, udArrType);

Related Posts


1 comment:

  1. Hi Bob,

    For working with lists of items in lightning components, I like to use the indexVar attribute of aura:iteration.

    <aura:iteration items="{!v.myList}" indexVar="idx" var="myItem"\>
      <c:myCmp item="{!myItem}" index="{!idx}"/>

    Whenever myCmp is changed, the event that component fires includes its index. Then the parent component handles updating the value. I haven't found a major downside to this method yet.