Declare reactive data properties in vue.js? - javascript

I have a very basic vue.js app:
var app = new Vue({
el: '#app',
delimiters: ['${', '}'],
data: {
messages: [
'Hello from Vue!',
'Other line...'
]
}
})
The following html works fine:
<div class="container" id="app">
<div class="row" style="">
<div class="col-md-8 offset-md-2">
<span v-for="msg in messages">${msg}</span>
</div>
</div>
</div>
However very similar html block does not:
<div class="container" id="app">
<div class="row" style="">
<div class="col-md-8 offset-md-2">
<textarea id="chat_area" readonly="" rows="20">
<span v-for="msg in messages">${msg}</span>
</textarea>
</div>
</div>
</div>
[Vue warn]: Property or method "msg" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
I'm using Vue v2.3.3. What could be the problem?

As documentation says, interpolation in textareas won't work, so you need to use v-model.
If the only thing you want to do is to display html inside textarea, you could in theory use an ugly workaround by wrapping your array fields inside a function and set that function as textarea v-model:
var app = new Vue({
el: '#app',
delimiters: ['${', '}'],
data: {
messages: [
'Hello from Vue!',
'Other line...'
]
},
computed:{
multiLineMessages: function(){
var result = "";
for(var message of this.messages){
result += '<span>' + message + '</span>'
}
return result;
}
}
});
template part:
<div class="container" id="app">
<div class="row" style="">
<div class="col-md-8 offset-md-2">
<textarea v-model="multiLineMessages" placeholder="add multiple lines">
</textarea>
</div>
</div>
</div>
It's more like a proof that it's doable but I highly don't recommend using it anywhere, as html shouldn't be generated this way (especially larger chunks of it).
jsFiddle preview

Related

In Knockout how do you go about nesting custom components when using an html binding?

I'm a bit new to knockout. I'm trying to get a custom component to dynamically load another custom component. I have a variable called location_board that contains html and that html has a custom component in it. . When I use the data-bind="html: location_board" it put the line for the in the dom but it doesn't run the custom component to fill out that node. Note: If I add the npc-widget directly to the template it works. It just doesn't work when it is added though the html binding. From my research I think this means I need to applyBindings on it? I'm not sure how to go about that in this situation though.
Any help is apricated.
Here is my full code for the custom component.
import {Database} from './database.js'
let database = new Database();
import {ResourceManager} from "./resource-manager.js";
let resourceManager = new ResourceManager();
let locationRegister = {
fog_forest: {
name: "The Ghostly Woodland",
image: "url('img/foggy_forest.jpeg')",
description: `
Place holder
`,
location_board: `
<npc-widget id="john-npc" params="id: 1, tree: 'shopkeep', speed: 50"></npc-widget>
<div>In</div>
`
}
};
ko.components.register('location-widget', {
viewModel: function (params) {
let self = this;
self.function = function () {
console.log("Functions!")
}
for(let k in locationRegister[params.id]) {
console.log(k)
this[k] = locationRegister[params.id][k];
}
console.log(this.name)
//return { controlsDescendantBindings: true };
},
template:
`
<div class="row">
<div class="col-lg-12">
<h2 id="title" class="tm-welcome-text" data-bind="html: name"></h2>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div data-bind="style: { 'background-image': image}" class="location-picture mx-auto d-block">
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="location-description mx-auto d-block" data-bind="html: description"></div>
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-lg-12">
<div id="location_board" data-bind="html: location_board">
</div>
</div>
</div>
`
});

- Cannot use v-for on stateful component root element because it renders multiple elements

Getting the error the error in the title, I am new to vue.js and I cannot figure it out
Cannot debug this for the life of me, can somebody help?
const app = new Vue({
el: '#location-box',
data: {
locations: [
{
name: "Europe",
desc: "Loren ipsum"
},
{
name: "America",
desc: "Loren ipsum"
}
],
},
})
My HTML:
<div id="location-section">
<div class="main-container">
<div class="location-grid-container">
<div id="info-box" class="outline">
</div>
<div>
<div id="location-box" v-for="location in locations" class="outline">
<h1>{{location.name}}</h1>
</div>
</div>
</div>
</div>
The root element that you pass to the el option of your new Vue app must be unique, and serve only as a placeholder.
Then you can use Vue directives on its children.
So in your case you could do:
<div id="location-section">
<div class="main-container">
<div class="location-grid-container">
<div id="info-box" class="outline">
</div>
<div id="location-box"><!-- Root element for your Vue app -->
<div v-for="location in locations" class="outline">
<h1>{{location.name}}</h1>
</div>
</div>
</div>
</div>
new Vue({
el: '#location-box',
// etc.
});

Vue.js dynamic element creation with localStorage

I'm completely new to Vue and I can't understand the proper way to render components with dynamic values in Vue.js.
So I have this code below :
new Vue({
el: "#notes",
data: {
title: "",
body: ""
},
methods: {
add: function() {
localStorage.setItem($("#title").val(), $("#body").val());
location.reload(true);
},
clear: function() {
localStorage.clear();
location.reload(true);
}
},
created: function() {
for (i = 0; i < localStorage.length; i++) {
this.title = localStorage.key(i);
this.body = localStorage.getItem(localStorage.key(i));
}
}
});
<div id="notes">
<div class="container">
<div class="form-group">
<label for="title">Enter title</label>
<input class="form-control" id="title"/>
</div>
<div class="form-group">
<label for="body">Enter body</label>
<textarea class="form-control" id="body"></textarea>
</div>
<div class="form-group">
<button class="btn btn-primary" #click="add">Add</button>
</div>
<div class="card" style="width:18rem;" v-for="i in localStorage">
<div class="card-body">
<h5 class="card-title">{{title}}</h5>
<p class="card-text">{{body}}</p><a class="card-link" #click="clear">Delete</a>
</div>
</div>
</div>
</div>
And I can't figure out why does this code renders the elements with the same values, I mean if I have two values in localStorage it renders two elements with the value of the last one on localStorage.
Maybe there's some problem with the loop on created? I need some help with understanding the Vue rendering and fixing my function
CodePen
Thank you very much for spending your precious time with my issue! Thank you for any help!
You have one title variable and one body variable. It can't store two values in one variable, so the second overwrites the first. You need arrays.

contenteditable div append a html element and v-model it in Vuejs

Here is my Html code.
<div id="app">
<button #click="renderHtml">clisdfsdfsdfck to appen html</button>
<div class="flex">
<div class="message" #input="fakeVmodel" v-html="html" contenteditable="true"></div>
<div class="message">{{ html }}</div>
</div>
</div>
Here is js part
let app = new Vue({
el: '#app',
data: {
html: 'some text',
},
methods: {
fakeVmodel: function(e){
this.html = e.target.innerText;
},
renderHtml: function(){
this.html += '<img src="https://cdn-images-1.medium.com/max/853/1*FH12a2fX61aHOn39pff9vA.jpeg" alt="" width=200px>';
}
}
});
The problem is, when I click the button to push a html tag(img) to my variable (html) and it works. but after typing, it will remove the tag part that was insert. Is that any way to append to html code successful in Vue?
Here is the codepen example
https://codepen.io/weretyc/pen/EwXZYL?editors=1010
The main problem:
The html disappears because of this.html = e.target.innerText;. Instead, use this.html = e.target.innerHTML;. innerHTML resolves to the full HTML content.
Secondary Problem:
After typing, the cursor focuses the beginning of the div. This is because v-html causes the div to update.
To solve, ensure that v-html only updates the div on focusout.
Full Example
let app = new Vue({
el: '#app',
data: {
html: 'some text',
},
methods: {
updateHtml: function(e) {
this.html = e.target.innerHTML;
},
renderHtml: function(){
this.html += '<img src="https://cdn-images-1.medium.com/max/853/1*FH12a2fX61aHOn39pff9vA.jpeg" alt="" width=200px>';
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.js"></script>
<div id="app">
<button #click="renderHtml">click to append html</button>
<div class="flex">
<div class="message" #focusout="updateHtml" v-html="html" contenteditable="true"></div>
<br>
<div class="message">{{ html }}</div>
</div>
</div>

Vue.js dynamic two way data binding between parent and child components

I'm trying to use a combination of v-for and v-model to get a two-way data bind for some input forms. I want to dynamically create child components. Currently, I don't see the child component update the parent's data object.
My template looks like this
<div class="container" id="app">
<div class="row">
Parent Val
{{ ranges }}
</div>
<div class="row">
<button
v-on:click="addRange"
type="button"
class="btn btn-outline-secondary">Add time-range
</button>
</div>
<time-range
v-for="range in ranges"
:box-index="$index"
v-bind:data.sync="range">
</time-range>
</div>
<template id="time-range">
<div class="row">
<input v-model="data" type="text">
</div>
</template>
and the js this
Vue.component('time-range', {
template: '#time-range',
props: ['data'],
data: {}
})
new Vue({
el: '#app',
data: {
ranges: [],
},
methods: {
addRange: function () {
this.ranges.push('')
},
}
})
I've also made a js fiddle as well https://jsfiddle.net/8mdso9fj/96/
Note: the use of an array complicates things: you cannot modify an alias (the v-for variable).
One approach that isn't mentioned often is to catch the native input event as it bubbles up to the component. This can be a little simpler than having to propagate Vue events up the chain, as long as you know there's an element issuing a native input or change event somewhere in your component. I'm using change for this example, so you won't see it happen until you leave the field. Due to the array issue, I have to use splice to have Vue notice the change to an element.
Vue.component('time-range', {
template: '#time-range',
props: ['data']
})
new Vue({
el: '#app',
data: {
ranges: [],
},
methods: {
addRange: function () {
this.ranges.push('')
},
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div class="container" id="app">
<div class="row">
Parent Val
{{ ranges }}
</div>
<div class="row">
<button
v-on:click="addRange"
type="button"
class="btn btn-outline-secondary">Add time-range
</button>
</div>
<time-range
v-for="range, index in ranges"
:data="range"
:key="index"
#change.native="(event) => ranges.splice(index, 1, event.target.value)">
</time-range>
</div>
<template id="time-range">
<div class="row">
<input :value="data" type="text">
</div>
</template>
To use the .sync modifier, the child component has to emit an update:variablename event that the parent will catch and do its magic. In this case, variablename is data. You still have to use the array-subscripting notation, because you still can't modify a v-for alias variable, but Vue is smart about .sync on the array element, so there's no messy splice.
Vue.component('time-range', {
template: '#time-range',
props: ['data'],
methods: {
emitUpdate(event) {
this.$emit('update:data', event.target.value);
}
}
})
new Vue({
el: '#app',
data: {
ranges: [],
},
methods: {
addRange: function () {
this.ranges.push('')
},
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div class="container" id="app">
<div class="row">
Parent Val
{{ ranges }}
</div>
<div class="row">
<button
v-on:click="addRange"
type="button"
class="btn btn-outline-secondary">Add time-range
</button>
</div>
<time-range
v-for="range, index in ranges"
:data.sync="ranges[index]"
:key="index">
</time-range>
</div>
<template id="time-range">
<div class="row">
<input :value="data" type="text" #change="emitUpdate">
</div>
</template>

Categories