在 Laravel Livewire 中更新 javascript 的更好方法?
A better way to update javascript in Laravel Livewire?
我最近 运行 在构建 Laravel Livewire 组件时遇到一个问题,其中 javascript 部分不会在 select 输入更改时更新。该组件是来自 Chartist.js 库的图表,它在加载时显示,但是当我更改 select 输入时,图表消失了。我想出了一个解决方案,但感觉很脏,任何人都有更好的解决方案。
行-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
,那么当我使用 select 输入时图表会按预期工作,但图表不会在加载时显示。所以为了解决这个问题,我必须设置一个超时并触发一个事件,该事件在加载时显示图表,但也会在更新时显示图表。我觉得有更好的方法来做到这一点,我只是错过了一些东西。
我从未使用过图表师,但我通常使用这种方法(如果代码对您有意义,请告诉我)。
<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 组件观察值更容易调试。当页面变大(并且有很多事件)时,使用事件进行后端和前端之间的通信会给调试带来挑战。
使用此代码,您的字符将在 livewire 初始加载后加载(您无需检查 'livewire:load' 事件)。每次派发 updateJS 事件时不会挂载图表实例,只会更新数据。
Ps:我不知道 Chartist 是如何工作的,所以我没有把代码放在 chart.update(...)
上,但由于它是一个图表库,它应该有更新数据的选项.我只是想告诉你我的方法,希望对你有帮助。
为了让它工作,我删除了 setTimeout()
和 Livewire.On('updateJS')
并在 livewire:load
中添加了另一个监听 livewire:update
的事件监听器,在这里我们'用
重新更新图表
chart.update({labels: @this.labels, series: @this.data})
这是完整的代码片段。我觉得这是一个更干净的解决方案,不会让人觉得脏哈哈。
<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>
我最近 运行 在构建 Laravel Livewire 组件时遇到一个问题,其中 javascript 部分不会在 select 输入更改时更新。该组件是来自 Chartist.js 库的图表,它在加载时显示,但是当我更改 select 输入时,图表消失了。我想出了一个解决方案,但感觉很脏,任何人都有更好的解决方案。
行-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
,那么当我使用 select 输入时图表会按预期工作,但图表不会在加载时显示。所以为了解决这个问题,我必须设置一个超时并触发一个事件,该事件在加载时显示图表,但也会在更新时显示图表。我觉得有更好的方法来做到这一点,我只是错过了一些东西。
我从未使用过图表师,但我通常使用这种方法(如果代码对您有意义,请告诉我)。
<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 组件观察值更容易调试。当页面变大(并且有很多事件)时,使用事件进行后端和前端之间的通信会给调试带来挑战。
使用此代码,您的字符将在 livewire 初始加载后加载(您无需检查 'livewire:load' 事件)。每次派发 updateJS 事件时不会挂载图表实例,只会更新数据。
Ps:我不知道 Chartist 是如何工作的,所以我没有把代码放在 chart.update(...)
上,但由于它是一个图表库,它应该有更新数据的选项.我只是想告诉你我的方法,希望对你有帮助。
为了让它工作,我删除了 setTimeout()
和 Livewire.On('updateJS')
并在 livewire:load
中添加了另一个监听 livewire:update
的事件监听器,在这里我们'用
chart.update({labels: @this.labels, series: @this.data})
这是完整的代码片段。我觉得这是一个更干净的解决方案,不会让人觉得脏哈哈。
<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>