I'm attemtping to create a custom ComboBox control. The control will have a button on either side of the ComboBox itself, something like this:
[<-] [ --------------- V ] [->]
These buttons will be "next" and "previous" which will allow the user to quickly toggle between options in the dropdown (moving down and up respectively).
I've tried overriding the defaultAutoCreate config of the ComboBox, but I'm getting some odd behavior. Here's what I've tried so far: (this is all inside of a custom class extending ComboBox)
initComponent: function () {
// default ComboBox structure
var comboStructure = {
tag: 'span',
children: [
{ tag: 'a', cls: 'nav prev', html: 'Previous' },
{ tag: 'input', type: 'text', size: '24', autocomplete: 'off' },
{ tag: 'a', cls: 'nav next', html: 'Next' }
]
};
console.log(comboStructure);
var config = {
triggerAction: 'all',
lazyRender: true,
mode: 'local',
store: new Ext.data.ArrayStore({
fields: [
'myId',
'displayText'
],
data: [[1, 'Banner #1'], [2, 'Banner #2']]
}),
valueField: 'myId',
displayField: 'displayText',
defaultAutoCreate: comboStructure
};
Ext.apply(this, config);
IbwUi.controls.PreviewBannerSelectDropdown.superclass.initComponent(this);
}
The 2nd "child" of the comboStructure is actually the default value that gets initialized for ComboBox, I looked at the source code. The code works without error, but there's some odd behavior with the rendering of the combo itself, see here:
Any ideas on how to add my custom elements around the ComboBox when it renders?
Don't do it like this. Instead put all three components into a container class that implements the logic you need.
Related
Binding model property to a form field is pretty easy in ExtJS:
// ...skipped everything until fields config for brevity
}, {
xtype: 'textfield',
bind: '{modelInstance.someField}'
}, { // ...
In this case, modelInstance string field someField will be synchronized to textbox value, thanks to two way binding. And that is great.
What I want to achieve is to get same kind of behavior in the case when model field is not a string but an array. This is the model:
Ext.define('namespace.model.CustomModel', {
fields: ['easyField', {
name: 'hardField',
type: 'auto' // This will be an array during runtime
}],
idProperty: 'easyField'
});
I would like to do something like this:
// ...skipped everything until fields config for brevity,
// assume that viewmodel and everything else are set up as expected
}, {
xtype: 'textfield',
bind: '{modelInstance.easyField}'
}, {
xtype: 'gridfield',
bind: {
gridArray: '{modelInstance.hardField}'
}
}, { // ...
Understandably, I want gridfield component to extend Ext.grid.Panel and synchronize its store data to modelInstance field hardField.
Currently I have this:
Ext.define('namespace.form.field.GridField', {
extends: 'Ext.grid.Panel',
xtype: 'gridfield',
// skip requires for brevity
viewModel: {
stores: {
gridFieldItems: {
type: 'gridfielditems' // Simple in memory store
}
},
data: {
}
},
store: '{gridFieldItems}',
// This config enables binding to take place as it creates getters and setters,
// gridArray is set initially to '{modelInstance.hardField}' as expected
config: {
gridArray: null
},
// This is necessary for this grid-field component to update
// '{modelInstance.hardField}' back in the owners viewModel.
publishes: {
gridArray: true
},
// ???
// bind: {
// gridArray: bind gridArray to store data somehow?
// }
});
Here's the problem:
how do I inject existing modelInstance.hardField array as gridFieldItems store initial data,
how do I bind gridArray config to store data so that it is updates as we go along cruding the grid,
do all of these in an elegant MVVM way without writing a bunch of listeners trying to force syncing between JS objects.
Please provide tested solution which is known to work, I already tried a lot of different ways myself, but without success so far.
Here is a working fiddle to achieve this binding. The easy way is to bind the array field with "data" attribute of a store.
A good suggestion on the work you've done is to avoid defining a viewmodel inside a generic component (gridfield) but use viewmodels only on you application specific views.
On you generic component you should define only configuration attributes with setter/getter/update logics in order to be able to use them with bind. In this case there is no need of custom properties as the store is enough.
edit
To avoid the "easy binding" you can implement the set/get logic of the array in your girdfield component. Fore example using the "updateGridArray" method called by the setter and the "datachanged" event of the store.
The fiddle is updated and the example girdfield uses the cell editing plugin to show the 2-way binding.
fiddle: https://fiddle.sencha.com/#view/editor&fiddle/2a3b
Ext.define('Fiddle.model.CustomModel', {
extend: 'Ext.data.Model',
fields: ['easyField', {
name: 'hardField',
type: 'auto' // This will be an array during runtime
}],
idProperty: 'easyField'
});
Ext.define('Fiddle.fiddleview.CustomViewModel', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.fiddleview',
data: {
currentModel: null
}
});
Ext.define('Fiddle.form.field.GridField', {
extend: 'Ext.grid.Panel',
xtype: 'gridfield',
config: {
gridArray: null
},
publishes: [
'selection',
'gridArray'
],
selModel: 'cellmodel',
plugins: {
cellediting: {
clicksToEdit: 1
}
},
columns: [{
text: 'Column 1',
flex: 1,
editor: true,
dataIndex: 'field1'
}],
initComponent: function () {
this.store = {
fields: ['field1'],
data: [],
listeners: {
scope: this,
datachanged: function (store) {
this.setGridArray(store.getRange().map(function (record) {
return record.getData();
}));
}
}
};
this.callParent();
},
updateGridArray: function (gridArray) {
this.getStore().suspendEvent('datachanged');
if (Ext.isEmpty(gridArray)) {
this.getStore().removeAll();
} else {
this.getStore().loadData(gridArray);
}
this.getStore().resumeEvent('datachanged');
}
});
var myView = Ext.create('Ext.container.Container', {
renderTo: Ext.getBody(),
viewModel: 'fiddleview',
items: [{
xtype: 'gridfield',
bind: {
gridArray: '{currentModel.hardField}'
}
}]
});
// Bind on model's array to check two-way update
// It will execute also on cell edit
myView.getViewModel().bind('{currentModel.hardField}', function (value) {
window.alert('model\'s array changed');
});
// The binding is now active, when the "currentModel" in the viewmodel changes, grid rows are refresched
myView.getViewModel().set('currentModel', new Fiddle.model.CustomModel({
hardField: [{
field1: 'value1'
}]
}));
// Also when data changes in the "currentModel"
myView.getViewModel().get('currentModel').get('hardField').push({
field1: 'value2'
});
I'm trying to edit an open source program (and learn Extjs meanwhile) and I encountered a problem about models. I don't want to put code here since it is too long but I can provide if necessary.
So I got a a class which extends Ext.form.Panel and model "PartModel" assigned to it. This model has a string field called "partNumber" along with many other fields.
In this panel I want to choose a part number from a combobox from predefined values at database and assign it to "partNumber".
The problem is I want to assign value that is "displayed" in the combobox.(Not one of store fields, I'm using a custom XTemplate for it)
How can I do it?
Edit: Adding combobox code. I thought adding "dataIndex: 'partNumber'" would be sufficient to do job but this code isn't working at all. I can see Part Numbers strings from combobox but when I choose one and hit save button it doesn't save. (There are many other fields working well with that save button maybe I just need to add another button to save part number?)
{
xtype: 'combobox',
dataIndex: 'partNumber',
fieldLabel: "Part Number",
labelWidth: 150,
flex: 1,
store:{
xtype: 'store',
autoLoad: true,
model: 'PartGroupsClasses',
proxy: getPartGC()},
queryMode: 'local',
renderTo:Ext.getBody(),
tpl:Ext.create('Ext.XTemplate','<tpl for="."><div class="x-boundlist-item">{code}-{descr}-{ccode}-{cdescr}</div></tpl>'),
displayTpl:Ext.create('Ext.XTemplate','<tpl for=".">{code}{descr}{ccode}{cdescr}</tpl>')
}
Edit2: Figured out save button is basically calling following function.
Ext.override(Ext.data.Model, {
setDataWithAssociations: function(data) {
for (var i in data) {
if (this.fields.containsKey(i)) {
this.set(i, data[i]);
}
if (this.associations.containsKey(i)) {
var store = this[i]();
store.add(data[i]);
}
}
}
});
I would do something like this... in your combobox's model, add an extra field that makes use of the convert function to create your displayValue, and then in your combobox, just use that value for your displayValue property.
Model
Ext.define('MyComboModel', {
extend: 'Ext.data.Model',
fields: [
{
name: 'code',
type: 'string'
},
{
name: 'desc',
type: 'string'
},
{
name: 'ccode',
type: 'string'
},
{
name: 'cdesc',
type: 'string'
},
{
name: 'displayValue',
type: 'string',
convert: function(value, record) {
return record.get('code') +
record.get('desc') +
record.get('ccode') +
record.get('cdesc');
}
}
]
});
Combo
xtype: 'combobox',
name: 'Field2',
valueField: 'displayValue',
displayField: 'displayValue',
fieldLabel: 'Field2',
queryMode: 'local',
Full example.
I dont thing your question is clear enough for a clear answer...
I am unclear on your objective but if you want to have something display on store and behind it have the value on the file please take a look see if this examples helps
Store
this.data = Ext.create('Ext.data.JsonStore', {fields: ['id', 'data'],
data: [{id: 1, data: 'data1'},
{id: 2, data: 'data2'},
{id: 3, data: 'data3'},
{id: 4, data: 'data4'}]});
Combo
xtype:'Combobox',
name:'wtv',
displayField: 'data',
valueField: 'id'
It will display the combo with data but if you get the combo with the selection path and do for example
Selector
refs: [{
ref:Combo
selector:'Panel Combobox[name=wtv]'
}]
Later you can do something like
Panel.getCombo().getValue() and it will not
give you back the displayed field (Data) but it will give the id.
Sorry for bad formating! Hope it helps
This is a 2-part question:
Primary Question: Say I have a combobox (code below) that is pulling from a store. Say I have an array that I want to filter the data by so that only certain values actually show up in the dropdown. I can't seem to find the parameter that will do that... Is there a simple way to do this?
Secondary question: I also need to be able to disable and reenable items within the drop down based on actions of the user after the dropdown is created. Is there a function that causes items to be reenabled/disabled within the dropdown?
Note: By disabled I mean 'not present' aka removed in the dom, but still present in the non-filtered store.
{
xtype: 'combobox',
anchor: '100%',
name: 'Permission_id',
fieldLabel: 'Permissions',
hideLabel: false,
displayField: 'Name',
forceSelection: true,
store: 'PermissionStore',
typeAhead: true,
valueField: 'id',
valueNotFoundText: 'Add Permission'
}
I think all you need is work with filters. If you want to display only certain values in the dropdown, the only thing you have to do is filter the store and if what yo have is an array, you can use an ArrayStore for that.
About your second question, if you filter your store, original values are not lost, they are save internally as an snapshot then when you clear your filters, old values are displayed again in the dropdonlist.
Please take a look at this working example: http://jsfiddle.net/lontivero/Mz6S4/1/
First answer: the method is .filter(). For example:
var store = Ext.create('Ext.data.ArrayStore', {
fields: ['Id', 'Name'],
data: [
[ 1, 'Lucas' ],
[ 2, 'Pablo' ],
[ 3, 'Francisco' ]
]
});
Ext.create('Ext.form.field.ComboBox', {
fieldLabel: 'Permissions',
displayField: 'Name',
store: store,
valueField: 'Id',
renderTo: Ext.getBody()
});
//Hide Pablo in the dropdownlist
store.filter([
{filterFn: function(record) { return record.get('Name') !== 'Pablo'; }}
]);
// uncomment this to reenable hidden records (to display Pablo again)
//store.clearFilter();
Second answer: clear the store's filters
As lontivero said, filters solve your issue:
primary: array can contain data but filter will hide it from the dropdown
secondary: filters can be changed to hide and show in the dropdown
Your remaining problem, then, is how to change the filter from non-Ext code. Here you can use the fact that Ext is just javascript and can be called from any other javascript that has nothing to do with Ext.
So:
Add some functions that apply the filters to add and remove, in a place/scope that is accessible to the HTML DOM
Add them to the onclick handlers of the (plain HTML) buttons
The trick is really to access the store by using the Ext lookup by id.
So if the following code (extending on lontivero's jsfiddle) is directly in a js file (or in a script tag), it does what you're asking for:
(jsfiddle: http://jsfiddle.net/mCv6A/)
// functions that do the filtering
var noPablo = function(record) { return record.get('Name') !== 'Pablo' }
var noJames = function(record) { return record.get('Name') !== 'James' }
// the combination of functions we'll use
var withoutJamesFilter = [{ filterFn: noPablo }, { filterFn: noJames }]
var withJamesFilter = [{ filterFn: noPablo }]
function addJames()
{
var store = Ext.getStore('people')
store.clearFilter()
store.filter(withJamesFilter)
}
function delJames()
{
var store = Ext.getStore('people')
store.clearFilter()
store.filter(withoutJamesFilter)
}
Ext.onReady(function()
{
var store = Ext.create('Ext.data.ArrayStore', {
id: 'people',
fields: ['Id', 'Name'],
data: [
[ 1, 'Lucas' ],
[ 2, 'Pablo' ],
[ 3, 'Francisco' ],
[ 4 , 'James' ]
]
})
Ext.create('Ext.form.field.ComboBox', {
fieldLabel: 'Permissions',
displayField: 'Name',
store: store,
valueField: 'Id',
renderTo: Ext.getBody()
})
// start without james in list
store.filter(withoutJamesFilter)
})
Things to consider when using it for real (rather than in a simplified example):
wrapping addJames, delJames and the variables (noPablo, noJames etc) they use in an object or immediate function so the variables don't clutter the global (window) scope
rewriting the filter variables to better share implementation (something like a function that takes a list of names and generates a filter array or filter function that filters out those names would be sensible)
I define a combobox like this
{
xtype: 'combobox',
emptyText: 'Functions'
store: [ 'none' ]
}
then, on some event the store should load new data, so I get the store from the combobox and try this:
oFunctionStore.loadData( ['dothis', 'dothat', 'dosomething' ] );
but after this, the combobox has a dropdown without any visible content, just tiny blank lines.
// Change this...
oFunctionStore.loadData( ['dothis', 'dothat', 'dosomething' ] );
// to this...
oFunctionStore.loadData( [ [ 'dothis' ], [ 'dothat' ], [ 'dosomething' ] ] );
The combobox implicitly creates an Ext.data.ArrayStore, which will convert arrays into models.
The data parameter passed to
loadData is expected to be either an array of models, or an
array of objects than can be converted to models (in this case, an
array of arrays).
On the initial store load, the original array was converted to [ [ 'none' ] ] behind the scenes.
See an example here
carStore - any store for main combo.
carModelStore - store, which should be depended on selection in the carStore - based combo box
var carModelStore = new Ext.data.Store({
reader: new Ext.data.JsonReader({
fields: ['id', 'car-model'],
root: 'rows'
}),
storeId: 'car-model-store',
proxy: new Ext.data.HttpProxy({
url: 'carmodeldataprovider.json?car-name=lamborghini'
}),
autoLoad: true
});
{ xtype: 'combo', name: 'car-name', fieldLabel: 'Car', mode: 'local', store: carStore, triggerAction: 'all',
listeners: {
select: function(combo, records, eOpts){
var carName = records.get('car-name'); // the element selected in combo
var carModelStore = Ext.StoreMgr.lookup("car-model-store");
carModelStore.proxy.setUrl('carmodeldataprovider.json?car-name=' + carName, false);
carModelStore.reload();
}
}
}
I'd like to know a way of how to update or reload the list values of a ExtJs ComboBox. For instance, I have a some checkboxes. These checkboxes determine what values the ComboBox should have. So, after selecting some of those, I click the drowndown list (combobox) to see the values.
In short, how can I change the values (store) of a ComboBox already has.
Hope someone can help me
Thanks
I've been using this undocumented ExtJs API function to change the store at runtime:
mycombobox.bindStore(newStore);
as stated by the support team in http://www.sencha.com/forum/showthread.php?40749-Change-Combobox-store-data-update.
Edit: If I want to put the new data when the store is populated, I do something like this
storeMyStore = new Ext.data.Store({
...
listeners: {
load: function(this, records, options) {
cbMyCombo.bindStore( storeMyStore );
}
}
});
It goes a little something like this
{
xtype: 'checkbox',
//configs
listeners : {
checked : function (checkbox, checkedBool) {
var yourCombo = Ext.getCmp(yourComboID);
//I'm not sure what params you will need to reload the comboBox from your
// service but hopfully this will give the jist of things. . .
yourCombo.store.reload(
{
params:
{yourParam : checkedBool},
{yourRowID : rowID}
});
}
}
Here I making a combobox that is updated after a selection is made on another ComboBox.
Basically, the final user can use the two comboboxes to select a main category and a sub-category, which is based on the selection made on the first combobox.
This is the store for First comboBox:
Ext.define("StoreSubject", {
extend: "Ext.data.Model",
fields: [
{
name: 'i_Id'
},
{
name: 's_Value'
}
]
});
var StoreSubject = Ext.create('Ext.data.JsonStore', {
model: 'StoreSubject',
proxy: {
type: 'ajax',
url: '../General/AdministrationDefaultXMLDOM.aspx?qid=15',
reader: {
type: 'json'
}
}
});
StoreSubject.load();
This is the store for Second comboBox:
Ext.define("StoreLanguageGroup", {
extend: "Ext.data.Model",
fields: [
{
name: 'i_Id'
},
{
name: 's_Value'
}
]
});
var StoreLanguageGroup = Ext.create('Ext.data.JsonStore', {
model: 'StoreLanguageGroup',
proxy: {
type: 'ajax',
url: '../General/AdministrationDefaultXMLDOM.aspx?qid=16',
reader: {
type: 'json'
}
}
});
My code for Comobox is looks like this..
First ComboBox :
var cmbSubjectName = Ext.create('Ext.form.field.ComboBox', {
id: 'cmbSubjectName',
fieldLabel: '<b>Subject</b>',
name: 'cmbSubjectName',
valueField: 's_Value',
displayField: 's_Value',
allowBlank: false,
anchor: '80%',
labelWidth: 150,
emptyText: '[--Choose--]',
minChars: 0,
store: StoreSubject,
queryMode: 'local',
typeAhead: true,
listeners: {
'select': {
fn: function (combo, value) {
var strSubjectName = Ext.getCmp('cmbSubjectName').getValue();
Ext.getCmp('cmbLanguageType').clearValue();
Ext.getCmp('cmbLanguageType').getStore().load({
params: {
SubjectName: strSubjectName
}
});
}
}
},
});
This code is used to call and override the combox store (Impotent otherwise it will keep on loading )
Ext.override(Ext.LoadMask, {
onHide: function () {
this.callParent();
}
});
//---------------------------
Second ComboBox :
var cmbLanguageType = Ext.create('Ext.form.field.ComboBox', {
id: 'cmbLanguageType',
fieldLabel: '<b>Language</b>',
multipleSelect: false,
name: 'cmbLanguageType',
valueField: 's_Value',
displayField: 's_Value',
allowBlank: false,
anchor: '80%',
labelWidth: 150,
emptyText: '[--Choose--]',
minChars: 0,
store: StoreLanguageGroup,
queryMode: 'local',
typeAhead: true,
});
Hope this will helps you.. and Please rate my answer
In an event listener on the checkboxes, get a reference to the store that your ComboBox is reading from. Then invoke its add or remove functions to update the data in the store based on what check box is checked Once the updates are complete, invoke a doLayout() on the ComboBox component, it should re-render itself based on the current state of the store.
Although I think there is a way to have it automatically update any time the store updates -- I haven't used that yet.