1 package SGN
::Controller
::JavaScript
;
3 use namespace
::autoclean
;
4 use Moose
::Util
::TypeConstraints
;
9 use Digest
::MD5
'md5_hex';
12 use List
::MoreUtils qw
/ uniq first_index /;
13 use Storable qw
/ nfreeze /;
15 BEGIN { extends
'Catalyst::Controller' }
16 with
'Catalyst::Component::ApplicationAttribute';
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' => (
34 has
'_package_defs' => (
37 ); sub _build__package_defs
{
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',
53 Serve a minified, concatenated package of javascript, assembled and
54 stored on the previous request.
56 Args: package identifier (32-character hex)
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 );
69 "JS: serving pack $key = ("
70 .(join ', ', @
{ $c->stash->{js
} || [] })
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
);
87 Serve a single (minified) javascript file from our js path.
92 my ( $self, $c, @args ) = @_;
94 $c->stash->{js
} = [ File
::Spec
->catfile( @args ) ];
95 $c->forward('View::JavaScript');
100 forwards to View::JavaScript
105 my ( $self, $c ) = @_;
107 # handle missing JS with a 404
108 if( @
{ $c->error } == 1 && $c->error->[0] =~ /^Can't open '/ ) {
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}
128 Replaces comments like:
130 <!-- INSERT_JS_PACK -->
134 <script src="(uri for js pack)" type="text/javascript">
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 ) {
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
168 $c->uri_for( $c->controller('JavaScript')->action_for_js_package([ 'sgn', 'jquery' ]))
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
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;
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 = ("
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;
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) {
229 return map { s!^\/fake_prefix/!!; $_ } $jsan->uris;
232 has _jsan_params
=> ( is
=> 'ro', isa
=> 'HashRef', lazy_build
=> 1 );
233 sub _build__jsan_params
{
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";
241 uri_prefix
=> '/fake_prefix',
245 JSAN
::ServerSide
->new( %{ shift->_jsan_params } );
249 __PACKAGE__
->meta->make_immutable;