1

While working on my framework an idea came to my mind. I'm using $elect(id).hide() to select an element and do stuff with it. There's nothing wrong. But what if I wanted to change the selector $elect() and use something else? I'd have to fine each $elect in the document and manually replace all of them. That's boring. So I was thinking if something like this is possible:

var selector = $elect; // or anything else

function [selector](id) {
    if (!(this instanceof [selector])) {
        return new [selector](id);
    }
    this.elm = document.getElementById(id);
}

[selector].prototype = {
    hide:   function () { this.elm.style.display = 'none';  return this; }
}

window.[selector] = [selector];

Dynamically changing the function/object names. This would save a lot of time. And if someone else were to use the framework they can define their own selectors. Wouldn't that be nice? Any ideas?

akinuri
  • 10,690
  • 10
  • 65
  • 102
  • So you want to assign properties into a prototype, but be able to dynamically choose to which object's prototype to add those into or something along those lines? – Jani Hartikainen Sep 22 '13 at 16:05
  • Something like that. Well, I also should be able to change the constructor's name too. Using the `selector` variable. – akinuri Sep 22 '13 at 16:12

1 Answers1

0

What about this simple generic pattern (my favorite)?

(function(){                                  //IIF constructor builder
   (this[arguments[0]]= function(/*args*/){   //The constructor
      /*constructor payload code*/
   }).prototype= {                            //Methods & Properties to inherit:
      method_1: function(){ /*your code*/ }   //method 1
   ,  method_2: function(){ /*your code*/ }   //method 2
   ,  property_1: /*your code*/               //property 1
   ,  property_2: /*your code*/               //property 2
   };
})(/*constructor identifier*/);

this will refer to the context where this build-function is run. So, if the constructor builder is run against the window, then window will have global function $elect. So you can also use this pattern inside your modular system.


Update:
You have asked about instanceof (and judging by the code you supplied, you need it so one could omit new when calling your constructor). This can make things a little bit more tricky.

To me personally, 'users' !== 'coders' and 'coders' (who write code (using x library) for users to use) should know when to use new.
While it might be useful to throw new/console.log/alert error during development, there should be no need for this in the final (minimized/optimized) production-code (unless your argument would be that you have a lot of new statements that can be omitted, so your code can be minified even further).

First one must decide whether to optimize for
execution-speed and use of new (code runs often?):

if(/*called with new*/){ 
    /*constructor payload code*/ 
} else { 
    /*error code*/ 
}

or smallest filesize by lack of new at cost of execution-speed (code runs just a couple of times? ):

if(!(/*called with new*/)){ /*error code*/ } 
/*constructor payload code*/

note that the last option might also be more practical to comment-out/remove for production code if you intend to require the use of new, giving the fastest execution-speed.

The line /*called with new*/ could be replaced with this instanceof arguments.callee Although arguments.callee is a nice generic solution (since nothing is hardcoded/named), it will throw an exception if you 'use strict';-mode, since it is now deprecated (for some reason) in favor of named function expressions (and according to John Resig, one could also use the expression this function in the future).

MDN specifies that a function name can be used only within the function's body, so for strict-mode we can name our (previously anonymous) function (C in the following example)
and check if this instanceof C (instead of this instanceof arguments.callee):

(function(){                                  //IIF constructor builder
   (this[arguments[0]]= function C(id){       //The constructor
      if(!(this instanceof C)){ /*error code*/ }  //Catch ID-10-T Error
      /*constructor payload code*/
   }).prototype= {                            //Methods & Properties to inherit:
      method_1: function(){ /*your code*/ }   //method 1
   ,  method_2: function(){ /*your code*/ }   //method 2
   ,  property_1: /*your code*/               //property 1
   ,  property_2: /*your code*/               //property 2
   };
})('$elect');

If /*error code*/ only prevents a silent error by:
alert/console.log/throw new Error ('Error: forgot new')
then up until now this pattern (both strict and non-strict variant) is mind-numbingly generic.

If you want to 'fix' the call by returning the proper new object (besides the now optional warning to the coder) then one must think about the arguments (if any) that are passed to your constructor.
So for constructors that do not expect arguments, your /*error code*/ should contain:
return new arguments.callee(); for non-strict or
return new C(); for strict
Constructors that expect (optional) named arguments, your /*error code*/ should pass them hard-coded like:
return new arguments.callee(arg_1, arg_2 /*,etc..*/); for non-strict or
return new C(arg_1, arg_2 /*,etc..*/); for strict

As example:

(function(){                                  //IIF constructor builder
   (this[arguments[0]]= function C(id){       //The constructor
      if(!(this instanceof C)) return new C(id);  //Fix ID-10-T Error
      this.elm= document.getElementById(id);
   }).prototype= {                            //Methods & Properties to inherit:
      show: function(){                       //Method show
         this.elm.style.display= '';
      }
   ,  hide: function(){                       //Method hide
         this.elm.style.display= 'none';
      }
   ,  toggle: function(){                     //Method toggle
         this[this.elm.style.display ? 'show' : 'hide']();
      }
   };
})('$elect');

I am currently un-aware of a simple and elegant way to pass any (up to 256 for firefox) non-hardcoded unknown (expected/optional/named) arguments (short of one single array/object as first argument). The problem is that arguments is an array(-like) object, so one can't simply pass it like return new C(arguments);
.apply() to the rescue one might think, since it accepts an array of arguments as second argument (unlike .call() that needs comma-separated arguments we were just trying to avoid), but alas: new and apply/call can't be used together 1, 2... (Someone, please leave a comment if you know of an elegant solution that fits in this pattern, thank you)

Lastly a small variation (and possible application) to the above pattern. The main difference is that this pattern returns a constructor (so you can simply name the variable that references it) (instead of the above pattern that directly sets the constructor):

var myLibrary= {
   $elect: (function(C){
              (C= function(id){
                 if(!(this instanceof C)) return new C(id);         
                 this.elm= document.getElementById(id);            
              }).prototype= {
                 addBorder: function(col){                            
                    this.elm.style.border='1px solid '+col;        
                 }                                                  
              ,  delBorder: function(){                            
                    this.elm.style.border='';                       
                 }    
              }; return C;                                             
            })()
, var_foo: 'foo'
, var_bar: 'bar'
};

Example fiddle here.

Hope this helps!

Community
  • 1
  • 1
GitaarLAB
  • 14,536
  • 11
  • 60
  • 80
  • An otherwise good solution, but instead of passing in a string, it would be better to just pass in the object. eg. `(function(obj) { obj = whatever; obj.prototype = whatever; })($elect);` – Jani Hartikainen Sep 22 '13 at 16:17
  • @GitaarLAB Can you show a working example of your code with a single method in jsfiddle? – akinuri Sep 22 '13 at 19:41
  • @akinuri: done, see updated answer.     @JaniHartikainen: Why is that better and.., I can't get your example to work `(function(obj) { obj = whatever; obj.prototype = whatever; })($elect);` (also not when prepending `var $elect;` or `var $elect={};`). Could you **please elaborate** (maybe post your way as an answer to)? Are there differences in scope/closing chain between these two patterns? – GitaarLAB Sep 23 '13 at 08:48