How to have one-time push in laravel blade
Asked Answered
V

9

18

I am trying to create a HTML widget with Laravel blade similar to the following (widget.blade.php):

@push('scripts')
   <script src="{{ asset('js/foo.js') }}"></script>
   <script>
   ...
   </script>
@endpush
@push('styles')
   <link href="{{ asset('css/bar.css') }}" rel="stylesheet">
@endpush
<div>
... HTML contents
</div>

and I use the widget in an other blade like:

<div>
  ... 
  @include('widget')
</div>
<div>
  ... 
  @include('widget')
</div>

The problem is when I use the widget multiple times in a page the 'scripts' and 'styles' repeated multiple times.

How can I prevent Laravel to push 'scripts' and 'styles' multiple times?

Veer answered 1/7, 2016 at 0:53 Comment(4)
Use @yield and @sectionCourse
@Course Could you elaborate more or make me an example please? NOTE: 'scripts' and 'styles' were use with other blades even the blades that include the widgetVeer
Based on my understanding you want to include the script files and your customized widget in all blades? Is it right?Course
@Course No. In that case, I will put my scripts and styles in master.bladeVeer
V
24

As of Laravel 7.25, Blade now includes a new @once component that will only render the items within the tags one time. https://laravel.com/docs/8.x/blade#the-once-directive

In the following answer I assumed you are familiar with Blade extension. This method has been tested on Laravel 5.2 and 5.3 (See note below).

After testing Ismail RBOUH's Answer (so please read it), It seems there are two problems with the solution:

1- The $isDisplayed variable is not in a same scope with the other included widgets so each @include push its scripts to stack. As a result I change it to:

Blade::directive('pushonce', function ($expression) {
    $isDisplayed = '__pushonce_'.trim(substr($expression, 2, -2));
    return "<?php if(!isset(\$__env->{$isDisplayed})): \$__env->{$isDisplayed} = true; \$__env->startPush{$expression}; ?>";
});
Blade::directive('endpushonce', function ($expression) {
    return '<?php $__env->stopPush(); endif; ?>';
});

2- The solution limit the use of @pushonce to one widget. i.e. in the case of 2 or more widgets (widget1.blade.php, widget2.blade.php, ...) it prevent to push other widgets scripts. So, I add domain to @pushonce with the following code:

Blade::directive('pushonce', function ($expression) {
    $domain = explode(':', trim(substr($expression, 2, -2)));
    $push_name = $domain[0];
    $push_sub = $domain[1];
    $isDisplayed = '__pushonce_'.$push_name.'_'.$push_sub;
    return "<?php if(!isset(\$__env->{$isDisplayed})): \$__env->{$isDisplayed} = true; \$__env->startPush('{$push_name}'); ?>";
});
Blade::directive('endpushonce', function ($expression) {
    return '<?php $__env->stopPush(); endif; ?>';
});

Usage:

widget1.blade.php

@pushonce('scripts:widget1')
   <script src="{{ asset('js/foo.js') }}"></script>
   <script>
   ...
   </script>
@endpushonce

widget2.blade.php

@pushonce('scripts:widget2')
   <script src="{{ asset('js/bar.js') }}"></script>
   <script>
   ...
   </script>
@endpushonce

NOTE FOR L 5.3: change the following line:

$domain = explode(':', trim(substr($expression, 2, -2)));

to

$domain = explode(':', trim(substr($expression, 1, -1)));
Veer answered 1/7, 2016 at 4:31 Comment(1)
It's better to copy @endpushonce directive code to your answer. Otherwise it's a little confusing.Ball
B
12

This Works for me.

@once
    @push('page_scripts')
        <script type="text/javascript">
           
        </script>
    @endpush
@endonce
Beaman answered 7/4, 2021 at 13:17 Comment(0)
J
6

One solution is to extend Blade by creating a pushonce directive as follows:

Blade::directive('pushonce', function ($expression) {

    $isDisplayed = '$__pushonce_'.trim(substr($expression, 2, -2));

    return "<?php if(!isset({$isDisplayed})): {$isDisplayed} = true; \$__env->startPush{$expression}; ?>";
});

Blade::directive('endpushonce', function ($expression) {

    return '<?php $__env->stopPush(); endif; ?>';
});

It must be added to AppServiceProvider boot method.

Usage:

@pushonce('scripts')
   <script src="{{ asset('js/foo.js') }}"></script>
   <script>
   ...
   </script>
@endpushonce

@pushonce('styles')
   <script src="{{ asset('js/foo.js') }}"></script>
   <script>
   ...
   </script>
@endpushonce

Please test it and let me know if that's help.

Jennifferjennilee answered 1/7, 2016 at 1:47 Comment(3)
Thanks. The solution is partially correct. The correct one is too long and Stackoverflow doesn't allow me to comment it. So I added it as a new answer.Veer
Okey. Good Luck ;)Jennifferjennilee
On line 3 \$__env->startPush{$expression}; is wrong. I spent much time to understand why this is not working. But at the end I found your code is not correct. Please edit it as \$__env->startPush({$expression}); . Add 2 parantheses.Targett
E
3

I've changed the code a bit:

Blade::directive('pushonce', function ($expression) {
    $var = '$__env->{"__pushonce_" . md5(__FILE__ . ":" . __LINE__)}';

    return "<?php if(!isset({$var})): {$var} = true; \$__env->startPush({$expression}); ?>";
});

Blade::directive('endpushonce', function ($expression) {
    return '<?php $__env->stopPush(); endif; ?>';
});

I'm using file and line number to set the variable, so can simply do:

@pushonce('scripts')
   Whatever
@endpushonce

Instead of:

@pushonce('scripts:widget1')
   Whatever
@endpushonce
Enstatite answered 4/8, 2017 at 20:47 Comment(0)
A
3

Apparently I don't have enough cred or something to reply to a comment above.

I recommend changing

$isDisplayed = '__pushonce_'.$push_name.'_'.$push_sub;

to

$isDisplayed = '__pushonce_' . md5($push_name.'_'.$push_sub);

because if your push looks like

@pushonce('after-styles')

PHP will not be happy with you because of the _ in the string.

Askew answered 10/8, 2018 at 20:50 Comment(1)
$push_sub = isset($domain[1]) ? $domain[1] : ''; Will also protect against not having "namespaced" push.Askew
R
3

Appstract/laravel-blade-directives provides an implementation of @pushonce('scripts:widget'), as well as other handy directives.

Just do composer require appstract/laravel-blade-directives and you're away.

Ruth answered 14/10, 2019 at 12:0 Comment(1)
This should be the accepted answer. I tried @once with @push inside of it, but it just kept adding multiple instances of the script. This is the only thing that actually worked for me.Fourchette
S
1

From Laravel 7.25.0 you can use @once blade directive.

https://github.com/laravel/framework/pull/33812

Surveillance answered 20/8, 2020 at 1:57 Comment(0)
S
0

From v7.25 you can use the @once directive. Using this with @push and @stack will get what you want.

Reference: https://laravel.com/docs/7.x/blade#the-once-directive

Example....

reveal.blade.php

<div>
   <h2 onclick="reveal(this)">{{$title}}</h2>
   <p class="hide">{{$text}}</p>
</div>

@once
@push('head.styles')
<style>
   .hide { display: none; }
</style>
@endpush
@endonce

@once
@push('body.scripts')
<script>
    window.reveal = function(element) {
        let nextElementStyle = element.nextElementSibling.style;
        nextElementStyle.display = nextElementStyle.display ==='block'?'none':'block';
    }
</script>
@endpush
@endonce

master.blade.php

<html>
   <head>
      @stack('head.styles')
   </head>
   <body>
      @include('reveal', ['title' => 'hello world', 'text'=>'This is some text'])
      @include('reveal', ['title' => 'hello world', 'text'=>'This is some text'])

      @stack('body.scripts')
   </body>
</html>
Stop answered 24/8, 2020 at 11:49 Comment(0)
P
0

For anyone who struggling with @pushonce with variable passing this works for me Laravel 10

Add this to AppServiceProvider boot() method

     Blade::directive('pushonce', function ($expression) {
        return 
        "<?php 
            [\$pushName, \$pushSub] = explode(':', trim({$expression}));
            \$key = '__pushonce_'.str_replace('-', '_', \$pushName).'_'.str_replace('-', '_', \$pushSub);
            if(! isset(\$__env->\$key)): \$__env->{\$key} = 1; \$__env->startPush(\$pushName);
            echo isset(\$__env->\$key) == 1 ? 'yes' : 'no';
        ?>";
    });
    Blade::directive('endpushonce', function ($expression) {
        return '<?php $__env->stopPush(); endif; ?>';
    });

Usage blade:

$variable = 'modals:test';
@pushonce($variable)

Usage layout:

@stack('modals')
Pete answered 3/8, 2023 at 12:58 Comment(1)
This answer isnt really relevant to this question. If you wish you could post a new question and provide this answer yourself, this makes it easier for it to be useful to other stackoverflow usersAnnabelle

© 2022 - 2024 — McMap. All rights reserved.