Why my array gets overlaped - javascript

I have a simple project where i am trying to learn the concepts of vue.js using componenetes, comunication between components(i use eventBus) i am using the webkit-simple template to approach this, basicly what happens, is that i have 1 component that consists in a simple textarea where i add some text, that text should be displayed in my second component, that is a template where i render a array with all my texts that i inserted, like a list of quotes.
component addQuote
<template>
<div class="row">
<div class="col-md-12">
<div class="form-group">
<div class="col-md-offset-3 col-md-6">
<label>Quote:</label>
<textarea v-model="quote.text" class="form-control" rows="5"></textarea>
<div class="text-center">
<button #click="addQuote" class="btn btn-primary center">Add Quote</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { quoteBus } from '../main.js';
export default {
methods: {
addQuote() {
if (this.counter < 10) {
this.counter++;
this.quote.key =+ new Date();
quoteBus.$emit('saveQuote', this.quote);
}
}
},
data: function () {
return {
quote: {},
counter: 0
}
},
created(){
quoteBus.$on('decreaseCounter', () => {
this.counter--
});
}
}
</script>
<style scoped>
.row {
margin-top: 40px;
}
.center {
margin: 0 auto;
}
div .text-center {
margin-top: 20px;
}
</style>
component quotes
<template>
<div class="row">
<div class="col-md-3" v-for="(quote,$index) in quotes" #click="deleteQuote($index)" :key="quote.key">
<div class="spacing">
<h2>{{quote.text}}</h2>
</div>
</div>
</div>
</template>
<script>
import { quoteBus } from '../main.js';
export default {
data: function () {
return {
quotes: []
}
},
methods: {
deleteQuote(i){
this.quotes.splice(i,1);
quoteBus.$emit('decreaseCounter');
}
},
created() {
quoteBus.$on('saveQuote', quote => {
this.quotes.unshift(quote);
console.log(JSON.stringify(this.quotes));
});
}
}
</script>
<style scoped>
h2 {
font-family: 'Niconne', cursive;
}
div .col-md-3 {
border: 1px solid darkgray;
padding: 10px;
}
div .row {
margin-top: 40px;
}
.spacing {
margin: 10px;
padding: 10px;
}
</style>
the problem is, everytime i add a quote the text replace all the elements before.
Example:
9th entry: text: "abcdef", all the entries in the array has this value in text, all my divs has the value of abcdef, what is happening :S

const quoteBus = new Vue();
Vue.component('addQuote', {
template: '#addQuote-template',
methods: {
addQuote() {
if (this.counter < 10) {
this.counter++;
this.quote.key = +new Date();
quoteBus.$emit('saveQuote', Object.assign({}, this.quote));
}
}
},
data: function() {
return {
quote: {},
counter: 0
}
},
created() {
quoteBus.$on('decreaseCounter', () => {
this.counter--
});
}
});
Vue.component('quotes', {
template: '#quotes-template',
data: function() {
return {
quotes: []
}
},
methods: {
deleteQuote(i) {
this.quotes.splice(i, 1);
quoteBus.$emit('decreaseCounter');
}
},
created() {
quoteBus.$on('saveQuote', quote => {
this.quotes.unshift(quote);
console.log(JSON.stringify(this.quotes));
});
}
});
new Vue({
el: '#app'
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.4/vue.min.js"></script>
<template id="addQuote-template">
<div class="row">
<div class="col-md-12">
<div class="form-group">
<div class="col-md-offset-3 col-md-6">
<label>Quote:</label>
<textarea v-model="quote.text" class="form-control" rows="5"></textarea>
<div class="text-center">
<button #click="addQuote" class="btn btn-primary center">Add Quote</button>
</div>
</div>
</div>
</div>
</div>
</template>
<template id="quotes-template">
<div class="row">
<div class="col-md-3" v-for="(quote,$index) in quotes" #click="deleteQuote($index)" :key="quote.key">
<div class="spacing">
<h2>{{quote.text}}</h2>
</div>
</div>
</div>
</template>
<div id="app">
<add-quote></add-quote>
<quotes></quotes>
</div>
The problem is that there is only one instance of this.quote in your addQuote component. You pass that particular object to quotes to be put into the array every time. When an object is put into an array, it is by-reference. If you put the same object into an array multiple times, you just have multiple references to the object's contents. Every element of your array is a reference to the same set of contents.
You need to send a copy of the object instead:
quoteBus.$emit('saveQuote', Object.assign({}, this.quote));

Related

how to open add modal based on click event from Parent component- vuejs

There is a product page where product list component and action buttons are there. This is the parent component. That product list component consists of list table and edit/add modal. Now, problem is Add action event is in parent component. But, add modal related data is available in child component.
So, how can i open that model based on click event from parent? Here i am doing like this approach.
Parent Component(Product Component snippets )
<template>
..... other code ....
<div class="action-buttons">
<vu-button class="add-action" #click="onAddAction">
<svg-icon
fill="#0071E3"
name="add"
height="20"
width="28"
/>
</vu-button>
</div>
<ChildComponent :open-add-modal="isAddModal" />
</template>
Methods in Parent component
onAddAction() {
this.editable = false;
this.isAddModal = true;
},
Now, in child component i passing boolean props openAddModal but i am checking condition into created hook to show Add modal.
Problem is in initial rendering or page load add modal is showing up not in click event. How can i solve this issue?
Child component(Created hook)
created() {
if(this.openAddModal) {
this.showModal = true;
this.formType = 'add';
this.editId = null;
}
},
I want to show add modal based on click event from parent not in initial page load.
You can try using a watcher instead of checking the value of open-add-modal in the created hook. This way, when the prop open-add-modal in the child component changes, you can check the new value there and emit the needed data to the parent to then open the modal.
Example:
Parent component code
<template>
<div>
<p>Parent component</p>
<button #click="changeOpenAddModal">Clic to get child data</button>
<button #click="resetParent">Reset data in parent</button>
<p>Data from child: {{ childData }}</p>
<br />
<br />
<Child
:openAddModal="this.openAddModal"
#child-component-data-emit="this.setChildData"
/>
</div>
</template>
<script>
import Child from "./Child";
export default {
name: "Parent",
components: { Child },
data() {
return {
childData: null,
openAddModal: false,
};
},
methods: {
changeOpenAddModal() {
this.openAddModal = !this.openAddModal;
console.log(
"changing openAddModal data. New value is ",
this.openAddModal
);
},
setChildData(data) {
console.log("setting child data", data);
this.childData = data;
},
resetParent() {
this.childData = null;
this.changeOpenAddModal();
},
},
};
</script>
Child component code
<template>
<div>
<p>Child component</p>
</div>
</template>
<script>
export default {
name: "Child",
props: {
openAddModal: {
type: Boolean,
default: false,
},
},
data() {
return {
childData: {
prop1: "lorem",
prop2: "ipsum",
prop3: "dolor",
},
};
},
watch: {
openAddModal: function (newValue, oldValue) {
console.log("child watcher with newValue", newValue);
if (newValue) {
this.$emit("child-component-data-emit", this.childData);
}
},
},
mounted: function () {
console.log("prop openAddModal value on mounted:", this.openAddModal);
},
};
</script>
I have built a few modals with Vue 2 and Vue CLI, and use an alternate approach for showing, hiding, and determined if add or edit mode. No watch or separate add/edit mode boolean is necessary.
The 'product' processing is somewhat contrived since no database or AJAX are used in this example, but you should get be able to evaluate the functionality.
Parent.vue
<template>
<div class="parent">
<h4>Parent of Form Modal</h4>
<div class="row">
<div class="col-md-6">
<button class="btn btn-secondary" #click="showAddModal">Show Add Modal</button>
<button class="btn btn-secondary btn-edit" #click="showEditModal">Show Edit Modal</button>
</div>
</div>
<form-modal v-if="displayModal"
:parentProduct="product"
#save-product-event="saveProduct"
#close-modal-event="hideModal"
/>
</div>
</template>
<script>
import FormModal from './FormModal.vue'
export default {
components: {
FormModal
},
data() {
return {
product: {
id: 0,
name: '',
description: ''
},
displayModal: false
}
},
methods: {
showAddModal() {
this.resetProduct();
this.displayModal = true;
},
showEditModal() {
this.product.id = 1;
this.product.name = 'productEdit';
this.product.description = 'productEditDescription';
this.displayModal = true;
},
hideModal() {
this.displayModal = false;
},
saveProduct(modalProduct) {
this.product = modalProduct;
this.hideModal();
console.log(this.product);
},
resetProduct() {
this.product.id = 0;
this.product.name = '';
this.product.description = '';
}
}
}
</script>
<style scoped>
.btn-edit {
margin-left: 0.5rem;
}
</style>
FormModal.vue
<template>
<!-- The Modal -->
<div id="form-modal" class="modal-dialog-container">
<div class="modal-dialog-content">
<div class="modal-dialog-header">
<h4>{{ modalTitle }}</h4>
</div>
<div class="modal-dialog-body">
<form #submit.prevent="saveProduct">
<div class="form-group">
<label for="product-name">Name</label>
<input type="text" class="form-control" id="product-name" v-model="product.name">
</div>
<div class="form-group">
<label for="product-description">Description</label>
<input type="text" class="form-control" id="product-description" v-model="product.description">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
<button type="button" class="btn btn-secondary btn-close" #click="closeModal">Cancel</button>
</form>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
parentProduct: {
type: Object,
required: true
}
},
data() {
return {
product: this.parentProduct
}
},
computed: {
modalTitle() {
return this.product.id === 0 ? 'Add Product' : 'Edit Product';
}
},
methods: {
closeModal() {
this.$emit('close-modal-event');
},
saveProduct() {
// Add product
if (this.product.id === 0) {
this.product.id = 2;
}
this.$emit('save-product-event', this.product);
}
}
}
</script>
<style scoped>
.modal-dialog-container {
/* display: none; Hidden by default */
position: fixed;
/* Stay in place */
z-index: 1;
/* Sit on top */
left: 0;
top: 0;
width: 100%;
/* Full width */
height: 100%;
/* Full height */
overflow: auto;
/* Enable scroll if needed */
background-color: rgb(0, 0, 0);
/* Fallback color */
background-color: rgba(0, 0, 0, 0.4);
/* Black w/ opacity */
}
.modal-dialog-content {
background-color: #fefefe;
margin: 10% auto;
padding: 20px;
border: 1px solid #888;
border-radius: 0.3rem;
width: 30%;
}
.btn-close {
margin-left: 0.5rem;
}
</style>

How to have things data in vuejs to be printed in console

Say I have a data object and in that object, there is a property called anime1. How would I access have anime1 printed on the console as the user types their input in. This entire thing is in a component. I tried creating a function called log that when pressed would console.log the value of anime1 but that didn't work. This is what I've tried out so far.
<template>
<section class="hero">
<div class="parent-1">
<h1 class="title is-1">Compare two animes! :)</h1>
</div>
<div class="columns">
<div class="column">
<b-field class="label" label="Anime 1">
<b-input value="Enter the first anime!" v-model="anime1"></b-input>
</b-field>
</div>
<div class="column">
<b-field class="label" label="Anime 2">
<b-input value="Enter the second anime!" v-model="anime2"></b-input>
</b-field>
</div>
</div>
<div class="button-spacing">
<b-button class="button" type="is-primary" click="#log"
>Compare!</b-button
>
</div>
</section>
</template>
<script>
import Vue from "vue";
import Buefy from "buefy";
import "buefy/dist/buefy.css";
Vue.use(Buefy);
export default {
data() {
return {
anime1: "",
anime2: "",
};
},
methods: {
log(anime1) {
console.log(anime1);
},
},
};
</script>
<style>
.title.is-1 {
text-align: center;
}
.parent-1 {
margin-top: 10%;
}
.columns {
margin-top: 2%;
margin-right: 10%;
margin-left: 10%;
}
.label {
text-align: center;
}
.button {
width: 10%;
}
.button-spacing {
text-align: center;
}
</style>
To call a function as you type on the input field, add #input to it.
<b-input value="Enter the first anime!" v-model="anime1" #input="log()"></b-input>
Then, you can easily access the anime1 inside your log() using this.anime1 so you don't need to put parameters on it.
data() {
return {
anime1: "",
anime2: "",
}
},
methods: {
log(){
console.log(this.anime1)
}
}
Alternatively, as #Sphinx said in the comment, you can watch the anime1, then call a function once it changes.
<b-input value="Enter the first anime!" v-model="anime1"></b-input>
data() {
return {
anime1: "",
anime2: "",
}
},
watch: {
anime1(newVal) {
this.log(newVal);
}
},
methods: {
log(string) {
console.log(string);
},
}
Also, the way you add your click handler on your button won't work. It should be v-on:click, or simply #click. More on event handlers here.
<b-button class="button" type="is-primary" #click="log('comparing...')">Compare!</b-button>

convert funcion global js to vuejs global, vue laravel

My function js just show color border required, and if input any place has something change color.
in javascript work good but in vue no work.
I need to use this function in vue. anyplace, any component of vue
app.js
$('.req' ).on('keyup', function() {
let val = this.value
val = $.trim(val)
if(val==""){
$(this).removeClass('valid');
}else{
$(this).addClass('valid');
}
});
$('.req').change(function() {
let val = this.value
val = $.trim(val)
if(val==""){
$(this).removeClass('valid');
}else{
$(this).addClass('valid');
}
})
Anyplace.blade.php
<form>
<input type="text" class="req form-control " name="last_name">
</form>
app.css
.valid{
border-left: 2px solid #9cff2b !important;
}
.req{
border-left: 2px solid red;
}
component-example.vue
<template>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">papa</div>
<div class="card-body">
papa. esto viene del hijo {{ messaje }}
<br>
<input type="text" v-model="modelo" class="req ">
</div>
</div>
<hijo :numero=modelo #verInfo="recibirpapa" > </hijo>
</div>
</div>
</div>
</template>
<script>
export default {
mounted() {
console.log('Component papa.')
},
data(){
return{
modelo:"",
messaje:''
}
},methods:{
recibirpapa(valor){
this.messaje = valor;
}
},
}
</script>
You can try this code. include <req-input></req-input> anywhere you want.
Vue.component('req-input', {
template: '#req-input-template',
props: {
value: {
type: [String, Number],
default: ""
}
},
data: function() {
return {
currentValue: "",
valid: false
}
},
watch: {
value: {
handler(value) {
const string = value.trim()
if (string=="") {
this.valid = false
} else {
this.valid = true
}
this.currentValue = value
},
immediate: true
}
},
methods: {
handleInput() {
this.$emit("input", this.currentValue);
}
}
})
new Vue({
el: "#app",
data: {
modelo: ""
},
})
.valid {
border-left: 2px solid #9cff2b !important;
}
.req{
border-left: 2px solid red;
}
.form-control:focus {
outline: 0
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<req-input v-model="modelo"></req-input>
modelo: {{ modelo }}
<script type="text/x-template" id="req-input-template">
<input
type="text"
v-model="currentValue"
#input="handleInput"
:class="['form-control req', { 'valid': valid }]" />
</script>
</div>

How to conditionally append an element in a scoped Vue Component?

I am trying to create a Component for titles that can be edited when they get double clicked.
The Component takes the h-tag that should be used and the title as props and should produce a normal h-tag, that turns into an input field once double clicked.
This already works if there is only one title on the page, however once there are multiple Components used on one page, it breaks as the Component is not scoped properly. But I can't figure out how.
Here is the code:
<template>
<div class="edit-on-click">
<input
:class="sizeClass"
type="text"
v-if="edit"
v-model="editedTitle"
#blur="finishEdit"
#keyup.enter="finishEdit"
v-focus="true"
/>
<span v-show="!edit" #dblclick.prevent="edit = true"></span>
</div>
</template>
The mounted hook I can't figure out how to scope:
mounted() {
let node = document.createElement(this.size); // Takes h-tag (h1, h2 etc.)
let titleText = document.createTextNode(this.finalTitle); // Takes title
node.appendChild(titleText);
node.classList.add("editable-title");
// This breaks the code once there are multiple components in the document
document.getElementsByTagName("span")[0].appendChild(node);
},
How can I scope this in an efficient way? Thank you very much in advance!
Well, with Vue, you'll probably want to avoid creating DOM elements the "native" way whenever possible, as you might run into race condition where Vue is unaware of the existence of these elements which you probably want be reactive at some point in time (in your case, the <span> double-clicking).
What you could do instead, is perhaps to dynamically "switch between" these different headings with this <component> and the v-bind:is prop. Consider the following example:
Vue.component('EditableHeading', {
template: '#editable-heading',
props: {
size: {
type: String,
default: 'h1'
},
value: {
type: String,
required: true
}
},
data() {
return {
editing: false
}
},
methods: {
confirm(e) {
this.$emit('input', e.target.value);
this.close();
},
start() {
this.editing = true;
this.$nextTick(() => {
this.$el.querySelector('input[type="text"]').select();
});
},
close() {
this.editing = false;
}
}
})
new Vue({
el: '#app',
data: () => ({
titleList: [],
text: 'New Title',
size: 'h3'
}),
methods: {
addNewTitle() {
this.titleList.push({
text: this.text,
size: this.size
});
}
}
})
.edit-on-click {
user-select: none;
}
.heading-size {
margin-top: 1rem;
width: 24px;
}
p.info {
background-color: beige;
border: 1px solid orange;
color: brown;
padding: 4px 5px;
margin-top: 2rem;
}
<script src="https://vuejs.org/js/vue.min.js"></script>
<div id="app">
<editable-heading
v-for="(title, index) of titleList" :key="index"
v-model="title.text"
:size="title.size">
</editable-heading>
<div>
<label>
Heading size:
<input v-model="size" class="heading-size" />
</label>
</div>
<div>
<label>
Title:
<input v-model="text" />
</label>
</div>
<div>
<button #click="addNewTitle()">Add new title</button>
</div>
<p class="info">
[double-click]: Edit <br />
[enter]: Confirm <br />
[esc/mouseleave]: Cancel
</p>
</div>
<script id="editable-heading" type="text/x-template">
<div class="edit-on-click">
<input
type="text"
v-if="editing"
:value="value"
#blur="close"
#keydown.enter="confirm"
#keydown.esc="close" />
<component :is="size" v-else #dblclick="start">{{value}}</component>
</div>
</script>

"Unknown custom element: <ShowList>" in browser with Vue.js

I used vue cli create to setup a basic boilerplate project. I did some small modifications that initially worked. Then at some point both of my components stopped working in the browser. With no warnings from vue-cli-service serve.
In the browser console I get the error Unknown custom element for both of my elements.
Here is my main.js;
import './sass/style.scss'
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
/*
new Vue({
render: h => h(App)
}).$mount('#app')
*/
new Vue({
el: '#app',
template: '<App/>',
render: h => h(App),
components: { App }
}).$mount('#app');
Here is my App.vue;
<template>
<div id="app">
<ShowList list_id="default"/>
</div>
</template>
<script>
import ShowList from './components/ShowList.vue'
export default {
name: 'app',
components: {
ShowList
}
}
</script>
<script lang="scss">
#import "sass/common.scss";
// Static footer
.footer {
position: absolute;
bottom: 0;
width: 100%;
height: $footer_height; /* Set the fixed height of the footer here */
margin-bottom:0;
}
.navbar {
margin-bottom:0;
}
</script>
And here is one of the components;
<template>
<div class="row">
<div class="col-md-4 order-md-2 mb-4">
<h4 class="d-flex justify-content-between align-items-center mb-3">
<span class="text-muted">Din lista</span>
</h4>
<ul class="list-group mb-3">
<li v-for="beer in beers" v-bind:key="beer.name" class="list-group-item d-flex justify-content-between lh-condensed">
<div>
<h6 class="my-0">{{ beer.name }}</h6>
<small class="text-muted">{{ beer.description }}</small>
</div>
<span class="text-muted">{{ beer.price }}</span>
</li>
</ul>
</div>
</div>
</template>
<script>
var beer_items = [
{
name: 'Hoegarden',
price: '19:90 kr',
description: 'Tyskt öl'
},
{
name: 'Leffe',
price: '16:90 kr',
description: 'Belgiskt öl'
},
{
name: 'Fat Tire',
price: '17:50 kr',
description: 'Amerikanskt öl'
}
];
export default {
name: 'ShowList',
props: {
list_id: String
},
data () {
return {
beers: beer_items
};
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="scss" scoped>
.flip-list-move {
transition: transform 0.5s;
}
.no-move {
transition: transform 0s;
}
.ghost {
opacity: .5;
background: #C8EBFB;
}
.list-group {
min-height: 20px;
}
.list-group-item {
cursor: move;
}
.list-group-item i{
cursor: pointer;
}
</style>
I tried using hyphenated component names like show-list too but it did not help.
I feel like I've missed something simple and essential but so far nobody on Vue Gitter has been able to figure out what.
could it be because you've got <script lang="scss"> instead of <style lang="scss"> tags in your App.vue?

Categories

Resources