Vuejs Modal. Avoid changing props, nuxt.js iview - javascript

I need help with modals, i tried to get this question sorted yesterday but then it got even worse. So please any help would be appreciated.
I have a parent component and inside of there i have a modal (child component)
In my parent the code:
<template>
<div class="all">
<Button type="primary" #click="modalUp()">Press me</Button>
<appTest #changed = "modal1 = $event" :modal1='modal1'> </appTest>
{{modal1}}
</div>
</template>
<script>
/* eslint-disable */
import test from '~components/test.vue'
export default {
data(){
return{
modal1: false
}
},
components: {
appTest: test
},
methods: {
modalUp() {
this.modal1 = true
}
},
watch:{
modal1: function(){
this.$on('changed', (data)=>{
console.log(data)
})
}
}
}
</script>
<style lang="css" scoped>
</style>
inside of the child component (appTest) i have this
<template>
<div id="" >
<Modal v-model="modal1" title="MODALLLL" #on-ok="ok" #on-cancel="cancel">
<p>#twitter</p>
<p>#facebook</p>
<p>Good</p>
{{modal1}}
</Modal>
</div>
</template>
<script>
/* eslint-disable */
export default {
props: ['modal1'],
data() {
return {
}
},
methods: {
ok() {
this.$Message.info('all good');
},
cancel() {
this.$Message.info('Cancel');
this.$emit('changed')
}
},
watch:{
modal1: function(){
this.$emit('changed', this.modal1)
}
}
}
</script>
<style lang="css" scoped>
</style>
So this code works in one way, the modal shows up correctly but once its gone and when we get back to parent component it gives me this vue warning AVOID MUTATING PROP
I checked the docs and everything but vuejs docs give examples like 2+2 which is not helpful in this case. I watched videos on the internet etc but still don't know how to get it done in a proper way.
What would be the best way to get it working?
I'm using modal from iview

Change your child to use a computed value.
export default {
props: ['modal1'],
computed:{
showModal:{
get(){return this.modal1},
set(v){ this.$emit("changed", v)}
}
},
}
And update the child template to
<Modal v-model="showModal" ...></Modal>
Doing this, whever you change modal1 in the parent, the value will be updated in the Modal component, and whenever the Modal component changes the value, it will be sent to the parent.

Related

Vue js pass a function from child chile to another child component on a click event

I am trying to pass a function when a button is clicked, the button is clicked in a child element, then passed through a parent element to another child component, and i dont want to use the store for that, How can i do that?
components/footer/footer.vue
-- This is where the button is clicked
<template>
<div class="footer-bottom-header-menu-bar mob" #click="showMenu">
<img src="~/assets/svg/menubar.svg" alt="+" />
</div>
</template>
<script>
export default {
methods: {
showMenu() {
this.$emit("show-menu");
}
}
}
</script>
layouts/default.vue
--This is the parent component where that receives the click function and is to pass it into the app-header
<template>
<div>
<app-header />
<Nuxt />
<app-footer #show-menu="showMenu()"/>
</div>
</template>
<script>
import header from "~/components/header/header";
import footer from "~/components/footer/footer";
export default {
components: {
'app-header': header,
'app-footer': footer
},
methods: {
showMenu() {
console.log("clicked");
}
}
}
</script>
components/header/header.vue
-- I want the click function to perform an action inside this component
<script>
export default {
data() {
return {
showMenuBar: false
}
},
}
</script>
Why are you worried about passing a function around?
When you emit the show-menu event simply toggle a piece of data in your parent component like this:
<template>
<div>
<app-header :showMenuBar="showMenuBar" />
<Nuxt />
<app-footer #show-menu="showMenu"/>
</div>
</template>
<script>
import header from "~/components/header/header";
import footer from "~/components/footer/footer";
export default {
components: {
'app-header': header,
'app-footer': footer
},
data() {
return {
showMenuBar: false;
};
},
methods: {
showMenu() {
// I would make this more dynamic than always
// hardcoding it to true, but you get the idea
this.showMenuBar = true;
}
}
}
</script>
Then in your AppHeader simply take it in as a prop:
<script>
export default {
props: {
showMenuBar: {
type: Boolean,
default: false,
},
}
</script>
You can declare any attribut on your parent component:
data() {
return { toBeWatched: 0 };
}
Then pass it like a props from the parent to the header child:
<app-header :Trigger="toBeWatched" />
When you listen to the #show-menu event (comming from footer child),
make any change on your attribut:
<app-footer #show-menu="toBeWatched++" />
Finally you can watch for this change on your header child and
trigger your function.
<script>
export default {
data() {
return {
showMenuBar: false
};
},
props: ['Trigger'],
watch: {
Trigger() {
this.showMenuBar = !this.showMenuBar; // or do whatever you want
console.log('showMenuBar : ' + this.showMenuBar);
}
}
};
</script>

Vue - Access prop in child component, regular javascript

I am loading a Vue component from another Vue component and am passing a property to that component. I need to access this property in the regular javascript of that component, but cannot figure out how to do this.
The simplified parent component could look as follows:
<template>
<div>
<MenuEdit :menu-list="menuList"></MenuEdit>
</div>
</template>
<script>
import MenuEdit from '#/components/MenuEdit';
export default {
name: 'Admin',
data: function () {
return {
menuList: ["Item1","Item2","Item3","Item4"]
};
},
components: {
MenuEdit
}
}
</script>
<style scoped>
</style>
And the MenuEdit could look as follows:
<template>
<div>
{{ menuList }}
</div>
</template>
<script>
//console.log(this.menuList) // Does not work.
export default {
name: 'MenuEdit',
props: [
'menuList'
],
methods: {
testMenu: function() {
console.log(this.menuList) //This works fine
}
}
}
</script>
<style scoped>
</style>
EDIT
To add some context to the question, I am implementing sortablejs on Buefy using the following example: https://buefy.org/extensions/sortablejs
Instead of calling "vnode.context.$buefy.toast.open(Moved ${item} from row ${evt.oldIndex + 1} to ${evt.newIndex + 1})" at the end of the first const, I want to update the component (or better said, update the related Array).
In the example, the const are defined outside of the component, which is why I ended up with this question.
You cannot access the prop as that code (where your console.log is) runs before the component is mounted, before it's even declared really
If you want to access stuff when the component is first mounted, you can use the mounted lifecycle method

Vue - Apply "v-model" on user-defined component which use ace-editor

Code
CodeEditor.vue:
<template>
<div class="ace-container">
<div class="ace-editor" ref="ace"></div>
</div>
</template>
<script>
import ace from 'ace-builds'
import 'ace-builds/webpack-resolver'
import 'ace-builds/src-noconflict/theme-monokai'
import 'ace-builds/src-noconflict/mode-javascript'
export default {
mounted() {
this.aceEditor = ace.edit(this.$refs.ace, {
maxLines: 60,
minLines: 10,
fontSize: 14,
theme: this.themePath,
mode: this.modePath,
tabSize: 4
})
},
data() {
return {
aceEditor: null,
themePath: 'ace/theme/monokai',
modePath: 'ace/mode/javascript'
}
},
methods: {
setCode(code) {
this.aceEditor.setValue(code);
},
getCode() {
return this.aceEditor.getValue();
},
}
}
</script>
<style>
.ace-editor {
width: 600px;
height: 600px;
}
</style>
QuizExecution.vue: (partly)
<template>
<v-app height="100%">
<div id="qz-wrapper">
<!--
<v-textarea id="programmingText" v-model="answerData[question.id]"
#change="saveAnswer(qe.id, question.id)" label="Code" outlined></v-textarea>
-->
<CodeEditor id="programmingText" v-model="answerData[question.id]"
#change="saveAnswer(qe.id, question.id)"></CodeEditor>
</div>
</v-app>
</template>
<script>
import Vue from 'vue'
import CodeEditor from "./CodeEditor";
export default {
components: {CodeEditor},
data() {
return {
// ..
}
}
}
</script>
<style scoped>
</style>
Description
With vuetify's <v-textarea>, I can use v-model to bind its content to a data property, in bi-direction dynamically, so that could init on load, and save on change with a #change property.
Then I want to replace the input area with ace-editor, which support features like syntax highlight.
So, I have defined a component as in CodeEditor.vue, then import & use it in QuizExecution.vue.
But, the v-model and #change won't work on the <CodeEditor> tag.
Questions
How to apply v-model and #click on this <CodeEditor> with in QuizExecution.vue.
Aka. init it with data from container component, and retrieve its content on change and trigger an event to save.
Or, is there anyway to achieve the same result: init on creation & save on change.
You can use props and watch the change events with #update_question_id;
<CodeEditor id="programmingText" :question_id="answerData[question.id]"
#update_question_id="answerData[question.id]=#event"
></CodeEditor>
....
watch:{
answerData(){
saveAnswer(this.qe.id, this.question.id)
}
}
CodeEditor.vue:
You can get the question_id value with props. I think it would be string or number.
And also watch question_id then use $emit to send change $event to main component.
export default {
props:{
question_id: [String,Number]
},
watch:{
question_id(val){
this.$emit("update_question_id",val)
}
}
.....

Why wont the component respond to the custom event?

I am emitting an event called emit-event-main2-counter in Main2.vue
Why will the data cntr in Bottom.vue not update?
App.vue
<template>
<div class="app">
<Main2 />
<Bottom/>
</div>
</template>
<script>
import Main2 from "./components/Main2";
import Bottom from "./components/Bottom";
export default {
components: {
Main2,
Bottom
},
}
</script>
<style scoped>
h1 {
color: red;
}
</style>
Main2.vue
<template>
<div>
main2 template <span class="text1">{{message}}</span>
<button type="button" v-on:click="btnClickButton">my click</button>
<div>{{counter}}</div>
</div>
</template>
<script>
import appInput from "./appInput.vue";
export default {
data: () => {
return {
message: "theText",
counter: 0,
}
},
components: {
appInput,
},
methods: {
btnClickButton(e) {
this.$root.$emit('emit-event-main2-counter', this.counter)
console.log('button');
this.counter +=1;
}
}
}
</script>
<style scoped>
.text1 {
color:red;
}
.text2 {
color:blue;
}
</style>
Bottom.vue
<template>
<div class="Bottom" v-on:emit-event-main2-counter="cntr = $event">
bottom text and cntr ({{cntr}})
</div>
</template>
<script>
export default {
data: () => {
return {
cntr: 0
}
},
}
</script>
<style scoped>
</style>
You could emit an event to parent from Main2 having as parameters this.counter and in the parent one receive that and pass it through props to Bottom
In Main2 :
this.$emit("emit-event-main2-counter",this.counter);
in the parent component :
<template>
<Main2 v-on:emit-event-main2-counter="sendToBottom"/>
<Bottom :cntr="pcounter"/>
....
</template>
data:{
pcounter:0
},
methods:{
sendToBottom(c){
this.pcounter=c
}
}
Bottom should have property called cntr
props:["cntr"]
Bottom.vue
<template>
<div class="Bottom" >
bottom text and cntr ({{cntr}})
</div>
</template>
<script>
export default {
props:["cntr"],
data: () => {
return {
}
},
}
</script>
If you want to use root events, you need to emit the event with this.$root.$emit() and also listen to the event on the root like this: this.$root.$on().
You should use it directly in the script part. Listen to the root event e.g. in the created() hook and then disable it with $off in the beforeDestroy() hook.
However, I wouldn't encourage you to use $root events. It is usually better to communicate between the components like #BoussadjraBrahim proposed in his answer.
If you have a more complex application, it makes sense to take a look at Vuex and store the complete state in the Vuex store. By doing this, you can watch the global application state in the components and react if it changes. In this scenario, you would use the Vuex store instead of a root EventBus.

Change a property's value in one component from within another component

I'm trying to wrap my head around hoe Vue.js works, reading lots of documents and tutorials and taking some pluralsight classes. I have a very basic website UI up and running. Here's the App.vue (which I'm using kinda as a master page).
(To make reading this easier and faster, look for this comment: This is the part you should pay attention to)...
<template>
<div id="app">
<div>
<div>
<CommandBar />
</div>
<div>
<Navigation />
</div>
</div>
<div id="lowerContent">
<!-- This is the part you should pay attention to -->
<template v-if="showLeftContent">
<div id="leftPane">
<div id="leftContent">
<router-view name="LeftSideBar"></router-view>
</div>
</div>
</template>
<!-- // This is the part you should pay attention to -->
<div id="mainPane">
<div id="mainContent">
<router-view name="MainContent"></router-view>
</div>
</div>
</div>
</div>
</template>
And then in the same App.vue file, here's the script portion
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import CommandBar from './components/CommandBar.vue';
import Navigation from './components/Navigation.vue';
#Component({
components: {
CommandBar,
Navigation,
}
})
export default class App extends Vue {
data() {
return {
showLeftContent: true // <--- This is the part you should pay attention to
}
}
}
</script>
Ok, so the idea is, one some pages I want to show a left sidebar, but on other pages I don't. That's why that div is wrapped in <template v-if="showLeftContent">.
Then with the named <router-view>'s I can control which components get loaded into them in the `router\index.ts\ file. The routes look like this:
{
path: '/home',
name: 'Home',
components: {
default: Home,
MainContent: Home, // load the Home compliment the main content
LeftSideBar: UserSearch // load the UserSearch component in the left side bar area
}
},
So far so good! But here's the kicker. Some pages won't have a left side bar, and on those pages, I want to change showLeftContent from true to false. That's the part I can't figure out.
Let's say we have a "Notes" component that looks like this.
<template>
<div class="notes">
Notes
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
#Component
export default class Notes extends Vue {
data() {
return {
showLeftContent: false // DOES NOT WORK
}
}
}
</script>
Obviously, I'm not handling showLeftContent properly here. It would seem as if the properties in data are scoped only to that component, which I understand. I'm just not finding anything on how I can set a data property in the App component and then change it in a child component when that child is loaded through a router-view.
Thanks!
EDIT:
I changed the script section of the Notes component from:
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
#Component
export default class Notes extends Vue {
data() {
return {
showLeftContent: false // DOES NOT WORK
}
}
}
</script>
to:
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
#Component
export default class Notes extends Vue {
mounted() {
this.$root.$data.showLeftContent = false;
}
}
</script>
And while that didn't cause any compile or runtime errors, it also didn't have the desired effect. On Notes, the left side bar still shows.
EDIT 2:
If I put an alert in the script section of the Notes component:
export default class Notes extends Vue {
mounted() {
alert(this.$root.$data.showLeftContent);
//this.$root.$data.showLeftContent = false;
}
}
The alert does not pop until I click on "Notes" in the navigation. But, the value is "undefined".
EDIT 3:
Struggling with the syntax here (keep in mind this is TypeScript, which I don't know very well!!)
Edit 4:
Inching along!
export default class App extends Vue {
data() {
return {
showLeftContent: true
}
}
leftContent(value: boolean) {
alert('clicked');
this.$root.$emit('left-content', value);
}
}
This does not result in any errors, but it also doesn't work. The event never gets fired. I'm going to try putting it in the Navigation component and see if that works.
As it says on #lukebearden answer you can use the emit event to pass true/false to the main App component on router-link click.
Assuming your Navigation component looks like below, you can do something like that:
#Navigation.vue
<template>
<div>
<router-link to="/home" #click.native="leftContent(true)">Home</router-link> -
<router-link to="/notes" #click.native="leftContent(false)">Notes</router-link>
</div>
</template>
<script>
export default {
methods: {
leftContent(value) {
this.$emit('left-content', value)
}
}
}
</script>
And in your main App you listen the emit on Navigation:
<template>
<div id="app">
<div>
<Navigation #left-content="leftContent" />
</div>
<div id="lowerContent">
<template v-if="showLeftContent">
//...
</template>
<div id="mainPane">
//...
</div>
</div>
</div>
</template>
<script>
//...
data() {
return {
showLeftContent: true
}
},
methods: {
leftContent(value) {
this.showLeftContent = value
}
}
};
</script>
A basic approach in a parent-child component relationship is to emit events from the child and then listen and handle that event in the parent component.
However, I'm not sure that approach works when working with the router-view. This person solved it by watching the $route attribute for changes. https://forum.vuejs.org/t/emitting-events-from-vue-router/10136/6
You might also want to look into creating a simple event bus using a vue instance, or using vuex.
If you'd like to access the data property (or props, options etc) of the root instance, you can use this.$root.$data. (Check Vue Guide: Handling Edge)
For your codes, you can change this.$root.$data.showLeftContent to true/false in the hook=mounted of other Components, then when Vue creates instances for those components, it will show/hide the left side panel relevantly.
Below is one demo:
Vue.config.productionTip = false
Vue.component('child', {
template: `<div :style="{'background-color':color}" style="padding: 10px">
Reach to root: <button #click="changeRootData()">Click me!</button>
<hr>
<slot></slot>
</div>`,
props: ['color'],
methods: {
changeRootData() {
this.$root.$data.testValue += ' :) '
}
}
})
new Vue({
el: '#app',
data() {
return {
testValue: 'Puss In Boots'
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<h2>{{testValue}}</h2>
<child color="red"><child color="gray"><child color="green"></child></child></child>
</div>

Categories

Resources