Adds validation and binding logic to forms. Adds teacher register steps.

This commit is contained in:
Dylan Sarrazyn
2024-02-20 16:08:30 +01:00
parent e604c51ee9
commit 8438d63060
22 changed files with 294 additions and 267 deletions
@@ -1,4 +0,0 @@
extends Resource
class_name RegisterQuestion
var txt : String
+7 -27
View File
@@ -27,28 +27,17 @@ func _ready():
func _go_to_step(step_index: int):
var next_step
# Check if step is already instantiated
# Clear the steps
for step in steps.get_children(false):
step.hide()
if step.name == str(progress_bar.value):
step.completed.disconnect(_on_step_completed)
pass
if step.name == str(step_index):
next_step = step
step.queue_free()
# Instantiate the step
if not next_step:
next_step = teacher_steps[step_index].instantiate()
next_step.name = str(step_index)
next_step.object = register_data
steps.add_child(next_step)
else:
next_step.show()
var next_step = teacher_steps[step_index].instantiate()
next_step.name = str(step_index)
next_step.data = register_data
steps.add_child(next_step)
# Connect the step
next_step.completed.connect(_on_step_completed)
@@ -56,18 +45,9 @@ func _go_to_step(step_index: int):
progress_bar.value = step_index
func _reset_steps():
progress_bar.value = 0
progress_bar.max_value = 0
for step in steps.get_children(false):
step.queue_free()
func _on_back_button_pressed():
if progress_bar.value == 0:
print("Back to title screen")
# get_tree().change_scene_to_file()
else:
_go_to_step(progress_bar.value-1)
+22 -47
View File
@@ -4,65 +4,40 @@ class_name Step
signal completed
const checked_properties := [
"value",
"text"
]
@onready var question_label : Label = %QuestionLabel
@onready var form_validator : FormValidator = %FormValidator
@onready var form_binder : FormBinder = %FormBinder
@onready var form_container : Control = %FormContainer
@export var question_txt : String
@export var object : Resource
@export var data : Resource
func _ready():
if question_txt:
question_label.text = question_txt
_fill_form()
form_binder.read(data)
func _fill_form():
if not object:
func _on_form_validator_control_validated(control, passed, messages):
var label = find_child(control.name + "Error", true, false) as Label
if not label:
return
for node in form_container.get_children(false):
if node.name in object:
for property_name in checked_properties:
if property_name in node:
node.set(property_name, object.get(node.name))
else:
push_warning("Property not found in object for node: " + node.name + ". All nodes inside FormContainer must reflects a property from the resource.")
func _write_object() -> bool:
if not object:
return false
for node in form_container.get_children(false):
var value
if node.name in object:
for property_name in checked_properties:
if property_name in node:
value = node.get(property_name)
if value:
object.set(node.name, value)
else:
return false
return true
if passed:
label.hide()
else:
label.text = ". ".join(messages)
label.show()
func _on_validate_button_pressed():
print(form_validator.validate())
# Validate the fields
if not form_validator.validate():
push_warning("Validation failed (" + str(self) + ")")
return
# Writes data in object
if _write_object():
# Emit completed signal
emit_signal("completed")
else:
# TODO Show error message
print("Please fill all the fields")
if not form_binder.write():
push_warning("Impossible to write data in object (" + str(self) + ")")
return
# Emit completed signal
emit_signal("completed")
+12 -6
View File
@@ -1,7 +1,8 @@
[gd_scene load_steps=3 format=3 uid="uid://bxd3i06rqpxf0"]
[gd_scene load_steps=4 format=3 uid="uid://bxd3i06rqpxf0"]
[ext_resource type="Script" path="res://sources/menus/register/steps/base_step.gd" id="1_02ocl"]
[ext_resource type="Script" path="res://addons/godot-form-validator/form_validator.gd" id="2_jbdsv"]
[ext_resource type="Script" path="res://addons/godot-form-validator/form_validator.gd" id="2_nw61e"]
[ext_resource type="Script" path="res://sources/utils/binder/form_binder.gd" id="3_mk32l"]
[node name="BaseRegisterForm" type="Control"]
layout_mode = 3
@@ -78,10 +79,14 @@ offset_top = -40.0
offset_right = 20.0
grow_horizontal = 2
grow_vertical = 0
script = ExtResource("2_jbdsv")
auto_validate = true
script = ExtResource("2_nw61e")
[node name="FormContainer" type="VBoxContainer" parent="FormValidator"]
[node name="FormBinder" type="Control" parent="FormValidator"]
unique_name_in_owner = true
anchors_preset = 0
script = ExtResource("3_mk32l")
[node name="FormContainer" type="VBoxContainer" parent="FormValidator/FormBinder"]
unique_name_in_owner = true
custom_minimum_size = Vector2(1624, 812)
layout_mode = 1
@@ -95,7 +100,8 @@ offset_top = -812.0
offset_right = 812.0
grow_horizontal = 2
grow_vertical = 0
theme_override_constants/separation = 25
theme_override_constants/separation = 20
alignment = 1
[connection signal="pressed" from="RightMargin/RightContainer/ValidateButton" to="." method="_on_validate_button_pressed"]
[connection signal="control_validated" from="FormValidator" to="." method="_on_form_validator_control_validated"]
@@ -1,13 +0,0 @@
extends Step
@onready var password := %password
@onready var password_confirm := %password_confirm
func _on_validate_button_pressed():
if password.text != password_confirm.text:
print("Make sure the password is the same in both fields")
return
super._on_validate_button_pressed()
@@ -1,30 +1,45 @@
[gd_scene load_steps=10 format=3 uid="uid://cwqcybk5tvbly"]
[gd_scene load_steps=14 format=3 uid="uid://cwqcybk5tvbly"]
[ext_resource type="PackedScene" uid="uid://bxd3i06rqpxf0" path="res://sources/menus/register/steps/base_step.tscn" id="1_66bx5"]
[ext_resource type="Script" path="res://sources/menus/register/steps/credentials_step.gd" id="2_uwjaa"]
[ext_resource type="Script" path="res://addons/godot-form-validator/control_validator.gd" id="3_hblcf"]
[ext_resource type="Script" path="res://addons/godot-form-validator/rules/required_rule.gd" id="4_4ltsd"]
[ext_resource type="Script" path="res://addons/godot-form-validator/rules/email_rule.gd" id="5_cnymo"]
[ext_resource type="Script" path="res://sources/utils/binder/control_binder.gd" id="6_ugvpe"]
[ext_resource type="Script" path="res://addons/godot-form-validator/validators/line_edit_validator.gd" id="6_viaho"]
[ext_resource type="Script" path="res://sources/menus/register/steps/validation/confirm_rule.gd" id="7_7y5q1"]
[sub_resource type="Resource" id="Resource_os08u"]
script = ExtResource("4_4ltsd")
fail_message = "A value is required."
[sub_resource type="Resource" id="Resource_j7vhe"]
[sub_resource type="Resource" id="Resource_6p4qq"]
script = ExtResource("5_cnymo")
fail_message = "Value must be a valid email address."
[sub_resource type="Resource" id="Resource_84kj5"]
script = ExtResource("6_viaho")
validation_order = 1
validation_method = 0
validation_method = 1
skip_validation = false
rules = Array[Resource("res://addons/godot-form-validator/rules/validator_rule.gd")]([SubResource("Resource_os08u"), SubResource("Resource_j7vhe")])
rules = Array[Resource("res://addons/godot-form-validator/rules/validator_rule.gd")]([SubResource("Resource_os08u"), SubResource("Resource_6p4qq")])
[sub_resource type="Resource" id="Resource_5jooi"]
script = ExtResource("4_4ltsd")
fail_message = "A value is required."
[sub_resource type="Resource" id="Resource_104uy"]
script = ExtResource("7_7y5q1")
confirm_control_path = NodePath("../../PasswordConfirmField")
fail_message = "Value must be the same in both fields."
[sub_resource type="Resource" id="Resource_icogs"]
script = ExtResource("6_viaho")
validation_order = 1
validation_method = 1
skip_validation = false
rules = Array[Resource("res://addons/godot-form-validator/rules/validator_rule.gd")]([SubResource("Resource_5jooi"), SubResource("Resource_104uy")])
[node name="CredentialsStep" instance=ExtResource("1_66bx5")]
script = ExtResource("2_uwjaa")
question_txt = "Fill in your credentials"
[node name="ValidateButton" parent="RightMargin/RightContainer" index="0"]
text = "Validate"
@@ -32,23 +47,52 @@ text = "Validate"
[node name="QuestionLabel" parent="PanelContainer/QuestionContainer" index="0"]
text = "Fill in your credentials"
[node name="email" type="LineEdit" parent="FormValidator/FormContainer" index="0"]
[node name="EmailContainer" type="VBoxContainer" parent="FormValidator/FormBinder/FormContainer" index="0"]
layout_mode = 2
[node name="EmailFieldError" type="Label" parent="FormValidator/FormBinder/FormContainer/EmailContainer" index="0"]
visible = false
layout_mode = 2
vertical_alignment = 1
[node name="EmailField" type="LineEdit" parent="FormValidator/FormBinder/FormContainer/EmailContainer" index="1"]
layout_mode = 2
theme_override_font_sizes/font_size = 70
placeholder_text = "Email"
placeholder_text = "Email*"
[node name="ControlValidator" type="Node" parent="FormValidator/FormContainer/email" index="0"]
[node name="ControlValidator" type="Node" parent="FormValidator/FormBinder/FormContainer/EmailContainer/EmailField" index="0"]
script = ExtResource("3_hblcf")
validator = SubResource("Resource_84kj5")
[node name="password" type="LineEdit" parent="FormValidator/FormContainer" index="1"]
unique_name_in_owner = true
layout_mode = 2
theme_override_font_sizes/font_size = 70
placeholder_text = "Password"
[node name="ControlBinder" type="Control" parent="FormValidator/FormBinder/FormContainer/EmailContainer/EmailField" index="1"]
anchors_preset = 0
script = ExtResource("6_ugvpe")
property_name = "email"
[node name="password_confirm" type="LineEdit" parent="FormValidator/FormContainer" index="2"]
unique_name_in_owner = true
[node name="PasswordContainer" type="VBoxContainer" parent="FormValidator/FormBinder/FormContainer" index="1"]
layout_mode = 2
theme_override_constants/separation = 20
[node name="PasswordFieldError" type="Label" parent="FormValidator/FormBinder/FormContainer/PasswordContainer" index="0"]
visible = false
layout_mode = 2
vertical_alignment = 1
[node name="PasswordField" type="LineEdit" parent="FormValidator/FormBinder/FormContainer/PasswordContainer" index="1"]
layout_mode = 2
theme_override_font_sizes/font_size = 70
placeholder_text = "Confirm password"
placeholder_text = "Password*"
[node name="ControlValidator" type="Node" parent="FormValidator/FormBinder/FormContainer/PasswordContainer/PasswordField" index="0"]
script = ExtResource("3_hblcf")
validator = SubResource("Resource_icogs")
[node name="ControlBinder" type="Control" parent="FormValidator/FormBinder/FormContainer/PasswordContainer/PasswordField" index="1"]
anchors_preset = 0
script = ExtResource("6_ugvpe")
property_name = "password"
[node name="PasswordConfirmField" type="LineEdit" parent="FormValidator/FormBinder/FormContainer/PasswordContainer" index="2"]
layout_mode = 2
theme_override_font_sizes/font_size = 70
placeholder_text = "Confirm password*"
@@ -1,20 +1,20 @@
[gd_scene load_steps=2 format=3 uid="uid://do5e7c1oxkn5g"]
[gd_scene load_steps=3 format=3 uid="uid://do5e7c1oxkn5g"]
[ext_resource type="PackedScene" uid="uid://bxd3i06rqpxf0" path="res://sources/menus/register/steps/base_step.tscn" id="1_w3urq"]
[ext_resource type="Script" path="res://sources/utils/binder/control_binder.gd" id="2_asuou"]
[node name="DevicesCountStep" instance=ExtResource("1_w3urq")]
question_txt = "Choose the number of devices you are using"
[node name="QuestionLabel" parent="PanelContainer/QuestionContainer" index="0"]
text = "Choose the number of devices you are using"
[node name="FormContainer" parent="." index="2"]
offset_left = -812.0
offset_top = -812.0
offset_right = 812.0
[node name="devices_count" type="SpinBox" parent="FormContainer" index="0"]
[node name="DevicesCountField" type="SpinBox" parent="FormValidator/FormBinder/FormContainer" index="0"]
layout_mode = 2
min_value = 1.0
max_value = 90.0
value = 1.0
[node name="ControlBinder" type="Control" parent="FormValidator/FormBinder/FormContainer/DevicesCountField" index="2"]
anchors_preset = 0
script = ExtResource("2_asuou")
property_name = "devices_count"
@@ -1,19 +1,49 @@
[gd_scene load_steps=2 format=3 uid="uid://buhnx0oblueuq"]
[gd_scene load_steps=8 format=3 uid="uid://buhnx0oblueuq"]
[ext_resource type="PackedScene" uid="uid://bxd3i06rqpxf0" path="res://sources/menus/register/steps/base_step.tscn" id="1_ksrhk"]
[ext_resource type="Script" path="res://addons/godot-form-validator/control_validator.gd" id="2_03dua"]
[ext_resource type="Script" path="res://addons/godot-form-validator/rules/required_rule.gd" id="3_qewnl"]
[ext_resource type="Script" path="res://sources/utils/binder/control_binder.gd" id="4_1k1yt"]
[ext_resource type="Script" path="res://sources/menus/register/steps/validation/item_list_validator.gd" id="4_rimto"]
[sub_resource type="Resource" id="Resource_kw8sn"]
script = ExtResource("3_qewnl")
fail_message = "A value is required."
[sub_resource type="Resource" id="Resource_ar6ux"]
script = ExtResource("4_rimto")
validation_order = 1
validation_method = 1
skip_validation = false
rules = Array[Resource("res://addons/godot-form-validator/rules/validator_rule.gd")]([SubResource("Resource_kw8sn")])
[node name="MethodStep" instance=ExtResource("1_ksrhk")]
question_txt = "Choose the education method you are using"
[node name="QuestionLabel" parent="PanelContainer/QuestionContainer" index="0"]
text = "Choose the education method you are using"
[node name="method" type="ItemList" parent="FormContainer" index="0"]
[node name="MethodContainer" type="VBoxContainer" parent="FormValidator/FormBinder/FormContainer" index="0"]
layout_mode = 2
[node name="MethodSelectError" type="Label" parent="FormValidator/FormBinder/FormContainer/MethodContainer" index="0"]
visible = false
layout_mode = 2
text = "Test"
vertical_alignment = 1
[node name="MethodSelect" type="ItemList" parent="FormValidator/FormBinder/FormContainer/MethodContainer" index="1"]
layout_mode = 2
theme_override_constants/h_separation = 25
theme_override_constants/v_separation = 25
theme_override_font_sizes/font_size = 70
auto_height = true
item_count = 2
item_0/text = "Complete"
item_1/text = "App Only"
[node name="ControlValidator" type="Node" parent="FormValidator/FormBinder/FormContainer/MethodContainer/MethodSelect" index="1"]
script = ExtResource("2_03dua")
validator = SubResource("Resource_ar6ux")
[node name="ControlBinder" type="Control" parent="FormValidator/FormBinder/FormContainer/MethodContainer/MethodSelect" index="2"]
anchors_preset = 0
script = ExtResource("4_1k1yt")
property_name = "method"
@@ -0,0 +1,8 @@
extends Step
@onready var students_count_field := %StudentsCountField
func _ready():
super._ready()
if data and "devices_count" in data:
students_count_field.min_value = data.devices_count
@@ -1,15 +1,23 @@
[gd_scene load_steps=2 format=3 uid="uid://cyhoa68y1yyyg"]
[gd_scene load_steps=4 format=3 uid="uid://cyhoa68y1yyyg"]
[ext_resource type="PackedScene" uid="uid://bxd3i06rqpxf0" path="res://sources/menus/register/steps/base_step.tscn" id="1_par6r"]
[ext_resource type="Script" path="res://sources/utils/binder/control_binder.gd" id="2_hpxb0"]
[ext_resource type="Script" path="res://sources/menus/register/steps/teacher/students_count_step.gd" id="2_susi5"]
[node name="StudentsCountStep" instance=ExtResource("1_par6r")]
question_txt = "Choose the total number of students"
script = ExtResource("2_susi5")
[node name="QuestionLabel" parent="PanelContainer/QuestionContainer" index="0"]
text = "Choose the total number of students"
[node name="students_count" type="SpinBox" parent="FormContainer" index="0"]
[node name="StudentsCountField" type="SpinBox" parent="FormValidator/FormBinder/FormContainer" index="0"]
unique_name_in_owner = true
layout_mode = 2
min_value = 1.0
max_value = 90.0
value = 1.0
[node name="ControlBinder" type="Control" parent="FormValidator/FormBinder/FormContainer/StudentsCountField" index="2"]
anchors_preset = 0
script = ExtResource("2_hpxb0")
property_name = "students_count"
@@ -0,0 +1,18 @@
@tool
extends ValidatorRule
class_name ConfirmRule
@export_node_path("Control") var confirm_control_path
func _init() -> void:
fail_message = "Value must be the same in both fields."
func apply(control: Control, value) -> RuleResult:
var result = RuleResult.new()
if confirm_control_path && value is String:
var confirm_control = control.get_child(0).get_node(confirm_control_path)
result.passed = confirm_control && Validation.find_validator(control).get_value(confirm_control) == value
if not result.passed:
result.message = fail_message
return result
@@ -0,0 +1,22 @@
@tool
extends Validator
class_name ItemListValidator
func get_value(control: Control):
var item_list = control as ItemList
if not item_list:
return null
var selected_indexes = item_list.get_selected_items()
if not selected_indexes or selected_indexes.size() == 0:
return null
if item_list.select_mode == ItemList.SELECT_SINGLE:
return selected_indexes[0]
return selected_indexes
func is_type(node) -> bool:
return node is ItemList
+35
View File
@@ -0,0 +1,35 @@
@tool
extends Control
class_name ControlBinder
@export var property_name : String
var control : Control
func _ready():
control = get_parent()
func get_value():
if not control:
return null
if control is Range:
return control.value
elif control is LineEdit or control is TextEdit:
return control.text
return null
func set_value(value):
print("Writing " + str(value) + " in " + str(self.owner))
if not control:
return
if control is Range:
control.value = float(value)
elif control is LineEdit or control is TextEdit:
control.text = str(value)
+54
View File
@@ -0,0 +1,54 @@
@tool
extends Control
class_name FormBinder
@export var data : Resource
var _control_binder_map: Dictionary = {}
func _ready():
_find_binders(self)
func _find_binders(node: Node) -> void:
for child in node.get_children():
var control_binder = child as ControlBinder
if control_binder:
var parent = control_binder.get_parent()
if parent:
_control_binder_map[parent] = control_binder
_find_binders(child)
func read(resource : Resource) -> void:
if resource:
data = resource
if not data:
return
for control in _control_binder_map.keys():
var binder = _control_binder_map[control]
if binder.property_name in data:
var property_value = data.get(binder.property_name)
if typeof(property_value) == typeof(binder.get_value()):
binder.set_value(property_value)
else:
push_warning("Property typing and control doesn't match for property '" + binder.property_name + "' (" + str(binder.control) + ")")
else:
push_warning("Property " + binder.property_name + " not found in " + data.get_class())
func write() -> bool:
if not data:
return false
for control in _control_binder_map.keys():
var binder = _control_binder_map[control]
if binder.property_name in data:
data.set(binder.property_name, binder.get_value())
else:
push_warning("Property " + binder.property_name + " not found in " + data.get_class())
return true