line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
=head1 NAME |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
Mojolicious::Plugin::Toto - A simple tab and object based site structure |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
=head1 SYNOPSIS |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
#!/usr/bin/env perl |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
use Mojolicious::Lite; |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
plugin 'toto' => |
12
|
|
|
|
|
|
|
nav => [ qw{brewery pub beer} ], |
13
|
|
|
|
|
|
|
sidebar => { |
14
|
|
|
|
|
|
|
brewery => [ qw{brewery/list brewery/search brewery} ], |
15
|
|
|
|
|
|
|
pub => [ qw{pub/list pub/search pub} ], |
16
|
|
|
|
|
|
|
beer => [ qw{beer/list beer/search beer} ], |
17
|
|
|
|
|
|
|
}, |
18
|
|
|
|
|
|
|
tabs => { |
19
|
|
|
|
|
|
|
brewery => [qw/view edit delete/], |
20
|
|
|
|
|
|
|
pub => [qw/view edit delete/], |
21
|
|
|
|
|
|
|
beer => [qw/view edit delete/], |
22
|
|
|
|
|
|
|
}; |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
app->start; |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
=head1 DESCRIPTION |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
This plugin provides a navigational structure and a default set |
29
|
|
|
|
|
|
|
of routes for a Mojolicious or Mojolicious::Lite app |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
The navigational structure is a slight variation of |
32
|
|
|
|
|
|
|
L |
33
|
|
|
|
|
|
|
example used by twitter's L. |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
The plugin provides a sidebar, a nav bar, and also a |
36
|
|
|
|
|
|
|
row of tabs underneath the name of an object. |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
The row of tabs is an extension of BREAD or CRUD -- in a BREAD |
39
|
|
|
|
|
|
|
application, browse and add are operations on zero or many objects, |
40
|
|
|
|
|
|
|
while edit, add, and delete are operations on one object. In |
41
|
|
|
|
|
|
|
the toto structure, these two types of operations are distinguished |
42
|
|
|
|
|
|
|
by placing the former in the side nav bar, and the latter in |
43
|
|
|
|
|
|
|
a row of tabs underneath the object to which the action applies. |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
Additionally, a top nav bar contains menu items to take the user |
46
|
|
|
|
|
|
|
to a particular side bar. |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
=head1 HOW DOES IT WORK |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
After loading the toto plugin, the default layout is set to 'toto'. |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
Defaults routes are generated for every sidebar entry and tab entry. |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
The names of the routes are of the form "controller/action", where |
55
|
|
|
|
|
|
|
controller is both the controller class and the model class. |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
The following templates will be automagically used, if found |
58
|
|
|
|
|
|
|
(in order of preference) : |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
- templates///.html.ep |
61
|
|
|
|
|
|
|
- templates//.html.ep |
62
|
|
|
|
|
|
|
- templates/.html.ep |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
Or if no object is selected : |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
- templates//none_selected.html.ep |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
(This one links connects to "list" and "search" if |
69
|
|
|
|
|
|
|
these routes exist, and provides an autocomplete |
70
|
|
|
|
|
|
|
form if the model class has an autocomplete() method.) |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
Also the templates "single" and "plural" are built-in |
73
|
|
|
|
|
|
|
fallbacks for the two cases described above. |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
The stash values "object" and "tab" are set for each auto-generated route. |
76
|
|
|
|
|
|
|
Also "noun" is set as an alias to "object". |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
A version of twitter's L is |
79
|
|
|
|
|
|
|
included in this distribution. |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
=head1 OPTIONS |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
In addition to "menu", "nav/sidebar/tabs", the following options are recognized : |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
=over |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
=item prefix |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
prefix => /my/subpath |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
A prefix to prepend to the path for the toto routes. |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
=item head_route |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
head_route => $app->routes->find('top_route"); |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
A Mojolicious::Route::Route object to use as the parent for all routes. |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
=item model_namespace |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
model_namespace => "Myapp::Model' |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
A namespace for model classes : the model class will be camelized and appended to this. |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
=back |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
=head1 EXAMPLE |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
There are two different structures that toto will accept. |
110
|
|
|
|
|
|
|
One is intended for a simple CRUD structure, where each |
111
|
|
|
|
|
|
|
object has its own top level navigational item and |
112
|
|
|
|
|
|
|
a variety of possible actions. The other form is intended |
113
|
|
|
|
|
|
|
for a more complex situation in which the list of objects |
114
|
|
|
|
|
|
|
does not correspond to the list of choices in the navigation |
115
|
|
|
|
|
|
|
bar. |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
=head2 Simple structure |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
The "menu" format can be used to automatically generate |
120
|
|
|
|
|
|
|
the nav bar, side bar and rows of tabs, using actions |
121
|
|
|
|
|
|
|
which correspond to many objects or actions which |
122
|
|
|
|
|
|
|
correspond to one object. |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
#!/usr/bin/env perl |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
use Mojolicious::Lite; |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
plugin 'toto' => |
129
|
|
|
|
|
|
|
menu => [ |
130
|
|
|
|
|
|
|
beer => { |
131
|
|
|
|
|
|
|
many => [qw/search browse/], |
132
|
|
|
|
|
|
|
one => [qw/picture ingredients pubs/], |
133
|
|
|
|
|
|
|
}, |
134
|
|
|
|
|
|
|
pub => { |
135
|
|
|
|
|
|
|
many => [qw/map list search/], |
136
|
|
|
|
|
|
|
one => [qw/info comments/], |
137
|
|
|
|
|
|
|
} |
138
|
|
|
|
|
|
|
]; |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
app->start; |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
=head2 Complex structure |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
The "nav/sidebar/tabs" format can be used |
145
|
|
|
|
|
|
|
for a more versatile structure, in which the |
146
|
|
|
|
|
|
|
nav bar and side bar are less constrained. |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
use Mojolicious::Lite; |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
get '/my/url/to/list/beers' => sub { |
151
|
|
|
|
|
|
|
shift->render_text("Here is a page for listing beers."); |
152
|
|
|
|
|
|
|
} => "beer/list"; |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
get '/beer/create' => sub { |
155
|
|
|
|
|
|
|
shift->render_text("Here is a page to create a beer."); |
156
|
|
|
|
|
|
|
} => "beer/create"; |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
plugin 'toto' => |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
# top nav bar items |
161
|
|
|
|
|
|
|
nav => [ |
162
|
|
|
|
|
|
|
'brewpub', # Refers to a sidebar entry below |
163
|
|
|
|
|
|
|
'beverage' # Refers to a sidebar entry below |
164
|
|
|
|
|
|
|
], |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
# possible sidebars, keyed on nav entries |
167
|
|
|
|
|
|
|
sidebar => { |
168
|
|
|
|
|
|
|
brewpub => [ |
169
|
|
|
|
|
|
|
'brewery/phonelist', |
170
|
|
|
|
|
|
|
'brewery/mailing_list', |
171
|
|
|
|
|
|
|
'pub/search', |
172
|
|
|
|
|
|
|
'pub/map', |
173
|
|
|
|
|
|
|
'brewery', # Refers to a "tab" entry below |
174
|
|
|
|
|
|
|
'pub', # Refers to a "tab" entry below |
175
|
|
|
|
|
|
|
], |
176
|
|
|
|
|
|
|
beverage => |
177
|
|
|
|
|
|
|
[ 'beer/list', # This will use the route defined above named "beer/list" |
178
|
|
|
|
|
|
|
'beer/create', |
179
|
|
|
|
|
|
|
'beer/search', |
180
|
|
|
|
|
|
|
'beer/browse', # This will use the controller at the top (Beer::browse) |
181
|
|
|
|
|
|
|
'beer' # Refers to a "tab" entry below |
182
|
|
|
|
|
|
|
], |
183
|
|
|
|
|
|
|
}, |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
# possible rows of tabs, keyed on sidebar entries without a / |
186
|
|
|
|
|
|
|
tabs => { |
187
|
|
|
|
|
|
|
brewery => [ 'view', 'edit', 'directions', 'beers', 'info' ], |
188
|
|
|
|
|
|
|
pub => [ 'view', 'info', 'comments', 'hours' ], |
189
|
|
|
|
|
|
|
beer => [ 'view', 'edit', 'pictures', 'notes' ], |
190
|
|
|
|
|
|
|
}; |
191
|
|
|
|
|
|
|
; |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
app->start; |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
=head1 NOTES |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
To create pages outside of the toto framework, just set the layout to |
199
|
|
|
|
|
|
|
something other than "toto', e.g. |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
get '/no/toto' => { layout => 'default' } => ... |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
This module is experimental. The API may change without notice. Feedback is welcome! |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
=head1 TODO |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
Document the autcomplete API. |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
=head1 AUTHOR |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
Brian Duggan C |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
=cut |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
package Mojolicious::Plugin::Toto; |
216
|
4
|
|
|
4
|
|
8475
|
use Mojo::Base 'Mojolicious::Plugin'; |
|
4
|
|
|
|
|
11
|
|
|
4
|
|
|
|
|
35
|
|
217
|
4
|
|
|
4
|
|
950
|
use Mojo::ByteStream qw/b/; |
|
4
|
|
|
|
|
10
|
|
|
4
|
|
|
|
|
288
|
|
218
|
4
|
|
|
4
|
|
37
|
use File::Basename 'dirname'; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
239
|
|
219
|
4
|
|
|
4
|
|
22
|
use File::Spec::Functions 'catdir'; |
|
4
|
|
|
|
|
25
|
|
|
4
|
|
|
|
|
222
|
|
220
|
4
|
|
|
4
|
|
2681
|
use Mojolicious::Plugin::Toto::Model; |
|
4
|
|
|
|
|
12
|
|
|
4
|
|
|
|
|
31
|
|
221
|
4
|
|
|
4
|
|
121
|
use Cwd qw/abs_path/; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
205
|
|
222
|
|
|
|
|
|
|
|
223
|
4
|
|
|
4
|
|
20
|
use strict; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
120
|
|
224
|
4
|
|
|
4
|
|
18
|
use warnings; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
18807
|
|
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
our $VERSION = "0.25"; |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
sub _render_static { |
229
|
0
|
|
|
0
|
|
0
|
my $c = shift; |
230
|
0
|
|
|
|
|
0
|
my $what = shift; |
231
|
0
|
|
|
|
|
0
|
$c->render_static($what); |
232
|
|
|
|
|
|
|
} |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
sub _cando { |
235
|
34
|
|
|
34
|
|
71
|
my ($namespace,$controller,$action) = @_; |
236
|
34
|
|
66
|
|
|
214
|
my $package = join '::', ( $namespace || () ), b($controller)->camelize; |
237
|
34
|
100
|
|
|
|
1930
|
return $package->can($action) ? 1 : 0; |
238
|
|
|
|
|
|
|
} |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
sub _to_noun { |
241
|
8
|
|
|
8
|
|
19
|
my $word = shift; |
242
|
8
|
|
|
|
|
25
|
$word =~ s/_/ /g; |
243
|
8
|
|
|
|
|
32
|
$word; |
244
|
|
|
|
|
|
|
} |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
sub _add_sidebar { |
247
|
14
|
|
|
14
|
|
21
|
my $self = shift; |
248
|
14
|
|
|
|
|
20
|
my $app = shift; |
249
|
14
|
|
|
|
|
21
|
my $routes = shift; |
250
|
14
|
|
|
|
|
37
|
my ($prefix, $nav_item, $object, $tab) = @_; |
251
|
14
|
50
|
|
|
|
43
|
die "no tab for $object" unless $tab; |
252
|
14
|
50
|
|
|
|
36
|
die "no nav item" unless $nav_item; |
253
|
|
|
|
|
|
|
|
254
|
28
|
50
|
|
|
|
1322
|
my ($template) = ( |
255
|
14
|
50
|
|
|
|
345
|
( map { (-e "$_/$object/$tab.html.ep") ? "$object/$tab" : () } @{ $app->renderer->paths } ), |
|
28
|
|
|
|
|
2361
|
|
256
|
14
|
|
|
|
|
23
|
( map { (-e "$_/$tab.html.ep" ) ? "$tab" : () } @{ $app->renderer->paths } ), |
|
14
|
|
|
|
|
340
|
|
257
|
|
|
|
|
|
|
); |
258
|
14
|
50
|
|
|
|
495
|
$template = $tab if $app->renderer->get_data_template({template => $tab, format => 'html', handler => 'ep'}); |
259
|
14
|
100
|
|
|
|
10677
|
$template = "$object/$tab" if $app->renderer->get_data_template({template => "$object/$tab", format => "html", handler => "ep"}); |
260
|
|
|
|
|
|
|
|
261
|
14
|
50
|
|
|
|
1048
|
my $namespaces = $routes->can('namespaces') ? $routes->namespaces : $routes->root->namespaces; |
262
|
14
|
100
|
66
|
|
|
206
|
$namespaces = [ '' ] unless $namespaces && @$namespaces; |
263
|
14
|
|
|
|
|
31
|
my $found_controller = grep { _cando($_,$object,$tab) } @$namespaces; |
|
14
|
|
|
|
|
59
|
|
264
|
|
|
|
|
|
|
|
265
|
14
|
|
|
|
|
424
|
$app->log->debug("Adding sidebar route for $prefix/$object/$tab"); |
266
|
14
|
100
|
|
|
|
612
|
$app->log->debug("found template $template for $object/$tab ($nav_item)") if $template; |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
my $r = $routes->under( |
269
|
|
|
|
|
|
|
"$prefix/$object/$tab" => sub { |
270
|
10
|
|
|
10
|
|
248990
|
my $c = shift; |
271
|
10
|
|
100
|
|
|
103
|
$c->stash->{template} = $template || "plural"; |
272
|
10
|
|
|
|
|
125
|
$c->stash(object => $object); |
273
|
10
|
|
|
|
|
204
|
$c->stash(noun => $object); |
274
|
10
|
|
|
|
|
164
|
$c->stash(tab => $tab); |
275
|
10
|
|
|
|
|
158
|
$c->stash(nav_item => $nav_item); |
276
|
14
|
|
|
|
|
207
|
})->any; |
277
|
|
|
|
|
|
|
|
278
|
14
|
100
|
|
|
|
34566
|
$app->log->debug("found controller for $object/$tab (controller : $object, action : $tab)") if $found_controller; |
279
|
14
|
100
|
|
|
|
132
|
$r = $r->to(controller => $object, action => $tab) if $found_controller; |
280
|
14
|
|
|
|
|
275
|
$r->name("$object/$tab"); |
281
|
|
|
|
|
|
|
} |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
sub _add_tab { |
284
|
20
|
|
|
20
|
|
59
|
my $self = shift; |
285
|
20
|
|
|
|
|
29
|
my $app = shift; |
286
|
20
|
|
|
|
|
22
|
my $routes = shift; |
287
|
20
|
|
|
|
|
53
|
my ($prefix, $nav_item, $object, $tab) = @_; |
288
|
40
|
50
|
|
|
|
1689
|
my ($default_template) = ( |
289
|
20
|
50
|
|
|
|
714
|
( map { (-e "$_/$object/$tab.html.ep") ? "$object/$tab" : () } @{ $app->renderer->paths } ), |
|
40
|
|
|
|
|
1703
|
|
290
|
20
|
|
|
|
|
29
|
( map { (-e "$_/$tab.html.ep" ) ? "$tab" : () } @{ $app->renderer->paths } ), |
|
20
|
|
|
|
|
489
|
|
291
|
|
|
|
|
|
|
); |
292
|
20
|
50
|
|
|
|
695
|
$default_template = $tab if $app->renderer->get_data_template({template => $tab, format => "html", handler => "ep"}); |
293
|
20
|
100
|
|
|
|
1893
|
$default_template = "$object/$tab" if $app->renderer->get_data_template({template => "$object/$tab", format => "html", handler => "ep"}); |
294
|
|
|
|
|
|
|
|
295
|
20
|
50
|
|
|
|
1765
|
my $namespaces = $routes->can('namespaces') ? $routes->namespaces : $routes->root->namespaces; |
296
|
20
|
100
|
66
|
|
|
255
|
$namespaces = [ '' ] unless $namespaces && @$namespaces; |
297
|
20
|
|
|
|
|
39
|
my $found_controller = grep { _cando($_,$object,$tab) } @$namespaces; |
|
20
|
|
|
|
|
142
|
|
298
|
20
|
|
|
|
|
557
|
$app->log->debug("Adding route for $prefix/$object/$tab/*key"); |
299
|
20
|
100
|
|
|
|
665
|
$app->log->debug("Found controller class for $object/$tab/key") if $found_controller; |
300
|
20
|
100
|
|
|
|
99
|
$app->log->debug("Found default template for $object/$tab/key ($default_template)") if $default_template; |
301
|
|
|
|
|
|
|
my $r = $routes->under("$prefix/$object/$tab/(*key)" |
302
|
|
|
|
|
|
|
=> { key => '', show_tabs => 1 } |
303
|
|
|
|
|
|
|
=> sub { |
304
|
8
|
|
|
8
|
|
226388
|
my $c = shift; |
305
|
8
|
|
|
|
|
19
|
my $template = $default_template; |
306
|
8
|
|
|
|
|
38
|
my $key = lc $c->stash('key'); |
307
|
8
|
|
|
|
|
125
|
$c->stash(object => $object); |
308
|
8
|
|
|
|
|
155
|
$c->stash(noun => _to_noun($object)); |
309
|
8
|
|
|
|
|
133
|
$c->stash(tab => $tab); |
310
|
8
|
50
|
|
|
|
124
|
if ( $key ) { |
311
|
8
|
50
|
33
|
|
|
17
|
if ( ( grep { -e "$_/$object/$key/$tab.html.ep" } @{ $app->renderer->paths } ) |
|
16
|
|
|
|
|
969
|
|
|
8
|
|
|
|
|
275
|
|
312
|
|
|
|
|
|
|
|| $c->app->renderer->get_data_template( {template => "$object/$key/$tab", format => "html", handler => "ep"}) ) { |
313
|
0
|
|
|
|
|
0
|
$template = "$object/$key/$tab"; |
314
|
|
|
|
|
|
|
} |
315
|
|
|
|
|
|
|
} else { |
316
|
0
|
|
|
|
|
0
|
$template = "none_selected"; |
317
|
0
|
0
|
|
|
|
0
|
$template = "$object/none_selected" if grep { -e "$_/$object/none_selected.html.ep" } @{ $app->renderer->paths }; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
318
|
|
|
|
|
|
|
} |
319
|
8
|
|
100
|
|
|
635
|
$c->stash->{template} = $template || "single"; |
320
|
8
|
|
|
|
|
167
|
my $instance = $c->current_instance; |
321
|
8
|
|
|
|
|
109
|
$c->stash( instance => $instance ); |
322
|
8
|
|
|
|
|
136
|
$c->stash( nav_item => $nav_item ); |
323
|
8
|
|
|
|
|
123
|
$c->stash( $object => $instance ); |
324
|
8
|
50
|
|
|
|
118
|
$c->render unless $key; |
325
|
8
|
50
|
|
|
|
87
|
$key ? 1 : 0; |
326
|
|
|
|
|
|
|
} |
327
|
20
|
|
|
|
|
362
|
)->any; |
328
|
20
|
100
|
|
|
|
22722
|
$r = $r->to("$object#$tab") if $found_controller; |
329
|
20
|
|
|
|
|
316
|
$r->name("$object/$tab"); |
330
|
|
|
|
|
|
|
} |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
sub _menu_to_nav { |
333
|
2
|
|
|
2
|
|
4
|
my $self = shift; |
334
|
2
|
|
|
|
|
7
|
my ($conf,$menu) = @_; |
335
|
2
|
|
|
|
|
3
|
my $nav; |
336
|
|
|
|
|
|
|
my $sidebar; |
337
|
0
|
|
|
|
|
0
|
my $tabs; |
338
|
0
|
|
|
|
|
0
|
my $object; |
339
|
2
|
|
|
|
|
8
|
for (@$menu) { |
340
|
8
|
100
|
|
|
|
28
|
unless (ref $_) { |
341
|
4
|
|
|
|
|
10
|
$object = $_; |
342
|
4
|
|
|
|
|
8
|
push @$nav, $object; |
343
|
4
|
|
|
|
|
9
|
next; |
344
|
|
|
|
|
|
|
} |
345
|
4
|
50
|
|
|
|
8
|
for my $action (@{ $_->{many} || [] }) { |
|
4
|
|
|
|
|
25
|
|
346
|
8
|
|
|
|
|
12
|
push @{$sidebar->{$object}}, "$object/$action"; |
|
8
|
|
|
|
|
34
|
|
347
|
|
|
|
|
|
|
} |
348
|
4
|
|
|
|
|
12
|
push @{$sidebar->{$object}}, $object; |
|
4
|
|
|
|
|
10
|
|
349
|
4
|
50
|
|
|
|
10
|
for my $action (@{ $_->{one} || [] }) { |
|
4
|
|
|
|
|
17
|
|
350
|
7
|
|
|
|
|
9
|
push @{$tabs->{$object}}, $action; |
|
7
|
|
|
|
|
28
|
|
351
|
|
|
|
|
|
|
} |
352
|
|
|
|
|
|
|
} |
353
|
2
|
|
|
|
|
7
|
$conf->{nav} = $nav; |
354
|
2
|
|
|
|
|
6
|
$conf->{sidebar} = $sidebar; |
355
|
2
|
|
|
|
|
43
|
$conf->{tabs} = $tabs; |
356
|
|
|
|
|
|
|
} |
357
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
sub register { |
359
|
4
|
|
|
4
|
1
|
263
|
my ($self, $app, $conf) = @_; |
360
|
4
|
|
|
|
|
143
|
$app->log->debug("registering plugin"); |
361
|
|
|
|
|
|
|
|
362
|
4
|
100
|
|
|
|
704
|
if (my $menu = $conf->{menu}) { |
363
|
2
|
|
|
|
|
9
|
$self->_menu_to_nav($conf,$menu); |
364
|
|
|
|
|
|
|
} |
365
|
4
|
|
|
|
|
10
|
for (qw/nav sidebar tabs/) { |
366
|
12
|
50
|
|
|
|
42
|
die "missing $_" unless $conf->{$_}; |
367
|
|
|
|
|
|
|
} |
368
|
4
|
|
|
|
|
49
|
my ($nav,$sidebar,$tabs) = @$conf{qw/nav sidebar tabs/}; |
369
|
|
|
|
|
|
|
|
370
|
4
|
|
50
|
|
|
38
|
my $prefix = $conf->{prefix} || ''; |
371
|
4
|
|
33
|
|
|
267
|
my $routes = $conf->{head_route} || $app->routes; |
372
|
|
|
|
|
|
|
|
373
|
4
|
|
|
|
|
1197
|
my $base = catdir(abs_path(dirname(__FILE__)), qw/Toto Assets/); |
374
|
4
|
|
|
|
|
24
|
my $default_path = catdir($base,'templates'); |
375
|
4
|
|
|
|
|
9
|
push @{$app->renderer->paths}, catdir($base, 'templates'); |
|
4
|
|
|
|
|
137
|
|
376
|
4
|
|
|
|
|
187
|
push @{$app->static->paths}, catdir($base, 'public'); |
|
4
|
|
|
|
|
115
|
|
377
|
4
|
|
|
|
|
309
|
$app->defaults(layout => "toto", toto_prefix => $prefix); |
378
|
|
|
|
|
|
|
|
379
|
4
|
|
|
|
|
225
|
$app->log->debug("Adding routes"); |
380
|
|
|
|
|
|
|
|
381
|
4
|
|
|
|
|
124
|
my %tab_done; |
382
|
|
|
|
|
|
|
|
383
|
4
|
50
|
|
|
|
18
|
die "toto plugin needs a 'nav' entry, please read the pod for more information" unless $nav; |
384
|
4
|
|
|
|
|
13
|
for my $nav_item ( @$nav ) { |
385
|
8
|
|
|
|
|
3166
|
$app->log->debug("Adding routes for $nav_item"); |
386
|
8
|
|
|
|
|
303
|
my $first; |
387
|
8
|
50
|
|
|
|
158
|
my $items = $sidebar->{$nav_item} or die "no sidebar for $nav_item"; |
388
|
8
|
|
|
|
|
158
|
for my $subnav_item ( @$items ) { |
389
|
23
|
|
|
|
|
1577
|
$app->log->debug("routes for $subnav_item"); |
390
|
23
|
|
|
|
|
846
|
my ( $object, $action ) = split '/', $subnav_item; |
391
|
23
|
100
|
|
|
|
64
|
if ($action) { |
392
|
14
|
|
66
|
|
|
60
|
$first ||= $subnav_item; |
393
|
14
|
|
|
|
|
51
|
$self->_add_sidebar($app,$routes,$prefix,$nav_item,$object,$action); |
394
|
|
|
|
|
|
|
} else { |
395
|
9
|
|
|
|
|
21
|
my $first_tab; |
396
|
9
|
|
33
|
|
|
41
|
$first ||= "$object/default"; |
397
|
|
|
|
|
|
|
my $tabs = $tabs->{$subnav_item} or |
398
|
9
|
50
|
|
|
|
71
|
do { warn "# no tabs for $subnav_item"; next; }; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
399
|
9
|
50
|
|
|
|
48
|
die "tab row for '$subnav_item' appears more than once" if $tab_done{$subnav_item}++; |
400
|
9
|
|
|
|
|
29
|
for my $tab (@$tabs) { |
401
|
20
|
|
66
|
|
|
168
|
$first_tab ||= $tab; |
402
|
20
|
|
|
|
|
72
|
$self->_add_tab($app,$routes,$prefix,$nav_item,$object,$tab); |
403
|
|
|
|
|
|
|
} |
404
|
9
|
|
|
|
|
409
|
$app->log->debug("Will redirect $prefix/$object/default/key to $object/$first_tab/\$key"); |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
$routes->get("$prefix/$object/default/*key" => { key => '' } => sub { |
407
|
0
|
|
|
0
|
|
0
|
my $c = shift; |
408
|
0
|
|
|
|
|
0
|
my $key = $c->stash("key"); |
409
|
0
|
|
|
|
|
0
|
$c->redirect_to("$object/$first_tab", key => $key); |
410
|
9
|
|
|
|
|
474
|
} => "$object/default"); |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
$routes->get( |
413
|
|
|
|
|
|
|
"$prefix/$object/autocomplete" => { layout => "default" } => sub { |
414
|
0
|
|
|
0
|
|
0
|
my $c = shift; |
415
|
0
|
|
|
|
|
0
|
my $query = $c->param('q'); |
416
|
0
|
0
|
|
|
|
0
|
return $c->render_not_found unless $c->model_class->can("autocomplete"); |
417
|
0
|
|
|
|
|
0
|
my $results = $c->model_class->autocomplete( q => $query, object => $object, c => $c, tab => $c->param('tab') ); |
418
|
|
|
|
|
|
|
# Expects an array ref of the form |
419
|
|
|
|
|
|
|
# [ { name => 'foo', href => 'bar' }, ] |
420
|
0
|
|
|
|
|
0
|
$c->render( json => $results ); |
421
|
9
|
|
|
|
|
7754
|
} => "$object/autocomplete"); |
422
|
|
|
|
|
|
|
} |
423
|
|
|
|
|
|
|
} |
424
|
8
|
50
|
|
|
|
5967
|
die "Could not find first route for nav item '$nav_item' : all entries have tabs\n" unless $first; |
425
|
|
|
|
|
|
|
$routes->get( |
426
|
|
|
|
|
|
|
$nav_item => sub { |
427
|
5
|
|
|
5
|
|
157373
|
my $c = shift; |
428
|
5
|
|
|
|
|
31
|
$c->redirect_to($first); |
429
|
8
|
|
|
|
|
61
|
} => $nav_item ); |
430
|
|
|
|
|
|
|
} |
431
|
|
|
|
|
|
|
|
432
|
4
|
|
|
|
|
2704
|
my $first_object = $conf->{nav}[0]; |
433
|
4
|
|
|
2
|
|
59
|
$routes->get("$prefix/" => sub { shift->redirect_to($first_object) } ); |
|
2
|
|
|
|
|
126511
|
|
434
|
|
|
|
|
|
|
|
435
|
4
|
|
|
|
|
1742
|
for ($app) { |
436
|
4
|
|
|
38
|
|
74
|
$_->helper( toto_config => sub { $conf } ); |
|
38
|
|
|
|
|
615213
|
|
437
|
|
|
|
|
|
|
$_->helper( model_class => sub { |
438
|
30
|
|
|
30
|
|
3478
|
my $c = shift; |
439
|
30
|
50
|
|
|
|
138
|
if (my $ns = $conf->{model_namespace}) { |
440
|
0
|
|
|
|
|
0
|
return join '::', $ns, b($c->current_object)->camelize; |
441
|
|
|
|
|
|
|
} |
442
|
30
|
50
|
|
|
|
455
|
$conf->{model_class} || "Mojolicious::Plugin::Toto::Model" |
443
|
|
|
|
|
|
|
} |
444
|
4
|
|
|
|
|
467
|
); |
445
|
|
|
|
|
|
|
$_->helper( |
446
|
|
|
|
|
|
|
tabs => sub { |
447
|
25
|
|
|
25
|
|
3650
|
my $c = shift; |
448
|
25
|
50
|
66
|
|
|
192
|
my $for = shift || $c->current_object or return; |
449
|
25
|
50
|
|
|
|
131
|
@{ $conf->{tabs}{$for} || [] }; |
|
25
|
|
|
|
|
209
|
|
450
|
|
|
|
|
|
|
} |
451
|
4
|
|
|
|
|
391
|
); |
452
|
|
|
|
|
|
|
$_->helper( current_object => sub { |
453
|
59
|
|
|
59
|
|
123944
|
my $c = shift; |
454
|
59
|
100
|
|
|
|
205
|
$c->stash('object') || [ split '\/', $c->current_route ]->[0] |
455
|
4
|
|
|
|
|
416
|
} ); |
456
|
|
|
|
|
|
|
$_->helper( current_tab => sub { |
457
|
22
|
|
|
22
|
|
12929
|
my $c = shift; |
458
|
22
|
50
|
|
|
|
72
|
$c->stash('tab') || [ split '\/', $c->current_route ]->[1] |
459
|
4
|
|
|
|
|
363
|
} ); |
460
|
|
|
|
|
|
|
$_->helper( current_instance => sub { |
461
|
30
|
|
|
30
|
|
65484
|
my $c = shift; |
462
|
30
|
|
33
|
|
|
98
|
my $key = $c->stash("key") || [ split '\/', $c->current_route ]->[2]; |
463
|
30
|
|
|
|
|
561
|
return $c->model_class->new(key => $key); |
464
|
4
|
|
|
|
|
547
|
} ); |
465
|
|
|
|
|
|
|
$_->helper( printable => sub { |
466
|
45
|
|
|
45
|
|
63786
|
my $c = shift; |
467
|
45
|
|
|
|
|
90
|
my $what = shift; |
468
|
45
|
|
|
|
|
98
|
$what =~ s/_/ /g; |
469
|
4
|
|
|
|
|
374
|
$what } ); |
|
45
|
|
|
|
|
192
|
|
470
|
|
|
|
|
|
|
$_->helper( a_printable => sub { |
471
|
0
|
|
|
0
|
|
0
|
my $c = shift; |
472
|
0
|
|
|
|
|
0
|
my $what = shift; |
473
|
0
|
0
|
|
|
|
0
|
return ( ($what =~ /^[aeiou]/ ? "an " : "a ").$c->printable($what)); |
474
|
4
|
|
|
|
|
390
|
} ); |
475
|
|
|
|
|
|
|
} |
476
|
|
|
|
|
|
|
|
477
|
4
|
|
|
|
|
397
|
$self; |
478
|
|
|
|
|
|
|
} |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
1; |