Thursday, February 5, 2026

Riding the Wave: My First Experience with Agentforce Vibe Coding


If you’ve been following the latest buzz in the Salesforce ecosystem, you’ve likely heard about Agentforce vibes . It’s the new frontier of vibe coding—a conversational approach where you describe what you want, and the AI handles the heavy lifting of planning, building, and deploying.

I recently decided to take it for a spin using the classic Dreamhouse sample app. Here’s a look at my journey—the wins, the vibes, and the areas where there's still room to grow.

          I installed Dreamhouse sample app in Developer Edition org and used it as the foundation for                              agentforce vibes experiment.  

DreamHouse app installation instructions


           

As shown in above screenshot, there is Create property section which is screen flow component, and I am planning to include new data point House Type to accomodate whether the type of house (Ranch, Colonial or Modern)

 

I started with following direction to Agentforce vibes:  
 
House type brand new field on property creation form

while creating new property there should be House type picklist field, non mandatory field with 'Ranch','Colonial','Modern' picklist values. 

Almost immediately Agentforce created plan and proposed it to me since I was using plan mode. 

                

 It propsed with me following steps which sound appropraite to perform the task,

- Create field metadata  

- Update layout

- Update Create Property flow (if used) 

- Update permission set

It understood my request correctly and prepared the plan, next step I switched to Act mode. Immediately it started to execute the plan, Agentforce got few questions for me, seeking inputs from me. And it offered me three options as follow,  which I can select by clicking on it. 

It should update the Create_property flow to include this new House Type field on its screen, or is the standard “New Property” UI the only creation path? 

- Update both standard UI and Create_property flow

- Only update the standard UI (page layout)

- Only update the flow 

I chose first option, update both standard UI and Create_property flow. 

Also it come up with suitable House Type field API name, label name and the section of page layout to add this field. It asked me for confirmation, also gave me choice if I need any changes on that. 


     

In next moment it asked me the permission to modify the necessary files, I generously                    approved it. Then it displayed the modified House type field meta file, field permission file,        create_property flow file. I saved them all and started guessing on how agentforce would deploy the changes. 


   

 Deploy the Changes 

Agentforce figured out that after modifications next step is the deployment. For that it informed me that Agentforce wants to use a tool on the Salesforce DX MCP server: Deploy metadata. 

After my green signal, it attempted the deployment, I saw few errors related  to org configuration, it resolved them by his own and came up with files needs to be deployed. 

For first attempt it encountered with error saying picklist is not valid value for enum. It asked me permission to edit the flow to resolve the error, of course I granted it and let agentforce figure out and fix the error. 




It modifed the flow and second time attempted the deployment, this time error was about picklistoptions invalid at the location, I let agentforce find the solution and allowed to fix it. 

It changed house type field from picklist to text on create_property flow screen. I let agentforce to make the change to see the deployment progress. 



With new fix, it attempted deployment for third time and booom it successfully completed the task.On flow screen I could see house type input box and not the picklist dropdown and at field level it is picklist field. Agentforce suceesfully deployed the changes,  I was able to create property record with new change. 


              







Here's what was accomplished:

1. Created a new custom field `House_Type__c` on the property__c object:

   - Field type: Picklist

   - Values: Ranch, Colonial, Modern

   - Required: false

   - Added to the property layout in the "Property Information" section (not marked required)

2. Updated the Create Property flow:

   - Added an optional input field for House Type using a standard InputField

   - The field is mapped to the new House_Type__c field on Property__c

3. Updated the dreamhouse permission set:

   - Granted Read/Edit permissions for the new House_Type__c field

4. Successfully deployed all changes to the DevHub org

 

Second Task for Agentforce

Now I decided to add newly created field(House Type) on property tile.  As per request agentforce analyzed the request and prepared the plan. 



 

This is what analysis done by Agentforce on adding field on property tile, house type 

Now let me check the House_Type__c field definition to understand what data we're working with:

Let me also check the JavaScript file for the propertyTile component to understand how the data is being handled:

Based on my analysis of the property tile component and the House Type field, I now have a clear understanding of what needs to be done.

Plan to implement: 

I need to modify the propertyTile.html file to add the House Type display after the Beds and Baths information. The change should be made in the HTML template where the Beds/Baths information is currently displayed.

The modification will involve:

1. Updating the HTML template to include the House Type after the Beds/Baths information

2. The new format will be: "Beds: {property.Beds__c} - Baths: {property.Baths__c} - House Type: {property.House_Type__c}"


It didn't consider the parent LWC of PropertyTile LWC, Property details gets passed to child LWC (PropertyTile) from parent LWC, In the same way it was expected from agentforce to include HouseType in parent LWC and pass those details to PropertyTile LWC. 

with the proposed plan of agentforce, it only changed the HTML file of PropertyTile LWC, obviously House Type data didn't show up on property tile because it was not getting passed from parent LWC. 


Summary 

What impressed me first was how Agentforce Vibes doesn't just write code; it plans. When I gave it the prompt, it generated a comprehensive implementation strategy:

Metadata Creation: It identified the need for a new field-meta.xml file.

Layout Updates: It planned to add the field to the standard Property Layout.

Flow Integration: It recognized that the Dreamhouse app uses a `Create_property.flow` and planned to add the picklist there.

Permissions: It even remembered to update the `dreamhouse.permissionset-meta.xml` so I could actually see the new field!

 Persistence,  When an error occurred while modifying the Flow, Agentforce didn't give up—it rewrote the picklist options to fix the issue and keep moving.

While the experience was largely positive, it wasn't without its "learning moments."

The Context Gap: While Agentforce successfully added the `House_Type__c` field to the Property Tile LWC HTML, it missed a crucial step. It didn't automatically update the parent LWC to fetch the new field. This meant the field was ready to display, but the data wasn't being passed down yet.


Final thoughts

Org Complexity Matters

I tested this on the Dreamhouse App, which is a clean, well-structured sample gallery. In a real-world Salesforce org with:

Thousands of lines of legacy code
Complex Managed Packages
Deeply nested Permission Set Groups The AI has a lot more noise to filter through. What worked seamlessly in a developer org might require more hand-holding.
This highlights exactly where we are: Agentforce is a powerful partner that automates the "boring" parts, but it still requires a developer's eye to catch those architectural nuances.



 

Tuesday, March 5, 2024

Run reports with filters and timeframe in apex

Run Reports salesforce documentation

Why you need to run reports through apex

1. Consider a scenario, you have report combining multiple objects with multiple filter conditions. along with that you report should consider records within certain timeframe. and report has summary variables. 

2. Users need to run report multiple times to see aggregate of fields (summary variable) in different timeframes. 

3. It's good idea if we store aggregate or total of fields for different timeframes. 

so that user can see summary of fields yearwise, quartewise in single glance. 

4. we can diplay how much sale was done last year, last quarter or last two quarters. 

5. To implement this, we don't have to implement or mimic report logic through apex instead we can run report by passing necessary inputs and get results, further we can just display these results on vf page or LWC or we can store this in variables and display that on record detail page. 


Example 1. 

In this example start date, end date and accountId are input to report. 

Dates are used to set duration for report. Filters are added as per sequence.

In the result we get aggregate result for mentioned accountid. 


 

 public String runReport(Date startDate, Date endDate, Id accountId){

 List <Report> reportList = [SELECT Id,DeveloperName FROM Report where

                                    DeveloperName = 'reportDeveloperName'];

        

       Id reportId = (String)reportList.get(0).get('Id');

        Reports.ReportDescribeResult describe = Reports.ReportManager.describeReport(reportId);

        Reports.ReportMetadata reportMd = describe.getReportMetadata();

        

        Reports.StandardDateFilter dateFilter = new Reports.StandardDateFilter();

        dateFilter.setDurationValue('CUSTOM');

        String setStartDate = startDate.year()+'-'+startDate.Month()+'-'+startDate.Day();

        String setEndDate = endDate.year()+'-'+endDate.Month()+'-'+endDate.Day();

        

        dateFilter.setEndDate(setEndDate);

        dateFilter.setStartDate(setStartDate);

        dateFilter.setColumn('createddate');

        reportMd.setStandardDateFilter(dateFilter);

        // Override filter and run report

        Reports.ReportFilter filter1 = reportMd.getReportFilters()[0];

        filter1.setValue(accountId); // Account id on which we want to run report

        Reports.ReportFilter filter2 = reportMd.getReportFilters()[1];

        filter2.setValue('filter value'); 

        Reports.ReportResults results = Reports.ReportManager.runReport(reportId, true);

        Reports.ReportFactWithSummaries factSum =

            (Reports.ReportFactWithSummaries)results.getFactMap().get('T!T');

          System.debug('Value...'+ factSum.getAggregates()[0].getvalue());

          System.debug('Value of Result: ' + factSum.getAggregates()[0].getLabel());


        return factSum.getAggregates()[0].getvalue().toString();

        

    }


Example 2. 

There is limit on how many times we can run report per hour, above example code may hit that limit because we are running report for each accountid separately. Learn more on limits

  • Your org can request up to 500 synchronous report runs per hour.
  • Your organization can request up to 1,200 asynchronous requests per hour.

To overcome this, group rows in report by accountid, now we can run report only once for multiple accountids. 
Start date, end date and accountIds (accountids separated by comma). 
Map is returned from method with accountid and respective result value. 

 
  
  
 public static Map<Id,String> runReport(Date startDate, Date endDate, String accountIds){
      List <Report> reportList = [SELECT Id,DeveloperName FROM Report where
                                    DeveloperName = 'reportDeveloperName'];
        
       Id reportId = (String)reportList.get(0).get('Id');
        
        Reports.ReportDescribeResult describe = Reports.ReportManager.describeReport(reportId);
        Reports.ReportMetadata reportMd = describe.getReportMetadata();
        
        Reports.StandardDateFilter dateFilter = new Reports.StandardDateFilter();
        dateFilter.setDurationValue('CUSTOM');
        String setStartDate = startDate.year()+'-'+startDate.Month()+'-'+startDate.Day();
        String setEndDate = endDate.year()+'-'+endDate.Month()+'-'+endDate.Day();
        
        dateFilter.setEndDate(setEndDate);
        dateFilter.setStartDate(setStartDate);
        dateFilter.setColumn('createddate');
        reportMd.setStandardDateFilter(dateFilter);
        // Override filter and run report
        Reports.ReportFilter filter1 = reportMd.getReportFilters()[0];
        filter1.setValue(accountIds); // Account id on which we want to run report
        Reports.ReportFilter filter2 = reportMd.getReportFilters()[1];
        filter2.setValue('Report filter value');
  
        Reports.ReportResults results = Reports.ReportManager.runReport(reportId, reportMd);
        
        Reports.ReportFactWithSummaries factSum =
            (Reports.ReportFactWithSummaries)results.getFactMap().get('T!T');
        
        // Get the first down-grouping in the report
        Reports.Dimension dim = results.getGroupingsDown();
        
        Reports.GroupingValue groupingVal = dim.getGroupings()[0];
        
        Map<Id,String> Account_Summary_val = new Map<Id,String>();
        for(Reports.GroupingValue gp:dim.getGroupings()){
        
            
            // Construct a fact map key, using the grouping key value
            String factMapKey = gp.getKey() + '!T';
            
            // Get the fact map from the report results
            Reports.ReportFactWithSummaries factDetails =
                (Reports.ReportFactWithSummaries)results.getFactMap().get(factMapKey);
            
            // Get the first summary amount from the fact map
            Reports.SummaryValue sumVal = factDetails.getAggregates()[0];
            Account_Summary_val.put(gp.getLabel().remove(','),sumVal.getlabel().remove(',').toString());
            
        }
        return Account_Summary_val;        
    }

 

Please comment if you have any queries



  

Monday, October 16, 2023

 Billing specialist superbadge



       Before attempting for this superbadge, I suggest to study and understand below Salesforce Billing concepts in detail.  Salesforce CPQ and Billing Object and Data Model and relationships knowledge is crucial to pass the challenges.  

    Salesforce Billing Concepts  

  • CPQ Order Management and Invoice Relationship
  • Salesforce Billing CPQ Object and Data Model
  • Salesforce CPQ Order Generation,CPQ Order Grouping
  • Invoice and Order Relationship
  • Usage and Usage Summary
  • Consumption Schedule
  • Invoice Line Pricing
  • Invoice Generation and Automation
  • Next Billing Date
  • Payment and Invoice Adjustments
  • Payment Relationship
  • Invoice Adjustments
  • Create and Allocate a Debit Note
  • Legal Entity and Taxation
  • Tax Integration and Tax Rates


 To help you get started, here are a few tips:

  • Enable below settings from installed CPQ package setting:
           Allow Multiple Orders
           Enable Usage Based Pricing

  • On solarBot product : 
            Subscription Pricing: Fixed Price
            Charge Type: Recurring
            Billing Type: Arrears
            Subscription Term populated
            Billing Frequency populated

  • Update shipping address on Account, for correct tax calculation
  • Update , Tax treatment and legal entity on order product , Estimated tax amount will be updated 
  • on order product make sure taxes calculated properly, use Tax Calculation Status should be complete. if you want calculate taxes again, make Tax Calculation Status queued to calculate taxes again.
  • Make sure , taxes are calculated properly before activating the orders. 
  • Usage summary and consumption schedule 
  • 12 Usage summary and 1 Usage summary records automatically gets created on usage order product. 
  • Create 2 usage records with details provided in challenge, for duration on 1 day i.e Today.
  • Total amount on solar bot usage product order should be zero.
  • Activate the orders by providing completion date as Today 
  • Set invoices status to posted so that you can create Credit Note and allocate amounts. 
  • Challenge does not complete with Credit Note, you need to create Payment Invoice Lines for all order products of first order. 
  • If you realize a mistake and want to correct it, you can always deactivate the trigger from installed package and delete activated orders.  And start from original quote with new changes. 
  • Keep in mind , Data flows from Product -> quote line -> order product  
  • If you make change on Product and you want to reflect it on quote, remove product from quote and add it again to reflect latest changes. 
  • Same is applicable for consumption schedule details,  Consumption schedule -> quote line consumption schedule -> order product consumption schedule 
  • Complete all steps of last challenge on same day, from order generation to payment.

Please comment if you have any queries






Sunday, June 26, 2022

How to create LWC Multi-select combobox

During web app development multiselect combobox is a common scenario. Salesforce lightning combobox  component allows single selection from options. Salesforce provides solution to multiselect combobox with Dual Listbox  however this option is not feasible when we have to deal with large number options and dual listbox takes comparatively more space on UI screen. SLDS includes blueprint of multiselect combobox however complete implementation solution and ready to use component is missing. 

In this blog post, provided solution is feasible for large number of options because user can search particular option from combobox and by using scroll bar user can navigate through options. 

Even if we consider single select lightning combobox, same solution can be used where we can take advantage of search feature within combobox when number of options to be shown are large. 





  •  User can select multiple options from dropdown 
  •  Same component can be used as single select or multi select with help of 'multiselect' attribute
  • Combobox can be rendered with pre selected values from available options


                     





Parent LWC component 

test.html

 <template>  
   <lightning-card>  
   <lightning-layout multiplerows="true">  
     <lightning-layout-item size="12" small-device-size="4">  
       <div if:true={colorsList} class="slds-m-right_x-large slds-m-left_x-large">  
         <c-multiselect-combobox onselect= {single_select_Color} options={colorsList} label="Single select picklist"></c-multiselect-combobox>  
       </div><br>  
     </lightning-layout-item>  
     <lightning-layout-item size="12" small-device-size="4">  
      <div if:true={colorsList} class="slds-m-right_x-large slds-m-left_x-large">  
      <c-multiselect-combobox onselect= {multi_select_Colors} multiselect="true" options={colorsList} label="Multi select picklist"></c-multiselect-combobox>  
      </div>  
     </lightning-layout-item>  
     <lightning-layout-item size="12" small-device-size="4">  
       <div if:true={pre_selected_colors} class="slds-m-right_x-large slds-m-left_x-large">  
       <c-multiselect-combobox onselect= {preselect_Colors} multiselect="true" options={colorsList} selectedvalues={pre_selected_colors} label="preselected multiselect values"></c-multiselect-combobox>  
       </div>   
     </lightning-layout-item>  
   </lightning-layout>  
   </lightning-card>  
 </template>  

test.js

 import { LightningElement, track } from 'lwc';  
 export default class Test extends LightningElement {  
   @track colorsList =[];  
   @track pre_selected_colors=[];  
   @track single_selected_color;  
   @track multi_selected_colors=[];  
   @track preselected_colors_list=[]  
   connectedCallback() {  
    this.colorsList.push({value:'Red', label:'Red'});  
    this.colorsList.push({value:'Orange', label:'Orange'});  
    this.colorsList.push({value:'Yellow', label:'Yellow'});  
    this.colorsList.push({value:'Lavender', label:'Lavender'});  
    this.colorsList.push({value:'Orchid', label:'Orchid'});  
    this.colorsList.push({value:'Magenta', label:'Magenta'});  
    this.colorsList.push({value:'Purple', label:'Purple'});  
    this.colorsList.push({value:'Indigo', label:'Indigo'});  
    this.colorsList.push({value:'LimeGreen', label:'LimeGreen'});  
    this.colorsList.push({value:'Cyan', label:'Cyan'});  
    this.colorsList.push({value:'RoyalBlue', label:'RoyalBlue'});  
    this.colorsList.push({value:'Maroon', label:'Maroon'});  
    this.colorsList.push({value:'GhostWhite', label:'GhostWhite'});  
    this.colorsList.push({value:'Gray', label:'Gray'});  
    this.colorsList.push({value:'Black', label:'Black'});  
    this.colorsList.push({value:'Pink', label:'Pink'});  
    this.pre_selected_colors.push('Orange');  
    this.pre_selected_colors.push('Indigo');  
           this.template  
            .querySelectorAll('c-multiselect-combobox').forEach(element => {  
            if (element.label === 'Single select picklist') {  
            element.ReloadComponent(this.colorsList);  
            }  
           });  
           this.template  
            .querySelectorAll('c-multiselect-combobox').forEach(element => {  
            if (element.label === 'Multi select picklist') {  
            element.ReloadComponent(this.colorsList);  
            }  
           });  
           this.template  
            .querySelectorAll('c-multiselect-combobox').forEach(element => {  
            if (element.label === 'preselected multiselect values') {  
            element.ReloadComponentwith_preselectedvalues(this.colorsList,this.pre_selected_colors,true);  
            }  
           });  
   }  
   single_select_Color(event){  
      this.single_selected_color = event.detail.payload.value;  
      console.log('selected colors..',this.single_selected_color);  
   }  
   multi_select_Colors(event){  
     this.multi_selected_colors = event.detail.payload.values;  
      console.log('selected colors..'+this.multi_selected_colors);  
   }  
   preselect_Colors(event){  
     this.preselected_colors_list = event.detail.payload.values;  
      console.log('selected colors..'+this.preselected_colors_list);  
   }  
 }  


Child LWC component 'multiselectcombobox' 

multiselectcombobox.html

 <template>  
   <template if:true={label}>  
     <label class="slds-form-element__label">{label}</label>  
   </template>  
    <div onmouseleave={handleMouseOut}>  
   <div tabindex="-1">  
   <div class="slds-combobox_container" id="divboxcomponentid">  
     <div class="slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click slds-is-open" aria-expanded="true" aria-haspopup="listbox" role="combobox">  
       <!-- Search Input -->  
       <div class="slds-combobox__form-element slds-input-has-icon slds-input-has-icon_right" role="none">  
         <lightning-input dropdown-alignment="auto" disabled={disabled} class="inputBox" placeholder="Select an Option" onblur={blurEvent} onclick={showOptions} onkeyup={filterOptions} value={searchString} auto-complete="off" variant="label-hidden" id="combobox-id-1" ></lightning-input>  
         <lightning-icon class="slds-input__icon" icon-name="utility:down" size="x-small" alternative-text="search"></lightning-icon>  
       </div>  
       <!-- Dropdown List -->  
       <template if:true={showDropdown}>  
         <div id="listbox-id-1" class="slds-dropdown slds-dropdown_length-5 slds-dropdown_fluid" onscroll={handlescroll}><!--style="{! 'max-height:' + (8 + (v.recordCount * 40)) + 'px' }""-->  
           <ul class="slds-listbox slds-listbox_vertical recordListBox" role="presentation">  
             <template if:false={message} >  
               <template for:each={optionData} for:item="option">  
                 <template if:true={option.isVisible}>  
                   <li key={option.value} data-id={option.value} onmousedown={selectItem} class="slds-listbox__item eachItem">  
                     <template if:true={option.selected}>  
                       <lightning-icon icon-name="utility:check" size="x-small" alternative-text="icon" ></lightning-icon>  
                     </template>  
                     <span title={option.label} class="slds-media slds-listbox__option_entity verticalAlign slds-truncate">{option.label}</span>  
                   </li>  
                 </template>  
               </template>  
             </template>  
             <template if:true={message} >  
               <li class="slds-listbox__item">  
                 <span class="slds-media slds-listbox__option_entity verticalAlign slds-truncate">{message}</span>  
               </li>  
             </template>  
           </ul>  
         </div>  
       </template>  
     </div>  
   </div>  
   </div>  
    </div>  
   <!-- Multi Select Pills -->  
   <template for:each={optionData} for:item="option">  
     <template if:true={option.selected}>  
       <lightning-pill key={option.value} class="slds-m-around_xx-small" name={option.value} label={option.label} onremove={removePill}></lightning-pill>  
     </template>  
   </template>  
 </template>  


multiselectcombobox.js

 import { LightningElement, track, api } from 'lwc';  
 export default class MultiselectCombobox extends LightningElement {  
   @api options;  
   @api selectedValue;  
   @api selectedvalues = [];  
   @api label;  
   @api minChar = 2;  
   @api disabled = false;  
   @api multiselect = false;  
   @track value;  
   @track values = [];  
   @track optionData;  
   @track searchString;  
   @track message;  
   @track showDropdown = false;  
   connectedCallback() {  
     this.showDropdown = false;  
     let optionData = this.options ? (JSON.parse(JSON.stringify(this.options))) : null;  
     this.value = this.selectedValue ? (JSON.parse(JSON.stringify(this.selectedValue))) : null;  
     this.values = this.selectedvalues ? (JSON.parse(JSON.stringify(this.selectedvalues))) : null;  
           if(this.value || this.values) {  
      this.connectedcallback_extension(optionData);  
     }  
   }  
   connectedcallback_extension(optionData){  
          let count = 0;  
       let searchString;  
       console.log('multiselectcombobox.........',optionData[0]);  
       for(let i = 0; i < optionData.length; i++) {  
         if(this.multiselect) {  
           if(this.values.includes(optionData[i].value)) {  
             optionData[i].selected = true;  
             count++;  
           }   
         } else {  
           if(optionData[i].value == this.value) {  
             searchString = optionData[i].label;  
           }  
         }  
       }  
       if(this.multiselect)  
         this.searchString = count + ' Option(s) Selected';  
       else  
          this.searchString = searchString;  
     this.optionData = optionData;  
   }  
   filterOptions(event) {  
     this.searchString = event.target.value;  
     if( this.searchString && this.searchString.length > 0 ) {  
       this.message = '';  
       if(this.searchString.length >= this.minChar) {  
         let flag = true;  
         for(let i = 0; i < this.optionData.length; i++) {  
           if(this.optionData[i].label.toLowerCase().trim().startsWith(this.searchString.toLowerCase().trim())) {  
             this.optionData[i].isVisible = true;  
             flag = false;  
           } else {  
             this.optionData[i].isVisible = false;  
           }  
         }  
         if(flag) {  
           this.message = "No results found for '" + this.searchString + "'";  
         }  
       }  
       this.showDropdown = true;  
     } else {  
       this.showDropdown = false;  
     }  
      }  
   selectItem(event) {  
     let selectedVal = event.currentTarget.dataset.id;  
     if(event.currentTarget.dataset.id) {  
       let options = JSON.parse(JSON.stringify(this.optionData));  
       let count = this.selectItem_extension(options,selectedVal);  
       if(this.multiselect)  
         this.searchString = count + ' Option(s) Selected';  
       if(this.multiselect)  
         event.preventDefault();  
       else{  
          this.dispatchEvent(new CustomEvent('select', {  
            detail: {  
             'payloadType' : 'multi-select',  
             'payload' : {  
             'value' : this.value,  
             'values' : this.values  
             }  
           }  
         }));  
          this.showDropdown = false;  
       }  
     }  
   }  
   selectItem_extension(options,selectedVal){  
        let count = 0;  
        for(let i = 0; i < options.length; i++) {  
         options = this.selectItem_extension1(options,selectedVal,i);  
         if(options[i].selected) {  
           count = count+1;  
         }  
       }  
        this.optionData = options;  
        return count;  
   }  
   selectItem_extension1(options,selectedVal,i){  
      if(options[i].value === selectedVal) {  
           if(this.multiselect) {  
             if(this.values.includes(options[i].value)) {  
               this.values.splice(this.values.indexOf(options[i].value), 1);  
             } else {  
               this.values.push(options[i].value);  
             }  
             options[i].selected = options[i].selected ? false : true;    
           } else {  
             this.value = options[i].value;  
             this.searchString = options[i].label;  
           }  
         }  
         return options;  
   }  
   showOptions() {  
     if(this.disabled == false && this.options) {  
       this.message = '';  
       this.searchString = '';  
       let options = JSON.parse(JSON.stringify(this.optionData));  
       for(let i = 0; i < options.length; i++) {  
         options[i].isVisible = true;  
       }  
       if(options.length > 0) {  
         this.showDropdown = true;  
       }  
       this.optionData = options;  
     }  
      }  
   removePill(event) {  
     let value = event.currentTarget.name;  
     let count = 0;  
     let options = JSON.parse(JSON.stringify(this.optionData));  
     for(let i = 0; i < options.length; i++) {  
       if(options[i].value === value) {  
         options[i].selected = false;  
         this.values.splice(this.values.indexOf(options[i].value), 1);  
       }  
       if(options[i].selected) {  
         count++;  
       }  
     }  
     this.optionData = options;  
     if(this.multiselect){  
       this.searchString = count + ' Option(s) Selected';  
     }  
     this.dispatchEvent(new CustomEvent('select', {  
         detail: {  
           'payloadType' : 'multi-select',  
           'payload' : {  
             'value' : this.value,  
             'values' : this.values  
           }  
         }  
       }));  
   }  
   blurEvent() {  
     let previousLabel;  
     let count = 0;  
     for(let i = 0; i < this.optionData.length; i++) {  
       if(this.optionData[i].value === this.value) {  
         previousLabel = this.optionData[i].label;  
       }  
       if(this.optionData[i].selected) {  
         count++;  
       }  
     }  
     if(this.multiselect)  
          this.searchString = count + ' Option(s) Selected';  
     else  
          this.searchString = previousLabel;  
     if(this.multiselect){  
       this.dispatchEvent(new CustomEvent('select', {  
       detail: {  
         'payloadType' : 'multi-select',  
         'payload' : {  
           'value' : this.value,  
           'values' : this.values  
         }  
       }  
     }));  
     }  
   }  
   handleMouseOut(){  
     this.showDropdown = false;  
   }  
   handlescroll(){  
     this.showDropdown = true;  
   }  
   @api  
   ReloadComponent(newoptions) {  
     this.options = newoptions;  
     this.showDropdown = false;  
     let optionData = this.options ? (JSON.parse(JSON.stringify(this.options))) : null;  
     this.value = this.selectedValue ? (JSON.parse(JSON.stringify(this.selectedValue))) : null;  
     this.values = this.selectedvalues ? (JSON.parse(JSON.stringify(this.selectedvalues))) : null;  
           if(this.value || this.values) {  
        this.connectedcallback_extension(optionData);  
     }  
   }  
   @api  
   ReloadComponentwith_preselectedvalues(newoptions, selectedvalues, multiselect) {  
     this.options = newoptions;  
     if(this.selectedvalues.length>0){  
        this.selectedvalues = selectedvalues;  
     }  
     this.showDropdown = false;  
     let optionData = this.options ? (JSON.parse(JSON.stringify(this.options))) : null;  
     if(multiselect==false){  
        this.value = this.selectedvalue ? (JSON.parse(JSON.stringify(this.selectedvalues))) : null;  
     }  
     if(multiselect==true){  
       this.values = this.selectedvalues ? (JSON.parse(JSON.stringify(this.selectedvalues))) : null;  
     }  
     if(this.value || this.values || multiselect==false) {  
      this.ReloadComponentwith_preselectedvalues_extension(optionData,selectedvalues,multiselect);  
     }  
   }  
   ReloadComponentwith_preselectedvalues_extension(optionData,selectedvalues,multiselect){  
           let count = 0;  
      for(let i = 0; i < optionData.length; i++) {  
         if(multiselect && this.values) {  
           if(this.values.includes(optionData[i].value)) {  
             optionData[i].selected = true;  
             count++;  
           }   
         }   
         else {  
           this.value = selectedvalues[0];  
           if(optionData[i].value == this.value) {  
            this.value = optionData[i].value;  
            this.searchString = optionData[i].label;  
          }   
         }  
       }  
       this.optionData = optionData;  
       if(multiselect && this.values)  
         this.searchString = count + ' Option(s) Selected';  
   }  
 }  


multiselectcombobox.css

 .verticalAlign {  
      cursor: pointer;  
   padding: 0px 5px !important;  
 }  
 .slds-dropdown {  
   padding:20px !important;  
 }  
 .recordListBox {  
      margin-top:0px !important;  
 }  
 .slds-listbox li {  
   padding: .05rem 0.7rem !important;  
   display: flex;  
 }  
 .inputBox input {  
      padding-left: 10px;  
 }  
 .eachItem:hover {  
   background-color: #F1F1F1;  
   cursor: pointer;  
 }  


You can find all code in detail on Github



Monday, August 12, 2019

 

How to Create/Version/Install Your First Unlocked Package

 

Why Unlocked Package?

Modular 

Unlocked packages organize your code and metadata in modular way. 

Reusable

These packages could be installed on different environments.
Best suited for AGILE development
Write-once-install-anywhere

Read more in - What is unlocked package?

Before we start, make sure you have DevHub enabled org with second generation packaging enabled on devhub org.



You built your code and metadata in salesforceDX project on local machine. And we are going to create unlocked package containing code and metadata which is there in salesforceDX project.
This code and metadata could be developed and tested by you locally or pulled through any org or scratch org. To learn more about salesforceDX project please visit package based development
Open it with visual studio code editor.

Initial sfdx.project.json file will look like :

 {  
  "packageDirectories": [  
   {  
    "path": "force-app",  
    "default": true  
   }  
  ],  
  "namespace": "",  
  "sfdcLoginUrl": "https://login.salesforce.com",  
  "sourceApiVersion": "46.0"  
 }  

Package directory will store created packages name, version name and number.
default parameter specifies default package to refer.
sfdcLoginUrl gets launched when we try to authenticate and login to devhub org.


Generate the package


force:package:create command used to create package that can be base package or it can depend on existing package.

 sfdx force:package:create --name mypackage --description "My First Package" --packagetype Unlocked --path force-app --nonamespace --targetdevhubusername DevHub  

--packagetype is the type of package
--targetdevhubusername to specify non-default devhub org.
--path specifies folder where your metadata resides


Package will be created with package Id :
 sfdx-project.json has been updated.  
 Successfully created a package. 0Ho0o0000008OSjCAM  
 === Ids  
 NAME    VALUE  
 ────────── ──────────────────  
 Package Id 0Ho0o0000008OSjCAM  


sfdx-project.json updated with package name, version name and number as follows:
 {  
   "packageDirectories": [  
     {  
       "path": "force-app",  
       "default": true,  
       "package": "mypackage",  
       "versionName": "ver 0.1",  
       "versionNumber": "0.1.0.NEXT"  
     }  
   ],  
   "namespace": "",  
   "sfdcLoginUrl": "https://login.salesforce.com",  
   "sourceApiVersion": "46.0",  
   "packageAliases": {  
     "mypackage": "0Ho0o0000008OSjCAM"  
   }  
 }  


Create version of package


In sfdx-project.json change version name to 'mypackage version1.0' and version number to '1.0.0.NEXT' to create first version of your package.

 sfdx force:package:version:create -p mypackage -d force-app -k test1234 --wait 10 -v DevHub  

-p is a package name
-d is a directory
-k is a key for that package which needs to match while installation

success message for creation of package on terminal:
 sfdx-project.json has been updated.  
 Successfully created the package version [08c0o00000000Z7AAI]. Subscriber Package Version Id: 04t0o000003jMTrAAM  
 Package Installation URL: https://login.salesforce.com/packaging/installPackage.apexp?p0=04t0o000003jMTrAAM  


In sfdx-project.json packagealiase added for version of package:
  "packageAliases": {  
     "mypackage": "0Ho0o0000008OSjCAM",  
     "mypackage@1.0.0-1": "04t0o000003jMTrAAM"  
   }  


Installation of package with the CLI


For installation of package use force:package:install command

 sfdx force:package:install --wait 10 --publishwait 10 --package mypackage@1.0.0-1 -k test1234 -r -u DevHub  

If publishwait is 0 package installation will fail , unless that package version is already available on your target org.  because publishwait specifies the maximum number of minutes that the command waits for the package version to be available in the target org.  default value is 0 .

--wait defines the maximum number of minutes that the command waits for the installation to complete after the package is available, but the installation continues even though wait time elapses.
you can check status of installation using force:package:install:report

-k is the key of package version
-u target org username or alias



Saturday, July 13, 2019

                            

Scratch Orgs


Scratch are new type of environment that are introduced with salesforceDX. Scratch orgs are fully customizable allowing developers to emulate different editions with different features and preferences.

You don't have to worry about credentials, after all scratch orgs are disposable and used for short period of time. when scratch org is created it does not contain any data, metadata or code from production environment. You can pull down code and customizations from devhub.

This demo explains how to create, open and delete scratch orgs.
Before creating scratch orgs , login and authenticate to DevHub enabled org, for details clickhere





Commands listed down below :
 sfdx force:org:create -s -f config/project-scratch-def.json -a myorg1 --durationdays 7  
 sfdx force:org:open  
 sfdx force:org:delete -u myorg1  

-s : set defaultusername
-f: definition file
-a: alias for scratch org, don;t need to use long usernames each time.
-u : target username

Scratch org definition file  -


A developer can select any edition,features, preferences of her choice. These configurations are stored in json format in definition file. These configurations can be shared across developers to get consistent scratch org created for every developer.

Let's start with default case, professional edition scratch org with default setting. You can create developer edition, enterprise and group editions.
We haven't yet added any features or settings.


 {  
  "orgName": "Sample Org",  
  "edition": "professional"  
 }  

Features -


Features are additional add-on functionality which can be supported by your edition. These are the functionalities that are not included by default in edition.These are the add-on licences with your edition. Some features require combination of feature and setting to work correctly. 

 {  
  "orgName": "Sample Org",  
  "edition": "professional",  
  "features": [  
   "PersonAccounts",  
    "custompps"  
  ]  
 }  


Settings -


With settings you can programmatically enable or disable settings of org. When these settings are enabled additional functionality is made available in setup UI. It is better to able to toggle these settings programmatically to support continuous integartion and automated development.


 {  
  "orgName": "Sample Org",  
  "edition": "professional",  
  "features": [  
   "PersonAccounts",  
   "customApps"  
  ],  
  "settings": {  
   "liveAgentSettings": {  
    "enableLiveAgent": true  
   }  
  }  
 }  

OrgPreferences -


OrgPreferences are settings that a user can configure in org. e.g S1DesktopEnabled is for lightning enabled org. These settings are enabled (or disabled)  in the orgPreferences section of the configuration file, in JSON format.

 "settings": {  
  "orgPreferenceSettings": {  
   "s1DesktopEnabled": true  
  },  
  "liveAgentSettings": {  
   "enableLiveAgent": true  
  }  
 }  


There is long list of features and settings that can be enabled in your scratch org,
For detailed list click here

Thursday, July 11, 2019

                    

Unlocked Packages


Unlocked packages is a new way to manage applications and deployment on salesforce environment. Packages are  container which contains set of related features, schema, customizations. It keeps customizations more organized and in tractable way. 
  
With package development, package will become ultimate source of truth. First you can create package with some basics and then update package version as you build it. So now it is just matter of installation of right version of package on your different environment and every org will be on same speed.


Configuration for your environment

  • Make sure Dev Hub is enabled from setup
  • Enable second-generation packaging on Dev Hub org

This demo explains how to login and authenticate to Dev Hub, then enable Dev Hub and second generation packaging.



DevHub can be authenticated through salesforce CLI as well with command
 sfdx force:auth:web:login   

DevHub Org
Developer hub org can create and manage scratch orgs. Scratch orgs are desposable and temporary environments. Scratch org is source driven and fully configurable and also facilitate continuous integration.


Second generation packaging allows to create packages in source driven development environment. we can create packages and deploy on salesforce environment

Enable Dev hub and Unlocked packages



More about Unlocked Packages


It is the new approach compared to traditional change set deployment and ant migration tools etc. It gives more control on managing change in environments.

With unlocked package development you could modularize your data and metadata customizations into package. First spend some wise time to divide functionality into packages. Maybe one package for Lightning App page, one for any app, visual force page and so on.

You can start to put your small functionality code and metadata into package. It is not necessary that everything of your org is in packages on first day.


Unlike managed packages Unlocked packages as name suggests you can change metadata of packages at any time ,even once installed on production environment. But you have to perform same changes on packages so that updated version of package could be installed on other orgs.


Changes made directly on production package does not automatically pulled out to package but it gives flexibility to quick small changes on production which comes with great responsibilities.