Tuesday, April 23, 2013

Tracer Bullets, Chaining Iterators, and Dynamic RegExp Fun

‹prev | My Chain | next›

Last night was one of those posts with a frantic race to the end. I got it working, but knew full well that there were still a few problems. The “it” in question is the navigation from embedded to full screen ICE Code Editor, which is initiated by clicking the SVG expand icon in the embedded editor:



In addition to transferring code between the two versions of the editor, I am also passing title information. Therein lies the problem. The title is transferred, but I am making no allowances for an existing project with the same name. The end result is that I have multiple projects with the same name:



Worse is that, if I try to delete one, both end up being removed.

As an initial workaround, I will add the copy number in parentheses after the project name. That is, if “Shapes Demo” already exists, then expanding another one should result in “Shapes Demo (1)”. To get there, I will engage in some intermediate range tracer bullets. Normally tracer bullets help build up nearby methods. In this case, I need to transfer information from the embedded ICE to the large ICE, determine if a project of the same name exists and then I'll try to illuminate my target. I could try to break this down into smaller bits, but the transfer of information complicates things. Anyhow...

The ICE.Full class does a bunch of work on the URL has, then end result being a call to the create() method of ICE.Store:
function processLocationHash() {
  // ...
  store.create(ICE.decode(hash.substr(2)), title);
}
So my tracer bullets will go one step further. In ICE.Store, I add a hasProjectNamed() method and put it to use in create():
Store.prototype.create = function(code, title) {
  if (!title) title = this._nextUntitled();
  if (this.hasProjectNamed(title)) title = title + ' 01';
  // ...
};

Store.prototype.hasProjectNamed = function(title) {
  return this.documents.some(function(doc) {
      return doc.filename == title;
    });
};
The ICE Code Editor will only work with modern browsers, so I have no compunction in using the some() iterator method on my document store. With that some() call, if any of the documents in localStorage have the same file as the new title, then the store does have a project named the same thing, and hasProjectNamed() returns true.

If my intermediate range tracer bullets are working, then I ought to have a project named “Shapes Demo 01” after navigating from the embedded ICE to the full screen version. And, indeed I do:



Now that I know that I am pointing in the right direction, it is time to hit the actual target. I replace the tracer bullet next title with a call to a new method, _nextProjectNamed():
Store.prototype.create = function(code, title) {
  if (!title) title = 'Untitled';
  if (this.hasProjectNamed(title)) title = this._nextProjectNamed(title);
  // ...
};
The _nextProjectNamed() method is some good old iterator-chaining, dynamic-regexp fun:
Store.prototype._nextProjectNamed = function(title) {
  title.replace(/\s*\(\d+\)$/, '');

  var nums = this.documents.
    filter(function(doc) {
      return doc.filename == title ||
             doc.filename.match(new RegExp('^' + title + ' \\(\\d+\\)$'));
    }).
    map(function(doc) {
      var num = doc.filename.
        replace(new RegExp('^' + title + '\\s*'), '').
        replace(/[)(\\s]+/g, '');
      return parseInt(num, 10);
    }).
    filter(function (num) {
      return !isNaN(num);
    }).
    sort();

  var next = nums.length ? nums[nums.length-1] + 1 : 1;
  return title + ' (' + next + ')';
};
As hairy as that looks, it is mostly based on prior work. It filters the entire localStorage list of documents for the ones that match the supplied title, then it strips everything but the copy number (if present) from the stored file names, then it ignores anything that is not a number from that list. The end result is a sorted list of copy numbers that can be used to build the next copy number.

That does the trick, even if intermediate copies are deleted:



I feel much better about this approach. I will likely take some time tomorrow to poke around the edges, but hopefully I am nearing the end of this batch of changes to ICE.


Day #730

No comments:

Post a Comment