【问题标题】:A better way to update javascript in Laravel Livewire?在 Laravel Livewire 中更新 javascript 的更好方法?
【发布时间】:2021-12-25 00:26:01
【问题描述】:

我最近在构建Laravel LiveWire组件时遇到一个问题,其中JavaScript部分在选择输入更改时不会更新。该组件是 Chartist.js 库中的一个图表,它会在加载时显示,但是当我更改选择输入时,图表会消失。我想出了一个解决方案,但感觉很脏,任何人都有更好的解决方案。

line-chart.blade.php

<div class="mt-6">
    <h2 class="text-xl text-gray-600 py-3 font-bold">Location Views</h2>
    <div class="bg-white rounded-lg shadow overflow-hidden overflow-y-scroll">
        <div class="flex justify-end px-10 py-6">
            <select wire:model="days" class="block w-40 pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-yellow-500 focus:border-yellow-500 sm:text-sm rounded-md">
                <option value="30">Last 30 Days</option>
                <option value="365">Last 12 Months</option>
            </select>
        </div>
        <div id="line-chart" class="relative ct-chart">
            <div class="hidden absolute inline-block chartist-tooltip bg-white text-xs shadow text-center px-3 py-1 rounded-md w-36">
                <span class="chartist-tooltip-key"></span><br>
                <span class="chartist-tooltip-date"></span><br>
                <span class="chartist-tooltip-value"></span>
            </div>
        </div>
    </div>
</div>

@push('styles')
    <link rel="stylesheet" href="//cdn.jsdelivr.net/chartist.js/latest/chartist.min.css">
@endpush

@push('scripts')
    <script src="//cdn.jsdelivr.net/chartist.js/latest/chartist.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chartist-plugin-tooltips@0.0.17/dist/chartist-plugin-tooltip.min.js"></script>
@endpush

@push('js')
    <script>
        document.addEventListener('livewire:load', function () {
            setTimeout(() => {
                Livewire.emit('updateJS')
            })

            Livewire.on('updateJS', function () {
                var data = {
                    labels: @this.labels,

                    // Our series array that contains series objects or in this case series data arrays
                    series: @this.data,
                };

                var options = {
                    height: 300,
                    fullWidth: true,
                    chartPadding: 40,
                    axisX: {
                        offset: 12,
                        showGrid: true,
                        showLabel: true,
                    },
                    axisY: {
                        offset: 0,
                        showGrid: true,
                        showLabel: true,
                        onlyInteger: true,
                    },
                }

                new Chartist.Line('#line-chart', data, options).on("draw", function (data) {
                    if (data.type === "point") {
                        data.element._node.addEventListener('mouseover', e => {
                            const tooltip = document.getElementsByClassName('chartist-tooltip')

                            tooltip[0].style.top = data.y - 75 + 'px'
                            tooltip[0].style.left = data.x > 200 ? data.x - 150 + 'px' : data.x + 'px'

                            tooltip[0].classList.remove('hidden')

                            const key = document.getElementsByClassName('chartist-tooltip-key')
                            key[0].innerHTML = data.meta[1]

                            const meta = document.getElementsByClassName('chartist-tooltip-date')
                            meta[0].innerHTML = data.meta[0]

                            const value = document.getElementsByClassName('chartist-tooltip-value')
                            value[0].innerHTML = data.value.y === 1 ? data.value.y + ' view' : data.value.y + ' views'
                        })

                        data.element._node.addEventListener('mouseleave', e => {
                            const tooltip = document.getElementsByClassName('chartist-tooltip')
                            tooltip[0].classList.add('hidden')
                        })
                    }
                })
            })
        })
    </script>
@endpush

LineChart.php

<?php

namespace App\Http\Livewire\Components;

use Carbon\Carbon;
use Carbon\CarbonPeriod;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Collection;
use Livewire\Component;

class LineChart extends Component
{
    /**
     * @var Collection
     */
    public Collection $data;

    /**
     * @var array
     */
    public array $labels;

    /**
     * @var int
     */
    public int $days = 30;

    /**
     *
     */
    public function mount()
    {
        $this->data();

        $this->labels = $this->labels();
    }

    /**
     * Trigger mount when days is updated.
     */
    public function updatedDays()
    {
        $this->mount();

        $this->emit('updateJS');
    }

    /**
     * @return Application|Factory|View
     */
    public function render()
    {
        return view('livewire.components.line-chart');
    }

    /**
     * Generates the chart data.
     */
    public function data()
    {
        $locations = request()->user()->locations;

        if ($this->days === 30) {
            $this->data = $locations->map(function ($location) {
                return $this->getDatesForPeriod()->map(function ($date) use ($location) {
                    return [
                        'meta' => [
                            Carbon::parse($date)->format('M j'),
                            $location->name
                        ],
                        'value' => $location->views->filter(function ($view) use ($date) {
                            return $view->viewed_on->toDateString() === $date;
                        })->count()
                    ];
                })->toArray();
            });
        }

        if ($this->days === 365) {
            $this->data = $locations->map(function ($location) {
                return $this->getDatesForPeriod()->map(function ($date) use ($location) {
                    return [
                        'meta' => [
                            Carbon::parse($date)->format('M'),
                            $location->name
                        ],
                        'value' => $location->views->filter(function ($view) use ($date) {
                            return $view->viewed_on->month === Carbon::parse($date)->month;
                        })->count()
                    ];
                })->toArray();
            });
        }
    }

    /**
     * Creates the labels for the chart.
     *
     * @return array
     */
    protected function labels()
    {
        return $this->getDatesForPeriod()->map(function ($date) {
            if ($this->days === 30) {
                return Carbon::parse($date)->format('M j');
            } else {
                return Carbon::parse($date)->format('M');
            }
        })->toArray();
    }

    /**
     * Gets the dates for the specified period.
     *
     * @return Collection
     */
    protected function getDatesForPeriod()
    {
        if ($this->days === 30) {
            return collect(CarbonPeriod::create(now()->subDays($this->days)->toDateString(), now()->toDateString()))
                ->map(function ($date) {
                    return $date->toDateString();
                });
        }

        if ($this->days === 365) {
            return collect(now()->startOfMonth()->subMonths(11)->monthsUntil(now()))
                ->map(function ($date) {
                    return $date->toDateString();
                });
        }
    }
}

如果我将document.addEventListener('livewire:load', function () {} 更改为livewire:update,那么当我使用选择输入时,图表会按预期工作,但是在加载时图表不会显示。所以为了解决这个问题,我必须设置一个超时并触发一个事件,该事件在加载时显示图表,但也会在更新时显示图表。我觉得有更好的方法来做到这一点,我只是错过了一些东西。

【问题讨论】:

  • 我建议你使用 AlpineJS。 lib 在 livewire 请求之间保持状态,因此您不必担心 livewire 已经加载或设置超时。
  • @itepifanio 我不确定如何使用 AlpineJs 来改进这一点。我正在根据从前端传递的信息在后端运行查询。这超出了 AlpineJS 的能力。
  • Alpinejs 使用 @entangle 属性与 livewire 组件进行良好的通信。我为你添加了一个 sn-p。

标签: laravel laravel-livewire


【解决方案1】:

我从未使用过图表专家,但我通常使用这种方法(如果代码对您有意义,请告诉我)。

<div class="mt-6" x-data="{
    labels: @entangle('labels'), 
    series: @entangle('data'),
    chart: null
}"
x-init="() => {
    const options = {}; // I'll omit part of the code here
    chart = new Chartist.Line('#line-chart', data, options);
},
$watch('series', (dataChart) => {
     // I usually put both, the series data and the labels in an associative array on the livewire component back-end
    const { series, labels } = dataChart;
    chart.update(dataChart);
"
>
    <h2 class="text-xl text-gray-600 py-3 font-bold">Location Views</h2>
    <div class="bg-white rounded-lg shadow overflow-hidden overflow-y-scroll">
        <div class="flex justify-end px-10 py-6">
            <select wire:model="days" class="block w-40 pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-yellow-500 focus:border-yellow-500 sm:text-sm rounded-md">
                <option value="30">Last 30 Days</option>
                <option value="365">Last 12 Months</option>
            </select>
        </div>
        <div id="line-chart" class="relative ct-chart">
            <div class="hidden absolute inline-block chartist-tooltip bg-white text-xs shadow text-center px-3 py-1 rounded-md w-36">
                <span class="chartist-tooltip-key"></span><br>
                <span class="chartist-tooltip-date"></span><br>
                <span class="chartist-tooltip-value"></span>
            </div>
        </div>
    </div>
</div>

我认为从 livewire 组件中观察值更容易调试。当页面变大(并且有很多事件)时,使用事件来进行后端和前端之间的通信可能会带来调试挑战。

使用此代码,您的 char 将在 livewire 初始加载后加载(您无需检查 'livewire:load' 事件)。每次发送 updateJS 事件时都不会挂载图表实例,只会更新数据。

Ps:我不知道 Chartist 是如何工作的,所以我没有把代码放在chart.update(...) 上,但是由于它是一个图表库,它应该有一个更新数据的选项。我只是想向您展示我的方法,希望对您有所帮助。

【讨论】:

  • 我完全忘记了@entangle 指令。天才!话虽如此,现在我收到以下错误Cannot access 'series' before initialization。图表在初始加载时加载正常,但是当我更改选择时,图表上的数据消失了,我得到了那个错误。
  • 哈哈哇!所以我在不使用 AlpineJS 的情况下找到了修复,我删除了 setTimeout 和 Livewire.on 并添加了以下document.addEventListener('livewire:update', function () { chart.update({labels: @this.labels, series: @this.data}) }),就像一个魅力!尽管您上面的答案对我不起作用,但它使我找到了正确的答案,所以谢谢!
【解决方案2】:

为了让它工作,我删除了setTimeout()Livewire.On('updateJS'),并在livewire:load 内添加了另一个事件侦听器,用于侦听livewire:update,在这里我们更新图表

chart.update({labels: @this.labels, series: @this.data})

这里是完整的代码sn-p。我觉得这是一个更清洁的解决方案,不会觉得脏哈哈。

<script>
document.addEventListener('livewire:load', function () {
    var data = {
        labels: @this.labels,

        // Our series array that contains series objects or in this case series data arrays
        series: @this.data,
    };

    var options = {
        height: 300,
        fullWidth: true,
        chartPadding: 40,
        axisX: {
            offset: 12,
            showGrid: true,
            showLabel: true,
        },
        axisY: {
            offset: 0,
            showGrid: true,
            showLabel: true,
            onlyInteger: true,
        },
    }

    const chart = new Chartist.Line('#line-chart', data, options).on("draw", function (data) {
       if (data.type === "point") {
           data.element._node.addEventListener('mouseover', e => {
               const tooltip = document.getElementsByClassName('chartist-tooltip')

               tooltip[0].style.top = data.y - 75 + 'px'
               tooltip[0].style.left = data.x > 200 ? data.x - 150 + 'px' : data.x + 'px'

               tooltip[0].classList.remove('hidden')

               const key = document.getElementsByClassName('chartist-tooltip-key')
               key[0].innerHTML = data.meta[1]

               const meta = document.getElementsByClassName('chartist-tooltip-date')
               meta[0].innerHTML = data.meta[0]

               const value = document.getElementsByClassName('chartist-tooltip-value')
               value[0].innerHTML = data.value.y === 1 ? data.value.y + ' view' : data.value.y + ' views'
           })

           data.element._node.addEventListener('mouseleave', e => {
               const tooltip = document.getElementsByClassName('chartist-tooltip')
               tooltip[0].classList.add('hidden')
           })
        }
    })

    document.addEventListener('livewire:update', function () {
        chart.update({labels: @this.labels, series: @this.data})
    })
})
</script>

【讨论】:

    猜你喜欢
    • 2023-01-30
    • 2022-08-17
    • 2021-11-25
    • 1970-01-01
    • 2020-03-31
    • 2019-04-17
    • 2017-09-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多