How to Store a draft of a unsaved Record

Is it possible to add a unsaved record as a draft? Yes, it is possible. It is possible using our form script capability of object.

1.Requirements

I assume that the reader of this article has a good understanding of  Java script, Jquery. User should have some knowledge on add or delete DOM elements. User should have some knowledge on Agileapps exposed Rest API and Ajax calls.

2.Challenges

In draft capability of a record has following challenges.

  • Where to store those draft data and how to store?
  • How to get previously saved data in the current form?
  • How to differentiate among multiple drafts of a record created by different users?
  • How to apply a draft, if record form layout got changed?

3.Getting started

  • First we will create an object, let’s name it “DraftStorage”. Create some fields of these object “related_record, related_object, user_id, draft_data”. Make draft_data field as of type “text area” and others can be text field.
  • This DraftStorage will contain all of our draft records.
  • Now pick any object where you want to apply this draft capability and write some form script
  • Go to Setting>> Object >> select an object >> select from >> select a layout >> select Form script >> Onload script >> New ui script

In Onload Script box write below

 /*********************
Create 2 button HTML element (Save as Draft, Apply Draft). Add your Object id of Current Object.
Add Object id of Draft Object. add draft object internal name.
*********************/
 
var $saveDraft = $('<input type="button" id="saveDraft" style="margin-left:2px" value="Save as Draft" class="mat-raised-button mat-primary"/>'); 
var $applyDraft = $('<input type="button" id="applyDraft" style="margin-left:2px" value="Apply Draft" class="mat-raised-button mat-primary"/>'); 
var currentObjectId='5ab03dbf20be4740bb9b74731bbf3def';
var draftObjectId='bebe34d8a36c41c6a06b8c9c5dc9eaf4';
var draftObjectName='DraftStorage';
var userId=getUserDetails().id;
 
/******** End of Varible Declarations ********/
 
/******** Get the drafts after loading the record, define its Click event *************/
getDrafts();
if ($('#saveDraft').length == 0) { 
$saveDraft.appendTo($(".left-container")[0]);
}
 
$('#saveDraft').click(function() {
  getData();
})
 

In reusable script function write below

 /************* set a object model and add record in draft data *****************/
 
function getData() {
 var objectModel = '{';
 var sections = $('aac-record-form .mat-tab-label');
 var sectionCount=0;
 if(sectionCount < sections.length) {
   traverseSections(sectionCount);
  }
   
/************ Iterate to all the sections and get the field informations **************/  
function traverseSections(sectionCount) {
 $('aac-record-form .mat-tab-label')[sectionCount].click();
  
/*********** Added a delay of 0.3 second for each section to get data **************/  
 setTimeout(function(){  
   var sectionName = $('aac-record-form .mat-tab-label')[sectionCount].getElementsByTagName('span')[0].innerText;
   objectModel = objectModel+'"'+sectionName+'":{';
   var formGroups = $('aac-record-form .mat-tab-body-content')[sectionCount].getElementsByClassName('form-group');
   var formGroupCount=0;
   
/*********** Iteraing to all fields of a section **************/
   while(formGroupCount<formGroups.length) {
     objectModel = objectModel+'"'+'formgroup'+formGroupCount+'": {';
     var fieldLabel = formGroups[formGroupCount].getElementsByTagName('label')[0].innerText;
     objectModel = objectModel+'"title":"'+fieldLabel+'",';
     if(formGroups[formGroupCount].getElementsByClassName('form-control')[0]) {
       var fieldVal=formGroups[formGroupCount].getElementsByClassName('form-control')[0].value;
         
       objectModel = objectModel+'"value": "'+fieldVal+'"},';
       } 
     else {
       objectModel = objectModel+'"value":""},';
       }
     if (formGroupCount==formGroups.length-1) { objectModel=objectModel.slice(0,-1); }
       formGroupCount++;
       }
     sectionCount++;
     if(sectionCount<sections.length) {
       objectModel=objectModel+'},'
       traverseSections(sectionCount);
       }
    else {
       objectModel = objectModel+'}}';
       if ($('#applyDraft').length==0) {
        addDataToDraft(objectModel);
        } 
      else {
        getDraftId(objectModel);
         }
       }
     }, 300); 
  }
}
 
/************ 
Add unsaved record data to Draft object as a draft recrod
it should have the fields draft_data,related_object,related_record,user_id
****************/
 
function addDataToDraft(modeldata) {
  var oid = currentObjectId;
  var rid = recordId;
  var paramValue = modeldata; 
  var inputXML = "<platform><record><draft_data>"+modeldata+"</draft_data>"
      +"<related_object>"+oid+"</related_object>"
      +"<related_record>"+rid+"</related_record>"
      +"<user_id>"+userId+"</user_id>"
      +"</record></platform>"; 
 
    // Use jQuery's ajax method to invoke the REST API
    $.ajax({
      type: 'POST',
      url: '/networking/rest/record/'+draftObjectId,
      contentType: 'application/xml',
      Accept:'application/xml',
      data: inputXML, 
      dataType: 'json',
      success: function (data) 
      {
      $applyDraft.appendTo($(".left-container")[0]); 
$('#applyDraft').click(function() {
  var x = JSON.parse(modeldata);
  var id = data.platform.message.id;
fetchData(x, id); 
})
        alert('success');  
      }
 
    });
}
 
/********** Over ride a existing draft ************/  
function updateDraft(objectModel, draftId) {
var inputXML= '<platform><record><draft_data>'+objectModel+'</draft_data></record></platform>';
    $.ajax({
      type: 'PUT',
      url: '/networking/rest/record/'+draftObjectId+'/'+draftId,
      contentType: 'application/xml',
      Accept:'application/xml',
      data: inputXML, 
      dataType: 'json',
      success: function (data) 
      {
        alert('success');  
      }
 
  })
}
 
/********* get the id of existing draft **********/
function getDraftId(objectModel) {
$.ajax({ 
type: 'GET', 
url: "/networking/rest/execSQL?sql=select * from "+draftObjectName+" where related_record="+recordId+" AND related_object='"+currentObjectId+"' AND user_id='"+userId+"'", 
contentType: 'application/xml', 
Accept:'application/json', 
dataType: 'json', 
success: function (data) 
  {
  updateDraft(objectModel,data.platform.record.id);
}
    })
}
 
/************* get Draft Data onload of the record *****************/
function getDrafts() {
  $.ajax({ 
  type: 'GET', 
  url: "/networking/rest/execSQL?sql=select * from "+draftObjectName+" where related_record="+recordId+" AND related_object='"+currentObjectId+"' AND user_id='"+userId+"'", 
  contentType: 'application/xml', 
  Accept:'application/json', 
  dataType: 'json', 
  success: function (data) 
{
  if (data.platform.record) {
$applyDraft.appendTo($(".left-container")[0]); 
$('#applyDraft').click(function() {
  var x = JSON.parse(data.platform.record.draft_data);
  var id = data.platform.record.id;
fetchData(x, id);
})
}
}
});
}
 
/************* apply draft to record *****************/  
function fetchData(draftData, id) {
 var sections = $('aac-record-form .mat-tab-label');
 var sectionCount=0;
 if(sectionCount < sections.length) {
    fetchSections() 
 }
 
/************* apply draft data to all sections *****************/
function fetchSections() {
  $('aac-record-form .mat-tab-label')[sectionCount].click();
  setTimeout(function(){
  var sectionName = $('aac-record-form .mat-tab-label')[sectionCount].innerText;
  var formGroups = $('aac-record-form .mat-tab-body-content')[sectionCount].getElementsByClassName('form-group');
  var formGroupCount=0;
    while(formGroupCount<formGroups.length) {
      if(formGroups[formGroupCount].getElementsByClassName('form-control')[0]) {
        var fieldTitle = formGroups[formGroupCount].getElementsByTagName('label')[0].innerText;
        var sectionMeta= Object.values(draftData[sectionName]);
        var fieldMeta = sectionMeta.find(sm=>sm.title==fieldTitle);
        if (fieldMeta) {
    formGroups[formGroupCount].getElementsByClassName('form-control')[0].value = fieldMeta.value;
        }
      }
      formGroupCount++;
    }
  sectionCount++;
  if(sectionCount < sections.length) {
  fetchSections();
 
    else { 
      deleteDraft(id); 
    }
  }, 300);
  
  
}
  
/************* delete draft record after apply *****************/  
function deleteDraft(id) {
    $.ajax({ 
type: 'DELETE', 
url: '/networking/rest/record/'+draftObjectId+'/'+id,
contentType: 'application/xml', 
Accept:'application/json', 
dataType: 'json', 
success: function (data) 
alert('Applied');
$('#applyDraft').remove();
});
  
  }
  
}

4. Achived Challenges

  • Using above script for any record user can see a "save draft" button (Button name and style can be changed).
  • Once User clicks the "save draft" button it will collect all the metadata of current record (Meta data will be collected by traversing all the section with a fraction of second), then it will form a JSON string using those meta data.
  • At the same time it will create a record in DraftStorage , that record will capture its ObjectId, RecordId, LoggedInUserId, and draft meta data.
  • On loading of the object it will execute a SQL statement to check wheather a draft exist with the reference of current record id , object id and logged in user id. If it exitst user can see "apply draft " button.
  • By clicking the "apply draft " button , it will populate the saved draft data with the fields.
  • Once a draft applied the same draft referenced record will delete from draftStorage object.
  • If a draft already exist and user again save the draft with some other modification then it will override with previous draft.

5.Considerations

  • Once a draft applied, changes will apply to all fields, then "apply draft" button will disappear, Now the user has to save the record manually.
  • If a form layout structure changed, like if a section deleted then draft will apply for other sections
  • If a section renamed then for that section draft will not apply.
  • If a field is deleted or renamed then the draft will not apply to that field.
  • if a field order is changed or section order is changed then the draft will apply with out any issue.
  • if a field is deleted and another field is created with the same name then draft will add the field value as of last field value.

6.Anonymous user access

  • This draft can be accessible to the creator of the draft only.
  • Users can not access others draft and apply.
  • For a record, user can store one draft only.

7. Notes

  • This draft capability is not applicable for file, image, and junction object fields.
  • If a field is hidden or disabled then draft will not apply to that field.
  • User should not change anything in records of draft object, otherwise it will effect in onload script of the object.
  • For good practice user should hide the draft object from tab section.