Showing posts with label reinvention. Show all posts
Showing posts with label reinvention. Show all posts

Monday, July 23, 2012

Dependency

The Java world has Maven and Ivy.  Tools meant to untangle dependency webs, to get you what you need, when you need it and put it where you want it.  I've seen a few attempts over the years to adapt Maven to handle javascript dependencies.  They were abandoned unfinished.  I haven't looked much in the last year.

Javascript development is immature.  Like Java 10 years ago, you generally need to manage the resources manually.  It's up to you to juggle version control, version numbers and untangle the web of dependency.  Once you have your libraries in their proper versions and understand what order you need them in, there are tools to help you execute what you decide/know.  RequireJS, yepnode.js, and Grunt interest me the most so far, but they all have different priorities and all get involved late in the dependency management problem. They help you execute deployment and development, but only after you have manually resolved all your dependencies.

Programmatic resolution is hard. To do it well requires organized public repositories, which require standardized naming, versioning, and dependency declaration. Not to mention bandwidth. The person and/or organization that takes it upon themselves to get this rolling in the javascript world has a lot of work cut out for them. But plenty of reward as well.

Ender does programmatic resolution, and it's package.json could probably be made to do more, but it only seems to run just one level deep, when a deep, tangled dependency web is the inevitable reality of any modular application development. It also neglects script loading, test running, support for app code, and stands as a competitor to other frameworks, not a complement.

Dependency management in the javascript world is harder than Java because javascript doesn't require compilation. Compilation funnels all Java devs through a common build step, making a natural insertion point for a dependency management tool. Javascript lacks that chokepoint. Testing, linting, minimizing, concatenating--all of these have some potential but are still inconsistently used or only used in production deployment, not development. And both deployment and development themselves often are embedded within build/deploy processes for whatever other platforms are being used to build the server side of the web app. Any javascript build process ultimately needs to work as a subprocess of those builds.

Javascript/HTML5 as a platform is still wild, untamed by the build tools and processes that smooth out other application development environments.  What i would really love to have in taming it is a dependency management tool that works with these features:

  • Repository support - public would be great, easy tools or conventions for private ones are acceptable. CDNs only get us part-way there; wheel authors need to be able to post libraries.
  • Standards for declaring dependency info - to optimize performance and untangle the web this should support/require:
    • library name and version
    • CDN URLs for the library
    • global variables exposed by the library
    • the libraries dependencies
      • name
      • version (range)
      • when needed (execution, document-ready, window-load, optional, etc)
    • license/copyright info
  • Conditional script loading (for polyfills)
  • Easy, declarative switching between development (test and lint, but no minimization or concatenation) and deployment (minimize and maybe concatenate)
  • Plugins for easy integrating with other build tools (Ant/Maven/etc)
Support for non-browser javascript and things like passing modules into each other (to avoid global exposure) are bonuses.  I don't care about them currently, but other people will.

A large portion of the code seems to be out there.  Grunt build process, Ender dependency resolution, yepnope script loading, and you're already well on your way. If you build it, they will come. Well, i certainly will! I'd built it myself, but there are only so many hours in a day.


#javascript

Wednesday, July 11, 2012

More Fun With Custom Events - state.js

My web apps these days are typically a single page, perhaps excepting a login page or some help content.  One page, composed from many files--during development, at least--does most of the work.  This design obviously began with the usual problems of lacking browser history control and bookmark-ability.  Looking at solutions others made, as you might expect, prompted me to reinvent another wheel.

Lo, state.js:

This enables browser history and bookmarks, by shoving simple key/value pairs into either the query string or the hash, as supported.  Keeping everything in the hash for all browsers would have been simpler, but then it would not survive a server-side login redirection process, undermining the ability to effectively bookmark a gated app.

state.js keeps track of the key/value pairs in memory, in order to notify listeners when those values are changed by the browser/user.  You can register a listener directly with state('foo', listenFn) or just watch for 'foo.state' events on the document.  This latter feature is part of the most interesting feature of state.js: it can be used entirely via custom events.  You do not need to ever directly reference the 'state' function in your app code or other js libs.  Granted, if you want soft dependency like this, it is easy to just have your config code wait for document ready and check if 'state' is available, but as state.js is such an event-oriented bit of code to begin with, i found it useful, amusing, and trivial to add an event-based API.

As an example, this:

state('foo', function(val, oldVal) {
    console.log('foo changed from ',oldVal,' to ',val);
});
state('foo', 'bar');// set foo to bar

is equivalent to this:

$(document).on('foo.state', function(val, oldVal) {
    console.log('foo changed from '+oldVal+' to '+val);
})
.trigger('state', ['foo','bar']);// set foo to bar

And here's the other useful features:

var all = state();// get a copy of current state
var foo = state('foo');// get current 'foo' state
$(document).trigger('back');// tell the browser to go back a page
$(document).trigger('forward'); // === history.forward()

As you can probably guess, this event-based API comes in quite handy when using trigger.js.  Now your page's controls can control state values and shift browser history:

<button trigger="nextPg state['pg',2]">Next</button>
<button trigger="back">Back</button>

Friday, July 6, 2012

Another Short Cut - key.js

Keyboard shortcuts are a wheel that just begs for reinvention.  And being the sucker i am, i gave in and wrote me a new wheel just for that.

Like trigger.js, this is about better events and giving page controls control.  Jump below the gist for explanation:


This does two things:

  • allows keyboard shortcuts to be assigned in HTML
  • triggers namespaced, natural language 'key' events
<a key="alt-r" href="#reply">Reply</a>
<button key="home">Start Over</button>
$('#foo').on('key.ctrl-delete', Foo.deleteAll);
Both behaviors do not by default happen for most "plain" key events (roughly /[a-zA-Z0-9]/) unless you set key.all = true or add a 'key-all' class to your page's root tag (i.e. <html class="key-all">).

For the HTML shortcuts, key.js triggers 'click' events as appropriate. If you need a different event, add a trigger="different" and use trigger.js to translate the click.

key.js also provides a key() function to jQuery to let you trigger key events from javascript, as is quite necessary for testing your key events and shortcuts.

Obviously, if you look at the code, there are a great many keyCode values for which this wheel does not provide an English translation. At this point, 100% completeness seems both difficult (due to OS variation) and largely useless. Almost all "normal" keyboard shortcuts are accounted for, and any developer using this code can easily add more to key.codes. As with most of my wheels, this one is quite adjustable and extensible. Enjoy!

Give Controls Control - trigger.js

When you put a <button> or an <a> or an <input type="image"> into your markup, you intend them to do something, not just sit there and look pretty.  This inevitably means writing code like this:
$('a.blah').on('click', function() {
    console.log('blah');
});
STOP!  You actually meant to use a custom event, right?  Let's try that again:
$('a.blah').on('click', function() {
    $(this).trigger('blah');
});
$(document).on('blah', function() {
    console.log('blah');
});
Ah, much better. Sure, it's longer, but this way you are self-documenting and making your code easier to test, extend and maintain.

But what if it didn't really have to be longer? What if the button itself could be told to trigger the custom event. After all, that ".on('click'...)" business is just boilerplate and not particularly friendly to the world of keyboard accessibility. Well, this is what trigger.js does for you. Instead of setting up the custom event with some boilerplate javascript, we just extend HTML with a 'trigger' attribute:

<a trigger="blah">Blather</a>
$(document).on('blah', function(e) {
    console.log('blah');
});
Now there's no "blah blah blah" boilerplate; i get to focus on what i'm doing more than how i'm doing it.  It gives my controls control, rather than shoving their functions off into a separate file.  And this can be extended to accomplish even more.

But before we talk about the "more", here is trigger.js:


Wait, you say! Why isn't it just like this:
It could be that simple, true. If that's all you need, use that. But it's hard to resist adding a little flair when you are inventing the wheel. The first thing i wanted was sequenced events.  Why trigger just one when you can trigger more? After all, i like to "use custom events like crazy", and many "actions" in applications are actually combinations of sequential, dependent, but separate actions. Consider:

<button trigger="validate submit">Submit</button>

This way, my "validate" code doesn't have to be tied to "submit" code and so on.  And when "validate" fails, it can still call e.preventDefault() to keep "submit" from firing.

Once i had sequenced events in my markup, it was natural and easy to add them to jQuery's trigger function, especially for debugging things in the console.

$('#cancel').trigger('reset back');

That done, i remembered that i have long wanted to have my own custom event properties.  Instead of "moveUp" and "moveDown", i wanted to have "move" events where i could test for "up" or "down" properties, just like i test e.ctrlKey.  So, trigger.js now supports:

<button trigger="move:up next">Up</button>
$('#game').on('move', function(e) {
    var roll = dice.value();
    if (e.up) player.climb(roll);
    if (e.down) player.slide(roll);
    if (player.winner) {
        e.preventDefault();// all done
    }
}).on('next', function() {
    player = player.next();
});
Finally, i occasionally envied the ability to associate data with an event, like you can when calling trigger() on a jQuery collection. So i added that as well:
<button trigger="level[10]">Loud</button>
<button trigger="level[11]">Spinal Tap</button>
$(document).on('level', function(e, level) {
    speakers.goTo(level);
});
And finally, i wanted my apps to be more keyboard accessible. So, trigger.js also listens for 'enter' events on elements that don't naturally convert them to clicks (div, span, a[!href]) and does that for them.

By all means, if these extra features aren't useful to you, just use the simple, four-line version. But if you want to give your document's controls a bit more control, trigger.js is ready to help. :)