so I'm using spatie media library and I can upload images to storage and database. And I'm trying to display multiple images in a vue component. This is my Controller.
public function showHotelPhoto($id)
{
$hotel = Hotel::first()->getFirstMedia('images')->getUrl();
return Inertia::render('AdminHotelPhoto', [
'data' => $hotel,
]);
}
And this my vue component, and I already success to fetch the image URL but only one image URL. And in my cases, I need to display all media from the database.
<template>
<breeze-authenticated-layout>
<template #header>
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
Foto Hotel
</h2>
</template>
{{ data }}
<img v-bind:src="image">
{{ test }}
</breeze-authenticated-layout>
</template>
<script>
import BreezeAuthenticatedLayout from '#/Layouts/Authenticated'
export default {
components: {
BreezeAuthenticatedLayout,
},
props: ['data', 'test'],
data () {
return {
image: this.data
}
},
}
</script>
It looks like you are close, try changing your component so you are properly referencing the image with the url, assuming that your data prop contains a url referencing it via this.data in the :src attribute:
<template>
<breeze-authenticated-layout>
<template #header>
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
Foto Hotel
</h2>
</template>
<div>
<img :src="this.data" />
</div>
</breeze-authenticated-layout>
</template>
<script>
import BreezeAuthenticatedLayout from '#/Layouts/Authenticated'
export default {
components: {
BreezeAuthenticatedLayout,
},
props: ['data', 'test'],
data () {
return {
image: this.data
}
},
}
</script>
Related
I have a Vuejs 3 dropdown reusable component. My problem is that the #foreach statement runs before the component loads so it causes a flash of the foreach results which is very ugly upon refresh or when the page is loading.
To demonstrate please check this gif:
My component in blade:
<Dropdown title="{{ isset($currentCategory) ? ucwords($currentCategory->name) : 'Categories' }}">
<Dropdowncontent>
<Dropdownitems href="/">
All
</Dropdownitems>
<div>
#foreach ($categories as $category)
<Dropdownitems
href="/?category={{ $category->slug }}&{{ http_build_query(request()->except('category')) }}"
class="{{ isset($currentCategory) && $currentCategory->is($category) ? ' selectedCategoryItem' : '' }}">
{{ $category->name }}
</Dropdownitems>
#endforeach
</div>
</Dropdowncontent>
</Dropdown>
I added a div to contain the #foreach statement but i don't know what to do from here. I don't want to use alpineJS as it will defeat the purpose of using Vue (I guess?).
I just need a way to only display this div or the #foreach statement if the component is fully loaded or if the button is pressed or something like that. Any ideas?
-- EDIT --
I tried to hide the links in my 'dropdownitems' vue component and set the default value to false. The links are now hidden but still the blade #foreach statement echoing out the results as text before the component is loaded:
<template>
<a v-if="showLinks" href="" class="demogard categoryItems">
<slot />
</a>
</template>
<script>
export default {
name: "Dropdownitems",
setup() {
const showLinks = false;
return {
showLinks,
};
},
};
</script>
<style></style>
Here is a gif to show the result of that:
-- EDIT --
Here is my dropdown component:
<template>
<div
class="relative"
v-click-outside="onClickOutside"
#click="showCategories"
>
<slot name="toggler">
<button
class="flex max-h-52 w-full overflow-auto py-2 pl-3 pr-9 text-sm font-semibold lg:inline-flex lg:w-32"
>
{{ title }}
</button>
</slot>
<slot />
</div>
</template>
<script>
import vClickOutside from "click-outside-vue3";
import { ref, onMounted, provide } from "vue";
export default {
name: "Dropdown",
props: ["title"],
directives: {
clickOutside: vClickOutside.directive,
},
setup() {
const sharedState = ref(false);
const showCategories = () => {
sharedState.value = !sharedState.value;
};
const onClickOutside = (event) => {
sharedState.value = false;
};
provide("sharedState", sharedState);
return {
sharedState,
showCategories,
onClickOutside,
};
},
};
</script>
<style></style>
As your question, I think you have to add if condition on your dropdown component.
Your dropdown component should be like this
#dropdown.vue
<template>
<div class="dropdown">
<div #click="show = !show">{{title}}</div>
<div v-if="show">
<slot />
</div>
</div>
</template>
<script>
import { ref } from "vue";
export default {
props: ["title"],
setup(props) {
const show = ref(false);
return {
show,
};
},
};
</script>
Demo
---- EDIT ----
#dropdown.vue
<template>
<div
class="relative"
v-click-outside="sharedState = false"
>
<slot name="toggler">
<button
class="flex max-h-52 w-full overflow-auto py-2 pl-3 pr-9 text-sm font-semibold lg:inline-flex lg:w-32"
#click="sharedState = !sharedState"
>
{{ title }}
</button>
</slot>
<div v-if="sharedState">
<slot />
</div>
</div>
</template>
<script>
import vClickOutside from "click-outside-vue3";
import { ref, onMounted, provide } from "vue";
export default {
name: "Dropdown",
props: ["title"],
directives: {
clickOutside: vClickOutside.directive,
},
setup() {
const sharedState = ref(false);
// const showCategories = () => {
// sharedState.value = !sharedState.value;
// };
// const onClickOutside = (event) => {
// sharedState.value = false;
// };
provide("sharedState", sharedState);
return {
sharedState,
//showCategories,
//onClickOutside,
};
},
};
</script>
<style></style>
Try with a #if directive:
Conditional Rendering
from the documentation:
<button #click="awesome = !awesome">Toggle</button>
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
As showed in the example it render the "h1" tag conditionally respect the "awesome" variable.
In this case i will set a default value of "false" and i will turn it to "true" in the mounted hook:
Lifecycle
It's impossible to load Vue before PHP because your webpage only displays when full PHP code is received from the server. Therefore, we're never able to stop PHP or HTML from flashing if we're using them inside a reusable Vue component.
The solution I made is simply passing the value of the foreach loop as a prop to the Vue component in order for it to be displayed from there, not from my blade file.
Here's my code in blade after passing the value of the category name as a prop to my Vue component.
<Dropdown title="{{ isset($currentCategory) ? ucwords($currentCategory->name) : 'Categories' }}">
<Dropdowncontent>
<Dropdownitems href="/" category="All"></Dropdownitems>
#foreach ($categories as $category)
<Dropdownitems
category="{{ $category->name }}"
href="/?category={{ $category->slug }}&{{ http_build_query(request()->except('category')) }}"
class="{{ isset($currentCategory) && $currentCategory->is($category) ? ' selectedCategoryItem' : '' }}">
</Dropdownitems>
#endforeach
</Dropdowncontent>
</Dropdown>
Here is me displaying it from there the Vue dropdown items component:
<template>
<a href="" class="demogard categoryItems">
<slot>{{ category }}</slot>
</a>
</template>
<script>
export default {
name: "Dropdownitems",
props: ["category"],
};
</script>
<style></style>
I'm trying to create a table view with laravel framework using jetstream inertia js vue package, and came across revogrid npm package, so i installed it using the vue 3 version.
Installation went fine, but when i imported the component, it gives me Could not find a declaration module for '#revolist/vue3-datagrid' and after npm run dev no changes happened.
my container.vue :
<template>
<app-layout>
<template #header>
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
Customer
</h2>
</template>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
<v-grid
theme="compact"
:source="rows"
:columns="columns"
></v-grid>
</div>
</div>
</div>
</app-layout>
</template>
<script>
import AppLayout from '#/Layouts/AppLayout'
export default {
data: function () {
return {
columns: [{ prop: "name" }, { prop: "details" }],
rows: [{
name: "1",
details: "Item 1",
}]
}
},
components: {
AppLayout,
VGrid
},
methods: {
fetchCustomer: function () {
axios.get('/api/customers')
.then(response => this.rows = response.data)
.catch(error => console.log(error))
}
},
created: function () {
this.fetchCustomer()
}
}
</script>
error :
error message
result is empty page, while expected result is a sample table
I have this problem but i try some solutions in web but and i can't resolve it: Unknown custom element:
- did you register the component correctly? For recursive components, make sure to provide the "name" option.
The problem appears to be in those fils :
Please I need help as soon as possible
routes.js:
import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../components/Home.vue";
import PostDetails from "../components/PostDetails.vue";
Vue.use(VueRouter);
const routes = [{
path: "/",
component: Home,
name: "Home"
},
{
path: "/post/:slug",
component: PostDetails,
name: "postDetails"
}
];
const router = new VueRouter({
routes: routes,
hashbang: false,
mode: "history"
});
export default router;
app.js:
import Vue from "vue";
import VueRouter from "vue-router";
require('./bootstrap');
window.Vue = require('vue');
Vue.component(key.split('/').pop().split('.')[0], files(key).default))
Vue.component('app-home', require('./AppHome.vue'));
import router from "./routes/routes.js";
const app = new Vue({
el: '#app',
vuetify,
render: h => h(App),
router: router,
});
AppHome.vue:
<template>
<div>
<router-view></router-view>
</div>
</template>
<script>
export default {
};
</script>
Home.vue:
<template>
<div class="container">
<div class="row my-4">
<div class="col-md-8">
<div class="card">
<div class="card-header">Articles</div>
<div
class="card-body"
:key="index"
v-for="(post, index) in posts.data"
>
<div class="media">
<img
:src="post.photo"
class="rounded img-fluid mr-2 shadow-sm"
alt=""
srcset=""
/>
<div class="media-body text-justify">
<router-link :to="post.path">
<h3>{{ index }}:{{ post.title }}</h3>
</router-link>
<p>
<span class="textdefaut">
{{ post.user.name }}
</span>
<span class="text-danger">
{{ post.added }}
</span>
</p>
<p class="lead text-justify">
{{ post.body.substr(0, 200) }}...
</p>
<hr />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
posts: {},
};
},
created() {
this.getPosts();
},
methods: {
getPosts() {
axios
.get("/api/posts")
.then((response) => {
console.log(response.data);
this.posts = response.data;
})
.catch((err) => console.log(err));
},
},
};
</script>
Post Details:
<template>
<div>
Posts details , This shows that the route is really working!!
<router-link to="/Home"><a>Back to the root</a></router-link>
</div>
</telpmate>
<script>
export default {
data () {
return {
message: 'Hoera!!!!'
};
}
};
</script>
If all of your Vuejs components (including AppHome.vue) are in /resources/js/components directory, you must change:
Vue.component('app-home', require('./AppHome.vue'));
by
Vue.component('app-home', require('./components/AppHome.vue'));
...in your app.js
Try to append .default option:
Vue.component('app-home', require('./components/AppHome.vue').default);
i have a list of movies am trying to display the details of one movie using the show method
here is my component for all movies
<template>
<div>
<el-row :gutter="10">
<el-col :span="6" v-for="movie in movies" v-bind:key="movie">
<el-card shadow="always" :body-style="{ padding: '0px'} ">
<img v-bind:src="movie.cover_photo" class="image" >
<div style="padding: 14px;">
<div class="bottom clearfix">
<router-link :to="{name:'details',params:{id:movie.id}}">{{movie.title}}</router-link>
<router-view></router-view>
<h4>{{ movie.year }}</h4>
<h4>{{ movie.type }}</h4>
</div>
<div class="block">
<el-rate :max="10" ></el-rate>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import axios from 'axios'
export default {
data() {
return {
movies: [],
movie:{
id:'',
}
};
},
created(){
this. fetchMovieList();
this.showMovie
},
methods: {
fetchMovieList() {
axios.get('/movies').then(response => {
this.movies = response.data;
})
.catch(error=>console.log(error))
},
showMovie(id){
axios.get('/movies/'+id).then((res)=>{
if(res.data.status==true){
this.movie= res.data.movie;
console.log(res.data.movie)
}else{
alert('No Movie founded with this id')
}
})
.catch((err)=>{alert('error')})
}
}
}
</script>
<style scoped>
.image {
width: 100%;
display: block;
}
</style>
my show method:
public function show($id)
{
$movie=Movie::find($id);
if($movie){
return response()->json(['status'=>true,'movie'=>$movie]);
}else{
return response()->json(['status'=>false]);
}
}
my router on app.js:
const movie=Vue.component('details', require('./components/DetailsComponent.vue').default);
/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
const router=new VueRouter({
mode:'history',
routes:[
{
path:'/movie/:id',
name:'movie',
component:movie
},
],
});
const app = new Vue({
el: '#app',
router,
});
when i click on the router link it just changes the url to the id of the movie but it doesnt show the component with details when i hit the show endpoint with a specific id it returns the movie in json format on the browser
I think your variable of v-for conflict with the same variable of data().
You should try another variable name of v-for.
Something like
<el-col :span="6" v-for="value in movies" v-bind:key="movie">
<el-card shadow="always" :body-style="{ padding: '0px'} ">
<img v-bind:src="value.cover_photo" class="image" >
<div style="padding: 14px;">
<div class="bottom clearfix">
<router-link :to="{name:'details',params:{id:value.id}}">{{value.title}}</router-link>
<router-view></router-view>
<h4>{{ value.year }}</h4>
<h4>{{ value.type }}</h4>
</div>
<div class="block">
<el-rate :max="10" ></el-rate>
</div>
</div>
</el-card>
</el-col>
Hope this helps you: Reacting to Params Changes
Regards,
I want to add one component inside other when user clicks a button. but how can we render the component in the virtual dom.
I tried using v-html but its not working.
Whats the best way to solve this issue?
export default {
data(){
return{
elements : {
hotel : '<hotel-form></hotel-form>'
},
}
},
methods:{
addHotel(){
console.log('add');
}
}
}
<template>
<div class="container" style="margin-top:300px;">
<div class="row" id="mainform">
<div v-for="item in elements">
<div v-html="item"></div>
</div>
</div>
<button #click="addHotel()">add hotel</button>
</div>
</template>
I would bind an array (hotels) to a <hotel-form> component tag via v-for. This way, no hotel-form components will be initially rendered, and then you can push an object (with any data to want bound to the hotel-form component) to the hotels array and the DOM will automatically render a new corresponding hotel-form component.
Here's a simple example:
Vue.component('hotel-form', {
template: '#hotel-form',
props: { id: Number, name: String },
});
new Vue({
el: '#app',
data() {
return { hotels: [], count: 0 }
},
methods: {
addHotel() {
this.hotels.push({ name: 'foo', id: this.count++ })
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="app">
<div id="mainform">
<hotel-form v-for="hotel in hotels" :key="hotel.id" v-bind="hotel">
</hotel-form>
</div>
<button #click="addHotel">add hotel</button>
</div>
<template id="hotel-form">
<div>
<h4>Hotel Form #{{ id }}</h4>
<div>{{ name }}</div>
</div>
</template>