JavaScript for C# developers: writing a library (part 3)

Now that we created a rudimentary date library in part 2, it’s time to move back to the cookie code.

Oatmeal Raisin Cookiephoto © 2010 Janet Hudson | more info (via: Wylio)My plan, as I stated in part 1, is to create an object with three methods: add, remove, and read. The first two of these methods require writing to the document.cookie string, so it seems to make sense to get that one out of the way first by making it a local (private) function that the public methods can call. I’ll also add a local variable ($jd) to hold the date object we created last time:

(function ($) {

  var $j = $.jmbLibrary;
  var $jd = $j.date;
  var $jc = $j.cookie = {};

  var updateCookie = function (name, value, expiryDate) {
    document.cookie = name + "=" + encodeURIComponent(value) +
      "; expires=" + expiryDate.toUTCString() +
      "; path=/";
  };

}) (jQuery);

As recommended by Mozilla’s developer documentation on the cookie interface, I’m using encodeURIComponent to ensure that the cookie value has no illegal characters within it (there are many, but the most obvious is a semicolon since that’s used for a delimiter when reading the cookie string back). Also there’s no need for validation checks here since the function can only be called internally.

Let’s look at the add method next:

  $jc.add = function (name, value, expiryDate) {
    if ((!name) || (!value)) { return; }
    if (!$j.date.isDate(expiryDate)) {
      expiryDate = $jd.addDays($jd.now(), 30);
    }

    updateCookie(name, value, expiryDate);
  };

So, if either the name or the value isn’t present, just get out. If the expiry date isn’t set, we set it to a month hence (well, OK, 30 days for now; as I said before, adding months to a date is not as simple as it first appears). After all that, just call the internal function to add/update the cookie.  

Removing a cookie? Pretty easy. All we do is to update the cookie to expire in the past:

  $jc.remove = function (name) {
    if (!name) { return; }
    updateCookie(name, "removed", $jd.addDays($jd.now(), -1));
  };

If there’s no name, return immediately, otherwise update the cookie to expire a day ago. I also set the value to some nonsense value: there’s a possibility that the browser may not delete the cookie immediately: it depends on when it does the check for a cookie being expired.

Now the read method. The obvious way is to read the document.cookie string and parse it every time. But, then I got to thinking. What if I read the string and parsed it into an internal list of cookies when the document was initially loaded? And then update the list of cookies whenever I added or deleted a cookie? That sounds like much the better plan. 

(Of course that demonstrates one of the issues in writing an article like this: the intermediate versions of the code are no longer available, such as the one where I implemented the “parse every time” algorithm. I’m presenting a fait accompli without showing the false starts or the dead ends, nor the refactorings that got me to this particular place. That’s the joy of writing a programming article, you look like a prescient god when in reality you’re anything but. I’ll nevertheless discuss the reasons I took the course I did.)

Doing this also gives me a chance to expose another feature of jQuery: the ability to defer execution until the document is loaded.

var cookieList = {};

var init = function () {
  var cookies = document.cookie.split(";");
  var i = cookies.length;
  while (i--) {
    var cookieParts = cookies[i].split("=");
    var name = $.trim(cookieParts[0]);
    if (name) {
      cookieList[name] = decodeURIComponent(cookieParts[1]);
    }
  }
};

$(init);

What I’ve done here is to create an empty object called cookieList and then declare a function called init that reads the document.cookie string, parses it into name=value pairs, and then parses each of those into the name and the value part (remembering to decode the latter), storing them as properties of the cookieList object. Notice I’m using a jQuery built-in function called trim to remove any whitespace from the name.

Then there’s the interesting part: I pass the init function object to the jQuery function through its namesake. What this does is to add the function to a list of functions that should be called (in sequence) when the document is finally loaded. (I’m assuming, if you like, that the cookies are only available properly once the document is loaded, maybe they’re available before but who knows.)

Once I have this cookieList, it was easy to write a read method, and the work of moments to update the list when a cookie is added/updated or removed.

The final code in my jmbCookie.js file looks like this:

(function ($) {

  var $j = $.jmbLibrary;
  var $jd = $j.date;
  var $jc = $j.cookie = {};

  var cookieList = {};

  var updateCookie = function (name, value, expiryDate) {
    document.cookie = name + "=" + encodeURIComponent(value) +
      "; expires=" + expiryDate.toUTCString() +
      "; path=/";
  };

  var removeCookie = function (name) {
    updateCookie(name, "removed", $jd.addDays($jd.now(), -1));
    delete cookieList[name];
  };

  $jc.remove = function (name) {
    if (!name) { return; }
    removeCookie(name);
  };

  $jc.add = function (name, value, expiryDate) {
    if (!name) { return; }
    if (!value) {
      removeCookie(name);
      return;
    }

    if (!$j.date.isDate(expiryDate)) {
      expiryDate = $jd.addDays($jd.now(), 30);
    }

    updateCookie(name, value, expiryDate);
    cookieList[name] = value;
  };

  $jc.read = function (name) {
    if (!name) { return null; }
    return cookieList[name];
  };

  var init = function () {
    var cookies = document.cookie.split(";");
    var i = cookies.length;
    while (i--) {
      var cookieParts = cookies[i].split("=");
      var name = $.trim(cookieParts[0]);
      if (name) {
        cookieList[name] = decodeURIComponent(cookieParts[1]);
      }
    }
  };

  $(init);

})(jQuery);

(Notice I added a special case to the add method: if the value is empty, count it as deleting the cookie. We could then just get rid of the remove method altogether.)

Having now seen the route to this final code, your first thought might be: why not just expose cookieList as a public property? Then we could get rid of the read method altogether and just read the cookieList properties directly. Certainly we could, but consider this: if we did so, it would be impossible to stop users of the library from modifying the cookieList object by adding or removing properties. Unlike C#, we cannot attach some kind of setter to the property so that we’re notified when an item in the object is modified or removed, so there’s no way we could add the relevant cookie when our user added a new property.

Next time, we’ll go even further with this library code. Consider this: I’ve said nothing about testing it.

Loading similar posts...   Loading links to posts on similar topics...

No Responses

Feel free to add a comment...

Leave a response

Note: some MarkDown is allowed, but HTML is not. Expand to show what's available.

  •  Emphasize with italics: surround word with underscores _emphasis_
  •  Emphasize strongly: surround word with double-asterisks **strong**
  •  Link: surround text with square brackets, url with parentheses [text](url)
  •  Inline code: surround text with backticks `IEnumerable`
  •  Unordered list: start each line with an asterisk, space * an item
  •  Ordered list: start each line with a digit, period, space 1. an item
  •  Insert code block: start each line with four spaces
  •  Insert blockquote: start each line with right-angle-bracket, space > Now is the time...
Preview of response