PDA

View Full Version : [CLOSED] Radio Group does not work if bound data is boolean



nflawson
Jul 04, 2014, 3:03 AM
Hi, I have a simple control with three radio button groups and a text box (see below). I have a data source that has four fields one of which is called 'notBool' and it contains a true/false string and another is called isBool and it contains, yes you guessed it, true/false as boolean values. The other two fields are an integer which is linked to an isNumber radio group and a text field which is just there to check its working.

I have set the Dataindex of each radio button to its respective field name and I am loading data through a form.panel using the loadRecord() function.

The 'isNumber' radio group works correctly. I can set the radio button's InputValue to any number and the buttons follow the isNumber field value.

The 'notBool' radio group's buttons have an InputValue of 'true' or 'false' and these are linked to the 'notBool' string field and they work perfectly.

The problem occurs with the 'isBool' radio group which also has it's radio buttons InputValue set to 'true' or 'false' and linked to the 'isBool' boolean field and they do not work.

I have tried different capitalizations like 'True'/'False' and 'TRUE'/'FALSE' and even 1/0 and I am yet to find a combination that works. I have tried using 'setValue()' on the Radio group rather than through the form and this doesn't work.

So my obvious question is 'what am I doing wrong?' and my alternative question is 'what is wrong with Ext.net 2 that this is failing?' When I look at the generated javascript and compare it with that on the Sencha site for the radio group example it looks fine apart from the addition of the DataIndex field.


<%@ Control Language="VB" ClassName="Test" CodeFile="Test.ascx.vb" Inherits="Test" ClientIDMode="Static" %>
<%@ Register Assembly="Ext.Net" Namespace="Ext.Net" TagPrefix="ext" %>
<ext:ResourceManager runat="server" Theme="Gray" RenderScripts="Embedded" ScriptMode="Debug" SourceFormatting="true" />
<ext:Store runat="server" ID="TestStore" AutoDataBind="true" AutoLoad="true" AutoSync="true">
<Model>
<ext:Model runat="server">
<Fields>
<ext:ModelField Name="notBool" />
<ext:ModelField Name="isBool" Type="Boolean" />
<ext:ModelField Name="TextVal" />
<ext:ModelField Name="isNumber" Type="Int" />
</Fields>
</ext:Model>
</Model>
<Listeners>
<Load Handler="#{frmTest}.getForm().loadRecord(records[0]);
#{isBool}.setValue(records[0].data);" />
</Listeners>
</ext:Store>
<ext:FormPanel runat="server" ID="frmTest" Frame="true" Width="600" Height="400">
<Items>
<ext:RadioGroup runat="server" ID="notBool" GroupName="notBool" FieldLabel="Not Boolean" ColumnsNumber="2">
<Items>
<ext:Radio runat="server" DataIndex="notBool" InputValue="true" BoxLabel="Yes" />
<ext:Radio runat="server" DataIndex="notBool" InputValue="false" BoxLabel="No" />
</Items>
</ext:RadioGroup>
<ext:RadioGroup runat="server" ID="isBool" GroupName="isBool" FieldLabel="Is Boolean" ColumnsNumber="2">
<Items>
<ext:Radio runat="server" BoxLabel="Yes" DataIndex="isBool" InputValue="true" />
<ext:Radio runat="server" BoxLabel="No" DataIndex="isBool" InputValue="false" />
</Items>
</ext:RadioGroup>
<ext:RadioGroup runat="server" ID="isNumber" GroupName="isNumber" FieldLabel="Is Number" ColumnsNumber="2">
<Items>
<ext:Radio runat="server" DataIndex="isNumber" InputValue="1" BoxLabel="Yes" />
<ext:Radio runat="server" DataIndex="isNumber" InputValue="2" BoxLabel="No" />
</Items>
</ext:RadioGroup>
<ext:TextField runat="server" ID="TextVal" DataIndex="TextVal" FieldLabel="Text" />
</Items>
</ext:FormPanel>

<script runat="server">
Imports Microsoft.VisualBasic

Public Class Test
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
Dim BoolTest As New List(Of TestBool)
BoolTest.Add(New TestBool With {.notBool = "true", .isBool = True, .TextVal = "Yes it did", .isNumber = 1})

Me.TestStore.DataSource = BoolTest
Me.TestStore.DataBind()

End Sub
End Class

Class TestBool
Public Property notBool As String
Public Property isBool As Boolean
Public Property TextVal As String
Public Property isNumber As Integer
End Class
</script>

nflawson
Jul 04, 2014, 6:40 AM
An update...

If I use...


#{isBool}.setValue(records[0].data);


This calls...


setValue: function(value) {
var cbValue, first, formId, radios,
i, len, name;

if (Ext.isObject(value)) {
for (name in value) {
if (value.hasOwnProperty(name)) {
cbValue = value[name];
first = this.items.first();
formId = first ? first.getFormId() : null;
radios = Ext.form.RadioManager.getWithValue(name, cbValue, formId).items;
len = radios.length;

for (i = 0; i < len; ++i) {
radios[i].setValue(true);
}
}
}
}
return this;
}

Which calls 'getWithValue' (see below) and the code makes an equality comparison between the string inputValue and the boolean value and it fails.

I'm not a JavaScript expert but it seems that this comparison is ok when the two are string and integer, one or the other gets automatically cast and the comparison works.

When they are string and boolean for some reason there is no automatic cast and it fails.

Of course, this may have been browser specific, I am using IE11 so I tested it in Chrome 35 and the result was the same. Wheeeew!!! Something that is a problem in both IE and Chrome...

So I tried overriding the code below using value.toString() and it works. The corresponding radio button was set correctly. I can possibly live with this and I am not sure if this is an Ext.net problem or an Ext problem but it is a problem...


Ext.define('Ext.form.RadioManager', {
extend: Ext.util.MixedCollection,
singleton: true,

getByName: function (name, formId)
{
return this.filterBy(function (item)
{
return item.name == name && item.getFormId() == formId;
});
},

getWithValue: function (name, value, formId)
{
return this.filterBy(function (item)
{
return item.name == name && item.inputValue == value && item.getFormId() == formId;
});
},

getChecked: function (name, formId)
{
return this.findBy(function (item)
{
return item.name == name && item.checked && item.getFormId() == formId;
});
}
});

Daniil
Jul 04, 2014, 8:26 AM
Hi @nflawson,

It is all because of how JavaScript compares primitives.
http://es5.github.io/x11.html#x11.9.3

These both statements return false.

"true" == true
"false" == false

Though these ones returns true.

"1" == true
1 == true
"0" == false
0 == false


As far as you can see, true and false are considered as 1 and 0 in such comparisons.

So, if inputValue is "1" setting it with a boolean true should work. The same with "0" and false.

nflawson
Jul 07, 2014, 8:01 AM
Thank you,
Although I was sure I tried 0 and 1 they do work if you use radioGroup.setValue() and pass in the data object. But I am still having issues and I suspect that the 0/1 inputValue setting has been masked by a further issue which is that the radioGroup doesn't work if you use loadRecord(records[0]) or setValues(records[0].data) on a form.

As you know, loadRecord eventually calls setValues with the record.data object. This then iterates through the data object and passes the value to radioGroup.setValue(value) (see below) as a singleton and not an object and the net result is that the main body of the function is bypassed and the value isn't set. The call to setValue(value) is made inside the Ext.form.basic.override.setValues() function. Here is the original code of the radioGroup.setValue function...



setValue: function (value)
{
var cbValue, first, formId, radios,
i, len, name;

if (Ext.isObject(value))
{
for (name in value)
{
if (value.hasOwnProperty(name))
{
cbValue = value[name];
first = this.items.first();
formId = first ? first.getFormId() : null;
radios = Ext.form.RadioManager.getWithValue(name, cbValue, formId).items;
len = radios.length;

for (i = 0; i < len; ++i)
{
radios[i].setValue(true);
}
}
}
}
return this;
}


I'm not sure of the implications of what I'm suggesting, but there is a way around this and it involves turning the value back into an object (see below). I am assuming that the name of the radioGroup (through the GroupName property of the control) has been assigned to the name property of the child radioButton and so the object I create uses the name of the radioGroup to create an object with a field that can then be passed to the getWithValue function that will then find the correct radioButton to show as checked. Here is the code...



setValue: function (value)
{
var cbValue, first, formId, radios,
i, len, name;

if (!Ext.isObject(value))
{
var tempVal = value;
value = new Object();
value[this.name] = tempVal;
}

for (name in value)
{
if (value.hasOwnProperty(name))
{
cbValue = value[name];
first = this.items.first();
formId = first ? first.getFormId() : null;
radios = Ext.form.RadioManager.getWithValue(name, cbValue, formId).items;
len = radios.length;

for (i = 0; i < len; ++i)
{
radios[i].setValue(true);
}
}
}
return this;
}


It's easy to see why the original code required an object since it needs to find the radioButton that matches the name of the value object's properties and since other control types based on textField have only one control they are happy to be passed a singleton. The need to have a value object passed into setValue() is unique to the radioGroup and checkboxGroup. I have modified the RadioGroup's setValue function and I am looking at the CheckBox function which is "hairier".

You could argue that, as other types of control can accept an object, you could change the calling function so that it passes an object and then any other controls that need an object will be fed what they need. I have looked at this and there are a few places where setValue(singleton) is used so modifying the radioGroup (and CheckboxGroup) seem to be the better option.

For now, for me, I'm going to use what I have done and see if there are any other issues. I do have checkboxes on my form and I am now looking at their operation to be sure they work but I suspect I'll need to make the same sort of change.

Daniil
Jul 07, 2014, 11:12 AM
Could you, please, provide a test case to reproduce the problem? I would like to investigate the issue in details.

nflawson
Jul 10, 2014, 2:09 AM
Here is my original sample with the removal of the sertValue entry in the store load handler...


<%@ Control Language="VB" ClassName="Test" CodeFile="Test.ascx.vb" Inherits="Test" ClientIDMode="Static" %>
<%@ Register Assembly="Ext.Net" Namespace="Ext.Net" TagPrefix="ext" %>
<ext:ResourceManager runat="server" Theme="Gray" RenderScripts="Embedded" ScriptMode="Debug" SourceFormatting="true" />
<ext:Store runat="server" ID="TestStore" AutoDataBind="true" AutoLoad="true" AutoSync="true">
<Model>
<ext:Model runat="server">
<Fields>
<ext:ModelField Name="notBool" />
<ext:ModelField Name="isBool" Type="Boolean" />
<ext:ModelField Name="TextVal" />
<ext:ModelField Name="isNumber" Type="Int" />
</Fields>
</ext:Model>
</Model>
<Listeners>
<Load Handler="#{frmTest}.getForm().loadRecord(records[0]);" />

</Listeners>

</ext:Store>
<ext:FormPanel runat="server" ID="frmTest" Frame="true" Width="600" Height="400">
<Items>
<ext:RadioGroup runat="server" ID="notBool" GroupName="notBool" FieldLabel="Not Boolean" ColumnsNumber="2">
<Items>
<ext:Radio runat="server" DataIndex="notBool" InputValue="true" BoxLabel="Yes" />
<ext:Radio runat="server" DataIndex="notBool" InputValue="false" BoxLabel="No" />
</Items>
</ext:RadioGroup>
<ext:RadioGroup runat="server" ID="isBool" GroupName="isBool" FieldLabel="Is Boolean" ColumnsNumber="2">
<Items>
<ext:Radio runat="server" BoxLabel="Yes" DataIndex="isBool" InputValue="1" />

<ext:Radio runat="server" BoxLabel="No" DataIndex="isBool" InputValue="0" />

</Items>
</ext:RadioGroup>
<ext:RadioGroup runat="server" ID="isNumber" GroupName="isNumber" FieldLabel="Is Number" ColumnsNumber="2">

<Items>
<ext:Radio runat="server" DataIndex="isNumber" InputValue="1" BoxLabel="Yes" />
<ext:Radio runat="server" DataIndex="isNumber" InputValue="2" BoxLabel="No" />
</Items>
</ext:RadioGroup>
<ext:TextField runat="server" ID="TextVal" DataIndex="TextVal" FieldLabel="Text" />
</Items>
</ext:FormPanel>

<script runat="server">
Imports Microsoft.VisualBasic

Public Class Test
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
Dim BoolTest As New List(Of TestBool)
BoolTest.Add(New TestBool With {.notBool = "true", .isBool = True, .TextVal = "Yes it did", .isNumber = 1})

Me.TestStore.DataSource = BoolTest
Me.TestStore.DataBind()

End Sub
End Class

Class TestBool
Public Property notBool As String
Public Property isBool As Boolean
Public Property TextVal As String

Public Property isNumber As Integer

End Class
</script>




If you step through the call to loadRecord() in the store handler you'll get to the call to iterate the record.data and it is here that a singleton is passed to setValue on the radioGroup.

Daniil
Jul 10, 2014, 11:14 AM
Thank you for the test case.

In a common case you can put to a RadioGroup Radios with different Names. So, a RadioGroup expects such a value

{
name1: value1,
name2: value2
}

Though, I would say it is a rare case when a RadioGroup holds Radios with different names, so, in most cases a value for a RadioGroup is

{
name: value
}
which, I agree with you, could be simplified to just "value".

Well, your override of RadioGroup's setValue() is good. You could simplify it to:

Ext.form.RadioGroup.override({
setValue: function (value) {
if (!Ext.isObject(value)) {
var v = {};

v[this.name] = value;
arguments[0] = v;
}

return this.callParent(arguments);
}
});

nflawson
Jul 11, 2014, 5:38 AM
Thank you for your help. Actually if you look at Sencha's description of the radioGroup's setValue(value) method they describe value as...


"The map from names to values to be set".

So passing a singleton to the radioGroup is outside this spec. A radioGroup should only be passed a name/value pair object/array and not a single value.

Yes, it is an assumption on my part that the groupName property has been set and this has been passed to the radioButton children but without "fiddling" with the setValues() method there is no easy way of getting the necessary map. Something in the doco to this effect might also be useful to help others know what the requirements are to make this work.

Since me last message I have had an opportunity to look at the original issue again as I have a problem if inputValue is set to 1/0.

In my actual form, the fields are bound to an object created through the Entity Data toolset and bound to an SQL Server table. So the boolean columns are defined as nullable (of boolean) in the bound object and nullable(of boolean) does not recognise 1/0 as valid true/false values. The result is that I get problems when I try to save the data.

The form I am dealing with is a monster (and a great use case for Ext.NET) as it has around 90 fields which get displayed and validated differently depending on the choices made along the way. There are around 30 radioGroups many of which are bound to boolean columns and it just isn't practical to do object.column1 = field1.text, object.column2 = field2.text etc. I need a way that is self maintaining and simple to populate the database columns. Making my form fields use an ID that matches their dataIndex, I can use a post back of the form data that is an object that matches the table definition. I do validation and type checking in javascript and I can then save the raw data from the form straight into the database.

This all worked in Ext.NET 1.## where a radioGroup allowed its radioButtons to be assigned inputValues of true/false when bound to a boolean column. I have had to go back to my original suggestion and change the getWithValue method to use toString() to convert the database column data into comparable values...


Ext.define('Ext.form.RadioManager', {
extend: Ext.util.MixedCollection,
singleton: true,

getByName: function (name, formId)
{
return this.filterBy(function (item)
{
return item.name == name && item.getFormId() == formId;
});
},

getWithValue: function (name, value, formId)
{
return this.filterBy(function (item)
{
if (value != null && value != undefined) {
return item.name == name && item.inputValue == value.toString() && item.getFormId() == formId;
} else {
return
}
});
},

getChecked: function (name, formId)
{
return this.findBy(function (item)
{
return item.name == name && item.checked && item.getFormId() == formId;
});
}
});



There was a small problem with null values so I had to add the check for null/undefined. Using this change to the code I can continue to use true/false for the boolean bound radioGroups and the data is automatically read from and posted to my table. Obviously, the toString() call is redundant for string and integer values and you would need to be careful with float values not converting accurately. One way around this would be to do the toString() only on the boolean values but since my useage has only integer, string and boolean values this change works for me and the monster lives again...

Daniil
Jul 11, 2014, 5:16 PM
Thank you for the detailed explanation. I can confirm all your findings.

Yes, there is definitely some inconsistency in ExtJS with setting and getting values. If you set something by a .setValue() call, it doesn't mean that you'll the same back by a getValue() call. This doesn't work well in all the cases.

field.setValue(field.getValue());

It is very-very frustrating. Though, I don't think we can do much with that at this point. Many people already deal with the existing situation and worked around if needed. If we change something, it is a good chance that something else might be broken.

Hopefully, it will get better with ExtJS 5.

As for your override, I think it is good.