views:

447

answers:

7

In trying to make my Javascript unobtrusive, I'm using onLoads to add functionality to <input>s and such. With Dojo, this looks something like:

var coolInput = dojo.byId('cool_input');
if(coolInput) {
  dojo.addOnLoad(function() {
    coolInput.onkeyup = function() { ... };
  });
}

Or, approximately equivalently:

dojo.addOnLoad(function() {
  dojo.forEach(dojo.query('#cool_input'), function(elt) {
    elt.onkeyup = function() { ... };
  });
});

Has anyone written an implementation of Ruby's andand so that I could do the following?

dojo.addOnLoad(function() {
  // the input's onkeyup is set iff the input exists
  dojo.byId('cool_input').andand().onkeyup = function() { ... };
});

or

dojo.byId('cool_input').andand(function(elt) {
  // this function gets called with elt = the input iff it exists
  dojo.addOnLoad(function() {
    elt.onkeyup = function() { ... };
  });
});
A: 

As far as I know there isn't a built-in JavaScript function that has that same functionality. I think the best solution though is to query by class instead of id and use dojo.forEach(...) as you will be guaranteed a non-null element in the forEach closure.

You could always use the JavaScript equivalent:

dojo.byId('cool_input') && dojo.byId('cool_input').whateverYouWantToDo(...);
ihumanable
The double-lookup is exactly what I'm trying to avoid. It wouldn't just be for dojo.byId; other functions can be _far_ slower.
James A. Rosen
A: 

I've never used dojo, but most javascript frameworks (when dealing with the DOM) return the calling element when a method is called from the element object (poor wording, sorry). So andand() would be implicit.

dojo.addOnLoad(function() {
  dojo.byId('cool_input').onkeyup(function(evt) { /*event handler code*/
  });
});
tj111
Except when byId doesn't find the requested element. Andand will simply do nothing, but your code would attempt to call onkeyup on that non-existent object.
Rob Kennedy
+1  A: 

How about something like this:

function andand(elt, f) {
  if (elt)
    return f(elt);
  return null;
}

Call like this:

andand(dojo.byId('cool_input'), function(elt) {
  // this function gets called with elt = the input iff it exists
  dojo.addOnLoad(function() {
    elt.onkeyup = function() { ... };
  });
});
Rob Kennedy
+2  A: 

I don't know Dojo, but shouldn't your first example read

dojo.addOnLoad(function() {
    var coolInput = dojo.byId('cool_input');
    if(coolInput)
        coolInput.onkeyup = function() { ... };
});

Otherwise, you might end up trying to access the element before the DOM has been built.

Back to your question: In JavaScript, I'd implement andand() as

function andand(obj, func, args) {
    return obj && func.apply(obj, args || []);
}

Your example could then be written as

dojo.addOnLoad(function() {
    andand(dojo.byId('cool_input'), function() {
        this.onkeyup = function() { ... };
    });
});

which isn't really that much shorter than using the explicit if statement - so why bother?

Christoph
A: 

For a list:

Array.prototype.andand = function(property, fn) {

    if (this.filter(property).length > 0) this.map(fn);
}
kunjaan
This won't work. andand is supposed to stop execution if the Array is null. Of course, if the Array is null, it's no longer an Array. Therefore, the andand method is no longer available.
harley.333
+1  A: 

The exact syntax you want is not possible in JavaScript. The way JavaScript executes would need to change in a pretty fundamental fashion. For example:

var name = getUserById(id).andand().name;
//                        ^
//                        |-------------------------------
// if getUserById returns null, execution MUST stop here |
// otherwise, you'll get a "null is not an object" exception

However, JavaScript doesn't work that way. It simply doesn't.

The following line performs almost exactly what you want.

var name = (var user = getUserById(id)) ? user.name : null;

But readability won't scale to larger examples. For example:

// this is what you want to see
var initial = getUserById(id).andand().name.andand()[0];
// this is the best that JavaScript can do
var initial = (var name = (var user = getUserById(id)) ? user.name : null) ? name[0] : null;

And there is the side-effect of those unnecessary variables. I use those variables to avoid the double lookup. The variables are mucking up the context, and if that's a huge deal, you can use anonymous functions:

var name = (function() {return (var user = getUserById(id)) ? user.name : null;})();

Now, the user variable is cleaned-up properly, and everybody's happy. But wow! what a lot of typing! :)

harley.333
+1  A: 

You want dojo.behavior.

dojo.behavior.add({
    '#cool_input': {
        onKeyUp: function(evt) { ... }
    }
});
Steven Huwig