Check all binding
hashitha opened this issue · 11 comments
I have the following form with a lot of checkboxes. Is there any way to bind one checkbox to all the others. The reason for this is to quickly tick all the checkboxes instead of going one by one.
<form>
<title>SECOND TRIMESTER ULTRASOUND</title>
<heading>Patient details</heading>
<row>
<col>
<text>Name: {ContextBinding FirstName} {ContextBinding LastName} ({ContextBinding GenderAsString})</text>
</col>
<col>
<text>DOB: {ContextBinding DateOfBirth} ({ContextBinding AgeString})</text>
</col>
</row>
<row>
<col>
<text>Patient ID: {ContextBinding PatientId}</text>
</col>
<col>
<text>Accession: {ContextBinding PatientId}</text>
</col>
</row>
<hr />
<row>
<col>
<input
type="string"
name="EDD"
label="EDD"></input>
</col>
<col>
<input
type="string"
name="Weeks"
label="Weeks"></input>
</col>
<col>
<input
type="string"
name="Days"
label="Days"></input>
</col>
</row>
<title>Placenta</title>
<row>
<col>
<row>
<input
type="bool"
name="has-Anterior"
label="Anterior"></input>
</row>
<row>
<input
type="bool"
name="has-Posterior"
label="Posterior"></input>
</row>
<row>
<input
type="bool"
name="has-Fundal"
label="Fundal"></input>
</row>
<row>
<col>
<input
type="bool"
name="has-Clear"
label="Clear (>2cm)"></input>
</col>
<col
width="0.7">
<input
type="string"
name="has-ClearLength"
visible="{Binding has-Clear}"
label="mm"></input>
</col>
</row>
<row>
<input
type="bool"
name="has-Low"
label="Low"></input>
</row>
<row>
<input
type="bool"
name="has-Covering"
label="Covering"></input>
</row>
<row>
<input
type="bool"
name="has-Liquor"
label="Liquor"></input>
</row>
<row>
<input
type="string"
name="CervicalLength"
label="Cervical Length (mm)"></input>
</row>
</col>
<col>
<textarea
name="PlacentaComments"
label="Placenta comments" />
</col>
</row>
<hr />
<title>Growth Parameters</title>
<row>
<col>
<row>
<col>
<input
type="string"
name="BPD"
label="BPD (mm)"></input>
</col>
<col
width="0.5">
<text>=</text>
</col>
</row>
<row>
<col>
<input
type="string"
name="HC"
label="HC (mm)"></input>
</col>
<col
width="0.5">
<text>=</text>
</col>
</row>
<row>
<col>
<input
type="string"
name="AC"
label="AC (mm)"></input>
</col>
<col
width="0.5">
<text>=</text>
</col>
</row>
<row>
<col>
<input
type="string"
name="FL"
label="FL (mm)"></input>
</col>
<col
width="0.5">
<text>=</text>
</col>
</row>
<row>
<col>
<input
type="string"
name="H2"
label="H2 (mm)"></input>
</col>
<col
width="0.5">
<text>=</text>
</col>
</row>
<row>
<col>
<input
type="string"
name="meanusage"
label="Mean U/S Age"></input>
</col>
<col
width="0.5">
<text>=</text>
</col>
</row>
</col>
<col>
<row>
<col>
<input
type="string"
name="bpd_weeks"
label="Weeks"></input>
</col>
<col>
<input
type="string"
name="bpd_days"
label="Days"></input>
</col>
</row>
<row>
<col>
<input
type="string"
name="hc_weeks"
label="Weeks"></input>
</col>
<col>
<input
type="string"
name="hc_days"
label="Days"></input>
</col>
</row>
<row>
<col>
<input
type="string"
name="ac_weeks"
label="Weeks"></input>
</col>
<col>
<input
type="string"
name="ac_days"
label="Days"></input>
</col>
</row>
<row>
<col>
<input
type="string"
name="fl_weeks"
label="Weeks"></input>
</col>
<col>
<input
type="string"
name="fl_days"
label="Days"></input>
</col>
</row>
<row>
<col>
<input
type="string"
name="h2_weeks"
label="Weeks"></input>
</col>
<col>
<input
type="string"
name="h2_days"
label="Days"></input>
</col>
</row>
<row>
<col>
<input
type="string"
name="mean_weeks"
label="Weeks"></input>
</col>
<col>
<input
type="string"
name="mean_days"
label="Days"></input>
</col>
</row>
</col>
<col>
<row>
<col>
<text>Heart rate:</text>
</col>
<col>
<input
type="string"
name="Heart_Rate"
label="bpm"></input>
</col>
</row>
<row>
<col>
<text></text>
</col>
<col>
<input
type="string"
name="BPDf"
label=""></input>
</col>
</row>
<row>
<col>
<text></text>
</col>
<col>
<input
type="string"
name="BPDf"
label=""></input>
</col>
</row>
</col>
</row>
<hr />
<title>Foetal anatomy</title>
<row>
<col
width="1.1">
<row>
<col
width="0.27">
<heading>Head</heading>
</col>
<col>
<input
type="bool"
name="has-Tick_All_Head"
label=""></input>
</col>
</row>
<row>
<input
type="bool"
name="has-Faix"
label="Faix"></input>
</row>
<row>
<input
type="bool"
name="has-Cavum_Septum"
label="Cavum Septum"></input>
</row>
<row>
<input
type="bool"
name="has-Skull_Bones"
label="Skull Bones"></input>
</row>
<row>
<input
type="bool"
name="has-Choroid_Plexus"
label="Choroid Plexus"></input>
</row>
<row>
<col>
<input
type="bool"
name="has-Ventricles"
label="Ventricles"></input>
</col>
<col
width="0.7">
<input
type="string"
name="has-VentriclesLength"
visible="{Binding has-Ventricles}"
label="mm"></input>
</col>
</row>
<row>
<col>
<input
type="bool"
name="has-Cerebellum"
label="Cerebellum"></input>
</col>
<col
width="0.7">
<input
type="string"
name="has-CerebellumLength"
visible="{Binding has-Cerebellum}"
label="mm"></input>
</col>
</row>
<row>
<col>
<input
type="bool"
name="has-Cisterna_Magna"
label="Cisterna Magna"></input>
</col>
<col
width="0.7">
<input
type="string"
name="has-Cisterna_MagnaLength"
visible="{Binding has-Cisterna_Magna}"
label="mm"></input>
</col>
</row>
<row>
<col>
<input
type="bool"
name="has-Nuchal_Fold"
label="Nuchal Fold"></input>
</col>
<col
width="0.7">
<input
type="string"
name="has-Nuchal_FoldLength"
visible="{Binding has-Nuchal_Fold}"
label="mm"></input>
</col>
</row>
</col>
<col>
<row>
<heading
icon="heart">Heart</heading>
</row>
<row>
<input
type="bool"
name="has-Position"
label="Position/Axis"></input>
</row>
<row>
<input
type="bool"
name="has-Four_Chambers"
label="Four Chambers"></input>
</row>
<row>
<input
type="bool"
name="has-LVOT"
label="LVOT"></input>
</row>
<row>
<input
type="bool"
name="has-RVOT"
label="RVOT"></input>
</row>
<row>
<input
type="bool"
name="has-Aortic_Arch"
label="Aortic Arch"></input>
</row>
<row>
<input
type="bool"
name="has-Ductal_Arch"
label="Ductal Arch"></input>
</row>
</col>
</row>
<hr />
<br />
<row>
<col
width="1.1">
<row>
<heading>Face</heading>
</row>
<row>
<input
type="bool"
name="has-Orbits"
label="Orbits"></input>
</row>
<row>
<input
type="bool"
name="has-Lips_Nose"
label="Lips/Nose"></input>
</row>
<row>
<input
type="bool"
name="has-Jaw"
label="Jaw"></input>
</row>
<row>
<input
type="bool"
name="has-Profile"
label="Profile"></input>
</row>
</col>
<col>
<row>
<heading>Abdomen</heading>
</row>
<row>
<input
type="bool"
name="has-Diaphragm"
label="Diaphragm"></input>
</row>
<row>
<input
type="bool"
name="has-Stomach"
label="Stomach"></input>
</row>
<row>
<input
type="bool"
name="has-LeftKidney"
label="Left Kidney"></input>
</row>
<row>
<input
type="bool"
name="has-RightKidney"
label="Right Kidney"></input>
</row>
<row>
<input
type="bool"
name="has-Bladder"
label="Bladder"></input>
</row>
<row>
<input
type="bool"
name="has-Abdo_Wall"
label="Abdo Wall"></input>
</row>
</col>
</row>
<hr />
<br />
<row>
<col
width="1.1">
<row>
<heading>Spine</heading>
</row>
<row>
<input
type="bool"
name="has-Coronal"
label="Coronal"></input>
</row>
<row>
<input
type="bool"
name="has-Sagital"
label="Sagital"></input>
</row>
<row>
<input
type="bool"
name="has-Axial"
label="Axial"></input>
</row>
<row>
<input
type="bool"
name="has-Skinline"
label="Skinline"></input>
</row>
</col>
<col>
<row>
<heading>Limbs</heading>
</row>
<row>
<input
type="bool"
name="has-12_Long_Bones"
label="12 Long Bones"></input>
</row>
<row>
<input
type="bool"
name="has-Hands"
label="Hands"></input>
</row>
<row>
<input
type="bool"
name="has-Feet"
label="Feet"></input>
</row>
<row>
<input
type="bool"
name="has-Position_of_joins"
label="Position of joins"></input>
</row>
</col>
</row>
<hr />
<br />
<row>
<col
width="1.1">
<row>
<heading>Umbilical cord</heading>
</row>
<row>
<input
type="bool"
name="has-Insertion"
label="Insertion"></input>
</row>
<row>
<input
type="bool"
name="has-3VC"
label="3VC"></input>
</row>
</col>
<col></col>
</row>
<hr />
<row>
<input
type="string"
name="Sonographer"
label="Sonographer"></input>
</row>
<row>
<action
name="reset"
content="RESET"
icon="close"
resets="true" />
<action
name="submit"
content="SUBMIT"
icon="check"
validates="true" />
</row>
</form>
See this images. If I tick the "Head" then everything in the head category should tick
This is quite useful, but I cannot think of a straightforward way to do this declaratively.
The dynamic object that holds the values implements INotifyPropertyChanged
, which you could use to listen to property changes, and do your side effects when a specific value changes.
((INotifyPropertyChanged)Form.Value).PropertyChanged += (s, e) => {
if (e.PropertyName == "Head") {
dynamic model = s;
if (model .Head == true) {
model.Property1 = true;
model.Property2 = true;
model.Property3 = true;
} // or use IDictionary<string,object> if you don't like dynamic
}
};
One could create some abstractions around this if we could have a way to attach metadata to properties.
For example:
<input type="bool" name="Head" data-sets="Property1,Property2,Property3" />
then we could do (pseudocode):
((INotifyPropertyChanged)Form.Value).PropertyChanged += (s, e) => {
var metadata = GetMetadataSomehow(e.PropertyName);
if (metadata.TryGetValue("data-sets", out var sets)) {
var props = sets.Split(",");
var dict = (IDictionary<string, object>)s;
foreach (var prop in props) {
dict[prop] = true;
}
}
};
if we could pull that metadata from the definition you could implement new behaviors for your form declaratively.
I'll think of other approaches meanwhile and will keep you updated. But I like this metadata concept so it will likely be implemented.
If you can implement <input type="bool" data-sets="Property1,Property2,Property3" />
this would work I think.
Maybe another way would be to bind the value of all the checkboxes to the main checkbox. Do you think this will work?
In a wpf binding there is the binding source and binding target. Binding source is always the model property, and we cannot change that, otherwise we could no longer read values from the model (when serializing to json etc).
In a WPF class form side effects could be done easily:
public bool CheckAll {
get {
return checkAll; // or maybe compute tis
}
set {
checkAll = true;
OnPropertyChanged();
if (value) {
Property1 = true;
Property2 = true;
Property3 = true;
}
}
}
In XML we have no way of executing side effects, unless we allow embedding javascript or cs roslyn scripts inside the form. But considering your form is hosted in a controlled and predictable environment, you can create underlying infrastructure for features you need, such as those special attributes for different behaviors.
I am working on this currently. Syntax will be like this: attr-myvalue="something"
, and then you can query metadata from formDefinition.GetAttributes("MyField").TryGetValue("myvalue", out value)
Feature is done, usages:
XML:
<form meta-myformattr="myvalue1">
<input name="FirstName" meta-myfieldattr="myvalue2" />
</form>
Accessing data:
string formAttr = myDefinition.Metadata["MyFormAttr"]; // dictionary is case-insensitive
string fieldAttr = myDefinition.GetDataFIeld("FirstName").Metadata["myfieldattr"];
Class/prop decoration:
[Meta("myformattr", "myvalue1")]
public class Form {
[Meta("myfieldattr", "myvalue2")]
public string FirstName { get; set; }
}
Accessing metadata is same from both xml and code-first.
Unrelated note, the row wrappers here are not necessary:
<col>
<row>
<heading>Limbs</heading>
</row>
<row>
<input
type="bool"
name="has-12_Long_Bones"
label="12 Long Bones"></input>
</row>
<row>
<input
type="bool"
name="has-Hands"
label="Hands"></input>
</row>
<row>
<input
type="bool"
name="has-Feet"
label="Feet"></input>
</row>
<row>
<input
type="bool"
name="has-Position_of_joins"
label="Position of joins"></input>
</row>
</col>
you can simplify it like this:
<col>
<heading>Limbs</heading>
<input
type="bool"
name="has-12_Long_Bones"
label="12 Long Bones"></input>
<input
type="bool"
name="has-Hands"
label="Hands"></input>
<input
type="bool"
name="has-Feet"
label="Feet"></input>
<input
type="bool"
name="has-Position_of_joins"
label="Position of joins"></input>
</col>
This will save some unnecessary ActionPanels from being created.
@edongashi sorry I don't understand how to use this. Can you please give an example. I only need one checkbox ticking all other checkboxes
I have a great idea, i will update here when i get time to do that (within the next 12 hours).
A plugin system named "behaviors", where you can inject various handlers, in this case a property changed behavior. You'll see soon.
Behaviors feature is done, well mostly done:
DynamicForm
class supports following methods:DynamicForm.AddBehavior(object behavior)
andDynamicForm.RemoveBehavior(object behavior)
- As you can see above, behaviors are only applied at class/global level
- Instance level (per form control) behaviors can be done if they are needed
- A behavior to be useful should implement
IPropertyChangedBehavior
to do side effects on property changed,IModelChangedBehavior
when a new form is mounted, or both. - More behaviors types will be added in time.
- This is basically a glorified observer pattern, but it's easier for the consumer because you don't have to worry about event leaks from manually hooking into inpcs.
For your task you need this behavior:
class CheckAllBehavior : IPropertyChangedBehavior
{
public void PropertyChanged(IPropertyChangedContext context)
{
if (!(context.Model is IDictionary<string, object> model))
{
return;
}
// Select all only on checked.
if (model[context.PropertyName] is true)
{
var meta = context
.FormDefinition
.GetElements()
.FirstOrDefault(elem => elem is DataFormField d && d.Key == context.PropertyName)
?.Metadata;
if (meta == null || !meta.TryGetValue("sets", out var value))
{
return;
}
var props = value?.Split(new[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries);
if (props != null)
{
foreach (var prop in props)
{
model[prop] = true;
}
}
}
}
}
This is already done in the demo (this specific solution will not be supported from the library). To see it in action write the following form:
<form>
<input type="bool" label="All" name="all" meta-sets="first,second,third" />
<input type="bool" label="First" name="first" />
<input type="bool" label="Second" name="second" />
<input type="bool" label="Third" name="third" />
</form>
Copy the class above into your project and modify it as you see fit. Don't forget to inject it somewhere in startup/init code.
Also I forgot, be careful when setting properties and reacting to their changes, as you might cause an infinite loop.
Did you get this working? I had updated the behavior to be much better. You can find it in the demo here. It handles mixed state and two way events (parent-child).
Just run the demo and type this:
<form>
<input type="bool" label="All" name="all" meta-sets="first,second,third" />
<input type="bool" label="First" name="first" />
<input type="bool" label="Second" name="second" />
<input type="bool" label="Third" name="third" />
</form>
@hashitha also what could be improved here: when serializing, you can check if the field is just an "all" setter, which you can ignore when serializing json.
if (field.Metadata.ContainsKey("sets")) {
// don't serialize this
continue;
}
because otherwise we get this:
{
"all": true,
"first": true,
"second": true,
"third": true
}
depending if you need the "all"
flag in your json or not.