浏览代码

'data-checked' view and controller for binding to checkboxes and radio buttons.

Michael Ragazzon 5 年之前
父节点
当前提交
5466f2b701

+ 37 - 16
Samples/basic/databinding/data/databinding.rml

@@ -124,7 +124,8 @@ form h2 {
 	display: block;
 	font-size: 16px;
 	font-weight: bold;
-	margin-top: 8px;
+	margin-top: 1em;
+	margin-bottom: 0.3em;
 }
 #rating {
 	display: inline-block;
@@ -141,6 +142,19 @@ form h2 {
 	font-effect: outline(2px #006600);
 	color: #ddd;
 }
+.picker p {
+	cursor: pointer;
+}
+input:disabled {
+	image-color: #aaac;
+}
+li {
+	display: block;
+	color: #eee;
+	margin-left: 1em;
+	margin-top: 0.3em;
+	margin-bottom: 0.3em;
+}
 </style>
 </head>
 
@@ -193,7 +207,6 @@ form h2 {
 </panel>
 <tab>Forms</tab>
 <panel id="controls" data-model="forms">
-	<h1>Todo</h1>
 	<form onsubmit="submit_form">
 		<h2>Full name</h2>
 		<div>
@@ -204,20 +217,22 @@ form h2 {
 			<input type="text" name="email"/>
 			<input type="password" name="password"/>
 		</div>
-		<h2>Favorite animal</h2>
+		<h2>Favorite animal: {{ animal }}</h2>
 		<div>
-			<input type="radio" name="animal" value="dog" checked/> Dog
-			<input type="radio" name="animal" value="cat"/> Cat
-			<input type="radio" name="animal" value="narwhal"/> Narwhal
-			<input type="radio" name="animal" value="no"/> I don't like animals
+			<input type="radio" name="animal" value="dog" data-checked="animal"/> Dog
+			<input type="radio" name="animal" value="cat" data-checked="animal"/> Cat
+			<input type="radio" name="animal" value="narwhal" data-checked="animal"/> Narwhal
+			<input type="radio" name="animal" value="no" data-checked="animal"/> I don't like animals
 		</div>
 		<h2>Favorite meals</h2>
 		<div>
-			<input type="checkbox" name="meals" value="pizza" data-attrif-checked="rating > 70" disabled/> Pizza
-			<input type="checkbox" name="meals" value="pasta" checked/> Pasta
-			<input type="checkbox" name="meals" value="lasagne" data-attrif-checked="lasagne" data-event-change="lasagne = ev.value != ''"/> Lasagne
-			<p data-visible="rating < 70">You can only have pizza if the rating is satisfactory.</p>
-			<p data-visible="lasagne">Lasagne is delicous!</p>
+			<input type="checkbox" name="meals" value="pizza" data-checked="pizza" data-attrif-disabled="rating < 70"/> Pizza
+			<input type="checkbox" name="meals" value="pasta" data-checked="pasta"/> Pasta
+			<input type="checkbox" name="meals" value="lasagne" data-checked="lasagne"/> Lasagne
+			<li style="color: red;" data-if="pizza && rating < 70">• You can only have pizza if the rating is satisfactory.</li>
+			<li data-if="pizza && rating >= 70">• Pizza is life!</li>
+			<li data-if="pasta">• Pasta is squiggly good!</li>
+			<li data-if="lasagne">• Lasagne is delicous!</li>
 		</div>
 		<h2>Rating</h2>
 		<div>
@@ -225,10 +240,16 @@ form h2 {
 		</div>
 		<h2>Subject</h2>
 		<div>
-			<select name="subject" data-event-change="selected_subject = ev.value">
-				<option data-for="s : subjects" data-value="it_index" data-attrif-selected="it_index == selected_subject">{{s}}</option>
-			</select>
-			<div>
+			<!--<select name="subject" data-event-change="selected_subject = ev.value">
+                <option data-for="s : subjects" data-value="it_index">{{s}}</option>
+            </select>
+            <select name="subject" data-value="selected_subject">
+                <option value="0" selected>{{ subjects[0] }}</option>
+                <option value="1">{{ subjects[1] }}</option>
+                <option value="2">{{ subjects[2] }}</option>
+                <option value="3">{{ subjects[3] }}</option>
+            </select>-->
+			<div class="picker">
 				<p data-for="s : subjects" data-style-color="it_index == selected_subject ? 'red' : 'black'" data-event-click="selected_subject = it_index">{{it_index + 1 + ': ' + s}}</p>
 			</div>
 			<p>

+ 7 - 1
Samples/basic/databinding/src/main.cpp

@@ -278,7 +278,10 @@ namespace FormsExample {
 
 	struct MyData {
 		int rating = 50;
-		bool lasagne = true;
+		bool pizza = true;
+		bool pasta = false;
+		bool lasagne = false;
+		Rml::String animal = "dog";
 		Rml::Vector<Rml::String> subjects = { "Choose your subject", "Feature request", "Bug report", "Praise", "Criticism" };
 		int selected_subject = 1;
 	} my_data;
@@ -292,7 +295,10 @@ namespace FormsExample {
 		constructor.RegisterArray<Rml::Vector<Rml::String>>();
 
 		constructor.Bind("rating", &my_data.rating);
+		constructor.Bind("pizza", &my_data.pizza);
+		constructor.Bind("pasta", &my_data.pasta);
 		constructor.Bind("lasagne", &my_data.lasagne);
+		constructor.Bind("animal", &my_data.animal);
 		constructor.Bind("subjects", &my_data.subjects);
 		constructor.Bind("selected_subject", &my_data.selected_subject);
 

+ 41 - 11
Source/Core/DataControllerDefault.cpp

@@ -67,6 +67,7 @@ void DataControllerValue::ProcessEvent(Event& event)
 	if (Element* element = GetElement())
 	{
 		const auto& parameters = event.GetParameters();
+
 		auto it = parameters.find("value");
 		if (it == parameters.end())
 		{
@@ -74,7 +75,15 @@ void DataControllerValue::ProcessEvent(Event& event)
 			return;
 		}
 
-		SetValue(it->second);
+		DataModel* model = element->GetDataModel();
+		if (!model)
+			return;
+
+		if (DataVariable variable = model->GetVariable(address))
+		{
+			if (SetValue(it->second, variable))
+				model->DirtyVariable(address.front().name);
+		}
 	}
 }
 
@@ -83,24 +92,45 @@ void DataControllerValue::Release()
 	delete this;
 }
 
-void DataControllerValue::SetValue(const Variant& value)
+bool DataControllerValue::SetValue(const Variant& value, DataVariable variable)
 {
-	Element* element = GetElement();
-	if (!element)
-		return;
+	return variable.Set(value);
+}
 
-	DataModel* model = element->GetDataModel();
-	if (!model)
-		return;
 
-	if (DataVariable variable = model->GetVariable(address))
+DataControllerChecked::DataControllerChecked(Element* element) : DataControllerValue(element)
+{}
+
+
+bool DataControllerChecked::SetValue(const Variant& value, DataVariable variable)
+{
+	bool result = false;
+	Variant old_value;
+
+	if (variable.Get(old_value))
 	{
-		variable.Set(value);
-		model->DirtyVariable(address.front().name);
+		// Value will be empty if the button was just unchecked, otherwise it will take the 'value' attribute.
+		const String new_value = value.Get<String>();
+
+		if (old_value.GetType() == Variant::BOOL)
+		{
+			// If the client variable is a boolean type, we assume the button acts like a checkbox, and set the new checked state.
+			result = variable.Set(Variant(!new_value.empty()));
+		}
+		else
+		{
+			// Otherwise, we assume the button acts like a radio box. Then, we do nothing if the box was unchecked,
+			// and instead let only the newly checked box set the new value.
+			if (!new_value.empty())
+				result = variable.Set(value);
+		}
 	}
+
+	return result;
 }
 
 
+
 DataControllerEvent::DataControllerEvent(Element* element) : DataController(element)
 {}
 

+ 11 - 3
Source/Core/DataControllerDefault.h

@@ -43,7 +43,7 @@ class DataExpression;
 using DataExpressionPtr = UniquePtr<DataExpression>;
 
 
-class DataControllerValue final : public DataController, private EventListener {
+class DataControllerValue : public DataController, private EventListener {
 public:
     DataControllerValue(Element* element);
     ~DataControllerValue();
@@ -57,13 +57,21 @@ protected:
     // Delete this.
     void Release() override;
 
-private:
-    void SetValue(const Variant& new_value);
+    // Set the new value on the variable, returns true if it should be dirtied.
+    virtual bool SetValue(const Variant& new_value, DataVariable variable);
 
     DataAddress address;
 };
 
 
+class DataControllerChecked final : public DataControllerValue {
+public:
+    DataControllerChecked(Element* element);
+
+    bool SetValue(const Variant& new_value, DataVariable variable) override;
+};
+
+
 class DataControllerEvent final : public DataController, private EventListener {
 public:
     DataControllerEvent(Element* element);

+ 39 - 0
Source/Core/DataViewDefault.cpp

@@ -132,6 +132,45 @@ bool DataViewAttributeIf::Update(DataModel& model)
 DataViewValue::DataViewValue(Element* element) : DataViewAttribute(element, "value")
 {}
 
+DataViewChecked::DataViewChecked(Element* element) : DataViewCommon(element)
+{}
+
+bool DataViewChecked::Update(DataModel & model)
+{
+	bool result = false;
+	Variant variant;
+	Element* element = GetElement();
+	DataExpressionInterface expr_interface(&model, element);
+
+	if (element && GetExpression().Run(expr_interface, variant))
+	{
+		bool new_checked_state = false;
+
+		if (variant.GetType() == Variant::BOOL)
+		{
+			new_checked_state = variant.Get<bool>();
+		}
+		else
+		{
+			const String value = variant.Get<String>();
+			new_checked_state = (!value.empty() && value == element->GetAttribute<String>("value", ""));
+		}
+
+		const bool current_checked_state = element->HasAttribute("checked");
+
+		if (new_checked_state != current_checked_state)
+		{
+			result = true;
+			if (new_checked_state)
+				element->SetAttribute("checked", String());
+			else
+				element->RemoveAttribute("checked");
+		}
+	}
+
+	return result;
+}
+
 
 DataViewStyle::DataViewStyle(Element* element) : DataViewCommon(element)
 {}

+ 7 - 0
Source/Core/DataViewDefault.h

@@ -82,6 +82,13 @@ public:
 	DataViewValue(Element* element);
 };
 
+class DataViewChecked final : public DataViewCommon {
+public:
+	DataViewChecked(Element* element);
+
+	bool Update(DataModel& model) override;
+};
+
 class DataViewStyle final : public DataViewCommon {
 public:
 	DataViewStyle(Element* element);

+ 4 - 0
Source/Core/Factory.cpp

@@ -178,12 +178,14 @@ struct DefaultInstancers {
 	DataViewInstancerDefault<DataViewStyle> data_view_style;
 	DataViewInstancerDefault<DataViewText> data_view_text;
 	DataViewInstancerDefault<DataViewValue> data_view_value;
+	DataViewInstancerDefault<DataViewChecked> data_view_checked;
 
 	DataViewInstancerDefault<DataViewFor> structural_data_view_for;
 
 	// Data binding controllers
 	DataControllerInstancerDefault<DataControllerValue> data_controller_value;
 	DataControllerInstancerDefault<DataControllerEvent> data_controller_event;
+	DataControllerInstancerDefault<DataControllerChecked> data_controller_checked;
 };
 
 static UniquePtr<DefaultInstancers> default_instancers;
@@ -268,11 +270,13 @@ bool Factory::Initialise()
 	RegisterDataViewInstancer(&default_instancers->data_view_style,          "style",   false);
 	RegisterDataViewInstancer(&default_instancers->data_view_text,           "text",    false);
 	RegisterDataViewInstancer(&default_instancers->data_view_value,          "value",   false);
+	RegisterDataViewInstancer(&default_instancers->data_view_checked,        "checked", false);
 	RegisterDataViewInstancer(&default_instancers->structural_data_view_for, "for",     true );
 
 	// Data binding controllers
 	RegisterDataControllerInstancer(&default_instancers->data_controller_value, "value");
 	RegisterDataControllerInstancer(&default_instancers->data_controller_event, "event");
+	RegisterDataControllerInstancer(&default_instancers->data_controller_checked, "checked");
 
 	// XML node handlers
 	XMLParser::RegisterNodeHandler("", MakeShared<XMLNodeHandlerDefault>());