views:

506

answers:

1

I'm experimenting with using ext_scaffold to generate UI elements for a Rails-backed web application.

However, I'm experiencing issues with the behavior of boolean fields. For instance, when I do

./script/generate ext_scaffold product name:string description:text sku:integer available:boolean

It builds the migration properly and generates a user interface with a checkbox for the boolean field.

Checking this box, however, only modifies the object if it's being created. If the record already exists, the state of the checkbox accurately reflects the field. However, editing it is failing -- i.e., checking or unchecking it and saving the record doesn't change it.

Dipping into the code, we find:

 items: [
    { fieldLabel: scaffoldPanel.labels['product[name]'], name: 'product[name]', xtype: 'textfield' },
    // ... other fields ...
    { fieldLabel: scaffoldPanel.labels['product[available]'], name: 'product[available]', xtype: 'checkbox', inputValue: '1' }, { xtype: 'hidden', name: 'product[available]', value: '0' }
    ],

I sense the problem is that either Rails or Ext is confused at the identically-named elements. Either way, after a record has been created, clicking the check box does nothing (checked or unchecked, the field remains '0'.)

After playing around a bit with this, it turns out if we place that hidden field ABOVE the checkbox, no matter what the field now gets set to true after editing. Not exactly desired behavior either.

Has anyone else experienced this issue? Are there any workarounds? Thanks.

UPDATE:

Note that even with the checkbox for the field checked, the POST request sent indicates the field is set to false. All other field types are getting updated appropriately...

UPDATE2:

Alright, found a great blog post describing Semergence's solution to this problem, but I'm still not quite able to get this to work... I've adapted his solution as follows:

   onOk: function() { 

      // ....

      if (scaffoldPanel.getFormPanel().currentMode == 'edit') {
        // set up request for Rails create action
        submitOptions.params._method = 'PUT';
        submitOptions.url = submitOptions.url + '/' + selected.data.id;
      }

  // ----- checkbox serializer ------
     var serializedForm = scaffoldPanel.getFormPanel().getForm();

 // because unchecked checkboxes do not submit values, manually force a value of 0  
     serializedForm.items.each(function(f) {  
  //alert("Checking \"" + f.getName() "\" field...");
        if (f.isFormField && f.getXType() == 'checkbox' && !f.getValue()) {  
   alert("Auto setting \"" + f.getName() + "\" field to 0...");
            serializedForm.getValues()[f.getName()] = '0';  
        }
      });
// ------- end serializer -------

     serializedForm.submit(submitOptions);
     //scaffoldPanel.getFormPanel().getForm().submit(submitOptions);  
   },

Now the alert gets thrown properly, indicating the correct field and asserting it will be set to zero.

Except this doesn't occur down in the post request which actually gets sent -- in fact, if the checkbox is unchecked, it doesn't have a value in the POST at all.

Can someone help me understand what's going on here?

+1  A: 

OK, turns out this is a known bug with the Ext framework. Wander over to the Extjs forums if you're having the same problem, several solutions outlined there. The simplest to implement is just to extend the checkbox class:

# patch.js -- include before creating any checkboxes
Ext.ns('Ext.ux.form');
Ext.ux.form.XCheckbox = Ext.extend(Ext.form.Checkbox, {
 offCls:'xcheckbox-off'
,onCls:'xcheckbox-on'
,disabledClass:'xcheckbox-disabled'
,submitOffValue:'false'
,submitOnValue:'true'
,checked:false

,onRender:function(ct) {
    // call parent
    Ext.ux.form.XCheckbox.superclass.onRender.apply(this, arguments);

    // save tabIndex remove & re-create this.el
    var tabIndex = this.el.dom.tabIndex;
    var id = this.el.dom.id;
    this.el.remove();
    this.el = ct.createChild({tag:'input', type:'hidden', name:this.name, id:id});

    // update value of hidden field
    this.updateHidden();

    // adjust wrap class and create link with bg image to click on
    this.wrap.replaceClass('x-form-check-wrap', 'xcheckbox-wrap');
    this.cbEl = this.wrap.createChild({tag:'a', href:'#', cls:this.checked ? this.onCls : this.offCls});

    // reposition boxLabel if any
    var boxLabel = this.wrap.down('label');
    if(boxLabel) {
        this.wrap.appendChild(boxLabel);
    }

    // support tooltip
    if(this.tooltip) {
        this.cbEl.set({qtip:this.tooltip});
    }

    // install event handlers
    this.wrap.on({click:{scope:this, fn:this.onClick, delegate:'a'}});
    this.wrap.on({keyup:{scope:this, fn:this.onClick, delegate:'a'}});

    // restore tabIndex
    this.cbEl.dom.tabIndex = tabIndex;
} // eo function onRender

,onClick:function(e) {
    if(this.disabled || this.readOnly) {
        return;
    }
    if(!e.isNavKeyPress()) {
        this.setValue(!this.checked);
    }
} // eo function onClick

,onDisable:function() {
    this.cbEl.addClass(this.disabledClass);
    this.el.dom.disabled = true;
} // eo function onDisable

,onEnable:function() {
    this.cbEl.removeClass(this.disabledClass);
    this.el.dom.disabled = false;
} // eo function onEnable

,setValue:function(val) {
    if('string' == typeof val) {
        this.checked = val === this.submitOnValue;
    }
    else {
        this.checked = !(!val);
    }

    if(this.rendered && this.cbEl) {
        this.updateHidden();
        this.cbEl.removeClass([this.offCls, this.onCls]);
        this.cbEl.addClass(this.checked ? this.onCls : this.offCls);
    }
    this.fireEvent('check', this, this.checked);

} // eo function setValue

,updateHidden:function() {
    this.el.dom.value = this.checked ? this.submitOnValue : this.submitOffValue;
} // eo function updateHidden

,getValue:function() {
    return this.checked;
} // eo function getValue

}); // eo extend

// register xtype
Ext.reg('xcheckbox', Ext.ux.form.XCheckbox);

 // eo file

You'll also need some css for the new checkboxes:

.xcheckbox-wrap {
 line-height: 18px;
 padding-top:2px;
}
.xcheckbox-wrap a {
 display:block;
 width:16px;
 height:16px;
 float:left;
}
.x-toolbar .xcheckbox-wrap {
 padding: 0 0 2px 0;
}
.xcheckbox-on {
 background:transparent url(../javascripts/ext/resources/images/default/menu/checked.gif) no-repeat 0 0;
}
.xcheckbox-off {
 background:transparent url(../javascripts/ext/resources/images/default/menu/unchecked.gif) no-repeat 0 0;
}
.xcheckbox-disabled {
 opacity: 0.5;
 -moz-opacity: 0.5;
 filter: alpha(opacity=50);
 cursor:default;
}

Finally, you may want to fix ext-scaffold to generate these new xcheckboxes for booleans and also not to generate the hidden field. I modified ext_scaffold_panel.js as follows:

    baseParams: scaffoldPanel.baseParams,
    items: [
<%= attributes.inject([]) do |items, a|
 item =  "        { fieldLabel: scaffoldPanel.labels['#{class_name.demodulize.underscore}[#{a.name}]']"
 item << ", name: '#{class_name.demodulize.underscore}[#{a.name}]'"
 item << case a.field_type
   when :text_field      then [:integer, :float, :decimal].include?(a.type) ? ", xtype: 'numberfield'" : ", xtype: 'textfield'"
   when :text_area       then ", xtype: 'textarea'"
   when :date_select     then ", xtype: 'xdatefield'"
   when :datetime_select then ", xtype: 'xdatetime'"
   when :check_box       then ", xtype: 'xcheckbox', inputValue: '1' //// }, { xtype: 'hidden', name: '#{class_name.demodulize.underscore}[#{a.name}]', value: '0'"
 end
 item << " }"
 items << item
end.join(",\n")
%>
    ],

Hope this helps anybody else struggling with this!

Joe