the singularity of being and nothingness
ExtJS 4.2 Walkthrough — Part 6: The Big Form
After a few weeks off, we’re back to it! Hopefully, you’ve done your homework and are caught up 🙂
We’ve been working over the last several sessions on laying down some supporting pieces for some real functionality within our application. Not that we haven’t been doing “real” stuff to this point; but most of the pieces have been admittedly simple and are parts of the app that will be used infrequently. That is, managing car colors, categories, makes and models isn’t particularly “business logic”–they’re just ancillary (although necessary) parts of a greater whole.
But now that we have several of these foundational elements in place, we can really start plugging away at the core purpose of our app, which is Inventory Management.
So without further ado, let’s get to it.
NOTE: The code for this session can be found on GitHub.
Our Goals for this Session
In this installment, we want to tackle the following:
- Build an Inventory Form for entering data
- Wire up form to a new controller for adding/editing inventory records
- Display inventory records in a grid (surprise, surprise!)
- Build a search form for our inventory records
Our inventory management form is actually going to be a bit complex (not in difficulty, but in data collection), so we won’t actually finish the whole thing this time. However, we will see some options for for nesting views and familiarize ourselves with some fun form field types.
Model and Store
By now, we’re pro with building models and stores, so we’ll not go into any detail on this whatsoever, beyond the fields that we want in the model. Car.js looks like this:
/** * Model representing a Staff object */ Ext.define('CarTracker.model.Car', { extend: 'CarTracker.model.Base', idProperty: 'CarID', fields: [ // id field { name: 'CarID', type: 'int', useNull : true }, // simple values { name: 'Description', type: 'string' }, { name: 'Year', type: 'int' }, { name: 'ListPrice', type: 'int' }, { name: 'SalePrice', type: 'int' }, { name: 'AcquisitionDate', type: 'date', dateWriteFormat: 'Y-m-d' }, { name: 'SaleDate', type: 'date', dateWriteFormat: 'Y-m-d' }, { name: 'IsSold', type: 'boolean' }, // relational properties { name: 'Status', type: 'auto' }, { name: 'Make', type: 'auto' }, { name: 'Model', type: 'auto' }, { name: 'Category', type: 'auto' }, { name: 'SalesPeople', type: 'auto' }, { name: 'Color', type: 'auto' }, { name: 'Features', type: 'auto' }, // decorated properties { name: '_Status', type: 'string', persist: false }, { name: '_Make', type: 'string', persist: false }, { name: '_Model', type: 'string', persist: false }, { name: '_Category', type: 'string', persist: false }, { name: '_Color', type: 'string', persist: false }, { name: '_Features', type: 'string', persist: false }, { name: '_SalesPeople', type: 'string', persist: false } ] });
As we’ve already discussed, I’m including a number of non-persistent fields (the ones with the underscores) which will contain the values of any foreign key values. Of course, if you want to look these up from the various store instances within the app, you can do that as well…I prefer this method better, but it’s completely up to you and what works best within your application.
A New Controller
In previous installments, we’ve tended to begin with the views when developing new sections of functionality. However, once you become familiar with the approach to section-based development within ExtJS 4 applications, sometimes starting with a Controller makes sense and helps you define from the get-go precisely what you want to happen (and then build the views to support that).
We’ll come back to this later, but let’s start by stubbing out some basic parts of our controller (Cars.js), based on the goals we’ve already defined:
/** * Controller for all car-related management functionality */ Ext.define('CarTracker.controller.Cars', { extend: 'CarTracker.controller.Base', ... views: [ 'car.List', 'car.edit.Form', 'car.edit.Window', 'car.search.Form', 'car.search.Window' ], refs: [ { ref: 'CarList', selector: '[xtype=car.list]' }, { ref: 'CarEditWindow', selector: '[xtype=car.edit.window]' }, { ref: 'CarEditForm', selector: '[xtype=car.edit.form]' }, { ref: 'CarSearchWindow', selector: '[xtype=car.search.window]' }, { ref: 'CarSearchForm', selector: '[xtype=car.search.form]' } ], init: function() { this.listen({ controller: {}, component: {}, global: {}, store: {}, proxy: {} }); } });
Nothing out of the ordinary from what we’ve already developed. However, as you’ll notice, we’ve already defined the views[], as well as the refs[]. As I said earlier, this shows that we’ve already planned what we want to accomplish for this particular section of our app. All that’s left now is to build out the views to support it, and by specifying this ahead of time, we have something of a blueprint of our next steps.
NOTE: If we were to add Cars.js to our app.js right now, we’d be greeted with several error messages as ExtJS complains about not being able to find the views defined. This is expected, of course, since we haven’t created those files yet. If you follow this controller-first approach, be careful about this. If you don’t realize what’s happening, you might waste precious time debugging.
And since we’re pretty comfortable with connecting our controllers to views, there’s actually no reason why we couldn’t start developing the supporting controller methods. However, since this is our first real form for this app, let’s hold off until a bit later.
The Form
Our form for adding/editing inventory records is going to be a bit different than the fairly good-sized one that we made for managing Sales Staff. There are good reasons for this.
First, as we saw in reviewing the Model for our records, we have a fair number of data fields to capture. If we’re not smart, this will make our form really big and awkward to complete.
Second, since we want to be able to manage a fancy list of “Features” that are available for our individual cars, we’re going to use the fancy ItemSelector field type. While this will let us easily maintain a list of features, it is a bulky (in visual size) field element. So it probably makes sense to deal with this specially and separately from the rest of the form fields.
In order to mitigate the impact of these two considerations, we’re going to incorporate a few tricks to help our form’s “flow” feel a bit more manageable:
- Group fields together logically (not necessarily functionally) by using Ext.form.FieldSet
- Turn our entire Ext.form.Panel in a Ext.tab.Panel. Our first tab will contain the majority of the details for the Car, and the second tab will contain our ItemSelector form field.
We have a plan! So let’s code! Here’s view/car/edit/Form.js:
/** * Form used for creating and editing Staff Members */ Ext.define('CarTracker.view.car.edit.Form', { extend: 'Ext.form.Panel', alias: 'widget.car.edit.form', requires: [ 'Ext.tab.Panel', 'Ext.form.FieldContainer', 'Ext.form.FieldSet', ... ], initComponent: function() { var me = this; Ext.applyIf(me, { items: [ { xtype: 'tabpanel', bodyPadding: 5, // set to false to disable lazy render of non-active tabs...IMPORTANT!!! deferredRender: false, items: [ { xtype: 'car.edit.tab.detail', title: 'Details' }, { xtype: 'car.edit.tab.feature', title: 'Available Features' } ] } ] }); me.callParent( arguments ); } });
As you can see, we start by defining our form just like we would with any custom component in our application. The important part is in items[]; you can see that the first (and only) items is a Ext.tab.Panel, which itself has two additional items. As we discussed above, what this will do is create a TabPanel within our Form. Each tab will have different content…in our case, the first tab (labeled “Details”) will have a number of form fields, while the second (labeled “Available Features”) will encapsulate our “Featurs” ItemSelector field.
NOTE: A very important thing to note is that we have set deferredRender: false on the TabPanel. By default, TabPanel’s lazy-render the contents of their tabs, which basically means that any content within those unselected tabs will be nonexistent until the tab is activated. While this works really well in some scenarios, it can be really BAD for forms, especially if your application is expecting ALL form fields to be submitted, regardless of whether they are modified or not.
That’s it. Now we simply need to define the contents of our tab panels. While you can put them wherever you like, I have opted to create a new tab package under view/car/edit, and here I’ll create two new files: Detail.js and Feature.js.
Detail.js
As we discussed above, this “tab” will contain the lion-share of the form fields in our form. As we also discussed, we’ll use Ext.form.FieldSet to provide some separation between logical sections of the form. In the end, we’re going for something that looks like so:
As we see above, we’ve divided the form between two fields sets: “Car Info” and “Sales Info”. Of course, the FieldSets don’t drive any particular functionality relating to the fields grouped beneath them, but they do provide a really nice visual separation and grouping which helps the eye focus on logical areas to attack.
A Ext.form.FieldSet is extremely simple. Here’s the syntax used above for the Car Info:
{ xtype: 'fieldset', title: 'Car Info', items: [...fields here...] }
Feature.js
We discussed above that the ItemSelector field allows us a really nice interface for selecting items from a list. While we could accomplish the same thing with a multi-select Ext.form.field.ComboBox, the ItemSelector field provides a little better visual representation of the data. Here’s an example of what ours will look like:
Pretty cool, huh?
While the ItemSelector fields provides a really powerful and aesthetically pleasing way to manage large selection lists, it’s configuration is actually not that different from the ComboBox that we’ve already been using:
{ xtype: 'itemselectorfield', name: 'Features', anchor: '100%', store: { type: 'option.feature' }, displayField: 'LongName', valueField: 'FeatureID', allowBlank: false, msgTarget: 'side', fromTitle: 'Available Features', toTitle: 'Selected Features', buttons: [ 'add', 'remove' ], delimiter: undefined }
A few things to note:
- By default, there are 6 buttons for ItemSelector field. You can target only particular ones by specifying an array of desired buttons in the buttons[] config. Since we don’t care about sort order at this point, we’ll just use “add” and “remove”
- By default, ItemSelector will bundle the selected values together as a comma-delimited string. If you need to submit the field as an array, simply set delimiter:null
- To get the images to show up in the buttons, we’ll need to add a few lines of code to our style.css file, as well as some images to our resources/images/icon folder (see source for these)
Completing the Form
Since we’ve been through developing forms before, we’ll not dwell on it here. To wrap up this form, we’ll do the same Form-in-Window style that we used for the Staff section, and our Details.js will need the following fields:
- Make (remotecombobox)
- Model (remotecombobox)
- Status (remotecombobox)
- Year (number field)
- Category (remotecombobox)
- Color (remotecombobox)
- Description (htmleditor)
- ListPrice (numberfield)
- AcquisitionDate (datefield)
- SalePrice (numberfield)
- SaleDate (datefield
- SalesPeople (remotecombobox w/multiSelect=true)
Back to the Controller
Since we’re using the same basic approach on this form as we used when creating our Staff section, we can utilize the same methods in our Cars.js controller, namely add(), edit(), showEditWindow() and save(). There is one important change we need to add, however.
Oftentimes, when utilizing the grid-to-form approach that we’ve been exploring, it is beneficial to have a slightly different view of data between the grid and the form. For example, on our Inventory form, we have a lot of fields that we *need* to track in the database; however, we don’t necessarily care to show every single data field in the grid. And in fact, with properties like Features, we can’t easily show all the data on the grid. Additionally, if we have a lot of fields per record, we increase the size of the JSON packet that must be returned from the server, and that can cause performance issues with our grid.
Because of this, we’ll make a slight adjustment to our workflow. Instead of loading the form with the same record that is selected in our grid, we can do a pre-form-populate AJAX request to retrieve the full data record from the server. This will let our grid perform with only the necessary data it needs, while still giving our form the full representation of the data that it needs.
To pull this off, we’ll first add a new method to our Base.js controller:
/** * Common way to retrieve full data record from the server before performing another action * @param {Ext.data.Record} record * @param {String} scope * @param {Functino} callbackFn * @param {Object} extraData */ loadDetail: function( record, scope, callbackFn, extraData ) { // first, reject any changes currently in the store //so we don't build up an array of records to save by viewing the records record.store.rejectChanges(); // make request for detail record Ext.Ajax.request({ url: record.store.getProxy().url + '/' + record.internalId + '.json', callback: function( options, success, response ) { if( success ) { // set "safe mode" so we don't get hammered with giant Ext.Error data = Ext.decode( response.responseText, true ); // update record record.set( data ); // call callback method callbackFn.call( scope, record, extraData ); } } }); }
Pretty simple. This method accepts 4 arguments:
- record – The data record (from our grid selection)
- scope – The scope in which to execute the method
- callbackFn – The method to fire when this is complete
- extraData – Any extra data to pass along to the callbackFn
To implement this, we simply need to tweak our edit() method in Cars.js a bit:
/** * Handles request to edit * @param {Ext.view.View} view * @param {Ext.data.Model} record * @param {HTMLElement} item * @param {Number} index * @param {Ext.EventObject} e * @param {Object} eOpts */ edit: function( view, record, item, index, e, eOpts ) { var me = this; // show window me.loadDetail( record, me, me.showEditWindow ); }
As we see, instead of calling showEditWindow() directly as we did in Staff.js, we now use our fancy new loadDetail() method, and fire showEditWindow() as our callback method.
Inventory Grid
We created a number of Ext.grid.Panels thus far, so we don’t need to dwell on this right now. Nothing has changed in our general approach, so we can simply create a new one with a few relevant columns:
- Make
- Model
- Category
- Year
- Color
- Acquired
- List Price
- Sale Price
- Sold?
- Sale Date
This setup does allow us to utilize a number of the data-type columns that ExtJS provides out of the box, so be sure to experiment with those. Ultimately, we want it to look like this:
Search Functionality
Now that we’ve got our regular grid/add/edit/save functionality out of the way, let’s venture into some new territory: adding a search form. In honesty, it’s not that out of bounds of what we’ve already done. It’s just a few more new views and a few new controller methods.
NOTE: At this point in our development, we’ve covered a good percentage of what the majority of your development will probably entail. This is important to realize, because adding functionality to ExtJS is really that simple: a few more views and controller methods. Once you understand this, the speed of your development will really take off!
First, let’s start by creating new package under view/car called search. In here, we’ll create two new classes: Form and Window (surprising, isn’t it?). Since we’ve been down this road before, I won’t go into a lot of detail about what they’re doing since it’s self-explanatory. Our search form (and window) needs to look like this:
I’ll leave you to flesh this out, but I will point out a few items.
First, you’ll notice that our ComboBox fields all have an “X” icon on them. This is a custom plugin (CarTracker.ux.form.field.plugin.ClearTrigger) that allows us to clear the value of the ComboBox field quickly. Our plugin, which we’ll create in ux/form/field/plugin, looks like this:
/** * @class CarTracker.ux.form.field.plugin.ClearTrigger * @extends Ext.AbstractPlugin * Plugin that adds the ability to clean an input field with a trigger class * @ptype cleartrigger */ Ext.define('CarTracker.ux.form.field.plugin.ClearTrigger', { extend: 'Ext.AbstractPlugin', alias: 'plugin.cleartrigger', constructor: function() { var me = this; me.callParent( arguments ); var field = me.getCmp(); }, init: function( field ) { var me = this; // combobox if( field.isXType( 'combobox' ) || field.isXType( 'datefield' ) || field.isXType( 'timefield' ) ) { field.trigger2Cls = 'x-form-clear-trigger'; field.onTrigger2Click = function() { field.clearValue(); } } else { field.triggerCls = 'x-form-clear-trigger'; field.onTriggerClick = function() { field.reset(); } } me.callParent( arguments ); } });
To use this on our ComboBox fields, we simply need to add it to each field’s plugins[] configuration, like so:
plugins: [ { ptype: 'cleartrigger' } ]
NOTE: In this example above, ptype functions in a similar was as xtype for components, but is a special convention just for plugins.
Adding Search to Our Controller
Now that we have our views in place, let’s do some searching! In our Cars.js controller, let’s first set up some listeners for our search form:
nit: function() { this.listen({ controller: {}, component: { ... 'grid[xtype=car.list] button#search': { click: this.showSearch }, 'grid[xtype=car.list] button#clear': { click: this.clearSearch }, 'window[xtype=car.search.window] button#search': { click: this.search }, 'window[xtype=car.search.window] button#cancel': { click: this.close } }, ... }); }
(If you haven’t already added the relevant search buttons to the grid and search window, respectively, go ahead and do that now).
Again, nothing new here. We’re simply adding methods in our controller to accomodate the new views…same methodology as with everything else we’ve done before, just different names. With this in mind, let’s concentrate on only two methods: clearSearch() and search().
search()
This is our main method for performing the search against the data in our grid:
/** * Executes search * @param {Ext.button.Button} button * @param {Ext.EventObject} e * @param {Object} eOpts */ search: function( button, e, eOpts ) { var me = this, win = me.getCarSearchWindow(), form = win.down( 'form' ), grid = me.getCarList(), store = grid.getStore(), values = form.getValues(), filters=[]; // loop over values to create filters Ext.Object.each( values, function( key, value, myself ) { if( !Ext.isEmpty( value ) ) { filters.push({ property: key, value: value }) } }); // clear store filters store.clearFilter( true ); // silent=true tells store not to reload store.filter( filters ); // close window win.hide(); }
Ultimately, what we are doing here is really quite simple (highlighted in bold):
- Get form fields from search form
- Create array of search filters ([{property=fieldName,value=fieldValue},…])
- Clear existing filters from store (so we don’t duplicate filters)
- Add filters to the store
Since our Cars.js store is configured to remote sort AND remotely filter data, all of our searches will necessarily be sent to the server to be processed, rather than handled locally within the store itself. This is important to understand, as any criteria that we search on will need to be recognized and acted upon by our server-side code. Our remote store, then, is merely making a “handshake” with the server that it will return the proper, filtered data; other than passing along the filters, our remote store is agnostic about the actual accuracy of the filters in relation to the data set which it is managing.
NOTE: I make this point because this is an often-asked question on the forums and StackOverflow. If you have a store with remote filtering, remote sorting, or both, you must absolutely pass off the responsibility of these actions to the server (generally incorporated into some manner of SQL query). Otherwise, your store will not know what to do with the data, other than to display whatever it receives back from the server.
clearSearch()
In addition to searching, we need to be able to clear the search, effectively returning our Inventory grid’s recordset back to its unfiltered form. This is super-simple:
/** * Clears search form and resets results * @param {Ext.button.Button} button * @param {Ext.EventObject} e * @param {Object} eOpts */ clearSearch: function( button, e, eOpts ) { var me = this, grid = me.getCarList(), store = grid.getStore(); // clear filter store.clearFilter( false ); // silent=false tells store to reload }
Wrapping Up and Planning for Next Time
We’ve covered a lot of ground in this installment. From adding and editing Inventory records, to search on them, we’ve knocked out a big chunk of what we want our app to ultimately be. You may have also noticed that we skipped over explicit walkthroughs of every particular bit of functionality. The hope is that by this point, we’ve done these approaches enough that you’re able to fill in the blanks on your own (it’s good practice!).
In spite of how much we accomplished on our form, it’s still incomplete. There are a few things we still need to implement in our Inventory form:
- Ability to upload and save images for each car
- Create dependent relationship between make and model on form (right now you can choose a “Ford Ram”…)
- A “detail” view that will show a graphical, non-editable view of each inventory record’s data
We’re out of time for that, but we’ll tackle in next time. See you then!
Print article | This entry was posted by existdissolve on June 23, 2013 at 9:59 am, and is filed under AJAX, ExtJS, ExtJS 4.2. App Walkthrough, JavaScript. Follow any responses to this post through RSS 2.0. You can leave a response or trackback from your own site. |
about 10 years ago
Hi, I’m following your (great) tutorial, and it’s getting better and better in every new step, but I have a problem, I can’t get any of my relational fields to be send to the server, all the other fields are sent correctly, but those that come from a different table are send to the server with empty values, I’ve google this issue, and even ask in the Sencha forum but I can’t solve the problem.
Everything points to the data type defined in the store and the form, but I tried every combination I can imagine (I’m a newbie, so, apparently I didn’t imagined enough combinations), but nothing, for example, the RemoteComboBox loaded for the color of the car, gets loaded with the colors in my database (MySQL and PHP by the way) I select a color, save a new record, and the request generated looks like this:
&records={“CreatedDate”:null,”Active”:true,”CarID”:null,”Description”:”adsfadsf”,”Year”:1922,”ListPrice”:3,”SalePrice”:3,”AcquisitionDate”:”2014-05-22T00:00:00″,”SaleDate”:”2014-05-22T00:00:00″,”IsSold”:false,”StatusName”:””,”MakeCountry”:””,”ModelName”:””,”CategoryName”:””,”SalesPeople”:[2],”ColorID”:””,”Features”:[]}&callback=Ext.data.JsonP.callback17
The “ColorID” for example, it’s empty, I’ve checked the code in GitHub but everything looks ok.
PLEASE HEEELP, THIS IS VERY IMPORTANT TO ME, I WOULD REALLY APPRECIATE IF YOU GIVE ME A HAND HERE.
Regards
about 10 years ago
Hi Federico–
Can you show the model config for your Car as well as for your Color? Also, can you show the response for your request for Colors, as well as the config for the combobox?
Thanks!
about 10 years ago
existdissolve, thank you very much for fast answer. Here is what I have:
Car Model (app/model/Car.js)
=====================
I’ve changed some fields names to those my PHP server is sending, like “StatusName” or “MakeCountry”, I supposed that this change wouldn’t affect the funcionality of the application since it’s just a name change, of course I’ve changed the name in the other files respectively.
/**
* Model representing a Car object
*/
Ext.define(‘CarTracker.model.Car’, {
extend: ‘CarTracker.model.Base’,
idProperty: ‘CarID’,
fields: [
// id field
{
name: ‘CarID’,
type: ‘int’,
useNull : true
},
// simple values
{
name: ‘Description’,
type: ‘string’
},
{
name: ‘Year’,
type: ‘int’
},
{
name: ‘ListPrice’,
type: ‘int’
},
{
name: ‘SalePrice’,
type: ‘int’
},
{
name: ‘AcquisitionDate’,
type: ‘date’,
dateWriteFormat: ‘Y-m-d’
},
{
name: ‘SaleDate’,
type: ‘date’,
dateWriteFormat: ‘Y-m-d’
},
{
name: ‘IsSold’,
type: ‘boolean’
},
// relational properties
{
name: ‘StatusName’,
type: ‘auto’
},
{
name: ‘MakeCountry’,
type: ‘auto’
},
{
name: ‘ModelName’,
type: ‘auto’
},
{
name: ‘CategoryName’,
type: ‘auto’
},
{
name: ‘SalesPeople’,
type: ‘auto’
},
{
name: ‘ColorID’,
type: ‘auto’,
useNull: true
},
{
name: ‘Features’,
type: ‘auto’
}
// decorated properties
{
name: ‘_Status’,
type: ‘string’,
persist: false
},
{
name: ‘_Make’,
type: ‘string’,
persist: false
},
{
name: ‘_Model’,
type: ‘string’,
persist: false
},
{
name: ‘_Category’,
type: ‘string’,
persist: false
},
{
name: ‘_Color’,
type: ‘string’,
persist: false
},
{
name: ‘_Features’,
type: ‘string’,
persist: false
},
{
name: ‘_SalesPeople’,
type: ‘string’,
persist: false
}
]
});
Color Model (app/model/Color.js)
=======================
This model is exactly the same as the one in your tutorial, I’m also showing the Base class from which the Color model is extending from (just in case).
/**
* Model representing a Color object
*/
Ext.define(‘CarTracker.model.option.Color’, {
extend: ‘CarTracker.model.option.Base’,
idProperty: ‘ColorID’,
fields: [
// id field
{
name: ‘ColorID’,
type: ‘int’,
useNull : true
}
]
});
/**
* Base {@link CarTracker.model.Base} from which all other “Option” models will extend
*/
Ext.define(‘CarTracker.model.option.Base’, {
extend: ‘CarTracker.model.Base’,
fields: [
// non-relational properties
{
name: ‘LongName’,
type: ‘string’
},
{
name: ‘ShortName’,
type: ‘string’
}
]
});
Colors RemoteComboBox Server Response
===============================
I’m using PHP on a virtual machine server, the IP of that server is 192.168.183.136 and I can access that IP from my local machine.
URL Generated by EXT:
———————————-
http://192.168.183.136/CarTracker/index.php?r=Color&_dc=1399652593958&query;=&page=1&start=0&limit=25&sort;=%5B{“property”:”LongName”,”direction”:”ASC”}]&callback=Ext.data.JsonP.callback3
JSON Response for Above URL
———————————————
I’ve validated this JSON in http://jsonlint.com/, and defined “registros” for my root property in the proxy file (root: ‘registros’).
{
“registros”: [
{
“ColorID”: “1”,
“LongName”: “Red”,
“ShortName”: “Red”,
“CreatedDate”: “2014-04-25 00:00:00”,
“Active”: “1”
},
{
“ColorID”: “2”,
“LongName”: “Blue”,
“ShortName”: “Blue”,
“CreatedDate”: “2014-04-25 00:00:00”,
“Active”: “1”
},
{
“ColorID”: “3”,
“LongName”: “Orange”,
“ShortName”: “Orange”,
“CreatedDate”: “2014-04-25 00:00:00”,
“Active”: “1”
},
{
“ColorID”: “4”,
“LongName”: “Acid green”,
“ShortName”: “Acid”,
“CreatedDate”: “2014-04-25 00:00:00”,
“Active”: “1”
},
{
“ColorID”: “5”,
“LongName”: “Aero”,
“ShortName”: “Aero”,
“CreatedDate”: “2014-04-25 00:00:00”,
“Active”: “1”
}
],
“total”: “5”
}
RemoteComboBox config in Detail tab
===========================
I’ve deleted other parts of the tab object fot simplicity.
/**
* Car Tab
*/
Ext.define(‘CarTracker.view.car.edit.tab.Detail’, {
extend: ‘Ext.panel.Panel’,
alias: ‘widget.car.edit.tab.detail’,
layout: ‘form’,
initComponent: function() {
var me = this;
Ext.applyIf(me, {
items: [
{
xtype: ‘fieldset’,
title: ‘Car Info’,
defaults: {
layout: ‘hbox’,
margins: ‘0 10 0 10’
},
items: [
…
{
xtype: ‘fieldcontainer’,
items: [
…
{
xtype: ‘ux.form.field.remotecombobox’,
name: ‘Color’,
fieldLabel: ‘Color’,
displayField: ‘LongName’,
valueField: ‘ColorID’,
store: {
type: ‘option.color’
},
plugins: [
{ ptype: ‘cleartrigger’ }
],
editable: false,
forceSelection: true
}
]
}
…
]
}
]
});
me.callParent(arguments);
}
});
THANK YOU AGAIN !!!!.
about 10 years ago
The problem looks to be between the config of your model and the color combobox. On your model, the color property is named “ColorID”, but on your combobox, the name of your field is “Color”. Since the controller is going to marry the keys of the form to the keys of your model, the value from “Color” never gets set on “ColorID”. I would rename one or the other so that they are the same, and it should work as expected, just as with SalesPeople.
Hope that helps!
about 10 years ago
YES !, IT REALLY HELPED !!!, THANK YOU !!!, now it’s solved !. You’re the man! !!!! hehehe. Now it works!.
I have another problem with the itemselectorfield, it doesn’t get the Features store loaded with values (actually it seems that the form doesn’t make the request to the server at all) but I guess its the same problem of the mixed names, I will check and bother you again if I fail. Thanks !!.
about 10 years ago
Hello,
I have a grid and a toolbar which has xtype:datefield.
How to configure the component listener for the event when the datefields is clicked.
dockedItems: [
{
xtype: ‘toolbar’,
dock: ‘top’,
ui: ‘footer’,
items: [
{
xtype: ‘datefield’,
anchor: ‘100%’,
itemId: ‘dateCal’,
name: ‘Filter’,
fieldLabel: ‘Filter’,
format: ‘F Y’,
value: new Date()
}
]
}
something like this ??
‘grid[xtype=calendargrid] datefield#dateCal’: {
click: this.datefieldClicked
}
Thanks for the input.
about 10 years ago
Hi Sanjeev–
What do you mean by “click”? Are you talking about when a mouse click occurs within the text portion of the date field, or when the trigger (calendar icon) is clicked?
about 10 years ago
Hello Joel,
Thanks for the reply.
I would like to trap the event when the user clicks the (calendar icon) and then select (click) on any date in the calender (confirming the selection)
about 10 years ago
Ok, cool. For the clicking of the calendar icon, I would try listening to the expand event of the date field’s underlying trigger field: datefield#dateCal: { expand:…} Here are the docs for that: http://docs.sencha.com/extjs/4.2.3/#!/api/Ext.form.field.Date-event-expand. Note: expand occurs after the picker is displayed, so if you need to capture the click *before* the expand occurs, you’ll have to use a different approach.
For the selection of the date, you can listen to the select event: datefield#dateCal: { select: … }. Here are the docs for that: http://docs.sencha.com/extjs/4.2.3/#!/api/Ext.form.field.Date-event-select
Hope that helps!