Lightning Design System - Edit Parent and Child Records
Introduction
Salesforce introduced the Lightning Design System as part of the new Lightning Experience UX at the end of August 2015. The Salesforce Lightning Design System (SLDS) is the first time that Salesforce have provided the developer community with the CSS, fonts and icons to allow us to build UI components that match the LightningX look and feel. This means no more fragile solutions that pull in the standard stylesheets or wholesale cloning of the standard stylesheets into static resources. In my case it also likely means the end of using Bootstrap for Salesforce1 applications, although as LDS is currently only at version 0.8 I’ll probably give it a few more versions before I move over completely.
After a browsing the docs I was keen to give the SLDS a spin and decided to combine this with another idea I’ve been toying with - taking some of my Visualforce solutions that I’ve blogged about and rebasing those onto Lightning components. So the first sample I’m going with is editing parent and child records on a single screen.
You can find the original Visualforce-centric post here - I haven’t gone for feature-parity with that post, specifically around creating and deleting child records, as there is enough to cover without that, although I may add that in a later version. As always, I’ve gone with the Account sobject as the parent and Contact as the children.
Getting Started with the SLDS
If you haven’t looked at the SLDS yet, the best place to start (as with so many things Salesforce) is by getting your Trailhead badge for the Lightning Design System module.
Designing the Screen
When working with Lightning Components I find its useful to sketch out the layout before starting the development - the greater the degree of granularity, the more reuse you are likely to get. This was made much easier in this case as I decided to use the LDS Master Detail Layout, consisting of the following components:
- A page header
- A list of cards for the account and contact records
- A central form area to allow editing of the selected account or contact record
Here’s a screenshot of the completed page:
Once I had the basic layout I could start building the various components. I decided to go with a Lightning Application to host the page, as it meant I could navigate to it directly via a URL rather than having to build a navigation mechanism, which is a blog post all to itself!
Implementing the Application
There are quite a few elements to this solution, so I don’t intend to go through the code for all of them. The following sections detail some of the areas that I think are interesting - your mileage may vary - if there’s anything that I haven’t explained that you’d like to know more about, let me know in the comments and I’ll do my best to oblige. You can access the full codebase via the link to the Github repository at the end of this post.
The Application
The key part of the app markup is shown below:
<div class="slds"> <c:BBAppHeader appEvent="{!c.appEvent}"/> <div class="slds-grid slds-wrap slds-p-top--x-small"> <div class="slds-col slds-size--1-of-3 slds-p-around--medium"> <c:BBAccountContactList account="{!v.account}" contacts="{!v.contacts}" selectedContact="{!v.selectedContact}" editingContact="{!v.editingContact}" /> </div> <aura:if isTrue="{!v.editingContact}"> <div class="slds-col slds-size--2-of-3 slds-p-around--medium"> <c:BBContactEditForm contact="{!v.selectedContact}" /> </div> </aura:if> <aura:if isTrue="{! (!v.editingContact)}"> <div class="slds-col slds-size--2-of-3 slds-p-around--medium"> <c:BBAccountEditForm account="{!v.account}" /> </div> </aura:if> </div> </div>
The markup is wrapped in a div with the class ‘slds’ as the LDS is namespaced - so any styles that I use need to be inside an ancestor with this class or they will revert back to very ordinary looking HTML.
The left and right components, are rendered inside an LDS grid, as I want to fix the size of each of them relative to each other.
The left hand component, the list of account and contact cards, takes up 1/3 of the available space, so is wrapped in a div with the class “slds-size—1-of-3’ indicating that this div takes up one column out of a total of three available in the grid. The component takes an ‘editingContact’ attribute, which is set to true when the user clicks on a contact card and false when the user clicks on the account card.
The right hand component is wrapped inside a div with the class ‘slds-size—2-of-3’ indicating that it should take up two of the available three columns, or 2/3 of the available space. Depending on the value of the ‘editingContact’ attribute a form is rendered to allow the account or selected contact to be edited. Note that I’ve enclosed each from in its own aura:if component - while I could use the ‘else’ attribute, I find it more readable to draw out the conditional rendering in this way. Note also that I’ve repeated the containing div for each conditionally rendered component - I could put this outside the aura:if components, but I’ve gone this route so that if I need to I can apply different styling to the containing div based on whether the user is editing the account or a contact, although at present the styling is identical. Note that I’ve also added a style class of ‘slds-p-around—medium’ to add some padding to the left and right components, otherwise they will butt up hard against each other.
URL Parameters
The id of the account to edit is picked up from the URL - I couldn’t find a mechanism for this that is provided by the Lightning framework, so I’m pulling it via a JavaScript method in the application's helper:
getURLParameter : function(param) { var result=decodeURIComponent((new RegExp('[?|&]' + param + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search)||[,""])[1].replace(/\+/g, '%20'))||null; return result; }
The application helper then retrieves the account and contacts for the ID via a server side request:
var action = cmp.get("c.GetAccountAndContacts"); var params={"accountIdStr":accountId}; action.setParams(params); var self = this; action.setCallback(this, function(response) { try { self.actionResponseHandler(response, cmp, self, self.gotAccount); } catch (e) { alert('Exception ' + e); } }); $A.enqueueAction(action);
Note that I delegate the handling of the response from the server to a generic method - actionResponseHandler, which takes a parameter of the method that will actually handle the response - self.gotAccount in this case. This allows me to decouple all of the error handling around the request itself from the code that will process the response and update the data model. Note also that I pass the component itself, and the ‘self’ instance of the helper to the response handler - these are in turn passed to the gotAccount method so that when processing the response I can access attributes from the component or helper methods if I need to.
Component Event
The app header component takes an attribute of ‘appEvent’ that specifies a handler for a component event that is fired when the ‘Save' button in the header is clicked. As I plan to re-use the app header in the future, I don’t want to tie it to the expected behaviour of the page. Instead when the user clicks a button, an event is fired indicating the button that was clicked, which allows the containing application to decide what action to take.
The app header declares the type of event that it will fire, and the name of the attribute that will contain the handler:
<aura:registerEvent name="appEvent" type="c:BBAppEvent" />
The ‘Save' button specifies an onclick handler:
<button class="slds-button slds-button--neutral" onclick="{!c.fireSaveEvent}">Save</button>
The handler constructs the event, specifies which button the user clicked and fires the event:
var appEvent = cmp.getEvent("appEvent"); appEvent.setParams({"action" : "save"}); appEvent.fire();
The containing app receives the event through its declared handler and saves the records to the server:
appEvent : function(cmp, ev) { var action=ev.getParam("action"); if (action=="save") { this.save(cmp); } }
Two Way Attribute Binding
Aside from the event fired from the header component, two-way binding is used to “communicate’ updates between the various components. Using this mechanism means that any updates made to an account or contact in the editing form are also reflected in the account/contact list component, as references to the records are passed through the attribute binding. This means that the same variable instance is used in each component, so a change in one component is automatically picked up by any other component using that variable instance without any additional intervention on the part of my JavaScript.
SLDS Markup
If you aren’t used to CSS frameworks, SLDS can look like it introduces a lot of markup for containing divs etc and a lot of style classes, for example here’s a snippet from the app header:
<div class="slds-page-header"> <div class="slds-grid"> <div class="slds-col slds-has-flexi-truncate"> <div class="slds-media"> <div class="slds-media__figure"> <span class="slds-icon__container slds-icon-standard-account"> <c:BBsvg class="slds-icon" xlinkHref="/resource/BB_SLDS080/assets/icons/standard-sprite/svg/symbols.svg#account" /> <span class="slds-assistive-text">Account Icon</span> </span> </div> ... </div> </div> </div> </div>
All I can say here is that it becomes less jarring over time, as anyone who has spent time working with the Bootstrap framework can attest. One thing it does encourage you to do is to decompose your components as much as possible, to keep the amount of markup short and readable.
Github Repository
Related Posts
- Edit Parent and Child Records with Visualforce - Part 1
- Edit Parent and Child Records with Visualforce - Part 2
- Lightning, Visualforce and the DOM
- Lightning Components and JavaScript Libraries
- Lighting Components and CSS Media Queries
- Lightning Component Events
- Lightning Components and Unobtrusive JavaScript
- Lightning Components and Custom Apex Classes
How can we insert a new record which has a master detail relation with another.
ReplyDeleteYou have to populate the master-detail relationship field, just like Visualforce or the standard UI.
DeleteHi, i have a visualforce page that creates a record. After creation, i want to redirect to the new record. I have a visualforce page developed in salesforce classic, but i need the behavior in Lightning. When i tried to create the record in Lightning it redirects to Salesforce classic Theme. I am using "URL.getSalesforceBaseUrl().toExternalForm()" to redirect.
ReplyDeleteHi BOB,
ReplyDeleteAM New to salesforce and here is my question is i have a button on lightinig page when i click the save button it shld update in my Object X and Save in Obj Y , can u give me a lil bit code snippet for this ......... Tq