-1

I'm trying to create a grid using javascript prototype inheritance, however there is a problem I could not understand. In some point of the code the "new" keyword seems not work.

It's hard to explain, so the code are below and I put comments in the main points.

<head>
    <!-- this is the "parent class" -->
    <script type='text/javascript'>
        // constructor
        function Container(CSSClassName) {
            this.CSSClassName = CSSClassName;
            this.build = ContainerBuild;

            this.components = new Object();

            this.addComponent = ContainerAddComponent;
            this.getComponent = ContainerGetComponent;
        }

        /*
         * methods
         */

        function ContainerAddComponent(id, component) {
            this.components[id] = component;
        }

        function ContainerGetComponent(id) {
            return this.components[id];
        }

        function ContainerBuild() {
            this.element = document.createElement('div');
            this.element.className = this.CSSClassName;

            for (var i in this.components) {
                this.element.appendChild(this.getComponent(i).build());
            }

            return this.element;
        }
    </script>

    <!-- Below I'm using prototype inheritance -->
    <script type='text/javascript'>
        Grid.prototype = new Container('grd');
        function Grid() {
            this.addComponent('body', new GridPart());
            this.addComponent('foot', new GridPart());
        }

        GridPart.prototype = new Container('grd-part');
        function GridPart() {
            this.addComponent('comp', new Container('comp')); // this new keywork seems not to work.

            /* ***** I tried this code, but the result was the same.
            var comp = new Container('comp');
            this.addComponent('comp', Object.create(comp)); // same unexpected result.
            */
        }

        window.onload = function() {
            var grd = new Grid();
            document.getElementById('grd').appendChild(grd.build());

            grd.getComponent('body').element.style.background = 'red'; // ok!
            grd.getComponent('body').getComponent('comp').element.style.background = 'gold'; // unexpected behavior

            // Testing the objects.
            console.log(grd.getComponent('body') === grd.getComponent('foot')); // false, ok!
            console.log(grd.getComponent('body').getComponent('comp') === grd.getComponent('foot').getComponent('comp')); // true?? should be false!
        }
    </script>

    <style type='text/css'>
        .grd { border: 1px solid black }
        .grd-part {
            height: 25px;
            padding: 12px;
            border-bottom: 1px solid black;
        }
        .grd-part:last-child { border:none }
        .grd-part .comp {
            background: black;
            height: 25px;
        }
    </style>
</head>
<body>
    <div id='grd'></div>
</body>

If you copy and past this code into a html document you will see the problem. The yellow rectangle should be inside of the red rectangle!

Anyone knows what happens?

jairhumberto
  • 535
  • 1
  • 11
  • 31

2 Answers2

1

Glancing over your code, I suspect the problem is that every instance of Grid and GridPart share the same components object. You could verify this by having a look at the prototype chain or check the result of grd.getComponent('body').components === grd.getComponent('foot').components.

Don't do something like

Grid.prototype = new Container('grd');

it adds instance specific properties to the prototype, so all instances share the same instance specific properties (like components). Instead, use Object.create to establish inheritance:

Grid.prototype = Object.create(
  Container.prototype, 
  {constructor: {value: Grid, configurable: true, writable: true}}
);

and call the parent constructor inside the the child constructor:

function Grid() {
  Container.call(this, 'grd');
  this.addComponent('body', new GridPart());
  this.addComponent('foot', new GridPart());
}

Properties that are instance specific should be set in the constructor, properties that should be shared are set on the prototype. So in your case you should do Container.prototype. ContainerAddComponent = ContainerAddComponent;.

See also Benefits of using `Object.create` for inheritance


If you are already able to use ES6, use the new class syntax:

class Grid extends Container {
    constructor() {
      super('grd');
      this.addComponent('body', new GridPart());
      this.addComponent('foot', new GridPart());
    }
}
Community
  • 1
  • 1
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • Thank you! Now I know how to use Object.create for inheritance! I've tried before but without success. Thank you for the answer, it solved my problem. I will see about ES6 too. I didn't know that It was available already! Thank you :) – jairhumberto Aug 27 '14 at 15:21
  • 1
    I don't think the class syntax is supported by any browser yet, but there are transformation tools that convert this to valid ES5 code. For example http://www.phpied.com/writing-es6-today-with-jstransform/ – Felix Kling Aug 27 '14 at 15:22
1

The problem is not that the new keyword is not working anymore but rather it lies in the following line: GridPart.prototype = new Container('grd-part');

What it does is cause all GridPart objects to have the same this.components object.

So when you call this.addComponent inside the GridPart constructor function you override the same this.components object at index 'comp' with a new Container object

I've found a way to achieve the inheritance you are looking for here and integrated this solution into your code.

here is a working codepen

Community
  • 1
  • 1
Yonatan Naor
  • 1,367
  • 18
  • 19