Polymer dom-repeat issue - javascript

While rendering with Polymer an array of objects, it keeps launching me an exception.
Here's the data model retrieved from server:
{
"lastUpdate":"yyyy-MM-ddTHH:mm:ss.mmm",
"info": [
{
"title": "Some nice title"
},
...
]
}
Here's my Polymer component template:
<dom-module is="items-list">
<template>
<dl>
<dt>last update:</dt>
<dd>[[$response.lastUpdate]]</dd>
<dt>total items:</dt>
<dd>[[$response.info.length]]</dd>
</dl>
<template is="dom-repeat" items="{{$response.info}}">
{{index}}: {{item.title}}
</template>
</template>
<script src="controller.js"></script>
</dom-module>
And here's the controller:
'use strict';
Polymer(
{
properties: {
info: {
type: Array
},
$response: {
type: Object,
observer: '_gotResponse'
}
},
_gotResponse: function(response)
{
console.log(response);
if (response.info.length)
{
try
{
//here I try to set info value
}
catch(e)
{
console.error(e);
}
}
},
ready: function()
{
//set some default value for info
},
attached: function()
{
//here I request the service for the info
}
}
);
If tried to set info value as:
this.info = response.info;
this.set('info', response.info);
this.push('info', response.info[i]); //inside a loop
But the result breaks after rendering the first item, the exception launched is:
"Uncaught TypeError: Cannot read property 'value' of null"

If info === $response.info then why not just use items={{info}} in the repeat?
edit:
Try to modify your code in the following way.
'use strict';
Polymer({
properties: {
$response: {
type: Object
}
},
_gotResponse: function(response)
{
console.log(response);
...
this.$response = response
...
},
attached: function()
{
//here I request the service for the info
someAjaxFn(someParam, res => this._gotResponse(res));
}
});
You don't need an observer in this case. You only use an explicit observer when the implicit ones don't/won't work. I.e. fullName:{type:String, observer:_getFirstLastName}
or
value:{type:String, observer:_storeOldVar}
...
_storeOldVar(newValue, oldValue) {
this.oldValue = oldValue;
}
if you are updating the entire array, ie this.info, then you simple use this.info = whatever once within your function. If you don't want to update the entire array, just some element within it, then you will want to use polymer's native array mutation methods as JS array method doesn't trigger the observer.
Again, since your template doesn't use info, then you don't need the property info. If you want to keep info, then don't store info within $response. In fact, $ has special meaning in polymer so try not to name properties with it. you can simply use the property info and lastUpdate for your polymer.
Last note, beware of variable scoping when you invoke functions from polymer. Since functions within polymer instances often use this to refer to itself, it may cause
Uncaught TypeError: Cannot read property 'value' ..."
as this no longer refers to the polymer instance at the time of resolve.
For example, suppose your host html is
...
<items-list id='iList'></items-list>
<script>
iList._gotResponse(json); // this will work.
setTimeout(() => iList.gotResponse(json),0); //this will also work.
function wrap (fn) {fn(json)}
wrap (iList._gotResponse); //TypeError: Cannot read property '$response' of undefined.
function wrap2(that, fn) {fn.bind(that)(json)}
wrap2 (iList,iList._gotResponse); // OK!
wrap (p=>iList._gotResponse(p)) //OK too!!
wrap (function (p) {iList._gotResponse(p)}) //OK, I think you got the idea.
</script>

Related

data() variable refuses to update when set inside nested functions

I am very new to VUE so please forgive if my terminology is not correct.
I am trying to set a variable which is defined in the script tag "data()" function.
I am trying to set a new value for a variable defined in data() from inside the "created()" lifecycle event. This works fine if done at the root level, but i need to do it within 2 nested calls as shown below and it will not work when nested inside the function calls:
app.vue
<template>
<div class="container">
<Button #btn-click="providerPicked" id="current-provider" :text="'Current Provider: ' + currentProvider" />
</div>
</template>
<script>
import { ZOHO } from "./assets/ZohoEmbededAppSDK.min.js";
import Button from './components/Button'
export default {
name: 'App',
components: {
Button,
},
data() {
return{
currentProvider: 'x'
}
},
created() {
console.log("CREATED HOOK")
ZOHO.embeddedApp.on("PageLoad",function(data)
{
console.log(data);
//Custom Business logic goes here
let entity = data.Entity;
let recordID = data.EntityId[0];
ZOHO.CRM.API.getRecord({Entity:entity,RecordID:recordID})
.then(function(data){
console.log(data.data[0])
console.log(data.data[0].name)
//THIS DOES NOT WORK - variable still comes back 'x' in template, notice this is nested twice.
this.currentProvider = data.data[0].name;
});
});
ZOHO.embeddedApp.init();
//THIS DOES WORK - SETS VAR TO "reee" in template, notice this is not nested
this.currentProvider = "reee"
}
}
</script>
Use arrow functions instead of anonymous functions.
Arrow functions do not bind this, and thus this will refer to the outer scope.
created() {
console.log("CREATED HOOK")
ZOHO.embeddedApp.on("PageLoad", (data) => {
console.log(data);
//Custom Business logic goes here
let entity = data.Entity;
let recordID = data.EntityId[0];
ZOHO.CRM.API.getRecord({ Entity: entity, RecordID: recordID })
.then((data) => {
console.log(data.data[0])
console.log(data.data[0].name)
//THIS DOES NOT WORK - variable still comes back 'x' in template, notice this is nested twice.
this.currentProvider = data.data[0].name;
});
});
ZOHO.embeddedApp.init();
//THIS DOES WORK - SETS VAR TO "reee" in template, notice this is not nested
this.currentProvider = "reee"
}

How to update Vue component when promise resolve AND passing props to nested children

I have parent and child components. Parent's data is populated after an ajax call, that I want to pass to child as a props.
I tried different solutions, I will post two, but not yet working : could you please pin-point the error?
The component does react to changes. I also need to ensure that props are correctly passed to child and its nested children.
Attempts:
If I don't use 'v-if' directive, I will get an error for :query is
passed through as null.
So I use v-if (see version below)
and update the rendering, but it does not react and stay empty (even
though data is properly updated).
I also tried to initialize the query data as computed without using
the v-if directive, because after all I just need to cache the result
of the premise.
I also tried to emit an event on a bus, but the component does not
react.
Finally I tried to make the component reactive by making use of a
:key (e.g. :key="ready") that should make the component reactive when
the :key changes. But still no effect.
Template:
<template>
<div v-if="isLoaded()">
<input>
<metroline></metroline>
<grid :query="query"></grid>
</div>
</template>
script:
export default {
data() {
return {
ready : false,
query : null
}
},
components : {
metroline : Metroline,
grid : Grid
},
methods: {
isLoaded() {
return this.ready
},
firstQuery( uid, limit, offset) {
var url = // my url
// using Jquery to handle cross origin issues, solving with jsonp callback...
return $.ajax({
url : url,
dataType: 'jsonp',
jsonpCallback: 'callback',
crossDomain: true
})
}
},
created() {
var vm = self;
this.firstQuery(...)
.then(function(data){
this.query = data;
console.log('firstQuery', this.query);
// attempts to pass through the query
// bus.$emit('add-query', this.query);
this.ready = true;
self.isLoaded(); // using a self variable, temporarily, jquery deferred messed up with this; however the this.query of vue instance is properly updated
})
}
}
In created hook assign the component instance this to a a variable called that and access it inside the callback because in the callback you're outside the vue component context (this):
created() {
var vm = self;
var that=this;
this.firstQuery(...)
.then(function(data){
that.query = data;
console.log('firstQuery', this.query);
that.ready = true;
self.isLoaded();
})
}
}

Vue.js undefined error on object value when loaded and rendered

OK. I'm not a total newbie and do have some Vue xp but this is bugging me. What really obvious thing am I missing.
I have an object loaded via an ajax call inside a mounted method:
job: {
"title": "value",
"location": {
"name":"HONG KONG"
}
}
When I call {{ job.title }} all good. When I call {{ job.location.name }} I have an undefined error but the value renders. When I call {{ job.location }} I get the json object so it is defined.
Aaargh! I'm sure it's really simple but can't possibly see why this isn't as straight forward as it should be.
// Additional
This is my entire Vue class
const router = new VueRouter({
mode: 'history',
routes: []
});
const app = new Vue( {
router,
el: '#app',
data: {
job: {}
},
mounted: function () {
var vm = this
jQuery.ajax({
url: 'https://xxx' + this.jobId,
method: 'GET',
success: function (data) {
vm.job = data;
},
error: function (error) {
console.log(error);
}
});
},
computed: {
jobId: function() {
return this.$route.query.gh_jid
}
}
})
When your component renders it tries to get value from job.location.name but location is undefined before ajax request is completed. So I guess error is kind of Cannot read property 'name' of undefined.
To fix this you can define computed property locationName and return, for example, empty string when there is no loaded job object yet:
computed:{
//...
locationName() {
return this.job.location ? this.job.location.name : '';
}
}
Or you can define computed for location and return empty object if there is no location, or you can just add empty location object to your initial data(if you are sure that your API response always has location) like job: { location: {}} all ways will fix your issue.
Also there is a way to fix it with v-if directive in your template:
<div v-if="job.location">
{{ job.location.name }}
<!-- other location related stuff -->
</div>
An ES6 solution for you:
computed: {
getJobName(){
return this.job?.location.name
}
}
Optional Chaining

Change value of reactive variable in hook in Meteor

I have
Template.templateName.onCreated(function() {
this.variableName = new ReactiveVar;
this.variableName.set(true);
});
and in templateName I have an autoform. I need to set the reactive variable variableName to false when the autoform is submitted.
I have tried
AutoForm.hooks({
myForm: {
onSuccess: function(operation, result) {
this.variableName.set(false);
},
}
});
but it doesn't work since this. does not refer to the template templateName as it does in helpers and events. It would have worked if I used sessions instead since these are not limited/scoped to specific templates.
What can I do to change the reactive variable in an autoform hook?
I have also tried
AutoForm.hooks({
myForm: {
onSuccess: function(operation, result) {
this.template.variableName.set(false);
this.template.parent.variableName.set(false);
this.template.parent().variableName.set(false);
this.template.parentData.variableName.set(false);
this.template.parentData().variableName.set(false);
this.template.parentView.variableName.set(false);
this.template.parentView().variableName.set(false);
},
}
});
When using console.log(this.template) it does print an object. If I use console.log(this.template.data) I get
Object {id: "myForm", collection: "Meteor.users", type: "update", doc: Object, validation: "submitThenKeyup"…}
I use the reactive variable variableName to determine whether to either show the editable form or the nice presentation of data for the user. Maybe there is another better way to do this.
Edit of onCreated:
Template.templateName.onCreated(function() {
this.variableName = new ReactiveVar(false);
});
You may want to add a helper function to grab the Template instance:
Template.templateName.helpers({
getVariableName: function() {
return Template.instance().variableName.get();
}
});
and then you could potentially call within your form logic
AutoForm.hooks({
myForm: {
onSuccess: function(operation, result) {
Template.getVariableName.set(false);
},
}
});
The MeteorChef has a great article about reactive variables/dictionaries Reactive Variables.
If people stumble upon this for a solution, based this thread I as able to access the parents Reactive Dict/Reactive Variable after installing aldeed:template-extension like so
AutoForm.hooks({
postFormId: {
//onSuccess hook of my post form
onSuccess: function(formType, result) {
this.template.parent().reactiveDictVariable.set('imgId', 'newValueSetOnSuccess');
//Or reactive var, and depending on your hierarchy
//this.template.parent().parent().yourReactiveVar.set(undefined);
}
}
});
Here is Html and JS for reference.
<template name="postsTemplate">
{{#autoForm schema=postFormSchema id="postFormId" type="method" meteormethod="addPost"}}
<!-- other autoform stuff -->
This is reactive variable used in template -- {{imgId}}
{{/autoForm}}
</template>
Template.postsTemplate.created = function() {
//Using reactive-dict package here
this.reactiveDictVariable = new ReactiveDict();
this.reactiveDictVariable.set('imgId', undefined);
};
Template.posts.events(
"change .some-class": function(event, template) {
//other stuff
template.postImgState.set('imgId', 'something');
}

knockout throws Message: TypeError: <xxx> is not a function. What does it mean?

I have this code:
<ul id="chats" data-bind="foreach: chats">
<li>
<div class="chat_response" data-bind="visible: CommentList().length == 0">
<form data-bind="submit: $root.addComment">
<input class="comment_field" placeholder="Comment…" data-bind="value: NewCommentText" />
</form>
</div>
<a class="read_more_messages" data-bind="visible: moreChats, click: showMoreChats">Read More Messages...</a>
</li>
</ul>
function ChatListViewModel(chats) {
// Data
var self = this;
self.chats = ko.observableArray(ko.utils.arrayMap(chats, function (chat) {
return { CourseItemDescription: chat.CourseItemDescription,
CommentList: ko.observableArray(chat.CommentList),
CourseItemID: chat.CourseItemID,
UserName: chat.UserName,
ChatGroupNumber: chat.ChatGroupNumber,
ChatCount: chat.ChatCount,
NewCommentText: ko.observable("")
};
}));
self.moreChats = ko.observable(true);
self.showMoreChats = function () {
var LastChatGroupNumber = self.chats()[self.chats().length - 1].ChatGroupNumber;
$.ajax({
url: $.CourseLogic.dataitem.GetMoreChatsUrl,
data: "{chatGroupNumber: " + ko.toJSON(LastChatGroupNumber + 1) + "}",
type: "post",
contentType: "application/json",
success: function (chats) {
var chatList = self.chats();
$.each(chats, function (index, chat) {
self.chats.push(chat);
});
}
});
}
}
ko.applyBindings(new ChatListViewModel(initialData));
But I get this error when the showMoreChats function is called:
Unable to parse bindings.
Message: TypeError: CommentList is not a function;
Bindings value: visible: CommentList().length == 0
What does it mean?
It's not that CommentList is undefined, it's just that it's not an observable (hence not a function). The reason being that in your ajax callback, you are just pushing the 'chat' objects received from your server "as is". You're not creating for example a new observableArray called CommentList, but you're just putting a bare array CommentList - hence the thrown error by KO.
You would need to make the same transformation as you did when constructing self.chats in the viewmodel constructor, e.g.:
$.each(chats, function(index, chat) {
self.chats.push(
{
CourseItemDescription: chat.CourseItemDescription,
CommentList: ko.observableArray(chat.CommentList),
CourseItemID: chat.CourseItemID,
UserName: chat.UserName,
ChatGroupNumber: chat.ChatGroupNumber,
ChatCount: chat.ChatCount,
NewCommentText: ko.observable("")
}
);
});
By the way, you should also take a look at the ko.mapping plugin, it can do this transformation for you.
Edit: Also, for better performance you shouldn't push each individual item into the observable array, but add them in one bulk operation, something like:
self.chats( self.chats().concat( ko.utils.arrayMap(chats, function(chat) {
return { /* ... same as before ... */ };
} ) );
Found this question via Google, adding my case for reference.
It's really, really stupid; yet this is a mistake caused by inattention I often make:
When using one of the ko.utils array functions (arrayMap, arrayForEach, arrayFirst, etc.), Knockout will throw this error when you forget to specify the source array, eg. when you do:
ko.utils.arrayMap(function(item) { ... })
instead of:
ko.utils.arrayMap(myArray, function(item) { ... });
It means your binding cannot detect CommentList, i.e. CommentList is undefined in the current context.

Categories

Resources