Attempt to get organism controller to play nice with the main controller
[sgn.git] / lib / SGN / Controller / JavaScript.pm
blob1af2aee94bc573bdf70fc7021afe06b55dcab23b
1 package SGN::Controller::JavaScript;
2 use Moose;
3 use namespace::autoclean;
4 use Moose::Util::TypeConstraints;
6 use File::Spec;
8 use Carp;
9 use Digest::MD5 'md5_hex';
10 use HTTP::Status;
11 use JSAN::ServerSide;
12 use List::MoreUtils qw/ uniq first_index /;
13 use Storable qw/ nfreeze /;
15 BEGIN { extends 'Catalyst::Controller' }
16 with 'Catalyst::Component::ApplicationAttribute';
18 __PACKAGE__->config(
19 namespace => 'js',
20 js_include_path => SGN->path_to('js'),
24 my $inc = subtype as 'ArrayRef';
25 coerce $inc, from 'Defined', via { [$_] };
27 has 'js_include_path' => (
28 is => 'ro',
29 isa => $inc,
30 coerce => 1,
34 has '_package_defs' => (
35 is => 'ro',
36 lazy_build => 1,
37 ); sub _build__package_defs {
38 my $self = shift;
40 my $cache = Cache::File->new(
41 cache_root => $self->_app->path_to( $self->_app->tempfiles_subdir('cache','js_packs') ),
43 default_expires => 'never',
44 size_limit => 1_000_000,
45 removal_strategy => 'Cache::RemovalStrategy::LRU',
49 =head1 ACTIONS
51 =head2 js_package
53 Serve a minified, concatenated package of javascript, assembled and
54 stored on the previous request.
56 Args: package identifier (32-character hex)
58 =cut
60 sub js_package :Path('pack') :Args(1) {
61 my ( $self, $c, $key ) = @_;
63 # NOTE: you might think that we should cache the minified
64 # javascript too, but it turns out to not be much faster!
66 $c->stash->{js} = $self->_package_defs->thaw( $key );
68 $c->log->debug(
69 "JS: serving pack $key = ("
70 .(join ', ', @{ $c->stash->{js} || [] })
71 .')'
72 ) if $c->debug;
74 $c->forward('View::JavaScript');
76 # support caching with If-Modified-Since requests
77 my $ims = $c->req->headers->if_modified_since;
78 my $modtime = $c->res->headers->last_modified;
79 if( $ims && $modtime && $ims >= $modtime ) {
80 $c->res->status( RC_NOT_MODIFIED );
81 $c->res->body(' ');
85 =head2 default
87 Serve a single (minified) javascript file from our js path.
89 =cut
91 sub default :Path {
92 my ( $self, $c, @args ) = @_;
94 $c->stash->{js} = [ File::Spec->catfile( @args ) ];
95 $c->forward('View::JavaScript');
98 =head2 end
100 forwards to View::JavaScript
102 =cut
104 sub end :Private {
105 my ( $self, $c ) = @_;
107 # handle missing JS with a 404
108 if( @{ $c->error } == 1 && $c->error->[0] =~ /^Can't open '/ ) {
109 warn $c->error->[0];
111 $c->clear_errors;
113 $c->res->status( 404 );
114 $c->res->content_type( 'text/html' );
116 $c->stash->{template} = "/site/error/404.mas";
117 $c->stash->{message} = "Javascript not found";
118 $c->forward('View::Mason');
122 =head2 insert_js_pack_html
124 Scans the current $c->res->body and inserts <script> includes for the
125 current set of javascript includes in the $c->stash->{pack_js}
126 arrayref.
128 Replaces comments like:
130 <!-- INSERT_JS_PACK -->
132 with:
134 <script src="(uri for js pack)" type="text/javascript">
135 </script>
137 =cut
140 sub insert_js_pack_html :Private {
141 my ( $self, $c ) = @_;
143 my $js = $c->stash->{pack_js};
144 return unless $js && @$js;
146 my $b = $c->res->body;
148 my $pack_uri = $c->uri_for( $self->action_for_js_package( $js ) )->path_query;
149 if( $b && $b =~ s{<!-- \s* INSERT_JS_PACK \s* -->} {<script src="$pack_uri" type="text/javascript">\n</script>}x ) {
150 $c->res->body( $b );
151 delete $c->stash->{pack_js};
155 =head1 REGULAR METHODS
157 =head2 action_for_js_package
159 Usage: $controller->action_for_js_package([ 'sgn', 'jquery' ])
160 Desc : get a list of (action,arguments) for getting a minified,
161 concatenated set of javascript containing the given libraries.
162 Args : arrayref of of JS libraries to include in the package
163 Ret : list of ( $action, @arguments )
164 Side Effects: saves the list of js libs in a cache for subsequent
165 requests
166 Example:
168 $c->uri_for( $c->controller('JavaScript')->action_for_js_package([ 'sgn', 'jquery' ]))
170 =cut
172 sub action_for_js_package {
173 my ( $self, $files ) = @_;
174 @_ == 2 && ref $files && ref $files eq 'ARRAY' && @$files
175 or croak "action_for_js_package takes a single param, an arrayref of files";
177 my @files = uniq @$files; #< do not sort, load order might be important
178 for (@files) {
179 s/\.js$//;
180 s!\.!/!g;
183 # if prototype is present, move it to the front to prevent it
184 # conflicting with jquery
185 my $prototype_idx = first_index { /Prototype$/i } @files;
186 if( $prototype_idx > -1 ) {
187 my ($p) = splice @files, $prototype_idx, 1;
188 unshift @files, $p;
192 # add in JSAN.use dependencies
193 @files = $self->_resolve_jsan_dependencies( \@files );
195 my $key = md5_hex( join '!!', @files );
197 $self->_app->log->debug (
198 "JS: define pack $key = ("
199 .(join ', ', @files)
200 .')'
201 ) if $self->_app->debug;
204 # record files for this particular package of JS
205 if( $self->_package_defs->exists( $key ) ) {
206 $self->_app->log->debug("JS: key $key already exists in js_packs cache") if $self->_app->debug;
207 } else {
208 $self->_app->log->debug("JS: new key $key stored in js_packs cache") if $self->_app->debug;
209 $self->_package_defs->freeze( $key => \@files );
212 return $self->action_for('js_package'), $key;
217 ########## helpers #########
219 sub _resolve_jsan_dependencies {
220 my ( $self, $files ) = @_;
221 local $_; #< stupid JSAN writes to $_
223 # resolve JSAN dependencies of these files
224 my $jsan = $self->new_jsan;
225 for my $f (@$files) {
226 $jsan->add( $f );
229 return map { s!^\/fake_prefix/!!; $_ } $jsan->uris;
232 has _jsan_params => ( is => 'ro', isa => 'HashRef', lazy_build => 1 );
233 sub _build__jsan_params {
234 my ( $self ) = @_;
235 my $inc_path = $self->js_include_path;
236 die "multi-dir js_include_path not yet supported" if @$inc_path > 1;
237 my $js_dir = $inc_path->[0];
238 -d $js_dir or die "configured js_include_path '$js_dir' does not exist!\n";
239 return {
240 js_dir => "$js_dir",
241 uri_prefix => '/fake_prefix',
244 sub new_jsan {
245 JSAN::ServerSide->new( %{ shift->_jsan_params } );
249 __PACKAGE__->meta->make_immutable;