I have an app with plain JS and Vue in one file. I need to pass a variable value from JS to Vue.
0. All code in one file:
<script>
plain js
var plainJS = 100;
</script>
<script>
var app = new Vue({
el: '#vue-app',
....
</script>
main functionality of the app is on plain js. Vue does a small part with UI.
with js I can catch if one of my elements changed position (a dot on the screen)
I need fire popup(some alert) if checkBox is selected but the Dot wasn't moved.
checkBox is a Vue element
I can pass data from Django to Vue
this.vueVar = {{ djangoVar|safe }}
So how to pass
*var plainJS = 100;*
to vue app from plain JS part of the code?
Can you give me a simple way to set vueVar = plainJS?
UPDATE:
function from plain JS
function isDotMoved(length){
if(length != 0){
console.log(length)
return true;
}
return false;
};
so this function works when I grab and move my dot on the screen.
As well, I have a function in Vue part:
isDotsMoved(){
this.dotMoved = isDotMoved(length); // function from plain JS
console.log('moved', this.dotMoved)
if(!this.dotMoved){
toastr.info('Dot Moved');
}
},
I call this function onClick. It should fire Alert if dots were moved.
I use another function the same way:
function videoPause() {
inputVideo.pause();
};
And called it inside of my Vue part:
videoPauseVue() {
videoPause(); //function from plain JS
};
How can I do the same for isDotsMoved()?
First, i add isDotMoved function in the index.html script tag and declare it on window variable that can access anywhere in your code because it is global scope.
<script>
window.plainFunc = (function () {
return {
isDotMoved: function (length) {
if (length != 0) {
console.log(length);
return true;
}
return false;
}
};
})();
</script>
Then in vue I access it throught window variable
<template>
<div id="app">
<h1>Use Function from plainJS</h1>
<button #click="handleClick">Click here to invoke function isDotMove from script</script></button>
</div>
</template>
<script>
function isDotsMoved() {
let length = 10;
let dotMoved = window.plainFunc.isDotMoved(length); // function from plain JS
console.log("moved", dotMoved);
if (!dotMoved) {
alert("Dot Moved");
}
}
export default {
name: "App",
methods: {
handleClick: function () {
isDotsMoved();
},
},
};
</script>
<style>
</style>
Check my example on codebox: https://codesandbox.io/embed/vue-js-font-awesome-1--getting-started-forked-d8xist?fontsize=14&hidenavigation=1&theme=dark
You can access a ref on the root component if you store a variable of what createApp returns. Then each time you would update your plainJS var, also reassign a matching property (ref) on the "app" object. For the initial value you may use a "root prop" which is the 2nd param of the createApp function.
main.js
import { createApp } from "vue";
import App from "./App.vue";
var plainJS = 100;
const myApp = createApp(App, { plainJS: plainJS }).mount("#app");
setInterval(() => {
//interval used here to simulate a value that changes at arbitrary times
plainJS++;
myApp.varFromOutsideVue = plainJS; // 👀 this updates the ref
}, 500);
App.vue
<template>
<h1>{{ varFromOutsideVue }}</h1>
</template>
<script>
import { onMounted, onUnmounted, ref } from "vue";
export default {
name: "App",
props: {
plainJS: { type: Number },
},
setup(props) {
const varFromOutsideVue = ref(props.plainJS);
return {
varFromOutsideVue,
};
},
};
</script>
https://codesandbox.io/s/eager-rubin-6fv7p7?file=/src/main.js
Another option (see my other answer for a more direct solution) is to use the browser's native event system to "subscribe" to changes to the variable from within your vue app. Each time the value changes you emit a custom event and there is an event listener within your vue app set up to listen to those changes and update a reactive ref.
main.js
import { createApp } from "vue";
import App from "./App.vue";
var plainJS = 100;
function fireVarChangeEvent() {
const newEvent = new CustomEvent("varchanged", {
detail: plainJS
});
window.dispatchEvent(newEvent);
}
setInterval(() => {
//interval used here to simulate a value that changes at arbitrary times
plainJS++;
fireVarChangeEvent(); // call this function after each time plainJs var is updated
}, 500);
createApp(App, { plainJS: plainJS }).mount("#app"); //pass in the first value of plainJS as a prop, this will not stay reactive, hence the custom event
App.vue
<template>
<h1>{{ varFromOutsideVue }}</h1>
</template>
<script>
import { onMounted, onUnmounted, ref } from "vue";
export default {
name: "App",
props: {
plainJS: { type: Number },
},
setup(props) {
const varFromOutsideVue = ref(props.plainJS);
function updateVar(e) {
varFromOutsideVue.value = e.detail;
}
onMounted(() => {
window.addEventListener("varchanged", updateVar);
});
onUnmounted(() => {
window.removeEventListener("varchanged", updateVar);
});
return {
varFromOutsideVue,
};
},
};
</script>
Related
So I'm trying to build a component in Vue 3 that acts as a form, and in order for the data to be processed by the parent I want it to emit an object with all the inputs on change. The issue I'm having is that I don't seem to be able to call $emit from within watch() (probably because of the context, but I also don't see why the component-wide context isn't passed by default, and it doesn't accept this). I also cannot call any method because of the same reason.
I do see some people using the watch: {} syntax but as I understand it that is deprecated and it doesn't make a whole lot of sense to me either.
Here's a minimal example of what I'm trying to accomplish. Whenever the input date is changed, I want the component to emit a custom event.
<template>
<input
v-model="date"
name="dateInput"
type="date"
>
</template>
<script>
import { watch, ref } from "vue";
export default {
name: 'Demo',
props: {
},
emits: ["date-updated"],
setup() {
const date = ref("")
watch([date], (newVal) => {
// $emit is undefined
console.log(newVal);
$emit("date-updated", {newVal})
// watchHandler is undefined
watchHandler(newVal)
})
return {
date
}
},
data() {
return {
}
},
mounted() {
},
methods: {
watchHandler(newVal) {
console.log(newVal);
$emit("date-updated", {newVal})
}
},
}
</script>
Don't mix between option and composition api in order to keep the component consistent, the emit function is available in the context parameter of the setup hook::
<template>
<input
v-model="date"
name="dateInput"
type="date"
>
</template>
<script>
import { watch, ref } from "vue";
export default {
name: 'Demo',
props: {},
emits: ["date-updated"],
setup(props,context) {// or setup(props,{emit}) then use emit directly
const date = ref("")
watch(date, (newVal) => {
context.emit("date-updated", {newVal})
})
return {
date
}
},
}
</script>
if you want to add the method watchHandler you could define it a plain js function like :
...
watch(date, (newVal) => {
context.emit("date-updated", {newVal})
})
function watchHandler(newVal) {
console.log(newVal);
context.emit("date-updated", {newVal})
}
...
I'm new to Vue and having trouble with when / how to assign a prop value calculated using the a store object. I want to grab the id from the url (e.g. /location?locationid) and compare that against the locations object in the store to find the correct location (by location_id) and pass that matching object as prop to the child location component.
I'm not sure when to do the curLoc calculation currently in created(). I've tried doing it as a computed prop, with no luck. I've tried putting the calculation into a method and calling it on created() and update() and it works with a webpack update but not a page refresh.
<template>
<div class="location-wrap">
<main id="main" aria-label="content">
<h2 class="c-section__title">{{ this.curLoc.location_name // get get location_name of undefined }}</h2>
<div class="location">
<Location v-bind:loc="curLoc" /> // I want curLoc to be accessible to this child Location component.
</div>
</main>
</div>
</template>
<script>
import Location from "#/components/Location";
import { mapState } from "vuex";
export default {
name: "location",
components: {
Location,
},
computed: mapState(["locations"]),
data() {
return {
curLoc: {},
locationId: "",
locationName: ""
};
},
created() {
// curLoc calculation
let ref = location.href;
this.locationId = ref.substring(ref.indexOf("?") + 1);
this.locations.forEach(loc => {
if (loc.location_id === this.locationId) {
this.curLoc = loc;
console.log(this.curLoc); // nope
}
});
},
updated() {},
methods: {}
};
</script>
You can create a computed property like this
currLoc() {
/* remove currLoc from data */
let currLoc = {};
let ref = location.href;
this.locationId = ref.substring(ref.indexOf("?") + 1);
/* put check when initially locations isn't defined and can even use break */
this.locations.forEach(loc => {
if (loc.location_id === this.locationId) {
curLoc = loc;
console.log(curLoc); // should print your currLoc
}
});
return currLoc;
}
By making currrLoc a computed property we've ensured that it runs everytime the location changes. You can now use computed property in the template (currLoc and pass it as a prop)
I'm trying to copy a drag and drop task like trello link
Now, how can I call dragdrop.js's function inside MyComponent.vue's method?
Here's what I did...
MyComponent.vue
<template>
....simple html drag and drop structure goes here
</template>
<script>
import dragdrop from './dragdrop.js';
export default {
name: 'my-component',
components: {},
data: () => ({
dragdrop : dragdrop
}),
methods: {
dragStart(e) {
this.dragdrop.dragStart(e);
},
dropIt(e) {
this.dragdrop.dropIt(e);
},
allowDrop(e) {
this.dragdrop.allowDrop(e);
}
}
}
</script>
dragdrop.js
function dragStart(ev) {
....
}
function dropIt(ev) {
....
}
function allowDrop(ev) {
}
I got this error when I start dragging:
VM12491 tickets:36 Uncaught ReferenceError: dragStart is not defined
at HTMLDivElement.ondragstart (VM12491 tickets:36)
VM12492 tickets:36 Uncaught ReferenceError: allowDrop is not defined
at HTMLDivElement.ondragover (VM12491 tickets:36)
Firstly: you don't actually need to put the dragdrop in your data object. Here's a better way:
<script>
import { dropStart, dropIt, allowDrop } from './dragdrop.js'; // ES6 object destructuring
export default {
name: 'my-component',
methods: {
dragStart(e) {
return dragStart(e);
},
dropIt(e) {
return dropIt(e);
},
allowDrop(e) {
return allowDrop(e);
}
}
}
</script>
Answer: My guess is that you should probably return the functions that you want to run. It's hard to tell what the actual problem is with the HTML drag attributes without the actual HTML code though.
Export the function you need to use in the Vue component.
const dragStart = function (ev) {
console.log(ev);
};
export default dragStart;
Now you can import it in Vue like
import dragStart from "./dragdrop";
and can be used inside the component like
created() {
dragStart('test');
}
I have two components, the first one is for uploading a file and the second one to Show a file. Inside my Upload Component I would like to call the Preview Component and add a Parameter so that a method inside the Preview Component uses a value which is created inside the Upload Component.
So far I have done this:
UploadComponent.vue
<template>
…
<button #click="upload"></button>
<preview-component :url="this.location"></preview-component>
</template >
<script>
import PreviewComponent from '#/js/components/PreviewComponent';
export default {
components: {
'preview-component': PreviewComponent
},
props: ['url'],
data () {
return {
// ...
location: ''
}
},
methods: {
upload() {
// ... upload stuff then update the global var location
this.location = response.data.location;
},
}
}
</script>
This is my Preview Component:
<template>
<div id="body">
///...
</div>
</template>
<script>
export default {
props: ['url'],
methods: {
loadPdf (url) {
//
},
}
}
</script>
So far I am getting the error that url is not defined, so it actually does not sent the url from the UploadCOmponent to the PreviewComponent, how do I manage to sent it?
You got a ninja this in your UploadComponent's template.
It should be <preview-component :url="location"></preview-component>
Is there a way for a .vue file to be responsible for creating its own Vue instance in a Single File Component pattern?
Here's the Vue File.
// MyComponent.vue
<template><div>Hello {{ name }}!</div></template>
<script>
const Vue = require('vue');
// what would usually be exports default
const componentConfig = {
name: "my-component",
props: {
name: String,
},
};
function create(el, props) {
const vm = new Vue({
el,
render(h) {
return h(componentConfig, { props });
});
vm.$mount();
return vm;
}
module.exports = { create };
</script>
and then the usage in some JS file:
// index.js
const MyComponent = require('./MyComponent.vue');
const el = '.container';
const props = {
name: 'Jess',
};
MyComponent.create(el, props);
</script>
When I do the above, I get errors about not being able to find the template.
[Vue warn]: Failed to mount component: template or render function not defined.
found in
---> <MyComponent>
<Root>
Like instinctually, I don't understand how the Vue compiler would be able to magically deduce (from within the script tags) that I want to reference the template declared above... so.. yeah. Is there an explanation for why I can't do this, or thoughts on how I could get it to work?
What you are describing is done in a pre-compilation step through Webpack and Vue Loader. The Vue compiler doesn't actually parse Single File Components. What the Vue compiler can parse is templates provided in a component’s options object. So if you provide a template option in your componentConfig object your example will work. Otherwise you'll have to go through the pre-compilation step with Webpack and Vue Loader to parse a Single File Component's template. To do that you'll have to conform to the SFC structure defined in the spec. Here's an excerpt ..
Template
Each *.vue file can contain at most one <template> block at a
time.
Contents will be extracted and passed on to vue-template-compiler and
pre-compiled into JavaScript render functions, and finally injected
into the exported component in the <script> section.
Script
Each *.vue file can contain at most one block at a time.
The script is executed as an ES Module.
The default export should be a Vue.js component options object.
Exporting an extended constructor created by Vue.extend() is also
supported, but a plain object is preferred.
Any webpack rules that match against .js files (or the extension
specified by the lang attribute) will be applied to contents in the
<script> block as well.
To make your specific example work You can re-write main.js file like this ..
const MyComponent = require("./MyComponent.vue");
const el = ".container";
const data = {
name: "Jess"
};
MyComponent.create(el, data);
And your MyComponent.vue file (This could just as well be a js file as #Ferrybig mentioned below) ..
<script>
const Vue = require('vue');
function create(el, data) {
const componentConfig = {
el,
name: "my-component",
data,
template: `<div>Hello {{ name }}!</div>`
};
const vm = new Vue(componentConfig);
return vm;
}
module.exports = { create };
</script>
See this CodeSandbox
Or if you prefer render functions your MyComponent.vue will look like this ..
<script>
const Vue = require('vue');
function create(el, data) {
const componentConfig = {
el,
name: "my-component",
data,
render(h) { return h('div', 'Hello ' + data.name) }
};
const vm = new Vue(componentConfig);
return vm;
}
module.exports = { create };
</script>
CodeSandbox
One last thing to keep in mind: In any component you can use either a template or a render function, but not both like you do in your example. This is because one of them will override the other. For example, see the JSFiddle Vue boilerplate and notice how when you add a render function the template gets overridden. This would explain that error you were getting. The render function took precedence, but you fed it a component's options object that provides no template to be rendered.
You are really close to a working solution, but you are missing just some "glue" parts to combine everything together:
<template>
<div>Hello {{ name }}!</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
import Vue from "vue";
const Component = {
// Add data here that normally goes into the "export default" section
name: "my-component",
props: {
name: String,
},
data() {
return {
};
},
};
Component.create = function(el, props) {
const vm = new Vue({
el,
render(h) {
return h(Component, { props });
},
});
vm.$mount();
return vm;
};
export default Component;
</script>
This can then be used as follows in other files:
import App from "./App";
App.create("#app", {
name: 'Hello World!',
});
Example on codesandbox: https://codesandbox.io/s/m61klzlwy