How to structure nested reactive data in VUE - vue.js

I think this is a fairly common problem.
You fetch a list from an api, and then you display that list via
<div v-for="item in items">
<checkbox v-model="item.checked">
</div>
Now My question is about the checked property. when iterating over a list of undefined length, of unknown keys, it seems the checked property has to be created and attached to the item object like so
computed: {
items () {
var newList = Object.assign([], this.rootList) // shallow clone the api list
for (var i of newList) {
i.checked = false
// or
Vue.set(i, 'checked', false)
}
return newList
}
However this is not making my checkbox reactive. But more importantly, this way of adding new properties to the rootList object clone, is this the best practice? and if so, why is this not reactive? Even when using Vue.set

Computed properties are by default getter-only [...]
https://v2.vuejs.org/v2/guide/computed.html#Computed-Setter
Due to the limitations of modern JavaScript (and the abandonment of Object.observe), Vue cannot detect property addition or deletion. Since Vue performs the getter/setter conversion process during instance initialization, a property must be present in the data object in order for Vue to convert it and make it reactive.
https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
This might help: https://jsfiddle.net/eywraw8t/187063/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<div v-for="item in items">
<input type="checkbox" v-model="item.checked"> {{ item.name }}
</div>
<button #click="fetch">Fetch more items</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
items: []
},
methods: {
fetch() {
let itemsFromApiResponse = [
{ name: "Test 1" },
{ name: "Test 2" },
{ name: "Test 3" },
];
itemsFromApiResponse.forEach(item => this.items.push(Object.assign({ checked: false }, item)));
}
}
})
</script>
</body>
</html>

Related

How to bind selectedIndex of a select element with vue?

why can't I bind the selectedIndex property of a select element to a variable with vue and how can I achieve to do it anyway?
I'm trying to synchronize two select elements in a page by the index of the selected option. In order for the synchronization to work, the index must be propagated in both ways: from any element to a vue data variable and from this vue data variable to both elements.
At first, I tried to use v-bind.sync, but since it didn't work, I decided to try the explicit way via v-bind and a v-on:change event handler method. While updating the data variable in the event handler works, the binding doesn't. This is my example code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
</head>
<body>
<div id="app">
<select v-bind:selectedIndex="index" v-on:change="indexChanged">
<option>zero</option>
<option>one</option>
</select>
<select v-bind:selectedIndex="index" v-on:change="indexChanged">
<option>zero</option>
<option>one</option>
</select>
Index stored in vue-app: {{ index }}
</div>
<script>
let app = new Vue({
el: "#app",
data: {
index: 0,
},
methods: {
indexChanged: function(event) {
this.index = event.target.selectedIndex;
}
}
});
</script>
</body>
</html>
One clue I noticed, is that PyCharm complains where I try to bind selectedIndex. The tooltip says: "Unrecognized attribute or property name". (It also says "vue#3.2.0", although version 2.5.17 is in use here, which puzzles me.)
Anyway, I can perfectly get and set the value via the JS console in the browser and the select actually updates its selected option according to the new index.
>> document.getElementsByTagName("select")[0].selectedIndex
<- 0
>> document.getElementsByTagName("select")[0].selectedIndex = 1
<- 1
I looked for another property that holds the selected index' information and the only one I found was .option.selectedIndex. I don't know how to bind this either.
The only way I'm seeing now, is to bypass vue's reactiveness and take the sledgehammer approach:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
</head>
<body>
<div id="app">
<select v-on:change="indexChanged">
<option>zero</option>
<option>one</option>
</select>
<select v-on:change="indexChanged">
<option>zero</option>
<option>one</option>
</select>
Index stored in vue-app: {{ index }}
</div>
<script>
let app = new Vue({
el: "#app",
data: {
index: 0,
},
methods: {
indexChanged: function(event) {
this.index = event.target.selectedIndex;
const selects = document.getElementsByTagName("select");
for (let i = 0; i < selects.length; i++) {
selects[i].selectedIndex = this.index;
}
}
}
});
</script>
</body>
</html>
I'm aware that I could make the connection over the selects' value properties, which store the selected options' text with v-model, but this wouldn't be unambiguous if multiple options had the same text, which (shouldn't, yeah, but) might happen in my case. Is it in fact not possible to bind selectedIndex, because one shouldn't do it since the options' texts should always be unique? I'd be grateful for advice here.
You can also set a unique value for option equal to the array index. Example below:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
</head>
<body>
<div id="app">
<select v-model="selectIndex">
<option v-for="(item, index) in options" :key="index" :value="index">{{ item }}</option>
</select>
<select v-model="selectIndex">
<option v-for="(item, index) in options" :key="index" :value="index">{{ item }}</option>
</select>
Index stored in vue-app: {{ selectIndex }}
</div>
<script>
let app = new Vue({
el: "#app",
data: {
selectIndex: 0,
options: ['zero', 'one']
}
});
</script>
</body>
</html>

Vue: Why can't I use refs within component templates?

I'm struggling with Vue refs. If I define them in my main Vue instance's template, they work fine, but if I define them within a component template, they don't. What am I doing wrong?
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
</head>
<body>
<div id="app"></div>
<script src="main.js"></script>
</body>
</html>
main.js, version 1
const app = new Vue({
el: '#app',
template: `
<div ref="divRef">
<button ref="buttonRef">Submit</button>
</div>
`
});
result (matches expectation)
> app.$refs
> {buttonRef: button, divRef: div}
main.js, version 2
Vue.component('demo-page', {
template: `
<div ref="divRef">
<button ref="buttonRef">Submit</button>
</div>
`
});
const app = new Vue({
el: '#app',
template: '<demo-page ref="componentRef"></demo-page>'
});
expected result
> app.$refs
> {componentRef: VueComponent, buttonRef: button, divRef: div}
actual result
> app.$refs
> {componentRef: VueComponent}
$refs are scoped within a component. That's the reason, you can only see the componentRef in the $refs of the app itself. The other $refs are accessible within the scope of each component. So, try to access the $refs within the scope of that component.
If you are defining a ref on an element also having the attribute v-for on it, this.$refs.refName will return you an array of DOM elements. Keep in mind that $refs are not reactive unlike data properties.
See also the documentation to $refs with child component instances and the documentation to the ref="" attribute.

Error when rendering component in Vue.js

I am new to Vue.js and am trying to create a sample component as per the code below but ending up with "[Vue warn]: Error when rendering component <my-tag>: " I have looked at stackoverflow for a similar question asked before but that did not help. The code for component is as below:
Vue.component('my-tag', {
props: ['myTagAttr'],
template: '<span>{{myTagAttr.text}}</span>'
})
var data = {
myTagAttrVal: {
text: 'foobar',
color: 'Red'
}
}
var vm = new Vue({
el: '#demo',
data: data
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
</head>
<body>
<span id='demo'>
<my-tag v-bind:myTagAttr='myTagAttrVal'></my-tag>
</span>
</body>
</html>
Alternatively, code can be found at JSbin
HTML attributes are case-insensitive, so when using non-string templates, camelCased prop names need to use their kebab-case (hyphen-delimited) equivalents :
<my-tag v-bind:my-tag='myTagAttrVal'></my-tag>
Here is a working fiddle: https://jsfiddle.net/nxcbm6na/
You can find the details in the documenation
https://v2.vuejs.org/v2/guide/components.html#camelCase-vs-kebab-case

vue.js2 console err is "app.js:2 Uncaught SyntaxError: Unexpected identifier"

the result should be a paragraph contain a name and button to change this name
from "adel" to "mohamed" but it is not working ,not showing any thing in the browser and i have console err is:
app.js:2 Uncaught SyntaxError: Unexpected identifier,
You are running Vue in development mode.
Make sure to turn on production mode when deploying for production.
See more tips at https://vuejs.org/guide/deployment.html
html code
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>vue.js course</title>
<link rel="stylesheet" href="./styles.css">
<script src="https://unpkg.com/vue"></script>
</head>
<body>
<div id="vue-app-one">
<greeting></greeting>
</div>
<div id="vue-app-two">
<greeting></greeting>
</div>
<script src="./app.js"></script>
</body>
</html>
app.js code
Vue.component('greeting', {
template: "<h1>hello {{ name }}. <button v-on:click="chang">change name</button></h1>",
data(){
return {
name:"adel"
}
},
methods: {
chang: function(){
this.name = 'mohamed';
}
}
});
var one = new Vue({
el: '#vue-app-one'
});
var two = new Vue({
el: '#vue-app-two'
});
It's simple. Just change the double quotation marks used in template to single quotation marks.
you can't include the same quote mark inside the string if it's being used to contain them
https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Strings
template: "<h1>hello {{ name }}. <button v-on:click="chang">change name</button></h1>",
to
template: "<h1>hello {{ name }}. <button v-on:click='chang'>change name</button></h1>",
jsfiddle that works

How to use an Observable object in a WinJS ListView

I want to use an observable object within a ListView in the Windows 8 RT (Developer Preview from BUILD 2011) (using JavaScript).
The code below seems like it should work. It has a simple template for displaying a title and a description of each object in the HTML and a basic use of the WinJS.UI.Listview component.
I expect to see a list of objects, but always see the "wait spinner" when the list contains observables.
Experimentally, I've noticed that if the code doesn't convert the entire list (all but 3) to observables, then the list will show up. From doing some debugging, it would appear that it's somehow timing related and that the WinJS framework miscounts and fails to render the ListView entirely (as some of the objects are "pending") for some reason (the miscount confusion happens deep in a call to realizeItems in the ScrollView code). If I comment out the enableFirstChanceException function call, it fails while comparing two objects (but I don't know if it's relevant) in the function itemChanged (circular reference in value argument not supported).
Any idea on how to make this work with observable objects?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=1024, height=768" />
<title>WinWebApp1</title>
<!-- WinJS references -->
<link rel="stylesheet" href="/winjs/css/ui-dark.css" />
<script type="text/javascript" src="/WinJS/js/base.js"></script>
<script type="text/javascript" src="/WinJS/js/ui.js"></script>
<script type="text/javascript" src="/WinJS/js/binding.js"></script>
<script type="text/javascript" src="/WinJS/js/controls.js"></script>
<script type="text/javascript" src="/WinJS/js/res.js"></script>
<script type="text/javascript" src="/WinJS/js/animations.js"></script>
<script type="text/javascript" src="/WinJS/js/uicollections.js"></script>
<script type="text/javascript" src="/WinJS/js/wwaapp.js"></script>
<!-- WinWebApp1 references -->
<link rel="stylesheet" href="/css/default.css" />
<script src="/js/default.js"></script>
</head>
<body>
<div id="itemTemplate" data-win-control="WinJS.Binding.Template" >
<div class="itemContainer">
<!-- Displays the "title" field. -->
<div class="itemTitle" data-win-bind="innerText: title">
</div>
<!-- Displays the "description" field. -->
<div class="itemDescription" data-win-bind="innerText: description">
</div>
</div>
</div>
<div data-win-control="WinJS.UI.ViewBox">
<div class="fixed-layout">
<div id="basicListView" data-win-control="WinJS.UI.ListView" data-win-options="{itemRenderer: itemTemplate}">
</div>
</div>
</div>
</body>
</html>
And the JavaScript:
(function () {
'use strict';
// Uncomment the following line to enable first chance exceptions.
//Debug.enableFirstChanceException(true);
var myData = [
{ title: "Banana", description: "Banana Frozen Yogurt"},
{ title: "Orange", description: "Orange Sherbet"},
{ title: "Vanilla", description: "Vanilla Ice Cream"},
{ title: "Mint", description: "Mint Gelato"},
{ title: "Strawberry", description: "Strawberry Sorbet"},
{ title: "Kiwi", description: "Kiwi Sorbet" }
];
// this works:
//var myDataSource = new WinJS.UI.ArrayDataSource(myData);
// this does not:
for (var i = 0; i < myData.length ; i++) {
myData[i] = WinJS.Binding.as(myData[i]);
}
var myDataSource = new WinJS.UI.ArrayDataSource(myData);
document.addEventListener("DOMContentLoaded", function (e) {
WinJS.UI.processAll()
.then(function () {
var basicListView = WinJS.UI.getControl(document.getElementById("basicListView"));
basicListView.dataSource = myDataSource;
// when the observable works correctly, this should work (and live change the list)
//setTimeout(function () {
// basicListView.refresh();
// myData[0].title = "Yellow Banana";
// myData[5].title = "Kiwisicle";
//}, 3000);
});
});
WinJS.Application.start();
})();