如何让 one-time 推入 laravel blade

How to have one-time push in laravel blade

我正在尝试使用 Laravel blade 创建一个 HTML 小部件,类似于以下内容 (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>

我在另一个 blade 中使用小部件,例如:

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

问题是当我在一个页面中多次使用该小部件时,'scripts' 和 'styles' 重复了多次。

如何防止 Laravel 多次推送 'scripts' 和 'styles'?

一个解决方案是通过创建 pushonce 指令来扩展 Blade,如下所示:

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; ?>';
});

必须添加到 AppServiceProvider boot 方法中。

用法:

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

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

请测试一下,如果有帮助请告诉我。

从 Laravel 7.25 开始,Blade 现在包含一个新的 @once 组件,它只会渲染标签中的项目一次。 https://laravel.com/docs/8.x/blade#the-once-directive

在下面的回答中,我假设您熟悉 Blade extension。此方法已在 Laravel 5.2 和 5.3 上进行测试(请参阅下面的注释)。

经过测试(所以请阅读),解决方案似乎有两个问题:

1- $isDisplayed 变量与其他包含的小部件不在同一范围内,因此每个 @include 都将其脚本推送到堆栈。结果我将其更改为:

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- 该解决方案将@pushonce 的使用限制为一个小部件。即在 2 个或更多小部件(widget1.blade.php、widget2.blade.php、...)的情况下,它会阻止推送其他小部件脚本。因此,我使用以下代码将域添加到@pushonce:

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; ?>';
});

用法:

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

L 5.3 注意事项: 更改以下行:

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

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

我稍微修改了一下代码:

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; ?>';
});

我使用文件和行号来设置变量,所以可以简单地做:

@pushonce('scripts')
   Whatever
@endpushonce

而不是:

@pushonce('scripts:widget1')
   Whatever
@endpushonce

显然我没有足够的信用或其他东西来回复上面的评论。

我建议更改

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

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

因为如果你的推送看起来像

@pushonce('after-styles')

PHP不会因为字符串中的_而不满意你

Appstract/laravel-blade-directives 提供了 @pushonce('scripts:widget') 的实现,以及其他方便的指令。

只需 composer require appstract/laravel-blade-directives 即可。

从 Laravel 7.25.0 开始,您可以使用 @once blade 指令。

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

从 v7.25 开始,您可以使用 @once 指令。将此与 @push@stack 一起使用将得到您想要的。

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

例子....

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>

这对我有用。

@once
    @push('page_scripts')
        <script type="text/javascript">
           
        </script>
    @endpush
@endonce