I am just playing around with vuejs router and try to load a component.
I used the sample code and changed foo
// Define some components
var Foo = Vue.extend({
template: require('./components/test.vue')
});
var Bar = Vue.extend({
template: '<p>This is bar!</p>'
});
// The router needs a root component to render.
// For demo purposes, we will just use an empty one
// because we are using the HTML as the app template.
var App = Vue.extend({})
// Create a router instance.
// You can pass in additional options here, but let's
// keep it simple for now.
var router = new VueRouter()
// Define some routes.
// Each route should map to a component. The "component" can
// either be an actual component constructor created via
// Vue.extend(), or just a component options object.
// We'll talk about nested routes later.
router.map({
'/foo': {
component: Foo
},
'/bar': {
component: Bar
}
})
// Now we can start the app!
// The router will create an instance of App and mount to
// the element matching the selector #app.
router.start(App, '#app')
I also tested it with
Vue.component('Foo', {
template: require('./components/test.vue')
})
In my test.vue i have
<template>
<h2>Test</h2>
</template>
But not as soon as i use require i get everytime the error Required is not defined in my dev tools.
What do i wrong here?
require is a builtin in the NodeJS environment and used in Grunt build environments.
If you also want to use it in a browser environment you can integrate this version of it: http://requirejs.org
(Author) This is outdated:
Use Browserify or Webpack as there is active support in the Vue community
http://vuejs.org/guide/application.html#Deploying_for_Production (dead link)
I personally used this repo of the Vue GitHub-org to get started quickly.
Edit:
This has moved on a bit in early 2018.
Deployment guide: https://v2.vuejs.org/v2/guide/deployment.html
'getting started' type repo: https://github.com/vuejs/vue-loader
Related
We are trying to update a library and the newer version requires Vue 3 instead of Vue 2, namely tinymce-vue. Unfortunately, it is a company project using bootstrap-vue, which has no full compatibility with Vue 3 yet (bootstrap-vue3 is not production-ready and we use some components that are not migrated yet).
Migrating the full app to Vue 3 has been the main attempt. However, it does not allow to use the Bootstrap components in Vue 3, or if the compatibility mode is used, part of the app works but those that would require the component do not appear/work or then the other parts of the component needing Vue 3 are broken. Is there any way to provide maybe library-specific compatibility or what is the suggested way to proceed in this case when needing two libraries that require two different versions of Vue in the same component?
I am not sure if this question should be asked differently, it is my first question in StackOverflow, so please let me know if I need to reformulate or provide more details.
The problem is that Vue 2 and 3 applications are hard to impossible to coexist in the same project because they rely on vue package with the same name but different versions. Even if it's possible to alias vue package under a different name or use modular Vue (import Vue from 'vue') for one version and Vue CDN (window.Vue) for another version in first-party code, another problem that needs to be addressed is that Vue libraries need to use specific Vue version.
This requires to build and bundle sub-apps with their preferred Vue version and libraries, which is quite close to the concept of micro frontend applications.
Given that there is Vue 3 sub-app that uses Vue 3-specific library (tinymce-vue) and specifically written to expose all public API to communicate with the outside world:
let MyV3Comp = {
template: `<div>{{ myV3Prop }} {{ myV3Data }}</div`,
props: ['myV3Prop'],
emits: ['myV3Event'],
setup(props, ctx) {
const myV3Data = ref(1);
const myV3Method = () => {};
ctx.emit('myV3Event', Math.random());
// Component public api needs to be exposed to be available on mount() instance
ctx.expose({ myV3Data, myV3Method });
return { myV3Data, myV3Method }
},
};
// Sub-app entry point
let createMyV3App = initialProps => createApp(MyV3Comp, initialProps);
export default createMyV3App;
There is Vue 2 wrapper component that acts as a bridge between Vue 3 sub-app and the rest of Vue 2 app:
import createMyV3App from '.../my-v3-app-bundled';
let MyV2WrapperComp = {
template: `<div ref="v3AppWrapper"></div>`,
props: ['myV2Prop'],
emits: ['myV2Event'],
data() {
return { myV2Data: null };
},
methods: {
// Sync wrapper events
onMyV3Event(v) {
this.$emit('myV2Event', v);
}
},
watch: {
// Sync wrapper props and data
myV2Data(v) {
this.v3AppCompInstance.myV3Data.value = v;
},
myV2Prop(v) {
// Hacky! Better use data and methods from public api to pass info downwards
this.v3AppCompInstance._instance.props.myV3Prop = v;
},
},
mounted() {
// Vue 3 automatically translates onMyV3Event prop as myV3Event event listener
// Initial prop values make app props reactive
// and allow to be changed through _instance.props
this.v3App = createMyV3App({ onMyV3Event: this.onMyV3Event, myV3Prop: null });
// also available as undocumented this.v3App._instance.proxy
this.v3AppCompInstance = this.v3App.mount(this.$refs.v3AppWrapper);
// Sync wrapper data
// Hacky! Better use event from public api to pass info upwards
this.v3AppCompInstance._instance.proxy.$watch('myV3Data', v => this.myV2Data = v);
},
unmounted() {
this.v3App.unmount();
},
};
In case wrapper and sub-app need to be additionally synchronized based on specific points, e.g. provide/inject, template refs, etc, this needs to be specifically implemented. At this point it's no different than Vue 3->Vue 2 adapter or adapters that involve other frameworks (Angular, React).
ag-grid-vue documentation from ag-grid website clearly says:
You can provide Vue Router links within the Grid, but you need to
ensure that you provide a Router to the Grid Component being created.
with sample code:
// create a new VueRouter, or make the "root" Router available
import VueRouter from "vue-router";
const router = new VueRouter();
// pass a valid Router object to the Vue grid components to be used within the grid
components: {
'ag-grid-vue': AgGridVue,
'link-component': {
router,
template: '<router-link to="/master-detail">Jump to Master/Detail</router-link>'
}
},
// You can now use Vue Router links within you Vue Components within the Grid
{
headerName: "Link Example",
cellRendererFramework: 'link-component',
width: 200
}
What's missing here is how to make the "root" Router available. I've been looking into various sources and see many people have the same problem, but none got a clear answer.
https://github.com/ag-grid/ag-grid-vue/issues/1
https://github.com/ag-grid/ag-grid-vue/issues/23
https://github.com/ag-grid/ag-grid-vue-example/issues/3
https://forum.vuejs.org/t/vue-cant-find-a-simple-inline-component-in-ag-grid-vue/21788/10
Does ag-grid-vue still work with vue-router, then how, or is this just outdated documentation? Some people claim it worked for them so I assume it worked at one point.
I am not looking for cool answer at this point. I just want to know if it is possible. I tried passing router using window or created() and none worked so far.
Thank you!
the approach suggested by #thirtydot works well. The only downside was the user cannot right-click, but I found you can just define href link. So when you left-click, event listener makes use of router. When you right-click and open in new tab, browser takes href link.
You still need to make your root router available. Below code sample assumes you have the code inside the vue-router-aware Vue component that consumes ag-grid, hence this.$router points to the root router.
{
headerName: 'ID',
field: 'id',
cellRenderer: (params) => {
const route = {
name: "route-name",
params: { id: params.value }
};
const link = document.createElement("a");
link.href = this.$router.resolve(route).href;
link.innerText = params.value;
link.addEventListener("click", e => {
e.preventDefault();
this.$router.push(route);
});
return link;
}
}
I'm trying to add CloudKit JS to a new Vue project. Ideally I'd be able to access CloudKit's functions from any component in my app. I'm brand new to Vue, so please go easy on me. :)
So far, I've tried putting the following in main.js:
var fetch = require('node-fetch')
var CloudKit = require("./cloudkit.js")
CloudKit.configure({
services: {
fetch: fetch
},
containers: [{
containerIdentifier: '...',
apiToken: '...',
environment: 'development'
}]
})
That gives me a script error in cloudkit.js:
TypeError: Cannot read property 'ArrayBuffer' of undefined
So then I read this SO post and tried this in App.vue:
export default {
name: 'App',
components: {
'master': Master
},
mounted() {
let CloudKit = document.createElement('script')
CloudKit.setAttribute('src', 'https://cdn.apple-cloudkit.com/ck/2/cloudkit.js')
document.head.appendChild(CloudKit)
}
}
I'm then able to configure CloudKit and use it inside App.vue, but I'm unclear on how to make CloudKit available in all my components without redefining it as I've done in the mounted() function above.
How can I import CloudKit and make it available in my Vue app globally?
I might be over-simplifying things, but I tried adding:
<script src="https://cdn.apple-cloudkit.com/ck/2/cloudkit.js"></script>
...to the index.html file in my Vue project, and it seems to work great. The CloudKit object is available in all of my components. ¯\_(ツ)_/¯
Your can add a new global property in vue as well :
Import CloudKit as you did then add this in your main.js : Vue.prototype.$CloudKit = CloudKit
Now, Cloudkit is available in your project with this.$CloudKit
More info here
PS: you may configure cloudkit in mounted in the app
So I'm building an application using Laravel Spark, and therefore taking the opportunity to learn some Vue.js while I'm at it.
It's taken longer for me to get my head around it than I would have liked but I have nearly got Vue-multiselect working for a group of options, the selected options of which are retrieved via a get request and then updated.
The way in which I've got this far may well be far from the best, so bear with me, but it only seems to load the selected options ~60% of the time. To be clear - there are never any warnings/errors logged in the console, and if I check the network tab the requests to get the Tutor's instruments are always successfully returning the same result...
I've declared a global array ready:
var vm = new Vue({
data: {
tutorinstruments: []
}
});
My main component then makes the request and updates the variable:
getTutor() {
this.$http.get('/get/tutor')
.then(response => {
this.tutor = response.data;
this.updateTutor();
});
},
updateTutor() {
this.updateTutorProfileForm.profile = this.tutor.profile;
vm.tutorinstruments = this.tutor.instruments;
},
My custom multiselect from Vue-multiselect then fetches all available instruments and updates the available instruments, and those that are selected:
getInstruments() {
this.$http.get('/get/instruments')
.then(response => {
this.instruments = response.data;
this.updateInstruments();
});
},
updateInstruments() {
this.options = this.instruments;
this.selected = vm.tutorinstruments;
},
The available options are always there.
Here's a YouTube link to how it looks if you refresh the page over and over
I'm open to any suggestions and welcome some help please!
Your global array var vm = new Vue({...}) is a separate Vue instance, which lives outside your main Vue instance that handles the user interface.
This is the reason you are using both this and vm in your components. In your methods, this points to the Vue instance that handles the user interface, while vm points to your global array that you initialized outside the Vue instance.
Please check this guide page once more: https://v2.vuejs.org/v2/guide/instance.html
If you look at the lifecycle diagram that initializes all the Vue features, you will notice that it mentions Vue instance in a lot of places. These features (reactivity, data binding, etc.) are designed to operate within a Vue instance, and not across multiple instances. It may work once in a while when the timing is right, but not guaranteed to work.
To resolve this issue, you can redesign your app to have a single Vue instance to handle the user interface and also data.
Ideally I would expect your tutorinstruments to be loaded in a code that initializes your app (using mounted hook in the root component), and get stored in a Vuex state. Once you have the data in your Vuex state, it can be accessed by all the components.
Vuex ref: https://vuex.vuejs.org/en/intro.html
Hope it helps! I understand I haven't given you a direct solution to your question. Maybe we can wait for a more direct answer if you are not able to restructure your app into a single Vue instance.
What Mani wrote is 100% correct, the reason I'm going to chime in is because I just got done building a very large scale project with PHP and Vue and I feel like I'm in a good position to give you some advice / things I learned in the process of building out a PHP (server side) website but adding in Vue (client side) to the mix for the front end templating.
This may be a bit larger than the scope of your multiselect question, but I'll give you a solid start on that as well.
First you need to decide which one of them is going to be doing the routing (when users come to a page who is handling the traffic) in your web app because that will determine the way you want to go about using Vue. Let's say for the sake of discussion you decide to authenticate (if you have logins) with PHP but your going to handle the routing with Vue on the front end. In this instance your going to want to for sure have one main Vue instance and more or less set up something similar to this example from Vue Router pretending that the HTML file is your PHP index.php in the web root, this should end up being the only .php file you need as far as templating goes and I had it handle all of the header meta and footer copyright stuff, in the body you basically just want one div with the ID app.
Then you just use the vue router and the routes to load in your vue components (one for each page or category of page works easily) for all your pages. Bonus points if you look up and figure using a dynamic component in your main app.vue to lazy load in the page component based on the route so your bundle stays small.
*hint you also need a polyfill with babel to do this
template
<Component :is="dynamicComponent"/>
script
components: {
Account: () => import('./Account/Account.vue'),
FourOhFour: () => import('../FourOhFour.vue')
},
computed: {
dynamicComponent() {
return this.$route.name;
}
},
Now that we are here we can deal with your multiselect issue (this also basically will help you to understand an easy way to load any component for Vue you find online into your site). In one of your page components you load when someone visits a route lets say /tutor (also I went and passed my authentication information from PHP into my routes by localizing it then using props, meta fields, and router guards, its all in that documention so I'll leave that to you if you want to explore) on tutor.vue we will call that your page component is where you want to call in multiselect. Also at this point we are still connected to our main Vue instance so if you want to reference it or your router from tutor.vue you can just use the Vue API for almost anything subbing out Vue or vm for this. But the neat thing is in your main JS file / modules you add to it outside Vue you can still use the API to reference your main Vue instance with Vue after you have loaded the main instance and do whatever you want just like you were inside a component more or less.
This is the way I would handle adding in external components from this point, wrapping them in another component you control and making them a child of your page component. Here is a very simple example with multiselect pretend the parent is tutor.vue.
Also I have a global event bus running, thought you might like the idea
https://alligator.io/vuejs/global-event-bus/
tutor.vue
<template>
<div
id="user-profile"
class="account-content container m-top m-bottom"
>
<select-input
:saved-value="musicPreviouslySelected"
:options="musicTypeOptions"
:placeholder="'Choose an your music thing...'"
#selected="musicThingChanged($event)"
/>
</div>
</template>
<script>
import SelectInput from './SelectInput';
import EventBus from './lib/eventBus';
export default {
components: {
SelectInput
},
data() {
return {
profileLoading: true,
isFullPage: false,
isModalActive: false,
slackId: null,
isActive: false,
isAdmin: false,
rep: {
id: null,
status: '',
started: '',
email: '',
first_name: '',
},
musicTypeOptions: []
};
},
created() {
if (org.admin) {
this.isAdmin = true;
}
this.rep.id = parseInt(this.$route.params.id);
this.fetchData();
},
mounted() {
EventBus.$on('profile-changed', () => {
// Do something because something happened somewhere else client side.
});
},
methods: {
fetchData() {
// use axios or whatever to fetch some data from the server and PHP to
// load into the page component so say we are getting the musicTypeOptions
// which will be in our selectbox.
},
musicThingChanged(event) {
// We have our new selection "event" from multiselect so do something
}
}
};
</script>
this is our child Multiselect wrapper SelectInput.vue
<template>
<multiselect
v-model="value"
:options="options"
:placeholder="placeholder"
label="label"
track-by="value"
#input="inputChanged" />
</template>
<script>
import Multiselect from 'vue-multiselect';
export default {
components: { Multiselect },
props: {
options: {
type: [Array],
default() {
return [];
}
},
savedValue: {
type: [Array],
default() {
return [];
}
},
placeholder: {
type: [String],
default: 'Select Option...'
}
},
data() {
return {
value: null
};
},
mounted() {
this.value = this.savedValue;
},
methods: {
inputChanged(selected) {
this.$emit('selected', selected.value);
}
}
};
</script>
<style scoped>
#import '../../../../../node_modules/vue-multiselect/dist/vue-multiselect.min.css';
</style>
Now you can insure you are manging the lifecycle of your page and what data you have when, you can wait until you get musicTypeOptions before it will be passed to SelectInput component which will in turn set up Multiselect or any other component and then handle passing the data back via this.$emit('hihiwhatever') which gets picked up by #hihiwhatever on the component in the template which calls back to a function and now you are on your way to do whatever with the new selection and pass different data to SelectInput and MultiSelect will stay in sync always.
Now for my last advice, from experience. Resist the temptation because you read about it 650 times a day and it seems like the right thing to do and use Vuex in a setup like this. You have PHP and a database already, use it just like Vuex would be used if you were making is in Node.js, which you are not you have a perfectly awesome PHP server side storage, trying to manage data in Vuex on the front end, while also having data managed by PHP and database server side is going to end in disaster as soon as you start having multiple users logged in messing with the Vuex data, which came from PHP server side you will not be able to keep a single point of truth. If you don't have a server side DB yes Vuex it up, but save yourself a headache and wait to try it until you are using Node.js 100%.
If you want to manage some data client side longer than the lifecycle of a page view use something like https://github.com/gruns/ImmortalDB it has served me very well.
Sorry this turned into a blog post haha, but I hope it helps someone save themselves a few weeks.
Before I installed my router I set up a global Vue instance and used vue-resource like this:
export const befriend = (recipient, event) => {
Vue.http.post('/route',{
// Data
}).then(function(response) {
// Success
}.bind(this), function(response) {
// Error
});
}
Now my Vue instance is being instantiated by the router itself like this:
router.start(App, 'body');
How can I use vue-resource when I have no variable bound to my instance?
Nevermind I found the answer in the vue-router documentation hidden at the bottom of the page.
In addition:
The root Vue instance will be available as router.app once the initial
render is complete.
The router instance will be available in all descendants of the router
app as this.$router.
Hope this is of use for someone else who didn't read every bit of the docu mentation.