I read an article about Renderless Components,it split a component into a presentational component (view part) and a renderless component(logical part) via $scopedSlots property.Here is a simple Tag component. when you press enter , you'll add a new tag
<!DOCTYPE html>
<html>
<head>
<script src="http://vuejs.org/js/vue.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div id="app">
<custom-component v-model="tags">
<div slot-scope="{tags,addTag,newTag}">
<span v-for="tag in tags">
{{tag}}
</span>
<input type="text" #keydown.enter.prevent="addTag" v-model="newTag">
</div>
</custom-component>
</div>
<script>
Vue.component('custom-component',{
props:['value'],
data(){
return {
newTag:''
}
},
methods:{
addTag(){
this.$emit('input',[...this.value,this.newTag])
this.newTag = ''
}
},
render(h){
return this.$scopedSlots.default({
tags:this.value,
addTag:this.addTag,
newTag:this.newTag
})
}
})
new Vue({
el:'#app',
data:{
tags:[
'Test',
'Design'
]
}
})
</script>
</body>
</html>
But,it doesn't work,it seems that the newTag always is ''(empty string), when I use SPA way,
the emulator says "'v-model' directives cannot update the iteration variable 'newTag' itself" ,Here is demo on jsbin
The solution is , as mentioned in the article ,use :value attribute binding, and an #input event binding ,instead of v-model. demo on jsbin
<!DOCTYPE html>
<html>
<head>
<script src="http://vuejs.org/js/vue.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div id="app">
<custom-component v-model="tags">
<div slot-scope="{tags,addTag,inputAttrs,inputEvents}">
<span v-for="tag in tags">
{{tag}}
</span>
<input type="text" v-bind="inputAttrs" v-on="inputEvents">
</div>
</custom-component>
</div>
<script>
Vue.component('custom-component',{
props:['value'],
data(){
return {
newTag:''
}
},
methods:{
addTag(){
this.$emit('input',[...this.value,this.newTag])
this.newTag = ''
}
},
render(h){
return this.$scopedSlots.default({
tags:this.value,
addTag:this.addTag,
inputAttrs:{
value:this.newTag
},
inputEvents:{
input:(e) => {
this.newTag = e.target.value
},
keydown:(e) => {
if(e.keyCode === 13){
e.preventDefault()
this.addTag()
}
}
}
})
}
})
new Vue({
el:'#app',
data:{
tags:[
'Test',
'Design'
]
}
})
</script>
</body>
</html>
I don't know why v-model doesn't work.
EDIT
Questions above have been answered clearly, while I got another question after I read a reference link, and still v-model doesn't work question
<!DOCTYPE html>
<html>
<head>
<script src="http://vuejs.org/js/vue.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div id="app">
<base-test v-slot="sp">
<input type="text" v-model="sp.foo">
<div>{{ sp}}</div>
</base-test>
</div>
<script>
Vue.component('base-test', {
template: `
<div>
<slot :foo="foo"></slot>
</div>
`,
data(){
return{
foo: 'Bar',
}
}
});
// Mount
new Vue({
el: '#app',
});
</script>
</body>
</html>
As we can see, sp is an object. why v-model seems to be not working this time?
My solution was very simple (see v-model="tags[index]"):
Instead of doing this:
<template v-for="tag in tags">
<TagView :key="tag.key" v-model="tag" />
</template>
You should do this:
<template v-for="(tag, index) in tags">
<TagView :key="tag.key" v-model="tags[index]" />
</template>
The reason is you cannot pass iterated object tag into v-model for modifications. Please find more info about this: Iterating a list of objects with foreach
Consider the following two JavaScript examples:
for (let value of array) {
value = 10
}
function (value) {
value = 10
}
In both cases trying to assign 10 to value will only have an effect locally, it won't have any impact beyond the local scope. The caller, for example, would not be affected by the change.
Now consider these two examples where an object is used instead, where the object is of the form { value: 9 }:
for (let valueWrapper of array) {
valueWrapper.value = 10
}
function (valueWrapper) {
valueWrapper.value = 10
}
In this case the change is not limited to the local scope as we're updating objects. External code, such as the caller of the function, would also be impacted by this change to the value property as it can see the same object.
These examples are equivalent to trying to update a value using v-model in a variety of cases. The first two examples are equivalent to:
<template v-for="value in array">
<input v-model="value">
</template>
and:
<template v-slot="{ value }">
<input v-model="value">
</template>
The arguments passed to v-slot can very much be thought of as analogous to function parameters. Neither the loop nor the scoped slot would work as desired, exactly the same as they don't for their pure JavaScript equivalents.
However, the latter two of my four examples would be equivalent to:
<template v-for="valueWrapper in array">
<input v-model="valueWrapper.value">
</template>
and:
<template v-slot="{ valueWrapper }">
<input v-model="valueWrapper.value">
</template>
These should work fine as they are updating a property on an object.
However, to go back to the original question, it's important that we're binding the appropriate object. In this case we would need to bind the newTag property of the component. Copying that property to another object wouldn't work either as v-model would just be updating an irrelevant object.
I think we shouldn't modify the passed data to a slot, pretty much like component props. However, I think it could be a bug.
1st approach
The v-model directive works using a nested field in the passed data to the slot.
<!DOCTYPE html>
<html>
<head>
<script src="http://vuejs.org/js/vue.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div id="app">
<custom-component v-model="tags">
<div slot-scope="{tags,addTag,input}">
<span v-for="tag in tags">
{{tag}}
</span>
<input type="text" #keydown.enter.prevent="addTag" v-model="input.value">
</div>
</custom-component>
</div>
</body>
</html>
Vue.component('custom-component',{
props:['value'],
data(){
return {
input: {
value: ''
}
}
},
methods:{
addTag(){
this.$emit('input',[...this.value,this.input.value])
console.log([...this.value,this.input.value])
this.input.value = ''
}
},
render(h){
return this.$scopedSlots.default({
tags:this.value,
addTag:this.addTag,
input:this.input
})
}
})
new Vue({
el:'#app',
data:{
tags:[
'Test',
'Design'
]
}
})
2nd approach
Use the input event to get the input value attribute directly
<!DOCTYPE html>
<html>
<head>
<script src="http://vuejs.org/js/vue.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div id="app">
<custom-component v-model="tags">
<div slot-scope="{tags,addTag}">
<span v-for="tag in tags">
{{tag}}
</span>
<input type="text" #keydown.enter.prevent="addTag">
</div>
</custom-component>
</div>
</body>
</html>
Vue.component('custom-component',{
props:['value'],
data(){
return {
newTag:''
}
},
methods:{
addTag(evt){
console.log(evt.target.value)
this.$emit('input',[...this.value, evt.target.value])
evt.target.value = ''
}
},
render(h){
return this.$scopedSlots.default({
tags:this.value,
addTag:this.addTag,
newTag:this.newTag
})
}
})
new Vue({
el:'#app',
data:{
tags:[
'Test',
'Design'
]
}
})
You can also check these related issues:
StackOverflow
Using v-model inside scoped slots
Issues and Forum
https://forum.vuejs.org/t/v-model-and-slots/17616
https://github.com/vuejs/vue/issues/9726
Please use Ref of Vuejs Object for you solution, here use refence and testing for me, use tailwind, vue3, as a standalone component.
remember to rate this helps me a lot
// import function ref extiende of object
// importamos la funcion ref que extiende objeto vue
import { ref } from 'vue';
export default {
data() {
return {
// create refence with object for change
// creamos una referencia con los objetos que van a cambiar
fruits: ref({
oranges: {
name: 'naranjas',
value: false
},
apple: {
name: 'manzanas',
value: false
},
pear: {
name: 'peras',
value: false
}
})
};
}
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template>
<div>
<!-- use tailwindcss vue -->
<div class="w-2xl bg-blue-50 text-gray-400 font-bold">
<!-- iteration is here -->
<div v-for="fruit in fruits" :key="fruit">
<label for="">{{ fruit.name }}</label>
<!-- this can be a component example: #headlessui/vue-->
<input v-model="fruit.value" type="checkbox">
</div>
</div>
<div class="w-2xl bg-blue-50 text-gray-400 font-bold">
{{fruits}}
</div>
</div>
</template>
I had tried many ways to change the v-model but nothing just by referencing the javascript object
I am attempting to dynamically create some HTML content and bind a Vue component method to an event on the element. However this does not work. Below is a description of the problem and a snippet with a test case.
Steps to reproduce problem
Click Click me 1
Observe the console.
Click Toggle popover for tooltip.
Click Click me 2
Observe the console.
Expected behaviour
In Step 2 you should see "you should see this in the console" in the console. In Step 5 you should see "you should see this in the console" again.
Actual behaviour
In Step 2, "you should see this in the console" appears in the console. In Step 5, "you should see this in the console" does not appear in the console.
new Vue({
el: '#app',
methods: {
expectToSucceed() {
console.log('you should see this in the console');
},
},
created() {
$(document).ready(() => {
const mockedDynamicHtml = `
<div class="popover">
<h1>Example tooltip</h1>
<button #click="expectToSucceed" class="remove">Click me 2</button>
</div>
`;
$('[data-toggle="popover"]').popover({
html: true,
template: mockedDynamicHtml,
});
});
},
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<title></title>
</head>
<body>
<div id="app">
Toggle popover for tooltip
<br><br>
<p><span #click="expectToSucceed">Click me 1</span></p>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
</body>
</html>
It will be #click, like this:
<i #click="test"></i>
as shorthand of v-on is # not :.
here is my code.. where I made problem.. please help me out.. I am trying to create a controller what fill fetch data and show in html li part.. but I don't understand where is the error.. I have tried with adding jQuery min library and without it.. but failure.. kindly help me to short out this problem..
<html data-ng-app="myApp">
<head>
<title>First Angular application</title>
</head>
<body>
checkNames:
<input type="text" data-ng-model="namek">
<div class="container" data-ng-controller="SimpleController">
<ul>
<li data-ng-repeat="cast in castomers | filter:namek">{{cast.name|uppercase}} - {{cast.city}}</li>
</ul>
</div>
<script src="js/jquery.min.js"></script>
<script src="js/angular.min.js"></script>
<script>
function SimpleController($scope) {
$scope.castomers = [{ name: 'krishnendu sarkar', city: 'kolkata' },
{ name: 'chanchal sarkar', city: 'bangalore' },
{ name: 'nilava chakraborty', city: 'pune' }]
};
</script>
</body>
</html>
thanks in advance..
You should create and angular module first with name myApp then you could have data-ng-controller="SimpleController" to be move it over body tag so that the namek input field included inside the SimpleController controller context.
Add ng-app="myApp" on the body tag. so that angular module gets initialize on page.
Markup
<body data-ng-controller="SimpleController">
checkNames:
<input type="text" data-ng-model="namek">
<div class="container">
<ul>
<li data-ng-repeat="cast in castomers | filter:namek">{{cast.name|uppercase}} - {{cast.city}}</li>
</ul>
</div>
</div>
Controller
angular.module('myApp', []).controller('SimpleController', SimpleController);
function SimpleController($scope) {
$scope.castomers = [{
name: 'krishnendu sarkar',
city: 'kolkata'
}, {
name: 'chanchal sarkar',
city: 'bangalore'
}, {
name: 'nilava chakraborty',
city: 'pune'
}]
};
Demo PLunkr
please see this link here to see the whole code.
You should create angular module "myApp" which define the application then controller inside it.
I am trying to create a list object using the list.js library and to add it to the angular.js root scope in order to use it in the nested scopes. This works fine as long as I don't add a controller to the list elements.
If I do so a I get the following error in the javascript console.
"Cannot read property 'childNodes' of undefined"
Same happens if the list is initialised inside an init() function called with ng-init.
Here are the html and js files
<html>
<head>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/list.js/1.1.1/list.min.js"></script>
<script type="text/javascript" src="listBug.js"></script>
</head>
<body ng-controller="MainController" ng-app="theApp">
<div id="theList">
<ul class="list">
<li>
<div ng-controller="divController">
<span class="id"></span>
</div>
</li>
</ul>
</div>
</body>
</html>
listBug.js
var app = angular.module('theApp', [])
mainController=function($scope){
var opts={valueNames: ['id'] }
$scope.theList=new List('theList', opts);
}
app.controller("MainController", mainController)
divController=function($scope){
}
app.controller("divController", divController)
The problem is that the DOM isn't fully loaded when your code is being called. You can delay it by doing the following
mainController=function($scope){
angular.element(document).ready(function () {
var opts={valueNames: ['id'] }
$scope.theList=new List('theList', opts);
});
}
Trying to get a simple example with firebase and knockoutjs working. All i'm trying to do is take what is in firebase and bind it to my template in knockout. Sounds simple enough right? Well here is the code that's not working. I've looked it over but maybe I'm missing something. Oh this also makes use of knockoutfire.
<!DOCTYPE html>
<html>
<head>
<title>knockout</title>
</head>
<body>
<div id="viewModel">
<ul data-bind="foreach: chat">
<li data-bind="text: nick"></li>
</ul>
</div>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript" src="https://getfirebug.com/firebug-lite.js"></script>
<script type="text/javascript" src="knockout.js"></script>
<script type='text/javascript' src='https://cdn.firebase.com/v0/firebase.js'></script>
<script type="text/javascript" src='knockoutFire/knockoutFire.js'></script>
<script type="text/javascript" src='model.js'></script>
</body>
</html>
and the model.js:
var firebase = new Firebase("https://kingpinapp.firebaseio.com");
var viewModel = KnockoutFire.observable(firebase, {
chat: {
nick: true,
}
});
ko.applyBindings(viewModel, document.getElementById("viewModel"));
if I'm some how getting the model view wrong checkout the firebaseio link to see how the data is laid out. All I get when I visit index.html is a list with nothing in it. Just a bullet point, nothing else.
EDIT: just realised no one else can see my data. Well here is the JSON downloaded from the url:
{
"chat" : {
"nick" : "hello"
}
}
I think you must use with: chat instead of foreach: chat
<ul data-bind="with: chat">
If you need foreach binding, data in firebase look like;
{
"chat": {
"-XXX": {"nick": "hello"},
"-YYY": {"nick": "hi"}
}
}
and code:
var viewModel = KnockoutFire.observable(firebase, {
chat: {
"$chat": {
nick: true,
}
}
});