WebReflection/document-register-element

Extends the HTMLTableElement and HTMLTableRowElement

MagicJack opened this issue · 10 comments

Hi,

I try to create 2 Custom Elements by extend the HTMLTableElement and HTMLTableRowElement, but it fail.

After further debuging, it failed on the code for prototype chain clone.
defineProperty(o, key, gOPD(p, key));

gOPD(p,key) return the correct Object, but defineProperty() fail to define it in new object.

I tested it on IE10, here's part of the js code for extend HTMLTableElement:

    var HiTable = Object.create(HTMLTableElement.prototype);
    HiTable.setNamePref = function(namePref) {
        this.namePref = namePref;
    }
    document.registerElement('hi-table', { prototype: HiTable } );
...
    var myCreateTable = function (captionText, className, panelName) {
        var tblObj = document.createElement('hi-table');
        tblObj.setNamePref(className+'Row');

        if (className)
            tblObj.className = className;
        if (captionText) {
            tblObj.createCaption();
            tblObj.caption.innerHTML = captionText;
        }
        if (panelName)
            document.getElementById(panelName).appendChild(tblObj);
        return tblObj;
    }
...
    myCreateTable('Caption Text', 'whatStyle', 'whereToPut');

It fails at createCaption() in function myCreateTable()if the prototype is created from HTMLElement, cause the function belongs to HTMLTableElement only.

It fails at the same point if the prototype is created from HTMLTableElement, cause:
defineProperty() fail to define object returned by gOPD(p,key) (line 110)

Hi, I found the way to solved the problem by change my script.

document.registerElement('hi-table', { prototype: HiTable, extends:'table' } );
...
var tblObj = document.createElement('table', 'hi-table');

any idea why it has to be so?

Hi @MagicJack , first of all thanks for using github instead :-)

So here the thing … there are two ways, as you've realized already, to create Custom Elements

  1. the one using a tag with at least a dash in the name, x-bla, my-table
  2. the one using the is attribute

The main difference between these two methods is that the second one never requires Shadow DOM.

You can think about input, if you create a my-field element from HTMLInputElement prototype but you chose your own my-field name instead of input with is attribute as my-field, you will not see the input the way you expect, you'll rather see an empty space.

In order to create same input functionality you need the Shadow DOM behind the scene or you can manually inject the input at runtime or you create an <input>, and you use is attribute with your registered class.

I believe same applies for table element, and generally speaking all elements that are not already recognized by the browser and have a different behavior from HTMLElement or HTMLUnknownElement.

However, I've no idea why the code was failing and I'd like to understand if it's a IE only thing or if it's the same in every browser but the short answer to your problem is: use this form for elements with a special behavior

document.registerElement('hi-table', { prototype: HiTable, extends:'table' } );
var tblObj = document.createElement('table', 'hi-table');


document.registerElement('hi-input', { prototype: HiTable, extends:'input' } );
var tblObj = document.createElement('input', 'hi-input');

I hope this help, feel free to close this bug after whenever you want (or ask more)

Hi, @WebReflection:

  1. I test the code of my 1st post on chrome v36.0.1985.143 m, I got the same error as IE. They both stop at the function createCaption() when I create a new ht-table object.
    on chrome I got 'Uncaught TypeError: Illegal invocation'
    on IE10 it's a simular error message in chinese.
  2. I also try to rewite the workable code in the style you've posted, as below.
    But, it does not work! (what happen ?!?)
    document.registerElement('hi-table', {
        prototype: Object.create(
        HTMLTableElement.prototype, {
            setNamePref: { value: function() {
                this.namePref = namePref;
            }},
            insertHiRow: { value: function() {
                var rowObj = document.createElement('tr', 'hi-row');
                var tbObj;
                rowObj.init(this.namePref, idx);
                if (this.tBodies.length==0) {
                    this.appendChild(document.createElement('tbody'));
                }
                tbObj = this.tBodies[0];
                while (idx > tbObj.childNodes.length)
                    tbObj.appendChild(document.createElement('tr'));
                if (idx == tbObj.childNodes.length)
                    tbObj.insertBefore(rowObj);
                else
                    tbObj.insertBefore(rowObj, tbObj.childNodes[idx]);
                return rowObj;
            }}
        }),
        extends: 'table'
    });
...

    var tblObj = document.createElement('table', 'hi-table');

It would help a lot if you could actually write some code I can test … so many thing missing up there … setNamePref has no parameter, no idea where namePref comes from. Rest of the logic I don't know what it does, where is hi-row defined or anything else but for sure nothing happened since last time you told me that other form was working … no commits in here 'cause I don't know what to fix exactly.

The illegal invocation makes sense to me if you consider what I've said about inherited behavior VS "same kind of DOM node" which is the case of the is attribute.

I've added a test that use createCaption without problems. I will close this bug but feel free to ping me back if you have other questions.

Note: nothing actually changed in the code, not sure why your one is failing.

P.S. bear in mind that registration order matters … if you need hi-row inside hi-table you should register hi-row before the table one. Just in case that was your last issue.

OK, Thanks.
I find my bugs. Both code styles can work (thanks for the hint that setNamePref has no parameter).

also, after some testing, I got 2 conclutions:
1.If I'm just extending HTMLElement then create an object of what I'm registered (say some other than hi-table and hi-row of my code) will work fine.
2. If I want to extend HTMLTableElement or HTMLTableRowElement or anything else that's derivation of HTMLElement, the only possible workable combination is as below
(Still, I don't know why it has to be so.)

    var HiTable = Object.create(HTMLTableElement.prototype);
...
    document.registerElement('hi-table', { prototype: HiTable, extends:'table' } );
...
    var tblObj = document.createElement('table', 'hi-table');
  • Have to create the prototype by the derivated one
  • Have to register with extends:
  • Have to create the element by orginal with extension

The combination is still true when using the native suport of chrome v36

The codes below are what I used for the testing.

<!DOCTYPE html>
<html>
<head><title>test of registerElement</title>
<script type="text/javascript" src="registerElement-max.js"></script>
<script type="text/javascript">
    var HiElement = Object.create(HTMLElement.prototype);
    HiElement.func1 = function func1() {
        alert("This is function1 Hi-Element");
    }
    HiElement.func2 = function func2() {
        alert("This is function2 Hi-Element");
    }
    document.registerElement('hi-element', { prototype: HiElement } );

//  var HiTable = Object.create(HTMLElement.prototype); // Not workable
    var HiTable = Object.create(HTMLTableElement.prototype);
    HiTable.func1 = function func1() {
        alert("This is function1 of Hi-Table");
    }
    HiTable.func2 = function func2() {
        alert("This is function2 of Hi-Table");
    }
    document.registerElement('hi-table', { prototype: HiTable, extends:'table' } );
//  document.registerElement('hi-table', { prototype: HiTable } );      // not workable

    function fnInit() {
        var elemObj = document.createElement('hi-element');
        elemObj.innerHTML = "Hello World";
        elemObj.func1();    // OK
        var iPt = document.getElementById('insPt');
        iPt.appendChild(elemObj);

        // insertAdjacentHTML() belongs HTMLElement
        elemObj.insertAdjacentHTML('BeforeBegin', 'BBHeader[');
        elemObj.insertAdjacentHTML('AfterEnd', ']');

//      var tblObj = document.createElement('hi-table');    // This make createCaption() fail
        var tblObj = document.createElement('table', 'hi-table');   // This OK

        // This fail if registerElement() without "extends:'table'"
        // In chrome v36: tblObj.func1() is undefined
        tblObj.func1();

        // This fail if createElement() without extendsion
        tblObj.createCaption();
        tblObj.caption.innerHTML = "The Table Header";

        iPt.appendChild(tblObj);
    }
    window.addEventListener('load', fnInit);
</script>
</head>
<body>
Before The Insert Point
<div id="insPt"></div>
After The Insert Point
</body>
</html>

Thanks for the code and the clarification, my last question would be: is Chrome native producing same results? If yes, we are good .. if not, I might put a big warning in the front page … however, I think the is attribute js the preferred way in any case for backward compatibility, hopefully not a big deal … or you can still do something like:

var HiTable = Object.create(HTMLTableElement.prototype);
HiTable.createdCallback = function () {
  this.table = this.appendChild(
    document.createElement('table')
  );
};

document.registerElement('hi-table', { prototype: HiTable });

And use the internal table instead

Yes.
Chrome native, Chrome + extension code and IE10 + extension, they produce same results.
Maybe I'll try IE9 and IE11 later.

I think you could add another example for people who want to add extends the derivation of HTMLElement not just HTMLElement itself.

added example and explanation.

Best Regards