I am working on a component to render user details. The details are fetched from an API request and properties are updated but these changes are not reflected on the DOM.
The company I work for is still using Polymer 1.2 and I am having difficulty finding and understanding the documentation. Do I need to make the get request from the parent component or is it possible to do this directly inside the component? There is something fundamental I am not grasping about this and wondering if someone could shed some light on it for me.
Thank you.
Snippet from the template:
<p>{{i18n.GLOBAL_USERNAME}} - [[username]]</p>
<p>{{i18n.GLOBAL_FIRSTNAME}} - [[firstname]]</p>
<p>{{i18n.GLOBAL_LASTNAME}} - [[lastname]]</p>
<p>{{i18n.GLOBAL_EMAIL}} - [[email]]</p>
<p>{{i18n.GLOBAL_NUMBER}} - [[number]]</p>
Snippet from the polymer functions:
Polymer({
is: 'my-component',
properties: {
username: {
type: String,
value: "n/a",
},
firstname: {
type: String,
value: "n/a"
},
lastname: {
type: String,
value: "n/a"
},
email: {
type: String,
value: "n/a"
},
number: {
type: String,
value: "n/a"
},
},
ready: function () {
var user = app.auth.hasSession()
if (user !== null) {
app.getUserInfo(user.user_id, this._getUserSuccessful, this._getUserFail);
}
},
_getUserSuccessful: function (res) {
this.username = res.user.user_id
this.firstname = res.user.firstname
this.lastname = res.user.lastname
this.email = res.user.email
this.number = res.user.phone_number
console.log("got user details")
},
_getUserFail: function () {
console.log("failed to get user details")
},
});
You need to use Polymer setter methods instead of plain assignment.
Here's an example:
_getUserSuccessful: function (res) {
this.set('username', res.user.user_id)
this.set('firstname', res.user.firstname)
this.set('lastname', res.user.lastname)
this.set('email', res.user.email)
this.set('number', res.user.number)
console.log("got user details")
},
Using setter and/or array mutator methods is required for data-bound properties otherwise Polymer can't know that something has changed in order to update the bindings.
I'd really suggest you read the whole Data system concepts article before moving forward with doing any kind of work with Polymer 1.x.
Related
I am using the Async - React Select drop down in react - https://react-select.com/async
The dataset for the drop down needs to look like this :
async function getPromise4(): Promise<Person[]> {
return [
{
value: 'Tom',
label: 'Tom',
},
{
value: 'David',
label: 'David',
},
{
value: 'MeeMee',
label: 'MeeMee',
},
];
}
and I am trying to get this created from an API. The API works ok but it doesn't give it back as value / label - so I cannot return the data direct to the drop down by a promise.
I am struggling to create an object like about. This is the code I use to get the data from the API.
async function main(): Promise<void> {
const foo = await dynamicsWebApi.retrieveAll("accounts",["name"]).then(function (stuff) {
var records = stuff.value
records.forEach(function (value) {
console.log(value.name);
This above code gets the data from the API fine. I guess I need to iterate over the data and build an object like above. I am sure its quite reasonable to do but I cannot seem to figure it out.
I am having such a difficulty inserting observable into an array. What am I doing wrong here..
app.component.ts
const secondNavList = [];
this.appService.issuerList$.subscribe(iss => {
iss.forEach(value => {
console.log(value) //prints {name: 'A', id:'1'} {name: 'B', id:'2'}
secondNavList.push({
config: {
label: value.name
id: value.id
},
type: 'button'
});
});
};
console.log(secondNavList) // prints []
//But I want
//(2)[{...}.{...}]
appService.ts
get issuerList$(): Observable<Issuer[]>{
return this._issuerList.asObservable();
}
getIssuerList(){
const url = DBUrl
this.httpService.getData(url).subscribe((data:any[]) => {
let issuerList = [];
data.forEach(x=>{
issuerList.push(<Issuer>{name: x.issuerName, id: x.issuerId.toString()});
});
this._issuerList.next(issuerList)
})
}
Although inside my secondNavList, it contains data but I can't access it.
The fundamental issue you have is that you're trying to display the value of secondNavList before it is actually set in the subscriber. The rxjs streams are asynchronous, which implies that the the callback inside the subscribe method that appends to the list will get executed at some unknown point after subscribe is executed.
More importantly, I'd recommend that you try to take advantage of the map operator and array.map method, as well as the asyncronous pipes.
appService.ts
readonly issueUpdateSubject = new Subject<string>();
readonly issuerList$ = this.issueUpdateSubject.pipe(
switchMap(url => this.httpService.getData(url)),
map((data: any[]) => data.map(x => ({ name: x.issuerName, id: x.issuerId.toString() }))),
shareReplay(1)
);
getIssuerList() {
this.issueUpdateSubject.next(DBUrl);
}
app.component.ts
readonly secondNavList$ = this.appService.issuerList$.pipe(
map(iss => iss.map(value => ({
config: { label: value.name, id: value.id },
type: 'button'
}))
);
In the appService, instead of having an observable update a subject, I just had a subject emit update requests. Then instead of having to convert the subject to an observable, it just is an observable.
The shareReplay operator will share the most recently emitted list to any new subscribers.
Instead of appending to new arrays, I just use the array.map method to map each array element to the new desired object.
Instead of creating new array outside of the observable, and setting them in subscribe, I use the map operator to stream the latest instances of the arrays.
I find the more comfortable I got with rxjs the less I actually set the values of streams to instances of variables and rarely call subscribe - I just connect more and more streams and there values are used in components via async pipes. It's hard to get your head around it at first (or after a year) of using rxjs, but it's worth it in the end.
The error is because the observable value is an object array, and you want to add this into a simple object.
Try this.
const secondNavList = [];
this.appService.issuerList$.subscribe(iss => {
iss.forEach(value => {
console.log(value) //prints {name: 'A', id:'1'} {name: 'B', id:'2'}
value.forEach(v => {
secondNavList.push({
config: {
label: v.name,
id: v.id
},
type: 'button'
});
});
});
};
console.log(secondNavList) // prints []
Using React with TypeScript
Please could somebody provide an example of how I might be able to use a Kendo DataSource to read from a method which internally uses Axios to prod an external API for JSON data..? I must have flown through 20 different versions of this code trying different approaches, nothing seems to fit...
All I'm trying to do currently is supply a Kendo ComboBox with an array of {id: number, name: string}
Very basic stuff at the moment, but I do have to use a similar approach to this later on with a Kendo Grid which handles server side sorting and pagination so I'd like to get this working now then that should be somewhat easier later on...
The reason I want to use Axios is because I've written an api.ts file that appends appropriate headers on the gets and posts etc and also handles the errors nicely (i.e. when the auth is declined etc...)
A basic example of what I'm trying, which isn't working is this: -
public dataSource: any;
constructor(props: {}) {
super(props);
this.dataSource = new kendo.data.DataSource({
type: "odata",
transport: {
read: function() {
return [{ id: 1, name: "Blah" }, { id: 2, name: "Thing" }];
}.bind(this)
},
schema: {
model: {
fields: {
id: { type: "number" },
name: { type: "string" }
}
}
}
});
}
<ComboBox
name="test"
dataSource={this.dataSource}
placeholder={this.placeholder}
dataValueField="id"
dataTextField="name"
/>
Anybody got any thoughts on this please? :)
Easy fix in the end...
this.dataSource = new kendo.data.DataSource({
transport: {
read: function(options: any) {
options.success([{ id: 1, name: "Blah" }, { id: 2, name: "Thing" }]);
}.bind(this)
},
schema: {
model: {
fields: {
id: { type: "number" },
name: { type: "string" }
}
}
}
});
2 things were wrong..
Removed the type: "odata",
and
Added the usage of options in
All working fine now with the async await function also, just passing the data into the options.success in the .then on the promise. Job done :-)
I have a question about how to just get a certain element of an array using MongoDB and MeteorJS. I have the following schema for the user document:
bankList:[
{
id: "34567890987654345678",
name: "xfgchjbkn",
type: "credit"
},
{
id: "09876543456789098767"
name: "65789876t8",
type: "debit"
}
]
I first subscribe to only part of the fields in the array, specifically I gather a list of all the ids. Then I have an edit screen that should subscribe to all of the fields for a specific element in the array with a matching id. I do not want to expose the rest of the array just the single element. Currently, I use the following to first gather a list of just the ids:
Meteor.users.find({_id: this.userId},
{fields:{'bankList.id': 1}});
And the following publication-subscription method to get just a specific element's information:
Publication:
Meteor.publish("userBankAdvanced", function(bankId){
check(bankId,String);
if(this.userId){
return Meteor.users.find({_id:this.userId,"bankList.id": bankId}, {'bankList.$': 1});
}else{
this.ready();
}
});
Subscription:
this.route('edit_account', {
path: '/edit/account/',
waitOn: function(){
if(Session.get("bankId")){
return Meteor.subscribe('userBankAdvanced',Session.get("bankId"));
}
return null;
},
data: function(){
if(Session.get("bankId")){
return Meteor.users.findOne();
}
return null;
},
onBeforeAction: function(){
beforeHooks.isRevise(Session.get("bankId"));
}
});
The subscription method returns all of the elements of the array with all of the information.
I want, for example, just this (not the entire list with all of the information):
bankList:[
{
id: "34567890987654345678",
name: "xfgchjbkn",
type: "credit"
}]
It looks like you're just missing the "fields" specifier in your "userBankAdvanced" publish function. I wrote a test in meteorpad using your example and it seems to work fine. The bank id is hardcoded for simplicity there.
So instead of
return Meteor.users.find({_id:this.userId,"bankList.id": bankId}, {'bankList.$': 1});
try using
return Meteor.users.find({_id:this.userId,"bankList.id": bankId}, {fields: {'bankList.$': 1}});
No luck, in meteor the "fields" option works only one level deep. In other words there's no builtin way to include/exclude subdocument fields.
But not all is lost. You can always do it manually
Meteor.publish("userBankAdvanced", function (bankId) {
var self = this;
var handle = Meteor.users.find({
_id: self.userId, "bankList.id": bankId
}).observeChanges({
added: function (id, fields) {
self.added("users", id, filter(fields, bankId));
},
changed: function (id, fields) {
self.changed("users", id, filter(fields, bankId));
},
removed: function (id) {
self.removed("users", id);
},
});
self.ready();
self.onStop(function () {
handle.stop();
});
});
function filter(fields, bankId) {
if (_.has(fields, 'bankList') {
fields.bankList = _.filter(fields.bankList, function (bank) {
return bank.id === bankId;
});
}
return fields;
}
EDIT I updated the above code to match the question requirements. It turns out though that the Carlos answer is correct as well and it's of course much more simple, so I recommend using that one.
The following screenshot shows a combined form for sign-in and sign-up:
The following module is used to render the AuthView:
MyApp.module("User", function(User, App, Backbone, Marionette, $, _) {
User.AuthView = Marionette.ItemView.extend({
className: "reveal-modal",
template: "user/auth",
ui: {
signInForm: "#signin-form",
signUpForm: "#signup-form"
},
events: {
"focus input": "onFocus"
},
onFocus: function() {
console.log("Some input field has received focus.");
},
onRender: function() {
this.signInForm = new Backbone.Form({
schema: {
signInEmail: {
type: "Text",
title: "E-Mail address"
},
signInPassword: {
type: "Password",
title: "Password"
}
}
}).render();
this.ui.signInForm.prepend(this.signInForm.el);
this.signUpForm = new Backbone.Form({
schema: {
signUpEmail: {
type: "Text",
title: "E-Mail address"
},
signUpPassword: {
type: "Password",
title: "Password"
},
signUpPasswordConfirmation: {
type: "Password",
title: "Password confirmation"
}
}
}).render();
this.ui.signUpForm.prepend(this.signUpForm.el);
}
});
});
How can I automatically focus the first field in each sub-form whenever it is rendered? The first fields would be signInEmail for the signInForm and signUpEmail for the signUpForm.
I tried to listen to focus input events. Such an event is triggered when I click into one of the input fields, not before.
Meanwhile, inspired by the current answers I came up with the following helper function:
focusFirstFormField: function(form) {
if (_.isUndefined(form)) {
throw "IllegalStateException: Form is undefined."
}
// TODO: AuthView does not focus first sign-in field.
var firstInput = form.find(':input:first');
if (_.isObject(firstInput)) {
if (firstInput.length > 0) {
firstInput = firstInput[0];
}
else {
throw "IllegalStateException: Form find returns an empty jQuery object."
}
}
_.defer(function() {
firstInput.focus();
});
}
There is still need for improvement, though.
The events object are DOM events which are generally triggered by the user so that's not what you'll likely want to use in this case.
If I'm understanding you correctly you would like to put the focus in the first input of each of the forms but since you can only have focus on one thing at a time and they are rendering together you'll have to choose one or the other.
The simplest option is to add another line at the end of onRender focusing on the input. If your input is generating an input something like this:
<input type="text" name="signInEmail">
Then you can add:
this.$('[name=signInEmail]').focus();
If not you'll have to change the selector this.$(xxxx).focus() to suit.
You can use onDomRefresh event of the view. It will be triggered after view rendered and Dom refreshed.
onDomRefresh: function() {
this.focusFirstInput();
};
focusFirstInput: function() {
this.$(':input:visible:enabled:first').focus();
};
This solution applies to general cases. However, pay attention if you are using Bootstrap. I can't get this work there. Instead, I set autofocus: 'autofocus' in the field and it works.
You can add it to onRender method.
this.ui.signInForm.find('input[type=text]:first').focus();
this.ui.signUpForm.find('input[type=text]:first').focus();
On the onRender method I do :
$(this.el).find(':input[autofocus]').focus();
And I add the autofocus="" flag onto my HTML node. This works for refresh.