JavaScript – Indenting Text

One thing I needed to do recently was indent a series of strings with a variable number of tabs or spaces. Thusly, I decided to create the following function which can be used by anyone that would need such a function:

/**
 * Indents the given string
 * @param {string} str  The string to be indented.
 * @param {number} numOfIndents  The amount of indentations to place at the
 *     beginning of each line of the string.
 * @param {number=} opt_spacesPerIndent  Optional.  If specified, this should be
 *     the number of spaces to be used for each tab that would ordinarily be
 *     used to indent the text.  These amount of spaces will also be used to
 *     replace any tab characters that already exist within the string.
 * @return {string}  The new string with each line beginning with the desired
 *     amount of indentation.
 */
function indent(str, numOfIndents, opt_spacesPerIndent) {
  str = str.replace(/^(?=.)/gm, new Array(numOfIndents + 1).join('\t'));
  numOfIndents = new Array(opt_spacesPerIndent + 1 || 0).join(' '); // re-use
  return opt_spacesPerIndent
    ? str.replace(/^\t+/g, function(tabs) {
        return tabs.replace(/./g, numOfIndents);
    })
    : str;
}

The JS annotation pretty much sums up how you can use this function. Here is an example of using this function:

var msg = indent("Hello\nWorld!!!", 1);
alert('My message to you is:\n' + msg);
/* OUTPUT:
My message to you is:
    Hello
    world!!!
*/

Due to the fact that each computer may render a tab character differently, I have provided the ability to specify how many spaces to use in an indentation. This not only uses that many spaces for each of the indentations but also replaces all of the tab characters at the beginning of a line with that many spaces if any previously existed:

var msg = indent('Hello\n\tworld', 2, 6);
alert('My message to you is:\n' + msg);
/* OUTPUT:
My message to you is:
            Hello
                  world!!!
*/

Have fun indenting! 😎

JavaScript – Range Array Function

A cool function that exists in Python is the range function which actually creates a list of numbers within the specified range. Once again, since I love JavaScript, here a quick imitation of this function:

/**
 * Creates a range of numbers in an array, starting at a specified number and
 * ending before a different specified number.
 * @param {number} start  Indicates what number should be used as the first
 *     number in the returned array.  If this is the only number argument
 *     supplied, this will be used as the edge and 0 will be used as the start.
 * @param {number=} edge  Indicates the first number that should not appear in
 *     the range of numbers.  If this number preceeds the start in the range
 *     (taking into account the step), an empty array will be returned.  If not
 *     specified and not inferred this defaults to 0.
 * @param {number=} step  Indicates the difference between one number and the
 *     subsequent number placed in the returned array.  If not specified this
 *     defaults to 1.
 * @return {!Array.<number>}  Array of numbers in the specified range.
 */
function range(start, edge, step) {
  // If only one number was passed in make it the edge and 0 the start.
  if (arguments.length == 1) {
    edge = start;
    start = 0;
  }

  // Validate the edge and step numbers.
  edge = edge || 0;
  step = step || 1;

  // Create the array of numbers, stopping befor the edge.
  for (var ret = []; (edge - start) * step > 0; start += step) {
    ret.push(start);
  }
  return ret;
}

Of course, this is probably not a complete imitation, but as you can see it gets the job done in most cases:

range(10)
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

range(65, 69)
// [65, 66, 67, 68]

range(10, -10.1, -5)
// [10, 5, 0, -5, -10]

range(10, 1)
// []

If you want to use the function code, knock yourself out. 😎

JavaScript – Better Array Builder

Yesterday I wrote this post about a way to loosely imitate having array comprehensions in JavaScript. After thinking about it a bit more, I thought it would be best to change the function to be more like this:

/**
 * @license Array Builder v2 - By Chris West - MIT License
 * Builds an array either from another array or an object using the values.
 * @param {Array|Object|number} obj  If an array or object is given all of its
 *     items will be traversed.  If a number is given, an array with that length
 *     will be created and then traversed.
 * @param {string|Function|undefined} expression  Optional function or function
 *     expression that if the filter passes will be executed and the returned
 *     value will be placed in the array to be returned by this function.  If a
 *     string is specified, the context object (this) will be obj, the value
 *     will be $value and the key will be $key.  If a function is specified, the
 *     context object (this) will be obj, the key will be the first parameter
 *     and the value the second parameter.  If not specified and obj is number
 *     the value to be added to the returned array will be the key.  If not
 *     specified and obj is not a number, the value to be added to the returned
 *     array will be the value found.
 * @param {string|Function|undefined} optFilter  Optional function or function
 *     expression that will be evaluated for each value and only if a true-ish
 *     value results will the expression be evaluated and the resulting value
 *     added to the built array.  If a string is specified, the context object
 *     (this) will be obj, the value will be $value and the key will be $key.
 *     If a function is specified, the context object (this) will be obj, the
 *     key will be the first parameter and the value the second parameter.
 * @return {!Array}  The array built from the values returned from expression.
 */
var buildArray = (function(toString, undefined) {
  // Internal function which checks the type of an object or primitive
  // (excluding null and undefined).
  function typeIs(obj, typeName) {
    return toString.call(obj).slice(8, -1) == typeName;
  }

  return function(obj, expression, optFilter) {
    // If a number is passed instead of an array or object, create a blank array
    // with that length.
    var objWasNumber = typeIs(obj, 'Number');
    if (objWasNumber) {
        obj = new Array(obj);
    }

    // Make sure the expression is a function.
    if (expression == undefined) {
      expression = objWasNumber ? '$key' : '$value';
    }
    if (typeIs(expression, 'String')) {
      expression = new Function('$value', '$key', 'return ' + expression);
    }

    // If given make sure the filter is a function.
    if (optFilter && typeIs(optFilter, 'String')) {
      optFilter = new Function('$value', '$key', 'return ' + optFilter);
    }

    // This is called for each item in obj and is used to execute the filter (if
    // given) and then add the processed value to the array to be returned.
    function process(key) {
      var value = obj[key];
      if (!optFilter || optFilter.call(obj, value, key)) {
        ret.push(expression.call(obj, value, key));
      }
    }

    // Create the array, build it out, and return it.
    var ret = [];
    if (typeIs(obj, 'Array')) {
      for (var i = 0, len = obj.length; i < len; i++) {
        process(i);
      }
    }
    else {
      for (var key in obj) {
        process(key);
      }
    }
    return ret;
  };
})({}.toString);

Reasons for the Change

  • I wanted the ability to specify a number as the first parameter which would in turn create an array of that length and then process the array.
    buildArray(26, 'String.fromCharCode($key + 65)')
    // Returns array of the letters in the alphabet capitalized.
    
  • I wanted to remove the declaration of a variable which will be used to specify the name of the value.
  • I wanted the key to also be passed into the expression and filter functions.
    buildArray({ firstName: 'Chris', lastName: 'West' }, '$key')
    // ['firstName', 'lastName'] - Not necessarily in this order.
    
  • I wanted to be able to reference the object or array being examined.
    buildArray(['A', 'B', 'C', 'D', 'E'], 'this[this.length - $key - 1]')
    // ['E', 'D', 'C', 'B', 'A']
    
  • I wanted a way to mimic this call in python: range(10).
    buildArray(10)
    // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    

In conclusion there are many advantages to making these changes to the buildArray function. Of course, it still falls short of Python list comprehensions and true array comprehensions, but in the absence of such elegance we must resort to hacks. 😎