Custom picklist renderer when option source is a model

I would like to render a custom picklist for a reference field, populating the picklist with rows from a model. There are two reasons that the standard picklist renderer with a model source doesn’t seem to meet my needs in this case:

  1. When the field is in a non-editable state I don’t want it to render a link - just text.
  2. The field is in a table, and ideally I would like to prevent the same item being selected twice in different rows by hiding items that are already in use.
I have seen your code example for a custom renderer for a SFDC picklist field: http://community.skuid.com/t/how_do_i_render_custom_picklist_values This example doesn’t seem to work for my purpose because “field.metadata.picklistEntries” returns null when the component option source field is a model.

Ryan, You should be able to do this with a snippet, a model (to select the values that you want in the drop down which it sounds like you’ve already built this), and a custom field renderer. The solution should be similar to the one outlined in our community post on creating a drop down list from a custom setting. In any case, here’s what a snippet like this might look like using a custom reference field of Contact__c and a model called “Contacts”:

var field = arguments[0]; var value = arguments[1]; if (field.mode === 'edit') { // Build the Options for the PICKLIST var customOpts = []; skuid.$.each(skuid.model.getModel('Contacts').getRows(), function(i,row) { customOpts.push({ value : row.Id, // Will be stored in target object label : row.Name // Will display in the PICKLIST }); }); // Render the options as a PICKLIST var customSelect = skuid.ui.renderers.PICKLIST.edit({ entries : customOpts, required : false, value : value }).change(function() { console.log(skuid.$(this)); // Update the row in the target object field.model.updateRow(field.row,'Contact__c',skuid.$(this).val()); }); // Append the PICKLIST to the DOM element field.element.append(customSelect); } else { // If the mode is anything other than edit, display the field as Text skuid.ui.fieldRenderers.TEXT[field.mode](field,field.model.getFieldValue(field.row,'Contact__r.Name')); }

The added complication is making sure that the same row isn’t selected twice. You could enforce this at the model condition level (using our new “not in” operator for subquery conditions, for instance) and at the DB level with a trigger, but this wouldn’t keep track of which rows have been selected and unselected while you are in edit mode. You would have to write some pretty complicated JavaScript to accomplish this, so it may be more trouble than it’s worth. Does this help you out?

Thanks J, I’ve only just gotten back around to implementing this now, but it worked perfectly. Would it be possible to do an autocomplete field in the same way, so the text isn’t clickable when it’s not in edit mode? Ryan

J I have been trying to use this snippet but I have been getting an error that “value is undefined” on this section:

var customSelect = skuid.ui.renderers.PICKLIST.edit({

entries : customOpts, required : false, value : value }) It works if I change "value : value" to "value : customOpts" but then the changes don't show on the picklist, it stays on the first selection.. here is my full snippet 
var field = arguments[0],&nbsp; &nbsp; values = skuid.utils.decodeHTML(arguments[1]);<br>// &nbsp;Build the Options for the PICKLIST<br>var customOpts = [];<br>&nbsp; &nbsp; customOpts.push({<br>&nbsp; &nbsp; &nbsp; &nbsp; value : 'LDC', &nbsp; &nbsp; &nbsp; &nbsp; // Will be stored in target object&nbsp;<br>&nbsp; &nbsp; &nbsp; &nbsp; label : 'Single Bill' &nbsp; &nbsp; &nbsp; &nbsp;// Will display in the PICKLIST<br>&nbsp; &nbsp; },{<br>&nbsp; &nbsp; &nbsp; &nbsp; value : 'Dual', &nbsp; &nbsp; &nbsp; &nbsp; // Will be stored in target object&nbsp;<br>&nbsp; &nbsp; &nbsp; &nbsp; label : 'Dual Bill' &nbsp; &nbsp; &nbsp; &nbsp;// Will display in the PICKLIST<br>&nbsp; &nbsp; });<br>// &nbsp;Render the options as a PICKLIST<br>var customSelect = skuid.ui.renderers.PICKLIST.edit({<br>&nbsp; &nbsp; entries : customOpts,<br>&nbsp; &nbsp; required : true,<br>&nbsp; &nbsp; value : customOpts<br>}).change(function(){<br>&nbsp; &nbsp; console.log(skuid.$(this));<br>&nbsp; &nbsp; &nbsp; &nbsp; // &nbsp;Update the row in the target object<br>&nbsp; &nbsp; &nbsp; &nbsp; field.model.updateRow(field.row,'LDC_Rules__c',skuid.$(this).value);<br>&nbsp; &nbsp; });<br>// &nbsp;Append the PICKLIST to the DOM element<br>field.element.append(customSelect);

Moshe,

If you change…

var field = arguments[0],&nbsp; &nbsp; values = skuid.utils.decodeHTML(arguments[1]);

…to…

var field = arguments[0],&nbsp; &nbsp; <b>value</b> = skuid.utils.decodeHTML(arguments[1]);

…does that work?

Also, you can drop that “console.log(skuid.$(this));” bit. That’s just a slightly embarrassing slip on my part to leave debugging code in the “official response” to community question.

Yes I caught that values issue, but oddly it didn’t work after I fixed that. My actual problem seems to have been that instead of simply overwriting a picklist, I decided to try to get a filtered lookup to show up as a picklist!?!?! After I actually thought about what I was doing and switched to playing with a simple picklist life was great. Which leads to an interesting point about skuid which is that you can’t create dummy fields. My org has started using a ton of skuid for data entr, but we use apex webservices for the DML. Because of this I’ve been making a ton of dummy fields depending on the various scenarios that we have. Which has been an interesting experience. I would suggest that skuid add the ability to simply create a dummy field to hold data to be passed to a webservice. This would seriously limit the amount of custom field renderers I’ve been writing (although they are kind of fun). I appreciate the help as always. 

No problem! That’s an interesting suggestion, one that we’ll certainly take in to consideration. We don’t have anything at present, but the idea of “Ui only” fields has come up before. You may already be doing something like this, but one way we’ve approached this in the past is to centralize the Javascript Remoting calls in a Static Resource which is accessible by multiple pages (e.g., all the pages in a module named “LDC” automatically include the “LDCJS” Static Resource). You may be able to store some of your custom renderers like this too.

Since you can use a custom field renderer on any field, you don’t have to create a dummy field for your “LDS_Rules” Picklist, even if you are using core Skuid components for your layout like Table and Field Editor. Drop an ID field into your Table or Field Editor and set the Field Renderer to a snippet which builds out your Picklist. You’ll have to handle your own submits (e.g., go get the selected value and send it off to Apex for processing), but our newly release Actions Framework makes this a little easier than it used to be. You can save the record in the table/field editor like you always did before with Skuid, but then run a snippet which does the callout if the initial save was successful, for instance.


Ok for the record I am already using a lot of custom fields to do exactly that get selected values and pass them off to apex we actually have a snippet we just used, with 200 lines worth of getting fields. Recently we’ve started using JSON to stringify a skuid row and then process it in apex which is way more efficient. Thanks for the tips.

Oh yeah, JSON.stringify and JSON.deserialize (on the Apex side) are really great. It sounds like you were already moving the way that I was thinking too. I hope all this helps!

Hey J, I ran into another use case for me to use this snippet. I am turning a number field into a picklist with values that increment by 5. So basically I’m adding 5,10,15,20 etc to a picklist. It’s working great except that if the current value is 20, and the user clicks edit, the picklist seems to rebuild itself so the selected value shows up as 5 (which is the lowest number). Is there some kind of “setSelectedIndex()” function built in or something that can have the value start off as the current value of the field? My snippet looks like this:

var field = arguments[0],&nbsp; &nbsp; value = skuid.utils.decodeHTML(arguments[1]),<br>&nbsp; &nbsp;$ = skuid.$;<br>if(field.mode === 'edit'){<br>&nbsp; &nbsp; var customOpts = [];<br>&nbsp; &nbsp; &nbsp; &nbsp; customOpts.push({<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value : 5, &nbsp; &nbsp; &nbsp; &nbsp; // Will be stored in target object&nbsp;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; label : '5' &nbsp; &nbsp; &nbsp; &nbsp;// Will display in the PICKLIST<br>&nbsp; &nbsp; &nbsp; &nbsp; },{<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value : 10, &nbsp; &nbsp; &nbsp; &nbsp; // Will be stored in target object&nbsp;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; label : '10' &nbsp; &nbsp; &nbsp; &nbsp;// Will display in the PICKLIST<br>&nbsp; &nbsp; &nbsp; &nbsp; },{<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value : 15, &nbsp; &nbsp; &nbsp; &nbsp; // Will be stored in target object&nbsp;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; label : '15' &nbsp; &nbsp; &nbsp; &nbsp;// Will display in the PICKLIST<br>&nbsp; &nbsp; &nbsp; &nbsp; },{<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value : 20, &nbsp; &nbsp; &nbsp; &nbsp; // Will be stored in target object&nbsp;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; label : '20' &nbsp; &nbsp; &nbsp; &nbsp;// Will display in the PICKLIST<br>&nbsp; &nbsp; &nbsp; &nbsp; },{<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value : 25, &nbsp; &nbsp; &nbsp; &nbsp; // Will be stored in target object&nbsp;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; label : '25' &nbsp; &nbsp; &nbsp; &nbsp;// Will display in the PICKLIST<br>&nbsp; &nbsp; &nbsp; &nbsp; },{<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value : 30, &nbsp; &nbsp; &nbsp; &nbsp; // Will be stored in target object&nbsp;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; label : '30' &nbsp; &nbsp; &nbsp; &nbsp;// Will display in the PICKLIST<br>&nbsp; &nbsp; &nbsp; &nbsp; },{<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value : 35, &nbsp; &nbsp; &nbsp; &nbsp; // Will be stored in target object&nbsp;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; label : '35' &nbsp; &nbsp; &nbsp; &nbsp;// Will display in the PICKLIST<br>&nbsp; &nbsp; &nbsp; &nbsp; },{<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value : 40, &nbsp; &nbsp; &nbsp; &nbsp; // Will be stored in target object&nbsp;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; label : '40' &nbsp; &nbsp; &nbsp; &nbsp;// Will display in the PICKLIST<br>&nbsp; &nbsp; &nbsp; &nbsp; });<br>&nbsp; &nbsp; //Render the options as a PICKLIST<br>&nbsp; &nbsp; var customSelect = skuid.ui.renderers.PICKLIST.edit({<br>&nbsp; &nbsp; &nbsp; &nbsp; entries : customOpts,<br>&nbsp; &nbsp; &nbsp; &nbsp; required : true,<br>&nbsp; &nbsp; &nbsp; &nbsp; value : customOpts<br>&nbsp; &nbsp; }).change(function(){<br>field.model.updateRow(field.row,'Bandwidth__c',skuid.$(this).val(),{initiatorId: field._GUID});<br>alert('Bandwidth changes will not be sent to the Pricing Team automatically, you must notify them yourself.');<br>});<br>&nbsp; &nbsp; //Append the PICKLIST to the DOM element<br>&nbsp; &nbsp; field.element.append(customSelect);<br>}else{<br>&nbsp; &nbsp; skuid.ui.fieldRenderers.INTEGER[field.mode](field,value);<br>}&nbsp;

Moshe,

I think the issue is with the “value : customOpts” part. Our PICKLIST renderer’s edit mode looks for two properties that have to deal with the selected value: value and defaultValue. Both are simple values (e.g. if you want “5” selected, set value or defaultValue to “5”). As you probably guessed, value takes priority over defaultValue.

In your snippet, you’re sending in the whole customOpts Array for value, so the renderer can’t find a match on value, and the resulting select box has the first value selected by browser default. Try this instead:

skuid.ui.renderers.PICKLIST.edit({ entries : customOpts, required : true, value : value })<br>

Does that seem to behave as you expect?

Perfect thanks J!

This is a super-helpful thread. Thanks, J., Moshe, and Ryan!

I’d like to include a defaultValue, but it doesn’t seem to be working the way I’d expect. Here’s my code:

var field = arguments[0], value = skuid.utils.decodeHTML(arguments[1]), nurseModel = skuid.$M('Nurses'), $ = skuid.$; if (field.mode === 'edit'){ var initials = [], userId = skuid.utils.userInfo.userId; $.each(nurseModel.data, function(i, row){ initials.push({ value: row.Initials__c, label: row.Initials__c, <b>defaultValue: (row.Id == userId) ? true : false</b> }); }); var customSelect = skuid.ui.renderers.PICKLIST.edit({ entries : initials, required : false, value : value }).change(function() { // Update the row in the target object field.model.updateRow(field.row, field.id, $(this).val()); }); field.element.append(customSelect); } else { skuid.ui.fieldRenderers.TEXT[field.mode](field,value); }

The goal here is to create a dropdown list of the initials of certain users. I have the Initials__c field on the Users object (the Nurses model). I want the value to default to the running user. I’m getting the correct true/false for defaultValue within initials, but it’s not coming through to the picklist.

Do I need to set the defaultValue in customSelect instead?

Matt,

Yep, you got it. You need to send the actual value of the option that you want selected into customSelect. defaultValue gets sent into the field renderer (skuid.ui.renderers.PICKLIST.edit), not set as a boolean on an individual option in the entries array (var initials in your code). Put another way (for anyone TL;DRing for code snippets)…

var customSelect = skuid.ui.renderers.PICKLIST.edit({ entries : initials, required : false, <strong>defaultValue : VALUE_OF_THE_OPTION_THAT_YOU_WANT_SELECTED</strong> })

Make sense?

Makes sense. Thanks!

This is great! I’m trying to use the results from a model as option source for a picklist also. Even though the records in the model are unique, the field I am using as the display value is creating duplicate entries in the picklist. Can anyone help point me in the direction how I would go about:

  1. Remove duplicate values from the picklist (aggregate model is not a solution since the field in question is a datetime field and I need more granularity that HOUR_IN_DAY)

  2. Define rules to hide / display values based on whether there are 2 instances / 3 instances of the duplicate (again, aggregate model and HAVING xml hack won’t apply here unfortunately)