Sunday, October 23, 2011

Sencha Touch: Ext.Picker.Slot and the disappearing template

Ext.Picker is a useful component indeed. But recently, one aspect of an associated class's implementation made me wonder exactly why it was done in such a way. Specifically, the class implementation mounted an effective defense against using custom templates as the slot rendering mechanism, so I couldn't include images in the slot at all - which is exactly what I needed to do. Note that the behaviour inherited from DataView implies you can just supply a 'tpl' in configuration - but that just is not true.

(Note that the names of types and so on used in this post have been modified from the original implementation).

The class of interest is Ext.Picker.Slot, a private class and the default one for Ext.Picker 'slots'. Inside initComponent(), this assignment was the issue - forcing a standard template on you whether you like it or not:

 this.tpl = new Ext.XTemplate([  
       '<tpl for=".">',  
         '<div class="x-picker-item {cls} <tpl if="extra">x-picker-invalid</tpl>">{'   
          + this.displayField + '}</div>',  
          '</tpl>'  
     ]);  

There are a few ways of forcing the template you want in, but I chose to extend Ext.Picker.Slot, invoke the super type constructor, and then apply the template. As Sencha stress, Ext.Picker.Slot is a private class, so this can be regarded as a temporary measure, and not guaranteed to work with ST 2.0.

 m.ui.CustomSlot = Ext.extend(Ext.Picker.Slot, {  
   constructor: function(config) {  
     m.ui.CustomSlot.superclass.constructor.apply(this, arguments);  
     if (!Ext.isEmpty(config.template))   
        this.tpl = config.template;  
    }  
 });  
 Ext.reg('customslot', m.ui.CustomSlot);  

As part of the definition process, the type is 'xtype' registered with Sencha. Below is an example of the template I need to apply:

 m.ui.pickerSourceObjectTpl = new Ext.XTemplate(  
 '<tpl for=".">',  
 '<div class="x-picker-item {cls} <tpl if="extra">x-picker-invalid</tpl>">',  
 '<div class="m-item-image">{[this.getImageElement(values)]}</div>',  
 '<div class="m-item-info">',  
 '<p class="m-name header">{[this.getDisplayName(values)]}</p>',  
 '</div>',   
 '</div>',   
 '</tpl>',  
 {  
   getImageElement: function(obj) {  
    return m.ui.renderingSupport.getImageHTMLElement(obj);  
   },  
   getDisplayName: function(obj) {  
    return !Ext.isEmpty(obj.CustomisedName) ?   
        obj.CustomisedName : obj.Name;  
   }  
 });  

As I have some very specific behaviour associated with the picker implementation, I extended Ext.Picker. Note that there is a custom css class associated with the picker (class 'm-picker') - this is important for the actual operation of the picker in 'proper template' mode, as we need to set the attributes of the picker bar properly, or it looks rather strange.

1:  m.sencha.views.CustomPicker = Ext.extend(Ext.Picker, {  
2:    cls: 'm-picker',  
3:    defaultType: 'customslot',  
4:    constructor: function(config) {  
5:      m.sencha.views.CustomPicker.superclass.constructor.apply(this, arguments);  
6:      this.currentSource = '';  
7:      this.currentTarget = '';  
8:    },  
9:    filter: function() {  
10:      m.sencha.stores.sources.filterBy(function(rec, id) {  
11:        return m.domain.currentSession.isValidSource(rec.get('Number'));  
12:      });  
13:      this.slots[0].setSelectedNode(0);  
14:    },  
15:    listeners: {  
16:      pick: function(picker, obj, slot) {  
17:        this.currentSource = this.dispatchChangeToController({  
18:           action: 'sourceChanged',  
19:           selection: obj.Source,  
20:           cached: this.currentSource  
21:        });  
22:        this.currentTarget = this.dispatchChangeToController({  
23:           action: 'targetChanged',  
24:           selection: obj.Target,  
25:           cached: this.currentTarget  
26:        });  
27:      }  
28:    },  
29:    dispatchChangeToController: function(options) {  
30:     if (options.cached != options.selection && !Ext.isEmpty(options.selection)) {  
31:          Ext.dispatch({  
32:            controller: m.sencha.controllers.activeDispositionController,  
33:            action: options.action,  
34:            number: options.selection,  
35:            context: !Ext.isDefined(this.context) ? 'self' : this.context  
36:          });  
37:       }    
38:       return options.selection;  
39:    }  
40:  });  

Part of the SASS defintion for the custom css class is shown below - note that I am in no way a SASS or css expert, and I can claim no responsibility for what is excerpted below:

 .x-sheet.m-picker{  
      top:0 !important;  
      height:$picker-row-height*2 !important;  
      .x-picker-mask{  
          .x-picker-bar{  
              background:none;  
              border-top:rgba(0,0,0,0.2) 1px solid;  
              border-bottom:rgba(0,0,0,0.2) 1px solid;  
              @include box-shadow(rgba(0,0,0,0.1) 0 0 4px 0);  
          }  
      }  

Finally, we create slots to place in the picker sub type, at another place in code. Here, we just use the registered xtype, supply some base slot properties, and use the custom template property to specify our desired template.

1:  var slots = [{  
2:        xtype: 'customslot',  
3:        name: 'Source',  
4:        store: m.sencha.stores.sources,  
5:        valueField: 'Number',  
6:        template: m.ui.pickerSourceObjectTpl,  
7:        displayField: 'Name'  
8:      },  
9:      {  
10:        xtype: 'customslot',  
11:        name: 'Target',  
12:        store: m.sencha.stores.targets,  
13:        valueField: 'Number',  
14:        template: m.ui.pickerTargetObjectTpl,  
15:        displayField: 'Name'  
16:      }];  

No comments: