Access more fields in Lightning Experience
One of the features we really like in the Salesforce Lightning Experience are the Modal Boxes provided for the creation and editing of records. They provide a consistent and attractive interface for the user, and although they’re not directly customizable, they honor the record layout field order and resize and scroll accordingly.
Need help? The instructions and example code that follows is designed to show you how this customization works and to let developers add this to their own deployments. If you’d rather we deploy this for you, we can customize this feature and add it to your DayBack as part of an implementation package. Just get in touch.
For DayBack Calendar, they can provide a seamless way to interact with fields that aren’t available in the standard DayBack popover. This lets you access additional fields without having to leave the calendar and still take advantage of DayBack’s drag and drop editing.
Update, May 2017: DayBack now lets you customize the calendar’s popover by including additional fields.
Update, August 2020: The new DayBack package substantially simplifies calling these Lightning modals (you’ll see this in the instructions below).
Here’s a preview:
Let’s take a look at how we can access these modals by adding a little customization to DayBack. Though we’re showing some code here, this is all stuff you just copy and paste so admins can jump right in and make this mod.
For Developers: Throughout this post we’ll include special notes for developers as there is some cool stuff happening here behind the scenes.
For starters, DayBack lives as a Canvas app on a Visualforce page. We love this approach as we can use the same tab in Visualforce and Lightning with just a few small css changes. Additionally, we can make use of the sforce.one methods via JavaScript from our Visualforce page when we’re in Lightning. These give us some basic Modal Box functionality that we’ll use for this example. In order to do this, we need to use the Canvas platform’s publish/subscribe model to publish an event from DayBack to its containing Visualforce Page. The Visualforce page then has a corresponding subscription which will trigger the sforce.one method to bring up a Modal Box. We can use a DayBack Custom Action to publish an event to begin this flow.
Instructions
Step 1. Adding the Subscription to the Static Resource
Using the Latest DayBack Package
The latest version of DayBack includes the changes you’d have made here in the static resource, so move on to step two. You have the latest package if you installed DayBack after July 13, 2020. To double-check, visit Setup / Installed Packages: the version number for your copy of DayBack is on the packages line. The previous listing was 1.17 and these simplifications were introduced in 1.20.
If You’re Not Using the Latest DayBack Package
In older packages, you’ll want to add some JavaScript to DayBack’s Static Resource. To edit the Static Resource for DayBack, navigate over to Setup and use Quick Find to locate and select Static Resources. The one we’re looking for is DayBackJS.
Click on “DayBackJS” and then click the “View File” link: you’ll see the JavaScript in plain text where we can copy it into your favorite text editor (I like Atom).
Once you have the text copied into an editor, scan down to about line 91 where we have a comment block indicating where to add your subscriptions. Here, we’ll add two objects to the array for our Canvas events. Note that this event model allows for an event payload (e), so we can publish the necessary data we need for our sforce.one methods from DayBack.
As noted in the comments, we don’t recommend any other changes to this file.
Here’s the text to copy and paste:
{ "name":"dbk.editRecord", "onData": function(e) { sforce.one.editRecord(e.recordId); } }, { "name":"dbk.createRecord", "onData": function(e) { sforce.one.createRecord(e.entityName, e.recordTypeId); } },
We can now save this new JavaScript file and upload it to our organization as a new Static Resource. You can name it anything, but “My_DayBackJS.js” is the default value and makes set-up a little bit quicker. Once you’ve saved the file to your desktop, return to the list of static resources and click the “New” button centered above the list. Fill out the form shown below, choose your “My_DayBackJS.js” file and click “Save”:
Once that’s saved, check that the file size is greater than 0 bytes: if it says zero you had trouble uploading: just delete the resource and try again. Once you’ve uploaded the file successfully, you’ll need to tell DayBack to load this version of the JavaScript file and not the stock one DayBack started with. This is done in a simple Custom Setting in the DayBack package. From All Setup, type Custom Setting into the Quick Find and locate DayBack Alternate Javascript and click “Manage”:
Click the New button below the Custom Setting and enter the name of the new Static Resource you created. It defaults to My_DayBackJS, so if that’s what you used for your Static Resource, you can just press Save. Don’t hit the lower New button for the Setup Owner as DayBack does not require that for it to work and it will cause an error in DayBack’s loading routine:
Step 2. Creating the Custom Action
Now, when DayBack loads, it will load our new JavaScript file and activate our two new subscriptions. We can now set up buttons (Custom Actions) in DayBack that use this JavaScript. Let’s start with a very basic button from the Event object: this button will open up a Modal Box for editing the Event. What’s cool about this is that the Modal Box will show custom fields that aren’t available in DayBack’s Event popover.
In DayBack Calendar, click “Administrator Settings” on the calendar’s Settings tab, then select “Salesforce” under Calendar Sources. Select the “Events” source and scroll all the way down until you see the Custom Actions for Events. Click “Add New custom Action” and give it the name “Edit Event”, pasting the following code in where it says “URl or Function” (note that this is the same code you’ll use for ANY Salesforce object–not just Events):
//edit this record in its native modal //retrieve our canvas client object for authentication using fbk object var client = fbk.client(); //publish canvas event to visualforce page to open modal for this event. Sfdc.canvas.client.publish( client , {name : 'dbk.editRecord', payload : {recordId : '[[Id]]'} } );
Click “Back to Calendar” in the upper left, and click “refresh” in your browser to make sure the new static resource you entered above has been loaded since you may have opened DayBack before uploading that resource. Back on the calendar, you’ll see a new “Edit Event” button in the custom action drawer (click the gear in the event’s lower right to reveal the drawer)…
…and clicking on “Edit Event” works very nicely, overlaying the Modal Edit Box on top of DayBack. Notice that it even includes the custom field, “Event URL” we added to our event source:
Clicking “Save” on the modal shown above will commit your edits to Salesforce and who a nice green confirmation in DayBack:
Step 3. No Callback, but a Workaround
This looks great. However, the sforce.one methods do not provide callbacks. This means that when the user is finished with the Modal Box, we don’t have any way of notifying DayBack to refresh and reflect these changes. We get a nice Lightning notification that the event is saved (shown above), but we’d need to refresh DayBack manually to see any changes we made.
What we really want is to update DayBack with these changes, particularly if the date had changed, as soon as the Lightning Modal is saved.
To refresh the calendar after our changes, we’ll use the following Custom Action. You should be able to paste this without modification into your org’s custom action, replacing the text of the action you added in Step 2:
//edit this record in its native modal //scope variables var modStamp; var i=0; var killRecursion = false; //retrieve our canvas client object for authentication using fbk object var client = fbk.client(); //publish canvas event to visualforce page to open modal for this event. Sfdc.canvas.client.publish( client , {name : 'dbk.editRecord', payload : {recordId : '[[Id]]'} } ); //begin polling our event object for changes ping(); //add a mouseover event listener to the document so we can quickly detect if we've left our modal //this is how we detect the user has returned to DayBack after a cancel //add this listener on a timeout, so it's not accidentally triggered before the modal opens setTimeout ( function(){document.addEventListener('mouseover', modalCancelled);},2000); //called functions function modalCancelled() { //this listener should only fire once, so remove it now document.removeEventListener('mouseover', modalCancelled); //set this variable on a delay so we don't outrun our ping function setTimeout(function(){killRecursion = true;},1100); } function ping(){ //retrieve our canvas context object for REST links/urls using fbk object var context = fbk.context(); //Query this event's mod timestamp to see if the event has been aupdated/saved in SF. var select = 'SELECT+Id,LastModifiedDate+FROM+' + event.schedule.objectName + '+WHERE+Id=\'[[Id]]\''; //GET url to poll our record var url = context.links.queryUrl + '?q=' + select; //settings for our canvas GET call to salesforce var settings = {}; settings.client = client; settings.contentType = 'application/json'; settings.success = pingCallback; //call to Salesforce Sfdc.canvas.client.ajax ( url , settings ); function pingCallback(data){ if(data.status===200){ //success if(!modStamp){ //first run, get the current last modified modStamp = data.payload.records[0].LastModifiedDate; } else if(modStamp!==data.payload.records[0].LastModifiedDate) { //close the DayBack popover $rootScope.$broadcast('closePopovers'); //event has been modified or our dom has been mousedover. //refresh calendar, slight timeout for modal to close var element = $('.calendar'); setTimeout(function(){element.fullCalendar('refetchEvents');},400); return; } else if(killRecursion || i>200){ //our dom has been moused over, so they've cancelled //also a timeout (5 minutes?), kill recursion return; } i++; //call recursively, with slight delay setTimeout(ping,1000); } else{ //salesforce rest error alert ( data.payload[0].errorCode ); } } }
Now when we make a date change in our Modal Box, and click save, we get our nice green notification from Lightning, and we almost instantly refresh the calendar to reflect any changes. Since we’re presumably making all the changes we need to in the Lightning Modal Box, closing the DayBack popover here makes sense and flows nicely. Plus, if the date changes, we don’t want to leave the popover open pointing to its old spot.
Step 4. Beyond Buttons: Capturing Event Actions
In addition to using Custom Actions to make buttons that invoke these Modal Boxes, you can also call them from Event Actions. We can use the exact same JavaScript we used for our Custom Action for an On Event Click Action. By disabling the default action, we can bypass our DayBack popover and go right to the Lightning Modal. This may be the preferred experience for some users, particularly if they’re using lots of additional fields we can’t map to our DayBack popover. The only limitation here is that we don’t get a delete option as it’s not provided in the Lightning Modal. If deletion is important then using this code as a Custom Action may be the way to go as delete is available in DayBack’s popover.
Here’s what it looks like to use the action code above in an “On Event Click” Event Action:
Step 5. Creating New Records Using Modals
We’ll want to change the JavaScript just a bit when we’re creating events so you’ll use paste in a slightly different script if you want to create new events in the modal as well.
This is the JavaScript we came up with for the On Create Event action. It should also be set to prevent default action. This code is for the Standard Event object, so you’ll need to replace the edit properties at the top of this code to match the field names from the object you’re using:
//create a new record and bring up its modal box //create POST request from values captured from our click. //Edit properties here to match your target object fields //IsAllDayEvent, StartDateTime, OwnerId, and DurationInMinutes are for the standard Event object, replace them accordingly var request = {}; request.IsAllDayEvent = event.allDay; request.StartDateTime = event.allDay ? event.start.format('YYYY-MM-DD') : event.start.format() ; if(!event.allDay) { request.DurationInMinutes = 60; } //specify resource. this example is using Assigned To as the resource, so a SOQL query //is required to get the id for the actual resourceId and then the request will be transformed. //Depending on your object and resource the SOQL may not be required. if(event.resource[0] && event.resource[0]!=='none'){ request.OwnerId = event.resource[0]; } //resource object info var resourceObject = 'User'; var resourceNameField = 'Name'; //--------------You shouldn't need to edit below this line------------------ //scope variables var id; var modStamp; var i=0; var killRecursion = false; //retrieve our canvas client object for authentication using fbk object var client = fbk.client(); //retrieve our canvas context object for REST links/urls using fbk object var context = fbk.context(); if(request.OwnerId && request.OwnerId!=='none') { //attempting to edit the owner. Need to SOQL for the id based on the name. getResourceId(request.OwnerId); } else{ //resource not specified, go right to creation if(request.OwnerId){ delete request.OwnerId; } createEvent(); } function getResourceId(name){ //retrieve the query URL from context var url = context.links.queryUrl; //encode name for passing as URI component name = encodeURIComponent(name); //create SOQL statement var soql = 'SELECT+ID+FROM+' + resourceObject + '+WHERE+' + resourceNameField + '=\'' + name + '\''; //final URL for GET var finalUrl = url + '?q=' + soql; //additional settings for our GET var settings = {}; settings.client = client; settings.contentType = 'application/json'; settings.success = createEvent; settings.method = 'GET'; //call to Salesforce Sfdc.canvas.client.ajax ( finalUrl , settings ); } function createEvent(data){ if(data){ if(data.status===200 && data.payload.records && data.payload.records[0]){ request.OwnerId = data.payload.records[0].Id; } else{ alert( data.payload.errors[0].message ); return; } } //additional settings for our POST var settings = {}; settings.client = client; settings.contentType = 'application/json'; settings.success = eventCallback; settings.method = 'POST'; settings.data = JSON.stringify(request); var url = context.links.sobjectUrl + event.schedule.objectName; //call to Salesforce Sfdc.canvas.client.ajax ( url , settings ); } function eventCallback(data) { if(data.status===201) { //POST successful, retrieve the id of our new Event id = data.payload.id.substring(0,15); //publish canvas event to visualforce page to open modal for this event. Sfdc.canvas.client.publish( client , {name : 'dbk.editRecord', payload : {recordId : id} }); //begin polling our event object for changes ping(); //add a mouseover event listener to the document so we can quickly detect if we've left our modal //this is how we detect the user has returned to DayBack after a cancel //add this listener on a timeout, so it's not accidentally triggered before the modal opens setTimeout ( function(){document.addEventListener('mouseover', modalCancelled);},2000); } else { //POST Unsuccessful, throw an error alert ( data.payload.errors[0].message); } } function modalCancelled() { //this listener should only fire once, so remove it now document.removeEventListener('mouseover', modalCancelled); //set this variable on a delay so we don't outrun our ping function setTimeout(function(){killRecursion = true;},1100); } function ping(){ //Query this event's mod timestamp to see if the event has been aupdated/saved in SF. //These are universal fields and should work on all sf objects var select = 'SELECT+Id,LastModifiedDate+FROM+' + event.schedule.objectName + '+WHERE+Id=\'' + id + '\''; //GET url to poll our record var url = context.links.queryUrl + '?q=' + select; //settings for our canvas GET call to salesforce var settings = {}; settings.client = client; settings.contentType = 'application/json'; settings.success = pingCallback; //call to Salesforce Sfdc.canvas.client.ajax ( url , settings ); function pingCallback(data){ if(data.status===200){ //success if(!modStamp){ //first run, get the current last modified modStamp = data.payload.records[0].LastModifiedDate; } else if(modStamp!==data.payload.records[0].LastModifiedDate) { //event has been modified or our dom has been mousedover. //refresh calendar, slight timeout for modal to close var element = $('.calendar'); setTimeout(function(){element.fullCalendar('refetchEvents');},400); return; } else if(killRecursion || i>200){ //our dom has been moused over, so they've cancelled //also a timeout (5 minutes?), kill recursion //delete this event deleteRecord(id); return; } i++; //call recursively, with slight delay setTimeout(ping,1000); } else{ //throw salesforce rest error alert ( data.payload[0].errorCode ); } } } function deleteRecord(id) { //settings for our delete var settings = {}; settings.client = client; settings.contentType = 'application/json'; settings.success = deleteCallback; settings.method = 'DELETE'; var url = context.links.sobjectUrl + event.schedule.objectName + '/' + id + '/'; //call to Salesforce Sfdc.canvas.client.ajax ( url , settings ); function deleteCallback(data) { if(data.status!==204) { console.log(data); } } }
Conclusions
Even though we ran into some limitations with the sforce.one methods themselves, like having no callbacks, we’re still able to take advantage of them in DayBack both as both a Custom Action and as Event Actions. Salesforce Lightning users can take full advantage of DayBack’s drag and drop editing and still have access to additional fields that may not be available in the DayBack popover, providing the best of both worlds. Additionally, using the native Modal Box enforces the org’s particular business logic, like required fields, directly in the Lightning context. We hope you can make some nice use of these examples and stay tuned as we’ll continue to look at ways of incorporating these Modal Boxes more efficiently and in more ways for DayBack.
Leave a Reply