Archive for February, 2014

There have been a lot of times I have needed some information for a bililiteRange plugin that was associated with the underlying element, rather than a specific range. For instance, in bililiteRange(element).text('foo') then bililiteRange(element).undo() the undo needs to know about the previous text change. jQuery has a data() method that attaches an object to the element and you can add fields to that object. Actually, it only attaches an index that points to the actual object, since at least in some browsers the garbage collector had trouble with Javascript objects attached to DOM elements and you ended up with memory leaks. I'm not sure if that is still a problem, but it's an easy enough pattern to implement so I used it.

I didn't want to be jQuery-dependent and I wanted to be able to some more sophisticated things with my data, so I implemented my own. At its simplest, just use:

var data = bililiteRange(element).data();
data.foo = 'bar';
assert(bililiteRange(element).data().foo == 'bar');

bililiteRange(element).data() returns an object that you can add fields to and they will be saved across multiple calls to bililiteRange.

Continue reading ‘bililiteRange data’ »

Someone asked about adding a "today" button/link to flexcal. It's actually pretty simple:

<input id="date1"/>

$('#date1').flexcal({'class': 'multicalendar', calendars: ['en','he-jewish']});
var todaylink = $('<a>Today</a>').attr({
  'class': 'commit',
  rel: $.bililite.flexcal.date2string(new Date),
  title: $('#date1').flexcal('format', new Date),
  style: 'text-decoration: underline; cursor: pointer'
});
$('#date1').flexcal('box').find('.ui-flexcal').append(todaylink);

The key is the todaylink which I append to the calendar (the $('#date1').flexcal('box').find('.ui-flexcal') line). The underlying code uses the rel attribute to determine what day a given <a> in the calendar should go to; use $.bililite.flexcal.date2string(d) to get the right format for a given Date object. The $('#date1').flexcal('format', d) is the displayed format for a given date; you can override that (say to use a European day-month-year format).

The class of the link determines what do to: 'class': 'commit' sets the date in the input box and hides the datepicker; 'class': 'go' would just have the datepicker display that date.

I liked the way Dabblet does autoindenting (entering a new line copies the whitespace from the beginning of the current line, so you keep the same level of indentation). So I added an option to bililiteRange(element).text() to do that. Now bililiteRange(element).text('text to insert', select, true) with true passed as the last option will autoindent. My Prism editor now has a check box to implement that.

The code to do this is in the bililiteRange utilities, not the original code.

I also added two more bililiteRange plugins:

bililiteRange(element).indent(tabs)
Prepends the string tabs to each line that contains part of the range. Thus bililiteRange(element).bounds('selection').indent('\t') to indent by one tab (and if you want spaces, use those instead; I won't get into Holy Wars).
bililiteRange(element).unindent(n, tabSize)
Removes n tab characters or sequences of tabSize spaces from the start of each line.

And, inspired by jQuery data, added a bililiteRange(element).data() that returns an object tied to element that can be used to store any data on that element (not the bililiteRange) without memory leaks. Thus bililiteRange(element).data().tabSize = 4 can be used in future calls: assert(bililiteRange(element).data().tabSize == 4). In fact, unindent above does exactly that if tabSize is not passed in.

I was just working on adding autoindenting to bililiteRange, and actually took advantage of the fact that I had an automated test harness in place for that library. So I actually used test-driven development: write the tests for the code that doesn't exist yet, then write code until they pass. It's an odd way of thinking, but I realized that it was more fun than my usual development cycle (remember, I'm a hobbyist, so if it ain't fun, I don't have to do it):

  • Think of problem that needs solving (interesting)
  • Write code to solve problem (interesting)
  • Write demo/test code (sort of interesting)
  • Run test-fail test-mutter at code-recode debugging cycle (frustrating)

With TDD, it's more like:

  • Think of problem that needs solving (interesting)
  • Write demo/test code (sort of interesting)
  • Run test-fail test-Write code to solve problem cycle (interesting)

The tedious "debugging" phase is swallowed up in the interesting "write code to solve problem" phase and I enjoy it a lot more. There's still some tedious debugging if the test doesn't work, but the test code is simpler than the "production" code and generally easier to debug. I have had some problems getting my head around testing asynchronous code, but that probably means I need to simplify the whole system.

Now I need to learn the discipline to keep to this style of development and I'll be a happier hacker.