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

Commit 26a5b28

Browse files
authored
Created lookup.cmp
* 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
1 parent 9f9d17e commit 26a5b28

23 files changed

+705
-89
lines changed

README.md

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,37 @@ A library of lightweight Salesforce Lightning components that streamline develop
66
src="https://raw.githubusercontent.com/afawcett/githubsfdeploy/master/deploy.png">
77
</a>
88

9+
## sobjectMetadata.cmp
10+
* An extensible, markup-less component that returns an instance of LightningMetadataController.SObjectMetadata for the specified SObject
11+
12+
`<c:sobjectMetadata sobjectName="Account" aura:id="accountMetadataService" />`
13+
14+
## fieldMetadata.cmp
15+
* An extensible, markup-less component that returns an instance of LightningMetadataController.FieldMetadata for the specified field
16+
17+
`<c:fieldMetadata sobjectName="Account" fieldName="Type" aura:id="accountTypeMetadataService" />`
18+
919
## inputField.cmp
1020
* 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
1121

12-
`<c:inputField sobjectName="Account" record="{!v.myAccount}" fieldName="Type" />`
22+
`<c:inputField sobjectName="Account" fieldName="Type" record="{!v.myAccount}" />`
23+
24+
## lookup.cmp
25+
* Provides lookup functionality that Salesforce does not provide for developers in LEX. This component is used by inputField.cmp for lookup fields.
26+
27+
Users can search for the record or choose one of the recently viewed records automatically displayed on focus
28+
`<c:lookup sobjectName="Contact" fieldName="AccountId" record="{!v.myContact}" />`
29+
30+
Polymorphic fields, like Task.WhoId, automatically display an SObject Switcher.
31+
SObject-level permissions are automatically applied - only objects that the user has permission to view are displayed in the SObject Switcher.
32+
`<c:lookup sobjectName="Task" fieldName="WhoId" record="{!v.myTask}" />`
33+
![lookup-task-whoid](https://user-images.githubusercontent.com/1267157/34769563-6f5b8374-f5fe-11e7-88c7-98e6fbb0ec75.gif)
34+
1335

1436
## outputField.cmp
1537
* 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
1638

17-
`<c:inputField sobjectName="Account" record="{!v.myAccount}" fieldName="Type" />`
18-
39+
`<c:inputField sobjectName="Account" fieldName="Type" record="{!v.myAccount}" />`
1940

2041
## sobjectLabel.cmp
2142
* Displays the localized version of the provided SObject's label

src/aura/fieldMetadata/fieldMetadata.cmp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
<aura:component extensible="true" abstract="true" controller="LightningMetadataController">
1+
<aura:component extensible="true" controller="LightningMetadataController">
22

33
<!-- Public Attributes -->
4-
<aura:attribute name="sobjectName" type="String" required="true" />
5-
<aura:attribute name="fieldName" type="String" required="true" />
4+
<aura:attribute name="sobjectName" type="String" required="true" description="The API name of the SObject" />
5+
<aura:attribute name="fieldName" type="String" required="true" description="The API name of the field" />
6+
7+
<!-- Public Methods -->
8+
<aura:method name="fetch" action="{!c.doInit}" description="(Optional) Callback function to use after fetching the metadata">
9+
<aura:attribute name="callback" type="function"/>
10+
</aura:method>
611

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

1015
<!-- Handlers -->
1116
<aura:handler name="init" value="{!this}" action="{!c.doInit}" />

src/aura/fieldMetadata/fieldMetadataHelper.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
if(!sobjectName || !fieldName) return;
77

8+
var params = event.getParam('arguments');
9+
810
var action = component.get('c.getFieldMetadata');
911
action.setParams({
1012
'sobjectName': component.get('v.sobjectName'),
@@ -13,8 +15,11 @@
1315
action.setStorable();
1416
action.setCallback(this, function(response) {
1517
if(response.getState() === 'SUCCESS') {
16-
component.set('v.fieldMetadata', response.getReturnValue());
17-
component.set('v.label', response.getReturnValue().label);
18+
var fieldMetadata = response.getReturnValue();
19+
component.set('v.fieldMetadata', fieldMetadata);
20+
component.set('v.label', fieldMetadata.label);
21+
22+
if(params) params.callback(null, fieldMetadata);
1823
} else if(response.getState() === 'ERROR') {
1924
this.processCallbackErrors(response);
2025
}

src/aura/inputField/inputField.cmp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
<!-- COMBOBOX - TODO -->
4242
<!-- DATACATEGORYGROUPREFERENCE - TODO -->
4343
<!-- ID - TODO -->
44-
<!-- REFERENCE - TODO -->
4544
<!-- TIME - TODO -->
4645

4746
<!-- BOOLEAN -->
@@ -126,6 +125,13 @@
126125
/>
127126
</aura:if>
128127

128+
<!-- REFERENCE -->
129+
<aura:if isTrue="{!v.displayType == 'REFERENCE'}">
130+
<c:lookup sobjectName="{!v.sobjectName}" fieldName="{!v.fieldName}" required="{!v.required}" disabled="{!or(v.fieldMetadata.isUpdateable == false, v.disabled)}"
131+
record="{!v.record}"
132+
/>
133+
</aura:if>
134+
129135
<!-- STRING -->
130136
<aura:if isTrue="{!v.displayType == 'STRING'}">
131137
<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}"

src/aura/lookup/lookup.cmp

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<aura:component extends="c.fieldMetadata" controller="LookupController">
2+
3+
<!-- Public Config Attributes -->
4+
<aura:attribute name="record" type="SObject" default="{}" />
5+
<aura:attribute name="disabled" type="Boolean" description="(Optional) Disables the input field" />
6+
<aura:attribute name="required" type="Boolean" description="(Optional) Marks the field as required (true) or optional (false)" />
7+
<aura:attribute name="limitCount" type="Integer" default="5" description="Total number of records to return" />
8+
9+
<!-- Private Selected Record Attributes -->
10+
<aura:attribute name="selectedParentRecordId" type="Id" access="private" />
11+
<aura:attribute name="selectedParentRecord" type="SObject" access="private" />
12+
13+
<!-- Private SObject Selector Attributes -->
14+
<aura:attribute name="showSObjectSelector" type="Boolean" access="private" default="false" />
15+
<aura:attribute name="parentSObjectName" type="String" access="private" />
16+
<aura:attribute name="parentSObjectMetadata" type="Object" access="private" />
17+
18+
<!-- Private Search Result Attributes -->
19+
<aura:attribute name="showSearchResults" type="Boolean" access="private" default="false" />
20+
<aura:attribute name="searchResults" type="Object[]" access="private" />
21+
22+
<!-- Handlers -->
23+
<aura:handler name="init" value="{!this}" action="{!c.doInit}" />
24+
<aura:handler name="change" value="{!v.fieldMetadata}" action="{!c.parseFieldMetadata}" />
25+
<aura:handler name="change" value="{!v.selectedParentRecordId}" action="{!c.loadSelectedParentRecord}" />
26+
<aura:handler name="change" value="{!v.parentSObjectName}" action="{!c.loadParentSObjectMetadata}" />
27+
28+
<!-- Markup -->
29+
<div class="slds-form-element">
30+
<div class="slds-form-element__control">
31+
<div class="slds-combobox_container slds-has-object-switcher">
32+
<!-- SObject Switcher -->
33+
<aura:if isTrue="{! and(empty(v.selectedParentRecord), v.fieldMetadata.relationshipReferences.length > 1)}">
34+
<div class="slds-listbox_object-switcher slds-dropdown-trigger slds-dropdown-trigger_click slds-is-open">
35+
<button class="slds-button slds-button_icon" aria-haspopup="true" title="Select object to search in" onclick="{!c.toggleParentSObjectSelector}">
36+
<lightning:icon iconName="{!v.parentSObjectMetadata.tabIcon}" size="small" />
37+
<lightning:icon iconName="utility:down" size="x-small" />
38+
</button>
39+
<aura:if isTrue="{!v.showSObjectSelector}">
40+
<div class="slds-dropdown slds-dropdown_left slds-dropdown_small">
41+
<ul class="slds-dropdown__list" role="menu">
42+
<aura:iteration items="{!v.fieldMetadata.relationshipReferences}" var="relationshipReference">
43+
<aura:if isTrue="{!relationshipReference.isAccessible}">
44+
<li class="slds-dropdown__item slds-is-selected" role="presentation">
45+
<a href="javascript:void(0);" role="menuitemcheckbox" aria-checked="true" tabindex="0" data-sobjectname="{!relationshipReference.name}" onclick="{!c.selectParentSObject}">
46+
<span class="slds-truncate" title="{!relationshipReference.labelPlural}">
47+
<lightning:icon iconName="{!relationshipReference.tabIcon}" size="large" />
48+
&nbsp;{!relationshipReference.labelPlural}
49+
</span>
50+
</a>
51+
</li>
52+
</aura:if>
53+
</aura:iteration>
54+
</ul>
55+
</div>
56+
</aura:if>
57+
</div>
58+
</aura:if>
59+
<!-- Search Box -->
60+
<div class="slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click" aria-expanded="false" aria-haspopup="listbox" role="combobox">
61+
<div class="slds-combobox__form-element slds-input-has-icon slds-input-has-icon_right" role="none">
62+
<aura:if isTrue="{!empty(v.selectedParentRecord)}">
63+
<input
64+
aria-autocomplete="list"
65+
aria-controls="listbox-unique-id"
66+
autocomplete="off"
67+
class="slds-input slds-combobox__input"
68+
disabled="{!or(empty(v.parentSObjectMetadata), v.fieldMetadata.isUpdateable == false, v.disabled)}"
69+
id="combobox-unique-id"
70+
onfocus="{!c.fetchSearchResults}"
71+
onkeyup="{!c.fetchSearchResults}"
72+
placeholder="{! if(empty(v.parentSObjectMetadata), '', 'Search ' + v.parentSObjectMetadata.labelPlural) }"
73+
required="{!or(v.required, v.fieldMetadata.required)}"
74+
role="textbox"
75+
type="text"
76+
/>
77+
<span class="slds-icon_container slds-icon-utility-search slds-input__icon slds-input__icon_right">
78+
<lightning:icon iconName="utility:search" size="x-small" />
79+
</span>
80+
<aura:set attribute="else">
81+
<span class="slds-pill slds-pill_link">
82+
<a href="javascript:void(0);" class="slds-pill__action slds-p-left_x-small" title="{#v.selectedParentRecord.displayText}">
83+
<lightning:icon iconName="{!v.parentSObjectMetadata.tabIcon}" size="x-small" />
84+
<span class="slds-pill__label slds-p-left_x-small">{#v.selectedParentRecord.displayText}</span>
85+
</a>
86+
<button class="slds-button slds-button_icon slds-button_icon slds-pill__remove" title="Remove" onclick="{!c.clearSelection}" >
87+
<lightning:icon iconName="utility:close" size="small" />
88+
<span class="slds-assistive-text">Remove</span>
89+
</button>
90+
</span>
91+
</aura:set>
92+
</aura:if>
93+
</div>
94+
<!-- Search Results -->
95+
<aura:if isTrue="{!and(v.showSearchResults, greaterthan(v.searchResults.length, 0))}">
96+
<div id="listbox-unique-id" role="listbox">
97+
<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%;">
98+
<aura:iteration items="{!v.searchResults}" var="matchingRecord" indexVar="i">
99+
<li role="presentation" class="slds-listbox__item" data-selectedparentindex="{#i}" onclick="{!c.parentRecordSelected}">
100+
<span role="option" class="slds-media slds-listbox__option slds-listbox__option_entity slds-listbox__option_has-meta">
101+
<span class="slds-media__figure optionIcon">
102+
<span class="slds-icon_container" >
103+
<lightning:icon iconName="{#v.parentSObjectMetadata.tabIcon}" size="small" />
104+
<span class="slds-assistive-text">{!v.parentSObjectMetadata.label}</span>
105+
</span>
106+
</span>
107+
<span class="slds-media__body">
108+
<span class="slds-listbox__option-text slds-listbox__option-text_entity">{!matchingRecord.displayText}</span>
109+
</span>
110+
</span>
111+
</li>
112+
</aura:iteration>
113+
</ul>
114+
</div>
115+
</aura:if>
116+
</div>
117+
</div>
118+
</div>
119+
</div>
120+
</aura:component>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<AuraDefinitionBundle xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<apiVersion>41.0</apiVersion>
4+
<description>lookup</description>
5+
</AuraDefinitionBundle>

src/aura/lookup/lookup.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.THIS {
2+
width: 100%;
3+
}
4+
.THIS .slds-pill_link {
5+
margin: 4px;
6+
width: calc(100% - 8px);
7+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
({
2+
doInit : function(component, event, helper) {
3+
var record = component.get('v.record');
4+
var fieldName = component.get('v.fieldName');
5+
if(record.hasOwnProperty(fieldName)) {
6+
component.set('v.selectedParentRecordId', record[fieldName]);
7+
}
8+
},
9+
parseFieldMetadata : function(component, event, helper) {
10+
var fieldMetadata = component.get('v.fieldMetadata');
11+
12+
if(!fieldMetadata) return;
13+
14+
var defaultRelationshipReference;
15+
for(var i = 0; i < fieldMetadata.relationshipReferences.length; i++) {
16+
var relationshipReference = fieldMetadata.relationshipReferences[i];
17+
if(relationshipReference.isAccessible === true) {
18+
defaultRelationshipReference = relationshipReference;
19+
break;
20+
}
21+
}
22+
component.set('v.parentSObjectName', defaultRelationshipReference.name);
23+
},
24+
toggleParentSObjectSelector : function(component, event, helper) {
25+
component.set('v.showSObjectSelector', !component.get('v.showSObjectSelector'));
26+
component.set('v.showSearchResults', !component.get('v.showSearchResults'));
27+
},
28+
selectParentSObject : function(component, event, helper) {
29+
var parentSObjectName = event.currentTarget.dataset.sobjectname;
30+
component.set('v.parentSObjectName', parentSObjectName);
31+
component.set('v.showSObjectSelector', false);
32+
},
33+
loadParentSObjectMetadata : function(component, event, helper) {
34+
component.set('v.searchResults', null);
35+
36+
var fieldMetadata = component.get('v.fieldMetadata');
37+
var parentSObjectName = component.get('v.parentSObjectName');
38+
39+
var selectedSObjectMetadata;
40+
for(var i = 0; i < fieldMetadata.relationshipReferences.length; i++) {
41+
var relationshipReference = fieldMetadata.relationshipReferences[i];
42+
43+
if(relationshipReference.name !== parentSObjectName) continue;
44+
45+
selectedSObjectMetadata = fieldMetadata.relationshipReferences[i];
46+
break;
47+
}
48+
component.set('v.parentSObjectMetadata', selectedSObjectMetadata);
49+
},
50+
loadSelectedParentRecord : function(component, event, helper) {
51+
var selectedParentRecordId = component.get('v.selectedParentRecordId');
52+
var selectedParentRecord = component.get('v.selectedParentRecord');
53+
54+
// If no record ID, then there's nothing to load
55+
if(selectedParentRecordId === null) return;
56+
57+
// If we already have the parent record loaded, don't query again
58+
if(selectedParentRecord !== null && selectedParentRecordId === selectedParentRecord.Id) return;
59+
60+
// Query for the parent record
61+
helper.fetchSelectedParentRecord(component, event, helper);
62+
},
63+
fetchSearchResults : function(component, event, helper) {
64+
helper.fetchSearchResults(component, event, helper);
65+
component.set('v.showSearchResults', true);
66+
},
67+
hideSearchResults : function(component, event, helper) {
68+
component.set('v.showSearchResults', false);
69+
},
70+
parentRecordSelected : function(component, event, helper) {
71+
helper.parentRecordSelected(component, event, helper);
72+
},
73+
clearSelection : function(component, event, helper) {
74+
helper.clearSelection(component, event, helper);
75+
}
76+
})

0 commit comments

Comments
 (0)