I've created a webapp using Laravel Jetstream. I'm also using Vue and Inertia. Jetstream is dealing with the backend. A new user will land on Welcome.vue and upon loggin in, he will get to Dashboard.vue. This is my web.php:
Route::middleware([
'auth:sanctum',
config('jetstream.auth_session'),
'verified',
])->group(function () {
Route::get('/dashboard', function () {
return Inertia::render('Dashboard', [
'user' => Auth::user()
]);
})->name('dashboard');
});
I'd like to simply show a message on the Dashboard navbar, like "Welcome, name". This would be just a test, as I'd want to show several buttons, texts and stuff to auth users only across different views.
Problem is, my Welcome and Dashboard views are almost identical, they're 95% made up of components. It look like this:
<script setup>
import { Head, Link } from '#inertiajs/inertia-vue3';
import Main_w_side from '../Components/Custom/main_w_side.vue';
import Navabar from '../Components/Custom/Navabar.vue';
import Our_team from '../Components/Custom/OurTeam.vue';
import Portfolio from '../Components/Custom/Portfoglio.vue';
import CustomFooter from '../Components/Custom/CustomFooter.vue';
import { provide } from 'vue'
defineProps({
canLogin: Boolean,
canRegister: Boolean,
laravelVersion: String,
phpVersion: String,
});
</script>
<template>
<Head title="Dashboard v0.1"/>
<Navabar class="bg-white shadow dark:bg-gray-800" />
<Portfolio/>
<Main_w_side/>
<Our_team/>
<CustomFooter/>
</template>
So the Navbar.vue that Dashboard uses is the same that Welcome uses. The same goes for portfolio, Main_w_side, Our_team,and such.
I know I should use the v-if method
<p class="pl-12" v-if="loggedIn">You're logged in</p>
to show a div if a certain condition is satisfied, but I haven't found any guidance online, as most of them refer to blade, or to webapps made without Jetstream (using a user controller which I don't currently have)
I was also thinking that I should probably use Provide/Inject to let every component across the web app know if the user visiting has logged in. But I still don't know how I would do that.
I feel like there has to be an industry standard for this that I'm not aware of, as virtually almost every website would need this feature (instead of re-creating whole pages just to have a different div somewhere)
As per the default InertiaJS installation, you should be able to do something like this:
v-if="$page.props.auth.user"
so your code should look like this:
<p v-if="$page.props.auth.user" class="pl-12" v-if="loggedIn">You're logged in</p>
There's no need for provide/inject and you are able to retrieve the authenticated user on every component, no matter how deep in the component tree the component is, using inertia's $page instance property or usePage() method.
As #Abdullah Hejazy mentioned, in Vue2 and Vue3 you can simply do:
<p v-if="$page.props.auth.user" class="pl-12">You're logged in</p>
$page.props.auth.user will be null if the user isn't logged in. It is comming from the HandleInertiaRequests middleware.
In Vue3 you can also do something like this:
<script setup>
import { usePage, computed } from '#inertiajs/vue3'
const loggedIn = computed(() => {
return !!usePage().props.auth.user
})
</script>
<template>
<p v-if="loggedIn" class="pl-12">You're logged in</p>
</template>
Related
I am a totally noob at laravel and npm and vuejs things.
I made a new Laravel Project and instead of playing around with jquery I want to learn how to use vuejs.
I ran against a wall today :( trying 2 days to get this Multiselect (https://vue-multiselect.js.org/#sub-select-with-search) running on my project.
I think I am missing some basics ...
What I've done:
ran on terminal npm install vue-multiselect
created in resources/js/comonents/Multiselect.vue
pasted this code in /Multiselect.vue:
<template>
<div>
<multiselect
v-model="selected"
:options="options">
</multiselect>
</div>
</template>
<script>
import Multiselect from 'vue-multiselect'
export default {
components: { Multiselect },
data () {
return {
selected: null,
options: ['list', 'of', 'options']
}
}
}
</script>
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
added to my app.js in resources folder:
- import Multiselect from "vue-multiselect";
- Vue.component('v-multiselect', require('./components/Multiselect.vue'));
- const app = new Vue({
- el: "#app",
- data: {
- users: '',
- firmas: '',
}});
and in my blade file I used:
<v-multiselect></v-multiselect>
So far ... so good
npm run dev and refreshed the page.
Error:
index.js:133 [Vue warn]: Failed to mount component: template or render function not defined.
found in
---> <VMultiselect>
<Root>
so I have two questions is this the correct way to implement external vuejs components inte Laravel ?
and what If it is the right way am I doing wrong - at which points???
Thank you all out there to help me to learn ...
I'm glad you got your code working! To answer your question, it looks like you're using a mix of the external component you're importing and your own custom component which uses that component which may be what is confusing you a little bit.
When you do the following:
import Multiselect from "vue-multiselect";
inside your app.js file, you are importing an external component globally. When you place that import inside of a component, you are importing the external component for use within that component only. In your current code you've posted, you are importing it both globally and within your component.
If you are registering a component globally (within the element id assigned to the vue instance), you can register it like this within your app.js file:
import Multiselect from "vue-multiselect";
Vue.component('multiselect', Multiselect);
Then in your components, you will not have to import it again, but simply use it like this:
<template>
<div>
<multiselect v-model="selected" :options="options" placeholder="Select one" label="name" track-by="name"></multiselect>
</div>
</template>
<script>
export default {
data() {
return {
selected: null,
options: ['one','two','three'],
}
},
}
</script>
You would also be able to use this component in your blade since it is defined within your app.js file.
However with the setup you're using now, your fix of:
Vue.component('v-multiselect', require('./components/Multiselect.vue').default);
is not actually registering the external component. You are registering YOUR component.
So to answer your question, yes, you've taken an external component where you can make your custom component and easily add reusable content around it which is perfectly valid use, but you could either remove the extra import of Multiselect in your app.js file, or import the Multiselect external component globally, like I mentioned above.
Update:
Found the solution for my problem:
in my app js there was the error!
Vue.component('v-multiselect', require('./components/Multiselect.vue').default);
I registered the component wrong :(
So second question is answered :)
But do you guys do it the same way? or I am completly wrong implementing external commponets into laravel?
I don't want to use vueex or vuerouter for now ... I need to learn laravel itself ... afterwards I know how Laravel works I will use vuerouter ... for my projects ...
So sorry for the long text - but needed to explain a little bit more about the situation - thnaks for reading guys!
Thank you very much Sawyer,
I got it, I thought :(
I want to use this multiselect component muptliple times in my page.
So I removed the extra import in my app.js - saw it in phpstorm that it was unused but didn't know why :) - now I know.
What I have so far:
hit me if I am wrong :)
in app.js: (registering my own component)
Vue.component('v-multiselect', require('./components/Multiselect.vue').default);
added Multiselect.vue to my components folder in laravel with this src:
<template>
<div>
<multiselect v-model="value" :options="options"></multiselect>
</div>
</template>
<script>
import Multiselect from 'vue-multiselect'
// register globally
Vue.component('multiselect', Multiselect)
export default {
// OR register locally
components: { Multiselect },
data () {
return {
value: null,
options: ['option1','option2','option3']
}
}
}
</script>
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
and in my blade file:
<v-multiselect :options="['one','two','three']" ></v-multiselect>
I get no errors at all from vuejs butit isn't working as it should:
How do I overwrite the options array from my blade file ? As I saw on the documentation "options" is a prop of the component so why am I getting as select the predefined option array ['option1','option2','option3'] and not the array from the blade file:['one','two','three'] am I missing a shortcut or something else?? Thanks a lot for your patience ...
If you can tell me where to read about it - except the docs of vuejs - I think this will help me a lot!
I"m trying to redirect on click to another page, for some reason it's not working. This is the code on my vue where the redirect buttons are. I've tried two different ways and neither are working.
<el-row class="import-btn">
<a :href="'/imports/teachers'">
<el-button type="warning">
Importar
</el-button>
</a>
</el-row>
<el-row class="import-btn">
<el-button type="warning" #click="redirectStudents()">
Importar
</el-button>
</el-row>
redirectStudents() {
this.$inertia.visit('/imports/students');
},
I have the web.php routes like this
Route::resource('imports/students', 'ImportStudentController');
Route::resource('imports/teachers', 'ImportTeacherController');
In both the controllers I currently just have the index() filled
public function index()
{
return Inertia::render('Import/Students');
}
public function index()
{
return Inertia::render('Import/Teachers');
}
In the vue files for Teachers and Students I have basic layout and titles for those pages, so they're not empty.
When I click on the <a :href=""> button I get redirected to the link but the page is totally blank and when I click on the other button it opens up like a window inside also blank.
What is the correct way to fix this?
Links
Creating a link in Inertia.js is pretty straight-forward. It has a custom tag as to denote that it's something that falls into the domain of the framework.
<inertia-link href="/">Home</inertia-link>
UPDATE: Since version 0.7.0 of inertia-vue and version 0.5.0 of inertia-vue3 there has been a breaking change with an update to how links work.
For your Vue.js 2 components:
import { Link } from '#inertiajs/inertia-vue'
<Link href="/">Home</Link>
For Vue.js 3 components:
import { Link } from '#inertiajs/inertia-vue3'
<Link href="/">Home</Link>
Under the hood there is a <a> tag, which also means that all attributes passed will be sent to that underlying <a> tag.
Redirect
If all you're really looking for is a simple link-click - and not a redirect per se - then you're fine with the above code.
If you're instead interesting in a redirect - for example after updating a user or the something similar - then simply using the Redirect facade like you would do in any other Laravel application is sufficient.
class UsersController extends Controller
{
public function store()
{
User::create(
Request::validate([
'name' => ['required', 'max:50'],
'email' => ['required', 'max:50', 'email'],
])
);
return Redirect::route('users');
}
}
If you've installed the Inertia.js laravel adapter package, then this redirect will return a 303 status code, which is the same as a 302 status code, except that the request is changed into a GET request.
Hope this helps!
i'm trying to write my own blog software based on vue.js/laravel for learning purposes.
Background
I'm asking myself how i write vue.js components in which the paths/urls are not hard coded. In the following example i have a post-listing component which lists all posts from the database. The json data is returned by a laravel api route (e.g. /api/posts)
In the listing i use a link to a laravel view (e.g. /posts/{id}) which shows the actual body of a specific post with {id}.
Example
In laravel's api.php route file i can give a name to a specific route and use it with route('api.posts.index'). That's dynamic enough i guess?
api.php
Route::get('', 'Api\ApiPostsController#index')->name('api.posts.index');
index.blade.php
<post-listing postsview="{{ route('web.posts.show') }}" postsapi="{{ route('api.posts.index') }}"></post-listing>
PostListing.vue
In my vue component i refer to these properties postsview and postsapi
<template>
<div>
<h2 class="title is-2">Recent posts</h2>
<ul>
<li v-for="post in posts['data']" v-bind:key="post.id">
<a :href="postsview + '/' + post.slug" v-text="post.title"></a>
</li>
</ul>
</div>
</template>
<script>
export default {
props: ["postsapi", "postsview"],
data() {
return {
posts: []
};
},
methods: {
getPosts() {
axios.get(this.postsapi).then(response => (this.posts = response.data));
}
},
mounted() {
this.getPosts();
}
};
</script>
The question
Is there a "best-practice" way or at least a better approach? Somehow i'm not happy with this solution, but lacking experience, i don't know where to begin.
Thanks.
There are many ways to achive this, this are a few options that I know of.
1: Use blade to pass the route to the component
<component route="{{ route('route_name') }}"></component>
2: You can save a global variable with all the routes you have defined.
You can use Route::getRoutes() to get all the routes
and add it to your window on your front end
3: Use a library,
This library does exactly what you are looking for I think.
https://github.com/tightenco/ziggy
If find other options please let me know, this is a common issue for most laravel developers.
I am using vue-router for route navigation in my laravel/Vue.js app. I have a Post component holding individual post of a blog, with router-link tags on excepts of post like so:
<router-link v-bind:to="'/post/' + post.id">
<p class="post_body">{{ post.body | truncate(100) }} </p>
</router-link>
post.id comes from props cascaded down from the parent component, Posts.
The router-link should redirect to another component i called single which will show the single post in details when clicked.
<template>
<div class="single">
<h1>{{ id }}</h1>
</div>
</template>
<script>
export default{
data(){
return {
id: this.$route.params.id
}
},
created(){
console.log(this.id);
}
}
</script>
The single post loads fine. However, when i try to reload/refresh the page, it goes blank. Why does the single component only load when i click from the post component but when i try to reload the page/component, it goes blank (the console also goes blank on refresh).
To expand on #LinusBorg's answer, with Laravel you would define a catch all route to your app.blade.php view file:
Route::get('/{path?}', 'AppController#index')->where('path', '.*');
The controller's action would simply return the view:
// AppController.php
public function index()
{
return view('app');
}
I would assume that you are using history mode but haven't set up the server appropriately.
When using history mode, your web server has to redirect calls to frontend routes (like when you refresh /page/1) to index.html, so your Vue app can boot up and take over the route handling.
Link to the documentation here
I use Laravel 5.4 and React 15.5.4, code is writing in ES6.
I'd like replace Vue and use React and I did it. But I often will use small components for example 2 in different places of blade template. I don't want use one app component.
I'd like use something like:
<span class="react">
<TestComponent property={true} />
</span>
I can't do it automatically. Now I use
<span data-component="TestComponent" data-props="{property:true}" />
and in app.js
_.each(document.querySelectorAll('[data-react]'), element => {
let props ={};
Array.prototype.slice.call(element.attributes)
.forEach(item => {
props[item.name] = item.value;
if(item.name !== 'data-react'){
element.removeAttribute(item.name);
}
});
ReactDOM.render(React.createElement(reactComponents[element.getAttribute('data-react')],props),element);
});
It works but I need to use add all properties to one react component property and then use for example this.props.out.propery
I also would like set normal component tag in my blade component
I've try to use in app.js
_.each(document.querySelectorAll('.react'), item => {
ReactDOM.render(item.children,item);
});
Someone have any idea to solve this problem?
EDIT
I changed my solution to:
<span data-react="LoginForm" input="{{json(request()->old())}}" error="{{session('error')}}" errors="{{json($errors->getMessages())}}" />
or
<LoginForm data-react="LoginForm" input="{{json(request()->old())}}" error="{{session('error')}}" errors="{{json($errors->getMessages())}}" />
in blade and in resources/assets/js/app.js
var reactComponents = {
LoginForm: require('./components/login').default,
};
_.each(document.querySelectorAll('[data-react]'), element => {
let props ={};
Array.prototype.slice.call(element.attributes)
.forEach(item => {
props[item.name] = item.value;
});
ReactDOM.render(React.createElement(reactComponents[element.getAttribute('data-react')],props),element);
});
It works fine. This is not super clear solution but I have impression that the reasonable.
I can set components name in html code and add props almost same like in JSX.
As far as I know, you can not mix JSX components directly with Blade templates. The only server side rendering available today for React is NodeJS.
What you could do to improve your architecture is add specific HTML tags with certain ids and render the react components in them. So inside Blade you could do something like:
<div id="componentA"></div>
This will act as a place holder in your Blade template for that react component. Then you render your componentA from your app.js like this:
React.render(<ComponentA prop1='valueX'/>, document.getElementById("componentA"))
Remember that in this case the world of react and world of Blade run at different times.
You could use document.getElementsByTagName('LoginForm') getting all the instances and later iterate its attributes. It's clear code but not generic, because it will work just for LoginForm components.
If you want to render any tag name, then maybe it's better to use some attribute as you used with data-react.
getElementsByTagName isn't super supported by old browsers so maybe could a good idea to use jQuery as fallback $('LoginForm')