Group
Extension

Mojolicious-Plugin-JSUrlFor/lib/Mojolicious/Plugin/JSUrlFor.pm

package Mojolicious::Plugin::JSUrlFor;
use Mojo::Base 'Mojolicious::Plugin';

our $VERSION = '0.17';

use Mojo::ByteStream qw/b/;
use Data::Dumper;
use v5.10;

sub register {
    my ( $self, $app, $config ) = @_;

    if ( $config->{route} ) {
        $app->routes->get( $config->{route} => sub {
            my $c = shift;
            $c->render(
                inline => $c->app->_js_url_for_code_only(),
                format => 'js'
            );
        } )->name('js_url_for');
    }

    $app->helper(
        js_url_for => sub {
            my $c      = shift;
            state $b_js; # bytestream for $js

            if ( $b_js && $app->mode eq 'production' ) {
                return $b_js;
            }

            my $js = $app->_js_url_for_code_only;

            $b_js = b('<script type="text/javascript">'.$js.'</script>');
            return $b_js;
        }
    );

    $app->helper(
        _js_url_for_code_only => sub {
            my $c      = shift;
            my $endpoint_routes = $self->_collect_endpoint_routes( $app->routes );

            my %names2paths;
            foreach my $route (@$endpoint_routes) {
                next unless $route->name;

                my $path = $self->_get_path_for_route($route);
                $path =~ s{^/*}{/}g; # TODO remove this quickfix

                $names2paths{$route->name} = $path;
            }

            my $json_routes = $c->render_to_string( json => \%names2paths );
            utf8::decode( $json_routes );

            my $js = <<"JS";
var mojolicious_routes = $json_routes;
function url_for(route_name, captures) {
    var pattern = mojolicious_routes[route_name];
    if(!pattern) return route_name;

    // Fill placeholders with values
    if (!captures) captures = {};
    for (var placeholder in captures) { // TODO order placeholders from longest to shortest
        var re = new RegExp('[:*]' + placeholder, 'g');
        pattern = pattern.replace(re, captures[placeholder]);
    }

    // Clean not replaces placeholders
    pattern = pattern.replace(/[:*][^/.]+/g, '');

    return pattern;
}
JS
            return $js;
        } );
}


sub _collect_endpoint_routes {
    my ( $self, $route ) = @_;
    my @endpoint_routes;

    foreach my $child_route ( @{ $route->children } ) {
        if ( $child_route->is_endpoint ) {
            push @endpoint_routes, $child_route;
        } else {
            push @endpoint_routes, @{ $self->_collect_endpoint_routes($child_route) };
        }
    }
    return \@endpoint_routes
}

sub _get_path_for_route {
    my ( $self, $parent ) = @_;

    my $path = $parent->pattern->unparsed // '';

    while ( $parent = $parent->parent ) {
        $path = ($parent->pattern->unparsed//'') . $path;
    }

    return $path;
}

1;
__END__

=head1 NAME

Mojolicious::Plugin::JSUrlFor - Mojolicious "url_for" helper for javascript

=head1 SYNOPSIS

  # Mojolicious
  $self->plugin('JSUrlFor');

  # Mojolicious::Lite
  plugin 'JSUrlFor';

  # In your application
  my $r = $self->routes;
  $r->get('/messages/:message_id')->to('messages#show')->name('messages_show');

  # In your layout template
  <head>
  <%= js_url_for%>
  </head>

  # In your javascript
  $.getJSON( url_for( 'messages_show', {message_id: 123} ), params, function() { ... } )


  # Instead of helper you can use generator for generating static file
  ./your_app.pl generate js_url_for public/static/url_for.js

  # And then in your layout template
  <head>
    <script type="text/javascript" src='/static/url_for.js'> </script>
  </head>

  # Or let it generate on the fly
  # Can be useful if you have only RESTful API without templates and you want to provide routes names for UI
  $self->plugin('JSUrlFor', {route => '/javascript/url.js'});
  <head>
    <script type="text/javascript" src='/javascripts/url.js'> </script>
  </head>

=head1 DESCRIPTION

I like Mojolicious routes. And one feature that I like most is that you can name your routes.
So, you can change your routes without rewriting a single line of dependent code. Of course this works if you
use route names in all of your code. You can use route name everywhere except... javascript.
But with L<Mojolicious::Plugin::JSUrlFor> you can use route names really everywhere.
This plugin support mounted (see L<Mojolicious::Plugin::Mount> ) apps too.

L<Mojolicious::Plugin::JSUrlFor> contains only one helper that adds C<url_for> function to your client side javascript.

=head1 HELPERS

=head2 C<js_url_for>

In templates C<< <%= js_url_for %> >>

This helper will add C<url_for> function to your client side javascript.

In I<production> mode this helper will cache generated code for javascript I<url_for> function

=head1 CONFIG OPTIONS

=head2 C<route>

Simulate static javascript file. It can be useful if you have RESTful API and want to provide js file with routes.

=head1 GENERATORS

=head2 C<js_url_for>

  ./your_app.pl generate js_url_for $relative_file_name

This command will create I<$relative_file_name> file with the same content as C<js_url_for> helper creates.
Then you should include this file into your layout template with I<script> tag.

=head1 METHODS

L<Mojolicious::Plugin::JSUrlFor> inherits all methods from
L<Mojolicious::Plugin> and implements the following new ones.

=head2 C<register>

  $plugin->register;

Register plugin in L<Mojolicious> application.

=head1 AUTHOR

Viktor Turskyi <koorchik@cpan.org>

=head1 BUGS

Please report any bugs or feature requests to Github L<https://github.com/koorchik/Mojolicious-Plugin-JSUrlFor/>

Also you can report bugs to CPAN RT

=head1 SEE ALSO

L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.

=cut


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.