Skip to content

Instantly share code, notes, and snippets.

@benvds
Last active August 29, 2015 14:23
Show Gist options
  • Save benvds/529acf573b1d66bde9c9 to your computer and use it in GitHub Desktop.
Save benvds/529acf573b1d66bde9c9 to your computer and use it in GitHub Desktop.

Kalender – Technical considerations

These are some technical considerations for the Kalender component. It returns a calendar matrix containing day objects for a given month. It's implementation is an experiment in writing a Javascript component in a functional style.

When starting I keep things in one file even when there are multiple modules in it. No need to split things up until the it grows to about 150 lines. After 150 lines it gets harder to keep clarity and concepts need to be separated, named and have it's own place.

At the start of the project I mindlessly added lodash, thinking I'd be really needing helper functions like reduce and contains. Turned out I'd could do without. This makes a big difference dependency wise, the library now only has dependencies for building and testing but none are required to get things running.

Removing reduce and contains forced me to think about data structures a bit more. I like objects as data structures for the ability to nest, group and name values with keys. DAYS_PER_MONTH started as an object with the amount of days as the key. Calculating the amount of days using reduce & contains it looked like this:

var DAYS_PER_MONTH = {
    '28': [1],
    '30': [3, 5, 8, 10],
    '31': [0, 2, 4, 6, 7, 9, 11]
};

function amountOfDays(year, month) {
    return Number(_.reduce(DAYS_PER_MONTH,
                           function(result, months, daysForMonth) {
        return _.contains(months, month) ? daysForMonth : result;
    }, 0));
 }}

The function is pretty vague, handling leap days was therefore handled elsewhere which isn't so good. After taking out lodash DAYS_PER_MONTH just became an array of numbers. Initially I didn't prefer this approach because of the duplicate numbers you get in the array and duplication automatically feels wrong to me. The resulting function looks a lot better though:

function amountOfDays(month) {
    var DAYS_PER_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    var normalAmount = DAYS_PER_MONTH[month.month - 1];

    return hasLeapDay(month) ? normalAmount + 1 : normalAmount;
}

As you can see the last one has DAYS_PER_MONTH inside the function. I don't have any hard rules where to put these constants. Does the data belong on the module level or inside the function? The first one is probably faster but can lead to more noise.

When splitting things up you're required to use some sort of module system. Also this being a core component, as in no GUI stuff, it needs tests. I prefer to use the standards, which for Javascript now is the es6 module system. But being ably to test quickly is even more important. Sadely node doesn't (yet?) work with es6 module so I just use commonjs modules and have Browserify handle the packaging.

Other es6 features which you could use like consts, destructuring and defaults are all nice to have. I'm fine using es5 features and mainly rely on map which is supported from IE9+. For testing I use mocha because of it's API and output but this preference is just really subjective.

You can notice I use objects for passing around data. At the very start I used arrays to pass around data like [year, month] but objects are way more descriptive { year: year, month: month }. It also makes debugging a lot easier.

Something "dangerous" I chose to do but am not really certain of is deviating from the standard Javascript Date interface. The readme files starts with a big warning, which may be a smell. During development I changed my mind on this issue once but then reverting it back later.

Javascript's Date interface inconsistently uses a 0-based index for months and week days, but not for days or years. I changed the month value to be 1-based instead of 0-based. Use cases for the component consist out of getting input from the back-end, user input from the GUI and outputting dates (as in days). All these data use "normal" 1 based indexes for months.

Another thing that I find hard is to decide what goes in and what not. I've decided to include 2 additional attributes on the day objects: the first is an indication of the current day, the second one is the indication of sibling months. When eventually rendering a calendar you're likely to want to have this information.

Ranges, selections and weekend attributes are not included. Events, including selections and selection ranges, don't belong to the calendar core module as it's not always necessary and can easily be added in a separate module. I'm still considering weekends as I'm not sure if these are the same everywhere and what implications adding this would have.

The whole project is implemented in a functional style. It allows state but only within functions and I restrict a maximum statements per function style rule. This improvement in clarity comes at a cost though. It is substantially slower than the similar calender-base for example. However on my system it returns a calendar within 1 millisecond which is fast enough for me.

AirBnB has a style rule that each variable should be declared with a separate var, instead of just one and separating the declaration with a comma. I've used this last style for quite a while but putting the vars back made me realize it is much better, for two reasons: 1 it is less error prone, 2 when adding an additional variables indentation doesn't change for the previous variables. Changing indentation or comma's resulted in diffs which didn't really apply to the actual change:

var DAYS_PER_MONTH =
-    '28': [2],
-    '30': [4, 6, 9, 11],
-    '31': [1, 3, 5, 7, 8, 10, 12]
-};

var DAYS_PER_MONTH = {
+        '28': [2],
+        '30': [4, 6, 9, 11],
+        '31': [1, 3, 5, 7, 8, 10, 12]
+    },
+    MONTH_WITH_ADDITIONAL_DAY_ON_LEAP_YEAR = 2;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment