1. #1
    Sencha User
    Join Date
    May 2011
    Location
    Toronto
    Posts
    13
    Vote Rating
    -2
    SenchaGuru has a little shameless behaviour in the past

      -1  

    Lightbulb Extjs 4 Dynamic Model

    Extjs 4 Dynamic Model


    Hi all

    I am building an application where the user can generate tables on the fly. The description of these tables are stored at the back-end.

    I needed to generate models from these descriptions and be able to use data grid objects to manipulate the data (crud operations).

    I have looked in this forum for a solution to this as I had realized that the current ExtJS 4 does not support adding fields dynamically to a model or a store, .. but I had no luck finding something that works for what I needed (in Extjs4).

    Anyways, I came up with a solution using 'eval' that does exactly what I wanted , .. and I wanted to share it with you.

    Here it is,..

    Code:
    Ext.Loader.setConfig({
        enabled: true
    });
    Ext.Loader.setPath('Ext.ux', 'http://dev.sencha.com/deploy/ext-4.0.1/examples/ux');
    Ext.require([
        'Ext.form.*',
        'Ext.data.*',
        'Ext.grid.*',
        'Ext.ux.grid.FiltersFeature',
        'Ext.layout.container.Column'
        ]);
    
    // This data can be pulled off a back-end database
    // Used to generate a model and a data grid
    var records = [{
        data:{
            "dataIndex":"first",
            "name":"First Name",
            "type":"string"
        }
    },{
        data:{
            "dataIndex":"last",
            "name":"Last Name",
            "type":"String"
        }
    },{
        data:{
            "dataIndex":"email",
            "name":"Email",
            "type":"string"
        }
    }];
    
    // Lookup table (type => xtype)
    var type_lookup = new Object;
    type_lookup['int'] = 'numberfield';
    type_lookup['float'] = 'numberfield';
    type_lookup['string'] = 'textfield';
    type_lookup['date'] = 'datefield';
    type_lookup['boolean'] = 'checkbox';
    
    // Skeleton store
    var store_template = {
        autoLoad: true,
        autoSync: true,
        remoteFilter: false,
        
        // DATA is inserted here for the example to work on local drive (use proxy below)
        data:[{id:1,first:"Fred",last:"Flintstone",email:"fred@flintstone.com"},
              {id:2,first:"Wilma",last:"Flintstone",email:"wilma@flintstone.com"},
              {id:3,first:"Pebbles",last:"Flintstone",email:"pebbles@flintstone.com"},
              {id:4,first:"Barney",last:"Rubble",email:"barney@rubble.com"},
              {id:5,first:"Betty",last:"Rubble",email:"betty@rubble.com"},
              {id:6,first:"BamBam",last:"Rubble",email:"bambam@rubble.com"}],
        proxy: {
            type: 'rest',
            url: 'http://dev.sencha.com/deploy/ext-4.0.1/examples/restful/app.php/users',
            reader: {
                type: 'json',
                root: 'data'
            },
            writer: {
                type: 'json'
            }
        }
    };
    
    // Skeleton grid (_PLUGINS_ & _STORE_, are placeholders)
    var grid_template = {
        columnWidth: 1,
        plugins: '_PLUGINS_',
        height: 300,
        store: '_STORE_'
    }
    
    // Skeleton window (_ITEMS_ is a placeholder)
    var window_template = {
        title: 'Dynamic Model / Window',
        height: 400,
        width: 750,
        layout: 'fit',
        items: '_ITEMS_'
    }
    
    // Generate a model dynamically, provide fields
    function modelFactory(name,fields){
        model =  {
            extend: 'Ext.data.Model',
            fields: fields
        };
        eval("Ext.define('"+name+"',"+Ext.encode(model)+");");
    }
    
    // Generate a dynamic store
    function storeFactory(name,template,model){
        template.model = model;
        eval(name+" = Ext.create('Ext.data.Store',"+Ext.encode(template)+");");
    }
    
    // Generate a dynamic grid, .. store name is appended as a string because otherwise, Ext.encode
    // will cause 'too much recursion' error (same for plugins)
    function gridFactory(name,template,store,plugins){
        script =  name+" = Ext.create('Ext.grid.Panel', "+Ext.encode(template)+");";
        script = script.replace("\"_STORE_\"", store);
        script = script.replace("\"_PLUGINS_\"", plugins);
        eval(script);
    }
    // Generate a dynamic window, .. items are appended as a string to avoid Ext.encode error
    function windowFactory(winName,winTemp,items){
        script += winName+" = Ext.create('Ext.window.Window',"+Ext.encode(winTemp)+").show();";
        script = script.replace("\"_ITEMS_\"", items);
        eval(script);
    }
    
    // Generate a model, a store a grid and a window dynamically from a record list!
    function generateDynamicModel(records){
        
        fields = [{
            name: 'id',
            type: 'int',
            useNull:true
        }];
    
        columns = [{
            text: 'ID',
            sortable: true,
            dataIndex: 'id'
        }];
    
        for (var i = 0; i < records.length; i++) {
    
            fields[i+1] =  {
                name: records[i].data.dataIndex,
                type: records[i].data.type
            };
    
            columns[i+1] = {
                text: records[i].data.name,
                sortable: true,
                dataIndex: records[i].data.dataIndex,
                field:  {
                    xtype: type_lookup[records[i].data.type]
                }
            };
        }
    
        grid_template.columns = columns;
    
        modelFactory('myModel',fields);
        storeFactory('myStore',store_template,'myModel');
        gridFactory('myGrid',grid_template,'myStore','[rowEditing]');
        windowFactory('myWindow',window_template,'[myGrid]');
    
        // Direct access to the store created above 
        myStore.load();
    }
    
    Ext.onReady(function(){
        rowEditing = Ext.create('Ext.grid.plugin.RowEditing');
        generateDynamicModel(records);
    });
    I never used 'eval' to such an extend, but it works surprisingly well,..

    You can also download the working example (attached).

    Cheers

    Adnan
    Attached Files

  2. #2
    Sencha User
    Join Date
    Jul 2008
    Posts
    53
    Vote Rating
    0
    jash is on a distinguished road

      0  

    Default


    Your example is not really what I mean, let me clarify

    FYI. I use python with sqlalchemy on my back-end, no framework all scratch build . I Used mod_python in the past and are now using mod_wsgi with a self written mod_python emulator, so I can run mod_python code on top of mod_wsgi. It is a huge project I am working on in my spare time for eight years now and I "only" need to build the front-end with Extjs en Sencha Touch.

    In short it is an CRM/CMS/Webshop/Social media/web-desktop with almost everything you can think of integrated, from twitter to calendaring, syncing with phones, tablets. redefined email. It is even possible to write "apps" from within the web-desktop, so it is HUGE technically and hopefully somewhat "huge" financially in the near future. Everything is based on open-source, the server runs on mac, linux and windows (if you really want that), cloud or local.

    In SQLAlchemy (object relational mapper/database toolkit for python) (http://www.sqlalchemy.org)
    it is possible to make polymorphic tables:

    consider a table:

    Addresses
    which has an ID (autonumber, primary key)
    and a discriminator field

    Email_Addresses
    which has an ID (foreign key to Addresses.ID, primary key)
    Value

    Some Other_Addresses
    which has an ID (foreign key to Addresses.ID, primary key)
    Value

    Now in sqlalchemy you can set up the Objects (wrapping the database tables) with Polymorphic.
    If I create an Email_Address a record in Addresses is created with discriminator set to "Email_Addresses" and the same for "Other_Addresses"

    Addresses:

    ID Discriminator
    1 Email_Addresses
    2 Other_Addresses
    3 Email_addresses
    4 etc.

    Email_Addresses:
    ID Value
    1 "somebody@somedomain.com"
    3 "someotherbody@someotherdomain.com" ( )

    Other_Addresses

    ID Value
    2 "somevalue"

    Now I can query addresses (to get all):

    I will get the following result:

    ID discriminator Value
    1 Email_Addresses "somebody@somedomain.com"
    2 Other_addresses "somevalue"
    3 Email_Addresses "someotherbody@someotherdomain.com"

    SQLAlchemy will return in fact 3 objects [Email_addresses, Other_Addresses, Email_addresses]

    But I can query Email_addresses (to get all) and I wil get:
    ID discriminator Value
    1 Email_Addresses "somebody@somedomain.com"
    3 Email_Addresses "someotherbody@someotherdomain.com"

    SQLAlchemy will return in fact 2 objects [Email_addresses, Email_addresses]

    and Other_Addresses (to get all)

    ID discriminator Value
    2 Other_addresses "somevalue"

    SQLAlchemy will return in fact 1 object [Other_Addresses]

    For my case this is very use full and it is way more complex (I simplified)

    I made all the objects in such a way that they can output Ext.Data.model defines
    Works fine for Email_addresses and Other Addresses. but NOT for Addresses since the results on querying that returns [Mixed Models]

    I can however make an Addresses model and I will get the "Object Type" from the discriminator
    But then I have to do something like this:

    store_addresses - >model addresses
    store_email
    store_other

    store_addresses.load
    in store_addresses_load event:
    store.each(address):
    {
    if (address.discriminator == "Email_addresses")
    {
    create and load Email_addresses where ID = addres.id
    add record to store
    }
    and then for all the other address types the same
    }

    stores could be set up with HasMany and BelongsTo.

    also I could let address "become" an array [Mixed addresses]

    but that will break store.sync on addresses.....

    I really struggled to get this all working within an application setup, models, stores and direct but I have got it working now. except for reflecting the polymorphic stuff.

    The Idea is that I can just add another address type in my back-end and that models and views are generated by the backend software. so for a contact forms are loaded for all addresses related to that contact no matter how many email addresses. Grids are not really an option in my case. but that is a different story.

    Jash

  3. #3
    Sencha User
    Join Date
    Aug 2009
    Posts
    5
    Vote Rating
    0
    torres10 is on a distinguished road

      0  

    Default


    Great code!

    This is exactly what I'm writing.

    Thanks for sharing.

  4. #4
    Sencha User
    Join Date
    May 2011
    Location
    Toronto
    Posts
    13
    Vote Rating
    -2
    SenchaGuru has a little shameless behaviour in the past

      -1  

    Default


    Quote Originally Posted by jash View Post
    Your example is not really what I mean, let me clarify
    I made all the objects in such a way that they can output Ext.Data.model defines
    Works fine for Email_addresses and Other Addresses. but NOT for Addresses since the results on querying that returns [Mixed Models]
    If I understood this correctly, .. you can still use the code above to solve your problem (I think). All you need to do is to generate a single model definition of the mixed models returned (by merging their fields) on the server-side, .. something like this maybe,..

    Code:
    [{
            "dataIndex":"field_name1_model1",
            "name":"field_title1",
            "type":"string"
    },{
            "dataIndex":"field_name2_model2",
            "name":"field_title2",
            "type":"int"
    }, etc ]
    From this, you can generate a Sencha model and data store which you can use to interact with the server (as above, .. skip the Grid and Window if you wish to) ,.. Obviously, for any data manipulation, your server-side must support this new mixed model (somehow).

    Hope that helps.

  5. #5
    Sencha User
    Join Date
    Jun 2011
    Posts
    19
    Vote Rating
    0
    tejas@reach1to1.com is on a distinguished road

      0  

    Default Great Work

    Great Work


    Hey...was also trying for the same...but found difficult to crack....Thanks

  6. #6
    Sencha - Community Support Team edspencer's Avatar
    Join Date
    Jan 2009
    Location
    Palo Alto, California
    Posts
    1,939
    Vote Rating
    9
    edspencer is a jewel in the rough edspencer is a jewel in the rough edspencer is a jewel in the rough

      2  

    Default


    @SenchaGuru that's way overcomplicated - you can easily define new classes at runtime based on any configuration... you can replace this:

    Code:
    model =  {
        extend: 'Ext.data.Model',
        fields: fields
    };
    eval("Ext.define('"+name+"',"+Ext.encode(model)+");");
    With this:

    Code:
    var model = {
        extend: 'Ext.data.Model',
        fields: fields
    };
    
    Ext.define(name, model);
    No need for eval or anything like that (and also note the "var" statement in my replacement above - previously you'd been leaking a global variable there. In fact there's really no need to create that var in the first place, your function should just be:

    Code:
    // Generate a model dynamically, provide fields
    function modelFactory(name, fields) {
        return Ext.define(name, {
            extend: 'Ext.data.Model',
            fields: fields
        });
    }
    Ext JS Senior Software Architect
    Personal Blog: http://edspencer.net
    Twitter: http://twitter.com/edspencer
    Github: http://github.com/edspencer

  7. #7
    Sencha User
    Join Date
    May 2011
    Location
    Toronto
    Posts
    13
    Vote Rating
    -2
    SenchaGuru has a little shameless behaviour in the past

      0  

    Thumbs up


    Hi Edspencer,

    Thanks for your comment, .. I am new to ExtJS so your feedback is much appreciated

    My problem however was not creating the model dynamically, .. it was mainly to solve the problem I described in my post, which is simply:

    How can one generate a gird panel to view/edit a dynamic database table? ,.. that's, a table with columns added and removed dynamical at runtime? .. Can this be done without the use of eval, as posted here?

    Cheers

    Adnan

  8. #8
    Sencha - Community Support Team edspencer's Avatar
    Join Date
    Jan 2009
    Location
    Palo Alto, California
    Posts
    1,939
    Vote Rating
    9
    edspencer is a jewel in the rough edspencer is a jewel in the rough edspencer is a jewel in the rough

      1  

    Default


    Check out the reconfigure function at http://docs.sencha.com/ext-js/4-0/#/api/Ext.grid.Panel - this enables you to update your grid's fields and columns at run time
    Ext JS Senior Software Architect
    Personal Blog: http://edspencer.net
    Twitter: http://twitter.com/edspencer
    Github: http://github.com/edspencer

  9. #9
    Sencha User tobiu's Avatar
    Join Date
    May 2007
    Location
    Munich (Germany)
    Posts
    2,680
    Vote Rating
    112
    tobiu is a name known to all tobiu is a name known to all tobiu is a name known to all tobiu is a name known to all tobiu is a name known to all tobiu is a name known to all

      0  

    Default


    hi ed,

    any plans to change reconfigure back to the way it was in ext 3?

    i liked it a lot more to enter the store and the colModel (both ext objects) instead of the column array. this has the big problem, that you can not set the default config for the columns.


    best regards
    tobi
    Best regards
    Tobias Uhlig
    __________

    S-CIRCLES Social Network Engine

  10. #10
    Sencha - Community Support Team edspencer's Avatar
    Join Date
    Jan 2009
    Location
    Palo Alto, California
    Posts
    1,939
    Vote Rating
    9
    edspencer is a jewel in the rough edspencer is a jewel in the rough edspencer is a jewel in the rough

      0  

    Default


    @tobiu I'm not sure what you mean by not being able to set configs - can you elaborate?
    Ext JS Senior Software Architect
    Personal Blog: http://edspencer.net
    Twitter: http://twitter.com/edspencer
    Github: http://github.com/edspencer