JavaScript & Script Coding Best Practice

When using JavaScript within Synergy or CallScripter there are a number of industry best practice principles that can be applied to larger projects.
As principles they may not all apply to every situation and can require judgement in implementation.
Some of these principles are interrelated and should be read in order.
 
 

Use JS Exceptions

When an error scenario is reached use a JavaScript exception to flag the issue up to the highest level of JS possible for it to be dealt with. You will see examples of this in the other use cases below.
 
 

Using Error Catching Code Structure

Write the logical parts of code so that should any data combination not be valid it is caught and doesn't just fall through. This stops odd results or errors happening when unexpected data is entered.
 
Non Catching Code
Passing in "true" or "False" would work as expected, but "Bob" or "1" would come back as false. Would this be expected by others if they used the function?
function IsStringTrue (stringToCheck) {
    if(stringToCheck.toLowerCase() == "true") {
      return true;
    }
    else {
        return false;
    }
}
 
Catching Code
Passing in "true" or "False" would work as expected and now "Bob" and "1" both throw an error.
function IsStringTrue (stringToCheck) {
    if(stringToCheck.toLowerCase() == "true") {
      return true;
    }
    else if(stringToCheck.toLowerCase() == "false") {
        return false;
    }
    else {
        throw "IsStringTrue function had a non bool string passed to it";
    }
}
 

Store Code in Precise Functions

All code should ideally be stored within a function to allow re-use. Each function should be as precise and short as possible to allow for as much re-use as possible and to keep the functions use simple.
By having smaller precise functions it is also far easier to appreciate, design and handle errors.
 
All In One Function
function DoAgeChecks (age, targetAge, above, minimumAge, maximumAge) {
    if(Number.isInteger(age) &&  Number.isInteger(targetAge)) {
        if(above && age > targetAge) {
            return true;
        }
        else if(!above && age < targetAge) {
            return true;
        }
        else {
            return false;
        }
    }
    else if(Number.isInteger(age) && Number.isInteger(minimumAge) && Number.isInteger(maximumAge) {
        if(age >= minimumAge && age <= maximumAge) {
            return true;
        }
        else {
            return false;
        }
    }
    else {
        throw "DoAgeChecks called without valid parameter options, would be a pain to work out which one is missing";
    }
}
 
Seperate Functions
function CheckAgeIsOver (age, targetAge) {
    if(Number.isInteger(age) && Number.isInteger(targetAge))
        if(age > targetAge) {
            return true;
        }
        else {
            return false;
        }
    }
    else {
        throw "CheckAgeIsOver called with invalid age or targetAge";
    }
}


function CheckAgeIsUnder (age, targetAge) {
    if(Number.isInteger(age) && Number.isInteger(targetAge)) {
        if(age < targetAge) {
            return true;
        }
        else {
            return false;
        }
    }
    else {
        throw "CheckAgeIsUnder called with invalid age or targetAge";
    }
}

function CheckAgeIsBetween (age, minimumAge, maximumAge) {
    if(Number.isInteger(age) && Number.isInteger(minimumAge) && Number.isInteger(maximumAge)) {
        if(age >= minimumAge && age <= maximumAge) {
            return true;
        }
        else {
            return false;
        }
    }
    else {
        throw "CheckAgeIsBetween called with invalid age or minimumAge or maximumAge";
    }
}
 
 

Use Nested Functions

As part of "Store Code in Precise Functions" you will have a lot of nested JS functions calling other JS functions during their execution. This is good and is how the built in JS functions work.
This can be seen in the "Store Code in Precise Functions" example where the built in "Number.IsInteger()" function is called. If nested functions weren't being used I would have had to check the string wasn't blank and that it converted to a number successfully multiple times in my functions.
 
 

Self Descriptive Naming

By using self descriptive naming in all opportunities the need for separate comments and documentation can be reduced.
A self descriptive name is when a control name, control description or JS function name describes the behaviour and context sufficiently for the calling code to be human readable.
Note: both examples below has descriptive CS variable names (var_xxx) to enhance the examples readability.
 
 
Non Descriptive JS Control example
Here it is hard to see what what generally this code section does and what exactly is going to happen when its run. We may have had to open multiple other controls to find this one.
Control Name: Calculate_23
Control Description: [BLANK]
Control Code:
if([var_celebration_type] == "birthday") {
    [var_agent_message] = addIt([var_caller_name], "Happy Birthday!";
}
else if([var_celebration_type] == "wedding aniversary") {
    [var_agent_message] = addIt([var_caller_name], "Happy Wedding Anniversary!";
}
else if ........

........
........

function addIt(text1, text2) {
    var text3= text1 +"   " + text2+ "   " + text1;
    return text3;
}
 
Descriptive JS Control example
Here every opportunity to make the code human readable and self descriptive has been done. The changes have been highlighted yellow.
Control Name: Set Agent Message text
Control Description: Set agent message from callers name and celebration type
Control Code:
if([var_celebration_type] == "birthday") {
    [var_agent_message] = wrapTextWithExtraText([var_caller_name], "Happy Birthday!";
}
else if([var_celebration_type] == "wedding aniversary") {
    [var_agent_message] = wrapTextWithExtraText([var_caller_name], "Happy Wedding Anniversary!";
}
else if ........

........
........

function wrapTextWithExtraText(middleText, wrappingText) {
    var newText = wrappingText +"   " + middleText + "   " + wrappingText;
    return newText;
}
 
 

Large Scale Projects

  • Error handling should be configurable so you can easily switch between debug (working on the script) and usage by the end user (friendly error messages).
  • Separate JS function containers should be used to store collections of related functions. As a "GetAge" function would behave differently based on its context.
  • Separate JS function containers also prevent custom code affecting the main Synergy/CallScripter product. This can be seen in all 3rd party libraries, ie: LIBRARY.FUNCTION
  • Always select explicit data items and never rely upon data happening to have attributes in the right order or the data being ordered logically. While often it will be, occasionally it may come through differently. By explicitly stating desired data attributes and ordering you are protected from these oddities.