My First Lightning Component on Utility Bar via Eclipse

Note: I wrote this post in October 2016 but didn’t publish it because I was getting issues trying to making Eclipse work. However, after few months and do not find a solution, I decided to publish what I wrote. Maybe first steps could help others. An if you have the answer to my issue, I would really appreciate your help.

In addition I have included a super simple Lightning Component and how to integrate in the new Utility Bar that Spring ’17 bring to us.

When you develop some code, which is the tool you use for it? If you have never created some complex line of code, maybe you think that Developer Console allows you to do what you need. However, if your daily basis is programming, you need a tool outside of the org for that.

Nowadays I use Mavensmate, but on my early days I was an Eclipse fan, so when I knew that there is a beta version of Force.com IDE in order to create Lightning Components, I decided I had to check it. And this post is about it. Well, more about how to install it, as well as create a super simple Lightning Component.

First of all, I tried to open my Eclipse and create a project againts my org. But I got this error:

captura-de-pantalla-2016-10-08-a-las-12-18-48

Oh my God!! It’s true that long time ago since I don’t open Eclipse (Juno in my case) !!! So first of all, fix it. How? Here there is a link that explain you about this Salesforce change, but here is what I did on my Mac.

  1. Run java -version on cmd — old version as well !! So via System Preferences, click on Java icon and updated the version to 8 one there. And also download latest JDK.

Now java -version returned to me 8 one. So I tried again and this time it worked.

However I tried to install Force.com IDE for lightning and I got an error, so checked more instructions and I found this article and I realized that my version was older than 4.5, so decided to download a newer version. If this is not your case, jump next section

How to install eclipse

From here you can download latest Eclipse version (be sure you get it for Mac or PC depending on your machine OS). Well, actually you download an installer. When I executed it, I got an error message, so after looking at several blogs, like this one, I realized that I had to update the installer via the top right coner icon.

captura-de-pantalla-2016-11-11-a-las-14-20-53

Then just select Eclipse IDE for Java Developers and click on install. Again I got an error with Neon, so I decided to try with another Product which version is higher than 4.5, so I chose Mars

Captura de pantalla 2016-11-22 a las 17.16.09.png

How to install Force.com IDE

The installation is quite straight forward, you just need to follow below steps (find also the information here)

  1. In Eclipse, click Help > Install New Software.
  2. Add this new update site: https://developer.salesforce.com/media/force-ide/beta_eclipse45
  3. Be sure that Group items by category checkbox is not selected.
  4. Select the checkbox next to Force.com IDE (required).
  5. If you have Apex Debugger licenses, select the checkbox next to Force.com IDE Debugger.
  6. Select the checkbox next to Force.com Lightning Support
  7. Click Next and start the installation.

20170203 Note There is new link to Eclipse IDE because it is no beta anymore as well as you can also find more information on Salesforce page as well. I tried with them as well, but no luck, still does not work.

How to work with Eclipse

Now you can create the project as usual, but you will realize that the folder structure looks different. It also have the aura folder. If we want to add / create the aura element we will find the entry as well.

How to create a Lightning Component

As you can find lot of information related to Lightning Components creation (including Trailheads) I will not explain in deep what it is a Lightning Component and how to create it. Just show you how you can develop something super simple in Eclipse.

Use Case – Employee List

I have a custom object, Employee, in my organization

Captura de pantalla 2016-11-23 a las 10.49.10.png

And I would like to create a Lightning Component that list all my Employees and be able to insert on the Home tab, for instance.

20170203 Note: I was not able to make Eclipse work, so this code has been developed in Salesforce Developer Console. Do you want me to help anyhow and get some credits? Find my question on stackExchange as well as Developer Forums

Super simple Employee List Lightning component

I will create 2 component.

The first one will just show the FirstName and Surname of an Employee

<!-- EmployeeListItem -->
<aura:component>
    <aura:attribute name="employee" type="Employee__c"/>
    <li>{!v.employee.FirstName__c}{!v.employee.Surname__c}</li>
</aura:component>

And the second one will iterate over a list of Employees and make calls to the above component in order to show the information.

<!-- EmployeeList -->
<aura:component controller="EmployeeController" 
                implements="flexipage:availableForAllPageTypes">
    <aura:attribute name="employees" type="Employee__c[]"/>
    <aura:handler name="init" value="{!this}" action="{!c.doInit}" />
    <h3>Employees</h3>
    <aura:iteration items="{!v.employees}" var="empl">
       <c:EmployeeListItem employee="{!empl}"/>
    </aura:iteration>
</aura:component>

But for that, it needs to retrieve the information somehow. That’s why we have the reference to the server side controller EmployeeController.

/**
* @agarciaodeian
**/

public with sharing class EmployeeController
{
    @AuraEnabled
    public static List<Employee__c> getEmployees()
    {
        List<Employee__c> listRetrieved = new List<Employee__c>();        
        if(Schema.sObjectType.Employee__c.isAccessible())
        {
               listRetrieved = [SELECT Id, Name, FirstName__c, 
                                       Role__c, Surname__c 
                                FROM Employee__c];
        }
        return listRetrieved;
    }
}

Finally, in order to get the result, in the EmployeeList we execute the action that initialize the list of employees making a call to the JavaScript controller

({
    doInit : function(component, event) {
       var action = component.get("c.getEmployees");
       action.setCallback(this, function(a) {
          component.set("v.employees", a.getReturnValue());
        });
        $A.enqueueAction(action);
    }
})

Once I have the code in the organization, I can create a project on Eclipse and I would see my components under the aura folder

captura-de-pantalla-2017-02-04-a-las-11-42-36

If I open EmployeeList, for instance, I can see also the EmployeeListController.js tab as well. So it is easier and it is sorter than in the Developer Console.

How to add my Lightning Component to the Utility Bar

captura-de-pantalla-2017-02-04-a-las-11-57-11

 

 

 

Once I have the Lighting Component, I can show the result in the Home page tab for instance

 

 

 

And now that Spring ’17 is arriving, we can include also our lighting components into the Utility Bar. Previously we had to create it via metadata and deploy but now it’s a matter of point and clicks.

Go to App Manager and edit your app. Now, one of its steps is the Utility Bar  and after clicking on Add button you can find same Lightning Components than in the App Builder.

captura-de-pantalla-2017-02-04-a-las-15-32-54

For instance, standard like Flow (Beta), Manage, and Custom. So I select my custom lighting Component Employee List.

And … that’s all!! Now I have my super simple LC in the Utility Bar, so that I can see the list of all Employees from everywhere, including Salesforce Setup page.

captura-de-pantalla-2017-02-04-a-las-15-36-51

Lightning Experience and Visual Flow

Today I’m going to do some researches with one of these #clicksnotcode features that Salesforce provides in the Platform, Visual Flow. Ok, this is not a hot topic now, but if we want to check how it works in Lightning Experience (LEX from now onwards) then, this post could be a bit more interesting for you.

Take into account that Visual Flow in LEX is in beta

The use case it’s simple. I have a Hotel, and I would like to get some feedback from my guests, so I will send them a survey.

The Flow would be very simple, and as the creation is not the main goal here, I will not show it step by step. But if you are new, double check Visual Flow documentation and also TrailHead.

First of all we need to open Flow Designer, so, can I do it in LEX? Yes. Go to Setup and just write “flow” in order to find it and be able to open this platform feature.

captura-de-pantalla-2016-12-02-a-las-10-35-11

As you can see on below image, the Flow Designer looks like the Classic UI one, so you would not need to learn how to work with it if you already used it.

captura-de-pantalla-2016-12-02-a-las-12-08-29

Once I have created my Flow, we can add it to our Home page, but remember to Activate it, otherwise it would not be available in Lightning App Builder.

So, go to Home, Edit this page, and Lightning App Builder will help you to add it there.

How? Really simple, if you go to Standard Apps, one of them is Flow (beta) so you only need to click on it, and your flow will appear in the home page.

captura-de-pantalla-2016-12-02-a-las-10-57-11

But actually I have 2 active flows, so what can I do? When you highlight your custom Flow, on the right side you have the option to select the one you need to show, so just pick one.

captura-de-pantalla-2016-12-02-a-las-11-12-38

I can also say that I had to try a couple of times till I got this behavior. First time, Book Room flow did not appear on the drop down list, so there were not way to add it to my Home page. But remember, this feature is on beta, so be patience and try till you get it.

Last thing to mention is the fact that you can chose between 1 or 2 columns. My first thought was that this attribute would help me to determine if I wanted 2 flows on the same row on the page layout, but it is not related to that. It just help you to set all your flow fields in 2 columns instead of one. For instance, these 2 screens show same fields but they are sorted in a different way.

captura-de-pantalla-2016-12-02-a-las-12-11-41captura-de-pantalla-2016-12-02-a-las-12-11-08

And how it works? In Spanish we say better a image rather than thousand words but in this case, better a video rather than thousand images.

 

Maybe your last question is if we can add it to any other place? Yes, for instance, I can include it in my Guest record layout

captura-de-pantalla-2016-12-02-a-las-13-06-23

But let’s make up a little bit and define something for every guest:

captura-de-pantalla-2016-12-04-a-las-20-20-05

In order to do that, I had to add a Lookup step in the Flow and use the variable {!recordId} as a filter. So that, I can retrieve Guest object record and use it in my Flow.

captura-de-pantalla-2016-12-04-a-las-20-26-44

It was not easy to find the way to retrieve the recordId but this post helped me a lot.

I hope you liked this short entry about Flow. You can find more information on Salesforce release notes entry.

 

 

 

Maps and Visualforce

Since I was checking Force.com documentation in order to pass Platform Developer Transition exam I had the opportunity to learn more about some topics that I don’t usually use on my daily job.

One of those are Maps, and I’m not talking about Collections (Lists, Sets and Maps). I would like to talk about locations on this post.

Till some releases ago, the only way to add a location into your visualforce page, was via JavaScript. Let’s see an example. (Bellow use case is based on this Salesforce Trailhead)

I’m working on a healthcare app, and we would like to show a map on every doctor’s record, so that, the customer would be able to find the location easily.

In order to get it, I will create a visualforce page with the map and include it in the standard layout.

JavaScript Map Version

First thing is to add the listener:

 google.maps.event.addDomListener(window, 'load', initialize);

that call an initialize function

 var map;
 
 function initialize() {
     var mapOptions = {
         center: new google.maps.LatLng(43.2616465, -2.9393368),
         zoom: 15
     };
     map = new google.maps.Map(document.getElementById("map-canvas"), 
                               mapOptions);
     loadDoctor();
 }

Where we define map properties, like latitude, longitude and zoom.

But this function call another, loadDoctor 

 function loadDoctor()
 {
     Visualforce.remoting.Manager.invokeAction(
         '{!$RemoteAction.DoctorsController.findTheDoctor}',
         '{!Id}',
         function(result, event)
         {
             if (event.status) {
                 var name = result.SurgeryName__c;
                 var lat = result.Location__Latitude__s;
                 var lng = result.Location__Longitude__s;
                 addMarker(name, lat, lng);
             } else {
                 alert(event.message);
             }
        },
        {escape: true}
     );
 }

Above function uses a RemoteAction method, so we know that a Controller is included on this page. This communication with the controller just returns information about the Doctor’s records like his place name and the Location. Maybe you are wondering what below line means.

Location__Latitude__s  and  Location__Longitude__s

Location is a Geolocation field, and, in the end, it is a Compound field, so in order to see the information, we need to decouple into its “elements”, Latitude and Longitude.

Finally, on above method, we have a call to another function that will add the marker on the google map

 function addMarker(name, lat, lng)
 {
     var marker = new google.maps.Marker
           ({
               position: new google.maps.LatLng(lat, lng),
               map: map,
               title: name
           });
 }

With this code, the result is this one:

Captura de pantalla 2016-07-30 a las 22.17.58

Apex Map Version

Is there a different way to get the same result? Yes !! Since Spring ’15 we have a new tag available on visualforce pages: apex:map and with this piece of code:

<apex:map width="100%" height="100%" mapType="roadmap" zoomLevel="15"
          center="43.2616465, -2.9393368">
    <apex:mapMarker title="{!Doctor__c.SurgeryName__c}" 
                    position="{!Doctor__c.Location__Latitude__s},
                              {!Doctor__c.Location__Longitude__s}">
    </apex:mapMarker>
</apex:map>

we can get exactly what we had on the javascript code as we can see on below image that compares both maps. Left side uses apex:maps whereas on the right side the map is created with javascripts.

Captura de pantalla 2016-07-30 a las 22.24.42

More about apex:map

Attributes

  • Height and Width could be defined as pixels or percentage
  • Center could be used with Location data, Address and also with JSON format. From my point of view, it would be easier to create a method in the controller with the data
public Map<String,Double> getDoctorsCenter()
{
    Map<String,Double> mapCenter = new Map<String,Double>
                                   {
                                      'latitude' => 43.2616465,
                                      'longitude' => -2.9393368
                                    };
    return mapCenter;
}

and use the method in the visualforce page.

<apex:map width="100%" height="100%" mapType="roadmap" 
          zoomLevel="15"
          center="{!doctorsCenter}">

Above example set geolocation with hardcode but if we have this information on data base, the code could be safer and don’t fail if there is any change on this data.

MapMarker

  • Similar as before, position attribute could be defined based on Address, JSON or using the Geolocation field. On above example, we directly used the compound sub-fields, but it would be also fine to get this information in the controller, create the map and call the method in the page as we show on center attribute.
  • If your idea is to show all doctors around your city, instead of adding an mapMarker per record, remember to use apex:repeat tag
<apex:map width="100%" height="100%" mapType="roadmap" zoomLevel="15"
          center="43.2616465, -2.9393368">
    <apex:repeat value="{!locations}" var="pos">
        <apex:mapMarker position="{!pos}"/>
    </apex:repeat>
</apex:map>
  • Another mapMarker attribute is icon that help us to show a different image than the red bubble that google offer to us. A good way to show it is using a static resource and make a call to it.
<apex:mapMarker title="{!Doctor__c.SurgeryName__c}" 
                position="{!Doctor__c.Location__Latitude__s},
                          {!Doctor__c.Location__Longitude__s}"
                icon="{!URLFOR($Resource.MapMarkers, 'myIcon.png')">
</apex:mapMarker>

MapInfoWindows

  • This is another tag to show even more information. So for instance, below code show the doctor’s name and phone if we click on the marker icon
<apex:map width="100%" height="100%" mapType="roadmap" zoomLevel="15"
          center="43.2616465, -2.9393368">
    <apex:mapMarker title="{!Doctor__c.SurgeryName__c}" 
                    position="{!Doctor__c.Location__Latitude__s},
                              {!Doctor__c.Location__Longitude__s}">
        <apex:mapInfoWindow >
            <apex:outputPanel layout="block" style="font-weight: bold;">
                <apex:outputText >{!Doctor__c.SurgeryName__c}
                </apex:outputText>
            </apex:outputPanel>
            <apex:outputPanel>
                <apex:outputText >{!Doctor__c.Phone__c}
                </apex:outputText>
            </apex:outputPanel>
        </apex:mapInfoWindow>
    </apex:mapMarker>
 </apex:map>

 

Captura de pantalla 2016-08-02 a las 17.58.24.png

JavaScript vs apex:map

  1. Unfortunately, we cannot create a page with map tag in a Developer Edition, however JavaScript is available on Developer editions. But if you want to give it a try, create your own Trial edition.
  2. As JavaScript is executed on the client side, the map is loaded quicker than the one that use apex:map.
  3. On the other side, Salesforce takes care of any apex tag so any change on code versions, or browser, etc. that could make the code fail, Salesforce will deal with it.
  4. JavaScripts needs a method in the controller in order to get Location information dynamically and if we want to show the map inside a Standard layout, we must define the RemoteAction method as global that makes you define the class as global as well. This is not important till you add the code inside a package. As a reminder, global classes are available to everybody that install your package and maybe this is not your desire.
  5. RemoteAction is not able to get parameters from url. So I cannot do something like
Id currentDoctorId = controller.getId()

and then use it on the RemoteAction method. However,  in our case, we can use directly the Id of the record on the visualforce page as a workaround:

Visualforce.remoting.Manager.invokeAction(
         '{!$RemoteAction.DoctorsController.findTheDoctor}',
         '{!Id}',
         function(result, event){....});

Resources

  1. Apex:map Salesforce documentation
  2. Github Repository with code used on above examples

Mi primera Visualforce IV – Controladores II

¡Cuánto tiempo! Pues si, en Enero fue mi último post en Español y 11  meses después vuelvo a escribir una entrada en nuestra lengua materna. Y es que últimamente he estado un pelín ocupada con mi bebe de 7 meses.

Esta será la última entrada con el título Mi primera Visualforce ya que terminaremos de cubrir conocimientos básicos. Pero no significa que no seguiré escribiendo en Español. Seguid atentos porque publicaré cosas.

¡Pero vamos al lío! Todos los objetos tienen asociados un controlador estándar que le proporciona funcionalidad (Échale un vistazo a esta entrada). Además, podemos crear controladores personalizados para extender y dar más potencia a nuestra página.

Además los controladores pueden tener un único elemento como argumento, como expliqué en mi última entrada, ó a una lista de registros.

Controlador asociado a varios registros

 

Controlador Estándar

Como recordarás, para usar el controlador estándar, sólo tienes que usar el atributo standardController del tag apex:page

<apex:page standardController="Account">
</apex:page>

Entonces, ¿cuál es la diferencia? Como supondrás, necesitaré una variable para poder almacenar todas las cuentas, y después iterar sobre ella. Para ello, apex:page ofrece otro atributo recordsetVar cuyo valor será la lista de registros. Una vez que la tengamos, sólo tenemos que mostrar los campos que deseemos de nuestra cuenta. En el código de más abajo, usaremos el tag apex:repeat aunque podríamos haber usado también apex:datatable como en posts anteriores.

<apex:page standardController="Account" recordsetVar="records">
   <apex:form >
      <apex:pageBlock title="Listado de Cuentas">
         <apex:pageBlockSection columns="1">
            <apex:repeat value="{!records}" var="acc">
               <apex:outputField value="{!acc.Name}"/>
            </apex:repeat>
         </apex:pageBlockSection>
      </apex:pageBlock>
   </apex:form>
</apex:page>

Siendo este el resultado

Captura de pantalla 2016-11-07 a las 18.52.19.png

Controlador Personalizado

¿Y cómo puedo conseguir lo mismo pero con un controlador creador por mi mismo? Sólo tienes que seguir estos pasos:

Crear un Controlador en Apex

Una clase en Apex sencilla. Pero ten en cuenta lo que explicamos en el último post sobre nomenclatura, sharings, etc. Lo único que debemos tener en cuenta es el constructor. Esta vez, en vez de utilizar ApexPages.StandardController, usaremos la clase ApexPages.StandardSetController que nos permitirá recuperar la lista de registros seleccionados (por ejemplo) con el método getRecords de forma similar que el controlador de único registro nos ofrecía el método getRecord. Pero además, en este caso, tenemos métodos para facilitar la paginación cómo getHasNext ó getHasPrevious.

public with sharing class AccountListController
{
   public AccountListController(ApexPages.StandardSetController controller)
   {
      //TODO añadir código
   }
}

Añadir método para recuperar la lista de cuentas

Muy sencillo. Añadimos la variable registroCuentas con sus métodos get y set. Por otro lado, añadimos un método que recuperará las listas que tenemos en base de datos por medio de una SOQL calcularCuentas. Y por último, en el constructor, rellenamos la variable privada con la información.

public with sharing class AccountListController
{
   private List<Account> registroCuentas;
 
   public AccountListController(ApexPages.StandardSetController controller)
   {
      setRegistroCuentas(calcularCuentas());
   }
 
   public List<Account> calcularCuentas()
   {
      try
      {
         return [Select Id, Name From Account];
      }
      catch(Exception ex)
      {
         ApexPages.addMessages(ex);
         return null;
      }
   }
 
   public List<Account> getRegistroCuentas()
   {
      return registroCuentas;
   }
 
   public void setRegistroCuentas(List<Account> value)
   {
      registroCuentas = value;
   }
}

Modificar la página para usar el Controlador

En este último paso sólo tenemos que añadir el atributo extensions cuyo valor es el nombre del controlador personalizado que hemos creado. Además, debemos usar nuestro método como valor en el atributo recordSetVar.

<apex:page standardController="Account" 
           extensions="AccountListController" 
           recordsetVar="registroCuentas">
   <apex:form >
      <apex:pageBlock title="Listado de Cuentas Personalizado">
         <apex:pageBlockSection columns="1">
            <apex:repeat value="{!registroCuentas}" var="acc">
               <apex:outputField value="{!acc.Name}"/>
            </apex:repeat>
         </apex:pageBlockSection>
      </apex:pageBlock>
   </apex:form>
</apex:page>

captura-de-pantalla-2016-11-07-a-las-19-23-30

Como puedes ver, el look & feel de la página es igual que la página que usa el controlador estándar.

A partir de ahí, eres tu quien pone los límites en una página.

Alternativa

Por último, si nuestro caso de uso es simplemente mostrar la lista de Cuentas, pero no nos piden nada más (por ejemplo mostrar sólo unos campos), podemos usar el tag apex:enhancedList y no reinventar la rueda. Con esta línea de código, podemos mostrar todas las cuentas de nuestra organización junto con sus enlaces de editar, borrar, seleccionar, etc y sin ningún esfuerzo.

<apex:page >
   <apex:enhancedList type="Account" 
                      height="300" 
                      rowsPerPage="10" 
                      id="AccountList" />
</apex:page>

 

captura-de-pantalla-2016-11-08-a-las-17-45-17

Esta ha sido una entrada sencilla en la que hemos aprendido

  • Mostrar una lista de Cuentas por medio del controlador Standard
  • Mostrar una lista de Cuentas via un controlador personalizado
  • Mostrar una lista de Cuentas usando el tag apex:enhancedList 

Nos vemos en el próximo post.

Dreamforce ’16 – What I did not tweet

One of the first things I did after coming back from my maternity leave, was to attend Dreamforce ’16. And this post is about this amazing event. However I would like to write something different from others. First of all, don’t expect a technical entry where I praise all Salesforce news. They were really good this year, but DF is more than a technical event. It’s networking, it’s enjoy the place, the sun. It’s comeback with ideas to explore at home and keep feelings of these sort time.

So, what are you going to read here? My experience but with small sentences. Although my smartphone was ready to write tweets, I did not have time for it. It was write or listen, so I decide to pay attention and make pics.

So, What I did not tweet?

Day 1 – 4th of October

06:00 – Start my #DF16 trip. @Bilbao airport. Long queue for checking. Will I miss my flight? It’s a bit delayed … so far so good.

09:30 – At Schiphol airport. Close to lose my connection to San Francisco!! So lets do some exercise, run along Amsterdam airport!! I get it. Sit down and #relax for the next 11 hours.

11:45 – #Border has no end 😦  But I’m on time for #FinancialForce DevTalks. Starting to feel tired.

13:00 – Where is my luggage? This suitcase is similar to mine but it is not mine!! Sorry Mrs García, not sure where your belongings are 😦 😦 😦 #luggagelost #noclotheswithme

14:00 – Arrive to the hotel with a new San Francisco T-shirt bought at the airport and looking forward to having a shower. Sorry Mrs García, your room is not available yet. You would have to wait. What?? 😦 😦 😦 😦 😦 😦 #anythingelse?

img_4929

 

 

14:30 – Dreamforce registration. Here is my credential!!

 

 

 

 

 

17:00 (2:00 in Spain) – FinancialForce Dev Talks’16 starts!! Nice to share the stage with Andy Fawcett and Jesse Atlman

img_1256

19:00 – Time for #MVP dinner … I cannot do it. Tired and need to buy some clothes for tomorrow. But I think that I missed my #amazingbbq 🙂

Day 2 – 5th of October

img_4808

 

7:00 – Nice views from my room #lovesunnydays

 

11:00 – @Salesforce Circle of Success session. Success: arrive on time – lost in the hotel. No session success … 1 attendee in a table with 5 MVPs. Poor him lot of questions about what he needed. Should it be in the other way around? @Salesforce, #2far2lazy2go.

 

 

12:30 – Searching my #MVP seat @Salesforce Keynote. I’m so exciting!! This year I’m not on the last row of the room (literally last one).

13:00 – @Salesforce Keynote starts !! Don’t miss it.

13:15 – @Will.I.am and WHERESTHELOVE #ovation

img_4807

14:00 – Einstein makes your app smarter. On my #todo list for home.

14:30 – @Red moment. It’s not charity, it’s justice. Where you have born shouldn’t determine how long is your life.

14:45 – Never been so close to Mark Benioff #FanMoment 😀

 

 

 

17:00 – Moscone West has changed a lot!! New location for #Trailhead area and Theaters #newworld #admin #developers

img_4853

20:30 – Salesforce Gala starts with U2 “Uno, dos, tres, catorce!!”

img_4819

Day 3 – 6th of October

07:30 – Starting the day with energy @Mel’sDinner and my @FinancialForce colleagues

img_4832

08:30 to 17:00 – @SalesforceDevelopers day. #Heroku #Gamification #RESTAPI #SOAPAPI #NewPlatformEventObject #BigObjects and #Developer KeyNote with Jesse and David Frudd

img_4848

 

20:00 – A tired day ends with #clamchowder

 

 

 

 

Day 4 – 7th of October

9:00 – Time to come back @home but before taking, pic of Moscone area and Salesforce arch. Looking forward to coming back again. #DF17 the 6th of November.

Platform Developer Certification – Transition Exam

As a developer, I wanted to learn as much as possible and a good way to do it is getting any of the certifications that Salesforce offers to us.

I was able to get 401 and 501 credentials, so I decided to share some tips about them writing an entry some months ago.

However a year ago, Salesforce decided to modify certifications and 401 and 501 were not available anymore.

So what could I do? Do I lose my credentials? No at all. I still can say that I got these certifications. However Salesforce also advices to us to move to the new one.

In order to do it we have different paths as you can see on below image:

Captura de pantalla 2016-07-12 a las 15.09.09

I decided to go for the combo Transition Exam, but lot of questions came to my mind. So let me share all answers I got. Maybe they can also help you.

Q – How is the Exam?

A – This is a test exam. We have 30 minutes to answer 16 multi-choice questions, and we need to get 63% (11 questions) right to pass the exam. Per my experience, don’t waste the time. Read carefully and if you are not completely sure, mark to review it later and move to the next one.

Q – When do I get the results?

A – After submitting the exam. As usual, we would get the Pass or Fail response. Unfortunately we don’t get the score. You can only see if you passed it or not.

Q – Is this a proctored exam?

A – Yes !! So take care and prepare your place for the exam in advance.

Q – What can I do if I fail the exam?

A – Good questions!! That also worried to me. This is a free exam and you can try to pass it as many times as you need. The only thing that you need to take into account is that you cannot try it more than 3 times per release. This mean. Salesforce deliver 3 releases a year, so you have 9 opportunities to do it per year.

Q – What should I study to pass it? 

A – There is no guide to know what you need. To be honest, you should get Platform Developer I and II guides and check what it is required for them and study the same. I reviewed all the material that I used to pass 501 exam. And in addition, I checked:

Obviously, Trailheads are also very useful to remember certain contents for example Life cycle one.

Q – Should I look for any other material?

A – Something I didn’t find on above documents and I was asked for was about:

  • Compounds fields
  • Certifications, kind of certifications depending on device …
  • Streaming API

Actually the exam was a challenged and that also helped me to learn even more.

Q – What kind of questions could I find?

A – This test exam provided different options and you need to select one or many responses.

An example could be (This is not a question of my exam. This is an example I have created based on what I found during the exam)

Having this piece of code:

@RestResource(urlMapping='/user_defined_type_example/*')
global with sharing class MyOwnTypeRestResource 
{
   @HttpPost
   global static MyUserDefinedClass echoMyType(MyUserDefinedClass ic)
   {
      return ic;
   }

   global class MyUserDefinedClass
   {
      global String string1;
      global String string2 { get; set; }
      private String privateString;
      global transient String transientString;
      global String staticString;
   }
}

And these JSON calls:

1. 
{
   "ic" : {
             "string1" : "value for string1",
             "string2" : "value for string2",
             "privateString" : "value for privateString"
          }
}

2.
{
   "ic" : {
             "string1" : "value for string1",
             "string2" : "value for string2",
          }
}

3.
{
   "ic" : {
             "string1" : "value for string1",
             "string2" : "value for string2",
             "transientString" : "value for transientString",
             "staticString" : "value for staticString"
           }
}

Which is the correct call?
a) 1
b) 2
c) 3
d) 1 and 3
e) 1 and 2
f) 1, 2 and 3

Q – Once I pass the exam. Do I have to do anything else? Assignment or any other test?

A – No !! Since that moment you get your new credentials and you only need to do the maintenance exam as usual 3 times a year.

Q – I have Force.com Developer (401) certification. Does the maintenance exam cover also that one?

A – I’m afraid that Platform Developers maintenance don’t cover 401 certifications, so you would have to do 2 exams per release. But nothing to worry too much.

Do you have any other question? You can open a case here.

Once you get it you can also check your certification in the verification page. Something that I will miss from this page is my Advance Developer logo.

Captura de pantalla 2016-07-12 a las 15.03.17

By the way, the correct answer on above question is option e).

Mi primera Visualforce III – Controladores

En este post vamos a continuar desde dónde lo dejamos. En la última entrada en Español hablábamos de cómo crear una página visualforce utilizando únicamente un Controlador Standard. Si todavía no has tenido la oportunidad de leerla sería interesante que lo hicieras antes de continuar. Aquí tienes el link.

Controlador Personalizado

Hasta ahora sólo hemos usado el controlador Standard asociado al objeto que usábamos en la página, Account. Y con él hemos conseguido hacer muchas cosas. Entonces, ¿por qué necesito crear uno? Con este tipo de controladores vamos a extender la funcionalidad, de forma que nuestra página puede llegar a ser más potente si cabe.

Tenemos dos tipos de controladores personalizados:

  • Asociados a una lista de registros de un objeto
  • Asociados a un único registro de un objeto

Dejaremos la lista de registros para un blog futuro y hoy vamos a centrarnos en qué hacer con un único registro.

Único Registro

Estos controladores se usarán cuando quiero realizar una acción asociada a un registro en concreto.

Para empezar sólo tengo que crear una clase Apex

Setup | Develop | Apex Classes

Y hacer click en el botón New.

A continuación sólo tienes que escribir estas líneas de código y guardar:

public with sharing class AccountController
{
    public AccountController(ApexPages.StandardController controller){}
}

Antes de continuar ¿qué podemos destacar del código anterior?

  1. Una clase puede ser:
    1. private: la clase es sólo conocida de forma local, es decir por el código de esa clase.
    2. public: la clase es visible en toda la aplicación.
    3. global: la clase es visible por cualquier código Apex en cualquier sitio, es decir, si empaquetamos la clase, ésta será visible y accesible para cualquier usuario que haya instalado el paquete. Mi recomendación por ahora es que tu clase sea pública de forma que así también puedes proteger la propiedad intelectual de tu clase si decides comercializarla en un futuro.
  2. With Sharing nos permite indicar permisos de acceso. Échale un vistazo a este blog en inglés de Andy Fawcett dónde explica el significado de estás palabras
  3. Palabra Controller en el nombre de la clase. Es una opción personal para saber que esa clase es un Controlador, pero si tu prefieres darle otro nombre, ¡adelante! sólo una recomendación. Mantén una misma convención en cuanto a nombres se refiere a lo largo de todo tu desarrollo, así que si creas otro controlador, dale un nombre similar
  4. Un constructor con un único argumento del tipo ApexPages.StandardController que proporciona una serie de métodos para poder recoger información del registro asociado a ese controlador

Una vez que tenemos el controller creado, vamos a asociarlo a una página. Por ejemplo, vamos a extender la funcionalidad de la página accountedit que creamos en el post anterior. Para ello sólo tenemos que añadir el atributo extensions a la definición de la página.

<apex:page standardController="Account" extensions="AccountController">
</apex:page> 

Y ahora, ¿tenemos que hacer algún otro cambio? No en principio podemos mantener el mismo código en la página aunque lo asociemos a un controlador custom.


<!-- accountedit -->
<apex:page standardController="Account" extensions="AccountController">
  <apex:form >
    <apex:pageBlock title="Vista Cuenta">
      <apex:pageBlockButtons >
        <apex:commandButton action="{!save}" value="Save"/>
      </apex:pageBlockButtons>
      <apex:pageBlockSection title="Campos Cuenta">
        <apex:inputField value="{!account.name}"/>
        <apex:inputField value="{!account.site}"/>
        <apex:inputField value="{!account.type}"/>
        <apex:inputField value="{!account.accountNumber}"/>
      </apex:pageBlockSection>
    </apex:pageBlock>
  </apex:form>
</apex:page>

Como ya aprendimos en el post anterior, si sobre escribimos el botón Edit del objeto Cuenta para mostrar nuestra página, el resultado será el siguiente:

Captura de pantalla 2016-01-18 a las 16.31.57

Entonces, ¿cual es la diferencia?

Por ahora ninguna.

Pero vamos a seguir añadiendo funcionalidad. En la página view del anterior post, mostrábamos además una lista relacionada con las Oportunidades asociadas a la cuenta seleccionada. Vamos a hacer lo mismo en nuestra página pero esta vez vamos a usar el tag  apex:BlockTable.  Este tag tiene como atributo value que usaremos para llamar al método que nos devuelve la lista de registros sobre la que vamos a iterar en la tabla. Y el atributo var que será la variable que usaremos en cada iteración.

<apex:pageBlockTable title="Opportunities" value="{!opportunityList}" var="opp">
</apex:pageBlockTable>

¿Y qué es opportunityList? Ahí es cuando entra en acción nuestro nuevo controlador dónde tendremos un método que via una consulta a base de datos (en Salesforce se llaman SOQL) obtendremos la lista de Oportunidades asociadas a la cuenta Account1.

Una vez que tenga esta información, usaremos los métodos get / set que al final serán los que el atributo value llamará.


public with sharing class AccountController
{
    private Id m_accountId;
    private List<Opportunity> m_opportunities;
    private ApexPages.StandardController m_controller;

    public AccountController(ApexPages.StandardController controller)
    {
        m_controller = controller;
        m_accountId = m_controller.getRecord().Id;
        m_opportunities = calculateOpportunities(m_accountId);
    }
   
    public List<Opportunity> getOpportunityList()
    {
        return m_opportunities;
    }

    public void setOpportunityList(List<Opportunity> value)
    {
        m_opportunities = value;
    }

    private List<Opportunity> calculateOpportunities(Id accId)
    {
        //Remember Separation of Concerns
        //Move to a Selector class
        List<Opportunity> oppList = [Select Id, Name From Opportunity Where AccountId = :accId];

        return oppList;
    }
}

Cómo hemos mencionado antes del código, tenemos una variable privada m_opportunities que será utilizada por el get / set. Y si te fijas, estos métodos tienen el nombre que hemos usado en nuestra página visualforce.

Además, tenemos un método calculateOpportunities donde vamos a hacer la consulta a base de datos, la SOQL. Recuerda que aunque en este ejemplo estoy haciendo una SOQL en un controlador, debemos mantener el SOC. Para más información echa un vistazo aquí.

Y este método será llamado en el constructor del controlador y lo usaremos para modificar la variable privada m_opportunities de forma que una vez que se llame al controlador, la lista de Oportunidades se rellenará automáticamente.

Otra cosa a destacar, es el filtro de nuestra consulta. Buscamos las Oportunidades cuyo campo Account tenga el mismo valor que la cuenta que tengo en mi página. Si recuerdas, ya hemos comentado que StandardController ofrece muchos métodos, entre ellos getRecord() que nos devolverá el registro que estamos visualizando en el momento de llamar a nuestro controlador.

El último paso antes de visualizar el resultado, es añadir alguna columna a nuestra tabla. Por ahora sólo añadiremos el Nombre de la oportunidad.


<apex:pageBlock title="Related Lists">
  <apex:pageBlockTable title="Opportunities" value="{!opportunityList}" var="opp" >
    <apex:column headerValue="Name">
      <apex:outputText value="{!opp.Name}"/>
    </apex:column>
  </apex:pageBlockTable>
</apex:pageBlock>

Sabiendo todo esto, ¿cual es el resultado?

Captura de pantalla 2016-01-21 a las 19.51.57.png

¿Pero y si queremos añadir un campo que no está en el objeto Oportunidad y mostrarlo en la tabla? Por ejemplo, me gustaría visualizar un checkbox que me indicara si la Oportunidad tiene estado Closed Won.

Al ser un campo calculado, la lista que devolveremos no será directamente del tipo Oportunidad. Lo que vamos a hacer es crearnos una clase interna con dos variables, por un lado el registro de la oportunidad, y por otro este nuevo campo que vamos a calcular. Una vez que lo tengamos, la lista que usaremos en la página será del tipo de la clase interna.

Por otro lado, en el método que calcula las Oportunidades asociadas a la Cuenta, haremos los cálculos pertinentes para rellenar el nuevo campo que crearemos.

Así quedaría el controlador:


public with sharing class AccountController
{
    private Id m_accountId;
    private List<OpportunityInfo> m_opportunities;
    private ApexPages.StandardController m_controller;

    public AccountController(ApexPages.StandardController controller)
    {
        m_controller = controller;
        m_accountId = controller.getRecord().Id;
        m_opportunities = calculateOpportunities(m_accountId);
    }

    public List<OpportunityInfo> getOpportunityList()
    {
        return m_opportunities;
    }

    public void setOpportunityList(List<OpportunityInfo> value)
    {
        m_opportunities = value;
    }

    private List<OpportunityInfo> calculateOpportunities(Id accId)
    {
        Boolean isClosed;
        OpportunityInfo oppInfo;
        List<OpportunityInfo> oppInfoList = new List<OpportunityInfo>();

        //Remember Separation of Concerns
        //Move to a Selector class
        for(Opportunity opp : [Select Id, Name,
                                 StageName, Amount,
                                 CloseDate
                               From Opportunity
                               Where AccountId = :accId])
        {
            isClosed = opp.StageName == 'Closed Won' ? true : false;
            oppInfo = new OpportunityInfo(opp, isClosed);
            oppInfoList.add(oppInfo);
        }
        return oppInfoList;
     }

     //Inner class
     private class OpportunityInfo
     {
        public Opportunity OpportunityRecord {get; private set;}
        public Boolean ToClose {get; set;}

        private OpportunityInfo(Opportunity oppValue, Boolean toCloseValue)
        {
            OpportunityRecord = oppValue;
            ToClose = toCloseValue;
        }
      }
}

Y en la página visualforce, la tabla tendrá que hacer uso de la clase interna


<apex:pageBlock title="Related Lists">
   <apex:pageBlockTable title="Opportunities" value="{!opportunityList}" var="opp" >
      <apex:column headerValue="Name">
         <apex:outputField value="{!opp.OpportunityRecord.Name}"/>
      </apex:column>
      <apex:column headerValue="is Closed?">
         <apex:inputCheckbox value="{!opp.ToClose}" disabled="{!opp.ToClose}"/>
      </apex:column>
      <apex:column headerValue="Stage">
         <apex:outputField value="{!opp.OpportunityRecord.StageName}"/>
      </apex:column>
      <apex:column headerValue="Amount">
         <apex:outputField value="{!opp.OpportunityRecord.Amount}"/>
      </apex:column>
      <apex:column headerValue="CloseDate">
         <apex:outputField value="{!opp.OpportunityRecord.CloseDate}"/>
      </apex:column>
   </apex:pageBlockTable>
</apex:pageBlock>

Siendo el resultado:

Captura de pantalla 2016-01-21 a las 20.11.08.png

En este ejemplo todos los campos de la tabla son de sólo lectura. El único que es input es el campo personalizado que hemos creado a partir de la clase interna. Pero si te fijas, en todos los registros el checkbox es de tipo lectura menos en el último en que el campo es de escritura porque el Stage no es Closed Won. Sin embargo el tag que hemos usado en todos los casos es apex:inputCheckbox. ¿Por qué no usamos output? Porque este tag sólo da la opción de escritura. Sin embargo ofrece un atributo para deshabilitarlo y es el que hemos usado, disabled.

Por último, nos podríamos preguntar por el botón Save. Si mantenemos en nuestra página visualforce la misma línea para el botón save que en el post anterior, la funcionalidad será la misma. Usará el controlador Standard para hacer la modificación en base de datos.

Sin embargo podemos modificar esa funcionalidad para extenderla. Para ello tendremos que crear un método en nuestro controlador dónde añadiremos la funcionalidad que necesitemos y devolveremos la página hacia donde quiero navegar después de la modificación. En nuestro caso la versión sólo lectura de nuestra cuenta.

public PageReference saveUpdates()
{
    //TODO: Acciones para salvar
    return new PageReference('/' + m_accountId);
} 

Y hacer una llamada a ese método en nuestra página.

<apex:pageBlockButtons >
    <apex:commandButton action="{!saveUpdates}" value="Save"/>
</apex:pageBlockButtons> 

Como resumen, en esta entrada hemos aprendido:

  • Cómo crear un controlador asociado a un único registro
  • Cómo asociarlo a una visualforce
  • Cómo mostrar campos en nuestra página visualforce
  • Cómo crear un tabla con valores que vienen de base de datos
  • Cómo añadir a la tabla un campo que no existe en base de datos
  • Cómo modificar el botón Standard Save

Nos vemos en el próximo post.