4
votes

Anyone know if it's possible to extend a child blade?

My app has one common layout template, and then each page @extends from that. Each page may pull in a series of @includes for other chunks of HTML as required (eg modals).

@extends('common.layout')

@section('content')
    ... Some other content ...
    @include('modals.modal-1')
    @include('modals.modal-2')
@endsection

All of the modals have a lot of common boilerplate code (Bootstrap), so what I'd like to do is define a master model template, have all modals @extend from that, and then @include those in my pages as required. So /modals/modal-1.blade.php would look something like:

@extends('common.modals')

@section('modal-id', 'modal-1')
@section('modal-title','Modal title')

@section('modal-body')
    ... Some HTML / Blade here ...
@endsection

Every time I try though, the generated HTML is corrupted. In the above case, modal-1 would be shown twice because it appeared first, and modal-2 would not be present at all. Flip the order and modal-2 will appear twice.

I guess I could simply place my modal body in a variable and use it in the @include statement @include('modal.blade', ['body' => '<div>...</div>']), but using @extends feels more correct.

Any ideas?

2

2 Answers

5
votes

You can absolutely extend a Blade view from a view that has already been extended. However, you are mixing template inheritance with view inclusion and that is causing your weird results.

When inheriting templates with the @extend directive, you must always return the lowest child on the chain that you want. Let's say you have 3 generations of templates:

//grandparent.blade.php
<html>
<head>
    <title>My App</title>
</head>
<body>
    @yield('parent-content')
</body>
</html>

//parent.blade.php
@extends('grandparent')

@section('parent-content')
<div class="container">
    @yield('child-content')
</div>
@endsection

//child.blade.php
@extends('parent')

@section('child-content')
<div class="page">
    //stuff
</div>
@endsection

In this instance, you would return the child view and it would also contain the two generations of templates above it. But you could never return parent.blade.php and expect it to also return that child view. There might be 100 child views that extend the parent, so there would be no way of knowing which one.

Think of the @include directive as just a way to break up the HTML in your view nicely into smaller bits. Often you will use it for reusable pieces of code that you want to reference in multiple views. It is not the same thing as template inheritance though. Also remember that an included view will get all of the same data as its parent view (and you can even pass it more).

In your case, you have to decide what constitutes the base root of the page. Is the core of page modal-1? If so, you need to return modal-1from your controller as your child view and extend it up the chain. In that case, leave the file exactly like you have it in your post. Its parent view (common.modals) would need to be changed to this:

@extends('common.layout')

@section('content')
    ... Some other content ...

    @yield('modal-id')
    @yield('modals-title')
    @yield('modal-body')

    @include('modals.modal-2')
@endsection

Obviously you would put each of those yield statements where it makes sense on the page.

However, if modal-1 is not the core of the page, but just something extra that you want to include (like a widget), then you should include it like you are doing in the parent view. In that case, you will need to remove @extends directive from it and don't bother to wrap the main HTML in any section. It will just be passed to the view as-is. If you include sections in an included template, those sections must be yielded to in the view that includes it. So your modal-1 template would wind up looking like this:

<div>
    <p>HTML goes here. No need to extend anything or wrap in a section.
       You can still include {{$data}} though.
    </p>
</div>

@section('script')
    Including this section means that the view that is including this template
    already contains a @yield('script') directive.
    By including the @parent directive, this section will append that one.

@parent
@endsection
0
votes

I believe the proper way to include templates that already extends some others templates is to use @overwrite in place of @endsectionin your included template:

@extends('common.modals')

@section('modal-id', 'modal-1')
@overwrite
@section('modal-title','Modal title')
@overwrite

@section('modal-body')
    ... Some HTML / Blade here ...
@overwrite