How to make uploaded files required (and still usable)?

  • 1
  • Question
  • Updated 3 years ago
I'm working on a couple of prototypes where file uploads are required. Basic criteria include:
  • A user creates a record and adds files on to it. A reviewer reviews the record and attached files.
  • Scenario 1: Require at least one uploaded file for the record but limit to 3 files max
  • Scenario 2: Require 3 uploads to complete the process.
While I like the Chatter File structure, it doesn't appear to be natively supported in Skuid. So I'm looking for what the default ways of handling this case are. I like the flow of a wizard and thanks to some help from the Skuid support have a wizard with record creation and file attachment working. But...still not there in terms of the scenarios above and the constraints.

Constraints:

I would like the solution to be:
  • Fairly intuitive interface for the user to add the files (eg 3 areas/buttons/fields where the user can attach the file so it is clear they have done what they need to for the application).
  • Intuitive interface (and not a LOT of clicks) for the reviewers of the files received to view the files. 
  • Able to determine if the user has met the requirements (flag a status on the record if not all records are uploaded or send an email to user letting them know they have not met requirements).
I have looked at the model of uploading files to a field on a record. While that seems like the most promising for the user side of it, it seems terrible for the reviewer, as it seems to require at least 3 clicks to view the file (the file is stored in a custom object in a related list, once going to the record you have to click again to actually view the attachment).

Is there anything better for a solution? Thanks in advance for your advice!
Photo of Christopher Johnson

Christopher Johnson

  • 694 Points 500 badge 2x thumb

Posted 4 years ago

  • 1
Photo of J.

J., Official Rep

  • 7,470 Points 5k badge 2x thumb
Christopher,

Sorry, this is going to be a long response, but there are a number of ways you can approach this. There are two locations for storing files with Skuid's File component: In Field on Record and In Attachment to Record, each with different options. I'll try to cover both.

If you can use the In Field on Record approach, it's probably going to allow you to use more out of the box Skuid, so I'd recommend taking that approach, if you can.

Required File Uploads

(In Field on Record)

You should be able to accomplish either scenario. You'll have to create dedicated fields for the attachments, meaning you can't have a variable number of them. If you set the fields as required in your Salesforce field metadata, Skuid will take care of it for you, indicate to the user that the fields are required, and display a message if a save is attempted without providing values. However, users will be required to add all three files before saving:



*Red bar to right of the "High School Transcript" field indicates that the field is required

For scenario 1, it's a little more complicated, but you have options. You could enforce the rule with an Apex trigger (e.g. before insert/update, check to see if at least one of the three fields are populated, and if not, addError()). While we love saving users from writing Apex and the corresponding test code, this does enforce the rule at the DB level rather than the UI level. You lose the little red bar indicating that the field is required, but the big red message will still show on save, and this will be enforced across your entire org.

If you really only want this rule enforced for a specific Skuid page, then you could replicate that trigger logic in a JavaScript snippet to be called when the user clicks "Next" on the wizard. Our JavaScript API provides lots of hooks into your Salesforce data, and the current state on the client before it has been committed to the DB, so you'd be able to perform this evaluation without actually attempting a save.

To accomplish this, change the Action Type of the Next Step button to Run multiple actions, and make the first action Run a Skuid JavaScript Snippet. The body of the snippet should be something like...
var params = arguments[0],
	$ = skuid.$,
	step = params.step,
	stepEditor = step.editor,
	fieldsToCheck = ['Field1__c','Field2__c','Field3__c'],
	model = skuid.model.getModel('MyModel'),
	row = model.getFirstRow();

//  Clear out any messages that are already displayed
stepEditor.clearMessages();

//  See if any of our fields to check have a value
var valFlag = false;
$.each(fieldsToCheck,function(i,val){
	if (model.getFieldValue(row,val)) {
		valFlag = true;
	}
});

if (valFlag === false) {
	var messages = [{
		severity : 'ERROR',
		message : 'At least one of the following fields must be populated: ' + fieldsToCheck.join(', ')
	}];
	
	//  Have our step's editor handle the messages
	stepEditor.handleMessages(messages);

	//  Stop the Action Sequence, and execute any on-error actions
	return false;
}
You can add more actions in the sequence (e.g. Save the record), but the last one would be to navigate to the next step. The return false at the end of the snippet will make sure that these actions aren't run if one of the fields is blank.

(In Attachment to Record)

Since this approach uses a child record rather than a field on the record, making fields required isn't an option. Attachment isn't a triggerable object, so that option is off the table as well. You're left with going the snippet route. It may work to just check that at least one of the records in your Attachments model has a Salesforce Id, and if not, display a message:
var params = arguments[0],
	$ = skuid.$,
	step = params.step,
	stepEditor = step.editor,
	model = skuid.model.getModel('Attachments');

//  Clear out any messages that are already displayed
stepEditor.clearMessages();

//  See if any of our Attachment records have a SF Id
var valFlag = false;
$.each(model.getRows(),function(){
	var row = this,
		rowId = model.getFieldValue(row,'Id');
    
	if (skuid.model.isNewId(rowId) === false) {
		valFlag = true;
	}
});

if (valFlag === false) {
	var messages = [{
		severity : 'ERROR',
		message : 'At least one Attachment record must be added.'
	}];
	
    //  Have our step's editor handle the messages
    stepEditor.handleMessages(messages);

    //  Stop the Action Sequence, and execute any on-error actions
    return false;
}
To get it to max out at three, you could use a Model Action on the Attachments model which runs a snippet to make sure that the model.data.length is not greater than 3:

var params = arguments[0],
	$ = skuid.$,
	step = params.step,
	stepEditor = step.editor,
	model = skuid.model.getModel('Attachments');

//  Clear out any messages that are already displayed
stepEditor.clearMessages();

if (model.data.length > 3) {
	var messages = [{
		severity : 'ERROR',
		message : 'Only three Attachments are allowed.'
	}];
	
    //  Have our step's editor handle the messages
    stepEditor.handleMessages(messages);

    //  Stop the Action Sequence, and execute any on-error actions
    return false;
}
Note that you'll also want to make sure that you've got the right conditions on your Attachment model so that you don't pull extra attachments which would count against this total and prevent legitimate uploads.

Reviewing Uploaded Files


(In Field on Record)

Our File component does include a preview option out of the box. After you upload a file, the file name should appear next to the upload button (or by itself in read only mode):



Clicking on this box should open a new browser tab and attempt to load it in the browser. You are viewing

If it's a file format that your browser doesn't support, I think most browsers prompt the user to download the file, but again, that's a browser thing, so results may vary. If you are using the Field on Record approach, I'm curious, what is your page doing differently?

(In Attachment to Record)

Using a template field, you can get to the actual Attachment URL rather than the standard Salesforce Page for the Attachment record. Add a template to you layout, label it appropriately, and then make the body something like this:
<a href="/servlet/servlet.FileDownload?file={{{Id}}}" target="_blank">{{{Name}}}</a>
...replace out "{{{Name}}}" with whatever model field(s) you actually want displayed in that template.

"Able to determine if the user has met the requirements"

This one may be covered by the page message displayed in the snippets, but if you want to set a checkbox field, if validation fails, so that a script or trigger can fire off an email, you can add an on-error action with our actions framework:





Again, sorry that was really long, but does any of that help get you where you need to be?
Photo of Christopher Johnson

Christopher Johnson

  • 694 Points 500 badge 2x thumb
J - I really appreciate your thorough explanation here. It took much longer than anticipated to get to the implementation, but sometimes projects happen like that :)

I've been looking at using the lookup fields on the object and think it may work with one of the paths you explained, but not with making the field required. One use case I didn't mention, which I think informs the path, is that we are using this to create a skuid wizard that allows the user to fill out the application and save and continue later (eg the application can be saved as a draft, returned to later and then submitted). 

It seems that if I make the image field required, then the user would not be able to save their application and exit out. So it looks like the path is:
  • Add a button on the image upload page like "review and submit" in addition to a "save and exit".
  • Use the JavaScript approach listed above to check the 1 required field for completion if the user wants to submit.
What I like about this approach is that we can change the number of required fields by Skuid page if we want (so different applications using the same object can have different criteria).

Sound about right?
Photo of J.

J., Official Rep

  • 7,470 Points 5k badge 2x thumb
No worries! I certainly know how it goes. 

In short, yep, that sounds about right. The only additional thing that I would consider is some sort of server side enforcement on submit rather than just relying on JavaScript to enforce the "you need at least one of these" rule. For instance, if your submit button flips a status code to "Submit," you could use a Validation Rule or Trigger to check for a value in at least one of your Lookup Fields. It's optional, of course, but if you do want it enforced across the entire DB, you may find that you don't actually need the JavaScript check on submit either, come to think of it. 
Photo of Christopher Johnson

Christopher Johnson

  • 694 Points 500 badge 2x thumb
Wait a while, explore new options! Now that the new Skuid release is out, we are working on making this work with Chatter Files! That was our original desire a year ago and we waited long enough that it is becoming possible...we hope!

So now to see about adapting your attachment JS for chatter files. Before dealing with the minimum of one requirement, two things I attempted that didn't work as expected using out of the box functionality:
  • Add a render condition to the File Upload component. To try to cut off uploads at 3, I added a rendering condition to look at ContentDocumentLink, model property = # of rows in model, <= 3. I was hoping this would turn the button off after 3 uploads. My guess is it is only evaluated on page load, since it isn't working. Is that correct? Ways around or is off to JS to make it go?
  • Add a table to show the uploaded chatter files (and allow edit of title, description, or deletion of a row). I've tried using both ContentDocument and ContentDocumentLink objects in the model for this table and I get nothing...and no updating to the table after an upload. I'm hoping to allow for editing of those fields BEFORE actually saving the chatter file model data.
Any ideas on that? Any other suggestions or documentation on using the new file upload features in a user-friendly way? Wondering about use of a popup perhaps?