Skip to content
This repository has been archived by the owner on Sep 27, 2023. It is now read-only.

Commit

Permalink
Created lookup.cmp
Browse files Browse the repository at this point in the history
* Created a new extensible component, sobjectMetadata
* Created a lookup component - this is used by inputField.cmp for any lookup or master-detail fields. It supports for standard lookups, as well as polymorphic lookups
* fieldMetadata component is no longer abstract so it can be used as a service component
  • Loading branch information
jongpie authored Jan 12, 2018
1 parent 9f9d17e commit 26a5b28
Show file tree
Hide file tree
Showing 23 changed files with 705 additions and 89 deletions.
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,37 @@ A library of lightweight Salesforce Lightning components that streamline develop
src="https://raw.githubusercontent.com/afawcett/githubsfdeploy/master/deploy.png">
</a>

## sobjectMetadata.cmp
* An extensible, markup-less component that returns an instance of LightningMetadataController.SObjectMetadata for the specified SObject

`<c:sobjectMetadata sobjectName="Account" aura:id="accountMetadataService" />`

## fieldMetadata.cmp
* An extensible, markup-less component that returns an instance of LightningMetadataController.FieldMetadata for the specified field

`<c:fieldMetadata sobjectName="Account" fieldName="Type" aura:id="accountTypeMetadataService" />`

## inputField.cmp
* Provides a simple way to display an SObject's field as an input (editable) that automatically determines sobject-level security, field-level security, the field type, field label, etc. Attributes can be overridden to allow control over the field when needed

`<c:inputField sobjectName="Account" record="{!v.myAccount}" fieldName="Type" />`
`<c:inputField sobjectName="Account" fieldName="Type" record="{!v.myAccount}" />`

## lookup.cmp
* Provides lookup functionality that Salesforce does not provide for developers in LEX. This component is used by inputField.cmp for lookup fields.

Users can search for the record or choose one of the recently viewed records automatically displayed on focus
`<c:lookup sobjectName="Contact" fieldName="AccountId" record="{!v.myContact}" />`

Polymorphic fields, like Task.WhoId, automatically display an SObject Switcher.
SObject-level permissions are automatically applied - only objects that the user has permission to view are displayed in the SObject Switcher.
`<c:lookup sobjectName="Task" fieldName="WhoId" record="{!v.myTask}" />`
![lookup-task-whoid](https://user-images.githubusercontent.com/1267157/34769563-6f5b8374-f5fe-11e7-88c7-98e6fbb0ec75.gif)


## outputField.cmp
* Provides a simple way to display an SObject's field as an output (read-only) that automatically determines sobject-level security, field-level security, the field type, field label, etc. Attributes can be overridden to allow control over the field when needed

`<c:inputField sobjectName="Account" record="{!v.myAccount}" fieldName="Type" />`

`<c:inputField sobjectName="Account" fieldName="Type" record="{!v.myAccount}" />`

## sobjectLabel.cmp
* Displays the localized version of the provided SObject's label
Expand Down
13 changes: 9 additions & 4 deletions src/aura/fieldMetadata/fieldMetadata.cmp
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
<aura:component extensible="true" abstract="true" controller="LightningMetadataController">
<aura:component extensible="true" controller="LightningMetadataController">

<!-- Public Attributes -->
<aura:attribute name="sobjectName" type="String" required="true" />
<aura:attribute name="fieldName" type="String" required="true" />
<aura:attribute name="sobjectName" type="String" required="true" description="The API name of the SObject" />
<aura:attribute name="fieldName" type="String" required="true" description="The API name of the field" />

<!-- Public Methods -->
<aura:method name="fetch" action="{!c.doInit}" description="(Optional) Callback function to use after fetching the metadata">
<aura:attribute name="callback" type="function"/>
</aura:method>

<!-- Private Attributes -->
<aura:attribute name="fieldMetadata" type="Object" access="public" />
<aura:attribute name="fieldMetadata" type="Object" access="public" description="The field metadata object returned from the controller" />

<!-- Handlers -->
<aura:handler name="init" value="{!this}" action="{!c.doInit}" />
Expand Down
9 changes: 7 additions & 2 deletions src/aura/fieldMetadata/fieldMetadataHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

if(!sobjectName || !fieldName) return;

var params = event.getParam('arguments');

var action = component.get('c.getFieldMetadata');
action.setParams({
'sobjectName': component.get('v.sobjectName'),
Expand All @@ -13,8 +15,11 @@
action.setStorable();
action.setCallback(this, function(response) {
if(response.getState() === 'SUCCESS') {
component.set('v.fieldMetadata', response.getReturnValue());
component.set('v.label', response.getReturnValue().label);
var fieldMetadata = response.getReturnValue();
component.set('v.fieldMetadata', fieldMetadata);
component.set('v.label', fieldMetadata.label);

if(params) params.callback(null, fieldMetadata);
} else if(response.getState() === 'ERROR') {
this.processCallbackErrors(response);
}
Expand Down
8 changes: 7 additions & 1 deletion src/aura/inputField/inputField.cmp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
<!-- COMBOBOX - TODO -->
<!-- DATACATEGORYGROUPREFERENCE - TODO -->
<!-- ID - TODO -->
<!-- REFERENCE - TODO -->
<!-- TIME - TODO -->

<!-- BOOLEAN -->
Expand Down Expand Up @@ -126,6 +125,13 @@
/>
</aura:if>

<!-- REFERENCE -->
<aura:if isTrue="{!v.displayType == 'REFERENCE'}">
<c:lookup sobjectName="{!v.sobjectName}" fieldName="{!v.fieldName}" required="{!v.required}" disabled="{!or(v.fieldMetadata.isUpdateable == false, v.disabled)}"
record="{!v.record}"
/>
</aura:if>

<!-- STRING -->
<aura:if isTrue="{!v.displayType == 'STRING'}">
<ui:inputText aura:id="inputField" value="{!v.fieldValue}" required="{!v.required}" disabled="{!or(v.fieldMetadata.isUpdateable == false, v.disabled)}" class="slds-input" maxlength="{!v.fieldMetadata.maxLength}"
Expand Down
120 changes: 120 additions & 0 deletions src/aura/lookup/lookup.cmp
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<aura:component extends="c.fieldMetadata" controller="LookupController">

<!-- Public Config Attributes -->
<aura:attribute name="record" type="SObject" default="{}" />
<aura:attribute name="disabled" type="Boolean" description="(Optional) Disables the input field" />
<aura:attribute name="required" type="Boolean" description="(Optional) Marks the field as required (true) or optional (false)" />
<aura:attribute name="limitCount" type="Integer" default="5" description="Total number of records to return" />

<!-- Private Selected Record Attributes -->
<aura:attribute name="selectedParentRecordId" type="Id" access="private" />
<aura:attribute name="selectedParentRecord" type="SObject" access="private" />

<!-- Private SObject Selector Attributes -->
<aura:attribute name="showSObjectSelector" type="Boolean" access="private" default="false" />
<aura:attribute name="parentSObjectName" type="String" access="private" />
<aura:attribute name="parentSObjectMetadata" type="Object" access="private" />

<!-- Private Search Result Attributes -->
<aura:attribute name="showSearchResults" type="Boolean" access="private" default="false" />
<aura:attribute name="searchResults" type="Object[]" access="private" />

<!-- Handlers -->
<aura:handler name="init" value="{!this}" action="{!c.doInit}" />
<aura:handler name="change" value="{!v.fieldMetadata}" action="{!c.parseFieldMetadata}" />
<aura:handler name="change" value="{!v.selectedParentRecordId}" action="{!c.loadSelectedParentRecord}" />
<aura:handler name="change" value="{!v.parentSObjectName}" action="{!c.loadParentSObjectMetadata}" />

<!-- Markup -->
<div class="slds-form-element">
<div class="slds-form-element__control">
<div class="slds-combobox_container slds-has-object-switcher">
<!-- SObject Switcher -->
<aura:if isTrue="{! and(empty(v.selectedParentRecord), v.fieldMetadata.relationshipReferences.length > 1)}">
<div class="slds-listbox_object-switcher slds-dropdown-trigger slds-dropdown-trigger_click slds-is-open">
<button class="slds-button slds-button_icon" aria-haspopup="true" title="Select object to search in" onclick="{!c.toggleParentSObjectSelector}">
<lightning:icon iconName="{!v.parentSObjectMetadata.tabIcon}" size="small" />
<lightning:icon iconName="utility:down" size="x-small" />
</button>
<aura:if isTrue="{!v.showSObjectSelector}">
<div class="slds-dropdown slds-dropdown_left slds-dropdown_small">
<ul class="slds-dropdown__list" role="menu">
<aura:iteration items="{!v.fieldMetadata.relationshipReferences}" var="relationshipReference">
<aura:if isTrue="{!relationshipReference.isAccessible}">
<li class="slds-dropdown__item slds-is-selected" role="presentation">
<a href="javascript:void(0);" role="menuitemcheckbox" aria-checked="true" tabindex="0" data-sobjectname="{!relationshipReference.name}" onclick="{!c.selectParentSObject}">
<span class="slds-truncate" title="{!relationshipReference.labelPlural}">
<lightning:icon iconName="{!relationshipReference.tabIcon}" size="large" />
&nbsp;{!relationshipReference.labelPlural}
</span>
</a>
</li>
</aura:if>
</aura:iteration>
</ul>
</div>
</aura:if>
</div>
</aura:if>
<!-- Search Box -->
<div class="slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click" aria-expanded="false" aria-haspopup="listbox" role="combobox">
<div class="slds-combobox__form-element slds-input-has-icon slds-input-has-icon_right" role="none">
<aura:if isTrue="{!empty(v.selectedParentRecord)}">
<input
aria-autocomplete="list"
aria-controls="listbox-unique-id"
autocomplete="off"
class="slds-input slds-combobox__input"
disabled="{!or(empty(v.parentSObjectMetadata), v.fieldMetadata.isUpdateable == false, v.disabled)}"
id="combobox-unique-id"
onfocus="{!c.fetchSearchResults}"
onkeyup="{!c.fetchSearchResults}"
placeholder="{! if(empty(v.parentSObjectMetadata), '', 'Search ' + v.parentSObjectMetadata.labelPlural) }"
required="{!or(v.required, v.fieldMetadata.required)}"
role="textbox"
type="text"
/>
<span class="slds-icon_container slds-icon-utility-search slds-input__icon slds-input__icon_right">
<lightning:icon iconName="utility:search" size="x-small" />
</span>
<aura:set attribute="else">
<span class="slds-pill slds-pill_link">
<a href="javascript:void(0);" class="slds-pill__action slds-p-left_x-small" title="{#v.selectedParentRecord.displayText}">
<lightning:icon iconName="{!v.parentSObjectMetadata.tabIcon}" size="x-small" />
<span class="slds-pill__label slds-p-left_x-small">{#v.selectedParentRecord.displayText}</span>
</a>
<button class="slds-button slds-button_icon slds-button_icon slds-pill__remove" title="Remove" onclick="{!c.clearSelection}" >
<lightning:icon iconName="utility:close" size="small" />
<span class="slds-assistive-text">Remove</span>
</button>
</span>
</aura:set>
</aura:if>
</div>
<!-- Search Results -->
<aura:if isTrue="{!and(v.showSearchResults, greaterthan(v.searchResults.length, 0))}">
<div id="listbox-unique-id" role="listbox">
<ul role="presentation" class="slds-listbox slds-listbox_vertical slds-dropdown slds-dropdown_fluid slds-is-open" style="display:block; min-width:auto; max-width:100%; width:100%;">
<aura:iteration items="{!v.searchResults}" var="matchingRecord" indexVar="i">
<li role="presentation" class="slds-listbox__item" data-selectedparentindex="{#i}" onclick="{!c.parentRecordSelected}">
<span role="option" class="slds-media slds-listbox__option slds-listbox__option_entity slds-listbox__option_has-meta">
<span class="slds-media__figure optionIcon">
<span class="slds-icon_container" >
<lightning:icon iconName="{#v.parentSObjectMetadata.tabIcon}" size="small" />
<span class="slds-assistive-text">{!v.parentSObjectMetadata.label}</span>
</span>
</span>
<span class="slds-media__body">
<span class="slds-listbox__option-text slds-listbox__option-text_entity">{!matchingRecord.displayText}</span>
</span>
</span>
</li>
</aura:iteration>
</ul>
</div>
</aura:if>
</div>
</div>
</div>
</div>
</aura:component>
5 changes: 5 additions & 0 deletions src/aura/lookup/lookup.cmp-meta.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<AuraDefinitionBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>41.0</apiVersion>
<description>lookup</description>
</AuraDefinitionBundle>
7 changes: 7 additions & 0 deletions src/aura/lookup/lookup.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.THIS {
width: 100%;
}
.THIS .slds-pill_link {
margin: 4px;
width: calc(100% - 8px);
}
76 changes: 76 additions & 0 deletions src/aura/lookup/lookupController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
({
doInit : function(component, event, helper) {
var record = component.get('v.record');
var fieldName = component.get('v.fieldName');
if(record.hasOwnProperty(fieldName)) {
component.set('v.selectedParentRecordId', record[fieldName]);
}
},
parseFieldMetadata : function(component, event, helper) {
var fieldMetadata = component.get('v.fieldMetadata');

if(!fieldMetadata) return;

var defaultRelationshipReference;
for(var i = 0; i < fieldMetadata.relationshipReferences.length; i++) {
var relationshipReference = fieldMetadata.relationshipReferences[i];
if(relationshipReference.isAccessible === true) {
defaultRelationshipReference = relationshipReference;
break;
}
}
component.set('v.parentSObjectName', defaultRelationshipReference.name);
},
toggleParentSObjectSelector : function(component, event, helper) {
component.set('v.showSObjectSelector', !component.get('v.showSObjectSelector'));
component.set('v.showSearchResults', !component.get('v.showSearchResults'));
},
selectParentSObject : function(component, event, helper) {
var parentSObjectName = event.currentTarget.dataset.sobjectname;
component.set('v.parentSObjectName', parentSObjectName);
component.set('v.showSObjectSelector', false);
},
loadParentSObjectMetadata : function(component, event, helper) {
component.set('v.searchResults', null);

var fieldMetadata = component.get('v.fieldMetadata');
var parentSObjectName = component.get('v.parentSObjectName');

var selectedSObjectMetadata;
for(var i = 0; i < fieldMetadata.relationshipReferences.length; i++) {
var relationshipReference = fieldMetadata.relationshipReferences[i];

if(relationshipReference.name !== parentSObjectName) continue;

selectedSObjectMetadata = fieldMetadata.relationshipReferences[i];
break;
}
component.set('v.parentSObjectMetadata', selectedSObjectMetadata);
},
loadSelectedParentRecord : function(component, event, helper) {
var selectedParentRecordId = component.get('v.selectedParentRecordId');
var selectedParentRecord = component.get('v.selectedParentRecord');

// If no record ID, then there's nothing to load
if(selectedParentRecordId === null) return;

// If we already have the parent record loaded, don't query again
if(selectedParentRecord !== null && selectedParentRecordId === selectedParentRecord.Id) return;

// Query for the parent record
helper.fetchSelectedParentRecord(component, event, helper);
},
fetchSearchResults : function(component, event, helper) {
helper.fetchSearchResults(component, event, helper);
component.set('v.showSearchResults', true);
},
hideSearchResults : function(component, event, helper) {
component.set('v.showSearchResults', false);
},
parentRecordSelected : function(component, event, helper) {
helper.parentRecordSelected(component, event, helper);
},
clearSelection : function(component, event, helper) {
helper.clearSelection(component, event, helper);
}
})
Loading

0 comments on commit 26a5b28

Please sign in to comment.