3

I am making custom class wrappers for form input fields that internally contain a DOM Node and are augmented with extra functionality method.

My questions is if there is a similar method to .toString() for appending to the DOM as I would like to directly insert my objects to the DOM instead of calling additional methods

In other workds, here is an example of what I have:

function A () {
  this.element = documenet.createElement('input');
  // blah blah logic
  this.toString = function () {
    return '<input type="'+this.element.type+'" value="'+this.element.value+'" />';
  }
  // a similar method to this i'ld like
  this.toString = function () {
    return this.element;
  }
}

so that i can use it as follows:

var a = new A();

// this works as it calls .toString(), but it is a hack and it is not pretty
document.body.innerHTML += a;

// this is what i'd want to be able to do:
document.body.appendChild(a);

// this is what **I AM REALLY TRYING TO AVOID:**
document.body.appendCHild(a.toElement());

You can't simply inherit from the DOM Node as it is not a public class

I've tried looking at other questions but none seem to have the answer... any ideas would be highly welcomed

Alexander
  • 23,432
  • 11
  • 63
  • 73
Stefan
  • 3,962
  • 4
  • 34
  • 39

3 Answers3

3

You can't inherit from the native DOM constructors, but you can inherit your wrapper class from jQuery!

function A () {
    if (!(this instanceof A)) return new A(); // fix wrong constructor invocations
    if (this.length) { // shouldn't be set yet, so another odd invocation:
        var empty = Object.create(A.prototype); // a new, but *empty* instance
        empty.length = 0; // explicitly set length
        return empty;
    }

    this[0] = document.createElement('input');
    …
    // or you might want to call $.fn.init
    this.context = undefined; // not sure whether
    this.selector = ""; // we need those two lines
    this.length = 1; // but the length is obligatory
}
A.prototype = Object.create($.fn, {constructor:{value:A,configurable:true}});
A.prototype.toString = function () {
    return '<input type="'+this[0].type+'" value="'+this[0].value+'" />';
}

With that, you could just $(document.body).append(new A) or new A().appendTo(document.body) etc.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • one thing though, given that I now inherit from jQuery I was expecting to get the full functionality of the jquery object in my new classes, but for some reason the following code fails with the error (while using the exact constructors from above): Cannot read property `length` of undefined var a = new A(); var b = new B(); a.add(b); – Stefan Dec 18 '12 at 15:56
  • 1
    Urgh. `pushStack` (called from `add`) expects `this.constructor();` (without `new`!) to instantiate a new object. Possible solutions: a) don't overwrite the `constructor` property and let the result of `.add` be a jQuery instance b) fix your copy of jQuery and add the `new` c) add the line `if(!(this instanceof A))return new A();` (possibly augmented with arguments) to the `A` constructor. – Bergi Dec 18 '12 at 16:11
  • sounds reasonable, but that still does not fix it (i.e. the above fixed code with the instanceof check does not change anything) Not overwriting the constructor is not an option and I also don't want to tamper with jQuery internals due to portability issues, thus the need of proper wrappers :) – Stefan Dec 18 '12 at 16:44
  • actually, looking into it, other methods are broken as well, such as .parent() and probably other as well – Stefan Dec 18 '12 at 16:48
  • 1
    Ah, they called `this.constructor()` so `this instanceof A` didn't help to test. I fixed that by testing for the `length` property. Also, to prevent `a.add(b)` returning 3 elements we will need to return an empty selection when called from there. Working demo: http://jsfiddle.net/vRQbT/ – Bergi Dec 18 '12 at 17:08
1

Instead of thinking of A as an object, you can think of it as a factory method: http://jsfiddle.net/fnK56/2/

// We can modify ele however we like, even overriding the toString method.
function CustomElement() {
    var ele = document.createElement("input");
    ele.type = "button";
    ele.value = "Testing";
    // do stuff to ele
    ele.CustomProperty = {one:"two", three:"four"}; // we can even add custom properties that will persist with that DOM node. 

    ele.toString = function(){
        return '<input type="'+this.type+'" value="'+this.value+'" />';
    };

    return ele;
}

var tmp = CustomElement(); // get us a new CustomElement

document.body.innerHTML += tmp; // this works because of the overridden toString
tmp.id = "Test";
document.body.appendChild(tmp);​ // this works because tmp is still a DOM input node.

console.log(tmp.CustomProperty); // Object {one: "two", three: "four"} 
console.log(document.getElementById("Test").CustomProperty); // Object {one: "two", three: "four"} 
Shmiddty
  • 13,847
  • 1
  • 35
  • 52
  • 1
    hmm... nice trick, it is definitely an improvement, but it does have the downfall that it heavily clutters the elements with all the DOM Node attributes and methods which, although makes sense as what you are doing basically simulates inheriting from DOM Node, it becomes quite hard to debug. More importantly, this does not work with actual inheritance (I am trying to use have form elements inherit from each other, etc so this does not work in that case) – Stefan Dec 17 '12 at 18:26
1

This won't on all browsers (see http://www.meekostuff.net/blog/Overriding-DOM-Methods/) for a good discussion of options, but:

HTMLElement.prototype._appendChild = HTMLElement.prototype.appendChild;
HTMLElement.prototype.appendChild = function(target)
{
    if(target.toElement)
    {
        this._appendChild(target.toElement());
    }
    else
    {  
        //attempt to do it the old fashioned way
        this._appendChild(target);
    }


}

This enables your syntax:

// this is what i'd want to be able to do:
document.body.appendChild(a);

and hides the fact that "toElement()" is called internally.

I would note, when you start getting to this level of complexity, using a framework like jQuery starts to make sense.

Joe Enzminger
  • 11,110
  • 3
  • 50
  • 75
  • I use jquery, but i don't want to fiddle with the core objects/methods. In other words, it is not good practice at all to modify either HTMLElement.prototype nor alter the base jquery.append methods, etc, that is why I am trying to alter the objects I create instead :) – Stefan Dec 17 '12 at 18:23
  • That's simply bad practice. – Bergi Dec 17 '12 at 18:24
  • @tak3r: But you could inherit from jQuery! – Bergi Dec 17 '12 at 18:24
  • @Bergi - No argument on "bad practice". My answer was an attempt to provide a solution that met the requirements of the OP's question, not to opine on why he would want to do it that way in the first place. My aside about using jQuery was to hint that there is probably a better way to approach the problem than the one he chose to ask about. Indeed, if you are already using jQuery, then I would question why you are using document.createElement() and element.appendChild() at all. – Joe Enzminger Dec 17 '12 at 19:12
  • it was a simpler analogy to make as I am trying to make it as portable as possible. I don't mind using jquery but I was curious on alternatives as well, thus the raw javascript problem description :) – Stefan Dec 17 '12 at 22:43