how to display nested objects in vue.js - javascript

My returned data is an array of objects with a nested object. I'm unable to display the 'events' in a v-for loop in my template as I cannot seem to access that part of the returned data.
The data is returned like this (from Vue DevTools):
list: Object
user: "name"
id: "id"
events: Array[3]
0: Object
title: "event 1"
1: Object
title: "event 2"
2: Object
title: "event 3"
Using Vue-resource (via CDN) how do I get display just the events in my template?
Template:
<template id="events-template">
<div v-for="events in list">
#{{events.title}}
</div>
</template>
My application:
Vue.component('events', {
template: '#events-template',
data: function() {
return {
list: []
}
},
created: function() {
this.fetchEventsList();
},
methods: {
fetchEventsList: function() {
this.$http.get('events', function(events) {
this.list = events;
}).bind(this);
}
}
});

Ok it seems you try to access the false properties in your loop.
the list variable does not contain a list/array (object). The array you want to iterate is the events attribute of the list object. so list.events
Furthermore you want to access the property (title) of the "current" item (event) in the loop. Then you don't have to access to the hole array but rather to the current element. event.title
Replace your template block:
<template id="events-template">
<div v-for="event in list.events">
#{{event.title}}
</div>
</template>

Related

Updating item in array of objects in Vue.js: is it better to replace the array or change one value

I'm using Vue/Vuex to generate components from an array with this structure (retrieved from SQLite using better-sqlite3).
let table=[
{
id:1,
column_1:'data',
column_2:'data',
column_3:{'1':'data','2':'data','3':'data'}
},
{
id:2,
column_1:'data',
column_2:'data',
column_3:{'1':'data','2':'data','3':'data'}
},
...
]
There are cases where the data of multiple rows need to be updated at once. Since I need to pass data back and forth between SQLite and Vue, I'm wondering whether it'll be simpler and harmless to update the data with sql and then replace the entire javascript array with the updated data, including the unmodified rows. Otherwise I imagine I'll have to use .find() to go through and change the specific items. So my question is whether replacing the whole array is a Bad Idea as far as reactivity goes, or whether Vue can smart update accordingly.
Vue has a different way of tracking when it comes to update the UI
Vue uses getters/setters on the data object for mutation tracking. When you execute this.table = [{}, {}, {}, ...];, It will go through the setter of table. In the setter, there's a function to notify the watcher and add this data changes to a queue.
Limitation/Behaviour while updating the Arrays :
Vue cannot detect the following changes to an array :
When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue
When you modify the length of the array, e.g. vm.items.length = newLength
Demo as per above two statements :
const app = new Vue({
el: '#app',
data() {
return {
table: [{
id:1,
column_1:'data1',
column_2:'data',
column_3:{'1':'data','2':'data','3':'data'}
}, {
id:2,
column_1:'data2',
column_2:'data',
column_3:{'1':'data','2':'data','3':'data'}
}]
}
},
mounted() {
// We are updating the array item and It's not reactive (You can see it's not updating UI)
this.table[1] = {
id: 3,
column_1: 'data3',
column_2:'data',
column_3:{'1':'data','2':'data','3':'data'}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul>
<li v-for="item in table" :key="item.id">
{{ item.column_1 }}
</li>
</ul>
</div>
Answer of your Question : In consideration of above two statements, You have to update whole array including the unmodified rows.
Demo as per the answer of your question :
const app = new Vue({
el: '#app',
data() {
return {
table: [{
id:1,
column_1:'data1',
column_2:'data',
column_3:{'1':'data','2':'data','3':'data'}
}, {
id:2,
column_1:'data2',
column_2:'data',
column_3:{'1':'data','2':'data','3':'data'}
}]
}
},
mounted() {
// We are updating the whole array and It's reactive (You can see it's updating UI)
this.table = [{
id:1,
column_1:'data1',
column_2:'data',
column_3:{'1':'data','2':'data','3':'data'}
}, {
id: 3,
column_1: 'data3',
column_2:'data',
column_3:{'1':'data','2':'data','3':'data'}
}]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul>
<li v-for="item in table" :key="item.id">
{{ item.column_1 }}
</li>
</ul>
</div>

Trying to change object property and keep reactivity. Property or method "vm" is not defined on the instance but referenced during render

Until now, I was trying to change the value of an object property to false using #click='review.isActive = true', however, it turns out Vue cannot detect such changes which means that property won't be reactive.
Based on the documentation https://v2.vuejs.org/v2/guide/list.html#Caveats, this can be avoided by using one of the following methods:
Vue.set(vm.reviews, index, true)
vm.reviews.splice(index, 1, true)
Sadly I get an error using either way. I assume I don't quite understand how the Vue instance works and I'm trying to access it when I shouldn't be able to.
I assume Vue/vm are supposed to be the Vue instance but I can't seem to access it.
In both cases, I get error "Property or method "vm"/"Vue" is not defined on the instance but referenced during render."
I'm using Vue webpack-simple webpack template and my Vue instance seems to be initialized in main.js file like this:
var vm = new Vue({
el: '#app',
router: router,
store: store,
render: h => h(App)
});
The file in which the array manipulation is happening is different and a component.
Assuming your review object is an item in your reviews array and that they do not initially have an isActive property but you'd like to add one, you can use vm.$set to add a new reactive property.
For example
new Vue({
el: '#app',
data: {
reviews: [{
id: 1,
name: 'Review #1'
},{
id: 2,
name: 'Review #2'
},{
id: 3,
name: 'Review #3'
}]
}
})
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
<div v-for="review in reviews" :key="review.id">
<p>
Review {{ review.name }} status:
<code>{{ review.isActive ? 'ACTIVE' : 'INACTIVE' }}</code>
</p>
<button #click="$set(review, 'isActive', true)">Activate</button>
</div>
</div>
Note the use of $set in the #click handler.
From the comment below, if you wanted to add new properties after the data is retrieved, do it before you assign the value to your data property, eg
this.reviews = data.from.somewhere.map(review => ({...review, isActive: false}))
If you must add the new property after the data is assigned, you need to use vm.$set to make it reactive
this.reviews.forEach(review => {
this.$set(review, 'isActive', false)
})
Once these properties are added reactively, you can go back to simply changing them without $set
<button #click="review.isActive = true">

polymerfire/firebase-query transaction complete event

Very new to Polymer and Polymerfire. I couldn't find an answer here so hoping I can get help here. The basic question I have is "how do I work with the data that polymerfire/firebase-query sends?" Note I'm using polymerfire version 0.9.4, and polymer is version 1.4.0.
I can load my data from Firebase no problem using Firebase query, however some of the values are raw numbers that I need to convert to user friendly information. For example I have time stored in ms that I want to convert to a date, and a numeric field that indicates the "type" of data that is stored and I want to show an icon for it, not just a raw number. I figured my best option would be to use the transactions-complete promise or an observer. Both fire but neither seems to give me access to the data. The Observer's newData is an empty array, and transactions-complete.. well I don't really know what to do with that when the promise fires. Below is my relevant code. I also tried using notify: true, but I seem to not be grasping the concept correctly.
<firebase-query
id="query"
app-name="data"
path="/dataPath"
transactions-complete="transactionCompleted"
data="{{data}}">
</firebase-query>
<template is="dom-repeat" items="{{data}}">
<div class="card">
<div>Title: <span>{{item.title}}</span></div>
<div>Date Created: <span>{{item.dateCreated}})</span></div>
<div>Date Modified: <span>{{item.dateModified}}</span></div>
<div>Status: <span>{{item.status}}</span></div>
</div>
</template>
Polymer({
is: 'my-view1',
properties: {
data: {
notify: true,
type: Object,
observer: 'dataChanged'
}
},
dataChanged: function (newData, oldData) {
console.log(newData[0]);
// do something when the query returns values?
},
transactionCompleted: new Promise(function(resolve, reject) {
// how can I access "data" here?
})`
I wound up going another way entirely, which seemed to be a cleaner approach to what I was doing anyways. I broke it down into separate components. This way when the detail component was loaded, the ready function would allow me to adjust the data before it got displayed:
list.html:
<firebase-query
id="query"
app-name="data"
path="/dataPath"
data="{{data}}">
</firebase-query>
<template is="dom-repeat" items="{{data}}">
<my-details dataItem={{item}}></my-details>
</template>
details.html
<template>
<div id="details">
<paper-card heading="{{item.title}}">
<div class="card-content">
<span id="description">{{item.description}}</span><br/><br/>
<div class="details">Date Created: <span id="dateCreated">{{item.dateCreated}}</span><br/></div>
<div class="details">Last Modified: <span id="dateModified">{{item.dateModified}}</span><br/></div>
<div class="status"><span id="status">{{item.status}}</span><br/></div>
</div>
</paper-card>
</template>
Then in the javascript ready function I can intercept and adjust the data accordingly:
Polymer({
is: 'my-details',
properties: {
item: {
notify: true,
},
},
ready: function() {
this.$.dateModified.textContent = this.getDate(this.item.dateModified);
this.$.dateCreated.textContent = this.getDate(this.item.dateCreated);
this.$.status.textContent = this.getStatus(this.item.status);
},
Try the following changes:
Take out the transactions-completed attribute - it is only relevant when the query is updating data to Firebase
Change the dom-repeat template to get it's items attribute from convertedData - this allows you to do the data conversions to## Heading ## the results of the firebase-query
<firebase-query
id="query"
app-name="data"
path="/dataPath"
data="{{data}}">
</firebase-query>
<template is="dom-repeat" items="{{convertedData}}">
<div class="card">
<div>Title: <span>{{item.title}}</span></div>
<div>Date Created: <span>{{item.dateCreated}})</span></div>
<div>Date Modified: <span>{{item.dateModified}}</span></div>
<div>Status: <span>{{item.status}}</span></div>
</div>
</template>
Add a convertedData property to do your data conversions from data which has the raw data
Change the observer syntax as per the example. This sets up the observer to to observe for changes to deep property values which results in the observer method being fired - see: https://www.polymer-project.org/1.0/docs/devguide/observers#deep-observation
In the observer method you can populate the convertedData object from the data object which should then render the content
Polymer({
is: 'my-view1',
properties: {
data: {
notify: true,
type: Object
},
convertedData: {
notify: true,
type: Object
}
},
// observer syntax to monitor for deep changes on "data"
observers: [
'dataChanged(data.*)'
]
dataChanged: function (newData, oldData) {
console.log(newData);
// convert the "newData" object to the "convertedData" object
}
}

Polymer "dom-if" using toggle and item property

Goal
I'm wanting to use a toggle to filter (or hide via dom-if) elements based on the toggle itself and a boolean property of the element.
So with an array of objects:
[{..., active: true}, {..., active: false}]
I want to hide the "active:false" objects with the toggle.
Problem
I can't figure out how to get the if function "_showAlert(item)" to fire on toggle switch or more importantly if this is even the way I should go about this using Polymer. I'd appreciate guidance on either.
<paper-toggle-button checked="{{activeOnly}}">Active Only</paper-toggle-button>
<paper-listbox>
<template is="dom-repeat" items="[[alerts]]">
<template is="dom-if" if="{{_showAlert(item)}}">
<paper-item>...</paper-item>
</template>
</template>
</paper-listbox>
<script>
(function() {
'use strict';
Polymer({
is: 'alert-list',
properties: {
alerts: {
type: Array
},
activeOnly: {
type: Boolean,
notify: true
}
},
ready: function(){
this.alerts = [{..., active: true}, {..., active: false}];
},
_showAlert: function(item) {
// The alert item will have a boolean property called "active"
return (item.active || !this.activeOnly);
}
})
})();
</script>
I've just appended some data in the ready function for now for development purposes. I can get the list to display just fine and I can verify the binding between the toggle and the "activeOnly" property is working.
Thanks!
I assume you would want your showAlert function to re-evaluate every time either item.active or activeOnly change. To achieve that you have to pass them to the funciton as arguments (also see docs).
<template is="dom-if" if="{{_showAlert(item.active, activeOnly)}}">

How to fetch data from collections when the route/template consist on more than collection?

Example:
routes.js:
this.route("chapterPage", {
path: "/books/:bookId/chapters/:_id",
data: function() {
var chapter = Chapters.findOne(this.params._id);
var book = Books.findOne(this.params.bookId);
var chapters = Chapters.find({
bookId: this.params.bookId
}, {
sort: {
position: 1
}
});
return {
chapter: chapter,
book: book,
chapters: chapters
};
}
});
As you can see this template/route has two collections Book and Chapter. Previously, I used to call the collections individually like this:
chapter_form.js:
Template.chapterForm.events({
"input #input-content": function() {
var currentChapter = Session.get("currentChapter");
Chapters.update(currentChapter, {
$set: {
content: $("#input-content").html();
}
});
}
});
But now in my new route/template I can't do that since it isn't based on any collection:
chapter_page.js:
Template.chapterPage.events({
"input #input-content": function() {
console.log(chapter._id); // this returns is not defined
console.log(this._id); // this one too
}
});
How to get around this?
EDIT:
I also tried calling the chapter_form.html template:
<template name="chapterPage">
{{> chapterForm}}
</template>
But it doesn't display and shows stuff like: Cannot read property 'content' of undefined so it isn't recognizing the template.
There are two problems in your code.
First in the data function of the chapterPage route, you do not return the object containing your data.
// no return here in your question, need to do :
return {
chapter: chapter,
book: book,
chapters: chapters
};
Then in your event handler, you can access the data context using this, so the correct syntax to access the chapter or book id is this.chapter._id or this.book._id.
EDIT :
Inside templates route helpers and event handlers, this refers to the current data context assigned to the template.
There are several ways to assign a data context to a template.
You can use attribute="value" syntax along with template inclusion syntax.
{{> myTemplate param1="value1" param2="value2"}}
Template.myTemplate.helpers({
paramsJoined:function(){
return [this.param1,this.param2].join(",");
}
});
You may also use a helper value coming from the parent template data context :
<template name="parent">
{{> myTemplate someHelper}}
</template>
Template.parent.helpers({
someHelper:function(){
return {
param1:"value1",
param2:"value2"
};
}
});
If you don't specify a data context when using the template inclusion syntax, it is assumed to be inherited from the parent data context.
You can also use {{UI.dynamic}} (http://docs.meteor.com/#ui_dynamic) to specify a dynamic template name along with a dynamic data context.
{{> UI.dynamic template=Router.template data=Router.data}}
This is this kind of approach that iron:router is using to set dynamically the route data context of the route template (implementation is slightly more complex though).
Meteor provides utilities to access current data contexts as well as parent data contexts, which can be useful :
http://docs.meteor.com/#template_currentdata
http://docs.meteor.com/#template_parentdata

Categories

Resources