JavaScript – Modify URL Parameters

Recently I had to develop a solution which involved changing the HREF attributes of links in the navigation bar of a site. This had to be done on the JavaScript side because at my company we are the 3rd-party JavaScript solution for modifying other companies’ sites. That being said, I came up with one solution because the URL parameter was always the same but I figured I would publicly release the code for doing this in such a way that you can modify the value of any URL parameter:

/**
 * @license Copyright 2013 - Chris West - MIT Licensed
 */
(function(expCharsToEscape, expEscapedSpace, expNoStart, undefined) {
  /**
   * Modifies the given URL, returning it with the given parameter
   * changed to the given value.  The parameter is added if it didn't
   * already exist.  The parameter is removed if null or undefined is
   * specified as the value.
   * @param {string} url  The URL to be modified.
   * @param {string} paramName  The URL parameter whose value will be
   *     modified.
   * @param {string} paramValue  The value to assign.  This will be
   *     escaped using encodeURIComponent.
   * @return {string}  The updated URL.
   */
  modURLParam = function(url, paramName, paramValue) {
    paramValue = paramValue != undefined
      ? encodeURIComponent(paramValue).replace(expEscapedSpace, '+')
      : paramValue;
    var pattern = new RegExp(
      '([?&]'
      + paramName.replace(expCharsToEscape, '\\$1')
      + '=)[^&]*'
    );
    if(pattern.test(url)) {
      return url.replace(
        pattern,
        function($0, $1) {
          return paramValue != undefined ? $1 + paramValue : ''; 
        }
      ).replace(expNoStart, '$1?');
    }
    else if (paramValue != undefined) {
      return url + (url.indexOf('?') + 1 ? '&' : '?')
        + paramName + '=' + paramValue;
    }
    else {
      return url;
    }
  };
})(/([\\\/\[\]{}().*+?|^$])/g, /%20/g, /^([^?]+)&/);

The following are some example calls to this function:

// Initial URL
var url = 'http://example.com/';
alert(url);

// http://example.com/?q=search+term
url = modURLParam(url, 'q', 'search term');
alert(url);

// http://example.com/?q=search+term&name=Guillermo
url = modURLParam(url, 'name', 'Guillermo');
alert(url);

// http://example.com/?q=search+term+2&name=Guillermo
url = modURLParam(url, 'q', 'search term 2');
alert(url);

// http://example.com/?name=Guillermo
url = modURLParam(url, 'q');
alert(url);

// http://example.com/?name=Guillermo&q=termino
url = modURLParam(url, 'q', 'termino');
alert(url);

// http://example.com/?name=Guillermo
url = modURLParam(url, 'q', null);
alert(url);

Feel free to re-use the code! 8)

JavaScript – Substitution Groups & Functions

One of the things that I often end up doing is using JavaScript’s replace function with a regular expression and a callback function. Recently, though, I have been thinking about redeveloping an HTML application (HTA) that I made a long time ago for PCs. This HTA gave me the ability to write regular expressions and replacement strings which could alter the matched groups in ways that normal string substitution doesn’t allow without using callback functions. Let’s take the following file names as examples:

  • 001-the-nephews-come-to-town.mpg
  • 002-scroogey-scroogers.mp4
  • 32-hewi’s-lost-pet.mp4
  • 109-copped-by-the-coppers.mpeg

The file renamer that I use to have would allow these files to be renamed as the following by using /^(d+)(.+)(?=.w+$)/ as the regular expression and "Episode ${1,RLZ} -${2,D2S,PROPER}" as the replacement:

  • Episode 1 – The Nephews Come To Town.mpg
  • Episode 2 – Scroogey Scroogers.mp4
  • Episode 32 – Hewi’s Lost Pet.mp4
  • Episode 109 – Copped By The Coppers.mpeg

As the first step towards achieving my goal, I wrote the following sub function which can accomplish this:

(function() {
  var functions = {};
  var hasOwnProperty = functions.hasOwnProperty;
  this.sub = function(subject, reTarget, strReplacement, objFns) {
    if(!reTarget && !strReplacement && !objFns) {
      for(var key in subject) {
        if(hasOwnProperty.call(subject, key)) {
          functions[key] = subject[key];
        }
      }
    }
    else {
      return subject.replace(reTarget, function(match) {
        var args = arguments;
        var reGroups = [];
        var i = args.length - 2;
        while(--i > 0) {
          reGroups.push(i);
        }
        reGroups = '(' + reGroups.join('|') + ')';
        reGroups = new RegExp('\$(?:' + reGroups + '|\{' + reGroups + '((?:,\w+)*)\})', 'g');
        return strReplacement.replace(reGroups, function(match, index, index2, fnsToUse) {
          fnsToUse = fnsToUse ? fnsToUse.slice(1).split(',') : [];
          var ret = args[index || index2];
          for(var i = 0, len = fnsToUse.length; i < len; i++) {
            var fnName = fnsToUse[i];
            var fn;
            if((objFns && hasOwnProperty.call(objFns, fnName) && (fn = objFns[fnName]))
                || (hasOwnProperty.call(functions, fnName) && (fn = functions[fnName]))) {
              ret = fn(ret, args);
            }
          }
          return ret;
        });
      });
    }
  };
})();

This function actually acts differently depending on the parameters that are supplied. The first way in which the function can be called is with four parameters:

  1. subject – string:
    The string that is to be modified.
  2. reTarget – RegExp:
    The regular expression used to capture substrings.
  3. strReplacement – string:
    The replacement string which can have the normal captured group expressions (eg. $1, $3, etc.), the new type of captured group expressions (eg. ${1}, ${3,RLZ}, etc.) and/or normal substrings.
  4. objFns – Object:
    Optional object whose keys correspond to functions referenced in the new type of group expressions. Each value should be a function where the first parameter passed to it will be the matched group and the second will be the arguments object which is normally passed to the callback function. The functions should return the string replacement for the captured group expression.
var filename = sub(
  "023-we-are-the-tigers.jpg",
  /^(d+)(.+)(?=.w+$)/,
  "Episode ${1,RLZ} -${2,D2S,PROPER}",
  {
    RLZ: function(match) {
      return match.replace(/^0+(?!$)/, '');
    },
    D2S: function(match) {
      return match.replace(/-/g, ' ');
    },
    PROPER: function(match) {
      return match.toProperCase();
    },
    UPPER: function(match) {
      return match.toUpperCase();
    },
    CHARCODE: function(match) {
      return '<' + match + '=' + match.charCodeAt(0) + '>';
    }
  }
);
alert(filename);  // "Episode 23 - We Are The Tigers.jpg"

var str = sub(
  "abcdefghijklmnopqrstuvwxyz",
  /([aeiou])/g,
  "<${1,UPPER}>",
  {
    UPPER: function(match) {
      return match.toUpperCase();
    }
  }
);
alert(str);  // "<A>bcd<E>fgh<I>jklmn<O>pqrst<U>vwxyz" 

The reason the fourth parameter is optional is because those captured group expression replacement functions could be predefined for all calls to this sub function. If the function is just called with one parameter, that parameter should be the same as the fourth parameter outlined previously. The captured group expression replacement functions will remain persistent for the remainder of the page/program session:

sub({
  RLZ: function(match) {
    return match.replace(/^0+(?!$)/, '');
  },
  D2S: function(match) {
    return match.replace(/-/g, ' ');
  },
  PROPER: function(match) {
    return match.toProperCase();
  }
});
var filenames = [
  "001-the-nephews-come-to-town.mpg",
  "002-scroogey-scroogers.mp4",
  "32-hewi's-lost-pet.mp4",
  "109-copped-by-the-coppers.mpeg"
];
var exp = /^(d+)(.+)(?=.w+$)/;
var replacement = "Episode ${1,RLZ} -${2,D2S,PROPER}";
for(var i = 0; i < filenames.length; i++) {
  filenames[i] = sub(filenames[i], exp, replacement);
}
alert(filenames.join('n'));

The above code will result in the following filenames:

Episode 1 - The Nephews Come To Town.mpg
Episode 2 - Scroogey Scroogers.mp4
Episode 32 - Hewi's Lost Pet.mp4
Episode 109 - Copped By The Coppers.mpeg

Well, now that I have this code out of the way, hopefully the next step will be to actually make the File Renamer. When I do finish creating it, you can be sure to find it on this blog. 8)

JScript – File & Folder Chomper

I use to always work on Windows and at times it seemed that certain files just didn’t want to go to the recycling bin. For that reason I would always write a JScript which had the following code:

(new ActiveXObject("Scripting.FileSystemObject").DeleteFile(WScript.Arguments.Item(0))

When a file was dragged onto that script, the file would be de-referenced (deleted permanently). A friend recently called me and asked for a similar solution so I decided to post it for everyone:
Download the File & Folder Chomper

The above script works the same way (on Windows only): drag the files and folders onto the script’s icon in order to have all of the permanently deleted. Have fun!!!

Batch Excel To CSV Converter Application

I have been truly fascinated by the amount of visitors that I get because of the simple XLS/XLSX to CSV converter script that I made a while back (original post). For that reason I have created an HTML application (HTA) which offers the same functionality and then some:
XLS To CSV Converter
DOWNLOAD THIS APP

How this Differs from the Script

  • There is an easy to use interface.
  • You can customize how the resulting files are named.
  • You can filter which sheets will be exported as CSVs using simple text matches and/or regular expressions.
  • You will see the results of the conversion process in one place after the process is finished.

Using It

  • Download it from here.
  • Unzip the .HTA file.
  • Double-click on the .HTA file.
  • Click the Browse for Folder button and browse for the folder that contains the XLS and/or XLSX files whose worksheets should be exported as CSVs.
  • Modify the CSV Naming Schema as you like (hints are given in the application).
  • If you would like to only export worksheets with specific names, enter those names in the Sheet Filters textbox (only one per line). You may also use JavaScript-style regular expressions (one on each line) to match the sheets that you want to convert.
  • If you would like to remove the linefeed characters from all cells, check the appropriate checkbox.
  • Click the Start Conversion button and wait for the results to appear.

Plans for Improvement

As of right now this application is very simple, but I do hope to beef it up a little as time goes on. Let me know if you have any comments, questions, suggestions, or issues with this new application. Most of all, have fun!!! 8)

JavaScript – Get Window Size

At times you may want to get the size of the window. I don’t remember where I came across the original code, but I modified it and created the following function:

function getWindowSize() {
  var doc = document,
    body = doc.body,
    docElem = doc.documentElement,
    docElemHeight = docElem.clientHeight,
    docElemWidth = docElem.clientWidth,
    css1Compat = doc.compatMode === 'CSS1Compat';
  return {
    height: css1Compat && docElemHeight || body && body.clientHeight || docElemHeight,
    width: css1Compat && docElemWidth || body && body.clientWidth || docElemWidth
  };
}

As far as I can tell, this should work in all browsers, but if you find that it doesn’t at times, please let me know. ;)