File Coverage

blib/lib/RapidApp/TableSpec/Column/Profile.pm
Criterion Covered Total %
statement 29 29 100.0
branch 9 14 64.2
condition 4 12 33.3
subroutine 7 7 100.0
pod 0 3 0.0
total 49 65 75.3


line stmt bran cond sub pod time code
1             package RapidApp::TableSpec::Column::Profile;
2              
3 5     5   30 use strict;
  5         9  
  5         127  
4 5     5   22 use warnings;
  5         10  
  5         255  
5              
6             require Exporter;
7             our @ISA = qw(Exporter);
8             our @EXPORT_OK = qw(get_set);
9              
10 5     5   26 use RapidApp::Util qw(:all);
  5         11  
  5         5929  
11              
12             # Base profiles are applied to all columns
13             sub DEFAULT_BASE_PROFILES {(
14 2188     2188 0 6437 'BASE'
15             )}
16              
17             ##### ---------------------------------------
18             ## THESE ARE NOW IN THE CLIENT-SIDE: Ext.ux.RapidApp.Plugin.AppGridSummary
19             #our @number_summary_funcs = (
20             # { function => 'sum', title => 'Total' },
21             # { function => 'max', title => 'Max Val' },
22             # { function => 'min', title => 'Min Val' },
23             # { function => 'count(distinct({x}))', title => 'Count Unique' },
24             # { function => 'count', title => 'Count (Set)' },
25             #);
26             #
27             #our @text_summary_funcs = (
28             # { function => 'count(distinct({x}))', title => 'Count Unique' },
29             # { function => 'count', title => 'Count (Set)' },
30             # #{ function => 'max(length({x})', title => 'Longest' },
31             #);
32             #
33             #our @date_summary_funcs = (
34             # @number_summary_funcs,
35             # @text_summary_funcs,
36             # #{ function => 'CONCAT(DATEDIFF(NOW(),avg({x})),\' days\')', title => 'Ave Age (days)' }, #<-- doesn't work
37             # { function => 'CONCAT(DATEDIFF(NOW(),min({x})),\' days\')', title => 'Oldest (days)' },
38             # { function => 'CONCAT(DATEDIFF(NOW(),max({x})),\' days\')', title => 'Youngest (days)' },
39             # { function => 'CONCAT(DATEDIFF(max({x}),min({x})),\' days\')', title => 'Age Range (days)' }
40             #);
41             #
42             #push @number_summary_funcs, (
43             # { function => 'round(avg({x}),2)', title => 'Average' },
44             #);
45             ##### ---------------------------------------
46              
47             # Default named column profiles. Column properties will be merged
48             # with the definitions below if supplied by name in the property 'profiles'
49             sub DEFAULT_PROFILES {{
50            
51 50     50 0 5136 BASE => {
52             broad_data_type => 'text',
53             is_nullable => 1, #<-- initial/default
54             renderer => ['Ext.ux.showNull'] ,
55             editor => { xtype => 'textfield', minWidth => 80, minHeight => 22 },
56             summary_functions => 'text_summary_funcs'
57             },
58            
59             relcol => {
60             width => 175
61             },
62            
63             nullable => {
64             is_nullable => 1, #<-- redundant/default
65             editor => { allowBlank => \1, plugins => [ 'emptytonull' ] }
66             },
67            
68             notnull => {
69             is_nullable => 0,
70             editor => { allowBlank => \0, plugins => [ 'nulltoempty' ] }
71             },
72            
73             number => {
74             broad_data_type => 'number',
75             editor => { xtype => 'numberfield', style => 'text-align:left;' },
76             multifilter_type => 'number',
77             summary_functions => 'number_summary_funcs'
78             },
79             int => {
80             broad_data_type => 'integer',
81             editor => { xtype => 'numberfield', style => 'text-align:left;', allowDecimals => \0 },
82             },
83            
84             bool => {
85             menu_select_editor => {
86            
87             #mode: 'combo', 'menu' or 'cycle':
88             mode => 'menu',
89            
90             render_icon_only => 1,
91            
92             selections => [
93             {
94             iconCls => "ra-icon-cross-light-12x12",
95             #iconCls => "ra-icon-cross-tiny",
96             text => 'No',
97             value => 0
98             },
99             {
100             iconCls => "ra-icon-checkmark-12x12",
101             #iconCls => "ra-icon-tick-tiny",
102             text => 'Yes',
103             value => 1
104             }
105             ]
106             },
107            
108             # piggy-back on the existing quick-search pre-validation for
109             # enum columns -- Github issue #60
110             enum_value_hash => { '0'=>1,'1'=>1 },
111            
112             multifilter_type => 'bool'
113             },
114            
115             bool_old => {
116             # Renderer *not* in arrayref makes it replace instead of append previous
117             # profiles with th renderer property as an arrayref
118             renderer => 'Ext.ux.RapidApp.boolCheckMark',
119             xtype => 'booleancolumn',
120             #trueText => '1',
121             #falseText => '0',
122             editor => { xtype => 'checkbox' }
123             #editor => { xtype => 'logical-checkbox', plugins => [ 'booltoint' ] }
124             },
125             text => {
126             width => 100,
127             editor => { xtype => 'textfield', grow => \0 },
128             summary_functions => 'text_summary_funcs'
129             },
130             bigtext => {
131             width => 150,
132             renderer => ['Ext.ux.RapidApp.nl2brWrap'],
133             editor => { xtype => 'textarea', grow => \1 },
134             summary_functions => 'text_summary_funcs'
135             },
136             monotext => {
137             width => 150,
138             renderer => ['Ext.ux.RapidApp.renderMonoText'],
139             editor => { xtype => 'textarea', grow => \1 },
140             summary_functions => 'text_summary_funcs'
141             },
142             blob => {
143             width => 130,
144             renderer => 'Ext.ux.RapidApp.renderHex',
145             # Here we have a (simple) hex editor which works, however, we're still disabling
146             # editing out of the gate as the default because there are so many
147             # possible scenarios for binary data, and in most cases hex editing isn't
148             # useful. However, we still have this as the editor, so that if the user
149             # *does* want to edit, and manually sets allow_edit to true, the default editor
150             # be a sane choice (hex) which will match the default renderer.
151             editor => { xtype => 'ra-hexfield', grow => \1 },
152             allow_edit => \0,
153             broad_data_type => 'integer'
154             },
155             html => {
156             width => 200,
157             # We need this renderer in case the 'bigtext' profile above has been applied
158             # automatically. For HTML we *don't* want to nl2br() as it will totally break markup
159             renderer => 'Ext.ux.showNull',
160             editor => {
161             xtype => 'ra-htmleditor',
162             resizable => \1, #<-- Specific to Ext.ux.RapidApp.HtmlEditor ('ra-htmleditor')
163             #height => 200,
164             minHeight => 200,
165             minWidth => 400,
166             anchor => '-25',
167             },
168             },
169             markdown => {
170             width => 250,
171             renderer => 'Ext.ux.RapidApp.renderSourceCodeLineNumbers', # this cannot be "chained" with []
172             editor => {
173             xtype => 'ra-md-editor',
174             },
175             },
176             email => {
177             width => 100,
178             editor => { xtype => 'textfield', vtype => 'email' },
179             summary_functions => 'text_summary_funcs',
180             },
181             datetime => {
182             # We now disable quick search by default for datetime/date columns because
183             # it is more trouble than it is worth. Very rarely would it actually be useful,
184             # since the user can still use MultiFilter where they can do things like
185             # a relative search. Also, certain databases (PostgreSQL) throw exceptions
186             # when querying a datetime column with an invalid datetime string, so, properly
187             # supporting this will require server-side validation, which mary vary from
188             # backend to backend, etc. TODO: we may look at adding this support in the
189             # future using DBIx::Introspector.
190             # Note: the user is still free to manually change 'no_quick_search' if they
191             # really want it - this is just the default...
192             no_quick_search => \1,
193             editor => {
194             xtype => 'xdatetime2',
195             dateFormat => 'Y-m-d',
196             timeFormat => 'H:i', # default 'g:i A'
197             timeWidth => 60,
198             dateConfig => { plugins => ['form-relative-datetime'] },
199             minWidth => 200,
200             #editable => \0 #<-- force whole-field click/select
201             },
202             width => 130,
203             renderer => ["Ext.ux.RapidApp.getDateFormatter('M d, Y g:i A')"],
204             multifilter_type => 'datetime',
205             summary_functions => 'date_summary_funcs'
206             },
207             date => {
208             # See comment above in the datetime section...
209             no_quick_search => \1,
210             editor => {
211             xtype => 'datefield',
212             format => 'Y-m-d',
213             plugins => ['form-relative-datetime'],
214             minWidth => 120,
215             #editable => \0 #<-- force whole-field click/select
216             },
217             width => 80,
218             renderer => ["Ext.ux.RapidApp.getDateFormatter('M d, Y')"],
219             multifilter_type => 'date',
220             summary_functions => 'date_summary_funcs'
221             },
222             otherdate => {
223             # for other general 'date' columns that we have no special handling for yet,
224             # like 'year' in postgres
225             no_quick_search => \1,
226             },
227             money => {
228             editor => { xtype => 'numberfield', style => 'text-align:left;', decimalPrecision => 2 },
229             renderer => 'Ext.ux.showNullusMoney',
230             summary_functions => 'number_summary_funcs'
231             },
232             percent => {
233             editor => { xtype => 'numberfield', style => 'text-align:left;' },
234             renderer => ['Ext.ux.RapidApp.num2pct'],
235             summary_functions => 'number_summary_funcs'
236             },
237             noadd => {
238             allow_add => \0,
239             },
240             noedit => {
241             editor => '',
242             allow_edit => \0,
243             allow_batchedit => \0
244             },
245             zipcode => {
246             editor => { vtype => 'zipcode' }
247             },
248             filesize => {
249             renderer => 'Ext.util.Format.fileSize',
250             },
251             autoinc => {
252             allow_add => \0,
253             allow_edit => \0,
254             allow_batchedit => \0
255             },
256             img_blob => {
257             width => 120,
258             renderer => "Ext.ux.RapidApp.getEmbeddedImgRenderer()"
259             },
260             virtual_source => {
261             allow_add => \0,
262             allow_edit => \0,
263             allow_batchedit => \0
264             },
265             unsearchable => {
266             # This profile is for data types for which we do not yet properly support searching on,
267             # like PostgreSQL 'tsvector' and array columns...
268             no_quick_search => \1,
269             no_multifilter => \1
270             },
271             cas_link => {
272             editor => { xtype => 'cas-upload-field' },
273             renderer => 'Ext.ux.RapidApp.renderCasLink'
274             },
275             cas_img => {
276             editor => {
277             xtype => 'cas-image-field',
278             simple_value => 1 #<-- needed for back-compat
279             },
280             renderer => 'Ext.ux.RapidApp.renderCasImg'
281             },
282             soft_rel => {
283             # This currently applies only to single rels pointing at sources
284             # auto_editor_type set to 'combo' or 'dropdown'
285             auto_editor_params => { user_editable => 1 }
286             },
287             hidden => {
288             no_column => \1,
289             allow_add => \0,
290             allow_edit => \0,
291             allow_batchedit => \0,
292             no_quick_search => \1,
293             no_multifilter => \1
294             },
295             multirel => {
296             multifilter_type => 'number',
297             no_quick_search => \1,
298             editor => '',
299             summary_functions => 'number_summary_funcs'
300             }
301              
302             }};
303              
304             our %NO_ALLOW_ADD_PROFILES = ();
305             our %NO_ALLOW_EDIT_PROFILES = ();
306             {
307             my $Defs = &DEFAULT_PROFILES;
308             for my $prof (keys %$Defs) {
309             my $p = $Defs->{$prof};
310             my ($a,$e) = ($p->{allow_add},$p->{allow_edit});
311             if(exists $p->{allow_add}) {
312             $a = $$a if (ref($a));
313             $NO_ALLOW_ADD_PROFILES{$prof} = 1 if ("$a" eq '0');
314             }
315             if(exists $p->{allow_edit}) {
316             $e = $$e if (ref($e));
317             $NO_ALLOW_EDIT_PROFILES{$prof} = 1 if ("$e" eq '0');
318             }
319             }
320             }
321              
322              
323             our $SKIP_BASE = 0;
324              
325             # Cache collapsed profile sets process-wide for performance:
326             my %Sets = ();
327             sub get_set {
328 2290 100   2290 0 5971 my @profiles = $SKIP_BASE ? uniq(@_) : uniq(&DEFAULT_BASE_PROFILES(),@_);
329 2290         5878 my $key = join('|',@profiles);
330 2290 100       5700 unless (exists $Sets{$key}) {
331 45         113 my $profile_defs = &DEFAULT_PROFILES();
332 45         135 my $collapsed = {};
333 45         116 foreach my $profile (@profiles) {
334 153 50       369 my $opt = $profile_defs->{$profile} or next;
335 153         360 $collapsed = merge($collapsed,$opt);
336             }
337 45         928 $Sets{$key} = $collapsed;
338             }
339 2290         6929 return $Sets{$key};
340             }
341              
342             # One-off function to apply profiles to an arbitrary hashref w/o considering
343             # the base profile(s) and w/o overriding any existing params. This was added
344             # for GitHub #77 -- see special invocation in RapidApp::TableSpec::Role::DBIC
345             sub _apply_profiles_soft {
346 102 50 33 102   669 shift if ($_[0] && $_[0] eq __PACKAGE__); #<-- support calling as class method
347 102         268 my ($cnf,@profiles) = @_;
348            
349 102 50 33     585 die "apply_profiles(): first argument must be a HashRef" unless ($cnf && ref($cnf) eq 'HASH');
350            
351 102         367 @profiles = @{$cnf->{profiles}} if (
352             scalar(@profiles) == 0
353             && $cnf->{profiles}
354 102 50 33     818 && ref($cnf->{profiles}) eq 'ARRAY'
      33        
355             );
356            
357 102 50       368 die "No profiles supplied" unless (scalar(@profiles) > 0);
358            
359 102         293 local $SKIP_BASE = 1;
360 102         329 $cnf = merge( clone(&get_set(@profiles)) ,$cnf)
361             }
362              
363              
364             1;
365              
366              
367             __END__
368              
369             =head1 NAME
370              
371             RapidApp::TableSpec::Column::Profile - TableSpec Column Profile Definitions
372              
373             =head1 DESCRIPTION
374              
375             This class conatins the TableSpec column profile defintions. This class is used
376             internally and should not be called directly. See L<RapidApp::Manual::TableSpec>
377             for more info
378              
379             =head1 SEE ALSO
380              
381             =over
382              
383             =item *
384              
385             L<RapidApp::Manual::TableSpec>
386              
387             =back
388              
389             =head1 AUTHOR
390              
391             Henry Van Styn <vanstyn@cpan.org>
392              
393             =head1 COPYRIGHT AND LICENSE
394              
395             This software is copyright (c) 2013 by IntelliTree Solutions llc.
396              
397             This is free software; you can redistribute it and/or modify it under
398             the same terms as the Perl 5 programming language system itself.
399              
400             =cut