whitecube / nova-flexible-content

Flexible Content & Repeater Fields for Laravel Nova
MIT License
787 stars 234 forks source link

Call to undefined method Whitecube\NovaFlexibleContent\Layouts\Layout::getMedia() #365

Closed DieterVanhove closed 1 year ago

DieterVanhove commented 2 years ago

Hello, I'm a bit stuck wondering what I'm doing wrong. I'm trying to use the spatie/laravel-medialibrary & ebess/advanced-nova-media-library together with nova-flexible-content In nova it works wonderful but can't get the image show in my blade. If anybody has any suggestions or know what I'm doing wrong. Thanks in advance for your time :)

My layout looks as follows:

<?php

namespace App\Nova\Flexible\Layouts;

use Whitecube\NovaFlexibleContent\Layouts\Layout;
use Spatie\MediaLibrary\HasMedia;
use Ebess\AdvancedNovaMediaLibrary\Fields\Images;
use Whitecube\NovaFlexibleContent\Concerns\HasMediaLibrary;

class Image extends Layout implements HasMedia
{
    use HasMediaLibrary;

    protected $name = 'image';

    protected $title = 'Image';

    public function fields()
    {
        return [
            Images::make('Image', 'images'),
        ];
    }
}

And my model:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Whitecube\NovaFlexibleContent\Concerns\HasFlexible;

class Job extends Model implements HasMedia
{
    use HasFactory;
    use HasFlexible;
    use InteractsWithMedia;

    protected $guareded = [];

    protected $casts = [
        'publish_date' => 'date',
    ];
}

Controller:

public function show(Job $job)
    {
        return view('jobs.show', compact('job'));
    }

Blade:

 @foreach ($job->flexible('content') as $block)
        {{ $block->getMedia('images') }}
    @endforeach

composer:

"ebess/advanced-nova-media-library": "^4.0",
"spatie/laravel-medialibrary": "^10.0.0",
"whitecube/nova-flexible-content": "^1.0"
toonvandenbos commented 2 years ago

Hi @DieterVanhove, this does not seem to be an issue with the package, but I'm leaving it open in case somebody can help you with your question. I'm not a media-library user so my feedback won't help you much. However, I think our package is compatible with the packages you're mentioning, so maybe you'll find answers in other (maybe closed) issues discussing spatie's package integration.

salmanhijazi commented 2 years ago

You need to update your blade to either of these:

For single image:

@foreach ($job->flexible('content') as $block)
    <img src="{{ $block->getFirstMediaUrl('images') }}" />
@endforeach

For multiple images:

@foreach ($job->flexible('content') as $block)
    @foreach($block->getMedia('images') as $media)
        <img src="{{ $media->getUrl() }}" />
    @endforeach
@endforeach
DieterVanhove commented 2 years ago

Hi @salmanhijazi thanks for the reply, I've tried that also but the problem seems to be in the custom Layout. It has implements HasMedia & use HasMediaLibrary; but they don't seem to work. I've tried by adding those in the original layout file in the vendor and than it works perfectly. Haven't really found a proper solution to fixing this, vendor will hust be overwritten on deploy

salmanhijazi commented 2 years ago

You probably need to cast the flexible:

@foreach ($job->flexible('content', ['image' => \App\Nova\Flexible\Layouts\Image::class]) as $block)
    @foreach($block->getMedia('images') as $media)
        <img src="{{ $media->getUrl() }}" />
    @endforeach
@endforeach

Probably better to define that in the Job Model itself:

    public function getContentAttribute() {
        return $this->flexible('content', [
            'image' => \App\Nova\Flexible\Layouts\Image::class
        ]);
    }

Then you can define an Image attribute in the Layout as:

public function getImageAttribute()
{
    return $this->getFirstMediaUrl('images');
}

Then access in your blade file as:

@foreach ($job->content as $block)
        <img src="{{ $block->image }}" />
@endforeach
hayatbiralem commented 1 year ago

I just solved my problem and it was that I'm casting the wrong class.

Let me explain a little bit more:

Before

protected $casts = [
    'layouts' => \Whitecube\NovaFlexibleContent\Value\FlexibleCast::class
];

After

protected $casts = [
    'layouts' => \App\Casts\MyFlexibleCast::class
];

Conclusion

If you're using custom layout classes like me, you need to cast the right class.

DieterVanhove commented 1 year ago

@hayatbiralem solution worked for me! Thanks

ilwido commented 1 year ago

@DieterVanhove Can you please share with me a full example? I am struggling to make it works. Thank you !

DieterVanhove commented 1 year ago

@ilwido yes ofc, so in this example i have an multiple layouts I'll show you the image of it my image layout, here it's important to have HasMedia & HasMediaLibrary


namespace App\Nova\Flexible\Layouts;

use Whitecube\NovaFlexibleContent\Layouts\Layout;
use Spatie\MediaLibrary\HasMedia;
use Ebess\AdvancedNovaMediaLibrary\Fields\Images;
use Whitecube\NovaFlexibleContent\Concerns\HasMediaLibrary;

class Image extends Layout implements HasMedia
{
    use HasMediaLibrary;

    protected $name = 'image';

    protected $title = 'Image';

    public function fields()
    {
        return [
            Images::make('Image', 'images')
                ->enableExistingMedia()
                ->withResponsiveImages(),
        ];
    }
}

I made a custom cast called FlexibleCast, this includes all my layouts, and will be used in my model.


namespace App\Casts;

use Whitecube\NovaFlexibleContent\Value\FlexibleCast;
use App\Nova\Flexible\Layouts\Image;
use App\Nova\Flexible\Layouts\Listing;
use App\Nova\Flexible\Layouts\Quote;
use App\Nova\Flexible\Layouts\Text;
use App\Nova\Flexible\Layouts\TextWithFile;
use App\Nova\Flexible\Layouts\TextWithImage;
use App\Nova\Flexible\Layouts\Video;
use App\Nova\Flexible\Layouts\Title;

class MyFlexibleCast extends FlexibleCast
{
    protected $layouts = [
        'title' => Title::class,
        'text' => Text::class,
        'text-with-file' => TextWithFile::class,
        'text-with-image' => TextWithImage::class,
        'quote' => Quote::class,
        'listing' => Listing::class,
        'image' => Image::class,
        'video' => Video::class,
    ];
}

here is my project model, here it's important to use the FlexibleCast


namespace App\Models;

use App\Casts\MyFlexibleCast;
use App\Concerns\HasVisible;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Carbon\Carbon;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Whitecube\NovaFlexibleContent\Concerns\HasFlexible;

class Project extends Model implements HasMedia
{
    use HasFactory;
    use HasVisible;
    use InteractsWithMedia;
    use HasFlexible;

    protected $guareded = [];

    protected $casts = [
        'publish_date' => 'date',
        'content' => MyFlexibleCast::class
    ];

    // MediaLibrary
    public function registerMediaCollections(): void
    {
        $this->addMediaCollection('projects')->singleFile();
        $this->addMediaCollection('images');
    }

    public function getImagesAttribute($value)
    {
        return $value;
    }
}

And finally in my blade i read out my layouts as follows:

    @foreach ($project->content as $block)
        <x-blocks :block="$block"></x-blocks>
    @endforeach
in the x-block i can now call spatie's media
$block->getMedia('images')

its important to use the same key as in you model cast in this case i've called it content but you can name it whatever you want My class names could do a lot better but hope this helps!