I have a component that it has v-for to showing multiple repetition of data, I use vue3 with composition API, When I initialize table of data and log it, it shows the right value what I want, but the length it is 0 and it's not rendring on template, I don't know why !!
template :
<template>
<base-page>
<form #submit.prevent="submitForm">
<base-header>
<template #title>
<nav class="py-2 rounded-md w-full">
<ol class="list-reset flex">
<li>
<router-link
to="/dashboard"
class="text-blue-600 hover:text-blue-700"
>Accueil</router-link
>
</li>
<li><span class="text-gray-500 mx-2">/</span></li>
<li class="text-gray-500">
Ajouter une réservation pour {{ terrain.name }}
</li>
</ol>
</nav>
</template>
</base-header>
<div class="mt-4">
<div class="flex text-center grid grid-cols-5">
{{sessionList}}
<div v-for="(item, index) in sessionList" :key="index" class="rounded-lg ml-2 shadow-lg bg-white max-w-sm">
<h5 class="text-gray-900 text-center font-medium m-2">Card title</h5>
<a href="#!">
<img class="rounded-t-lg m-auto w-16 h-16" src="/img/green-ball.png" alt=""/>
</a>
<div class="p-2">
<p class="text-gray-700 text-base mb-4">
Some quick example text to {{index}}
</p>
<button type="button" class=" inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase rounded shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out">
Réserver
</button>
</div>
</div>
</div>
</div>
</form>
</base-page>
</template>
script :
<script setup>
import {ref,onMounted} from "vue"
import {mdiContentSavePlusOutline} from "#mdi/js"
import moment from "moment"
moment.locale('fr');
const terrain = ref({
id: 1,
name: "terrain 1",
capacity: 11,
duration: 1,
price: 8,
external: true,
complexe: "Stade 1",
formatted_created_at: "10/10/2022",
})
let date = new moment()
let datesList = []
let sessionList = []
let nextDate = date
for (let index = 0; index < 15; index++) {
datesList.push(nextDate.format('YYYY-MM-DD'))
let tab = []
let hourDate = nextDate
for (let i = 0; i < 4; i++) {
let debut = moment(nextDate).add(i+1,'hours').format('HH:mm:ss')
let fin = moment(nextDate).add(i+2,'hours').format('HH:mm:ss')
tab.push({
debut : debut,
fin : fin,
reserved : i % 2 == 0
})
}
sessionList[datesList[index]] = tab
nextDate = date.add(1,'days');
}
console.log(datesList)
console.log(sessionList)
</script>
log :
Edit
the sessionList variable it would have like this format :
You define sessionList as an array, but you are adding items to it like an object. So the arrays length is still 0, but you added properties with the dates as you can see in the screenshot. This is also why nothing is rendered.
I am not sure what you want to do with the date, but maybe just add it as another property of the tab object and then push this to the array:
tab.push({
debut : debut,
fin : fin,
reserved : i % 2 == 0,
date: datesList[index]
});
sessionList.push(tab);
EDIT after updated question:
Define sessionList as a Map:
const sessionList = new Map();
Then adding entries:
sessionList.set(datesList[index], tab);
In the template you then have to replace the v-for with:
<div v-for="[key, value] in sessionList" :key="key" ...>
Related
I have a property called slug: 1 for each object. I want to print them in the numeric order of the slug property, I tried using .sort((a,b) => a-b)
on the v-for="(link ) in blog.children.sort((a,b) => a-b)"
But could not make it work it will still print like this: 4 1 2 5 3
What i need to pint in html is: 1 2 3 4 5
here is my code:
<template>
<span v-for="blog in navigation" :key="blog._path">
<h3 class="text-lg mb-1 light-text-1">Lecciones</h3>
<NuxtLink
v-for="(link ) in blog.children.sort((a,b) => a-b)"
:key="link.slug"
:to="link._path"
class="flex flex-row space-x-1 no-underline prose-sm font-normal py-1 px-4 -mx-4"
>
<ol class="pl-1">
<li>
<span class="light-text-1 ">
{{ link.slug }}
</span>
</li>
</ol>
</NuxtLink>
</span>
</template>
<script setup>
const { data: navigation } = await useAsyncData('navigation', () => fetchContentNavigation('cuentos-biblicos'))
</script>
You must sort by slug:
example:
blog.children.sort((a,b) => a.slug-b.slug)
it is the initial state of the redux toolkit, cartSlice and it is an object and products is an array having oneProduct as an object containing product information like image, price, description, etc.
This is cart slice
import { createSlice } from "#reduxjs/toolkit";
const cartSlice = createSlice({
name: "cart",
initialState: {
products: [],
quantity: 0,
total: 0,
},
reducers: {
addProduct: (state, action) => {
state.products.push(action.payload);
state.quantity += 1;
state.total += action.payload.price * action.payload.quantity;
},
removeProduct: (state, action) => {
let index = state.products.indexOf(action.payload)
state.quantity -= action.payload
state.products.splice(index, 1)
}
},
});
export const { addProduct ,removeProduct} = cartSlice.actions;
export default cartSlice.reducer;
THIS OBJECT IS initilaState of redux. In CONSOLE OF BROWSER
Object
products
:
Array(2)
0
:
{status: 'Specific products is here', oneProduct: {…}}
1
:
{status: 'Specific products is here', oneProduct: {…}}
length
:
2
[[Prototype]]
:
Array(0)
quantity
:
2
total
:
NaN
[[Prototype]]
:
Object
The problem is I am unable to map products array console above . Below is the code for the orders page and kindly Look at map Method
import React, { useState } from 'react'
import { Link } from 'react-router-dom';
import { useSelector } from 'react-redux';
const Order = () => {
const cart = useSelector((state) => state.cart)
console.log(cart)
const quantity = useSelector((state) => state.cart.quantity)
const [counter, setCounter] = useState(1);
const counterNumber = (type) => {
if (type === "inc") {
setCounter((prev) => prev + 1);
} else {
counter > 1 && setCounter((prev) => prev - 1);
}
};
return (
<>
<h1 className='font-bold text-xl text-center my-5'>CART PAGE</h1>
<div className='flex justify-around flex-wrap my-5 space-x-2'>
<div className='flex flex-col shadow-md p-2'>
{cart.products.oneProduct?.map((item)=>(
<div className="Product_info flex align-middle m-auto space-x-2 ">
<div className="product_image my-auto" key={item._id}>
<img src={item.img} alt="" className='w-16 h-16 ' />
</div>
<div className="details">
<h1 className='font-bold text-lg m-3'>{item.title}</h1>
</div>
<div className="price my-auto ">
<h1 className='font-bold text-2xl cursor-pointer'>$ {item.price}</h1>
</div>
<div className="ince_dec flex align-middle justify-center space-x-2 mr-5">
<div className="dec font-extrabold text-3xl my-auto" onClick={() => counterNumber("dec")}>-</div>
<h1 className='font-bold text-2xl my-auto'> {counter} </h1>
<div className="inc font-extrabold text-3xl my-auto cursor-pointer" onClick={() => counterNumber("inc")}>+</div>
</div>
<hr />
</div>
))}
</div>
<div className=" sideCart ml-1 text-white bg-blue-400 p-10 ">
<h1 className="text-center text-black font-extrabold">Product Quantity is: </h1>
<div className="text-white text-center font-bold my-2">{cart.products.length<1 ?"Cart is empty": quantity }</div>
<ul >
<div className="items flex align-middle items-center ">
</div>
</ul>
<div className="bton flex justify-center align-middle">
<Link to="/checkout">
<button className=" cart flex ml-4 text-white bg-black border-0 py-2 px-4 focus:outline-none lg:w-32 hover:bg-indigo-600 rounded md:w-4 md:px-6 text-sm">Checkout Now</button>
</Link>
</div>
</div>
</div>
</>
)
}
export default Order
In my laravel vue application I have following datatable with some filters in a vue component(department-user-list.vue).
<template>
<div>
<cs-card
:cardButton="false"
:title="`Team members`"
>
<template slot="header-action">
<div class="inline-block mr-4" direction-from="top">
<open-overlay identifier="corporateInviteEmployeeToDepartmentModal">
<cs-button size="small" variant="secondary">
Invite team member
</cs-button>
</open-overlay>
</div>
<div class="inline-block" direction-from="top">
<cs-button #click="redirectToAssignation" size="small">
Assign team member
</cs-button>
</div>
</template>
<Datatable
v-model="selectedForAction"
:data="dataset"
:headers="headers"
:is-loading="isLoading"
:has-action-bar-column="true"
:key-id="`id`"
:search-panel="{placeholder: 'Search team member...'}"
#change="handlePaginationChange"
#paginate="loadDepartmentEmployees"
>
<!--Filter Slot-->
<template v-slot:filters>
<!--Nationality filter-->
<div class="w-2/12 pl-4 h-auto">
<cs-multiple-select
v-model="nationalitiesFilter"
:options="countries"
key-id="id"
label="name"
name="nationality"
placeholder="Nationality"
>
</cs-multiple-select>
</div>
<!--Certificate Status filter-->
<div class="w-6/12 pl-4 h-auto">
<cs-multiple-select
v-model="certificateStatusFilter"
:options="certificateStatus"
name="certificateStatusFilter"
placeholder="Qualification status"
#input="loadDepartmentEmployees"
/>
</div>
<!--Matrix Status filter-->
<div class="w-4/12 pl-4 h-auto">
<cs-multiple-select
v-model="matrixStatusFilter"
:options="matrixStatus"
key-id="value"
label="label"
name="matrixStatusFilter"
placeholder="Matrix status"
#input="loadDepartmentEmployees"
/>
</div>
<!--Employee Type filter-->
<div class="w-4/12 pl-4 h-auto">
<cs-multiple-select
v-model="selectedEmployeeTypes"
:options="employeeType"
key-id="value"
label="label"
name="selectedEmployeeTypes"
placeholder="Employee type"
#input="loadDepartmentEmployees"
>
</cs-multiple-select>
</div>
</template>
<!--Table Header-->
<template v-slot:header.country="{ header}">
<span class="material-icons-outlined">language</span>
</template>
<!--Table Body-->
<template v-slot:item.name="{ item }">
<div class="flex items-center cursor-pointer">
<div class="rounded-full w-8 h-8 mr-4 overflow-hidden">
<img :src="item.profile_image[0].url" alt=""
class="w-full h-full object-cover">
</div>
<a :href="employeeDetailRoute(item)">
<span class="text-certstyle-titles font-bold leading-loose">{{ item.name }}</span>
</a>
<span
v-if="item.is_subcontractor && item.company_name"
:title="item.company_name"
class="text-certstyle-text-light bg-certstyle-background flex font-semibold cursor-help text-xs rounded mx-2 py-1 px-2"
>
{{ item.company_name.substring(0,10) }}
<span v-if="item.company_name.length > 10">...</span>
</span>
</div>
</template>
<template v-slot:item.job_title="{ item }">
{{ item.current_jobtitle || item.department_jobtitle }}
</template>
<template v-slot:item.country="{ item }">
<span v-if="item.country" class="font-normal">
<country-flag width="w-5" :country-code="item.country.country_code"></country-flag>
</span>
</template>
<template v-slot:item.certificate_status="{ item }">
<div class="status--summary--component inline-block mr-2 relative"
#click="getValidityStatus(item.certificate_matrix) !== 'all valid' &&
getValidityStatus(item.certificate_matrix) !== '-'
? openCertificatesModal(item) : null
">
<label :class="getValidityStatusClass(item.certificate_matrix)" class="badge">
{{ getValidityStatus(item.certificate_matrix) }}
</label>
</div>
</template>
<template v-slot:item.matrix_status="{ item }">
<div class="status--summary--component inline-block mr-2 relative"
#click="getMatrixStatus(item.certificate_matrix) === 'non compliant'
? openCertificatesModal(item)
: null
">
<label :class="getMatrixStatusClass(item.certificate_matrix)" class="badge">
{{ getMatrixStatus(item.certificate_matrix) }}
</label>
</div>
</template>
<template v-slot:actionItems="slotProps">
<DatatableActionbarItem
:slot-props="slotProps"
identifier="removeConfirmationModal"
label="Remove"
variant="danger"
/>
</template>
<template v-slot:rowActionItems="slotProps">
<DatatableActionbarItem
icon=""
label="Contact details"
#click.native="openContactDetailsModal(slotProps.item)"
:slot-props="slotProps"
/>
</template>
</Datatable>
<modal-md
v-if="selectedEmployee !== null"
:options="{ useDefaultContentStyling: false }"
:identifier="`statusSummaryComponent`">
<template slot="modal_title">
Qualifications
<span v-if="selectedEmployee !== null && selectedEmployee !== undefined">
{{ selectedEmployee.name }}
</span>
</template>
<div class="bg-white" slot="modal_content">
<div
class="flex items-center justify-between text-certstyle-text-light bg-white border-certstyle-border border-b text-sm py-2 px-10">
<cs-dashboard-table-header-item-unstyled :item="`statusSummaryCertificateTitle`">
<p>Qualification</p>
</cs-dashboard-table-header-item-unstyled>
<cs-dashboard-table-header-item-unstyled :item="`statusSummaryCertificateStatus`">
<p>Validity Status</p>
</cs-dashboard-table-header-item-unstyled>
<cs-dashboard-table-header-item-unstyled :item="`statusSummaryMatrixStatus`">
<p>Matrix Status</p>
</cs-dashboard-table-header-item-unstyled>
</div>
<!--Certificates-->
<div class="text-certstyle-titles">
<div v-for="certificate in selectedEmployee.certificate_matrix"
v-if="selectedEmployee.certificate_matrix.length > 0"
class="table--row flex items-center justify-between border-certstyle-border last:border-b-0 border-b px-10 py-4">
<!-- Title -->
<cs-dashboard-table-item-unstyled
class="w-1/2"
:item="`statusSummaryCertificateTitle`">
<p :title="certificate.title" class=" ">
{{ certificate.title | limitString(30, '...') }}
</p>
</cs-dashboard-table-item-unstyled>
<cs-dashboard-table-item-unstyled class="w-32" :item="`statusSummaryCertificateStatus`">
<div class="flex items-center justify-start w-full">
<!--Expired styling-->
<label v-if="certificate.matrix_status === 0"
class="badge badge-danger">
-
</label>
<!--Expires styling-->
<label v-if="certificate.matrix_status && certificate.expired === 1"
class="badge badge-danger">
expired
</label>
<!--Expires styling-->
<label v-if="certificate.matrix_status && certificate.expire_soon === 1"
class="badge badge-danger">
expires soon
</label>
<!--Active styling-->
<label
v-if="certificate.matrix_status && certificate.expire_soon === 0 && certificate.expired === 0"
class="badge badge-success">
active
</label>
</div>
</cs-dashboard-table-item-unstyled>
<cs-dashboard-table-item-unstyled
class="w-32"
:item="`statusSummaryMatrixStatus`">
<!--Active styling-->
<label v-if="certificate.matrix_status"
class="badge badge-success">
compliant
</label>
<label v-else
class="badge badge-danger">
non-compliant
</label>
</cs-dashboard-table-item-unstyled>
</div>
<div v-else
class="table--row flex items-center justify-between border-certstyle-border last:border-b-0 border-b px-10 py-4">
<cs-dashboard-table-item-unstyled
class="w-1/2"
:item="`statusSummaryCertificateTitle`">
No certificates found
</cs-dashboard-table-item-unstyled>
<cs-dashboard-table-item-unstyled class="w-32"
:item="`statusSummaryCertificateStatus`">
</cs-dashboard-table-item-unstyled>
<cs-dashboard-table-item-unstyled
class="w-32"
:item="`statusSummaryMatrixStatus`">
</cs-dashboard-table-item-unstyled>
</div>
</div>
</div>
</modal-md>
</cs-card>
<contact-details
v-if="userToShowContactDetails"
:user="userToShowContactDetails"
/>
<add-employee-modal
:countries="countries"
:identifier="`addEmployeeModal`"
:invite-modal-title="`Invite to department`"
:suggestion-url="userSuggestionApiURL"
:title="`Assign to ${department ? department.name: ''}`"
#inviteEmployees="handleInvitedEmployees"
#selectEmployee="addEmployeeToDepartment"
#removeEmployee="handleRemoveEmployee"
/>
<cs-confirmation-modal
v-if="checkRole(['admin', 'planner'])"
#proceed="handleRemoveEmployee"
identifier="removeConfirmationModal">
<div class="text-certstyle-titles font-normal" slot="content">
Removing employees from this department can not be undo.
</div>
<div slot="cancelButton"
class="cursor-pointer hover:bg-certstyle-background border border-certstyle-border rounded px-6 py-2 text-certstyle-text-light mr-4">
Cancel
</div>
<cs-button slot="proceedButton">
Remove
</cs-button>
</cs-confirmation-modal>
</div>
</template>
This list and the filters works fine but when I try to search a user by typing their names, it kept giving me following error,
Duplicate keys detected: '1025'. This may cause an update error.
found in
---> at resources/js/components/reusable/datatable/Datatable.vue
at resources/js/components/reusable/csCard.vue
at resources/js/components/dashboard/communities/department/one-departments/department-user-list.vue
But when I try to filter the list by some other param, they get filtered without any error.
I'm struggling to find what I'm doing wrong here....
UPDATE
following is my non-filtered json data for a single user
{
"id": 1038,
"unique_id": "0a3938c1-07d5-3884-9df0-a8fe3201a3e5",
"first_name": "Mango",
"last_name": "F1",
"job_title": null,
"email": "mangoF1#gmail.com",
"phone_number": null,
"phone_country_calling_code": null,
"country_id": null,
"company_name": null,
"is_subcontractor": 0,
"current_jobtitle": "Deck Foreman",
"department_jobtitle": "Rigging Engineers",
"language": "en",
"email_verified_at": "2022-04-12T12:47:55.000000Z",
"gender": null,
"state": null,
"postal_code": null,
"house_number": null,
"street": null,
"city": null,
"date_of_birth": null,
"city_of_birth": null,
"emergency_number": null,
"opt_in": null,
"created_at": "2022-04-12T12:46:34.000000Z",
"updated_at": "2022-04-12T12:47:55.000000Z",
"emergency_number_country_calling_code": null,
"deleted_at": null,
"has_ip_restriction": 0,
"address": null,
"latitude": null,
"longitude": null,
"place_id": null,
"bouwpas_worker_id": null,
"bouwpas_certificate_id": null,
"certificate_matrix": [
{
"id": 463,
.....
}
]
},
Following is my controller function,
public function index($locale, Company $company, Department $department, Request $request)
{
$this->authorize('viewUsers', [Department::class, $company, $department]);
$users = $department->usersWithSubcontractors()
// ->notApiUser()
->select(
'users.id',
'unique_id',
'first_name',
'last_name',
'job_title',
'email',
'phone_number',
'unique_id',
'phone_country_calling_code',
'country_id',
'company_name',
'is_subcontractor',
)->addSelect([
'current_jobtitle' => JobTitle::selectRaw('IFNULL(project_specific_job_titles.title, job_titles.title)')
->join('project_track_records', 'project_track_records.user_id', 'users.id', 'inner')
->join('project_specific_job_titles', 'project_track_records.project_specific_job_title_id', 'project_specific_job_titles.id', 'inner')
->whereColumn('job_titles.id', 'project_specific_job_titles.job_title_id')
->whereDate('project_track_records.start_date', '<=', Carbon::now())
->whereDate('project_track_records.end_date', '>=', Carbon::now())
->limit(1),
'department_jobtitle' => JobTitle::selectRaw('title')->whereColumn('job_titles.id', 'department_user.job_title_id'),
])->when(request('q'), function ($q) {
$q->where(function ($q) {
$q->whereRaw("CONCAT_WS(' ', `first_name`, `last_name`) like '%" . request('q') . "%'");
});
})->when($request->get('employeeType'), function ($q) use ($request) {
$q->whereIn('users.is_subcontractor', $request->get('employeeType'));
})->paginate(\request('per_page', config('repository.pagination.limit')));
$users->load('country');
$users->append(['profile_image']);
$users->makeVisible(['unique_id']);
$users->map(fn ($item) => $item->certificate_matrix = (new GetCertificationMatrixQueryAction())->execute($item->id, $department->id));
if ($request->filled('certificateStatus')) {
$valid = in_array('valid', $request->get('certificateStatus'));
$expire_soon = in_array('expire soon', $request->get('certificateStatus'));
$expired = in_array('expired', $request->get('certificateStatus'));
if ($valid) $users->setCollection($users->filter(fn ($item) => $item->certificate_matrix->filter(fn ($certificate) => $certificate->matrix_status && !$certificate->expire_soon && !$certificate->expired)->count() === $item->certificate_matrix->count())->values());
if ($expire_soon && !$expired) $users->setCollection($users->filter(fn ($item) => $item->certificate_matrix->filter(fn ($certificate) => $certificate->matrix_status && $certificate->expire_soon)->count() > 0)->values());
if ($expired && !$expire_soon) $users->setCollection($users->filter(fn ($item) => $item->certificate_matrix->filter(fn ($certificate) => $certificate->matrix_status && $certificate->expired)->count() > 0)->values());
}
if ($request->filled('matrixStatus')) {
$compliant = in_array('compliant', $request->get('matrixStatus'));
$non_compliant = in_array('non-compliant', $request->get('matrixStatus'));
if ($non_compliant && !$compliant)
$users->setCollection($users->filter(fn ($item) => $item->certificate_matrix->filter(fn ($certificate) => $certificate->matrix_status === 0 || $certificate->expire_soon || !$certificate->expired)->count() > 0)->values());
if ($compliant && !$non_compliant)
$users->setCollection($users->filter(fn ($item) => $item->certificate_matrix->filter(fn ($certificate) => $certificate->matrix_status && !$certificate->expire_soon && !$certificate->expired)->count() == $item->certificate_matrix->count())->values());
}
return response()->json($users);
}
The sample data for single user has duplicate id values in certificate_matrix array (2nd and 11th record both have id of 443)
This would probably be causing an issue in the v-for loop at (where you do not have any :key actually)
<!--Certificates-->
<div class="text-certstyle-titles">
<div v-for="certificate in selectedEmployee.certificate_matrix"
v-if="selectedEmployee.certificate_matrix.length > 0"
class="table--row flex items-center justify-between border-certstyle-border last:border-b-0 border-b px-10 py-4">
Or at any other place where certificate_matrix records are being looped over
Using composite key combining value of two fields to generate unique key
One way to try to ensure unique keys on frontend is to have a combination (kind of like composite key index) as ${id}-${matrix_status} if that combination is to remain unique.
Using loop iteration index as composite suffix to generate unique key
If there is no other field value which can be combined to ensure unique keys for child nodes within v-for then the ${id}-${index} can be used to generate unique keys - eg:
<div v-for="(certificate, index) in selectedEmployee.certificate_matrix"
:key="`${certificate.id}-${index}`" //To ensure uniqueness of key
v-if="selectedEmployee.certificate_matrix.length > 0"
class="table--row flex items-center justify-between border-certstyle-border last:border-b-0 border-b px-10 py-4">
Using uuid as unique key
Another way is to add a uuid column on the database tables and then use the uuid as key in the <DataTable /> component markup including subcomponents where :key or :key-id are used.
Laravel makes it very easy to generate Uuid v4 - uuids via (string) Str::orderedUuid(); And also has $table->uuid('uid')->index() column type for migration files.
Can even have uid stored when a new model record is being created/persisted
// In any model class which has a uuid column
public static function booted()
{
static::creating(function ($model) {
$model->uid = Str::orderedUuid()->toString();
});
}
Laravel Docs - Helpers - Str Ordered Uuid
Laravel Docs - Migrations - Uuid Column Type
Laravel Docs - Uuid - Validation Rule
Update
Based on feedback via comment
Thank you for your answer, I have updated the question with the correct backend controller. The reason for id duplication is one user can have multiple departments. Because of that my eloquent gives me results with multiple user id's for some results. When I tried to group the results by user.id, it gives me an 500 error, and I need to pass the user.id as the key rather than passing uuid since the id has used for other functions, like user deletion and all... Any way to group the result from the backend to get only unique user ids?
To ensure only unique user records being sent via response from the controller, filter out duplicate records for the $users collection via unique() method available on Collection
//...method code
$users->setCollection($users->unique('id')->values());
return response()->json($users);
The error message tells you that you have duplicates and you are confused about the reason of getting such an error message, when the id of the user is unique (probably a primary key). However, it is important to note that you load your users with departments and if a user has multiple departments, then you will get that user multiple times. You can group the results to solve the issue.
I'm not entirely sure how to toggle the popover based on a boolean, in my case: if there are search results. I thought I could just use the open attribute. Any help is appreciated!
To sum up this component:
Type in a search term in the input
An API call is being made and results are returned from te API
The PopoverPanel should then be opened
<template>
<form :action="action" method="GET" class="flex-1">
<Popover :open="searchResults.length > 0" class="relative w-full">
<PopoverOverlay
:class="`bg-black ${open ? 'fixed inset-0 opacity-10' : 'opacity-0'}`"
#click="() => emptySearchResults()"
/>
<div class="relative">
<label for="search" class="sr-only">Search...</label>
<input
id="search"
v-model="searchTerm"
type="search"
placeholder="Search..."
class="h-10 w-full px-4 py-2 text-sm rounded-lg placeholder-gray-400 focus:outline-none"
>
</div>
<PopoverPanel class="absolute top-full right-0 w-full mt-3 py-2 px-2 bg-gray-700 rounded-lg shadow-lg">
<a
v-for="result in searchResults"
:key="result.id"
:href="result.url"
class="flex items-center font-semibold p-2 text-sm text-white leading-none rounded-lg hover:bg-gray-600"
>
<span class="mr-3 py-1 px-2 text-xs text-gray-900 leading-none bg-gray-300 rounded-full">
{{ result.module }}
</span>
{{ result.title }}
</a>
</PopoverPanel>
</Popover>
</form>
</template>
<script setup>
import { ref, watch } from 'vue';
import { Popover, PopoverPanel } from '#headlessui/vue';
import debounce from '#/Helpers/debounce';
import xhr from '#/Helpers/xhr';
const searchTerm = ref('');
const searchResults = ref([]);
const getSearchResults = debounce(async (value) => {
if (value === '') return;
const { data } = await xhr.post('search', { q: value });
const { results } = await data;
searchResults.value = results;
}, 250);
const emptySearchResults = () => {
searchTerm.value = '';
searchResults.value = [];
};
watch(searchTerm, (value) => getSearchResults(value));
</script>
friends
Problem
I have the problem that when I am in the video section watching a particular episode, in the local storage the src of the video that I saw before is stored.
My intention is to eliminate the value of the local storage once it leaves the video section.
Template Video
<template>
<!-- Main -->
<main class="flex-1 bg-grey-lightest z-0 py-5 px-0">
<div class="flex flex-wrap max-w-5xl mx-auto">
<!-- main col -->
<div class="w-full md:flex-1">
<!-- player -->
<div class="bg-black relative mb-3">
<video
id="video"
controls
><source :src="videos.video" type="video/mp4"></video>
</div>
<!-- video info -->
<div class="flex flex-wrap items-end">
<!-- title -->
<div class="pb-2">
<h1 class="text-xl my-2">
<div class="p-2 bg-indigo-800 items-center text-indigo-100 leading-none lg:rounded-full flex lg:inline-flex" role="alert">
<span class="flex rounded-full bg-indigo-500 uppercase px-2 py-1 text-xs font-bold mr-3">video</span>
<span class="flex rounded-full bg-indigo-500 uppercase px-2 py-1 text-xs font-bold mr-3">{{content}}</span>
<span class="flex rounded-full bg-indigo-500 uppercase px-2 py-1 text-xs font-bold mr-3">{{state}}</span>
<span class="flex rounded-full bg-indigo-500 uppercase px-2 py-1 text-xs font-bold mr-3">eps - {{eps}}</span>
<span class="font-semibold mr-2 text-left flex-auto">{{Title}}</span>
<svg class="fill-current opacity-75 h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M12.95 10.707l.707-.707L8 4.343 6.586 5.757 10.828 10l-4.242 4.243L8 15.657l4.95-4.95z"/></svg>
</div>
</h1>
<span class="text-base text-grey-darken">{{synopsis}}</span>
</div>
<!-- buttons actions -->
<div class="ml-auto">
<!-- likes -->
<div class="flex relative pb-2">
<!-- like -->
<div class="flex items-center">
<span class="text-black opacity-50 text-sm"> ライウアニミー</span>
</div>
<!-- hate -->
<div class="flex items-center ml-5">
<span class="text-xs text-grey-darken ml-1">🥇</span>
</div>
<div class="absolute w-full h-1 bg-grey pin-b t-5 rounded-full overflow-hidden">
<div class="absolute pin-l pin-t w-3/4 h-full bg-grey-darkest"></div>
</div>
</div>
</div>
<hr class="w-full border-t m-0 mb-3 "/>
</div>
</div>
</div>
</main>
</template>
Script
What I have implemented In the mounted property is that if the video exists, it eliminated the obj videos that belongs to the key vuex.
But what I have done does not do anything, when I leave the video section the video object maintains the src of the video.
<script>
import swal from 'sweetalert';
import {mapState} from 'vuex'
import store from '../store/store'
export default{
name: 'Video',
props: ['Id' , 'Title' , 'Eps' , 'Synopsis' , 'ContentType' , 'State'],
data(){
return{
id: this.Id,
eps: this.Eps,
synopsis: this.Synopsis,
content: this.ContentType,
state: this.State,
}
},
computed:{
...mapState(['videos' , 'isLoading'])
},
watch:{
"Eps": function(value){
this.eps = value;
let eps = this.eps;
let info = {id: this.id , eps: eps}
store.dispatch('GET_ANIME_VIDEO' , info)
swal("Message!", "Wait a few seconds for the video to load\nIt's normal that it takes a bit", "success");
},
"videos.video": function(value){
this.videos.video = value;
document.getElementById('video').load();
}
},
mounted(){
if(this.videos.video){
const vuex = JSON.parse(localStorage.getItem('vuex'));
delete vuex.videos;
localStorage.setItem("vuex", JSON.stringify(vuex));
}
},
};
</script>
Mutation
I think what I have to do is create a mutation to clean the localstorage
export const mutations = {
initialiseStore(state) {
// Check if the ID exists
if(localStorage.getItem('store')) {
// Replace the state object with the stored item
this.replaceState(
Object.assign(state, JSON.parse(localStorage.getItem('store')))
);
}
},
SET_LATEST_DATA(state , payload){
state.latest = payload;
},
SET_VIDEO_ANIME(state , payload){
state.videos = payload;
},
SET_ANIME_ALPHA(state , payload){
state.animesByAlpha = payload;
},
SET_ANIME_GENDER(state , payload){
state.animesByGender = payload;
},
SET_ANIME_SEARCH(state , payload){
state.searchAnimeList = payload;
},
SET_GET_SCHEDULE(state , payload){
state.schedule = payload;
},
SET_MOVIES(state , payload){
state.movies = payload;
},
SET_OVAS(state , payload){
state.ovas = payload;
},
IS_LOADING(state , payload){
state.isLoading = payload;
}
};
Action
GET_ANIME_VIDEO({commit} , info){
console.log("id: " , info.id , "chapter: " , info.eps)
A.get(`${API_URL_ENDPOINT.video}` + "/" + `${info.id}` + "/" + `${info.eps}`)
.then((res) =>{
console.log("video src = " , res.data)
commit('SET_VIDEO_ANIME' , res.data);
commit('IS_LOADING' , false);
}).catch((err) =>{
console.error(err);
});
},
Solution
For something like this, I'd use the Vue lifecycle hook beforeDestroy:
Called right before a Vue instance is destroyed. At this stage the instance is still fully functional.
Example:
export default {
// ... vue component stuff ...
beforeDestroy () {
localStorage.removeItem('vuex');
}
}
Explanation
This will ensure that before the instance is removed from the virtual AND real DOM, we are removing an item from the localStorage called vuex.
Please note, that this does not clear the state that's stored in Vuex. For that, you'd want to create a mutation and call that mutation in the same way from beforeDestroy().
Hope this helps!
My Solution
I found the solution, but I do not know if it is the most optimal. But he does the cleaning.
beforeDestroy(){
const vuex = JSON.parse(localStorage.getItem('vuex'));
if(vuex.videos){
localStorage.removeItem(vuex.videos);
}
}
Actual Problem
When I leave the video session and I want to go see another video, the screen of the previous video remains
My intention is to keep the screen black and the minutes of the video at zero