How can I change the whole data of a vue object? - vue.js

Simply, I just want to change the entire data of a Vue object, like this:
vueobj.$data = newdata;
but the official document says it's not allowed:
VM156 vue.js:597 [Vue warn]: Avoid replacing instance root $data. Use
nested data properties instead.
(found in )
So I tried another way: first destroy the the vueobj by $destroy(), then create a new vueobj to bind new data object to the same the UI element, but after that, the UI element still uses the old data. So how could I solve this problem? thanks!

this works for me:
<div id="app">
{{name}}: {{age}}
</div>
const app = new Vue({
el: "#app",
data: {
name: "Alice",
age: 30
},
methods: {
refresh(data) {
Object.assign(this.$data, data); // <--
}
}
});
const newData = {
name: "Bob",
age: 22
};
app.refresh(newData);

It is tricky, but you could iterate over the data properties and:
First: remove all data properties;
Second: add each property of the newdata object to the data object.
Demo below:
new Vue({
el: '#app',
data: {
name: 'Alice',
age: 30
},
methods: {
changeWholeData() {
let newdata = {name: 'Bob', age: 40};
// erase all current keys from data
Object.keys(this.$data).forEach(key => this.$data[key] = null);
// set all properties from newdata into data
Object.entries(newdata).forEach(entry => Vue.set(this.$data, entry[0], entry[1]));
}
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p>Name: {{ name }}</p>
<p>Age: {{ age }}</p>
<button #click="changeWholeData">Change Whole Data</button>
</div>

As it says, you need to use a nested property to wrap the data you want to replace so it just takes a little planning up front.
If you create your Vue instance like this,
var vueobj = new Vue({
el: '#app',
data: {
dataSource: {
name: 'Alice',
age: 30
}
}
})
then you can replace your data like this
vueobj.dataSource = newdata;
The dataSource wrapper property can be any valid property name. The downside is you can no longer access your inner properties directly on the Vue instance. E.G. you have to use vueobj.dataSource.name instead of vueobj.name, but I guess that's the trade off for all the other ease of use Vue is providing. You could create a name computed to solve this on a case-by-case basis but it would be tedious to do if there are many root properties.

Related

Vue interpolation does not work in the text obtained by API

Get text by API, in text have a entities in {{}}, like a:
Some text {{rules}} other text
Have in data values:
rules: "some text"
but this values dont interpolated, displays:
Some text {{rules}} other text
thanks for answer
Considering the comment above:
I have: template: {{someText}} have data: data() { return {
someText: "some text {{ myValue }} some text", myValue: "300", } },
How display data from myValue in template?
You don't do interpolation like that in Vue. Double braces ({{}}) are for the template part, not the scripts.
Look at this snippet:
new Vue({
el: "#app",
data() {
return {
someText: "some text {{ myValue }} some text",
myValue: "300"
}
},
computed: {
parsedSomeText() {
let ret = ''
if (/(\w+)(?= }})/g.test(this.someText)) {
let key = String(this.someText).match(/(\w+)(?= }})/g)[0]
if (Object.keys(this.$data).includes(key)) {
ret = this.someText.replace(/{{ (\w+) }}/g, this.$data[key])
}
}
return ret
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
myValue: {{myValue}}<br /> someText: {{someText}}<br />parsedSomeText: {{parsedSomeText}}
</div>
I created a small parser in parsedSomeText() computed property, that very crudely replaces the double braces in the someText data property and returns the modified string, so to get it working in the data property.
I advise you to look over the data you receive, and think of another solution - or utilize some rock-solid parsing technique to use it in production.
If you cannot avoid getting data in such a way, then you should look into dynamic (run-time) compilation (like: https://alligator.io/vuejs/v-runtime-template/) and render functions (https://v2.vuejs.org/v2/guide/render-function.html). With these, your Vue app becomes more versatile but more complex.

Vue-Select: Pushing a 2 dimensional array to :options

The plugin Vue-Select.
What I was trying to do is, make a search-select-dropdown input based on database.
So here's my SQL first named Ms_Location.
id_Loc | name_Loc
LOC0001 | Indonesia
LOC0002 | China
LOC0003 | America
My index.php
<!DOCTYPE html>
<html>
<head>
</head
<body>
<div class="form-group">
<label for="lokasi_id" class="control-label required"><strong>Lokasi</strong></label>
<v-select :options="lokasi_list" placeholder='Type location..'></v-select>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script src="https://unpkg.com/vue-select#latest"></script>
Vue.component('v-select', VueSelect.VueSelect);
var app = new Vue ({
el: '#app',
data: {
lokasi_select: '',
lokasi_list: [],
},
// End of data
computed: {
get_lokasi() {
var list_loc = new Array();
list_loc = <?php include('receive_lokasi.php') ?>;
for(var i=0; i<list_loc.length; i++) {
var pushLoc = {
label: list_loc[i][1], value: list_loc[i][0]
}
this.lokasi_list.push(pushLoc);
}
return list_loc[0][1];
}
}
})
});
</script>
</body>
</html>
And this is my receive_lokasi.php
<?php
include ('koneksi.php');
$condition = "1";
if(isset($_GET['userid'])){
$condition = " id=".$_GET['userid'];
}
$sqltran = mysqli_query($con, "SELECT id_Loc, name_Loc FROM ms_location")or die(mysqli_error($con));
$response = array();
while ($rowList = mysqli_fetch_array($sqltran,MYSQLI_NUM)) {
$response[] = $rowList;
}
echo json_encode($response);
mysqli_close($con);
?>
However, I can't seem to get the option shown. This only happens after I make the get_lokasi(). So the mistake is probably there? Or perhaps I was missing something.
I've tried to print the lokasi_list somewhere, and yes, the value is there, but not shown in the dropdown bar.
Also, I'm new to Vue, so any help would be good. Thanks!
Nevermind..
My mistake, I didn't notice my receive_lokasi.php code
Instead of using MYSQLI_NUM
while ($rowList = mysqli_fetch_array($sqltran,MYSQLI_NUM)) {
$response[] = $rowList;
}
I should be using MYSQLI_ASSOC, as documented in here.
while ($rowList = mysqli_fetch_array($sqltran,**MYSQLI_ASSOC**)) {
$response[] = $rowList;
}
After that change this
<v-select :options="lokasi_list" placeholder='Type location..'></v-select>
To this
<v-select label='nama_Location' :options="lokasi_list" placeholder='Type location..'></v-select>
After that, everything loads fine.
Vue's computed properties aren't normally used to populate vue data attributes, they normally take one or more data attributes and combine them into something different for the template to use.
In your code you've tried to populate the vue data attribute 'lokasi_list' in the computed property 'get_lokasi', but you never call 'get_lokasi' anywhere in the template so lokasi_list remains empty.
Another approach to this sort of situation is to use a vue method to fetch data from the php backend via an ajax call with something like axios, and you'd normally use that method in the vue app's created life cycle event to get the data asap.
e.g.
<script>
Vue.component('v-select', VueSelect.VueSelect);
var app = new Vue({
el: '#app',
data: {
lokasi_select: '',
lokasi_list: [],
},
created: function() {
this.fetchLocations();
},
methods: {
fetchLocations: function() {
axios.get('/api/locations-end-point')
.then((response) => {
this.lokasi_list = response.data //might need to change this to match how your php is returning the json
})
.catch((error) => {
//handle the error
})
}
}
});
</script>
Sorry to mention this, but in your php you've got:
if(isset($_GET['userid'])){
$condition = " id=".$_GET['userid'];
}
That looks like you were planning to use it as part of your sql, but it would have been vulnerable to SQL injection attacks, sorry If I'm pointing out something you already knew.

Vue.js How to output attribute inside html tag when attribute name is included in value?

If I have a data structure that contains field attributes as follows, how can I output the dataAttributes value inside the html?
var app3 = new Vue({
el: '#app-3',
data: {
field: {
type: 'text,
name: 'First Name',
class: 'form-control js-user-lookup',
dataAttributes: 'data-autocomplete-url=api/users data-selected=123',
}
}
})
<input :type="field.type"
:id="field.name"
:name="field.name"
:class="field.class"
{{field.dataAttributes}} />
You can't use the mustache syntax inside of html tags and I cant bind it to a data-* attribute since the attribute is part of the value e.g. data-autocomplete-url and data-selected?
You cannot do that with plain data-binding syntax. You will need to use the custom directive. It will look like this.
<input :name="field.name" v-data-binder="field.dataAttributes" />
Your directive definition will be something like:
// Register a global custom directive called `v-focus`
Vue.directive('data-binder', {
// When the bound element is inserted into the DOM...
inserted: function (el, binding) {
// Perform data attribute manipulations here.
// 1. Parse the string into proper key-value object
// binding.value
// 2. Iterate over the keys of parsed object
// 3. Use JavaScript *dataset* property to set values
}
})
You will also need updated hook in your directive definition to remove existing data-* attributes whenever the value passed to the directive changes.
You can more easily do it if you have the dataAttributes string passed as a javascript object and just bind it like that v-bind="myobject". If not possible you can transform it via a computed property
Check below example
<div id="app">
</div>
var instance = new Vue({
el:'#app',
data:function(){
return {
inputType:'password',
fieldAttributes:"data-autocomplete-url=api/users data-selected=123"
};
},
computed:{
getDataAttributes:function(){
var attributes = this.fieldAttributes.split(' ');
var attributesO = {};
for(var a=0;a<attributes.length;a++){
var attribute = attributes[a];
var attribute_ar = attribute.split('=');
attributesO[attribute_ar[0]] = attribute_ar[1];
}
return attributesO;
}
},
methods:{
getAttribute:function(){
alert(this.$refs.myinput.dataset.selected)
}
},
template:`<div>
<input ref="myinput" v-on:click="getAttribute" :type="inputType" v-bind="getDataAttributes" />
</div>`
});

How to use parent's mixin as a child model

Im trying to use a parent's mixin as a child model, but I couldn't make it work as the mixin isn't resolved on the 'data' definition.
Here is a fiddle:
<div id="vue-instance">
<div>
USER: {{user}}
<br/>
EMAIL: {{email}}
</div>
<input-list :field="field" v-for="field in fields"/>
</div>
js:
Vue.component('input-list', {
name: 'InputList',
props: ['field'],
template: '<div>{{field.id}}: <input type="text" v-model="field.model"/></div>'
})
var userData = {
data() {
return {
user: 'foo',
email: 'foo#barbar'
}
}
}
var vm = new Vue({
el: '#vue-instance',
mixins: [userData],
data() {
return {
fields: [
{
id: "UserName",
model: this.user
},
{
id: "Email",
model: this.email
}
]
}
}
});
https://jsfiddle.net/rafalages/rp7bu2qt/9/
The expected result would be update parent mixin value in the child input.
This is not an issue with the mixin, but rather the overall design, if you we to use data instead of mixin, you'd see the same behaviour.
This will not work they way you intend it to.
Two things about Vue worth re-iterating:
You pass props down and emit events up
Mutating a prop will not update the parent
more reading here:
https://v2.vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow
There is a note at the bottom
Note that objects and arrays in JavaScript are passed by reference, so if the prop is an array or object, mutating the object or array itself inside the child component will affect parent state.
But my recommendation is to think of this as a side-effect rather than a feature. You should not rely on the child from updating the value. The only reliable way (outside of other state management) is to pass a function.
Another issue is that referencing this in data during creation will not work
data: {
fields: [
{
id: "UserName",
model: this.user
},
{
id: "Email",
model: this.email
}
]
}
This will not use the data from this.user and will be set to null. You can use the mounted life-cycle function to set these like this:
mounted() {
this.fields = [
{
id: "UserName",
model: this.user
},
{
id: "Email",
model: this.email
},
]
}
which will set it to use the initial values. But referencing an object like this inside another object will also serve to create new bindings, essentially cutting the reactivity. This means that you'll use the values from those objects, but changes to this.fields[1].model will not update this.email
here is a semi-working fiddle that attempts to use your current design
https://jsfiddle.net/rp7bu2qt/113/
notice that...
the binding is using v-model, which is shorthand for binding a prop and an emit/update to a value
you see an error
You are binding v-model directly to a v-for iteration alias. This will not be able to modify the v-for source array because writing to the alias is like modifying a function local variable. Consider using an array of objects and use v-model on an object property instead.
changes to the input fields do not change the original data, just the content of fields[n].model

How to initialize value based on ajax response?

I need to create a checkbox that will be checked/unchecked depending on the value of a parameter coming from the database.
I'm not able to load that value when I'm rendering the page, so the idea is: render the page, "tell" the checkbox to "ask" the server what is the current value of the parameter and then check/uncheck the checkbox depending on the response. Then, if the user checks/unchecks the checkbox, make a new Ajax request to update the value in the database.
I wrote some code (I'm new in Vuejs, so for sure I'm doing something wrong):
var vm = new Vue({
el: '#root',
computed: {
checked() {
return this.initialize()
},
value() {
return this.checked
}
},
watch: {
checked() {
alert('watcher')
this.update();
}
},
methods: {
initialize(){
// Just pretending an initial value
var randomBoolean = Math.random() >= 0.5;
alert('Ajax request here to initialize it as ' + (randomBoolean ? 'checked' : 'unchecked'));
return randomBoolean;
},
update(){
alert('ajax request here to set it to ' + this.value)
}
}
});
You can check and run the code here: https://jsfiddle.net/hyn9Lcv2/
Basically it works to initialize the checkbox, but then it fails to update. If you check the console, there is this error:
[Vue warn]: Computed property "checked" was assigned to but it has no setter.
First have you thought of using the created() hook from the vue instance instead of watcher?
It's recommended and will execute the code as soon as the component is created.
From the doc:
new Vue({
data: {
a: 1
},
created: function () {
//Ajax call:
//onsuccess(response){
this.a = reponse.data.a
}
}
})
in the created hook you can do your ajax call, (axios is good library for that, worth checking it out: https://github.com/axios/axios ).
Then from your ajax response you can link the desired value to your checkbox by assigning it to a variable in the data object of the instance (in our case 'a')
Then bind it to your checkbox with the v-model like this:
<input
type="checkbox"
v-model="a">
I recommend to check the vue doc for more info on biding: https://v2.vuejs.org/v2/guide/forms.html#Checkbox-1
Hope it helps.
Just add bind click event
<div id="root">
<input id="check" type="checkbox" name="active" v-model="checked" #click="update">
<label for="check">Click me</label>
</div>
You need to fetch the database value when the component is created or mounted.
You then need to bind your checkbox with the initialized data.
Finally you need to watch the data to send an update to the database.
var vm = new Vue({
el: '#root',
data: {
//Your data
checked: null
},
// Function where you are going to fetch your data
mounted: function () {
console.log("Ajax call to initialize");
this.checked = Math.random() >= 0.5;
},
watch: {
// Watcher to save your data in the database
checked: function(newValue, oldValue){
if (oldValue === null) { return; } // to not make an useless update when data has been fetched
console.log("Ajax call to update value " + newValue);
}
}
});
<div id="root">
<input id="check" type="checkbox" name="active" v-model="checked" :disabled="checked === null">
<label for="check">Click me</label>
</div>
To fetch your data you can use for example Axios that works great with Vue.
To know more about life cycle of a component (to know if you should do the fetching at created or mounted) : https://v2.vuejs.org/v2/guide/instance.html