Custom picklist renderer when option source is a model

  • 3
  • Question
  • Updated 4 years ago
  • Answered
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.skuidify.com/skuid/t...

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.
Photo of Ryan Paddy

Ryan Paddy

  • 306 Points 250 badge 2x thumb

Posted 5 years ago

  • 3
Photo of J.

J., Official Rep

  • 7,470 Points 5k badge 2x thumb
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?
Photo of Ryan Paddy

Ryan Paddy

  • 306 Points 250 badge 2x thumb
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
Photo of Moshe Karmel

Moshe Karmel, Champion

  • 8,646 Points 5k badge 2x thumb
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],    values = skuid.utils.decodeHTML(arguments[1]);
//  Build the Options for the PICKLIST
var customOpts = [];
    customOpts.push({
        value : 'LDC',         // Will be stored in target object 
        label : 'Single Bill'        // Will display in the PICKLIST
    },{
        value : 'Dual',         // Will be stored in target object 
        label : 'Dual Bill'        // Will display in the PICKLIST
    });
//  Render the options as a PICKLIST
var customSelect = skuid.ui.renderers.PICKLIST.edit({
    entries : customOpts,
    required : true,
    value : customOpts
}).change(function(){
    console.log(skuid.$(this));
        //  Update the row in the target object
        field.model.updateRow(field.row,'LDC_Rules__c',skuid.$(this).value);
    });
//  Append the PICKLIST to the DOM element
field.element.append(customSelect);
Photo of J.

J., Official Rep

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

If you change...
var field = arguments[0],    values = skuid.utils.decodeHTML(arguments[1]);
...to...
var field = arguments[0],    value = 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.
Photo of Moshe Karmel

Moshe Karmel, Champion

  • 8,646 Points 5k badge 2x thumb
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. 
Photo of J.

J., Official Rep

  • 7,470 Points 5k badge 2x thumb
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.
(Edited)
Photo of Moshe Karmel

Moshe Karmel, Champion

  • 8,646 Points 5k badge 2x thumb
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.
Photo of J.

J., Official Rep

  • 7,470 Points 5k badge 2x thumb
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!
Photo of Moshe Karmel

Moshe Karmel, Champion

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

J., Official Rep

  • 7,470 Points 5k badge 2x thumb
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
})
Does that seem to behave as you expect?
(Edited)
Photo of Moshe Karmel

Moshe Karmel, Champion

  • 8,646 Points 5k badge 2x thumb
Perfect thanks J!
Photo of Matt Sones

Matt Sones, Champion

  • 31,478 Points 20k badge 2x thumb
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,
            defaultValue: (row.Id == userId) ? true : false
        });
    });
    
    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?
Photo of J.

J., Official Rep

  • 7,470 Points 5k badge 2x thumb
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,
defaultValue : VALUE_OF_THE_OPTION_THAT_YOU_WANT_SELECTED
})
Make sense?
Photo of Matt Sones

Matt Sones, Champion

  • 31,478 Points 20k badge 2x thumb
Makes sense. Thanks!