2
\$\begingroup\$

![enter image description hereContext- CRM system, front-end form.

Requirement- Within a form users have to give a rating to a section based on user inputs, each rating has different weighting. The rating is stored in an option set field. I have used JavaScript to calculate the average score of these rating fields. Also the weighting is stored in a separate option set field.

The option sets do not simply give me the score of say "1" they give me the value the option set is in the database " 778180000" from this I need to retrieve the score. Based on the option set value chosen I can set the score for example Option Set Chosen: "1- Poor performance" database value = 778180000, then from this I know I can set the score variable for section A to (1).

These percentage values could potentially change per business requirements. For example in the following year maybe Section A will increase to 50% AND Section B will change t0 20% this needs to be taken into account.

I am looking for code improvements. An how I can use functions / methods and objects. How could I implement some error handling?

*Beginner to JS.

function CalculateAverageScore(executionContext) {
    var formContext = executionContext.getFormContext();
    var ScoreOptionA = formContext.getAttribute("dp_testweightedaverage");
    var PercentageOptionA = formContext.getAttribute("dp_percentagevalue");
    var ScoreOptionB = formContext.getAttribute("dp_average2");
    var PercentageOptionB = formContext.getAttribute("dp_percentagevalue2");
    var ScoreOptionC = formContext.getAttribute("dp_pickascorefirmwide");
    var PercentageOptionC = formContext.getAttribute("dp_percentagevaluefirmwide");
// Define Scoring & Percentages 
    var scoreMap = {
        778180000: 1,
        778180001: 2,
        778180002: 3,
        778180003: 4,
        778180004: 5
    };

    var percentageMap = {
        778180000: 0.10,
        778180001: 0.20,
        778180002: 0.30,
        778180003: 0.40,
        778180004: 0.50
    };

    var SectionAScore, SectionAPercentage, SectionBScore, SectionBPercentage, SectionCScore, SectionCPercentage;
// Section A Score & Percentages 
    if (ScoreOptionA != null && PercentageOptionA != null) {
        var ScoreValueA = ScoreOptionA.getValue();
        var valuePercentageA = PercentageOptionA.getValue();
        SectionAScore = scoreMap[ScoreValueA];
        SectionAPercentage = percentageMap[valuePercentageA];
    }
//Section B Set Score & Percentages
    if (ScoreOptionB != null && PercentageOptionB != null) {
        var ScoreValueB = ScoreOptionB.getValue();
        var valuePercentageB = PercentageOptionB.getValue();
        SectionBScore = scoreMap[ScoreValueB];
        SectionBPercentage = percentageMap[valuePercentageB];
    }
// Section C Score & Percentages 
    if (ScoreOptionC != null && PercentageOptionC != null) {
        var ScoreValueC = ScoreOptionC.getValue();
        var valuePercentageC = PercentageOptionC.getValue();
        SectionCScore = scoreMap[ScoreValueC];
        SectionCPercentage = percentageMap[valuePercentageC];
    }
// Calculations
    var Section1Score = (SectionAScore * SectionAPercentage);
    var Section2Score = (SectionBScore * SectionBPercentage);
    var Section3Score = (SectionCScore * SectionCPercentage);
    var averagescore = (Section1Score + Section2Score + Section3Score);
    // Set Average Score Value
    formContext.getAttribute("dp_decimal").setValue(averagescore);
}

\$\endgroup\$
4

3 Answers 3

5
\$\begingroup\$

I added what I hope are more explicitly stated object advantages for future changes

Give objects the functions using their properties. This is a critical key for code to have attributes like reusability, non-duplication, simple & limited changes, understanding, and readability.

Clients just "ask" (call the methods), they don't do the calculations. Then client code can look like this:

var optionA = new option (scoreOptionA, weighting);
var optionB = new option (scoreOptionB, weighting);
var optionC = new option (scoreOptionC, weighting);


console.log(optionA.toString()); 
console.log(optionB.toString()); 
console.log(optionC.toString()); 


var sum, average = 0;
var options = [];
options.push(optionA, optionB, optionC);
options.forEach(option => {sum += option.score});
average = sum / options.length;   
console.log("Average: ${average}");

// Calculating average using an `options` object
// Comprehension improvement is obvious when 
// operation details are wrapped in the object's functions 

var sections = new options();
sections.push(optionA, optionB, optionC);
console.log("Average: ${sections.average()}");

Take object function encapsulation to the max

// Imagine you're in a war and 
// the guy next to you must always load
// your rifle for you. You're gonna die!
// So why do that with objects?

// Pass constructor parameters!
// Now the client does not have to know 
//   A) there's an array in there and
//   B) What method(s) to call so the program doesn't blow up.


var sections = new options(optionA, optionB, optionC);
console.log("Average: ${sections.average()}");

I was confused because of how things are named. I had to read and re-read to understand structure, relationships, and hierarchy. Naming things with "int" "array" etc. is bad, very bad. What is that thing to the users? That's what it IS. Name for what things they represent, not the Javascript technicalities used to make them.

Objects are just a way to organize data/properties AND associated functions. Objects without functionality is like a day without sunshine.

the following year maybe Section A will increase to 50% AND Section B will change t0 20% this needs to be taken into account.

weighting object accommodates change because it is an object. All the adjustment factors are encapsulated in one place.

var weighting = function () {
  var weightForMe = [
    { id: 778180000, weight: 1, percent: 0.1 },
    { id: 778180001, weight: 2, percent: 0.2 },
    { id: 778180002, weight: 3, percent: 0.3 },
    { id: 778180003, weight: 4, percent: 0.4 },
    { id: 778180004, weight: 5, percent: 0.5 }
  ]

  function weightOf ( ratingBase ) => weightForMe.find(rating.id === ratingBase);

} // weighting

These percentage values could potentially change per business requirements.

  • If changes do not affect all the options/sections then passing in a weighting object, a.k.a. "Dependency Injection" makes it trivial to create another "weighting" object with different values.
  • 'option' (vice individual variables) dramatically improves readability and understandability.
  • Note that option` does not change at all! Just pass in the appropriate "scales" object.

.

function option ( rating, scales ) {
  base: rating ,
  weights: scales.weightOf(rating),
  score : weights.weight * weights.percent 

  function toString() {
    return "Section: ${base} || score: ${score} || weight: ${weights.weight || percent: ${weights.percent}";
  }    
} // section

Why are weighting and option different objects when they are virtually identical? -> They have different purposes. weighting is just a data lookup structure while option is user-entered something-or-other that is adjusted per to user requirments.

I created the option object (is it a section?) because its properties are manipulated together. option object is the proper place to calculate an option's weighted score. Then it dawned on me the calculation can be done when the object is created. We have sunshine!

weighting passed to option makes it clear that option uses weighting. If we don't practice good coding techniques in simple code we won't do it - or know how - when things are complicated.

Overriding toString is exceptionally useful in development and testing.

\$\endgroup\$
1
  • \$\begingroup\$ Thank you for your comment! Super helpful, I am a beginner to JS so did not really understand how we could use objects. I have updated the post with some more context and added my updated script based on your feedback. Could you please inform me if this looks more suited? What else could I improve? Thank you for responding. \$\endgroup\$
    – BenDev21
    Feb 22 at 14:33
3
\$\begingroup\$

You're right, that sure is a lot of repetition.

We're mapping one number to another, using many ifs. That can also be done with an object that maps from e.g. dictionary key 778180000 to 1.

But this particular mapping happens to be linear. So you could define a function which will return ScoreValueA - 778179999;

The others are similar.


In general, when you see that a function has grown long enough that you can no longer view the whole thing without scrolling, it is time to Extract Helper functions.

\$\endgroup\$
1
  • \$\begingroup\$ Thank you for your comment! Super helpful, I am a beginner to JS so did not really understand how we could use objects. I have updated the post with some more context and added my updated script based on your feedback. Do you have any further advice on my newer code? Am I using objects correctly? Could a nice function be added? \$\endgroup\$
    – BenDev21
    Feb 22 at 14:41
2
\$\begingroup\$

Think about arrays, arrays of objects.

see here https://jsfiddle.net/9w2n34hf/

var scoreItemArray = [
  // A
  new scoreItemObject("dp_testweightedaverage", "dp_percentagevalue",
  [
    { id: 778180000, ival: 1, pc: 0.1 },
    { id: 778180001, ival: 2, pc: 0.2 },
    { id: 778180002, ival: 3, pc: 0.3 },
    { id: 778180003, ival: 4, pc: 0.4 },
    { id: 778180004, ival: 5, pc: 0.5 },

function CalculateAverageScore(executionContext) {
    var formContext = executionContext;
    var ip = getIntPercent(formContext, scoreItemArray[0]);
    var Section1Score = getIntPercent(formContext, scoreItemArray[0]);
    var Section2Score = getIntPercent(formContext, scoreItemArray[1]);
    var Section3Score = getIntPercent(formContext, scoreItemArray[2]);
    // Calculate and Set Average Score Value
    var averagescore = (Section1Score + Section2Score + Section3Score) / 3;
    event.preventDefault();
    my_formValSet(formContext, "dp_decimal", averagescore);
    }
\$\endgroup\$
4
  • 1
    \$\begingroup\$ Thank you for your comment! Super helpful, I am a beginner to JS so did not really understand how we could use objects. I have updated the post with some more context and added my updated script based on your feedback. Do you have any further advice on my newer code? Am I using objects correctly? Could a nice function be added? BTW super cool what you put together on here jsfiddle.net/9w2n34hf \$\endgroup\$
    – BenDev21
    Feb 22 at 14:42
  • \$\begingroup\$ For objects, the rough idea is to keep related stuff together. @Radarbob above mentions putting functions into the objects, which is a next step along the road of keeping stuff together. \$\endgroup\$
    – jmullee
    Feb 22 at 15:06
  • \$\begingroup\$ For your Qs from DB, I guess something like { id:7781234, q: "question .text.?", .answers: [ { val: 72, ans: "maybe", percent: 20 }, { val: 37, ans: "always", percent: 50 }, etc. Code could use this basic data to build the form questions, answers, and do scoring \$\endgroup\$
    – jmullee
    Feb 22 at 15:10
  • \$\begingroup\$ Think about arrays, arrays of objects and loops… \$\endgroup\$
    – greybeard
    Feb 23 at 20:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.