bind data to vue model dynamically in component - javascript

I'm trying to make a simple form that will accept user's input for different types of currency.
Here's a (broken) fiddle that hopefully gets across what I want to do:
https://jsfiddle.net/4erk8yLj/7/
I'd like my component to bind data to my root vue instance, but I'm not sure if my v-model string is allowable. Check it out:
Vue.component('conversion-row', {
props: ['currency', 'values'],
template: '<div>{{currency}}:</div><div><input v-model="values[currency]></div><',
});
var vm = new Vue({
el: "#app",
data: {
currencies: ['USD', 'BTC'],
values: {
'BTC': '',
'USD': ''
}
}
});
template:
<div id="app">
<li>
<conversion-row is li v-for="currency in currencies" v-bind:currency="currency">
</conversion-row>
</li>
</div>
What's a good way to fix this?

Couple of things you might need to correct:
First, the data property must be a function rather than an object. This allows every instance to get data recomputed every time it is being created, see:
var vm = new Vue({
el: "#app",
data() {
return {
currencies: ['USD', 'BTC'],
values: {
'BTC': 'BTC Value',
'USD': 'USD Value',
},
};
}
});
Second, <conversion-row> doesn't have values property bound. Here's what you can do:
<div id="app">
<li v-for="currency in currencies">
<conversion-row :currency="currency" :values="values"></conversion-row>
</li>
</div>
Last, the component should always aim for one root element (wrapper) and then you can nest as many children inside as you want. What's more, instead of using v-model, you can bind value which is the proper way to pass a value to an input (one-way data binding), check the following:
Vue.component('conversion-row', {
props: ['currency', 'values'],
template: '<div>{{currency}}:<input type="text" :value="values[currency]"></div>'
});
There's more improvements you could possibly make here like re-thinking if you need to pass values as well as currency to the conversion-row but I'm pretty sure you'll figure it out later on.
All that above will make your code run and execute properly, here's the working example (fork of yours):
https://jsfiddle.net/maciejsmolinski/mp8m0ben/1/
Does this help?
Not sure what you're aiming for in terms of using v-model, but here's an example of working v-model (based on your example):
Vue.component('conversion-row', {
props: ['currency', 'values'],
template: '<div>{{currency}}:<input type="text" v-model="values[currency]"></div>'
});
And the corresponding template:
<div id="app">
<p><strong>USD Value:</strong> {{ values.USD }}</p>
<p><strong>BTC Value:</strong> {{ values.BTC }}</p>
<br>
<li v-for="currency in currencies">
<conversion-row :currency="currency" :values="values"></conversion-row>
</li>
</div>
You can find it under the following URL:
https://jsfiddle.net/maciejsmolinski/0xng8v86/2/

Related

Using Vue.js directives within component template

I'm new to Vue.js and trying to create a component that connects to one object within some global-scope data and displays differently based on the specifics of each object. I think I'm misunderstanding how the directives v-if and v-on work within component templates. (Apologies if this should actually be two different questions, but my guess is that the root of my misunderstanding is the same for both issues).
Below is a minimal working example. My goal is to have each member entry only display the Disable button if the associated member is active, and enable changing their status via the button. (I also want to keep the members data at the global scope, since in the actual tool there will be additional logic happening outside of the app itself).
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<member-display
v-for="member in members"
v-bind:member="member"
></member-display>
</div>
<script>
var members = [
{name: "Alex", status: "On"},
{name: "Bo", status: "On"},
{name: "Charley", status: "Off"}
]
Vue.component('member-display', {
props: ['member'],
computed: {
active: function() {
// Placeholder for something more complicated
return this.member.status == "On";}
},
methods: {
changeStatus: function() {
this.member.status = 'Off';
}
},
// WHERE MY BEST-GUESS FOR THE ISSUE IS:
template: `
<div>
{{member.name}} ({{member.status}})
<button v-if:active v-on:changeStatus>Disable</button>
</div>
`
});
var app = new Vue({
el: "#app",
data: {
members: members
}
})
</script>
</body>
</html>
Thanks for your help!
The code v-if and the v-on for the button just have the wrong syntax. The line should look like this:
<button v-if="active" v-on:click=changeStatus>Disable</button>

Prop is undefined on Vue

HTML passed props to the Vue tag, no value existed. Even if I tried to print out mounted, created, undefined was printed.
Vue is running on the Django Server. So I changed delimiters and printed the apps, but nothing was printed out.
HTML Code
<radio-input :radio-content="personal"></radio-input>
Vue Code
radioInput = new Vue({
props: ["radioContent"],
template:
'<label>\n' +
' <input type="radio" :name="name" :value="value" :v-model="v_model" :required="required">\n' +
' <span>${ radioContent }</span>\n' +
'</label>\n',
delimiters: ['${', '}'],
el: 'radio-input',
data: {
name: 'A1_P1_S1_B0',
value: 'personal',
v_model: 'A1P1S1Q',
required: true,
}
});
Expected Result is "radioButton + 'personal' text". But "personal" text is not printed
Since personal is not a variable in the parents's data albeit its a static value that is passed to the <radio-input/> component.
To pass static props binding colons :someProp are not needed. When you use that its basically v-bind:someProp where the Vue component is going to look for someProp in its data or would try to consider it as a javascript expression like Number or Boolean or Array and not as static text to be passed on.
Change the HTML code for component to:
<radio-input radio-content="personal"></radio-input>
--Update based on code snippet & comment--:
Okay! couple of things here
props in Vue are something that is passed on from parent to child (consider it a way of communicating things parent->child which are Vue components or instance not pure HTML).
Why is not working here: You have only one Vue instance for
<radio-input/> that is sort of parent so defining prop onto it is
meaningless since there is no parent Vue that can pass this. Read
Some More
In this case you could simply remove the prop and the binding value
altogether and use personal text directly from data property
value that u've defined, if that's what you want.
<html>
<head>
</head>
<body>
<radio-input></radio-input>
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<script>
radioInput = new Vue({
template: '<label>\n' +
' <input type="radio" :name="name" :value="value" :v-model="v_model" :required="required">\n' +
' <span>${ value }</span>\n' +
'</label>\n',
delimiters: ['${', '}'],
el: 'radio-input',
data: {
name: 'A1_P1_S1_B0',
value: 'personal',
v_model: 'A1P1S1Q',
required: true,
}
});
</script>
</body>
</html>

Forcing v-validate to update rules (with Vue)

I'm using v-validate with Vue. I'm trying to figure out how to force v-validate to update rules. For example, I have something like this:
<template>
<div v-for="field in fields">
<input :name="field.name" v-validate="field.rules">
</div>
</template>
<script>
export default {
data() {
fields: [
{
name: "city",
rules: {
included: []
}
}
]
}
}
</script>
As you can see, my "included" array is empty on page load. I get the array from an AJAX request, and then I update my data:
this.fields[0].rules.included = cities
But v-validate doesn't seem to acknowledge the newly-added array. It only works if I hardcode the cities into my data. How can I force v-validate to respond to the updated rules?
Vue.js is unable to track updates on nested reference types.
Try:
let fields = [...this.fields]
fields[0].rules = cities
this.fields = fields
Use Vue.set to track changes : https://v2.vuejs.org/v2/guide/reactivity.html
Vue.set(this.fields[0], 'rules', cities);

Vue.js 2.5 list rendering

I have an array in JS/Vue that I would like to display in <ul>/<li> tags, and keep updated as the array gets new elements.
HTML:
<ul id="ulist">
<li v-for="user in users">
#{{ user }} <!-- "#" needed since this is in a laravel project with blade templates -->
</li>
</ul>
JS:
<script>
var socket = io('localhost:3000');
new Vue({
el: "#ulist",
data: {
users: []
},
mounted: function() {
this.$nextTick(function() {
socket.on('test-action', function(data) {
this.users.push(data.username);
console.log(data.username);
}.bind(this));
});
}
});
</script>
The array is being properly populated (as I can see via the console.log statement), but the <li v-for="user in users">... part doesn't seem to be working as none of the <li>...</li> elements get created. What am I doing wrong here?
Just to clarify: if I add hard coded values to the users array, those values show up in <li> elements fine, but no additional values that are added to the array (in the mounted function) show up in <li>...</li> elements.
Edit: version is 2.5.13, if it matters
Can you try this ?
<script>
var socket = io('localhost:3000');
new Vue({
el: "#ulist",
data: {
users: []
},
mounted: function() {
var _self = this;
this.$nextTick(function() {
socket.on('test-action', function(data) {
self.users.push(data.username);
console.log(data.username);
}.bind(this));
});
}
});
</script>
The problem is with the scope of your this variable. In your code this line:
this.users.push(data.username);
is scoped to the function call within the ajax request, if you use () => it will keep the current scope in context within your method. Also, you shouldn't need nextTick within the mounted call so try this:
<script>
var socket = io('localhost:3000');
new Vue({
el: "#ulist",
data: {
users: []
},
mounted: function() {
socket.on('test-action', data => {
this.users.push(data.username);
console.log(data.username);
});
}
});
</script>
Although you were using bind(this) you were using this within nextTick which was causing scope issues.
Another thing worth noting, lists require a key in vue v?? (I can't remember which) so it's best to add a key when using v-for:
<ul id="ulist">
<li v-for="(user, index) in users" :key="index">
#{{ user }} <!-- "#" needed since this is in a laravel project with blade templates -->
</li>
</ul>

polymerfire/firebase-query transaction complete event

Very new to Polymer and Polymerfire. I couldn't find an answer here so hoping I can get help here. The basic question I have is "how do I work with the data that polymerfire/firebase-query sends?" Note I'm using polymerfire version 0.9.4, and polymer is version 1.4.0.
I can load my data from Firebase no problem using Firebase query, however some of the values are raw numbers that I need to convert to user friendly information. For example I have time stored in ms that I want to convert to a date, and a numeric field that indicates the "type" of data that is stored and I want to show an icon for it, not just a raw number. I figured my best option would be to use the transactions-complete promise or an observer. Both fire but neither seems to give me access to the data. The Observer's newData is an empty array, and transactions-complete.. well I don't really know what to do with that when the promise fires. Below is my relevant code. I also tried using notify: true, but I seem to not be grasping the concept correctly.
<firebase-query
id="query"
app-name="data"
path="/dataPath"
transactions-complete="transactionCompleted"
data="{{data}}">
</firebase-query>
<template is="dom-repeat" items="{{data}}">
<div class="card">
<div>Title: <span>{{item.title}}</span></div>
<div>Date Created: <span>{{item.dateCreated}})</span></div>
<div>Date Modified: <span>{{item.dateModified}}</span></div>
<div>Status: <span>{{item.status}}</span></div>
</div>
</template>
Polymer({
is: 'my-view1',
properties: {
data: {
notify: true,
type: Object,
observer: 'dataChanged'
}
},
dataChanged: function (newData, oldData) {
console.log(newData[0]);
// do something when the query returns values?
},
transactionCompleted: new Promise(function(resolve, reject) {
// how can I access "data" here?
})`
I wound up going another way entirely, which seemed to be a cleaner approach to what I was doing anyways. I broke it down into separate components. This way when the detail component was loaded, the ready function would allow me to adjust the data before it got displayed:
list.html:
<firebase-query
id="query"
app-name="data"
path="/dataPath"
data="{{data}}">
</firebase-query>
<template is="dom-repeat" items="{{data}}">
<my-details dataItem={{item}}></my-details>
</template>
details.html
<template>
<div id="details">
<paper-card heading="{{item.title}}">
<div class="card-content">
<span id="description">{{item.description}}</span><br/><br/>
<div class="details">Date Created: <span id="dateCreated">{{item.dateCreated}}</span><br/></div>
<div class="details">Last Modified: <span id="dateModified">{{item.dateModified}}</span><br/></div>
<div class="status"><span id="status">{{item.status}}</span><br/></div>
</div>
</paper-card>
</template>
Then in the javascript ready function I can intercept and adjust the data accordingly:
Polymer({
is: 'my-details',
properties: {
item: {
notify: true,
},
},
ready: function() {
this.$.dateModified.textContent = this.getDate(this.item.dateModified);
this.$.dateCreated.textContent = this.getDate(this.item.dateCreated);
this.$.status.textContent = this.getStatus(this.item.status);
},
Try the following changes:
Take out the transactions-completed attribute - it is only relevant when the query is updating data to Firebase
Change the dom-repeat template to get it's items attribute from convertedData - this allows you to do the data conversions to## Heading ## the results of the firebase-query
<firebase-query
id="query"
app-name="data"
path="/dataPath"
data="{{data}}">
</firebase-query>
<template is="dom-repeat" items="{{convertedData}}">
<div class="card">
<div>Title: <span>{{item.title}}</span></div>
<div>Date Created: <span>{{item.dateCreated}})</span></div>
<div>Date Modified: <span>{{item.dateModified}}</span></div>
<div>Status: <span>{{item.status}}</span></div>
</div>
</template>
Add a convertedData property to do your data conversions from data which has the raw data
Change the observer syntax as per the example. This sets up the observer to to observe for changes to deep property values which results in the observer method being fired - see: https://www.polymer-project.org/1.0/docs/devguide/observers#deep-observation
In the observer method you can populate the convertedData object from the data object which should then render the content
Polymer({
is: 'my-view1',
properties: {
data: {
notify: true,
type: Object
},
convertedData: {
notify: true,
type: Object
}
},
// observer syntax to monitor for deep changes on "data"
observers: [
'dataChanged(data.*)'
]
dataChanged: function (newData, oldData) {
console.log(newData);
// convert the "newData" object to the "convertedData" object
}
}

Categories

Resources