How to know if the data in the viewmodel has been changed? - extjs

I have 3 field like this:
{
name: 'firstName',
fieldLabel: 'First name',
bind: '{person.firstName}',
xtype: 'textfield',
allowBlank: false,
listeners: {
blur : {
buffer : 1000,
fn : 'detectGender'
},
}
},
{
name: 'middleName',
fieldLabel: 'Middle name',
bind: '{person.midleName}',
xtype: 'textfield',
allowBlank: true,
listeners: {
blur : {
buffer : 1000,
fn : 'detectGender'
},
}
},
{
name: 'lastName',
fieldLabel: 'Last name',
bind: '{person.lastName}',
xtype: 'textfield',
allowBlank: true,
listeners: {
blur : {
buffer : 1000,
fn : 'detectGender'
},
}
},
And I have a function to determine the gender of the user:
detectGender: function () {
const vm = this.getViewModel()
const firstName = vm.get('person.firstName');
const lastName = vm.get('person.lastName');
const personName = `${firstName ? firstName : ' '}${middleName ? middleName : ' '}${lastName ? lastName : ' '}`
if (firstName) {
//ajax request to server with data: personName
// personName migth be SarahSmith, or Sarah, or SarahEllen, or SarahEllenSmith
}
},
It works, but my problem is that function 'detectGender' runs all three times if all these fields are filled in. And my server returns three responses. But i need run this only one time. Maybe there is some way to create an event for the viewmodel to listen when person name has been changed?
Or any ideas on how to send the user name from the three fields only once?
Hope i was clear enoght... Please any help

I think Arthur's comment is probably the simplest way to solve the issue.
However, here's a couple more options.
Opt1 - You could put each of the name fields in a "parent" and listen to the "blur" event on the parent. In the sencha fiddle example, look at the code "Opt1" which I've set up with a "fieldcontainer". Since a container doesn't have a "blur" event, the "focusleave" event is used. I think of my two options, this would be the best as it gives you a good indication the user is done entering the name.
// Opt 1: Detect "blur" on higher level element
// so you know user is done
xtype: 'fieldcontainer',
layout: 'hbox',
fieldLabel: 'Name',
listeners: {
// "Opt1: blur equivilent" for a container
focusleave: 'detectGender'
},
items: [{
name: 'firstName',
emptyText: 'First',
bind: '{person.firstName}',
xtype: 'textfield',
allowBlank: false
}, {
name: 'middleName',
emptyText: 'Middle',
bind: '{person.middleName}',
xtype: 'textfield',
allowBlank: true
}, {
name: 'lastName',
emptyText: 'Last',
bind: '{person.lastName}',
xtype: 'textfield',
allowBlank: true
}]
}
Opt2 - You brought up listening to the viewModel data. I've provided an example of this, but it's basically a "change" event, so you'd get an event each time someone typed a letter in the field, resulting in a similar problem you have now. I did an example for fun, with a throttled function to help delay, but I think you'd end up calling the server way too much as unlike the above, you don't know when someone is "done" entering the name.
This uses the bindings config on a ViewController, which is a convenience for manually calling viewModel.bind(), which is how you can "listen" to viewModel data.
Ext.app.ViewController - bindings config
Ext.app.ViewModel - bind method
Here's the fiddle, hopefully it's not too confusing having both examples in one.
Sencha Fiddle

Related

Unable to layout composite field items

I am using Ext JS 3.4 and in the composite field, there are three fields, code is as below:
xtype: 'compositefield',
name: 'comboField',
fieldLabel: 'Partner with',
width: 400,
cItems:[{
xtype: 'combo',
name: 'partnerTypeCombo',
value: 'ProviderName',
mode: 'local',
store: new Ext.data.ArrayStore({
fields: ['id', 'displayValue'],
data: [
['ProviderName', 'Provider Partner Name'],
['OtherProvider', 'Other Provider Partner']
]
}),
valueField: 'id',
displayField: 'displayValue',
listeners: {
scope: this,
select: function(combo, record, index) {
var providerField = this.formPanel.getForm().findField('comboField_providerPartnerNameField');
var otherProviderField = this.formPanel.getForm().findField('comboField_otherProviderPartnerNameField');
if (combo.value == "OtherProvider") {
providerField.setVisible(false);
otherProviderField.setVisible(true);
}
else {
providerField.setVisible(true);
otherProviderField.setVisible(false);
}
}
}
}, {
xtype: 'spacer',
width: 10,
flex: 0
}, {
xtype: 'modellinkfield',
name: 'providerPartnerNameField',
modelLevelType: 'Organization',
modelType: 'Organization',
pickerReport: {
reportName: 'TMS.SupplierVendorOrgPicker',
targetLevelType: 'Organization'
}
}, {
xtype: 'textfield',
name: 'otherProviderPartnerNameField',
hidden: true
}]
By using the above code and without hiding any field, I got the below result
But My expectation is
By default third field (which is text field) should be hidden
On selecting Combobox values, the next two fields should be visible/hidden.
Like if dropdown field value is "Provider Partner Name" then only second
field (modeling field) should be visible (shown as below)
And if dropdown field value is "Other Provider Name" then only third
field (i.e text field) should be visible.
But I am unable to achieve this third objective. I am getting the following output for this (the field is getting overridden)
And I am expecting the following output.
Looks like this may be some layout issue or maybe I need to apply some CSS style to handle this. Can someone please help me to solve this issue.
Able to solve this issue by using below code :
otherProviderField.ownerCt.doLayout();

How to dynamically set fieldLabel value in ExtJS?

I want to achieve a situation where my fieldLabel values come from store. Store has the values I need, but I can't figure out how to set these values to fieldLabel.
Let's say I have some block:
items: [{
name: 'someName',
fieldLabel: 'someFieldLabel'
}]
someName in name field comes from store and works as expected.
someFieldLabel value in fieldLabel field is present in store but it shows literally 'someFieldLabel', not the value from store.
Any suggestions how to make this work dynamically with values from store?
Below is the code from a fiddle on how to change the fieldLabel dynamically.
Ext.application({
name : 'Fiddle',
launch : function() {
Ext.create('Ext.panel.Panel', {
title: 'Dynamic Set Field Label',
layout: 'vbox',
renderTo: Ext.getBody(),
items: [{
xtype: 'textfield',
itemId: 'lblItem',
fieldLabel: 'Testing',
margin: '0 0 20 0'
}, {
xtype: 'button',
text: 'Set Field Label',
handler: function (btn) {
// alert('button presssed');
Ext.ComponentQuery.query('#lblItem')[0].setFieldLabel('New Label');
}
}]
})
}
});
You can see it working here.
Note: Using Ext.ComponentQuery it will return an Array of objects meeting the query requirements. You need to specify index to obtain a single object.
store.load({
callback: function (records) {
Ext.ComponentQuery.query('#lblItem')
}
})
items: [{
itemId: 'someField'
name: 'someName',
fieldLabel: 'someFieldLabel'
}];
on store load function
var somefield = Ext.ComponentQuery.query('field[itemId=someField]');
somefield[0].setFieldLabel('dynamic_field_label');

Composite components

I'd like to create a component which combines other existing components. A simplified example is below:
Ext.define('CCC.ThreeNames', {
extend: '???',
alias: 'widget.threenames',
items: [
{
xtype: 'textfield',
label: 'First',
itemId: 'first',
name: 'first'
},
{
xtype: 'textfield',
label: 'Middle',
itemId: 'middle',
name: 'middle'
},
{
xtype: 'textfield',
label: 'Last',
itemId: 'last',
name: 'last'
}
]
});
So let's say I want to use it:
items: [
{
xtype: 'threenames',
itemId: 'applicant',
name: 'applicant'
},
{
xtype: 'threenames',
itemId: 'dependent',
name: 'dependent'
}
]
First of all, what do I extend ??? from? I was thinking xtype: 'fieldset', but this type isn't considered a form field, so instead when you're pulling data via getValues, it skips the fieldset and iterates down to the text fields. The text fields don't "know" their context, because that's in the parent element, so they just report as first, middle and last, and now there are duplicate fields in the data set.
I was thinking during initialization, maybe the parent component munges the name and prefixes them with its own name, e.g. first, middle, last to applicant.first, applicant.middle, applicant.last, etc. but then that's going to muck up any code in the components that uses the name for whatever.
How to handle this? We'd really like to be able to reuse some complex composite components so that our designers don't have to hand code them all the time, and have the component work as an independent unit to save and retrieve data, so for example form.getValues() pulls out the data, without knowing anything about the composite component.
Would really appreciate a solution to this.
Regarding the possible duplicate suggested by Coderino Javarino: This question is not a duplicate of that one. That component is able to work using a single value for both fields, i.e. the date field interprets the value as a date, the time field interprets the value as a time. In effect it's really just a single component with a custom display type.
Naming fields requested way is a simple task:
Ext.define('CCC.ThreeNames', {
extend: 'Ext.container.Container', //you can use Ext.form.Panel for additional functionality
alias: 'widget.threenames',
items: [
{ xtype: 'textfield', itemId: 'first', name: 'first', fieldLabel: 'First' },
{ xtype: 'textfield', itemId: 'middle', name: 'middle', fieldLabel: 'middle' },
{ xtype: 'textfield', itemId: 'last', name: 'last', fieldLabel: 'last' },
],
listeners: {
beforerender: function() {
let self = this;
this.query('textfield').forEach(function(field){
field.name = self.name + '.' + field.name;
field.itemId = field.name;
});
}
}
});
using such naming is completely different thing. But as I understood from your comment that MVC pattern is not in use then this solutions should give expected result.
Here is the recommended way to achieve what you want
Ext.define('CCC.ThreeNames', {
extend: '???',
**xtype: 'threenames',**
alias: 'widget.threenames',
items: [
{
xtype: 'textfield',
label: 'First',
itemId: 'first',
name: 'first'
},
{
xtype: 'textfield',
label: 'Middle',
itemId: 'middle',
name: 'middle'
},
{
xtype: 'textfield',
label: 'Last',
itemId: 'last',
name: 'last'
}
]});
Now you can use it like this
items: [
{
xtype: 'threenames',
itemId: 'applicant',
name: 'applicant'
},
{
xtype: 'threenames',
itemId: 'dependent',
name: 'dependent'
}
]

Extjs bind config variable to value

I have the following application: https://fiddle.sencha.com/#view/editor&fiddle/1nmm. I'm using extjs 6.
After double click event on a row, I want to open a new tab. The new tab should contain a form with the information from the grid. My problem is that when I try to bind the displayfield value, the output is empty( nothing is shown).
xtype: 'displayfield',
fieldLabel: 'Id',
bind: {
value: '{record.data.ts_id}'
}
The above 'record' is declared as follow:
config: {
record: null,
rowIndex: null
},
bind: {
record: '{recordVM}',
rowIndex: '{rowIndexVM}'
}
How to properly bind to displayfield's value?
2 things:
1) Modify the way you're passing data to the viewmodel in the TabUIController:
viewModel: {
data: {
record: record,
rowIndex: rowIndex
}
}
There's no point trying to re-map those things.
2) Modify the bind statement in your view to value: '{record.ts_id}', the binding is smart enough to drill into the fields when it sees a record.
Try this:
TabUIGrid.js
bind: {
store :'{users}',
selection : '{myrecord}'
},
TabFormTest.js
{
xtype: 'displayfield',
fieldLabel: 'Name',
bind: '{myrecord.ts_name}'
}, {
xtype: 'displayfield',
fieldLabel: 'Email',
bind: '{myrecord.ts_email}'
}
I tested it on your fiddle and it works fine.

How to Set Single Field Dirty in ExtJS

PROBLEM: We toggle fields on form. When secondField is shown instead of firstField, then form is changed. But secondField is still marked as not dirty, because both fields remain unchanged. Showing secondField should always make it and the form (model) dirty.
RESEARCH: setDirty() method is done on whole record, setValue() acts as expected, but smells like a hack and can't be used for various field types (textfield, combobox).
QUESTION: How to manually set a single form field state changed to invoke saving its data?
You are mixing up data state with form visualisation. By default, "field is shown" has no relation to data, so you need to explicitly create one. This can be done by changing some data on toggling, or other way round — toggling on changing data.
For example, toggling can occur on checking/unchecking a checkbox field which will represent a piece of form data (also see fiddle):
Ext.create('Ext.form.Panel', {
viewModel: {
type: 'default'
},
items: [
{
xtype: 'checkbox',
reference: 'toggle',
itemId: 'toggle',
boxLabel: 'Toggle',
hidden: true
},
{
xtype: 'button',
text: 'Toggle',
enableToggle: true,
toggleHandler: function() {
var form = this.up('form'),
checkbox = form.child('#toggle');
checkbox.setValue(!checkbox.getValue());
console.log(form.isDirty() ? 'Dirty!' : 'Not dirty');
}
},
{
xtype: 'textfield',
name: 'firstField',
fieldLabel: 'First Field',
bind: {
hidden: '{toggle.checked}'
}
},
{
xtype: 'textfield',
name: 'secondField',
fieldLabel: 'Second Field',
bind: {
hidden: '{!toggle.checked}'
}
}
],
renderTo: Ext.getBody()
});

Resources