Hey Juriy, > I respect your style, T.J., but things like "return undefined" always > throw me off as being a bit redundant : ) Functions return undefined > implicitly when nothing is specified, so why not take advantage of > that?
I take your point, but it's a clarity thing. My background is overwhelmingly in compiled languages. I probably should have written: return /*undefined*/; ...since I'd pass all of this stuff through a packer/minifier anyway. <OT> The more serious JavaScript coding I do, the more I want a compiler. Source clarity *matters* to me, source malleability *matters* to me. I don't want a compiler in the sense of machine code or bytecode, really, but in the sense of: There's a version for people, and there's a version for machines, and the imperatives are different. Packers and minifiers help -- they certainly put the shame on people who don't comment -- but only so far. :-) </OT> -- T.J. ;-) On Oct 23, 5:30 pm, kangax <[EMAIL PROTECTED]> wrote: > On Oct 22, 5:42 am, "T.J. Crowder" <[EMAIL PROTECTED]> wrote: > > > > > @All: > > > You know how you plan to send a really brief, simple reply and things > > just get out of hand? Apologies for the long post... > > > @RobG: > > > > doSomething($('elID')) > > > I think he means using Prototype's element extensions (though I could > > be mistaken). Naturally if you like, you can code your own methods to > > handle being passed an undefined element. But while you can do this: > > > Element.toggle('elID'); > > > ...it will still throw an error, as most of Prototype's functions > > don't guard against undefined elements. (*Appropriately* don't, in my > > view; to me 90% of the time that's an application logic error.) > > > @OP: > > > If you really want this "nullsafe" behavior, there are at least three > > ways I can think of to get it, two of which come with a significant > > runtime cost: > > > 1. Modify your copy of Prototype.js: > > > Figure you'll have to do this about twice a year as you deploy new > > versions of Protoype, but that's not that bad, is it? Basically > > taking (say) Element.toggle: > > > toggle: function(element) { > > element = $(element); > > Element[Element.visible(element) ? 'hide' : 'show'](element); > > return element; > > }, > > > ...and inserting some code to fail silently if the element doesn't > > exist: > > > toggle: function(element) { > > element = $(element); > > if (!element) return undefined; // <=== New code > > Element[Element.visible(element) ? 'hide' : 'show'](element); > > return element; > > }, > > > That means that "Element.toggle('elID')" will fail silently rather > > than raising an error. (Naturally "$('elID').toggle()" will still > > raise an error, since that's trying to call a function on an undefined > > reference.) > > > 2. Be explicit with a wrapper > > > In my view, in the normal case if you're passing an undefined > > reference, it's an application logic error and so you *want* Prototype > > to throw an error. But there are certainly other cases where the > > thing you want to act on can *correctly* either exist or not. So why > > not be explicit about it in your code: > > > function nscall(func, element) { > > var args; > > > element = $(element); > > if (!element) > > { > > return undefined; > > } > > args = $A(arguments); > > args.shift(); > > args[0] = element; > > return func.apply(this, args); > > > } > > I respect your style, T.J., but things like "return undefined" always > throw me off as being a bit redundant : ) Functions return undefined > implicitly when nothing is specified, so why not take advantage of > that? > > My biased little version would be among these lines: > > function nscall(func, element) { > if (element = $(element)) { > return func.apply(null, $A(arguments).slice(1)); > } > > } > > We could also replace $A with `Array.prototype.slice.call` as a minor > speed improvement. > > > > > > > In places where it's valid that the element may not exist, instead of: > > > $('elID').toggle(); or Element.toggle('elID'); > > > ...it would be > > > nscall(Element.toggle, 'elID'); > > > You're being clear that it's okay if the element doesn't exist. > > > The runtime cost here is obviously several additional function calls, > > both to nscall() and within it. > > > 3. Wrap Prototype's Functions > > > Finally and most intrusively, you can always wrap the functions for > > which you want the behavior, using Function.wrap[1]: > > > Element.toggle = Element.toggle.wrap(function(originalFunction, > > element) { > > element = $(element); > > return element ? originalFunction(element) : undefined; > > > }); > > > That makes "Element.toggle('elID')" fail quietly if there is no > > element with the ID 'elID'. (Again, the methodized version "$ > > ('elID').toggle()" would of course still fail.) > > > You'd have to do this for all functions that you want to have behave > > that way, but I'm guessing it's probably a subset of the full Element > > API and even if not, that API doesn't change so frequently that it's a > > massive issue updating your list when you deploy a new Prototype > > version. But if you REALLY want to make it easy, here's a barely- > > tested function that takes a space-delimited list of Element methods > > and wraps them to make them fail silently if the element is undefined: > > > $w('toggle addClassName').each(function(fname) { > > Element[fname] = Element[fname].wrap(function(originalFunction, > > element) { > > var args; > > > element = $(element); > > if (!element) > > { > > return undefined; > > } > > args = $A(arguments); > > args.shift(); > > args[0] = element; // We've already looked it up, save doing > > it again > > return originalFunction.apply(this, args); > > }); > > > }); > > > (In that example, I've only wrapped 'toggle' and 'addClassName' -- > > although I think addClassName already fails silently.) > > > Breaking that down, we use the $w() utility function[2] to turn the > > space-delimited list into an Array, then we use Enumerable.each[3] > > (which is mixed into Array) to process each item in that Array. > > Inside our iterator, Element[fname] references the function, and so we > > wrap each of those using the given wrapper. The wrapper checks if the > > element exists and if so, passes on the element and any other > > arguments to the original function. (Note the args.shift(), since of > > course the first argument to our wrapper is the original function.) > > > Again, the runtime cost there is significant and I don't think I'd do > > it. > > I think `wrap` option is least obtrusive of the ones you presented. I > also think that `if (foo) bar(...)` (or, perhaps, `foo && bar(...)`) > is the best way to handle `null`/`undefined` values. Best from the > performance, clarity and maintenance points of view. > > On the other hand, this problem somewhat disappears once element > wrappers are introduced (as the ones we had lengthy discussions about > in the past). When every method is guaranteed to return a reference to > a wrapper - existence of an element/collection is of little importance > (besides runtime/memory costs) > > > > > [1]http://www.prototypejs.org/api/function/wrap > > [2]http://www.prototypejs.org/api/utility/dollar-w > > [3]http://www.prototypejs.org/api/enumerable/each > > > HTH, > > -- > > T.J. Crowder > > tj / crowder software / com > > [snip] > > -- > kangax --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Prototype & script.aculo.us" group. To post to this group, send email to prototype-scriptaculous@googlegroups.com To unsubscribe from this group, send email to [EMAIL PROTECTED] For more options, visit this group at http://groups.google.com/group/prototype-scriptaculous?hl=en -~----------~----~----~----~------~----~------~--~---