At this project I'm working on there is a legacy server-rendered web page and some components had problems I've been assigned to fix, and I convinced the team to rewrite those parts in Vue to kickstart our migration.
I wrote the whole mini-app using the Webpack template provided by Vue CLI and it works like a charm... in that specific environment.
If I npm run build the built index.html also works fine in a static server.
However, I can't seem to include the app in an existing page composed of many other elements. Shouldn't it be as simple as adding the <div id='myApp'></div> element to the HTML and loading the generated JS files?
If it helps, the legacy app is a Rails app using .erb templates and the JS files are being loaded through the main pipeline in application.js.
Does anyone know why nothing happens when I try this?
Edit: more information - this is how main.js looks before build:
/* eslint-disable */
import Vue from 'vue'
// UI components
import VueSelect from 'vue-select'
import DynamicForm from './components/DynamicForm/'
Vue.component('vue-select', VueSelect)
Vue.config.productionTip = false
const DynamicForms = new Vue({
el: '.dynamic-form',
render: h => h(DynamicForm)
})
Edit: I managed to get Vue to work by integrating Webpack to Rails with Webpacker. However I still have some problems regarding context:
This is my main.js in one of the Vue components. It was working fine until I tried the PropData stunt so I could reuse the component with different data in a few places.
/* eslint-disable */
import Vue from 'vue'
// UI components
import VueSelect from 'vue-select'
// import 'nouislider'
import DynamicForm from './components/DynamicForm/'
import fields from './fields'
import fieldRules from './field-rules'
Vue.component('vue-select', VueSelect)
Vue.config.productionTip = false
document.addEventListener('DOMContentLoaded', () => {
const el = document.createElement('div')
document.querySelector('.dynamic-form').appendChild(el)
const vm = new DynamicForm({
propsData: {
fields,
fieldRules
},
el,
render: h => h(DynamicForm)
})
})
This is DynamicForm/index.vue
<template>
<div id='app'>
<ParamList :fields='paramFields' :fieldRules='paramRules'></ParamList>
<Output></Output>
</div>
</template>
<script>
import Vue from 'vue'
import ParamList from './ParamList'
import Output from './Output'
export default Vue.extend({
props: [ 'fields', 'fieldRules' ],
name: 'DynamicForm',
components: {
ParamList,
Output
},
data () {
return {
paramFields: this.fields,
paramRules: this.fieldRules
}
}
})
</script>
<style>
</style>
The field and fieldData props are merely JSON/JSONP with some data I'm going to use inside those components. The idea is that I could write another main.js changing just the field and fieldData when initing the Vue instance.
What am I doing wrong?
I've managed to fix everything in a three-step change to my components.
Integrate Webpack into Rails using Webpacker. There's even a Vue template!
Change the root component (the one mounted at a real DOM element) to a Vue subclass using Vue.extend (so the module line # the .vue file read export default Vue.extend({ instead of simply export default {
Remove the render function from the new DynamicForm (the name I assigned Vue.extend to) so it renders its own template.
I hope it helps as it was quite a pain to me!
Related
After creating a CLI project, I need to add a Modal dialog, after a bit of searching I found what seems to be a good example at Create Reusable MODALS Using VUE JS, by Shmoji . The tutorial is well written and there is a YouTube as well.
In the modual.service.js file the following code exists:
import Vue from 'vue';
export default new Vue({
methods: {
open(component, props = {}) {
return new Promise((resolve, reject) => {
this.$emit('open', { component, props, resolve, reject });
});
}
}
})
Here is where I get the error that reads:
Uncaught TypeError: vue__WEBPACK_IMPORTED_MODULE_1__.default is not a constructor
It is pointing to the second line of code, export default new Vue({
In my main.js file, I already create Vue so it does not make sense to me why the modal author is trying to overwrite the Vue that already exists?
//main.js
import { createApp, reactive, computed, VueElement } from 'vue'
import App from './App.vue'
import router from './router'
import './assets/styles.css'
import moment from 'moment'
const app = createApp(App).use(router)
app.config.globalProperties.$moment=moment;
app.mount('#app')
Seems to me the global open method needs to be included in main.js but not sure how that is done or how to get around the above error if the modal service open method stays in its own file?
This Modal Dialog is not compatible with Vue3. I answered this question in more detail in this StackOverflow posting.
My Vue components work fine when declared in the top level HTML file, like this
<body>
<div class='app' id='app'>
<header-bar id='headerBar'></header-bar>
<journal-page></journal-page>
</div>
<script src="js/app.js"></script>
</body>
but using a <journal-card> component inside the <journal-page> component gives me the error:
[Vue warn]: Failed to resolve component: journal-card at <JournalPage>.
How do I fix this please?
Here's my top level code that loads the Vue components, app.js:
import * as _vue from 'vue';
import _headerBar from './widgets/headerBar.vue';
import _journalCard from './widgets/journalCard.vue';
import _journalPage from './widgets/journalPage.vue';
import _store from './data/store.js';
const app = _vue.createApp
({
components:
{
'headerBar': _headerBar,
'journalCard': _journalCard,
'journalPage': _journalPage
},
data : _store,
methods: {}
});
const mountedApp = app.mount('#app');
and here's my journal-page.vue container
<template>
<ul>
<journal-card v-for="item in journal" :key="item.id" :entry=item></journal-card>
</ul>
</template>
<script lang="js">
import _store from '../data/store.js';
export default {
'data': _store
};
</script>
and journal-card.vue component
<template>
<div>
hi imma journal entry
</div>
</template>
<script lang="js">
export default {
'data': null,
'props': [ 'entry' ]
};
</script>
Registering components in the root component's components option doesn't make them global. Doing that just makes them available to the root component itself, not its children.
To register components globally, use app.component in your top-level code:
main.js
import { createApp } from 'vue';
import App from './App.vue';
import MyGlobalComponent from './components/MyGlobalComponent.vue';
const app = createApp(App);
app.component('MyGlobalComponent', MyGlobalComponent); ✅
const mountedApp = app.mount('#app');
In my scenario issue was different. I was trying to render a similar multi word Vue component in a laravel blade file.
If you're referring a Vue component in a non .Vue file (like HTML / Laravel Blade etc), you should use kebab-cased format to refer the component name. Like my-global-component
Vue documentation - https://vuejs.org/guide/essentials/component-basics.html#dom-template-parsing-caveats
Also as a side note since this page shows up when looking in search engines for some problems with "Vue 3 Failed to resolve component", there are a few things that were deprecated with Vue 3 / Quasar 2:
eg. q-side-link
that silently disappeared (previous doc here)
as per this comment:
QSideLink -- no longer required! Simply use a QItem or whatever
component you want and bind an #click="$router.push(...)" to it.
Sorry if it's not exactly on topic but it will bite some other people, so I prefer to help one person with this comment ;-)
make sure the mounting code comes at last so
app.mount('#app')
app.component('list-view', ListView2)
is wrong and it will not work but
app.component('list-view', ListView2)
app.mount('#app')
is correct.
I've got this big legacy web app based on Codeigniter and jQuery.
The plan is to phase out jQuery and start using Vuejs instead. We are replacing certain parts of the website step by step.
I have just installed Nuxtjs and got the file structure the way i like it inside the legacy project.
Now to the question. How can i access a Vue component from one of my legacy javascript files?
App.vue
<template>
<div id="app">
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
</div>
</template>
<script lang="ts">
import Vue from "vue";
import HelloWorld from "./components/HelloWorld.vue";
export default Vue.extend({
name: "App",
components: {
HelloWorld
}
});
</script>
main.ts
import Vue from "vue";
import App from "./App.vue";
import store from "./store";
Vue.config.productionTip = false;
new Vue({
store,
render: h => h(App)
}).$mount("#app");
I want to display my App.vue inside an ordinary php/html view.
I am doing something similar right now, the project was originally written using Thymeleaf and jQuery and now we are switching to Vue.
You can communicate between vue components and non-vue components in a few ways, none of them are "pretty".
Communication
Classic JavaScript events
Pretty straightforward
// legacy code
document.dispatchEvent(new CustomEvent('legacy-vue.something-happened', { detail: payload }))
// vue component
created () {
document.addEventListener('legacy-vue.something-happened', this.onSomethingHappened)
},
destroyed () { // don't forget to remove the listener!
document.removeEventListener('legacy-vue.something-happened', this.onSomethingHappened)
}
Exposing EventHub
Similar to the previous one, but you are using vue events instead. This is what i would recommend, because it's the Vue way of handling events and your goal is to vuetify your app.
// initialization
const hub = new Vue()
Vue.prototype.$eventHub = hub
window.$vueEventHub = hub
// legacy code
$vueEventHub.$emit('something-happened', payload)
// vue component
created () {
this.$eventHub.$on('something-happened', this.onSomethingHappened)
},
destroyed () {
this.$eventHub.$off('something-happened', this.onSomethingHappened)
}
Exposing whole components
The most flexible way, but it's hard to see what's going on where. Event based approaches the lesser evil in my opinion (it's easy to track events).
// vue component
created () {
window.vueTableComponent = this
}
// legacy component
vueTableComponent.fetchNextPage()
vueTableComponent.registerOnPageFetchedCallback(callback);
Summary
No matter which approach you pick, i recommend doing something like this:
Let's say that you have TableComponent. TableComponent has few props like apiUrl, emits row-clicked event, etc. It would be best to design the component without thinking about your legacy code at all, and then create it's legacy wrapper because at one point it will be used both with vue-only screens and with mixed-screens (with both legacy components and vue components). An example:
LegacyTableComponentWrapper.vue
<template>
<table-component
:api-path="apiPath"
#row-clicked="onRowClicked"
ref="table-component"
/>
</template>
export default {
data: () => ({
apiPath: null
}),
methods: {
onRowClicked (row) {
this.$eventHub.$emit('table-row-clicked', row) // notify legacy code
},
onApiPathDefined (payload) {
this.apiPath = payload
}
},
mounted () {
// legacy code might require the TableComponent to act differently
// and if you don't want the TableComponent to know whenever it's legacy or not
// you can always override one or more of it's methods.
this.$refs['table-component'] = this.overriddenImplementationOfXYZ
},
created () {
this.$eventHub.$on('define-api-path', this.onApiPathDefined)
},
destroyed () {
this.$eventHub.$off('define-api-path', this.onApiPathDefined)
}
}
It's sure more work at first, but will save you a headache later, when you will be working on your first view which is fully in vue and all that legacy communication stuff is in the way.
I am new to Vue and Vuetify. I just created quick app to check both of them. But I am a running into issues in beginning. The vue fails to identify vuetify components despite following all the steps outlined in document. The error is like below -
vue.runtime.esm.js?ff9b:587 [Vue warn]: Unknown custom element:
- did you register the component correctly? For recursive
components, make sure to provide the "name" option.
found in
---> at src\App.vue
You can access the entire code at sandbox https://codesandbox.io/s/40rqnl8kw
You're likely experiencing a problem with the order of your operations. You're defining your own App component that uses the v-app component before you've even told Vue to make use of it, so Vue assumes you're using your own custom v-app component.
Place Vue.use(Vuetify) before starting any Vue instances via new Vue() that require Vuetify components, or place it within the component definitions themselves right at the top of the <script> tag after importing Vue and Vuetify within the single file component. Don't worry if you have more than one Vue.use(Vuetify) statement because only the first one will do anything--all subsequent calls will simply do nothing.
Original - Vue.use() is called before new Vue(), resulting in an error.
new Vue({
el: "#app",
components: { App },
template: "<App/>"
});
Vue.use(Vuetify);
Fix - Calling new Vue() after Vue.use() allows Vue to resolve the dependency correctly.
Vue.use(Vuetify);
new Vue({
el: "#app",
components: { App },
template: "<App/>"
});
There is another reason for this error that I recently ran into.
I recently upgraded from Vuetify 1.5 to 2.x and even though I had the order of operations correct as in the currently accepted answer here I was still receiving the error about v-app being unknown:
Unknown custom element: <v-app> - did you register the component
correctly? For recursive components, make sure to provide the "name"
option.
Turns out that the upgrade process requires the following addition to package.json devDependencies section which didn't originally exist in my vuetify 1.5x package:
"vuetify-loader": "^1.3.0"
(1.3.0 current version as of this writing)
Once I added that the error went away.
If you are coming from Google: for me it was breaking changes from v1 to v2, that made most Codepen examples useless.
I had to change this to get a very simple Vuetify app with navigation drawers to run again:
remove toolbar from <v-app toolbar>
replace v-toolbar with v-app-bar
replace v-app-bar-side-icon with v-app-bar-nav-icon
replace v-app-bar-title with v-toolbar
replace v-list-tile to v-list-item
replace all flat with text
Maybe this helps someone.
(edited to include cong yu's remark)
Edit: it looks like VuetifyLoader will automatcially do that for you.
Old Answer: Another possible problem is if you have a la carte enabled you will need to also specify all the components that you want included:
import Vue from 'vue'
import App from './App.vue'
import Vuetify, {
VApp, // required
VNavigationDrawer,
VFooter,
VToolbar,
VFadeTransition
} from 'vuetify/lib'
import { Ripple } from 'vuetify/lib/directives'
Vue.use(Vuetify, {
components: {
VApp,
VNavigationDrawer,
VFooter,
VToolbar,
VFadeTransition
},
directives: {
Ripple
}
})
With vuetify v2.x you should register the vuetify plugin as follows :
import Vue from 'vue'
/** register vuetify plugin globally **/
import Vuetify from 'vuetify/lib'
Vue.use(Vuetify)
const opts = {}
const vuetify= new Vuetify(opts)
/****/
new Vue({
vuetify,
}).$mount('#app')
Vuetify v3
import { createApp } from 'vue'
import App from './App.vue'
/*****/
import '#mdi/font/css/materialdesignicons.css'
import 'vuetify/lib/styles/main.sass'
import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'
const vuetify= createVuetify({
components,
directives,
})
/****/
const app = createApp(App)
app.use(vuetify)
app.mount('#app')
I experienced the same issue. It was caused by the cache of the browser make sure to clear the cache as well.
You will get this error even after installing the official Vuetify 3 (Alpha), due to the standard demo version generated during the install lacking adding components, i.e:
import * as components from "vuetify/components";
import * as directives from "vuetify/directives";
const vuetify = createVuetify({
components,
directives,
});
Thus the working version of main.ts for Vuetify 3 is:
import "vuetify/styles"; // Global CSS has to be imported
import { createApp } from "vue";
import { createVuetify } from "vuetify";
import App from "./App.vue";
import * as components from "vuetify/components";
import * as directives from "vuetify/directives";
const app = createApp(App);
const vuetify = createVuetify({
components,
directives,
});
app.use(vuetify).mount("#app");
// or app.use(vuetify); app.mount("#app");
In case someone like me new working on vue and nuxt. My mistake was that I did not put the s in the last. buildModule should be buildModules.
My nuxt.config.js:
export default {
buildModules:[
"#nuxtjs/vuetify"
],
module:[
"#nuxtjs/axios"
],
components:true
}
Forgive my lack of expertise, but I am attempting to integrate and import This Grid System Into my own Vue Setup and Im having some slight trouble. Now, I normally import Plugins like so:
import VuePlugin from 'vue-plugin'
Vue.use(VuePlugin)
and I'm then able to use the components of said plugin globally, however this is not a plugin and I'm having trouble pulling in/importing the needed components into my own components... suggestions?
If you use it via NPM:
First, install:
npm install --save vue-grid-layout
Then "register" it (probably a .vue - or .js - file):
import VueGridLayout from 'vue-grid-layout'
export default {
...
components: {
'GridLayout': VueGridLayout.GridLayout,
'GridItem': VueGridLayout.GridItem
}
If you use it via <script> tag:
Naturally, add it somewhere:
<script src="some-cdn-or-folder/vue-grid-layout.min.js"></script>
And "register" it (propably a .js file or another <script> tag):
var GridLayout = VueGridLayout.GridLayout;
var GridItem = VueGridLayout.GridItem;
new Vue({
el: '#app',
components: {
"GridLayout": GridLayout,
"GridItem": GridItem
},
And... in your templates
In both cases, you can then use <grid-layout ...></grid-layout> in your template.