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
Related
I watched a video lesson on vueschool and repeated the code as the teacher. But it doesn't work for me, and it works for him. In the browser console, it shows that there is no template indexer, but why does it all work?
HTML:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div id="app">
<p>Просто текст</p>
<click-counter></click-counter>
<click-counter></click-counter>
<click-counter></click-counter>
</div>
<script scr="vue.js"></script>
<script src="app.js"></script>
</body>
</html>
vue js
Vue.component ('click-counter', {
template: "<button #click="count++">{{count}}</button>",
data() {
return {
count: 0
}
}
})
new Vue ({
el: "#app"
})
In the component template, please use ' quotes to wrap the click function. Since you are using double quotes to wrap button, the same cannot be used in it which tends to close the first one.
Please find below the code
Vue.component('click-counter', {
template: "<button #click='count++'>{{count}}</button>",
data() {
return {
count: 0
}
}
})
new Vue({
el: "#app"
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.0/vue.js"></script>
<div id="app">
<click-counter/>
</div>
Change this line:
"<button #click="count++">{{count}}</button>"
Give this:
"<button #click='count++'>{{count}}</button>"
Make sure that, you have import vuejs in your file.
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
I followed this Vuejs video tutorial : https://laracasts.com/series/learn-vue-2-step-by-step/episodes/9?autoplay=true
My HTML seems like him (except his design) :
<div id="app">
<message title="My Component title" body="Lorem ipsum dry"></message>
</div>
Then my vuejs code :
<script type="text/javascript" src="{{ asset('js/vue.js') }}"></script>
<script>
new Vue({
el: '#app',
delimiters: ['${', '}']
});
Vue.component('message', {
props: ['title', 'body'],
template: `
<article class="message">
<div class="message-header">
${title}
</div>
<div class="message-body">
${body}
</div>
</article>
`
});
</script>
I changed Vuejs variable delimiters because it's a twig template file.
Inspecting the source code in the browser, the HTML code is not replaced by the code defined in the template... I don't see why.
You are declaring your template using JavaScript template strings (`).
You need to escape ${ in template strings, because they have specific meaning for them. Escape like: \${
Also, you need to declare the delimiters on the component itself.
JSBin demo: http://jsbin.com/notocozepi/edit?html,js,output
Source:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<div id="app">
<message title="My Component title" body="Lorem ipsum dry"></message>
</div>
<script type="text/javascript" src="https://unpkg.com/vue"></script>
<script>
Vue.component('message', {
props: ['title', 'body'],
delimiters: ['${', '}'],
template: `
<article class="message">
<div class="message-header">
\${title}
</div>
<div class="message-body">
\${body}
</div>
</article>
`
});
new Vue({
el: '#app',
delimiters: ['${', '}']
});
</script>
</body>
</html>
One last note: mind the order. The components must be defined before they are used.
I'm using angularJS to do some custom filter work, but it failed to display model information at the first stage.
<html lang="en" data-ng-app="myApp">
<Head>
<title>Custom filter</title>
<meta charset="utf-8">
</head>
<body>
<div data-ng-init="varNum=0">
<p>
<label for="number">Enter number from 1 to 99:</label>
<input type="number" data-ng-model="varNum" id="number"/>
</p>
<p>Your number: {{ varNum }}</p>
</div>
<script src="js/angular.min.js"></script>
<script src="js/appromannumber.js"></script>
</body>
</html>
appromannumber.js
var app = angular.module("myApp", []);
app.filter("myFilter", function (){
return function(myNum) {
var formatedNumber = "";
switch(myNum) {
0:formatedNumber="zero";break;
return formatedNumber;
}
});
But if I remove the value of ng-app, like data-ng-app = "", it will start to display the varNum. I have no idea why this happen.
Should not work angular without bootstrap module means without ng-app="moduleName". I suspect that you miss something else and I mentioned bellow some problem in your code that may will help you
in your filter you missed
case in switch statement. need case 0: instead of 0:...
return formatedNumber; this statement is unreachable. should use after switch statement
you can try like:
app.filter("myFilter", function (){
return function(myNum) {
var formatedNumber = "";
switch(myNum) {
case 0:formatedNumber="zero";break;
case 1:formatedNumber="One";break;
}
return formatedNumber;
};
});
and in html:
<div data-ng-init="varNum=0">
<p>
<label for="number">Enter number from 1 to 99:</label>
<input type="number" data-ng-model="varNum" id="number"/>
</p>
<p>Your number: {{ varNum | myFilter }}</p> // shown filtered value
</div>
maybe this is a trivial question.
so, when I run my vuejs application on browser with enables throttling download speed (sets to low connection). I got unfinished vue syntax output in browser.
I know we can trick this out with showing loading image before entire page has loaded, but it's there any best solution to fix this?
You can use the v-cloak directive, which will hide the Vue instance until the compilation finishes, if you combine it with the right CSS.
HTML:
<div v-cloak>{{ message }}</div>
CSS:
[v-cloak] { display: none; }
I attached the following codepen. You can see the difference with and without v-cloak.
<div id="js-app">
[regular]Hold it... <span>{{test}}</span><br/>
[cloak]Hold it... <span v-cloak>{{test}}</span>
</div>
http://codepen.io/gurghet/pen/PNLQwy
As suggested by others using v-cloak is proper solution. However as # DelightedD0D mentioned it IS clunky. Simple solution is to add some CSS in the pseudo selector ::before of v-cloak directive.
In your sass/less file something along the lines of
[v-cloak] > * { display:none; }
[v-cloak]::before {
content: " ";
display: block;
position: absolute;
width: 80px;
height: 80px;
background-image: url(/images/svg/loader.svg);
background-size: cover;
left: 50%;
top: 50%;
}
Of course you'd need to provide a valid and accessible path to loader image. It will render something like.
Hope it helps.
Using v-cloak directive you can hide un-compiled mustache bindings until vue instance is done compiling. You must use the CSS block to hide it until compiled.
HTML:
<div v-cloak>
{{ vueVariable }}
</div>
CSS:
[v-cloak] {
display: none;
}
This <div> will not be visible until the compilation is completed.
You can see this link Hide elements during loading using v-cloak for better understating.
Don't include any vuejs syntax in the HTML file:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>My Super app</title>
</head>
<body>
<div id="app"></div>
<script src="/app.js"></script>
</body>
</html>
In your main JavaScript, you can:
import Vue from 'vue'
import App from './App'
new Vue({
el: '#app',
components: { App },
template: '<App/>'
})
See the vuetify webpack template for reference.
Another solution is to use:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>My Super app</title>
</head>
<body>
<div id="app" is="App"></div>
<script src="/app.js"></script>
</body>
</html>
With:
import Vue from 'vue'
import App from './App'
Vue.component("App", App);
const app = new Vue({});
window.addEventListener("load", async () => {
app.$mount("#app")
})
Putting everything inside a <template> worked well for me.
The content is hidden until rendered by Vue.
<!-- index.html -->
<div id="app">
<template>
<span class="name">{{ name.first }} {{ name.last }}</span>
</template>
</div>
/* index.js */
new Vue({
el: '#app',
data: {
name: { first: 'David', last: 'Davidovich'}
}
});
**html**
<div v-cloak>{{ message }}</div>
**css**
[v-cloak] { display: none; }
Use <v-cloak> to hide your Vue code before binding data to relevant places. It's actually located in a place on Vue documentation that anyone might miss it unless you search for it or read thoroughly.
You could move any rendering to a simple wrapper component. The VueJS initialisation e.g. new Vue(....) shouldn’t contain any HTML apart from that single wrapper component.
Depending on setup you could even have <app>Loading...</app> where app is the wrapper component with any loading HTML or text in between which is then replaced on load.
Yep, you can use v-cloak, I like use spinkit, is a great library with only CSS, check a simple example:
var vm = null;
setTimeout(function() {
vm = new Vue({
el: '#app',
data: {
msg: 'Is great, using: ',
url: 'http://tobiasahlin.com/spinkit/'
}
});
}, 3000);
#app .sk-rotating-plane,
[v-cloak] > * { display:none }
body{
background: #42b983;
color: #35495e;
}
#app[v-cloak] .sk-rotating-plane {
display:block;
background-color: #35495e;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.rawgit.com/tobiasahlin/SpinKit/master/css/spinkit.css">
<div id="app" v-cloak>
<div class="sk-rotating-plane"></div>
<h1>Vue with c-cloak</h1>
<p>
{{ msg }}
<a :href='url'>{{ url }}</a>
<p>
</div>
Link:
- http://tobiasahlin.com/spinkit/
For those who use v-cloak in a view with multiple Laravel Blade files and it's not working, try to use the v-cloak on the parent blade file rather than in any child blade file.
I prefer using v-if with a computed property that checks if my data is ready, like this:
<template>
<div v-if="shouldDisplay">
{{ variableName }}
</div>
<div v-else>
Here you can insert a loader
</div>
</template>
<script>
export default {
name: 'Test',
data() {
return {
variableName: null,
};
},
computed() {
shouldDisplay() {
return this.variableName !== null;
}
},
mounted() {
this.variableName = 'yes';
},
};
</script>
In this way it's easier to show a loader only if the data is not ready (using v-else).
In this particular case v-if="variableName" would work as well, but there can be more complicated scenarios.
I got an error when I want try to add a component into a div DOM in another component.
Uncaught Error: Assertion Failed: You cannot append to an existing Ember.View. Consider using Ember.ContainerView instead.
here is my JSBin
App = Ember.Application.create();
App.MyListComponent= Ember.Component.extend({
layoutName:'my-list',
actions:{
addItem:function(){
console.log("add item action");
App.MyListItemComponent.create().appendTo("#holder");
},
},
});
App.MyListItemComponent= Ember.Component.extend({
layoutName:'my-list-item',
});
html, body {
margin: 20px;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Ember Starter Kit</title>
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.1/normalize.css">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://builds.emberjs.com/release/ember-template-compiler.js"></script>
<script src="http://builds.emberjs.com/release/ember.debug.js"></script>
</head>
<body>
<script type="text/x-handlebars">
<h2>Welcome to Ember.js</h2>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
{{my-list}}
</script>
<script type="text/x-handlebars" id="my-list">
<button {{action "addItem"}}>add item</button>
This is a holder:
<div id="holder">
i am inside holder
</div>
This is static item:
{{my-list-item}}
</script>
<script type="text/x-handlebars" id="my-list-item">
i am a list item
</script>
</body>
</html>
can you have a look and tell me how to do?
thank you very much
I dont think, your approach is right.
A component should be working independently from the context in which it is added. You can pass anything as argument to the component within the template. I would recommend displaying the components depending on some type of model that is added with each addItem click (in my example, a simple text).
Why don't you try it this way:
JS
App = Ember.Application.create();
App.MyListComponent= Ember.Component.extend({
components: Ember.A([]),
layoutName:'my-list',
actions:{
addItem:function(){
var components = this.get('components');
console.log(components);
components.pushObject ('test'); // or whatever you want: could also be components.pushObject(App.MyListItemComponent.create()); if you want to use it later somehow.
this.set('components', components);
console.log("add item action");
},
},
});
App.MyListItemComponent= Ember.Component.extend({
layoutName:'my-list-item',
});
html
<script type="text/x-handlebars" id="my-list">
<button {{action "addItem"}}>add item</button>
This is a holder:
<div id="holder">
i am inside holder {{components.length}}
{{#each components as |item|}}
{{my-list-item}}
{{/each}}
</div>
This is static item:
{{my-list-item}}