1. #1
    Sencha User
    Join Date
    Feb 2010
    Posts
    5
    Vote Rating
    0
    alex-tech is on a distinguished road

      0  

    Default Answered: Multiple Component Instance

    Answered: Multiple Component Instance


    Greetings,
    My background is PHP MVC and Java OOP styles so the posted tutorials do not seem to resonate with me and none of my experiments work.

    I have component class AM.view.crafter.Table laying out AM.view.crafter.Block components. My first attempt works only as long as the Block components have unique IDs:

    Code:
    Ext.define('AM.view.crafter.Block', {
        extend: 'Ext.panel.Panel',
        alias: 'widget.block',
        id: 0,
    
    
        constructor: function(id) {
            this.html = '<img src="images/'+id+'.png" />';
            this.callParent(arguments);
        }
    });
    Code:
    Ext.define('AM.view.crafter.Table', {
        extend: 'Ext.panel.Panel',
        alias: 'widget.craftingtable',
        layout: 'column',
    
    
        store: 'Components',
         items: [
            {
                xtype: 'panel',
                layout: 'vbox',
                items: [
                    Ext.create('AM.view.crafter.Block', '266'),
                    Ext.create('AM.view.crafter.Block', 'dec:30216'),
                    // Ext.create('AM.view.crafter.Block', '266') //  if this is not commented get error bellow
                ]
            }]
    }
    With that code I get Registering duplicate id. I thought this is because Ext.create does not actually create a separate new object instance but copies the prototype so I made it in this style but nothing shows up at all:

    Code:
    Ext.define('AM.view.crafter.Block', {
        extend: 'Ext.panel.Panel',
        alias: 'widget.block',
    
    
        config: {
          id: 0
        },
    
    
        html: '<img src="images/'+this.getId+'.png" />'
    });
    Code:
    Ext.define('AM.view.crafter.Table', {
        extend: 'Ext.panel.Panel',
        alias: 'widget.craftingtable',
        layout: 'column',
    
    
        store: 'Components',
    
    
        blocks: Array(),
    
    
        initComponent: function() {
            var block1 = Ext.create('AM.view.crafter.Block');
            block1.setId(266);
            var block2 = Ext.create('AM.view.crafter.Block');
            block2.setId('dec:30216');
            var block3 = Ext.create('AM.view.crafter.Block');
            block1.setId(266);
    
    
            this.blocks.push(block1);
            this.blocks.push(block2);
            this.blocks.push(block3);
    
    
            this.callParent(arguments);
        },
    
    
        items: [
            {
                xtype: 'panel',
                layout: 'vbox',
                items: this.blocks
            }]
    }
    I also thought about applying a Model to the component the way it is in Grid Panel components tutorial but do not know how to apply to it a custom component that does not handle a datastore (tutorials just assume the grid does it automatically I guess)

    Thank you in advance for some guidance.

  2. OK, let's start with this one:

    Code:
    Ext.define('AM.view.crafter.Block', {
        extend: 'Ext.panel.Panel',
        alias: 'widget.block',
    
    
        config: {
          id: 0
        },
    
    
        html: '<img src="images/'+this.getId+'.png" />'
    });
    The use of a config block is standard practice in Touch 2 but in ExtJS 4 I wouldn't go anywhere near it. It was half-baked in 4.0 and the attempts to fix it in 4.1 still fall short in my opinion.

    This line cannot possibly work:

    Code:
    html: '<img src="images/'+this.getId+'.png" />'
    The biggest problem here is that it is evaluated immediately, before Ext.define is even called. At that point the this reference will point to the global window object.

    It's important to understand how config options end up on this. It isn't a JavaScript feature, it's something done by the framework. In the constructor for components (and some other types of class) there is a line Ext.apply(this, config); which copies the config onto the current object. Prior to running the constructor the config will not be copied across.

    Components have a template method called initComponent that runs after this copying takes place. In general it is best practice to override this method rather than the constructor and to reference the properties via this rather than the config. This won't necessarily come naturally if you have a Java background, it took me a long while to stop mucking around in the constructor and just let things take their course.

    Next up:

    Code:
    Ext.define('AM.view.crafter.Block', {
        extend: 'Ext.panel.Panel',
        alias: 'widget.block',
        id: 0,
    
    
        constructor: function(id) {
            this.html = '<img src="images/'+id+'.png" />';
            this.callParent(arguments);
        }
    });
    As you correctly identified, the problem here is that you've put an id on the prototype so it will be shared between all instances. You should generally avoid using component ids at all. It isn't clear whether you're doing it on purpose or not but the property id has a special meaning and if you just wanted to pass through the image location I'd suggest using a different name for it.

    Quite why you're extending Panel isn't clear. Panels are heavy-weight components and should only be used where necessary (e.g. if you need docked items such as headers or toolbars). For adding some simple HTML it's overkill.

    A more Ext way of writing this might be:

    Code:
    Ext.define('AM.view.crafter.Block', {
        extend: 'Ext.Component',
        alias: 'widget.block',
    
        initComponent: function() {
            this.html = '<img src="images/' + this.imgId + '.png" />';
            this.callParent();
        }
    });
    You'd then create this using:

    Code:
    items: [
        Ext.create('AM.view.crafter.Block', {imgId: '266'}),
        Ext.create('AM.view.crafter.Block', {imgId: 'dec:30216'}),
        Ext.create('AM.view.crafter.Block', {imgId: '266'})
    ]
    or even better:

    Code:
    items: [
        {xtype: 'block', imgId: '266'},
        {xtype: 'block', imgId: 'dec:30216'},
        {xtype: 'block', imgId: '266'}
    ]
    You could throw in a defaultType too if you don't fancy typing all those xtypes.

    To take this a little further, if you need to be able to change the image dynamically after creation then I'd use a tpl instead:

    Code:
    Ext.define('AM.view.crafter.Block', {
        extend: 'Ext.Component',
        alias: 'widget.block',
    
        tpl: '<img src="images/{id}.png" />',
    
        initComponent: function() {
            this.callParent();
            this.setImgId(this.imgId);
        },
    
        setImgId: function(id) {
            this.update({id: id});
        }
    });
    See the docs for tpl, data and update if you want to know more about what I'm doing here.

  3. #2
    Sencha Premium Member skirtle's Avatar
    Join Date
    Oct 2010
    Location
    UK
    Posts
    3,624
    Vote Rating
    331
    Answers
    550
    skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future

      1  

    Default


    OK, let's start with this one:

    Code:
    Ext.define('AM.view.crafter.Block', {
        extend: 'Ext.panel.Panel',
        alias: 'widget.block',
    
    
        config: {
          id: 0
        },
    
    
        html: '<img src="images/'+this.getId+'.png" />'
    });
    The use of a config block is standard practice in Touch 2 but in ExtJS 4 I wouldn't go anywhere near it. It was half-baked in 4.0 and the attempts to fix it in 4.1 still fall short in my opinion.

    This line cannot possibly work:

    Code:
    html: '<img src="images/'+this.getId+'.png" />'
    The biggest problem here is that it is evaluated immediately, before Ext.define is even called. At that point the this reference will point to the global window object.

    It's important to understand how config options end up on this. It isn't a JavaScript feature, it's something done by the framework. In the constructor for components (and some other types of class) there is a line Ext.apply(this, config); which copies the config onto the current object. Prior to running the constructor the config will not be copied across.

    Components have a template method called initComponent that runs after this copying takes place. In general it is best practice to override this method rather than the constructor and to reference the properties via this rather than the config. This won't necessarily come naturally if you have a Java background, it took me a long while to stop mucking around in the constructor and just let things take their course.

    Next up:

    Code:
    Ext.define('AM.view.crafter.Block', {
        extend: 'Ext.panel.Panel',
        alias: 'widget.block',
        id: 0,
    
    
        constructor: function(id) {
            this.html = '<img src="images/'+id+'.png" />';
            this.callParent(arguments);
        }
    });
    As you correctly identified, the problem here is that you've put an id on the prototype so it will be shared between all instances. You should generally avoid using component ids at all. It isn't clear whether you're doing it on purpose or not but the property id has a special meaning and if you just wanted to pass through the image location I'd suggest using a different name for it.

    Quite why you're extending Panel isn't clear. Panels are heavy-weight components and should only be used where necessary (e.g. if you need docked items such as headers or toolbars). For adding some simple HTML it's overkill.

    A more Ext way of writing this might be:

    Code:
    Ext.define('AM.view.crafter.Block', {
        extend: 'Ext.Component',
        alias: 'widget.block',
    
        initComponent: function() {
            this.html = '<img src="images/' + this.imgId + '.png" />';
            this.callParent();
        }
    });
    You'd then create this using:

    Code:
    items: [
        Ext.create('AM.view.crafter.Block', {imgId: '266'}),
        Ext.create('AM.view.crafter.Block', {imgId: 'dec:30216'}),
        Ext.create('AM.view.crafter.Block', {imgId: '266'})
    ]
    or even better:

    Code:
    items: [
        {xtype: 'block', imgId: '266'},
        {xtype: 'block', imgId: 'dec:30216'},
        {xtype: 'block', imgId: '266'}
    ]
    You could throw in a defaultType too if you don't fancy typing all those xtypes.

    To take this a little further, if you need to be able to change the image dynamically after creation then I'd use a tpl instead:

    Code:
    Ext.define('AM.view.crafter.Block', {
        extend: 'Ext.Component',
        alias: 'widget.block',
    
        tpl: '<img src="images/{id}.png" />',
    
        initComponent: function() {
            this.callParent();
            this.setImgId(this.imgId);
        },
    
        setImgId: function(id) {
            this.update({id: id});
        }
    });
    See the docs for tpl, data and update if you want to know more about what I'm doing here.

  4. #3
    Sencha User
    Join Date
    Feb 2010
    Posts
    5
    Vote Rating
    0
    alex-tech is on a distinguished road

      0  

    Default works perfectly

    works perfectly


    Thank you so much for such a thorough answer!
    Tpl approach worked exactly as I needed.

Thread Participants: 1

Tags for this Thread