How to add/extend an object in a component - javascript

In vue.js, you can iterate over an array of items in your template like so:
<div id="app">
<div v-for="(item, i) in items">i: item</div>
</div>
<script>
var example2 = new Vue({
el: '#app',
data: {
items: ['one', 'two', 'three']
}
})
</script>
Through experimentation, I also discovered you can do something similar with an object instead of an array:
<div id="app">
<div v-for="(item, i) in items">i: item</div>
</div>
<script>
var example2 = new Vue({
el: '#app',
data: {
items: {one: 'one', two: 'two', three: 'three'}
}
})
</script>
If you want to add to the array, you can do something like example2.items.push('four'), and vue.js will react by inserting another DOM element. However, how would you go about inserting another item into an object instead in such a way that vue.js will react the same as it did to the array? You can't use the push method because it's not available to a generic object, so
I'm left trying something like:
example2.items.four = 'four'
But vue.js doesn't detect that change, so no new element is inserted into the DOM. My question is: How can i insert a new object ?

You have to use set like this:
this.$set(this.myObject, 'newKey', { cool: 'its my new object' })
You could use Object.assign too:
let newObject = { newKey: { cool: 'its my new object' }}
this.myObject = Object.assign({}, this.myObject, newObject)
More: https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats

Well, I asked too soon. I found the following documentation that answers my question. "Object Change Detection Caveats," found here: https://v2.vuejs.org/v2/guide/list.html

Related

Adding vue components based on condition in v-for

I have a JS array formatted similar to
[{type:'text', data: 'some text'},{type: 'image', data: 'link/to/image'}]
for the different values of type I have different vue components (<text-block>, <image-block>) and I want to use a v-for to loop over this array and based on the type, create the right vue component.
The examples for v-for show creating the same element many times like many <li>. Is there a way I can create different elements in a v-for?
You can just use v-if:
<div v-for="(loop, index) in loops" :key="index">
<text-block v-if="loop.type === 'text'"></text-block>
<image-block v-if="loop.type === 'image'"></image-block>
</div>
You can also use dynamic components:
<div v-for="(loop, index) in loops" :key="index">
<component :is="loop.type + '-block'"></component>
</div>
Make sure you have imported the components and defined them on the instance.
You can do something like this, say there's list of movies in an array:
<div id="app">
<ul>
<li v-for="movie in movies">{{ movie }}</li>
</ul>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
movie: ['some movie 1', 'movie 2', 'idk movies man']
}
});
setTimeout(function() {
app.movies.push('random movie');
}, 2000);
</script>

How to create Vue.js slot programmatically?

I have the following component with a slot:
<template>
<div>
<h2>{{ someProp }}</h2>
<slot></slot>
</div>
</template>
For some reasons, I have to manually instantiate this component. This is how I am doing it:
const Constr = Vue.extend(MyComponent);
const instance = new Constr({
propsData: { someProp: 'My Heading' }
}).$mount(body);
The problem is: I am not able to create slot contents programmatically. So far, I can create simple string based slot:
const Constr = Vue.extend(MyComponent);
const instance = new Constr({
propsData: { someProp: 'My Heading' }
});
// Creating simple slot
instance.$slots.default = ['Hello'];
instance.$mount(body);
The question is - how can I create $slots programmatically and pass it to the instance I am creating using new?
Note: I am not using a full build of Vue.js (runtime only). So I don't have a Vue.js compiler available to compile the template on the fly.
I looked into TypeScript definition files of Vue.js and I found an undocumented function on Vue component instance: $createElement(). My guess is, it is the same function that is passed to render(createElement) function of the component. So, I am able to solve it as:
const Constr = Vue.extend(MyComponent);
const instance = new Constr({
propsData: { someProp: 'My Heading' }
});
// Creating simple slot
const node = instance.$createElement('div', ['Hello']);
instance.$slots.default = [node];
instance.$mount(body);
But this is clearly undocumented and hence questionable approach. I will not mark it answered if there is some better approach available.
I think I have finally stumbled on a way to programmatically create a slot element. From what I can tell, the approach does not seem to work for functional components. I am not sure why.
If you are implementing your own render method for a component, you can programmatically create slots that you pass to child elements using the createElement method (or whatever you have aliased it to in the render method), and passing a data hash that includes { slot: NAME_OF_YOUR_SLOT } followed by the array of children within that slot.
For example:
Vue.config.productionTip = false
Vue.config.devtools = false;
Vue.component('parent', {
render (createElement) {
return createElement('child', [
createElement('h1', { slot: 'parent-slot' }, 'Parent-provided Named Slot'),
createElement('h2', { slot: 'default' }, 'Parent-provided Default Slot')
])
}
})
Vue.component('child', {
template: '<div><slot name="parent-slot" /><slot /></div>'
})
new Vue({
el: '#app',
template: '<parent />'
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id='app'>
</div>
(This doesn't really answer How to create Vue.js slot programatically?. But it does solve your problem.)
This trick is less hackish compared to using $createElement().
Basically, create a new component that register MyComponent as a local component.
const Constr = Vue.extend({
template: `
<MyComponent someProp="My Heading">
<div>slot here !!!</div>
</MyComponent>
`,
components: {
MyComponent: MyComponent
}
});
const instance = new Constr().$mount('#app');
Demo: https://jsfiddle.net/jacobgoh101/shrn26p1/
I just came across an answer to this in vue forum:
slots
The principle is: There is nothing like createElement('slot'..)
Instead there is a render function which provides the slotted innerHtml as function:
$scopedSlots.default()
Usage:
render: function (createElement) {
const self = this;
return createElement("div", this.$scopedSlots.default());
}
If you want to provide a default in case there is no content given for the slots, you need to code a disctinction yourself and render something else.
(The link above holds a more detailed example)
The function returns an array, therefore it can not be used as a root for the render function. It need to be wrapped into single container node like div in the example above.

Vue components / elements in v-html

I have a post.text data that contains the text of a blog post submitted by the user Much like in Twitter, users can mention other users with the sintax I am tagging #user1 in this post. When rendering the post, I want to replace all the #username instances with links to the page of the mentioned user.
With a regex match / replace I can easily transform the mentioned #username into something like (I'm using vue-router):
I am tagging <router-link :to="{name: 'user', params: {userId: post.userId}}">{{ dPost.user_name }}</router-link> in this post
But when I use it like this:
<p v-html="preparedText"></p>
vue doesn't reprocess the html to bind its own tags.
How to solve this problem? Thanks
What you want to do sortof breaks the normal Vue paradigm, but it can be done using Vue.compile. You'll need to use Vue.compile to generate the render functions and then manually create a new Vue instance
once your component has been mounted.
Here's an example:
Vue.component('post', {
template: `<div></div>`,
props: { text: String },
mounted() {
let regex = /\B\#([\w\-]+)/gim;
let username = this.text.match(regex)[0];
let linkText = this.text.replace(regex, `${username}`);
let res = Vue.compile(`<p>${linkText}</p>`);
let { render, staticRenderFns } = res;
new Vue({ el: this.$el, render, staticRenderFns })
}
})
new Vue({
el: '#app',
data() {
return { text: `Hi #user1, how are you?` }
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.min.js"></script>
<div id="app">
<post :text="text"></post>
</div>
You don't need to use v-html.
To dynamically render your components, simply use :is="your component name".

Vue instance inside Vue instance

Sup people!
I got this HTML code here:
// index.html
<div data-init="component-one">
<...>
<div data-init="component-two">
<button #click="doSomething($event)">
</div>
</div>
This basically references a Vue instance inside another Vue instance if I understood everything correctly. The respective JS code is split up in two files and looks like this:
// componentOne.js
new Vue(
el: '[data-init="component-one"]',
data: {...},
methods: {...}
);
// componentTwo.js
new Vue(
el: '[data-init="component-two"]'
data: {...}
methods: {
doSomething: function(event) {...}
}
);
Now, the problem with this is, that doSomething from componentTwo never gets called.
But when I do some inline stuff, like {{ 3 + 3 }}, it gets computed like it should. So Vue knows there is something. And it also removes the #click element on page load.
I tried fiddling around with inline-template as well, but it doesn't really work as I'd expect it to in this situation. And I figured it isn't meant for this case anyway, so I dropped it again.
What would the correct approach be here? And how can I make this work the easiest way possible with how it's set up right now?
The Vue version we use is 2.1.8.
Cheers!
The problem is that you have two vue instances nested to each other.
If the elements are nested, then you should use the same instance or try components
https://jsfiddle.net/p16y2g16/1/
// componentTwo.js
var item = Vue.component('item',({
name:'item',
template:'<button #click="doSomething($event)">{{ message2 }</button>',
data: function(){
return{
message2: 'ddddddddddd!'
}},
methods: {
doSomething: function(event) {alert('s')}
}
}));
var app = new Vue({
el: '[data-init="component-one"]',
data: {
message: 'Hello Vue!'
}
});
<div data-init="component-one">
<button >{{ message }}</button>
<item></item>
</div>
Separate instances work if they are independant of each other.
as follows:
https://jsfiddle.net/p16y2g16/
var app = new Vue({
el: '[data-init="component-one"]',
data: {
message: 'Hello Vue!'
}
});
// componentTwo.js
var ddd = new Vue({
el: '[data-init="component-two"]',
data: {
message: 'ddddddddddd!'
},
methods: {
doSomething: function(event) {alert('s')}
}
});
But when I do some inline stuff, like {{ 3 + 3 }}, it gets computed like it should. So Vue knows there is something.
Because you have parent instance 'componentOne'. It activated Vue for this template. If you need to set another instance inside, you have to separate part of template. Example (it can lag in snippet!) .
Alternative
https://jsfiddle.net/qh8a8ebg/2/
// componentOne.js
new Vue({
el: '[data-init="component-one"]',
data: {
text: 'first'
},
methods: {}
});
// componentTwo.js
new Vue({
el: '[data-init="component-two"]',
data: {
text: 'second'
},
template: `<button #click="doSomething($event)">{{text}}</button>`,
methods: {
doSomething: function(event) {
console.log(event);
}
}
});
<script src="https://vuejs.org/js/vue.min.js"></script>
<div data-init="component-one">
{{text}}
</div>
<div data-init="component-two">
</div>
The button element inside component-two is referenced as a slot in Vue.
The evaluation of the #click directive value happens in the parent component (component-one, which host component-two). Therefor, you need to declare the click handler over there (over component-one).
If you want the handler to be handled inside component-two, you should declare a click directive for the slot element in it's (component-two) template, and pass the handler function, for instance, as a pop.
good luck.
You're doing everything right except you've nested the 2nd Vue instance inside the 1st. Just put it to the side and it will work as expected.
Vue ignores binding more than once to the same element to avoid infinite loops, which is the only reason it doesn't work nested.
Use vue-cli to create a webpack starter app. vue init app --webpack
Then, try to structure your components this way. Read more: https://v2.vuejs.org/v2/guide/components.html#What-are-Components
This is main.js
import Vue from 'vue'
import ComponentOne from './ComponentOne.vue'
import ComponentTwo from './ComponentTwo.vue'
new Vue({
el: '#app',
template: '<App/>',
components: {
ComponentOne,
ComponentTwo
}
})
This is ComponentOne.vue
<template>
<div class="user">
<div v-for="user in users">
<p>Username: {{ user.username }}</p>
</div>
</div>
</template>
<script>
export default {
data () {
return {
users: [
{username: 'Bryan'},
{username: 'Gwen'},
{username: 'Gabriel'}
]
}
}
}
</script>
This is ComponentTwo.vue
<template>
<div class="two">
Hello World
</div>
</template>
<script>
export default {
}
</script>
<div th:if="${msg.replyFloor}">
<div class="msg-lists-item-left">
<span class="msg-left-edit"
th:classappend=" ${msg.unreadCount == 0} ? 'msg-all-read' ">您在</span>
<span th:text="${msg.topic.title}"
class="msg-left-edit-res"
th:classappend=" ${msg.unreadCount == 0} ? 'msg-all-read' ">问题回答</span>
<span th:text="${msg.type.name}"
class="msg-left-edit "
th:classappend=" ${msg.unreadCount == 0} ? 'msg-all-read' ">帖子相关</span>
<span class="msg-left-edit-number" >
产生了<span th:text="${msg.unreadCount} ? : ${msg.unreadCount} + '条新' : ${msg.unreadCount} + '条' "
th:class="${msg.unreadCount} ? : 'number-inner':''">2132条</span>回复
</span>
</div>
<div class="msg-lists-item-right">
<span th:text="${msg.lastShowTime}">2017-8-10</span>
</div>
</div>

Why all values are not Dynamically Passed in Html using Vue JS?

I use vue js with laravel.
I pass multiple variables threw vuejs component(template), i can pass few variables successfully.
But few of them not passed, shows Empty("").
HTML (Template):
<template v-if="showTemplate" id="segment_body">
<div class="col-md-2" align="center">
<b>Grid</b><br>
<i class=""></i>Grid |
<i class=""></i>List
</div>
<b>ID : #{{ t_id }}</b><br>
<b>LIST : #{{ t_showList }}</b>
</template>
HTML (Data Source):
<script> var a = { list: false} </script>
<div>
<campaign_segment :t_id=1 :t_showList="a.list"></campaign_segment>
</div>
VueJS :
Vue.component('campaign_segment', {
template: '#segment_body',
props: ['t_showList','t_id']
});
OUPUT :
ID : 1
LIST :
If i click Option "Grid",
OUTPUT :
ID: 1
LIST : False
Why I'm not get the value of list?
Why I only get the value of ID ?
Any other solutions ?
You're running into this issue because you're you're trying to pass in the javascript object directly to the component instead of passing it through the Vue instance.
Check out this bin: http://jsbin.com/fosifo/edit?html,js,output
The only real difference in the bin is that rather than trying to pass a to the component directly here (which won't work b/c the vue instance isn't aware of the data):
<script> var a = { list: false} </script>
<div>
<campaign_segment :t_id=1 :t_showList="a.list"></campaign_segment>
</div>
We pass it in through the vue instance:
In your html:
<script>
var a = {list: false}
</script>
In your javascript:
new Vue({
el: '#app',
data:{
a: a // the `a` value here is referencing that same `var a = {list: false}` in your markup.
}
})
By doing it this way, as the vue instance is being created it's able to bind it's a data property to the globally defined variable a from your markup.
It's essentially the same as doing this:
new Vue({
el: '#app',
data:{
a: {list: false}
}
})

Categories