• Design
  • Shotty
  • Blog
  • Reading
  • Photos
Menu

Jacob Ruiz

Product Designer
  • Design
  • Shotty
  • Blog
  • Reading
  • Photos
checkCurrencyFormat@2x.png

Mastering Javascript Fundamentals: AccountingJS, checkCurrencyFormat

May 13, 2018

Get the fundamentals down and the level of everything you do will rise. - Michael Jordan

As stated in my original post, I do 1 hour of video lessons from Watch and Code every day. If you're interested in learning Javascript in a way that goes beyond basic tutorials and gives you a foundational, practical knowledge without relying on frameworks - I'd highly recommend it. If you're reading these posts, please keep in mind that these are just my notes, and I'm not an expert (yet!). If your goal is also to master the fundamentals of Javascript, please head over to Watch and Code and start your journey there!

All screenshots were annotated using Shotty.


checkCurrencyFormat

A good way to structure comments before a function:

  1. Explanatory paragraph that provides context.
  2. Parameters: What does this function expect?
  3. Returns: What will it return?

In the case of AccountingJS, we can distill this wordy comment:

    /**
     * Parses a format string or object and returns format obj for use in rendering
     *
     * `format` is either a string with the default (positive) format, or object
     * containing `pos` (required), `neg` and `zero` values (or a function returning
     * either a string or object)
     *
     * Either string or format.pos must contain "%v" (value) to be valid
     *
     */

Into this more concise and clear comment:

    /**
     * Parses a format string or object and returns format obj for use in rendering
     *
     * Parameters:
     * string     has "default positive format", must contain "%v"
     * object     has 'pos' (required, must contain "%v"), 'neg', 'zero' properties
     * function   returns a string or object like above
     * 
     * Returns:
     * object
     *
     */

Now let's move into the body of the function:

The first line makes a variable defaults that is equal to lib.settings.currency.format:

Screen Shot 2018-05-14 at 10.38.18 AM.png

Which, earlier in the program, is set to "%s%v":

Screen Shot 2018-05-14 at 10.38.38 AM.png

These "%s" and "%v" symbols allow us to control the format of our currency: whether the symbol should come before the value, or vice versa. It also allows us to put a space in between if we want.

The next line is pretty simple:

Screen Shot 2018-05-14 at 10.45.59 AM-annotated.png

It checks to see whether the format parameter is a function (using typeof), and if it is, it sets format to the return value of the format() function. The return value should be a string or an object.

The next line checks to see if format is a string. Then it uses the match method that strings have to check to see if the string contains "%v" inside of it.

Screen Shot 2018-05-14 at 10.49.05 AM-annotated.png

.match simply checks to see if a given value is in a string. For example:

'jacob'.match('j') // ['g']
'jacob'.match('z') // null

And since the corresponding boolean value for any array will be true, this will give us a true or false result to the right of the && in the if statement. If both conditions are true: format is a string AND it contains "%v", then we go on to the code inside the if case:

Screen Shot 2018-05-14 at 11.00.03 AM-annotated.png

Remember from our comments that the function checkCurrencyFormat must return an object. So this is simply returning our object with the formats for the positive, negative, and zero cases.

In the negative case, it's doing some extra work to remove any negative sign that might have been in the string beforehand, and then adding it back in directly in front of the value. The idea here is to give a consistent format for negative numbers. However, this behavior is never really described, and could be surprising to a user who tried to format their negatives as "-- -$v", for example.

Ok so that handles the case where our format argument is a string. Well what if it's not a string and doesn't contain "%v"?


Question:

What if you wanted the format of your negative numbers to be in parentheses, without a negative sign? Wouldn't this code put an unwanted negative sign in there?

For example:

'(%s%v)'.replace("-", "").replace("%v", "-%v");
// returns: "(%s-%v)"
Thank you!

Continuing on: Then we go to the else if section:

Screen Shot 2018-05-14 at 11.20.59 AM-annotated.png

The point of this section is that if we had passed in an invalid string for format then it will create a default object and return it.

It will also return a default object if you passed in an object originally, but it was missing something like format.pos.

This part is a little confusing, and the ternary operator makes things hard to read:

Screen Shot 2018-05-14 at 11.20.59 AM-annotated 1.png

If defaults is not a string, then it will simply return defaults. And remember that defaults is the value from lib.settings.currency.format from the very top of the file.

Screen Shot 2018-05-14 at 12.05.20 PM-annotated.png

If it's not a string, then it's already on object, so we can just return it. That's what's happening here:

Screen Shot 2018-05-14 at 12.07.48 PM.png

If defaults is a string, then it will return lib.settings.currency.format, which is going to be set to this object:

Screen Shot 2018-05-14 at 12.10.31 PM.png

The point of this is this scenario:

If you run checkCurrencyFormat one time, and say we pass in an invalid string, we then have to return the defaults object right here:

Screen Shot 2018-05-14 at 12.13.08 PM.png

Then we run into the block of code pictured above, and lib.settings.currency.format will initially be a string. But then we're going to change it to an object. And that's not going to affect the first time we run it, but the second time we run it, if we have to return the defaults object again, this part is going to be true...

Screen Shot 2018-05-14 at 12.15.31 PM.png

...so we can just return the object immediately:

Screen Shot 2018-05-14 at 12.16.51 PM.png

Instead of having to create a new object:

Screen Shot 2018-05-14 at 12.17.34 PM.png

And that's why the comment says that it "casts it to an object for faster checking next time".

But really the performance difference is going to be marginal. You probably won't be able to tell the difference. But they've chosen to do it this way, so it's important to be able to read it to understand the code. It makes the code quite a bit more confusing.

To recap, what's going to happen here is, if defaults is NOT a string, that means it's an object, which means we've already gone through this logic once and lib.settings.currency.format is already an object. If defaults is still a string, it's going to go to this part of the ternary operator:

Screen Shot 2018-05-14 at 12.17.34 PM.png

It's going to set lib.settings.currency.format to this new object -- which, now that we've seen the logic here:

Screen Shot 2018-05-14 at 12.23.44 PM.png

It's pretty much the same thing. 

Screen Shot 2018-05-14 at 12.24.34 PM.png

So the positive is going to be the defaults, which at this point is a string, zero is going to be the same, and the negative, we're going to take the value and just throw a negative sign on the front. 

Finally, if the format argument to checkCurrencyFormat is not a string, and there's nothing wrong with the object, then you'll arrive at this case:

Screen Shot 2018-05-14 at 12.28.06 PM.png

Those are all the different possibilities going step by step through the code.

Code like this can be very hard to understand. Each specific step isn't complicated, but having gone through this entire thing, you might be uncomfortable because you find it difficult to summarize what happens in all the different cases. It's hard to keep track of in your head.

One thing you can do to fix this problem is to simply write out the different scenarios in the comments:

// Scenarios:
// A: 
// B:
// C:
// D:
// E:
// F:

First, we know the parameter, format, can be a string -- but more than just a string, it can be a valid string or an invalid string:

// Scenarios:
// A: Valid string
// B: Invalid string
// C:
// D:
// E:
// F:

The other thing we know is that it can be a valid object or an invalid object.

// Scenarios:
// A: Valid string
// B: Invalid string
// C: Valid object
// D: Invalid object
// E:
// F:

We also saw that format can also be a function:

// Scenarios:
// A: Valid string
// B: Invalid string
// C: Valid object
// D: Invalid object
// E: Function
// F:

You could also run checkCurrencyFormat and not pass in anything. So "nothing" is another scenario:

// Scenarios:
// A: Valid string
// B: Invalid string
// C: Valid object
// D: Invalid object
// E: Function
// F: Nothing

Now let's look at what happens in each of these cases.

In the first one, it will convert the string to a format object:

// Scenarios:
// A: Valid string    ==> convert string to a format object
// B: Invalid string
// C: Valid object
// D: Invalid object
// E: Function
// F: Nothing

In the second case where we have an invalid string, we'll try to create a default object -- we saw this logic here in the else if section:

Screen Shot 2018-05-14 at 12.40.17 PM.png

To summarize that, we could say "use default and turn it to an obj if it's not already:

// Scenarios:
// A: Valid string    ==> convert string to a format object
// B: Invalid string  ==> use default and turn it to an obj if it's not already
// C: Valid object
// D: Invalid object
// E: Function
// F: Nothing

So the very first time you run checkCurrencyFormat, format is probably going to be a string, but that first run-through will change the default all the way at the top:

Screen Shot 2018-05-14 at 12.43.58 PM.png

It will change this format property from a string to an object. That's why we can say "use default and turn it to an obj if it's not already."

The third case, and this is a good case, is if you pass in an object for format and there's nothing wrong with it -- in that case you can just (at the very bottom) assume everything is fine and simply return the format object. You don't have to do anything.

Screen Shot 2018-05-14 at 12.47.17 PM.png

You can just return format immediately.

So in this case we can leave the object alone:

// Scenarios:
// A: Valid string    ==> convert string to a format object
// B: Invalid string  ==> use default and turn it to an obj if it's not already
// C: Valid object    ==> leave the object alone
// D: Invalid object
// E: Function
// F: Nothing

If we have an invalid object, this is very similar to having an invalid string. We use default and turn it into an obj if it's not already:

// Scenarios:
// A: Valid string    ==> convert string to a format object
// B: Invalid string  ==> use default and turn it to an obj if it's not already
// C: Valid object    ==> leave the object alone
// D: Invalid object  ==> use default and turn it to an obj if it's not already
// E: Function
// F: Nothing

If we have a function, it falls into any of the other cases. It depends on what the function returns:

// Scenarios:
// A: Valid string    ==> convert string to a format object
// B: Invalid string  ==> use default and turn it to an obj if it's not already
// C: Valid object    ==> leave the object alone
// D: Invalid object  ==> use default and turn it to an obj if it's not already
// E: Function        ==> depends on what the function returns
// F: Nothing

The final case -- if you don't pass in anything into checkCurrencyFormat, then it will try to use default and turn it to an object it's not already:

// Scenarios:
// A: Valid string    ==> convert string to a format object
// B: Invalid string  ==> use default and turn it to an obj if it's not already
// C: Valid object    ==> leave the object alone
// D: Invalid object  ==> use default and turn it to an obj if it's not already
// E: Function        ==> depends on what the function returns
// F: Nothing         ==> use default and turn it to an obj if it's not already

An exercise like this is interesting to run through the scenarios and note things like: there are six possible things that you can pass in to the function, but if you look at the actual outcomes, there are only four unique outcomes. Six possible inputs, but only four unique outputs.

Summary:

  • checkCurrencyFormat takes an argument, format that can be a string, object, or function.
  • It returns a format object
  • So if the format parameter is a string, it changes it to an object and returns it.
  • If format is a valid object, it simply returns it back, untouched.
  • If format is an invalid object, it uses the default (lib.settings.currency.format) and changes it to an object if it isn't already.
Thank you!
← Mastering Javascript Fundamentals: AccountingJS, toFixed and Rounding IssuesMastering Javascript Fundamentals: AccountingJS Internal Helper Methods (Part 3): map() →
shotty-skinny2x.jpg

Shotty - Faster Access To Your Screenshots on Mac

Shotty is an award-winning Mac app I created to give you instant access to all your recent screenshots, right from the menu bar. You can even add annotations on-the-fly. Stop wasting time digging through Finder for your screenshots. I promise it’ll change your workflow forever (just read the App Store reviews!).



Most popular

information-architecture

Information Architecture: The Most Important Part of Design You're Probably Overlooking

Follow @JacobRuizDesign