—package
Mojolicious::Plugin::JSLoader;
# ABSTRACT: move js loading to the end of the document
use
strict;
use
warnings;
use
HTML::ParseBrowser;
use
Mojo::ByteStream;
use
version 0.77;
our
$VERSION
=
'0.08'
;
sub
register {
my
(
$self
,
$app
,
$config
) =
@_
;
my
$base
=
$config
->{base} ||
''
;
if
(
$base
and
substr
(
$base
, -1 ) ne
'/'
) {
$base
.=
'/'
;
}
$app
->helper(
js_load
=>
sub
{
my
$c
=
shift
;
return
''
unless
_match_browser(
$c
,
@_
);
if
(
$_
[1]->{check} ) {
my
$asset
=
$c
->app->static->file(
$_
[1]->{no_base} ?
$_
[0] :
"$base$_[0]"
);
return
''
if
!
$asset
;
}
if
(
$_
[1]->{inplace} ) {
my
(
$file
,
$config
) =
@_
;
my
$local_base
=
$config
->{no_base} ?
''
:
$base
;
$local_base
=
$c
->url_for(
$local_base
)
if
$local_base
;
$config
->{no_file} = 1
if
$config
->{js};
my
$js
=
$config
->{no_file} ?
qq~<script type="text/javascript">$file</script>~
:
qq~<script type="text/javascript" src="$local_base$file"></script>~
;
return
Mojo::ByteStream->new(
$js
);
}
push
@{
$c
->stash->{__JSLOADERFILES__} }, [
@_
];
return
1;
} );
$app
->hook(
after_render
=>
sub
{
my
(
$c
,
$content
,
$format
) =
@_
;
return
if
$format
ne
'html'
;
return
if
!
$c
->stash->{__JSLOADERFILES__};
return
if
'ARRAY'
ne
ref
$c
->stash->{__JSLOADERFILES__};
my
$load_js
=
join
"\n"
,
map
{
my
(
$file
,
$config
) = @{
$_
};
$file
//=
''
;
my
$local_base
=
$config
->{no_base} ?
''
:
$base
;
$config
->{no_file} = 1
if
$config
->{js};
$local_base
=
$c
->url_for(
$local_base
)
if
$local_base
;
if
(
$config
->{no_file} and
$config
->{on_ready} ) {
$file
=
sprintf
'$(document).ready( function(){%s});'
,
$file
;
}
my
$return
=
$config
->{no_file} ?
qq~<script type="text/javascript">$file</script>~
:
qq~<script type="text/javascript" src="$local_base$file"></script>~
;
$file
?
$return
: ();
}
@{
$c
->stash->{__JSLOADERFILES__} };
return
if
!
$load_js
;
${
$content
} =~ s!(</body(?:\s|>)|\z)!
$load_js
$1!;
});
}
sub
_match_browser {
my
(
$c
,
$file
,
$config
) =
@_
;
return
1
if
!
$config
;
return
1
if
ref
$config
ne
'HASH'
;
return
1
if
!
$config
->{browser};
return
1
if
ref
$config
->{browser} ne
'HASH'
;
my
$ua_string
=
$c
->req->headers->user_agent;
my
$ua
= HTML::ParseBrowser->new(
$ua_string
);
my
$name
=
$ua
->name;
my
$browser
=
$config
->{browser};
if
( !
exists
$browser
->{
$name
} ) {
return
exists
$browser
->{
default
} ? 1 :
''
;
}
my
(
$op
,
$version
) =
$browser
->{
$name
} =~ m{\A\s*([lg]t|!)?\s*([0-9\.]+)};
return
if
!
defined
$version
;
$op
=
''
if
!
defined
$op
;
if
(
$op
eq
'gt'
) {
return
version->parse(
$version
) <= version->parse(
$ua
->v );
}
elsif
(
$op
eq
'lt'
) {
return
version->parse(
$version
) >= version->parse(
$ua
->v );
}
elsif
(
$op
eq
'!'
) {
return
version->parse(
$version
) != version->parse(
$ua
->v );
}
return
version->parse(
$ua
->v ) == version->parse(
$version
);
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Mojolicious::Plugin::JSLoader - move js loading to the end of the document
=head1 VERSION
version 0.08
=head1 SYNOPSIS
In your C<startup>:
sub startup {
my $self = shift;
# do some Mojolicious stuff
$self->plugin( 'JSLoader' );
# more Mojolicious stuff
}
In your template:
<% js_load('js_file.js') %>
=head1 HELPERS
This plugin adds a helper method to your web application:
=head2 js_load
This method requires at least one parameter: The path to the JavaScript file to load.
An optional second parameter is the configuration. You can switch off the I<base> for
this JavaScript file this way:
# <script type="text/javascript" src="$base/js_file.js"></script>
<% js_load('js_file.js') %>
# <script type="text/javascript" src="http://domain/js_file.js"></script>
<% js_load('http://domain/js_file.js', {no_base => 1}); %>
=head3 config for js_load
There are several config options for C<js_load>:
=over 4
=item * no_base
Do not use the base url configured on startup when I<no_base> is set to a true value.
# <script type="text/javascript" src="http://domain/js_file.js"></script>
<% js_load('http://domain/js_file.js', {no_base => 1}); %>
=item * no_file
If set to a true value, you have to pass pure JavaScript
# <script type="text/javascript">alert('test');</script>
<% js_load("alert('test')", {no_file => 1}); %>
=item * on_ready
If set to a true value - in combination with a true value for I<no_file> - the javascript
code is wrapped in C<$(document).ready( function(){...});>. This is quite handy when you
have jquery installed and you want to run some javascript when the document is loaded.
# <script type="text/javascript">alert('test');</script>
<% js_load("alert('test')", {no_file => 1}); %>
=item * inplace
Do not load the javascript at the end of the page, but where C<js_load> is called.
# <script type="text/javascript" src="http://domain/js_file.js"></script>
<%= js_load('http://domain/js_file.js', {no_base => 1, inplace => 1}); %>
=item * browser
Load the javascript when a specific browser is used.
# Load the javascript when Internet Explorer 8 is used
# <script type="text/javascript" src="http://domain/js_file.js"></script>
<%= js_load('http://domain/js_file.js', {inplace => 1, browser => { "Internet Explorer" => 8 }}); %>
# Load the javascript when Internet Explorer lower than 8 or Opera 6 is used
# <script type="text/javascript" src="http://domain/js_file.js"></script>
<%= js_load('http://domain/js_file.js', {inplace => 1, browser => {"Internet Explorer" => 'lt 8', Opera => 6} }); %>
# Load the javascript when Internet Explorer is not version 8
<%= js_load('http://domain/js_file.js', {inplace => 1, browser => {"Internet Explorer" => '!8' } } ); %>
There's the "special" browser default. So you are able to load javascript for e.g. everything but IE6
# Load the javascript when Internet Explorer is not version 6
<%= js_load('http://domain/js_file.js', {inplace => 1, browser => {"Internet Explorer" => '!6', default => 1 } } ); %>
=item * check
If you want to avoid 404 errors that might occur when the filname is built dynamically, you can pass C<check> in the
config options:
# <public>/test.js exists, <public>/tester.js doesn't
% js_load( 'tester.js' );
% js_load( 'test.js' );
# -> you'll get a 404 error for "tester.js"
# <public>/test.js exists, <public>/tester.js doesn't
% js_load( 'tester.js', { check => 1 } );
% js_load( 'test.js', { check => 1 } );
# -> no 404 error, the javascript tag for tester.js isn't added to the HTML
When you pass C<check>, it is checked whether Mojolicious can create a L<static file|Mojolicious::Static/file> or not.
So the "file" doesn't have to be a file on disk, but a "file" in the C<__DATA__> section is ok, too.
Your class
__DATA__
@@ checktest.js
$(document).ready( function(){ alert('check') } );
Your template:
% js_load( 'checktest.js' ); # works
% js_load( 'checktest.js', { check => 1 } ); # works
% js_load( 'checktest2.js', { check => 1 } ); # tag is not added as checktest2.js doesn't exist
=back
=head1 HOOKS
When you use this module, a hook for I<after_render> is installed. That hook inserts
the C<< <script> >> tag at the end of the document or right before the closing
C<< <body> >> tag.
To avoid that late loading, you can use I<inplace> in the config:
<%= js_load( 'test.js', {inplace => 1} ) %>
=head1 METHODS
=head2 register
Called when registering the plugin. On creation, the plugin accepts a hashref to configure the plugin.
# load plugin, alerts are dismissable by default
$self->plugin( 'JSLoader' );
=head3 Configuration
$self->plugin( 'JSLoader' => {
base => 'http://domain/js', # base for all <script> tags
});
=head1 NOTES
This plugin uses the I<stash> key C<__JSLOADERFILES__>, so you should avoid using
this stash key for your own purposes.
=head1 AUTHOR
Renee Baecker <reneeb@cpan.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2017 by Renee Baecker.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
=cut