How to bind selectedIndex of a select element with vue? - vue.js

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>

Related

Use Vue only in part of an existing jQuery project

I have an old project and I want to use Vue to make some fields of one of the forms be shown or hidden, I'm importing Vue from CDN and initializing a new Vue app, I've tried setting an id to the body of the project, but it does not work, then I created a div inside the body section wrapping all existing code, but the result is all the content is gone and gives a blank page. If i initialize Vue with the form id now the form is blank.
I've checked that Vue is imported adding a console.log in created lifecycle and it works, the problem is old content of page is missing when I wrap it with id specified in Vue initialization.
Here's my code:
const vueApp = new Vue({
el: '#my-form',
data() {
return {
selectedProductType: "Producto 3x2 o 2x1",
}
},
created() {
console.log("+++++++++++++++++++++++++++++++++++++++++ CREATED");
},
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
</head>
<body class="fixed-layout skin-blue-dark p-0" id="page-top" oncopy="return false" oncut="return false" oncontextmenu="return false">
<p>A LOT OF CONTENT HERE</p>
<form action="" method="POST" class="form" id="my-form" data-action="create">
<p>A LOT OF CONTENT HERE</p>
<label for="name">Tipo de producto</label>
<select class="form-control" name="product_type" id="product-type" v-model="selectedProductType" required>
<option value="" disabled selected>Selecciona...</option>
<option value="Producto con descuento">Producto con descuento</option>
<option value="Producto 3x2 o 2x1">Producto 3x2 o 2x1</option>
<option value="Producto en combo">Producto en combo</option>
<option value="Producto no promocional">Producto no promocional</option>
</select>
<br>
{{ selectedProductType }}
</form>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
</body>
</html>
I've tried wrapping parts of code with different div IDs but it never works.
I want to know if it is possible to use Vue in my use case and how to achieve it.
Thanks.
EDIT 1:
I've added some code to current snippet, but I dont't know why it works in the snippet and not in my working code.
It's missing the creation of the Vue component, and the div with the initialisation element id and the vue components tags.
The initialisation part of Vue looks good to me, just make sure to happen after the components are defined.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
</head>
<body class="fixed-layout skin-blue-dark p-0" id="page-top" oncopy="return false" oncut="return false" oncontextmenu="return false">
<p>A LOT OF CONTENT HERE</p>
<div id="my-form">
<my-form></my-form>
</div>
<script>
Vue.component('my-form', {
template:`
<form action="" method="POST" class="form" id="my-form" data-action="create">
<p>A LOT OF CONTENT HERE</p>
<label for="name">Tipo de producto</label>
<select class="form-control" name="product_type" id="product-type" v-model="selectedProductType" required>
<option value="" disabled selected>Selecciona...</option>
<option value="Producto con descuento">Producto con descuento</option>
<option value="Producto 3x2 o 2x1">Producto 3x2 o 2x1</option>
<option value="Producto en combo">Producto en combo</option>
<option value="Producto no promocional">Producto no promocional</option>
</select>
<br>
{{ selectedProductType }}
</form>`,
data() {
return {
selectedProductType: "Producto 3x2 o 2x1",
}
}
})
const vueApp = new Vue({
el: '#my-form',
created(){
console.log("+++++++++++++++++++++++++++++++++++++++++ CREATED");
}
})
</script>
</body>
</html>
this should work!
data(){return{}} is needed when you are creating Vue.component, in case of new Vue you need data:{}
Try data:{} instead of data(){return{}} like this:
const vueApp = new Vue({
el: '#my-form',
data: {
selectedProductType: "Producto 3x2 o 2x1"
}
})
It looks like I just needed to set Vue in head section instead of the end of body tag, I added Vue from CDN:
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
And then, I initilized it after all components were loaded in the bottom of body tag.

What's the difference between {{}} and v-text in Vue.js?

In vue.js we know there are two ways to bind data to a view:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<p>{{msg}}</p>
<p v-text="msg"></p>
</div>
<script src="./lib/vue.js"></script>
<script>
var vm = new Vue({
el: "#app",
data: {
msg: 'hello world',
}
})
</script>
</body>
</html>
You see the tag p:
<p>{{msg}}</p>
<p v-text="msg"></p>
Is there any difference between them?
There are two differences between them:
If you use the <p>{{msg}}</p> to bind the data, you can add more content, such as:
Hi, {{msg}}!
The <p v-text="msg"></p> can avoid the bind flashing, that mean when you load the view, there will not appear {{msg}} like the first way. If you want to use <p>Hi, {{msg}}!</p> way, you can add the [v-cloak] property:
<style>
[v-cloak] {
display: none;
}
</style>
...
<p v-cloak>{{msg}}</p>

How to structure nested reactive data in VUE

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>

"Failed to generate render function" when i use tree component of ElementUI

I want to use Element UI in IE 11,and when i user the tree component,it creates errors.
It will create the error
Failed to generate render function:
SyntaxError 'missing in'
I want to show a icon before the text.But in IE 11,it errors while Chrome ok.
Anyone can tell me the problem..thx
the whole demo is
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>MLLRSJ</title>
<link rel="stylesheet" href="./Scripts/assets/Element/index.css">
<script src="./Scripts/assets/vue/vue.js"></script>
<script src="./Scripts/assets/Element/index.js"></script>
</head>
<body>
<div id='Element'>
<el-tree :data="tree" :props="treeProps">
<span slot-scope='{node,data}'>
<i class='el-icon-check'></i>
<span style="display:inline" v-html='node.label'></span>
</span>
</el-tree>
</div>
</body>
<script>
var vm = new Vue({
el:'#Element',
data:{
tree:[{
label:'1',
children:[{
label:'1-1'
}],
},
{
label:'2',
children:[
{
label:'2-1'
}
]
}
],
treeProps:{
label:'label',
children:'children'
}
}
})
</script>
</html>
Try to avoid curly braces(in slot-scope in your example) and arrow functions in inline templates if you work with IE11 (I had same problem in one of my projects):
<el-tree :data="tree" :props="treeProps">
<span slot-scope='someObject'>
<i class='el-icon-check'></i>
<span style="display:inline" v-html='someObject.node.label'></span>
</span>
</el-tree>

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