We launched new forums in March 2019—join us there. In a hurry for help with your website? Get Help Now!
    • 8274 ☆ A M B ☆
    • 222 Posts
    First, I am rather green at ExtJS/ModExt.

    I have a CMP that will fire a Window for create (or Update) of an item. In this window, I want to put another Grid that populates with the contents of a single field in the record being updated/created.

    The field is a JSON string (it is essentially an image library that is associated with a product, so the image field in the db table can contain 1-x number of images).

    What is the best way to handle loading then processing this grid (For update product, it would need to parse the JSON string into the grid, on save, it needs to process the Grid array back to JSON and put it in the image field)?

    Jason
      • 4172
      • 5,888 Posts
      you could use MIGXdb to create your CMP and a migx-inputTVtype for this field.
        -------------------------------

        you can buy me a beer, if you like MIGX

        http://webcmsolutions.de/migx.html

        Thanks!
        • 8274 ☆ A M B ☆
        • 222 Posts
        That is an effective way, although one of the purposes of this is to learn how to implement something like this via ExtJS when MIGXdb is not an option (like for an existing CMP that was built outside of MIGXdb).

        So far I have this for my "grid" in a form call, although it is throwing a js error
        Cataloger.window.UpdateProduct = function(config) {
            config = config || {};
            Ext.applyIf(config,{
                title: _('cataloger.product_update')
                ,url: Cataloger.config.connectorUrl
                ,baseParams: {
                    action: 'mgr/product/update'
                }
                ,fields: [{
                    xtype: 'hidden'
                    ,name: 'id'
                },{
                    xtype: 'textfield'
                    ,fieldLabel: _('cataloger.name')
                    ,name: 'product_no'
                    ,anchor: '100%'
                },{
                    xtype: 'textfield'
                    ,fieldLabel: _('cataloger.name')
                    ,name: 'product_name'
                    ,anchor: '100%'
                },{
                    xtype: 'textarea'
                    ,fieldLabel: _('cataloger.description')
                    ,name: 'description'
                    ,id:'description'
                    ,anchor: '100%'
                }
                    /*Image grid*/
                    ,{
                        xtype: 'grid'
                        ,url: Cataloger.config.connectorUrl
                        ,id: 'cataloger.grid.imagelist'
                        ,baseParams: { action: 'mgr/product/getList' }
                        ,fieldLabel: _('cataloger.images')
                        ,name: 'images'
                        ,anchor: '100%'
                        ,fields: ['id','image']
                        ,columns:[{
                            header:_('id')
                            ,dataIndex: 'id'
                            ,sortable:true
                            ,width:60
                        },{
                            header: _('cataloger.image')
                            ,dataIndex: 'image'
                            ,sortable:false
                            ,width:150
                            ,editor:{xtype:'textfield'}
                        }]
        
                    }
        
                ]
            });
            Ext.reg('cataloger-grid-images',Cataloger.grid.Images);
            Cataloger.window.UpdateProduct.superclass.constructor.call(this,config);
            /*once window activates, add TinyMCE*/
            this.on('activate',function(w,e) {
                if (typeof Tiny != 'undefined') { MODx.loadRTE('description'); }
            },this);
        };
        Ext.extend(Cataloger.window.UpdateProduct,MODx.Window);
        Ext.reg('cataloger-window-product-update',Cataloger.window.UpdateProduct);


        Error is : Uncaught TypeError: Cannot set property 'xtype' of undefined.

        So I am missing something, just not sure what on this (still plugging away with the ExtJS documentation and forums as well
          • 33968
          • 863 Posts
          Try this. You need to define your grid outside the form configuration:
          (not tested)
          Cataloger.window.UpdateProduct = function(config) {
              config = config || {};
              Ext.applyIf(config,{
                  title: _('cataloger.product_update')
                  ,url: Cataloger.config.connectorUrl
                  ,baseParams: {
                      action: 'mgr/product/update'
                  }
                  ,fields: [{
                      xtype: 'hidden'
                      ,name: 'id'
                  },{
                      xtype: 'textfield'
                      ,fieldLabel: _('cataloger.name')
                      ,name: 'product_no'
                      ,anchor: '100%'
                  },{
                      xtype: 'textfield'
                      ,fieldLabel: _('cataloger.name')
                      ,name: 'product_name'
                      ,anchor: '100%'
                  },{
                      xtype: 'textarea'
                      ,fieldLabel: _('cataloger.description')
                      ,name: 'description'
                      ,id:'description'
                      ,anchor: '100%'
                  },{
                       xtype: 'cataloger-grid-images'
                       ,baseParams: { 
                           // send the product id to your processor,
                           // or you'll get all images :)
                           action: 'mgr/product/getList'
                           ,id: config.record.id 
                       }
                  }]
              });
              Cataloger.window.UpdateProduct.superclass.constructor.call(this,config);
              /*once window activates, add TinyMCE*/
              this.on('activate',function(w,e) {
                  if (typeof Tiny != 'undefined') { MODx.loadRTE('description'); }
              },this);
          };
          Ext.extend(Cataloger.window.UpdateProduct,MODx.Window);
          Ext.reg('cataloger-window-product-update',Cataloger.window.UpdateProduct);
          
          Cataloger.grid.Images = function(config) {
              config = config || {};
          
              Ext.applyIf(config,{
                  url: Cataloger.config.connectorUrl
                  ,baseParams: { action: 'mgr/product/getList' }
                  ,fieldLabel: _('cataloger.images')
                  ,anchor: '100%'
                  ,fields: ['id','image']
                  ,columns:[{
                      header:_('id')
                      ,dataIndex: 'id'
                      ,sortable:true
                      ,width:60
                  },{
                      header: _('cataloger.image')
                      ,dataIndex: 'image'
                      ,sortable:false
                      ,width:150
                      ,editor:{xtype:'textfield'}
                  }]
              });
              Cataloger.grid.Images.superclass.constructor.call(this,config);    
          };
          Ext.extend(Cataloger.grid.Images,MODx.grid.Grid);
          Ext.reg('cataloger-grid-images',Cataloger.grid.Images);
          
            • 8274 ☆ A M B ☆
            • 222 Posts
            This does work. Since the field being returned is a JSON string in the db, how and where do I parse that to an array so that the grid will put each item/index as a separate row in the table? Is that done in the getList.class, or will the ExtJS code be doing that work?
              • 33968
              • 863 Posts
              What does your getlist processor look like? Are the images stored in a separate table, or as a list in json in the products table?

              If the latter, you could look at using a local grid and get ExtJS to parse the json into a store for your grid. But it's not well documented. It might be easier to pass the product id to the getlist processor, and in that class you can retrieve the product again and return the image list formatted for display in the grid.

              Checkout the modObjectGetListProcessor class in core/model/modx/modprocessor.class.php to see the methods you'll need to override. Feel free to post your code back here if you get stuck.
                • 8274 ☆ A M B ☆
                • 222 Posts
                Currently my imagegetlist.class.php looks like this
                class ProductImageGetListProcessor extends modObjectGetListProcessor {
                    public $classKey = 'Product';
                    public $languageTopics = array('cataloger:default');
                    public $defaultSortField = 'product_no';
                    public $defaultSortDirection = 'ASC';
                    public $objectType = 'cataloger.product';
                    public function prepareQueryBeforeCount(xPDOQuery $c) {
                        $id = $this->getProperty('id');
                        if(!empty($id)){
                            $c->where(array(
                                'id'=> $id
                            ));
                        }
                        return $c;
                    }
                }
                return 'ProductImageGetListProcessor';
                

                Which is fine to return a single row in the grid that contains the record's id, and the "image" field value (JSON string). But it is not what I need for this UpdateWindow. I have been trying to figure out how to access just the "image" field in the record and use THAT value as the datastore for the imagesGrid.

                I guess I am just not fully grasping how the mod*Processor classes work.

                Jason
                  • 33968
                  • 863 Posts
                  Something like this should do it. You need to extend modObjectGetProcessor as you're only retrieving one object. Then override the process() class to extract the list of images and format it for display in the grid.
                  class ProductImageGetListProcessor extends modObjectGetProcessor {
                      public $classKey = 'Product';
                      public $languageTopics = array('cataloger:default');
                      public $objectType = 'cataloger.product';
                      
                      public function process() {
                          $list = array();
                          $count = 0;
                          $imageList = $this->object->get('images');
                          $imageArray = $this->modx->fromJSON($imageList);
                  
                          foreach ($imageArray as $img) {
                              // this part really depends on how $img is stored. Array?
                              $list[] = $img;
                              $count++;
                          }
                          return $this->outputArray($list,$count);
                      }
                  
                  }
                  return 'ProductImageGetListProcessor';
                  

                  The images might have been better stored in a separate table, then you could run getlist directly on that instead.
                    • 8274 ☆ A M B ☆
                    • 222 Posts
                    I was concerned with having too many small custom tables floating around... Still not sure if there is a performance concern when my CMP and front have to call to 3+ separate db tables to gather the info for a single product page.

                    I was getting close to what you had... i was trying to extend the wrong process, and I keep forgetting to call $this->object and $this->modx instead of $object and $modx. I

                    I'll give this a swing

                    Oh and the images are being stored as a JSON string.
                      • 14951
                      • 151 Posts
                      Lucas,

                      Thank you for sharing your wisdom here! Extremely helpful!

                      -Jared