Magic real time error/warning rendering using javascript snippet on a Table row??

I want my users to notice right away when they are making a mistake. 

We have a table of Project records, which includes a field showing the amount of revenue we have remaining to recognize. We have another field, “Current Month Estimate,” that we use to estimate how much of this remaining revenue we’ll be able to recognize this month. 

If we only have $500 remaining to recognize, and a user types in $90,000 in the estimate field, I need something to happen. Since I can’t send electrical shocks through salesforce, I am hoping I can use some kind of skuid javascript magic to prevent the user from making this kind of error. 

Validation rules are not an option. 

It would be great if it were a real time error popup or rendering the background of the field red, or preventing a save to that row and showing an error message…

Any ideas?

In our next release,  Skuid will be including a new “ElectroZap” component.   Just like rendering conditions,  you dictate the data conditions that activate,  and there are optional properties for how big a jolt it delivers. 

(Oh - its not still April 1st is it? )

Seriously.  In javascript you should be able to evaluate some data condition in real time - and if the condition is true turn on a style.  Maybe the whole page background could turn red,  or a div could be changed from hidden to exposed.  Etc. 

Here is an example I threw together yesterday. I use a Custom Render Snippet on the Amount field on a Table of Opportunities to prevent users from entering an Amount > $1 Billion. If they do, I revert the user’s input to whatever value they last entered, and display a red field-level error message.

Here’s the end product:

And here’s how you do it:

1. add an Inline CSS resource, to define the styles we want to apply to the field when the user’s input is bad.

.field-error-messages {    
   color: red;
    padding: 2px;
}
.my-required-field textarea, .my-required-field input  {
    border: 1px solid #D00;
    border-right: 4px solid #D00;
}

2. add a new JavaScript Resource of type Inline (Snippet), called “AmountSanityCheck”, with the following Body:

var field = arguments[0],    
   value = skuid.utils.decodeHTML(arguments[1]),
    $ = skuid.$;
    
// Run the default renderer    
skuid.ui.fieldRenderers[field.metadata.displaytype][field.mode](field,value);
if (field.mode === 'edit') {
    
    var errorMessageBox;
    var addFieldError = function(errorMessage) {
        if (!errorMessageBox) {
           errorMessageBox = field.element.find('.field-error-messages');
           if(!errorMessageBox.length) {
               errorMessageBox = $('<div class="field-error-messages">');
               field.element.append(errorMessageBox);
           }
       }
       errorMessageBox.show();
       field.element.addClass('my-required-field');
       errorMessageBox.text(errorMessage);
    };
    
    var input = field.element.find(':input');
    
    var MAX_VALUE = 1000000000;
    var inputValueIsBad = function(inputValue) {
        return parseFloat(inputValue,10)>=MAX_VALUE;
    }
    
    skuid.utils.delayInputCallback(input,function(newValue,oldValue){
        var val = input.val();
       if (inputValueIsBad(input.val())) {
           // Add an error
           addFieldError('Yeah right, a deal for $1B+? Check your numbers dude.');
           // And revert the value
           if (inputValueIsBad(oldValue)) oldValue = 999999999;
           field.model.updateRow(field.row,field.id,oldValue);
           input.val(oldValue);
       }  else {
           if (errorMessageBox) errorMessageBox.hide();
           field.element.removeClass('my-required-field');
       }
    });
}

3. Set your Amount field to use the AmountSanityCheck custom render snippet.

Thanks for the solution Zach. I tried this solution on a custom field of Date type. When I enter the date from Datepicker this is not working. It works if I punch in the date manually. Is there a different approach when datepicker is involved?

Hi Nayana, here is a version that will work for Date fields:

var field = arguments[0],
value = skuid.utils.decodeHTML(arguments[1]),
$ = skuid.$;

if (field.mode === ‘edit’) {

// We'll do our own Date rendering logic
var datePicker = skuid.ui.renderers.DATE.edit({
    value : value,
    onChange : function(newValue,oldValue) {
       checkValue(newValue,oldValue);
    }
 });

// This logic adds an error message in a "field-error-messages" area
var errorMessageBox;
var addFieldError = function(errorMessage) {
    if (!errorMessageBox) {
       errorMessageBox = field.element.find('.field-error-messages');
       if(!errorMessageBox.length) {
           errorMessageBox = $('<div class="field-error-messages">');
           field.element.append(errorMessageBox);
       }
   }
   errorMessageBox.show();
   field.element.addClass('my-required-field');
   errorMessageBox.text(errorMessage);
};

var minDate = new Date();
var inputValueIsBad = function(dateValue) {
    // If the Stage is NOT Closed,
    // then make sure the date is after today
    if ($.inArray(field.model.getFieldValue(field.row,'StageName',true),['Closed Won','Closed Lost'])===-1) {
        return skuid.time.parseSFDate(dateValue) < minDate;
    } else {
        // Otherwise allow any values at all
        return false;
    }
}

var doUpdate = function(val){
    field.model.updateRow(field.row,field.id,val,{ initiatorId : field._GUID});
};

var checkValue = function(newValue,oldValue){
   if (inputValueIsBad(newValue)) {
       // Add an error
       addFieldError('Close Date must be after today.');
       // And revert the value
       // If the OLD value is bad, then set it to Today
       if (inputValueIsBad(oldValue)) oldValue = skuid.time.getSFDate(minDate);
       // Set our field's value back to the old value
       doUpdate(oldValue);
   }  else {
       // Hide the error message
       if (errorMessageBox) errorMessageBox.hide();
       field.element.removeClass('my-required-field');
       // And perform the update with the new value
       doUpdate(newValue);
   }
};

field.element.append(datePicker);

} else {
// Run the default renderer
skuid.ui.fieldRenderers[field.metadata.displaytype]field.mode;
}

That worked! Thank you Zach!!

But the variable oldValue on checkValue(newValue,oldValue) is returning a null value for me.

Nayana, I think the onChange function actually only takes the new value. In the edit mode renderer, I tried replacing

onChange : function(newValue,oldValue) {<br>&nbsp;checkValue(newValue,oldValue);<br>}


with 

var datePicker = skuid.ui.renderers.DATE.edit({ &nbsp; &nbsp; &nbsp; &nbsp;value : value,<br>&nbsp; &nbsp; &nbsp; &nbsp;onChange : function(newValue) {<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; checkValue(newValue,value);<br>&nbsp; &nbsp; &nbsp; &nbsp;}<br>&nbsp; &nbsp; });


No guarantee that that’s the best way to do it, but it captures the old value.

Is this delayInputCallback() any method in skuid.utils API?

This method has been added to the skuid.utils API docs.