Skip to content

Commit d0381f4

Browse files
committed
Refactor form_table.rb to follow proper MVP with Presenter
1 parent 3be6afc commit d0381f4

File tree

1 file changed

+59
-37
lines changed

1 file changed

+59
-37
lines changed

section-03-mvp-data-binding/exercise-05/form_table.rb

+59-37
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
require 'glimmer-dsl-libui'
22

3-
# TODO refactor to follow MVP correctly by extracting a presenter or extra model
4-
class FormTable
5-
Contact = Struct.new(:name, :email, :phone, :city, :state)
6-
7-
include Glimmer
3+
Contact = Struct.new(:name, :email, :phone, :city, :state) do
4+
def valid?
5+
values.map(&:to_s).any?(&:empty?)
6+
end
87

9-
attr_accessor :contacts, :name, :email, :phone, :city, :state, :filter_value
8+
def reset
9+
self.name = ''
10+
self.email = ''
11+
self.phone = ''
12+
self.city = ''
13+
self.state = ''
14+
end
15+
end
16+
17+
class FormTablePresenter
18+
attr_accessor :contacts, :filter_value, :unfiltered_contacts
1019

1120
def initialize
1221
@contacts = [
@@ -18,6 +27,38 @@ def initialize
1827
]
1928
end
2029

30+
def new_contact
31+
@new_contact ||= Contact.new
32+
end
33+
34+
def save_contact
35+
contacts << new_contact.dup # automatically inserts a row into the table due to explicit data-binding
36+
self.unfiltered_contacts = contacts.dup
37+
new_contact.reset # automatically clears form fields through explicit data-binding
38+
end
39+
40+
def filter_table
41+
self.unfiltered_contacts ||= contacts.dup
42+
# Unfilter first to remove any previous filters
43+
self.contacts = unfiltered_contacts.dup # affects table indirectly through explicit data-binding
44+
# Now, apply filter if entered
45+
unless filter_value.empty?
46+
self.contacts = contacts.filter do |contact| # affects table indirectly through explicit data-binding
47+
contact.members.any? do |attribute|
48+
contact[attribute].to_s.downcase.include?(filter_value.downcase)
49+
end
50+
end
51+
end
52+
end
53+
end
54+
55+
class FormTable
56+
include Glimmer
57+
58+
def initialize
59+
@presenter = FormTablePresenter.new
60+
end
61+
2162
def launch
2263
window('Contacts', 600, 600) {
2364
margined true
@@ -28,67 +69,48 @@ def launch
2869

2970
entry {
3071
label 'Name'
31-
text <=> [self, :name] # bidirectional data-binding between entry text and self.name
72+
text <=> [@presenter.new_contact, :name] # bidirectional data-binding between entry text and @presenter.name
3273
}
3374

3475
entry {
3576
label 'Email'
36-
text <=> [self, :email]
77+
text <=> [@presenter.new_contact, :email]
3778
}
3879

3980
entry {
4081
label 'Phone'
41-
text <=> [self, :phone]
82+
text <=> [@presenter.new_contact, :phone]
4283
}
4384

4485
entry {
4586
label 'City'
46-
text <=> [self, :city]
87+
text <=> [@presenter.new_contact, :city]
4788
}
4889

4990
entry {
5091
label 'State'
51-
text <=> [self, :state]
92+
text <=> [@presenter.new_contact, :state]
5293
}
5394
}
5495

5596
button('Save Contact') {
5697
stretchy false
5798

5899
on_clicked do
59-
new_row = [name, email, phone, city, state]
60-
if new_row.map(&:to_s).include?('')
100+
if @presenter.new_contact.valid?
61101
msg_box_error('Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
62102
else
63-
@contacts << Contact.new(*new_row) # automatically inserts a row into the table due to explicit data-binding
64-
@unfiltered_contacts = @contacts.dup
65-
self.name = '' # automatically clears name entry through explicit data-binding
66-
self.email = ''
67-
self.phone = ''
68-
self.city = ''
69-
self.state = ''
103+
@presenter.save_contact
70104
end
71105
end
72106
}
73107

74108
search_entry {
75109
stretchy false
76-
# bidirectional data-binding of text to self.filter_value with after_write option
77-
text <=> [self, :filter_value,
78-
after_write: ->(filter_value) { # execute after write to self.filter_value
79-
@unfiltered_contacts ||= @contacts.dup
80-
# Unfilter first to remove any previous filters
81-
self.contacts = @unfiltered_contacts.dup # affects table indirectly through explicit data-binding
82-
# Now, apply filter if entered
83-
unless filter_value.empty?
84-
self.contacts = @contacts.filter do |contact| # affects table indirectly through explicit data-binding
85-
contact.members.any? do |attribute|
86-
contact[attribute].to_s.downcase.include?(filter_value.downcase)
87-
end
88-
end
89-
end
90-
}
91-
]
110+
# bidirectional data-binding of text to @presenter.filter_value, filtering table after writing value to Model
111+
text <=> [@presenter, :filter_value,
112+
after_write: ->(filter_value) { @presenter.filter_table }
113+
]
92114
}
93115

94116
table {
@@ -99,7 +121,7 @@ def launch
99121
text_column('State')
100122

101123
editable true
102-
cell_rows <=> [self, :contacts] # explicit data-binding to self.contacts Model Array, auto-inferring model attribute names from underscored table column names by convention
124+
cell_rows <=> [@presenter, :contacts] # explicit data-binding to @presenter.contacts Model Array, auto-inferring model attribute names from underscored table column names by convention
103125

104126
on_changed do |row, type, row_data|
105127
puts "Row #{row} #{type}: #{row_data}"

0 commit comments

Comments
 (0)