I want to integrate a component into existing legacy application which uses angular js 1. I want to replace it with vuejs 3 and problem is that I cannot use webpack. I have created a standalone webpage with vuejs3 and bootstrap vue but somehow the component does not render. Any help or guidance what I am doing wrong? Is it doable with vuejs 3 to run a standalone component without any webpack?
Interesting thing is that it render the html page with a warning in console.
Index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Test</title>
<!-- Add Bootstrap and Bootstrap-Vue CSS to the <head> section -->
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap/dist/css/bootstrap.min.css"/>
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css"/>
<!-- Add Vue and Bootstrap-Vue JS just before the closing </body> tag -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.1/vue.global.js"></script>
<script src="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
</head>
<body>
<h1>Static HTML With Vue.js Single File Component</h1>
<div id="app">
</div>
<script type="module">
import app from './app.js'
const {createApp} = Vue;
createApp(app).mount('#app');
</script>
</body>
</html>
app.js
export default {
data() {
return {
perPage: 3,
currentPage: 1,
items: [
{ id: 1, first_name: 'Fred', last_name: 'Flintstone' },
.............
]
}
},
computed: {
rows() {
return this.items.length
}
},
template: `
<div class="overflow-auto">
<b-table
id="my-table"
:items="items"
:per-page="perPage"
:current-page="currentPage"
small
></b-table>
<b-pagination v-model="currentPage" :total-rows="rows" :per-page="perPage" aria-controls="my-table"></b-pagination>
<p class="mt-3">Current Page: {{ currentPage }}</p>
</div>
`,
};
Version 2.x.x of BootstrapVue does not support Vue 3.
The next major release BootstrapVue 3 (not yet released), will however support it. As of this post, an alpha of BootstrapVue 3 is aimed for Q1 of 2021.
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 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'm creating a slider web component from scratch to learn.
I want the button to hide when the attribute controlVisible is false and show when it's true, but my selector $('#botaoVoltar') doesn't get anything.
What am I missing?
index.html:
<body>
<slider-js controlVisible='false' ></slider-js>
</body>
polymer.html:
<polymer-element name="slider-js">
<template>
<center>
<div id="container">
<div id="Buttons">
<button name="voltar" id="botaoVoltar"><<</button>
</div>
</div>
</center>
</template>
</polymer-element>
<script>
Polymer('slider-js', {
controlVisible: false,
ready: function () {
if (this.controlVisible === false) {
$('#botaoVoltar').hide();
} else {
$('#botaoVoltar').show();
}
}
});
</script>
The attribute is working fine. If I console.log it, I can see if it is true or false, but the template still renders with the button.
jQuery can't get at Polymer's local DOM, so you'd have to use Polymer's own DOM API. Actually, Polymer's automatic node finding provides quick access to nodes that have IDs with this.$. For instance, you could access the botaoVoltar button in your example with this.$.botaoVoltar.
It looks like you're using old Polymer (pre 1.0). You should switch to the latest version of Polymer (1.5.0). Here's your code upgraded to the newest Polymer version:
<head>
<base href="https://polygit.org/polymer+1.5.0/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="polymer/polymer.html">
</head>
<body>
<span>With control-visible:</span>
<x-slider control-visible></x-slider>
<span>Without control-visible:</span>
<x-slider></x-slider>
<!-- Normally, you would define this element in its own file. -->
<dom-module id="x-slider">
<template>
<div id="container">
<div id="Buttons">
<button id="botaoVoltar"><<</button>
<button>>></button>
</div>
</div>
</template>
<script>
HTMLImports.whenReady(function() {
Polymer({
is: 'x-slider',
properties : {
controlVisible: {
type: Boolean,
value: false
}
},
ready: function() {
this.$.botaoVoltar.hidden = !this.controlVisible;
}
});
});
</script>
</dom-module>
</body>
codepen
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}}