
Code reviews (or code inspections) have been shown anecdotally and through research to be one of the best ways of discovering defects, getting developers on the same page, ensuring a consistent code style, and more. There are many ways to do it from pair programming to getting everyone in the room and putting code up on the projector. While it can be difficult to justify the time expense at first, code reviews quickly pay off. More than once I’ve seen defects caught that could have caused tens of hours (and much more) in bug fixing later be caught at the outset. It’s also a great way for developers to keep each other sharp and improve their knowledge and skills.
But this post really isn’t about why you should do code reviews but rather a tip on how. If you Google around for Eclipse code review plugins you will find the typical mix of open source and commercial. However, given that code reviews can be done so many different ways I was unable to find one that resonated with me; they all assume an explicit process (usually somewhat formal) that is not the one I’m looking for. Also, with our variation in project and team sizes, some flexibility in the process is needed.
Continue reading…
At a small tech gathering recently in Boston, font guru Paul Irish illustrated how the web is finally ready for a richer web fonts experience. For some time now, designers and developers have been stuck with only a handful of default “web friendly” fonts: Helvetica, Arial, Times, Courier, Georgia, among others. While a variety of techniques have cropped up over the years to satisfy the need for other fonts (such tools have included sifR, typeface.js, Cufón, and even text-to-image replacers), not all browsers incorporated native techniques for embedding unique fonts. This has been a source of frustration for designers trying to break out of the mold and do more sophisticated and exciting work, as alternative tools all have had implementation hazards and limitations of one kind or another.
This is all changing, as technology is crossing the threshold toward a brighter web fonts horizon. This isn’t to say the way ahead isn’t void of other challenges, particularly as concerns licensing. Rather, many of the key pieces are set to do the most basic of things: embed fonts natively into the browser with stunning visual results. So how is this possible (and why was it not really feasible earlier)?
The first place to look is at cross-browser implementation/support. Atypically, Internet Explorer is actually years ahead in the charge. They’ve allowed font embedding since IE4, where most of the other browsers have taken a while to catch up. (Though Opera actually introduced the spec.) From figures taken from StatCounter.com’s global stats website, 95% of the major browsers have native font embedding capabilities — that missing 5% being Firefox 3.0. (All other browsers support: IE6+, FF3.5+, Safari 3+, Chrome, and Opera.)
Continue reading…
In a component-based framework such as Tapestry 5 each component may contribute it’s own Javascript initialization for creating objects, form field initialization, etc. The result is a block of Javascript executing on page load similar to this:
Tapestry.onDOMLoaded(function() {
new PromptSaveOnDirtyForm()
$('form').observe('reset',function(){window.location.href='/example/projectdetail.traditionalpricinglayout.pricinglayout.form:reset?t:ac=1/bookspecspricing&t:cp=bookspecspricing'; return false;});
new DisplayFragmentSelect('impressionType','impressionhideable',0.1);
new BindingStyleUpdater('impressionType','bindingStyle');
new Ajax.Autocompleter('trimSize', 'trimSize:menu', '/example/projectdetail.trimsize:autocomplete?t:ac=1/bookspecspricing&t:cp=bookspecspricing', {"indicator":"trimSize:loader","paramName":"t:input"});
linkMediaFragment('bundledMedia','cdDvdWrapper');
BeanEditGrid.reorder('miscCosts_0:container');
BeanEditGrid.prepareDragDrop('miscCosts_0:container');
new JSONAutocomplete('name', 'name:menu', '/example/projectdetail.traditionalpricinglayout.name:autocomplete?t:ac=1/bookspecspricing&t:cp=bookspecspricing', $H({"indicator":"name:loader","paramName":"t:input"}).update({onSelect: updateCostType.curry('name')}).toObject());
new DisplayFragmentSelect('salesType','sthideable',0.1);
new DisplayFragmentSelect('businessType','bthideable',0.1);
new SelectOther('source','otherFragment','dummy');
new FireValueChanged('referenceISBN','/example/projectdetail.traditionalpricinglayout.printrundisplay.referenceisbn:fieldValueChanged?t:ac=1/bookspecspricing&t:cp=bookspecspricing',true,false,updateValues)
tabbox_tabbox = new TabBox(['miscCosts','printRun','itemMaster','notes'],'miscCosts','tabbox','selectedTabIndex');
if (typeof form_fireidupdated == 'undefined' ) {form_fireidupdated = new FireIdUpdated('form'); } else { form_fireidupdated.updateAndFire('form')}
new BookSpecsPricing('permissions','prePress','cover')
$('salesUnits').activate();
Tapestry.init({"zone":[{"update":"show","element":"formzone"},"historyZone"],"formLoopRemoveLink":[{"link":"removerowlink","fragment":"fragment","url":"/example/projectdetail.traditionalpricinglayout.projectcostnames:triggerRemoveRow/31340284_1268329414774/java.lang.String?t:ac=1/bookspecspricing&t:cp=bookspecspricing"},{"link":"removerowlink_0","fragment":"fragment_0","url":"/example/projectdetail.traditionalpricinglayout.projectcostnames:triggerRemoveRow/32235117_1268329414774/java.lang.String?t:ac=1/bookspecspricing&t:cp=bookspecspricing"} /* a LOT more javascript here */);Our solution was to create a “lazy” component allowing any Javascript contributed during render to be wrapped in a closure function. This function will lazy-initialize it’s contents and can safely be called multiple times.
Using the component looks like this:
The wrapper function is given a specific name that we can safely call before showing the UI. This would change the previous Javascript to something like this:
Tapestry.onDOMLoaded(function() {
$('form').observe('reset',function(){window.location.href='/example/projectdetail.traditionalpricinglayout.pricinglayout.form:reset?t:ac=1/bookspecspricing&t:cp=bookspecspricing'; return false;});
// init code for lightbox.
// note that it can be called multiple times safely and uses the function passed in as a parameter to the mixin
window.specsLazyInitState=false; window.specsLazyInit = function(){ if (window.specsLazyInitState) return; window.specsLazyInitState=true;
new DisplayFragmentSelect('impressionType','impressionhideable',0.1);
new BindingStyleUpdater('impressionType','bindingStyle');
new Ajax.Autocompleter('trimSize', 'trimSize:menu', '/example/projectdetail.trimsize:autocomplete?t:ac=1/bookspecspricing&t:cp=bookspecspricing', {"indicator":"trimSize:loader","paramName":"t:input"});
linkMediaFragment('bundledMedia','cdDvdWrapper');
Tapestry.init({"formLoopRemoveLink":[{"link":"removerowlink","fragment":"fragment"}] /* more js */);
};
// init code for some tabs
// if the function is empty we still create it so it's safe to always call
window._miscCostsLazyInit = function(){};
BeanEditGrid.reorder('miscCosts_0:container');
BeanEditGrid.prepareDragDrop('miscCosts_0:container');
window._printRunLazyInitState=false; window._printRunLazyInit = function(){ if (window._printRunLazyInitState) return; window._printRunLazyInitState=true;
new DisplayFragmentSelect('salesType','sthideable',0.1);
new DisplayFragmentSelect('businessType','bthideable',0.1);
new SelectOther('source','otherFragment','dummy');
new FireValueChanged('referenceISBN','/example/projectdetail.traditionalpricinglayout.printrundisplay.referenceisbn:fieldValueChanged?t:ac=1/bookspecspricing&t:cp=bookspecspricing',true,false,updateValues)
Tapestry.init({"zone":["historyZone"]} /* more js */);
};
window._itemMasterLazyInitState=false; window._itemMasterLazyInit = function(){ if (window._itemMasterLazyInitState) return; window._itemMasterLazyInitState=true;
Tapestry.init( /* more js */);
};
window._notesLazyInit = function(){};
tabbox_tabbox = new TabBox(['miscCosts','printRun','itemMaster','notes'],'miscCosts','tabbox','selectedTabIndex');
if (typeof form_fireidupdated == 'undefined' ) {form_fireidupdated = new FireIdUpdated('form'); } else { form_fireidupdated.updateAndFire('form')}
new BookSpecsPricing('permissions','prePress','cover');
postScriptInit();
$('salesUnits').activate();
Tapestry.init({"zone":[{"update":"show","element":"formzone"}],"linkZone":[["form","formzone"]],"formInjector":[{"element":"rowInjector_0","url":"/example/projectdetail.traditionalpricinglayout.misccosts.ajaxformloop.rowinjector:inject?edit-grid:id=miscCosts_0:container&t:ac=1/bookspecspricing&t:cp=bookspecspricing&t:formid=form"} /* more js here */);
});In a complex application you can have a number of JavaScript files loaded by the client including both 3rd-party and application-specific libraries. In one application this included prototype.js, scriptaculous.js (as well as effects.js, dragdrop.js, controls.js), window.js (for lightbox support), and application.js. For development purposes, keeping these separate is ideal but for production purposes, loading this many files can really slow down the browser even if the files are cached.
We created a method whereby all the core javascript libraries for the application are combined into a single, minified (and munged) library that can then be cached at first page load by clients. This uses YUI compressor, Maven 2, and the exec plugin.
Having added yuicompressor as a maven dependency we need to configure our code to run as part of the build:
Now we need our class to generate the file: