clevic 0.13.0.b9 → 0.13.0.b10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +3 -0
- data/lib/clevic/action_builder.rb +16 -16
- data/lib/clevic/ar_methods.rb +22 -22
- data/lib/clevic/attribute_list.rb +5 -5
- data/lib/clevic/cache_table.rb +18 -18
- data/lib/clevic/dataset_roller.rb +3 -3
- data/lib/clevic/default_view.rb +4 -4
- data/lib/clevic/delegate.rb +8 -8
- data/lib/clevic/delegates/combo_delegate.rb +22 -22
- data/lib/clevic/delegates/distinct_delegate.rb +5 -5
- data/lib/clevic/delegates/relational_delegate.rb +6 -6
- data/lib/clevic/delegates/set_delegate.rb +3 -3
- data/lib/clevic/dirty.rb +1 -1
- data/lib/clevic/emitter.rb +3 -3
- data/lib/clevic/extensions.rb +4 -4
- data/lib/clevic/field.rb +68 -68
- data/lib/clevic/field_valuer.rb +26 -26
- data/lib/clevic/filter_command.rb +6 -6
- data/lib/clevic/framework.rb +3 -3
- data/lib/clevic/generic_format.rb +2 -2
- data/lib/clevic/many_field.rb +3 -3
- data/lib/clevic/model_builder.rb +88 -84
- data/lib/clevic/model_column.rb +13 -13
- data/lib/clevic/ordered_dataset.rb +7 -7
- data/lib/clevic/qt/action_builder.rb +3 -3
- data/lib/clevic/qt/browser.rb +28 -28
- data/lib/clevic/qt/clipboard.rb +5 -5
- data/lib/clevic/qt/combo_delegate.rb +12 -12
- data/lib/clevic/qt/distinct_delegate.rb +1 -1
- data/lib/clevic/qt/extensions.rb +4 -4
- data/lib/clevic/qt/qt_combo_box.rb +7 -7
- data/lib/clevic/qt/relational_delegate.rb +5 -5
- data/lib/clevic/qt/search_dialog.rb +15 -15
- data/lib/clevic/qt/simplest_delegate.rb +2 -2
- data/lib/clevic/qt/table_model.rb +46 -46
- data/lib/clevic/qt/table_view.rb +48 -48
- data/lib/clevic/qt/text_delegate.rb +9 -9
- data/lib/clevic/rails_models_loaders.rb +3 -3
- data/lib/clevic/record.rb +8 -8
- data/lib/clevic/sampler.rb +6 -6
- data/lib/clevic/sequel_ar_adapter.rb +22 -22
- data/lib/clevic/sequel_clevic.rb +10 -10
- data/lib/clevic/sequel_meta.rb +5 -5
- data/lib/clevic/sequel_naked.rb +4 -4
- data/lib/clevic/swing/action.rb +20 -20
- data/lib/clevic/swing/action_builder.rb +2 -2
- data/lib/clevic/swing/boolean_delegate.rb +3 -3
- data/lib/clevic/swing/browser.rb +37 -37
- data/lib/clevic/swing/cell_editor.rb +13 -13
- data/lib/clevic/swing/cell_renderer.rb +7 -7
- data/lib/clevic/swing/clipboard.rb +19 -19
- data/lib/clevic/swing/combo_delegate.rb +26 -26
- data/lib/clevic/swing/confirm_dialog.rb +7 -7
- data/lib/clevic/swing/delegate.rb +4 -4
- data/lib/clevic/swing/extensions.rb +24 -24
- data/lib/clevic/swing/field.rb +1 -1
- data/lib/clevic/swing/relational_delegate.rb +2 -2
- data/lib/clevic/swing/row_header.rb +32 -32
- data/lib/clevic/swing/search_dialog.rb +31 -31
- data/lib/clevic/swing/selection_model.rb +12 -12
- data/lib/clevic/swing/swing_table_index.rb +6 -6
- data/lib/clevic/swing/table_model.rb +30 -30
- data/lib/clevic/swing/table_view.rb +54 -54
- data/lib/clevic/swing/table_view_focus.rb +4 -4
- data/lib/clevic/swing/tag_delegate.rb +14 -14
- data/lib/clevic/swing/tag_editor.rb +4 -4
- data/lib/clevic/swing/text_area_delegate.rb +6 -6
- data/lib/clevic/swing/text_delegate.rb +4 -4
- data/lib/clevic/table_index.rb +14 -14
- data/lib/clevic/table_model.rb +30 -30
- data/lib/clevic/table_searcher.rb +19 -19
- data/lib/clevic/table_view.rb +92 -92
- data/lib/clevic/table_view_paste.rb +19 -19
- data/lib/clevic/version.rb +1 -1
- data/lib/clevic/view.rb +22 -22
- data/models/accounts_models.rb +10 -10
- data/models/examples.rb +2 -2
- data/models/times_models.rb +32 -32
- data/models/values_models.rb +2 -2
- data/test/test_cache_table.rb +15 -15
- data/test/test_helper.rb +7 -7
- data/test/test_model_index_extensions.rb +6 -6
- data/test/test_table_model.rb +6 -6
- data/test/test_table_searcher.rb +25 -25
- metadata +33 -35
data/lib/clevic/field_valuer.rb
CHANGED
@@ -3,21 +3,21 @@ module Clevic
|
|
3
3
|
# Used for getting values from the entity based on the definitions
|
4
4
|
# in the field.
|
5
5
|
module FieldValuer
|
6
|
-
|
6
|
+
|
7
7
|
# the value for this index
|
8
8
|
# used to be gui_value, but that wasn't right
|
9
9
|
def raw_value
|
10
10
|
field.value_for( entity )
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
def display_value
|
14
14
|
field.do_format( raw_value ) unless raw_value.nil?
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
def edit_value
|
18
18
|
field.do_edit_format( raw_value ) unless raw_value.nil?
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
# Set the value from an editable text representation
|
22
22
|
# of the value
|
23
23
|
def edit_value=( value )
|
@@ -32,13 +32,13 @@ module Clevic
|
|
32
32
|
when [:date,:datetime].include?( field.meta.type ) && value =~ %r{^(\d{1,2})[ /-]?(\w{3})$}
|
33
33
|
today = Date.today
|
34
34
|
date = Date.parse( "#$1 #$2 #{Time.now.year.to_s}" )
|
35
|
-
|
35
|
+
|
36
36
|
# change year to last year
|
37
37
|
if (1..2).include?( today.month ) and (8..12).include?( date.month )
|
38
38
|
date = Date.civil( Date.today.year - 1, date.month, date.day )
|
39
39
|
end
|
40
40
|
date
|
41
|
-
|
41
|
+
|
42
42
|
# if a digit only is entered, fetch month and year from
|
43
43
|
# previous row
|
44
44
|
when [:date,:datetime].include?( field.meta.type ) && value =~ %r{^(\d{1,2})$}
|
@@ -49,19 +49,19 @@ module Clevic
|
|
49
49
|
else
|
50
50
|
value
|
51
51
|
end
|
52
|
-
|
52
|
+
|
53
53
|
# Mostly to fix date strings that have come
|
54
54
|
# out of the db and been formatted
|
55
55
|
# Accepts dd mmm yy.
|
56
56
|
# Assumes 20 as the century.
|
57
57
|
when [:date,:datetime].include?( field.meta.type ) && value =~ %r{^(\d{2})[ /-](\w{3})[ /-](\d{2})$}
|
58
58
|
Date.parse( "#$1 #$2 20#$3" )
|
59
|
-
|
59
|
+
|
60
60
|
# allow lots of flexibility in entering times
|
61
61
|
# 01:17, 0117, 117, 1 17, are all accepted
|
62
62
|
when field.meta.type == :time && value =~ %r{^(\d{1,2}).?(\d{2})$}
|
63
63
|
Time.parse( "#$1:#$2" )
|
64
|
-
|
64
|
+
|
65
65
|
# remove thousand separators, allow for space and comma
|
66
66
|
# instead of . as a decimal separator
|
67
67
|
when field.meta.type == :decimal
|
@@ -74,12 +74,12 @@ module Clevic
|
|
74
74
|
else
|
75
75
|
value
|
76
76
|
end.gsub( ',', '' ) # strip remaining commas
|
77
|
-
|
77
|
+
|
78
78
|
else
|
79
79
|
value
|
80
80
|
end
|
81
81
|
end
|
82
|
-
|
82
|
+
|
83
83
|
def find_related( attribute, value )
|
84
84
|
candidates = field.related_class.filter( attribute.to_sym => value ).all
|
85
85
|
if candidates.size != 1
|
@@ -87,7 +87,7 @@ module Clevic
|
|
87
87
|
end
|
88
88
|
candidates.first
|
89
89
|
end
|
90
|
-
|
90
|
+
|
91
91
|
# set the field value from a value that could be
|
92
92
|
# a text representation a-la edit_value, or possible
|
93
93
|
# a name from a related entity
|
@@ -96,10 +96,10 @@ module Clevic
|
|
96
96
|
when Symbol
|
97
97
|
# we have a related class of some kind,
|
98
98
|
self.attribute_value = find_related( field.display, value )
|
99
|
-
|
99
|
+
|
100
100
|
when NilClass
|
101
101
|
self.edit_value = value
|
102
|
-
|
102
|
+
|
103
103
|
when String
|
104
104
|
# allow plain strings, but not dotted paths
|
105
105
|
if field.display['.']
|
@@ -107,12 +107,12 @@ module Clevic
|
|
107
107
|
else
|
108
108
|
self.attribute_value = find_related( field.display.to_sym, value )
|
109
109
|
end
|
110
|
-
|
110
|
+
|
111
111
|
else
|
112
112
|
raise "display is a not a symbol or nil: #{display.inspect}"
|
113
113
|
end
|
114
114
|
end
|
115
|
-
|
115
|
+
|
116
116
|
# fetch the value of the attribute, without following
|
117
117
|
# the full path. This will return a related entity for
|
118
118
|
# belongs_to or has_one relationships, or a plain value
|
@@ -120,39 +120,39 @@ module Clevic
|
|
120
120
|
def attribute_value
|
121
121
|
entity.send( field.attribute )
|
122
122
|
end
|
123
|
-
|
123
|
+
|
124
124
|
# cache the writer method name since it's not likely to change
|
125
125
|
def writer
|
126
126
|
@writer ||= "#{field.attribute.to_s}="
|
127
127
|
end
|
128
|
-
|
128
|
+
|
129
129
|
# set the value of the attribute, without following the
|
130
130
|
# full path.
|
131
131
|
# TODO remove need to constantly recalculate the attribute writer
|
132
132
|
def attribute_value=( value )
|
133
133
|
entity.send( writer, value )
|
134
134
|
end
|
135
|
-
|
135
|
+
|
136
136
|
def tooltip
|
137
137
|
case
|
138
138
|
# show validation errors
|
139
139
|
when has_errors?
|
140
140
|
errors.join("\n")
|
141
|
-
|
141
|
+
|
142
142
|
# provide a tooltip when an empty relational field is encountered
|
143
143
|
# TODO should be part of field definition
|
144
144
|
when field.meta.type == :association
|
145
145
|
field.delegate.if_empty_message
|
146
|
-
|
146
|
+
|
147
147
|
# read-only field
|
148
148
|
when field.read_only?
|
149
149
|
field.tooltip_for( entity ) || 'Read-only'
|
150
|
-
|
150
|
+
|
151
151
|
else
|
152
152
|
field.tooltip_for( entity )
|
153
153
|
end
|
154
154
|
end
|
155
|
-
|
155
|
+
|
156
156
|
class Valuer
|
157
157
|
include FieldValuer
|
158
158
|
attr_accessor :field, :entity
|
@@ -160,16 +160,16 @@ module Clevic
|
|
160
160
|
@field, @entity = field, entity
|
161
161
|
end
|
162
162
|
end
|
163
|
-
|
163
|
+
|
164
164
|
def valuer( entity )
|
165
165
|
Valuer.new(field,entity)
|
166
166
|
end
|
167
|
-
|
167
|
+
|
168
168
|
def self.valuer( field, entity )
|
169
169
|
Valuer.new(field,entity)
|
170
170
|
end
|
171
171
|
end
|
172
|
-
|
172
|
+
|
173
173
|
# Provides the minimum necessary for valuing, ie for irb testing
|
174
174
|
module SimpleFieldValuer
|
175
175
|
include FieldValuer
|
@@ -7,17 +7,17 @@ module Clevic
|
|
7
7
|
@message = message || 'filtered'
|
8
8
|
@filter_block = filter_block
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
attr_reader :message
|
12
|
-
|
12
|
+
|
13
13
|
# Do the filtering. Return true if successful, false otherwise.
|
14
14
|
def doit
|
15
15
|
# store current dataset
|
16
16
|
@previous_dataset = @table_view.model.cache_table.dataset
|
17
|
-
|
17
|
+
|
18
18
|
# store auto_new
|
19
19
|
@auto_new = @table_view.model.auto_new
|
20
|
-
|
20
|
+
|
21
21
|
# reload cache table with new conditions
|
22
22
|
@table_view.model.auto_new = false
|
23
23
|
@table_view.model.reload_data( &@filter_block )
|
@@ -28,11 +28,11 @@ module Clevic
|
|
28
28
|
puts e.backtrace
|
29
29
|
false
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
def undo
|
33
33
|
# restore auto_new
|
34
34
|
@table_view.model.auto_new = @auto_new
|
35
|
-
|
35
|
+
|
36
36
|
# reload cache table with stored AR conditions
|
37
37
|
@table_view.model.reload_data( @previous_dataset )
|
38
38
|
end
|
data/lib/clevic/framework.rb
CHANGED
@@ -12,7 +12,7 @@
|
|
12
12
|
=end
|
13
13
|
|
14
14
|
class Class
|
15
|
-
|
15
|
+
|
16
16
|
# method_name can be a symbol or a string.
|
17
17
|
#
|
18
18
|
# If a method of this name doesn't already exist
|
@@ -26,7 +26,7 @@ class Class
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
def subclass_responsibility( method_name )
|
31
31
|
unless instance_methods.include?( method_name.to_s )
|
32
32
|
define_method( method_name ) do
|
@@ -34,5 +34,5 @@ class Class
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
37
|
-
|
37
|
+
|
38
38
|
end
|
@@ -22,7 +22,7 @@ module GenericFormat
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
# apply format to value. Use strftime for date_time types, or % for everything else.
|
27
27
|
# If format is a proc, pass value to it.
|
28
28
|
def do_generic_format( format, value )
|
@@ -50,7 +50,7 @@ module GenericFormat
|
|
50
50
|
nil
|
51
51
|
end
|
52
52
|
end
|
53
|
-
|
53
|
+
|
54
54
|
end
|
55
55
|
|
56
56
|
end
|
data/lib/clevic/many_field.rb
CHANGED
@@ -13,15 +13,15 @@ module Clevic
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
def many_builder
|
18
18
|
@many_view.builder
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
def many_fields
|
22
22
|
many_builder.fields
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
# return an instance of Clevic::View that represents the many items
|
26
26
|
# for this field
|
27
27
|
def many_view( &block )
|
data/lib/clevic/model_builder.rb
CHANGED
@@ -14,18 +14,18 @@ module Clevic
|
|
14
14
|
|
15
15
|
== View definition
|
16
16
|
|
17
|
-
Clevic::ModelBuilder defines the DSL used to create a UI definition (which is
|
18
|
-
actually a set of Clevic::Field instances), including any related tables,
|
17
|
+
Clevic::ModelBuilder defines the DSL used to create a UI definition (which is
|
18
|
+
actually a set of Clevic::Field instances), including any related tables,
|
19
19
|
restrictions on data entry, formatting and so on. The intention was to make
|
20
20
|
specifying a UI as painless as possible, with framework overhead only where
|
21
21
|
you need it.
|
22
22
|
|
23
23
|
To that end, there are 2 ways to define UIs:
|
24
24
|
|
25
|
-
- an Embedded View as part of the model (Sequel::Model) object (which is useful if you
|
25
|
+
- an Embedded View as part of the model (Sequel::Model) object (which is useful if you
|
26
26
|
want minimal framework overhead). Just show me the data, dammit.
|
27
27
|
|
28
|
-
- a Separate View in a separate class (which is useful when you want several
|
28
|
+
- a Separate View in a separate class (which is useful when you want several
|
29
29
|
diffent views of the same underlying table). I want a neato-nifty UI that does
|
30
30
|
(relatively) complex things.
|
31
31
|
|
@@ -58,32 +58,32 @@ could be defined like this:
|
|
58
58
|
belongs_to :invoice
|
59
59
|
belongs_to :activity
|
60
60
|
belongs_to :project
|
61
|
-
|
61
|
+
|
62
62
|
include Clevic::Record
|
63
|
-
|
63
|
+
|
64
64
|
# spans of time more than 8 ours are coloured violet
|
65
65
|
# because they're often the result of typos.
|
66
66
|
def time_color
|
67
67
|
return if self.end.nil? || start.nil?
|
68
68
|
'darkviolet' if self.end - start > 8.hours
|
69
69
|
end
|
70
|
-
|
70
|
+
|
71
71
|
# tooltip for spans of time > 8 hours
|
72
72
|
def time_tooltip
|
73
73
|
return if self.end.nil? || start.nil?
|
74
74
|
'Time interval greater than 8 hours' if self.end - start > 8.hours
|
75
75
|
end
|
76
|
-
|
76
|
+
|
77
77
|
define_ui do
|
78
78
|
plain :date, :sample => '28-Dec-08'
|
79
|
-
|
79
|
+
|
80
80
|
# The project field
|
81
81
|
relational :project do |field|
|
82
82
|
field.display = 'project'
|
83
|
-
|
83
|
+
|
84
84
|
# see Sequel::Dataset docs
|
85
85
|
field.dataset.filter( :active => true ).order{ lower(project) }
|
86
|
-
|
86
|
+
|
87
87
|
# handle data changed events. In this case,
|
88
88
|
# auto-fill-in the invoice field.
|
89
89
|
field.notify_data_changed do |entity_view, table_view, model_index|
|
@@ -95,29 +95,29 @@ could be defined like this:
|
|
95
95
|
end
|
96
96
|
end
|
97
97
|
end
|
98
|
-
|
98
|
+
|
99
99
|
relational :invoice, :display => 'invoice_number', :conditions => "status = 'not sent'", :order => 'invoice_number'
|
100
|
-
|
100
|
+
|
101
101
|
# call time_color method for foreground color value
|
102
102
|
plain :start, :foreground => :time_color, :tooltip => :time_tooltip
|
103
|
-
|
103
|
+
|
104
104
|
# another way to call time_color method for foreground color value
|
105
105
|
plain :end, :foreground => lambda{|x| x.time_color}, :tooltip => :time_tooltip
|
106
|
-
|
106
|
+
|
107
107
|
# multiline text
|
108
108
|
text :description, :sample => 'This is a long string designed to hold lots of data and description'
|
109
|
-
|
109
|
+
|
110
110
|
relational :activity do
|
111
111
|
display 'activity'
|
112
112
|
order 'lower(activity)'
|
113
113
|
sample 'Troubleshooting'
|
114
114
|
conditions 'active = true'
|
115
115
|
end
|
116
|
-
|
116
|
+
|
117
117
|
distinct :module, :tooltip => 'Module or sub-project'
|
118
118
|
plain :charge, :tooltip => 'Is this time billable?'
|
119
119
|
distinct :person, :default => 'John', :tooltip => 'The person who did the work'
|
120
|
-
|
120
|
+
|
121
121
|
records :order => 'date, start, id'
|
122
122
|
end
|
123
123
|
|
@@ -125,11 +125,11 @@ could be defined like this:
|
|
125
125
|
action_builder.action :smart_copy, 'Smart Copy', :shortcut => 'Ctrl+"' do
|
126
126
|
smart_copy( view )
|
127
127
|
end
|
128
|
-
|
128
|
+
|
129
129
|
action_builder.action :invoice_from_project, 'Invoice from Project', :shortcut => 'Ctrl+Shift+I' do
|
130
130
|
invoice_from_project( view, view.current_index ) do
|
131
131
|
# execute the block if the invoice is changed
|
132
|
-
|
132
|
+
|
133
133
|
# save this before selection model is cleared
|
134
134
|
current_index = view.current_index
|
135
135
|
view.selection_model.clear
|
@@ -137,35 +137,35 @@ could be defined like this:
|
|
137
137
|
end
|
138
138
|
end
|
139
139
|
end
|
140
|
-
|
140
|
+
|
141
141
|
# do a smart copy from the previous line
|
142
142
|
def self.smart_copy( view )
|
143
143
|
view.sanity_check_read_only
|
144
144
|
view.sanity_check_ditto
|
145
|
-
|
145
|
+
|
146
146
|
# need a reference to current_index here, because selection_model.clear will
|
147
147
|
# invalidate view.current_index. And anyway, its shorter and easier to read.
|
148
148
|
current_index = view.current_index
|
149
149
|
if current_index.row >= 1
|
150
150
|
# fetch previous item
|
151
151
|
previous_item = view.model.collection[current_index.row - 1]
|
152
|
-
|
152
|
+
|
153
153
|
# copy the relevant fields
|
154
154
|
current_index.entity.date = previous_item.date if current_index.entity.date.blank?
|
155
155
|
# depends on previous line
|
156
156
|
current_index.entity.start = previous_item.end if current_index.entity.date == previous_item.date
|
157
|
-
|
157
|
+
|
158
158
|
# copy rest of fields
|
159
159
|
[:project, :invoice, :activity, :module, :charge, :person].each do |attr|
|
160
160
|
current_index.entity.send( "#{attr.to_s}=", previous_item.send( attr ) )
|
161
161
|
end
|
162
|
-
|
162
|
+
|
163
163
|
# tell view to update
|
164
164
|
view.model.data_changed do |change|
|
165
165
|
change.top_left = current_index.choppy( :column => 0 )
|
166
166
|
change.bottom_right = current_index.choppy( :column => view.model.fields.size - 1 )
|
167
167
|
end
|
168
|
-
|
168
|
+
|
169
169
|
# move to the first empty time field
|
170
170
|
next_field =
|
171
171
|
if current_index.entity.start.blank?
|
@@ -173,7 +173,7 @@ could be defined like this:
|
|
173
173
|
else
|
174
174
|
:end
|
175
175
|
end
|
176
|
-
|
176
|
+
|
177
177
|
# next cursor location
|
178
178
|
view.selection_model.clear
|
179
179
|
view.current_index = current_index.choppy( :column => next_field )
|
@@ -190,10 +190,10 @@ could be defined like this:
|
|
190
190
|
unless invoice.nil?
|
191
191
|
# make a reference to the invoice
|
192
192
|
current_index.entity.invoice = invoice
|
193
|
-
|
193
|
+
|
194
194
|
# update view from top_left to bottom_right
|
195
195
|
table_view.model.data_changed( current_index.choppy( :column => :invoice ) )
|
196
|
-
|
196
|
+
|
197
197
|
unless block.nil?
|
198
198
|
if block.arity == 1
|
199
199
|
block.call( invoice )
|
@@ -204,17 +204,17 @@ could be defined like this:
|
|
204
204
|
end
|
205
205
|
end
|
206
206
|
end
|
207
|
-
|
207
|
+
|
208
208
|
end
|
209
209
|
|
210
210
|
== Separate View
|
211
211
|
|
212
212
|
To define a separate ui class, do something like this:
|
213
213
|
class Prospect < Clevic::View
|
214
|
-
|
214
|
+
|
215
215
|
# This is the Sequel::Model descendant
|
216
216
|
entity_class Position
|
217
|
-
|
217
|
+
|
218
218
|
# This must return a ModelBuilder instance, which is made easier
|
219
219
|
# by putting the block in a call to model_builder.
|
220
220
|
#
|
@@ -226,15 +226,15 @@ To define a separate ui class, do something like this:
|
|
226
226
|
model_builder do |mb|
|
227
227
|
# use the define_ui block from Position
|
228
228
|
mb.exec_ui_block( Position )
|
229
|
-
|
229
|
+
|
230
230
|
# any other ModelBuilder code can go here too
|
231
|
-
|
231
|
+
|
232
232
|
# use a different recordset
|
233
233
|
mb.records :conditions => "status in ('prospect','open')", :order => 'date desc,code'
|
234
234
|
end
|
235
235
|
end
|
236
236
|
end
|
237
|
-
|
237
|
+
|
238
238
|
And you can even inherit UIs:
|
239
239
|
|
240
240
|
class Extinct < Prospect
|
@@ -329,13 +329,13 @@ display, and can have optional keyboard shortcuts:
|
|
329
329
|
# view.current_index.entity will return the entity instance.
|
330
330
|
smart_copy( view )
|
331
331
|
end
|
332
|
-
|
332
|
+
|
333
333
|
action_builder.action :invoice_from_project, 'Invoice from Project', :shortcut => 'Ctrl+Shift+I' do
|
334
334
|
# a method in the class containing define_actions
|
335
335
|
invoice_from_project( view.current_index, view )
|
336
336
|
end
|
337
337
|
end
|
338
|
-
|
338
|
+
|
339
339
|
=== Notifications
|
340
340
|
|
341
341
|
Key presses will be sent here:
|
@@ -368,7 +368,7 @@ the order can be accessed in Clevic::View.order, and specified by
|
|
368
368
|
=end
|
369
369
|
|
370
370
|
class ModelBuilder
|
371
|
-
|
371
|
+
|
372
372
|
# Create a definition for entity_view (subclass of Clevic::View).
|
373
373
|
# Then execute block using self.instance_eval.
|
374
374
|
# entity_view must respond to entity_class, and if title is called, it
|
@@ -377,14 +377,13 @@ class ModelBuilder
|
|
377
377
|
@entity_view = entity_view
|
378
378
|
@auto_new = true
|
379
379
|
@read_only = false
|
380
|
-
|
381
|
-
@fields = OrderedHash.new
|
380
|
+
@fields = Hash.new
|
382
381
|
exec_ui_block( &block )
|
383
382
|
end
|
384
|
-
|
383
|
+
|
385
384
|
attr_accessor :entity_view
|
386
385
|
attr_accessor :find_options
|
387
|
-
|
386
|
+
|
388
387
|
# execute a block containing method calls understood by Clevic::ModelBuilder
|
389
388
|
# arg can be something that responds to define_ui_block,
|
390
389
|
# or just the block will be executed. If both are present,
|
@@ -405,92 +404,96 @@ class ModelBuilder
|
|
405
404
|
end
|
406
405
|
self
|
407
406
|
end
|
408
|
-
|
407
|
+
|
409
408
|
# The collection of Clevic::Field instances where visible == true.
|
410
409
|
# the visible may go away.
|
411
410
|
def fields
|
412
411
|
#~ @fields.reject{|id,field| !field.visible}
|
413
412
|
@fields
|
414
413
|
end
|
415
|
-
|
414
|
+
|
416
415
|
# return the index of the named field in the collection of fields.
|
417
416
|
def index( field_name_sym )
|
418
417
|
retval = nil
|
419
418
|
fields.each_with_index{|id,field,i| retval = i if field.attribute == field_name_sym.to_sym }
|
420
419
|
retval
|
421
420
|
end
|
422
|
-
|
421
|
+
|
423
422
|
# The ORM class
|
424
423
|
def entity_class
|
425
424
|
@entity_view.entity_class
|
426
425
|
end
|
427
|
-
|
426
|
+
|
428
427
|
# set read_only to true
|
429
428
|
def read_only!
|
430
429
|
@read_only = true
|
431
430
|
end
|
432
|
-
|
431
|
+
|
433
432
|
# should this table automatically show a new blank record?
|
434
433
|
def auto_new( bool )
|
435
434
|
@auto_new = bool
|
436
435
|
end
|
437
|
-
|
436
|
+
|
438
437
|
# should this table automatically show a new blank record?
|
439
438
|
def auto_new?; @auto_new; end
|
440
|
-
|
439
|
+
|
441
440
|
# DSL for changing the title
|
442
441
|
def title( value )
|
443
442
|
entity_view.title = value
|
444
443
|
end
|
445
444
|
|
445
|
+
def add_field( field )
|
446
|
+
fields[field.id || field.attribute] = field
|
447
|
+
end
|
448
|
+
|
446
449
|
# an ordinary field, edited in place with a text box
|
447
450
|
def plain( attribute, options = {}, &block )
|
448
451
|
read_only_default!( attribute, options )
|
449
452
|
field = Clevic::Field.new( attribute.to_sym, entity_class, options, &block )
|
450
|
-
|
453
|
+
|
451
454
|
# plain_delegate will be defined in a framework-specific file.
|
452
455
|
# This is becoming a kind of poor man's inheritance. I don't
|
453
456
|
# think I like that.
|
454
457
|
field.delegate = plain_delegate( field )
|
455
|
-
|
458
|
+
add_field field
|
456
459
|
end
|
457
|
-
|
460
|
+
|
458
461
|
# an ordinary field like plain, except that a larger edit area can be used
|
459
462
|
def text( attribute, options = {}, &block )
|
460
463
|
read_only_default!( attribute, options )
|
461
464
|
field = Clevic::Field.new( attribute.to_sym, entity_class, options, &block )
|
462
465
|
field.delegate = TextAreaDelegate.new( field )
|
463
|
-
|
466
|
+
add_field field
|
464
467
|
end
|
465
|
-
|
468
|
+
|
466
469
|
# Returns a Clevic::Field with a DistinctDelegate, in other words
|
467
470
|
# a combo box containing all values for this field from the table.
|
468
471
|
def distinct( attribute, options = {}, &block )
|
469
472
|
field = Clevic::Field.new( attribute.to_sym, entity_class, options, &block )
|
470
473
|
field.delegate = DistinctDelegate.new( field )
|
471
|
-
|
474
|
+
add_field field
|
472
475
|
end
|
473
|
-
|
476
|
+
|
474
477
|
# a combo box with a set of supplied values
|
475
478
|
def combo( attribute, options = {}, &block )
|
476
479
|
field = Clevic::Field.new( attribute.to_sym, entity_class, options, &block )
|
477
|
-
|
480
|
+
|
478
481
|
# TODO this really belongs in a separate 'map' field?
|
479
482
|
# or maybe put it in SetDelegate?
|
480
483
|
if field.set.is_a? Hash
|
481
484
|
field.format ||= lambda{|x| field.set[x]}
|
482
485
|
end
|
483
|
-
|
486
|
+
|
484
487
|
field.delegate = SetDelegate.new( field )
|
485
|
-
|
488
|
+
add_field field
|
486
489
|
end
|
487
490
|
|
488
|
-
# Returns a Clevic::Field with a restricted SetDelegate,
|
491
|
+
# Returns a Clevic::Field with a restricted SetDelegate,
|
489
492
|
def restricted( attribute, options = {}, &block )
|
490
493
|
options[:restricted] = true
|
491
494
|
combo( attribute, options, &block )
|
492
495
|
end
|
493
|
-
|
496
|
+
|
494
497
|
# For many_to_one relationships.
|
495
498
|
# Edited with a combo box using values from the specified
|
496
499
|
# path on the foreign key model object
|
@@ -499,34 +502,35 @@ class ModelBuilder
|
|
499
502
|
def relational( attribute, options = {}, &block )
|
500
503
|
field = Clevic::Field.new( attribute.to_sym, entity_class, options, &block )
|
501
504
|
field.delegate = RelationalDelegate.new( field )
|
502
|
-
|
505
|
+
add_field field
|
503
506
|
end
|
504
|
-
|
507
|
+
|
505
508
|
def tags( attribute, options = {}, &block )
|
506
509
|
field = Clevic::Field.new( attribute.to_sym, entity_class, options, &block )
|
507
|
-
|
510
|
+
|
508
511
|
# build a collection setter if necessary
|
509
512
|
unless entity_class.instance_methods.include? "#{attribute}="
|
510
513
|
raise NotImplementedError, "Need to build a collection setter for '#{attribute}='"
|
511
514
|
end
|
512
|
-
|
515
|
+
|
513
516
|
field.delegate = TagDelegate.new( field )
|
514
|
-
|
517
|
+
add_field field
|
515
518
|
end
|
516
519
|
|
517
520
|
# force a checkbox
|
518
521
|
def check( attribute, options = {}, &block )
|
519
522
|
read_only_default!( attribute, options )
|
520
|
-
field =
|
523
|
+
field = Clevic::Field.new( attribute.to_sym, entity_class, options, &block )
|
521
524
|
field.delegate = BooleanDelegate.new( field )
|
525
|
+
add_field field
|
522
526
|
end
|
523
|
-
|
527
|
+
|
524
528
|
# specify the dataset but just calling and chaining, thusly
|
525
529
|
# dataset.order( :some_field ).filter( :active => true )
|
526
530
|
def dataset
|
527
531
|
@dataset_roller = DatasetRoller.new( entity_class.dataset )
|
528
532
|
end
|
529
|
-
|
533
|
+
|
530
534
|
def records( *args )
|
531
535
|
puts "ModelBuilder#records is deprecated. Use ModelBuilder#dataset instead"
|
532
536
|
require 'clevic/sequel_ar_adapter.rb'
|
@@ -565,7 +569,7 @@ class ModelBuilder
|
|
565
569
|
# don't create an empty record, because sometimes there are
|
566
570
|
# validations that will cause trouble
|
567
571
|
auto_new false
|
568
|
-
|
572
|
+
|
569
573
|
# build columns
|
570
574
|
entity_class.attributes.each do |column,model_column|
|
571
575
|
begin
|
@@ -591,12 +595,12 @@ class ModelBuilder
|
|
591
595
|
end
|
592
596
|
end
|
593
597
|
end
|
594
|
-
|
598
|
+
|
595
599
|
# return the named Clevic::Field object
|
596
600
|
def field( attribute )
|
597
|
-
|
601
|
+
fields.find {|id,field| field.attribute == attribute }
|
598
602
|
end
|
599
|
-
|
603
|
+
|
600
604
|
# This takes all the information collected
|
601
605
|
# by the other methods, and returns a new TableModel
|
602
606
|
# with the given parent (usually a TableView) as its parent.
|
@@ -607,49 +611,49 @@ class ModelBuilder
|
|
607
611
|
@model = Clevic::TableModel.new
|
608
612
|
@model.builder = self
|
609
613
|
@model.entity_view = entity_view
|
610
|
-
@model.fields =
|
614
|
+
@model.fields = fields.values
|
611
615
|
@model.read_only = @read_only
|
612
616
|
@model.auto_new = auto_new?
|
613
|
-
|
617
|
+
|
614
618
|
# set view name
|
615
619
|
parent.object_name = @object_name if parent.respond_to? :object_name
|
616
|
-
|
620
|
+
|
617
621
|
# set UI parent for all delegates
|
618
622
|
# and model for each field
|
619
623
|
fields.each do |id,field|
|
620
624
|
field.delegate.parent = parent unless field.delegate.nil?
|
621
625
|
field.model = @model
|
622
626
|
end
|
623
|
-
|
627
|
+
|
624
628
|
# the data
|
625
629
|
@model.collection = create_cache_table
|
626
|
-
|
630
|
+
|
627
631
|
@model
|
628
632
|
end
|
629
|
-
|
633
|
+
|
630
634
|
protected
|
631
635
|
|
632
636
|
# set a sensible read-only value if it isn't already specified in options
|
633
637
|
def read_only_default!( attribute, options )
|
634
638
|
# sensible defaults for read-only-ness
|
635
|
-
options[:read_only] ||=
|
639
|
+
options[:read_only] ||=
|
636
640
|
case
|
637
641
|
when options[:display].respond_to?( :call )
|
638
642
|
# it's a Proc or a Method, so we can't set it
|
639
643
|
true
|
640
|
-
|
644
|
+
|
641
645
|
when entity_class.column_names.include?( options[:display].to_s )
|
642
646
|
# it's a DB column, so it's not read only
|
643
647
|
false
|
644
|
-
|
648
|
+
|
645
649
|
when entity_class.reflections.include?( attribute )
|
646
650
|
# one-to-one relationships can be edited. many-to-one certainly can't
|
647
651
|
entity_class.meta[attribute].type != :many_to_one
|
648
|
-
|
652
|
+
|
649
653
|
when entity_class.method_defined?( attribute )
|
650
654
|
# read-only if there's no setter for the attribute
|
651
655
|
!entity_class.method_defined?( "#{attribute.to_s}=" )
|
652
|
-
|
656
|
+
|
653
657
|
else
|
654
658
|
# default to not read-only
|
655
659
|
false
|