clevic 0.13.0.b9 → 0.13.0.b10
Sign up to get free protection for your applications and to get access to all the features.
- 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
|