-
8 Dec 2012 12:29 PM #1
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); } });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.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 ] }] }
Code:Ext.define('AM.view.crafter.Block', { extend: 'Ext.panel.Panel', alias: 'widget.block', config: { id: 0 }, html: '<img src="images/'+this.getId+'.png" />' });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)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 }] }
Thank you in advance for some guidance.
-
Best Answer Posted by skirtle
OK, let's start with this one:
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.Code:Ext.define('AM.view.crafter.Block', { extend: 'Ext.panel.Panel', alias: 'widget.block', config: { id: 0 }, html: '<img src="images/'+this.getId+'.png" />' });
This line cannot possibly work:
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.Code:html: '<img src="images/'+this.getId+'.png" />'
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:
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.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); } });
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:
You'd then create this using: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(); } });
or even better: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'}) ]
You could throw in a defaultType too if you don't fancy typing all those xtypes.Code:items: [ {xtype: 'block', imgId: '266'}, {xtype: 'block', imgId: 'dec:30216'}, {xtype: 'block', imgId: '266'} ]
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:
See the docs for tpl, data and update if you want to know more about what I'm doing here.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}); } });
-
10 Dec 2012 1:40 AM #2
OK, let's start with this one:
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.Code:Ext.define('AM.view.crafter.Block', { extend: 'Ext.panel.Panel', alias: 'widget.block', config: { id: 0 }, html: '<img src="images/'+this.getId+'.png" />' });
This line cannot possibly work:
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.Code:html: '<img src="images/'+this.getId+'.png" />'
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:
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.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); } });
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:
You'd then create this using: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(); } });
or even better: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'}) ]
You could throw in a defaultType too if you don't fancy typing all those xtypes.Code:items: [ {xtype: 'block', imgId: '266'}, {xtype: 'block', imgId: 'dec:30216'}, {xtype: 'block', imgId: '266'} ]
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:
See the docs for tpl, data and update if you want to know more about what I'm doing here.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}); } });
-
10 Dec 2012 5:49 PM #3
works perfectly
works perfectly
Thank you so much for such a thorough answer!
Tpl approach worked exactly as I needed.



Reply With Quote