Skip to content

Commit

Permalink
Merge pull request #136 from hotwired-laravel/partials-path-pattern
Browse files Browse the repository at this point in the history
Allow configuring partials path pattern
  • Loading branch information
tonysm authored Jan 4, 2024
2 parents c3baf9d + 968ab51 commit 235e89c
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 3 deletions.
44 changes: 44 additions & 0 deletions docs/conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,50 @@ When using this config, the redirection behavior will still happen, but the pack

You may want to split up your views in smaller chunks (aka. "partials"), such as a `comments/_comment.blade.php` to display a comment resource, or `comments/_form.blade.php` to display the form for both creating and updating comments. This allows you to reuse these _partials_ in [Turbo Streams](/docs/{{version}}/turbo-streams).

Alternatively, you may override the pattern to a `{plural}.partials.{singular}` convention for your partials location by calling the `Turbo::usePartialsSubfolderPattern()` method of the Turbo Facade from your `AppServiceProvider::boot()` method:

```php
<?php

namespace App\Providers;

use HotwiredLaravel\TurboLaravel\Facades\Turbo;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Turbo::usePartialsSubfolderPattern();
}
}
```

You may also want to define your own pattern for partials, which you can do using the `Turbo::resolvePartialsUsing()` method. This method accepts either a string pattern or a Closure. If you pass a string pattern, you'll have two replaceholders available: `{singular}` and `{plural}`, which will get replaced with the model's singular and plural names, respectively, when the pattern is used. If you pass a Closure, you'll have the model instance available and you must return the view pattern using Laravel's dot notation convention. The pattern returned from the Closure will also get the placeholders applied, if you need that. Here's how you could manually define the partials subfolder pattern:

```php
<?php

namespace App\Providers;

use HotwiredLaravel\TurboLaravel\Facades\Turbo;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Turbo::resolvePartialsPathUsing('{plural}.partials.{singular}');

// Or...

Turbo::resolvePartialsPathUsing(fn ($model) => 'partials.{singular}');
}
}
```

You may also want to define your own pattern, which you can do by either specifying a string where you have the `{plural}` and `{singular}` placeholders available, but you can also specify a Closure, which will receive the model instance. On that Closure, you must return a string with the view location using the dot convention of Laravel. For instance, the subfolder pattern sets the config value to `{plural}.partials.{singular}` instead of the default, which is `{plural}._{singular}`. These will resolve to `comments.partials.comment` and `comments._comment` views, respectively.

The models' partials (such as a `comments/_comment.blade.php` for a `Comment` model) may only rely on having a single `$comment` variable passed to them. That's because Turbo Stream Model Broadcasts - which is an _optional_ feature, by the way - relies on these conventions to figure out the partial for a given model when broadcasting and will also pass the model to such partial, using the class basename as the variable instance in _camelCase_. Again, this is optional, you can customize most of these things or create your own model broadcasting convention. Read the [Broadcasting](/docs/{{version}}/broadcasting) section to know more.

## Turbo Stream Channel Names
Expand Down
12 changes: 12 additions & 0 deletions src/Facades/Turbo.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace HotwiredLaravel\TurboLaravel\Facades;

use Closure;
use HotwiredLaravel\TurboLaravel\Broadcasters\Broadcaster;
use HotwiredLaravel\TurboLaravel\NamesResolver;
use HotwiredLaravel\TurboLaravel\Turbo as BaseTurbo;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Facade;
Expand All @@ -23,6 +25,16 @@
*/
class Turbo extends Facade
{
public static function usePartialsSubfolderPattern()
{
static::resolvePartialsPathUsing('{plural}.partials.{singular}');
}

public static function resolvePartialsPathUsing(string|Closure $pattern)
{
NamesResolver::resolvePartialsPathUsing($pattern);
}

protected static function getFacadeAccessor()
{
return BaseTurbo::class;
Expand Down
18 changes: 15 additions & 3 deletions src/NamesResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@

namespace HotwiredLaravel\TurboLaravel;

use Closure;
use HotwiredLaravel\TurboLaravel\Models\Naming\Name;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;

class NamesResolver
{
protected static $partialsPathResolver = '{plural}._{singular}';

public static function resolvePartialsPathUsing(string|Closure $resolver)
{
static::$partialsPathResolver = $resolver;
}

public static function resourceVariableName(Model $model): string
{
return Str::camel(Name::forModel($model)->singular);
Expand All @@ -17,9 +25,13 @@ public static function partialNameFor(Model $model): string
{
$name = Name::forModel($model);

$resource = $name->plural;
$partial = $name->element;
$replacements = [
'{plural}' => $name->plural,
'{singular}' => $name->element,
];

$pattern = value(static::$partialsPathResolver, $model);

return "{$resource}._{$partial}";
return str_replace(array_keys($replacements), array_values($replacements), value($pattern, $model));
}
}
40 changes: 40 additions & 0 deletions tests/Models/NamesResolverTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace HotwiredLaravel\TurboLaravel\Tests\Models;

use HotwiredLaravel\TurboLaravel\Facades\Turbo;
use HotwiredLaravel\TurboLaravel\NamesResolver;
use HotwiredLaravel\TurboLaravel\Tests\TestCase;
use Illuminate\Database\Eloquent\Model;
use Workbench\Database\Factories\ArticleFactory;

class NamesResolverTest extends TestCase
{
/** @test */
public function resolves_partial_naming()
{
$article = ArticleFactory::new()->make();

$this->assertEquals('articles._article', NamesResolver::partialNameFor($article));
}

/** @test */
public function resolves_partial_naming_using_subfolder()
{
$article = ArticleFactory::new()->make();

Turbo::usePartialsSubfolderPattern();

$this->assertEquals('articles.partials.article', NamesResolver::partialNameFor($article));
}

/** @test */
public function resolves_using_custom_closure()
{
$article = ArticleFactory::new()->make();

Turbo::resolvePartialsPathUsing(fn (Model $model) => 'partials.article');

$this->assertEquals('partials.article', NamesResolver::partialNameFor($article));
}
}
2 changes: 2 additions & 0 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace HotwiredLaravel\TurboLaravel\Tests;

use HotwiredLaravel\TurboLaravel\Facades\Turbo;
use HotwiredLaravel\TurboLaravel\Facades\TurboStream;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Orchestra\Testbench\Concerns\WithWorkbench;
Expand All @@ -17,6 +18,7 @@ protected function setUp(): void
parent::setUp();

TurboStream::fake();
Turbo::resolvePartialsPathUsing('{plural}._{singular}');
}

protected function defineEnvironment($app)
Expand Down

0 comments on commit 235e89c

Please sign in to comment.