Using embedded attributes in Vuetify Calendar - javascript

I'm trying to use the v-calendar component from Vuetify.
I saw in the documentation I can use the event-start prop if my events don't have the same attributes' names.
The problem is that my events have embedded attributes and I don't know if event-start handles this case.
My events :
events: [
{
id: 'b9d93291-6d95-47b9-994a-ee9f266fb6b8',
type: 'reservation_item',
attributes: {
start_date: '2020-09-23T00:00:00.000Z',
end_date: '2020-09-25T00:00:00.000Z',
},
},
]
The events example from vuetify :
events: [
{
name: 'Weekly Meeting',
start: '2020-09-07 09:00',
end: '2020-09-07 10:00',
},
],
I tried to do something like that but it doesn't work.
<v-calendar
ref="calendar"
locale="fr-fr"
:now="today"
:value="today"
:events="events"
event-start="attributes.start"
color="primary"
type="month"
></v-calendar>

After spelunking the source code for the vuetify plugin, the latter expects that the value be present in the event object, as a direct property. So you cannot acces other nested "children", it has to be a direct property.
There are two alternatives to make this work:
1- map your events array by moving the properties inside attributes to the root of your object then pass this prop to v-calendar : event-start="startDate"
2- Create a javascript class (MyEvent) with a fromJson method that take the raw JSON from your API ( this way you encapsulate the JSON into domain objects) and return an array of MyEvent instances. this way you can do for example : events[0].start and you don't even have to pass it as a value to the event-start prop, since by default it expects a start attribute as a default value.
Another advantage of this alternative, is that since the event is now encapsulated into its own javascript class, you can add helper methods, or getters/setter or any logic that would otherwise be inside your "view" logic, and contribute to have a better separation of concerns.

To make this works, I had to change my events data
<template>
<v-calendar
ref="calendar"
locale="fr-fr"
:events="myEvents"
event-start="start"
color="primary"
type="month"
></v-calendar>
</template>
data: () => ({
events: [
{
id: 'b9d93291-6d95-47b9-994a-ee9f266fb6b8',
type: 'reservation_item',
attributes: {
start_date: '2020-09-23T00:00:00.000Z',
end_date: '2020-09-25T00:00:00.000Z',
},
},
],
}),
computed: {
myEvents() {
const reservations = this.reservations
reservations.forEach((element) => {
element.start = element.attributes.start_date
element.name = 'test'
element.end = element.attributes.end_date
})
return reservations
},
}

Related

Vue cannot change attribute in nested v-for

I get an array of objects and I add to it the attribute clicked=false, so I can later add or remove a class using vue bind and the value of that attribute
Then I turn this array of objects to an object structure, so I can render the items by type, in the rendering of the loop.
I use a click method on each list item to change the clicked attribute, but it never changes.
How can I do this?
The object has this structure
grouped:{
typeA: [
{
clicked: false,
text: "a1",
type: "typeA"
},
{
clicked: false,
text: "a2",
type: "typeA"
}
],
typeB: [
{
clicked: false,
text: "b1",
type: "typeB"
}
]
}
And then to render by type I do
<div v-for="(group, type) in grouped" :key="type">
<b>{{type}}</b>
<div v-for="(item, index) in group" :key="index" #click="eventItemClick(item)" >
{{item.text}} {{item.clicked}}
</div>
</div>
all the eventItemClick method does is
eventItemClick(item){
item.clicked = !item.clicked;
},
I created a simple js fiddle example that demonstrates. Just remember to click the group button to group the array and render the list
Thanks
You're running into the reactivity caveat because you are trying to use data properties that don't exist at the time of rendering.
Use Vue.set when setting those properties:
this.todos.forEach(e => {
this.$set(e, 'clicked', false); // `Vue.set` in a module
});

Vuejs2: how to judge props update when using object as a prop?

Suppose I have an array feedsArray, the example value may look like this:
this.feedsArray = [
{
id: 1,
type: 'Comment',
value: 'How are you today ?'
},
{
id: 2,
type: 'Meet',
name: 'Daily sync up'
}
]
Suppose I have registered two components: Comment and Meet, Each component has a prop setting as the following:
props: {
feed: Object
}
and the main component has the following definition:
<component v-for="feed in feedsArray" :feed="feed" :key="feed.id" :is="feed.type"></component>
As you can see, it uses is property to select different component. My question is, how to detect feed object change in the child component ? Like when I set
this.feedsArray[0] = {
id: 1,
type: 'Comment',
value: 'I am not ok'
}
How can the Comment component detect the changes ? I tried to add a watcher definition in the child component like the following:
watch: {
feed: {
handler (val) {
console.log('this feed is changed')
},
deep: true
}
},
But it doesn't work here. Anyone know how to solve this ?
Do not assign directly to an array using index - use splice() instead, otherwise JavaScript can not detect that you have changed the array.
If you want to change only the value of an already existing key of an object - then simply update it e.g. this.feeds[0].value = 'I am not okay any more';
This works for existing keys only - otherwise you have to use this.$set(this.feeds[0], 'value', 'I am not okay any more');

How do you process an array outside the vue instance and assign the processed data to a (currently empty) array inside the vue instance?

I have an array of blog post inside a script tag on the page that looks like this:
var posts = [
{article_name: "name", category: "test".....},
{article_name: "name2", category: "test2".....},
{...}
]
There are 30 objects in each array. I need to grab the categories in each object and assign it to the data prop in a vue instance. I have an empty array inside the vue instance like:
var blog_posts_nav = new Vue({
el: '#blog-posts-nav',
data: {
tags: []
}
})
I want to have each individual "category" in the 30 objects mapped to the "tags" array in the data prop. I tried doing it via the created() hook but it seems that the created hook can't access data? I tried:
created() {
posts.forEach( function (item) {
this.tags.push(item.category)
});
}
But I get an error in the console that says tags is undefined. Any help how I would deal with this? Basically I want to do work on a set of data and assign it to an array inside vue before outputting it to the page and in a manner that vue can interact with the data.
So first and foremost, is the created() hook (or any hook) the best way to go about this? Or should I be using something like methods or computed?
What is the "best practices" way and how would I go about achieving this?
It's not the created() method that can't access this, it's the inner context of forEach.
You can capture this to a local constant first, then access it inside the loop.
console.clear()
var posts = [
{article_name: "name", category: "test" },
{article_name: "name2", category: "test2" },
]
Vue.component("tags",{
template:`
<div>
{{tags}}
</div>
`,
data(){
return {
tags: [],
}
},
created() {
const vm = this;
console.log('created', vm.tags)
posts.forEach( function (item) {
vm.tags.push(item.category)
console.log('created, posts.forEach', vm.tags)
});
}
})
new Vue({
el: "#app"
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.js"></script>
<div id="app">
<tags></tags>
</div>

Vue.js bind object properties

Why I can't bind the object properties in Vue? The object addr is not reactive immediately, but test is reactive, how come? In this case, how should I bind it?
HTML
<div id="app">
<input type="text" id="contactNum" v-model="addr.contactNum" name="contactNum">
<input type="text" id="test" v-model="test" name="test">
<br/>
{{addr}}<br/>
{{addr.contactNum}}<br/>
{{test}}
</div>
Javascript
var vm = new Vue({
el: '#app',
data: {
addr: {},
test: ""
}
});
Jsfiddle
During initialisation Vue sets up getters and setters for every known property. Since contactNum isn't initially set up, Vue doesn't know about that property and can not update it properly. This can be easly fixed by adding contactNum to your addr object.
var vm = new Vue({
el: '#app',
data: {
addr: {
contactNum: "" // <-- this one
},
test: ""
}
});
The above is called reactivity in Vue. Since Vue doesn't support adding properties dynamically to its reactivity system, we may need some kind of workaround. A possible solution is provided by the API. In case of dynamically added properties we can use Vue.set(vm.someObject, 'b', 2).
Doing so the markup would need to get some update. Instead of using v-model it'd be better to use an event listener like #input. In this case our markup could look like this.
<input type="text" id="contactNum" #input="update(addr, 'contactNum', $event)" name="contactNum">
So basically the function will get triggered every time the input elements value changes. Obviously doing so will also require some adjustments on the JS part.
var vm = new Vue({
el: '#app',
data: {
addr: {},
test: ""
},
methods: {
update: function(obj, prop, event) {
Vue.set(obj, prop, event.target.value);
}
}
});
Since Vue triggers Vue.set() on any reactive element, we simply call it on our own because Vue doesn't recognizes a dynamically added property as a reactive one. Of course, this is only one possible solution and there may be lots of other workarounds. A fully working example can be seen here.
As per my comments, there are several things that you want to consider:
The reason why your code is not working is due to the inherent inability of JS to watch for changes in object properties. This means that even though addr is reactive, any properties added to addr that is not done when it is declared will make it non-reactive. Refer to the VueJS docs for more details: https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
If you are going to have an arbitrary number of input fields, you are probably better of composing a custom input component, and simply use v-for to iteratively inject input fields based on the number of input fields you have.
Now back to the second point, if you know what fields addr will have, you can simply declare it in your app. We create a new updateFormData method, which is called by the component:
data: {
addrFields: ['contactNum', ...],
addr: {},
test: ""
},
methods: {
updateFormData: function(id, value) {
this.$set(this.addr, id, value);
}
}
We can still store your form data in the addr object, which will be updated by the updateFormData method based on the received payload using .$set(). Now, we can then create a custom Vue component for your input element.
In the example below, the component will iterate through all your addrFields, and pass down the addrField as a prop using :id="addrField". We also want to make sure that we capture the custom-named updated event emitted from within the component.
<my-input
v-for="(addrField, i) in addrFields"
:key="i"
:id="addrField"
v-on:inputUpdated="updateFormData"></my-input>
The template can look something like the following. It simply uses the id prop for both its id, name, and placeholder attribute (the latter for easy identification in the demo). We bind the #change and #input events, forcing it to trigger the updated callback:
<script type="text/template" id="my-input">
<input
type="text"
:id="id"
:name="id"
:placeholder="id"
#input="updated"
#change="updated">
</script>
In the component logic, you let it know that it will receive id as a prop, and that it should emit an inputUpdated event using $.emit(). We attach the ID and value as payloads, so that we can inform the parent what has updated:
var myInput = Vue.component('my-input', {
template: '#my-input',
props: {
id: {
type: String
}
},
methods: {
updated: function() {
this.$emit('inputUpdated', this.id, this.$el.value);
}
}
});
With the code above, we have a working example. In this case, I have created an arbirary array of input fields: contactNum, a, b, and c:
var myInput = Vue.component('my-input', {
template: '#my-input',
props: {
id: {
type: String
}
},
methods: {
updated: function() {
this.$emit('updated', this.id, this.$el.value);
}
}
});
var vm = new Vue({
el: '#app',
data: {
addrFields: ['contactNum', 'a', 'b', 'c'],
addr: {},
test: ""
},
methods: {
updateFormData: function(id, value) {
this.$set(this.addr, id, value);
}
}
});
<script src="https://unpkg.com/vue#2.1.3/dist/vue.js"></script>
<div id="app">
<my-input
v-for="(addrField, i) in addrFields"
:key="i"
:id="addrField"
v-on:updated="updateFormData"></my-input>
<input type="text" id="test" v-model="test" name="test" placeholder="test">
<br/>
<strong>addr:</strong> {{addr}}<br/>
<strong>addr.contactNum:</strong> {{addr.contactNum}}<br />
<strong>test:</strong> {{test}}
</div>
<script type="text/template" id="my-input">
<input
type="text"
:id="id"
:name="id"
:placeholder="id"
#input="updated"
#change="updated">
</script>
Edit your Vue data with this since it's getter and setter methods are not set up. Also, check out Declarative Reactive Rendering on Vue docs here:
data: {
addr: {
contactNum: "" // <-- this one
},
test: ""
}

How to get data ("children" in TreeView constructor) associated with YUI3 tree element on clicking by it

from example: enter link description here
// Create an array object for the tree root and child nodes
var children = [
{
children: [
{
label: 'File X'
},
{
label: 'File Y'
}
],
expanded: true,
label: 'Root'
}
];
// Create a TreeView Component
tree = new Y.TreeView(
{
boundingBox: '#myTreeView',
children: children
}
).render();
if I add some attrs to children array objects like:
children: [
{
label: 'File X',
internalId: 24342234,
customAttr: 'unicid',
}
]
var tree.on('click',function(e){
tree.get('lastSelected');
});
I can't get them after tree rendering and clicking on this tree node.
All nodes have the following built-in properties:
data Object Arbitrary serializable data related to the node. Use this property to store any data that should accompany a node when that node is serialized to JSON.
id String Unique id for the node. If you don't specify a custom id when creating a node, one will be generated automatically.
but it does not work for me..
console.log(tree.get('lastSelected').get('label'));
[gives "File X"]
console.log(tree.get('lastSelected').get('customAttr'));
[gives "undefined"]
I think you may be confusing the Y.Tree class from YUI with the Y.TreeView class from AlloyUI. The descriptions you gave for id and data come from the documentation for Y.Tree.Node from YUI, which is used by Y.Tree.
AlloyUI's Y.TreeView class uses Y.TreeNode objects instead, which don't have the data attribute you're trying to use to store custom data. Take a look at its documentation: http://alloyui.com/api/classes/A.TreeNode.html. To do this with AlloyUI you'll need to add some code of your own. You could extend Y.TreeNode for example, adding the data attribute to the subclass, and use it instead. Or you can store the data you need in the main node of each Y.TreeNode instance, which you can get via get('boundingBox'), and then use setData and getData on it.
Even if Y.TreeNode had this data attribute though, you'd need to change your code to add the custom data you need inside the data key instead of adding them together with the other attributes. For example:
children: [
{
label: 'File X',
data: {
internalId: 24342234,
customAttr: 'unicid'
}
}
]
Then you'd be able to get it through:
console.log(tree.get('lastSelected').get('data').customAttr);

Categories