【发布时间】: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。