PDA

View Full Version : [Ext 2.0] Ext.ux.ComponentLoader



aconran
28 Sep 2007, 11:50 PM
I am experimenting with loading my components via server-side json which non-developers can easily manipulate. My first pass at a ComponentLoader is here:



Ext.namespace('Ext.ux');

/**
* @class Ext.ux.ComponentLoader
* @author Aaron Conran
* Provides an easy way to load components dynamically. If you name these components
* you can use Ext.ComponentMgr's onAvailable function to manipulate the components
* as they are added.
* @singleton
*/
Ext.ux.ComponentLoader = function() {
var defaultXType = 'panel';
var cm = Ext.ComponentMgr;
return {
/**
* Load components from a server resource, config options include anything available in @link Ext.data.Connect#request
* Note: Always uses the connection of Ext.Ajax
*/
load : function(config) {
Ext.apply(config, {
callback: this.onLoad,
scope: this
});
Ext.Ajax.request(config);
},
// private
onLoad : function(opts, success, response) {
var config = Ext.decode(response.responseText);
for (var i = 0; i < config.components.length; i++) {
var comp = cm.create(config.components[i], config.defaultXType[i] || defaultXType)
}
}
};
}();


An Example of how you would use it:


Ext.ux.ComponentLoader.load({url: 'loadComponents.cfm'});
Ext.ComponentMgr.onAvailable('bookTree', function(bookTree) {
bookTree.root.appendChild(
new Ext.tree.TreeNode({text: 'JavaScript: The Definitive Guide', allowDrag: false}),
new Ext.tree.TreeNode({text: 'Pro JavaScript Techniques', allowDrag: false}),
new Ext.tree.TreeNode({text: 'Visual C# 2005 Recipes', allowDrag: false})
);
bookTree.on('click', function(node, e) {
var inner = String.format('Please place a nice review for {0} here.', node.attributes.text);
Ext.getCmp('content').body.update(inner);
});
});


And finally the JSON packet that the load received via the coldfusion request.


{
defaultXType: 'panel',
components: [
{
xtype: 'viewport',
layout: 'border',
items: [{
region: 'north',
border: false,
height: 100,
split: false,
html: 'Header'
},{
layout: 'border',
region: 'center',
border: false,
items: [{
xtype: 'treepanel',
region: 'west',
width: 320,
id: 'bookTree',
rootVisible:false,
root: new Ext.tree.TreeNode(),
title: 'Books'
},{
id: 'content',
region: 'center',
html: 'sample'
}]
},{
region: 'south',
height: 24,
title: 'Dynamic Loader Demo'
}]
}
]
}


I will be putting together an article on how to use the extension and some of the benefits/disadvantages of loading your components this way.

Aaron

brian.moeskau
29 Sep 2007, 10:00 AM
Cool idea Aaron. Looking forward to the accompanying article.

6epcepk
30 Sep 2007, 11:35 AM
Thanks for ux!
But how I can execute handlers?

'handler' => 'function() {alert("dsf")}'
// -> this.handler.call is not a function
Thanks.

mlarese
30 Sep 2007, 1:50 PM
is i t possible to define components to use inside other components?
something like



{
defaultXType: 'panel',
east:{
title:'east',
region:'east'
},
components: [{
xtype: 'viewport',
layout: 'border',
items: [ {
layout: 'border',
region: 'center',
border: false,
items: [{
xtype: 'treepanel',
region: 'west',
width: 320,
id: 'bookTree',
rootVisible:false,
root: new Ext.tree.TreeNode(),
title: 'Books'
},this.east,{
id: 'content',
region: 'center',
html: 'sample'
}]
},{
region: 'south',
height: 24,
title: 'Dynamic Loader Demo'
}]
}
]
}

aconran
30 Sep 2007, 5:23 PM
mlarese -

Why would you want to do that as opposed to placing the east config inside the components array? I suppose if you were going to use a similar configuration it may be useful. However, being as this JSON is expected to be generated via a server-side component I would expect that to be implemented on the server-side.

Aaron

aconran
30 Sep 2007, 5:25 PM
6epcepk -

What do you mean by execute handlers? This extension is primarily to load components via server-side JSON. You can manipulate/use the components after they are loaded. To determine when a specific component is loaded use onAvailable of the Ext.ComponentMgr singleton as shown in the above sample.

Aaron

mlarese
30 Sep 2007, 9:44 PM
@aconran
it was just an example.
the problem is that a json object if is to big is difficult to write and debug.
It is mor easy to write pieces of it and compose the big one.

6epcepk
1 Oct 2007, 12:42 AM
6epcepk -

What do you mean by execute handlers? This extension is primarily to load components via server-side JSON. You can manipulate/use the components after they are loaded. To determine when a specific component is loaded use onAvailable of the Ext.ComponentMgr singleton as shown in the above sample.

Aaron

Wnen JSON is loaded (menu items), handlers doesn't execute on menu item click.
Error: this.handler.call is not a function
Thanks.

Foggy
1 Oct 2007, 4:00 AM
IMHO you could use something like this
Ext.get('yourComponentId').on('click', doSomething);
or maybe with Ext.getCmp instead of Ext.get...

jamesfarrer
25 Apr 2008, 9:04 AM
Hi,

I note in all the examples it tends to revolve around a viewport....

However, I have something else I would like to do with it - is this possible?
I have a viewport defined statically in a js (which I want), with a toolbar as one of my regions - can I use component loader to load toolbar items into it?

Thanks

James

brookd
25 Apr 2008, 2:56 PM
This looks very promising and I am playing with it finally today. I am bit unclear how this should work. I started by loading a simple panel, via this json - which works great:



{
defaultXType: 'panel',
components: [
{
xtype: 'panel',
id:'testpanel',
title:'My nested panel',
iconCls: 'icon-el',
html:'Hello!',
autoHeight:true
}
]


}


Then, I went one step further, and just for testing added a button inside the panels items array:



{
defaultXType: 'panel',
components: [
{
xtype: 'panel',
id:'testpanel',
title:'My nested panel',
iconCls: 'icon-el',
html:'Hello!',
autoHeight:true,
items:[
{
xtype: 'button',
id:'mybutton',
text:'Click me'
}
]
}
]

}


That generated an error. The panel was loaded, but not the button. Does each component need to be loaded as a separate entry in the components array and then joined together on the client side? Is it not possible to nest components in this manner?

chalu
25 Apr 2008, 6:35 PM
With this in place, one could have an application where the user can edit (add / remove / configure) components (widgets) of the application, say in their profile and then get the configured stuff 'loaded' for them when they login.

brookd
25 Apr 2008, 8:09 PM
After some more testing, I found it IS possible to load nested components via the items array, just not a button. But the components array can contain multiple components and each one gets loaded and becomes available, so that works!

Now, I am new to JS, and not sure this is relevant or useful, But I added some code to this UX to track the components being loaded and be able to fire a function after ALL of the components are loaded. The function is defined in the loaded json like this:


{
defaultXType: 'panel',
onLoadComplete: function(){
console.log('Sweet, each component is loaded - now put em together or something');

var btn = Ext.ComponentMgr.get('mybutton');
var testpanel = Ext.ComponentMgr.get('testpanel');
testpanel.add(btn);
testpanel.doLayout();

},
components: [
{
xtype: 'panel',
id:'testpanel',
title:'My nested panel',
iconCls: 'icon-el',
html:'Hello!',
autoHeight:true
},

{
xtype: 'button',
id:'mybutton',
text:'Click me',
scope:this,
handler:function(){console.log('test')}
}

]


}

Basically, 'onLoadComplete' is the method that will run after each component is loaded. In my example, I am simply adding the button to the panel (since I couldn't do this in the items array for some reason), but you could also add any other listeners as required or whatever else needs to be done. For my use, this would allow me to keep the code that executes when the components load together with the remotely loaded components, so in a sense they contain their own logic.

The changes to the UX are below. Likely that are not very well done, I am new to JS. Basically I am just creating an array of components being loaded, tracking their load state, and once there all loaded calling the onLoadComplete which was returned.

What do you think? How can this be improved? Is this useful?




Ext.namespace('Ext.ux');

/**
* @class Ext.ux.ComponentLoader
* @author Aaron Conran
* Provides an easy way to load components dynamically. If you name these components
* you can use Ext.ComponentMgr's onAvailable function to manipulate the components
* as they are added.
* @singleton
*/
Ext.ux.ComponentLoader = function() {
var defaultXType = 'panel';
var cm = Ext.ComponentMgr;

return {

onloadMethod : '',
loadArray : [],

/**
* Load components from a server resource, config options include anything available in @link Ext.data.Connect#request
* Note: Always uses the connection of Ext.Ajax
*/
load : function(config) {
Ext.apply(config, {
callback: this.onLoad,
scope: this
});
Ext.Ajax.request(config);
},




// private
onLoad : function(opts, success, response) {
var config = Ext.decode(response.responseText);
// get the js to execute after all components are loaded
this.onloadMethod = config.onLoadComplete;

// create an array with a reference to the load state of each component
for (var i = 0; i < config.components.length; i++) {
this.loadArray.push({cmpId:config.components[i].id,loaded:false})

// add the onAvailable listener for each cmp being loaded
Ext.ComponentMgr.onAvailable(config.components[i].id, function(cmp) {
this.markLoaded(cmp.id);
},this);
};


// create each component
for (var i = 0; i < config.components.length; i++) {
var comp = cm.create(config.components[i], config.defaultXType[i] || defaultXType)
}



},


// mark each component as loaded (as it becomes available) and fire the onloadMethod once
// they are all loaded.
markLoaded: function(id){

// find the component in the loadarray and mark its load state to true
for (var i = 0; i < this.loadArray.length; i++) {
if (this.loadArray[i].cmpId == id){
this.loadArray[i].loaded = true;
}
}

// check that each component in the load array is loaded
// and execute the onloadMethod if they are
for (var i = 0; i < this.loadArray.length; i++) {
if (this.loadArray[i].loaded == false){
return false;
}
};

// if that loop was completed, then all components are loaded
this.onloadMethod();


}
,

};
}();

Lobos
26 Apr 2008, 3:00 PM
I have found more felxibilty in loading complete apps / modules / classes with ajax. It seems like there are too many limitiations in doing it this way as opposed to the having full flexibilty when you load and eval a object.

just my 2 cents

brookd
26 Apr 2008, 4:52 PM
Can you show me an example of how you do this please?

bhomass
24 Sep 2008, 8:13 PM
to Aaron

would you consider an enhancement where I can declare my own callback function during a call to ComponentLoader.load()?

jsakalos
2 Feb 2009, 2:12 PM
Could somebody who is using ComponentLoader post an example of a really complex component load please? Something like a grouping grid with remote store, with a toolbar (buttons with their handlers) and with some plugin(s)? I mean, both what is returned from server and client side hadling.

No theory please, only a working, used example.

mjlecomte
2 Feb 2009, 2:22 PM
I know of this demo:
http://extjs.com/deploy/dev/examples/remoteload/remoteload.php

Perhaps not complex enough for your taste though. You've seen the plugin version as well (something like "remoteComponent")?

jsakalos
2 Feb 2009, 2:45 PM
Hmmm, I had a different idea... The example contains 5 extended classes loaded standard way in script tags and ComponentLoader loads only viewport putting all these extensions together.

I was thinking about loading of a complete grid, not its xtype, including event handlers, plugins, store, etc. Because, if the user doesn't need the grid, no traces of it would be "pre-loaded" but the grid as a whole would be loaded from the server.

Anyway, thank you for the link, it shed some lite.

mjlecomte
2 Feb 2009, 2:57 PM
I think JScout, $JIT (Doug Hendricks), and qwikioffice.com showcase some approaches to that. That is there's no javascript for the class loaded whatsoever until "on demand".

yiadema
6 Feb 2009, 5:38 AM
Hmmm, I had a different idea... The example contains 5 extended classes loaded standard way in script tags and ComponentLoader loads only viewport putting all these extensions together.

I was thinking about loading of a complete grid, not its xtype, including event handlers, plugins, store, etc. Because, if the user doesn't need the grid, no traces of it would be "pre-loaded" but the grid as a whole would be loaded from the server.

Anyway, thank you for the link, it shed some lite.

Hi saki , (Sorry for my English) Could you please take a look to this implementation ? this works good for me

entry point

Ext.onReady(function(){

Ext.QuickTips.init();
Ext.form.Field.prototype.msgTarget = 'qtip';

var viewport = new Ext.Viewport({
layout: 'border',
items: [new Ext.TabPanel({
id: 'center-panel',
region:'center',
layoutOnTabChange : true,
enableTabScroll:true,
deferredRender: false,
tabPosition: 'top',
plugins: new Ext.ux.TabCloseMenu(),
border:false,
activeTab: 0,
minTabWidth :790,
items:[{
id:'tab-home-inicio',
title: 'Inicio',
iconCls: 'icon-home',
bodyStyle: "background:none",
nocache:true,
closable:true,
border:false,
hideMode: Ext.isIE ? 'offsets' : 'display'
}]
})
]
});

Ext.ux.ComponentLoader.load({
url:'default/index/inicio/',
params: {
testing: 'Testing params'
},
container:'tab-home-inicio'
});
});Grid and form loaded from the server with handlers,stores,etc... JSON format
default/index/inicio/


{
defaultXType: 'panel',
success: true,
components: [{
xtype: 'examplegrid',
id: 'administracion_rubro_rubro-grid',
collapsible: true,
title: 'Rubro',
ds: new Ext.data.JsonStore({
url: '/williams/web_root/administracion/rubro/filtro',
id: 'administracion_rubro_data-filter',
totalProperty: 'total',
root: 'data',
fields: [{
name: 'id_rubro'
},
{
name: 'descripcion'
}],
sortInfo: {
field: 'id_rubro',
direction: 'ASC'
},
remoteSort: true
}),
cm: new Ext.grid.ColumnModel({
defaultSortable: true,
columns: [{
dataIndex: 'id_rubro',
header: 'code'
},
{
dataIndex: 'descripcion',
header: 'Description'
}]
}),
sm: new Ext.grid.RowSelectionModel({
singleSelect: true,
listeners: {
rowselect: function(sm, row, rec) {
Ext.getCmp("administracion_rubro_rubro-form").getForm().loadRecord(rec);
Ext.getCmp("administracion_rubro_rubro-form").getComponent("administracion_rubro_update-field").setValue(rec.get('id_rubro'));
Ext.getCmp("administracion_rubro_id_rubro").setDisabled(true);
}
}
}),
filters: new Ext.grid.GridFilters({
filters: [{
type: 'string',
dataIndex: 'id_rubro'
},
{
type: 'string',
dataIndex: 'descripcion'
}]
}),
enableColLock: false,
loadMask: true,
plugins: this.filters,
autoHeight: true,
viewConfig: {
forceFit: true
},
cls: 'margin-panel'
},
{
xtype: 'panel',
title: 'Rubro',
autoHeight: true,
cls: 'margin-panel',
items: [{
xtype: 'form',
id: 'administracion_rubro_rubro-form',
frame: true,
labelAlign: 'left',
autoHeight: true,
labelWidth: 110,
border: false,
bodyStyle: 'padding:10px 10px 0;',
items: [{
xtype: 'hidden',
id: 'administracion_rubro_update-field',
name: 'id'
},
{
xtype: 'textfield',
fieldLabel: 'Code',
id: 'administracion_rubro_id_rubro',
name: 'id_rubro',
width: 200,
vtype: 'alphanum',
maxLength: 6,
allowBlank: false
},
{
xtype: 'textfield',
fieldLabel: 'Description',
id: 'administracion_rubro_descripcion',
name: 'descripcion',
width: 580,
maxLength: 40,
allowBlank: false
}],
buttons: [{
text: 'Guardar',
handler: function() {
if (Ext.getCmp('administracion_rubro_rubro-form').getForm().isValid()) {
Ext.MessageBox.confirm('Confirmar', 'Esta seguro de guardar?',
function(btn) {
if (btn == 'yes') {
Ext.getCmp('administracion_rubro_form-statusbar').showBusy('Guardando Datos ...');
Ext.getCmp('administracion_rubro_rubro-form').getForm().submit({
waitMsg: 'Guardando Datos ...',
waitTitle: 'Enviando Datos',
method: 'POST',
url: '/williams/web_root/administracion/rubro/agregar/',
success: function() {
Ext.getCmp('administracion_rubro_form-statusbar').setStatus({
text: 'Datos Guardados Correctamente',
iconCls: '',
clear: true
});
Ext.getCmp("administracion_rubro_id_rubro").setDisabled(false);
Ext.getCmp('administracion_rubro_rubro-grid').getStore().reload({
params: {
start: 0,
limit: 10
}
});
Ext.getCmp('administracion_rubro_rubro-form').getForm().reset();
},
failure: function(form, action) {
//show error
}
});
}
});
}
}
},
{
text: 'Nuevo',
handler: function() {
Ext.getCmp('administracion_rubro_rubro-form').getForm().reset();
Ext.getCmp("administracion_rubro_id_rubro").setDisabled(false);
}
}]
}],
bbar: new Ext.StatusBar({
id: 'administracion_rubro_form-statusbar',
plugins: new Ext.ux.ValidationStatus({
form: 'administracion_rubro_rubro-form'
})
})
}]
}And a extension for handling the grid filter plugin object pass to the grid config as filters

Ext.ns('Example');
Example.SimplePagingGrid = Ext.extend(Ext.grid.GridPanel, {
initComponent:function() {
Example.SimplePagingGrid.superclass.initComponent.apply(this, arguments);

Ext.apply(this, {
plugins:[this.filters],
bbar: new Ext.PagingToolbar({
store: this.store,
pageSize: 10,
displayInfo: true,
displayMsg: 'Mostrando ({0}-{1}) Total ({2})',
emptyMsg: "No topics to display",
plugins: this.filters
})
});

Example.SimplePagingGrid.superclass.initComponent.apply(this, arguments);
}
,onRender:function() {

// call parent
Example.SimplePagingGrid.superclass.onRender.apply(this, arguments);
// load the store
this.store.load({params:{start:0, limit:10}});

}

});
// register component
Ext.reg('examplegrid', Example.SimplePagingGrid);

jsakalos
6 Feb 2009, 6:19 AM
I was evaluating a possibility to use a component loading recently but I've dropped the idea - I've posted about it somewhere.

yiadema
6 Feb 2009, 7:11 AM
I was evaluating a possibility to use a component loading recently but I've dropped the idea - I've posted about it somewhere.
thanks for the reply , I've found your post here http://extjs.com/forum/showthread.php?p=282467#post282467 . I will evaluate your approach and thanks for share your knowledge :)