Close

I used to be a C# SharePoint Developer – Part 3 – General JavaScript Best Practices

In order to get a good basis for Best Practices for SharePoint and Apps, we need to keep the context of our development in mind. If we are writing for SharePoint our context alters, and so does our design pattern. This doesn’t matter if you are using a 3rd party library or one you built yourself. SharePoint has rules about including code and you must adhere to them so they work always as intended.

Apps are a different thing all together. Unlike the SharePoint context, they can pull in 3rd party libraries quite safely, without conflicting with other libraries loaded. This is due to the way they are separated from any other context.

This leaves us with a problem, do we use 3rd party libraries in SharePoint or not.

JavaScript syntax

JavaScript has grown to use camel case (More of a Community best practice), with lower case first word for all methods, namespaces and variables. Private variables have a dangling prefix (Underscore), and modules are Upper camel case.

The following sample isn’t complete, however it shows some of the major differences between Apps and SharePoint Context when it comes to Namespaces. (Note: This code isn’t designed for compatibility with MDS in SharePoint, therefore execution of it, will end up with some weird behaviour after garbage collection).

// Functional Expression -> Lower camel case
var rencoreGlobal = function () { };
// Method definition -> Lower camel case
function rencoreGlobal() { }
// Variables -> Lower camel case
var rencoreGlobal = "Rencore Code Analysis Engine";
// Namespace registration
// ASP.net + SharePoint
Type.RegisterNamespace("rencoreGlobal");
// SharePoint
_EnsureJSNamespace("rencoreGlobal");
// Apps
var rencoreGlobal = rencoreGlobal || {};

// Namespace usage after creation
rencoreGlobal = (function () {
    // Private constant -> Upper case with dangling prefix
    var _VERSION = "1";
    // Private variable -> Camel case with dangling Prefix
    var _rencore = {
    	// Public constant -> Upper case
    	var VERSION = "1";
        // Module -> Upper camel case
        Init: function () {
            // Init framework
        }
    };
    return _rencore;
})();

In the SharePoint context, filenames should always be lowercase, this is considered best practice by following SharePoint standards.

  • rencore.debug.js – None minified version of the file
  • rencore.js – Minified version of the file. You can include a Minification command in your compilation step, or during your TFS build. One tool for this is Web Essentials http://vswebessentials.com.

In Apps, development you should look at using the web standard of adding “.min” before the “.js” of your minified JavaScript files instead.

rencore.js -> rencore.min.js

Variable Scoping

Earlier we looked how variables are declared, but now I will address declaration in a Best Practices environment. Variables are what make a programming language useful. If there were no variables then the input/output of language would become static, at which point all functionality could be replaced by fixed statements.

In C# we look at variables to be scoped to the current Block Statement, in JavaScript we change the way this works slightly in order to work with Just in Time compilation.

The hard and fast rule of Variable declaration is when you require a new variable, always declare it specifically in the context you intended by using the var directive. This will scope your variable to the top of the current method scope. The method scoped is either the last previous function and therefore the ECMAScript  engine will move it to the top of here, or turn the variable into a global. To avoid variable confusion you are then advised to declare all variables you require at the top of the current method scope regardless (Note: Omitting var is not supported in strict mode, and will cause an error). Without strict mode any variable declared without the use of var will be hoisted into the global scope and could cause conflicts. If you intend to declare a global variable then do so by using window.varName.

Consider the following snippet:

function rencore() {
    for(var i = 0; i < 10; i++)
    {
        console.log(i);
    }
    var x = 10;
    while(x--)
    {
        console.log(x);
    }
    myGlobal = x;
}

The above is equivalent to the best practice of:

function rencore() {
    'use strict';
    var i = 0,
        x = 10;
    for(i = 0; i < 10; i++)
    {
        console.log(i);
    }
    while(x--)
    {
        console.log(x);
    }
    Window.myGlobal = x;
}

The process in which variables are moved up like this, is called hoisting. However, it is only declarations that are hoisted, not initialisations. So even though we have moved up the initialisation of the variable ourselves in the previous example, if the compiler had done this, only the declaration would have been moved.

This gives way to a weird state that some C# developers would be confused by. The ability of using a variable in lines before it was declared. Because in the compilation step, it is hoisted the variable has actually been moved.

Code formatting

If code isn’t readable and understandable in a global format, by you, other developers and even to the compiler, then it isn’t good code. Strict mode in JavaScript enforces some of these best practices, and Microsoft enforce others, and being in Microsoft land we should take care to follow the correct guidelines for coding as best as possible.

In order to supply code that is of a standard format and to allow for easier development, code collaboration and debugging, then the following rules should be adhered to:

  • Always use semi colons, where a statement is closed. Functional expressions do require a semi colon, method declarations do not.
  • Don’t put a space between the function keyword and its parenthesis
  • Always but the opening brace 1 space on the same line as the method declaration
  • Always but closing braces on their own line, with the exception of a closing parenthesis
  • Never have any white space in an empty construct.
  • Always include a space either side of ternary operators (‘:’ and ‘?’)
  • Do not put a space before any semicolon or comma
  • No whitespace at the end of the line, or blank lines
  • Indent with tabs, not spaces
  • All statements that generate a closure (Block Statement) should be surrounded by braces, with each statement on a separate line
  • Short object notation should not have a space before the colon, but can followed by one
  • Always add a new line at the end of the file
  • If the entire file is contains within a closure, the body of the method shouldn’t be indented in order to ease readability
  • Use short notation over literal declaration to help code readability

Provide comments where usage cannot be inferred. Check the MSDN intellisense help for best practices for providing code hints.
(http://msdn.microsoft.com/en-us/library/vstudio/hh874692%28v=vs.110%29.aspx)

Examples:

// Provide comments where usage cannot be inferred
// Correct semicolon usage
var rencore = {};
// Correct semicolon usage on functional expression
// Opening braces appear on the same line as
// the Block Type Statement
// Use intellisense to describe methods
var rencore = function (a) {
    /// <summary>
    /// Determines what engines will be executed
    /// </summary>
    /// <param name="a" type="Object">
    /// A key value pair collection of type Engine
    /// </param>
    /// <returns type="Object">
    /// A collection of methods which can be applied
    /// to different file types.
    /// </returns>
    return {};
};
// No semi colon on method declarations
// Opening braces appear on the same line as Block Statement
// No white space inside any empty construct {}, [], ()
function rencore() {
    // Do stuff
}
// Semi colon on short object notation instantiation
// Opening braces appear on the same line as the Block Type Statement
var rencore = {
    // No semi colon on properties, instead commas
    // Opening braces appear on the same line as Block Statement
    // No white space inside any empty construct {}, [], ()
    // Use intellisense to describe fields
    /// <field name='Engine' type='Method'>
    /// Rencore analysis engine.
    /// </field>
    Engine: {
        // Some stuff
    	// No space before commas or semicolons
    },
    // No white space inside any empty construct {}, [], ()
    Sdk: function () {
        // Do stuff
    }
};
// Always use braces around any statement that
// uses a closure (Block Statement)
// Opening braces appear on the same line as Block Statement
if(typeof rencore != "string") {
    // Do Stuff
}
// Always use spaces either side of the
// '?' and ':' in a ternary statement
// No white space inside any empty construct {}, [], ()
var rencore = rencore ? rencore : {};

Equality

Where possible use supplied SharePoint methods to check for null, undefined, empty strings, etc. These can be found in the ScriptHelpers class of sp.init.js.

Always use strict equality checks when performing your own checks, in order to check type and value. This is a triple equal syntax. (‘===’ or ‘!==’). If you are only checking type, then you can omit a strict checking, as the value that is being checked will always be string.

The reason behinds this as stated in the previous blog post (JavaScript history and basics) is that JavaScript uses Abstract Primitives and gives you access to these as well as the more concrete implementations. Combined with these Abstract Primitives we have an Abstract comparison. JavaScript’s implementation of this simple comparison (== or !=) is quite simple. What is the lowest common type of these two items that we can compare against, then do the comparison.

Example:

var rencore = "0";
if(rencore == 0) {
    /*
       incorrect usage unless we intended
       that we wanted to check the number as a string.
       Here the number is converted to a string on the
       right and compared to the value set in the Rencore
       variable.
    */
}
if(rencore === "0") {
    // Possible correct usage considering the above
}
if(typeof rencore == "string") {
    // correct usage as both sides are string already
}
if(true == "1") // true due to inference of the value 1 in the string
if(true === "1") // false due to different types, and no variable inference happens

// Other examples:

if(undefined == null) // true, as abstract objects they both have nothingness
if(undefined === null) // false, different types won't equate

// Also consider primitive wrappers are different object types during comparison.
typeof "Hello World." // string
typeof (new String("Hello World.") // object

// Reference types also act differently
// Note: both of these examples do not require strict comparison as they are the same type.
if([] == []) // false, as they have different instances
if((a = []) == a) // true, as they have the same reference

Because of this, when writing Enterprise Applications, variables that are passed to method, or DOM elements that are read, should be type checked before usage, and also where needed value checked.

This process of type checking should also be used for feature detection. Feature detection is the preferred method, and the more accurate method of detecting what your browser is capable of. If you are unsure if your browser supports a feature check the MSDN documentation, and provide a check for the feature. I.E. The most commonly used feature that isn’t fully supported is the console object. It is possible to detect these missing features and then “shim” in an alternative for use, and there are code examples, and libraries that provide this already. To check what your browser is capable of check the MSDN documentation here: http://msdn.microsoft.com/en-us/library/hh273397(v=vs.85).aspx

Module pattern for namespace separation

Whether you are writing code for SharePoint context, or App/SPFx context the module pattern provides separation and extensibility. This ensures that you provide a single global namespace, which is correctly registered when in context, and remove the need for global variables entirely.

The following example shows the module pattern usage for SharePoint context. (You may recognise it from my Rencore Blog posts)

// Register rencore namespace and create the rencore object
_EnsureJSNamespace("rencore")
// Create a global pattern for SOD module init registration
(function $_global_rencore() {
    // Short object notation private variables
    var _private = {
        installationFolder: "_layouts/rencore/js/",
        revision: "1.0a"
    };
    // Sealing pattern to allow modules to see frameworks
    // private variables when spread across multiple files
    var _private = my._private = my._private || {},
    _seal = my._seal = my._seal || function () {
        delete my._private;
        delete my._seal;
        delete my._unseal;
    },
    _unseal = my._unseal = my._unseal || function () {
        my._private = _private;
        my._seal = _seal;
        my._unseal = _unseal;
    };
    // Short object notation module registration
    rencoreAB = {
        // Example SOD implementations
        RegisterScript: function (filename, key, dependencies, ondemand) {
            RegisterSod(key, _private.installationFolder + filename + "?v=" + _private.revision);
            j = dependencies.length;
            while (j--) {
                RegisterSodDep(key, dependencies[j]);
            }
            if (typeof ondemand != "undefined" && !ondemand) {
                collaboris.execFromSOD(scripts[i].key, null, function () {
                }, true);
            }
        },
        GetScript: function (key, namespace, callback, bSync) {
            // Apply sealing pattern to callback
            _private.unseal();
            var callback = function () {
                callback.call();
                rencore._unseal();
            };
            if (typeof key == "string") {
                SP.SOD.executeFunc(key, namespace, function () {
                    console.log(key + ": Type available (" + namespace + ")");
                }, bSync);
                SP.SOD.executeOrDelayUntilScriptLoaded(callback, key);
            } else if (typeof key.length != "undefined") {
                SP.SOD.loadMultiple(key, callback, bSync);
            } else {
                return false;
            }
            return true;
        },
        NotifySOD: function (key) {
            if (typeof (NotifyScriptLoadedAndExecuteWaitingJobs) == "function") {
                NotifyScriptLoadedAndExecuteWaitingJobs(key);
            } else {
                throw "SP Context not found.";
            }
        },
        // Create a custom ready method compatible with MDS
        Ready: function (method) {
            if (typeof _spBodyOnLoadCalled == 'undefined' || _spBodyOnLoadCalled) {
                window.setTimeout(window[funcName], 0);
            }
            else {
                _spBodyOnLoadFunctionNames.push(funcName);
            }
        }
    }

    // Register other scripts in the framework, with their dependencies
    rencoreAB.RegisterScript("rencore.security.js", "rencore.security", "rencore", true);
    rencoreAB.RegisterScript("rencore.performance.js", "rencore.security", "rencore", true);
    return rencoreAB;
})();
// Notify that our script has loaded
rencore.NotifySOD("rencore");

Summary and coming up next…

JavaScript is an Abstract language, but you can wrap up it’s abstraction into stronger patterns. One way of doing this is of course TypeScript and is Microsoft’s currently recommended method. This however is a post or series of it’s own!

Writing your code in a structured way, and knowing why and how variables act, is the key to writing better applications. Once you know, there will be no more WTF JavaScript moments, because you will know how it reacts.

Please follow and like us:

Leave a Reply

Your email address will not be published. Required fields are marked *

© Hugh Wood 1980-Present