diff --git a/docs/conventions.md b/docs/conventions.md index 82d3a90..5f571a0 100644 --- a/docs/conventions.md +++ b/docs/conventions.md @@ -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 + '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 diff --git a/src/Facades/Turbo.php b/src/Facades/Turbo.php index 155ea5d..9453812 100644 --- a/src/Facades/Turbo.php +++ b/src/Facades/Turbo.php @@ -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; @@ -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; diff --git a/src/NamesResolver.php b/src/NamesResolver.php index 2437e87..cfb7dba 100644 --- a/src/NamesResolver.php +++ b/src/NamesResolver.php @@ -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); @@ -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)); } } diff --git a/tests/Models/NamesResolverTest.php b/tests/Models/NamesResolverTest.php new file mode 100644 index 0000000..af93c7e --- /dev/null +++ b/tests/Models/NamesResolverTest.php @@ -0,0 +1,40 @@ +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)); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 07e81fc..8a53a0c 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -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; @@ -17,6 +18,7 @@ protected function setUp(): void parent::setUp(); TurboStream::fake(); + Turbo::resolvePartialsPathUsing('{plural}._{singular}'); } protected function defineEnvironment($app)