本当に必要なものがsetDisplayField
メソッドである場合、実際にはメソッドが存在し、呼び出されsetRawValue
ます。
ただし、それだけで問題を解決できるかどうかは疑わしいです。確かに、それを使用して正しい表示を得ることができますが、価値が失われます...
var legacyData = [
{id: 'd', name: "Doll"}
];
var combo = Ext.widget({
renderTo: Ext.getBody()
,xtype: 'combo'
,fieldLabel: "Regular combo"
,valueField: 'id'
,displayField: 'name'
,queryMode: 'local'
,value: 'd'
,store: {
fields: ['id', 'name']
,data: []
,autoLoad: true
,listeners: {
load: function() {
var value = combo.value,
valueField = combo.valueField,
displayField = combo.displayField,
o = Ext.Array.findBy(legacyData, function(data) {
return data[valueField] === value;
});
if (o) {
combo.setRawValue(o[displayField]);
}
// Fail! This will alert 'Doll' instead of 'd'.
//
// If you set forceSelection to true, the value will be cleared
// all together.
//
alert(combo.getValue());
}
}
}
});
dbring が示唆するように、履歴レコードをコンボのストアに追加すると、ユーザーに履歴レコードを選択させたい場合にのみ、実際に問題が解決します。私の理解では、そうではありません。そうでなければ、そもそも問題がないからです...
コンボ クラスのコードを調べたところ、必要なことを達成するための最善の方法はメソッドをオーバーライドすることだと思いますfindRecord
。このメソッドによって返されるレコードは、コンボ ボックスの表示とその値の両方を更新するために使用されます。このレコードがコンボのストアに存在するかどうかに関係なく、コンボで選択できる値に影響を与えることはありません。完全!
コンボにa を追加したとしましょう。これを行うためにメソッドをlegacyStore
オーバーライドする方法は次のfindRecord
とおりです。
findRecord: function(field, value) {
var ls = this.legacyStore,
record = this.callParent(arguments);
if (record) {
// found in current store
return record;
} else if (ls) {
// try legacy value store
var idx = ls.findExact(field, value);
return idx !== -1 ? ls.getAt(idx) : false;
} else {
return false;
}
}
さて、対処すべき問題がさらにいくつか残っています...
findRecord
まず、呼び出された時点でレガシー ストアがロードされていない場合はどうなるでしょうか。それはうまくいきません。通常の形式ではsetValue
、ストアがロードされていない場合、コンボ ボックスはメソッドを中止し、メソッドからコールバックしますonLoad
。従来のストアでこの動作を再現するコードを追加する必要があります。
2 番目の問題はもっと些細なことですが、コンボが履歴値で初期化され、ユーザーがコンボの値を変更したものの、その選択を後悔した場合はどうなるでしょうか。フォーム全体をキャンセルして再入力しないと、元の値を取り戻すことはできません。この欲求不満を解消するために、コンボの動作を変更して、最後の値ではなく、クリア時に元の値を復元することができます。これが望ましいかどうかわからないので、オプションとして追加します。
これらすべてを考慮して、特定のユースケースを処理する「ux」の完全なコードを次に示します。
forceSelection
が に設定されtrue
、ストアが で作成された場合、コンボ ボックスの初期値を強制終了するバグのグローバルな修正が含まれていることに注意してくださいautoLoad: true
。詳細については、この他の質問を参照してください。または、グローバル修正をスキップして、loading: true
を使用するコンボのすべてのストアとレガシー ストアに追加することもできますforceSelection: true
。
/**
* Fixes the bug with autoLoad: true and forceSelection: true.
*
* See: https://stackoverflow.com/a/14440479/1387519
*/
Ext.define('Ext.ux.fix.data.Store.AutoLoadLoadingState', {
override: 'Ext.data.Store'
,constructor: function() {
this.callParent(arguments);
if (this.autoLoad) {
this.loading = true;
}
}
});
/**
* A combo box with support for legacy values.
*
* Legacy values will be displayed if their value is assigned to the
* combo, but they won't be selectable (nor queryable) by the user.
*/
Ext.define('Ext.ux.form.field.LegacyValueCombo', {
extend: 'Ext.form.field.ComboBox'
,alias: 'widget.legacyvaluecombo'
,requires: [
'Ext.ux.fix.data.Store.AutoLoadLoadingState'
]
/**
* The store that provides legacy values.
*
* @cfg {Ext.data.Store/Object/String}
*/
,legacyStore: undefined
/**
* If `true` and {@link #forceSelection} is also `true`, the
* {@link #originalValue original value} of the field will be restored when
* the field is cleared, instead of the last value.
*
* This may be useful considering that if the combo original value is a
* legacy one, and the user changes this value, they will have no other
* means to get it back afterward.
*
* @cfg {Boolean}
*/
,restoreOriginalValue: true
/**
* Ensure the legacy store is a store instance before initializing the
* combo box.
*/
,initComponent: function() {
var legacyStore = this.legacyStore;
if (legacyStore) {
this.legacyStore = Ext.data.StoreManager.lookup(legacyStore);
}
this.callParent(arguments);
}
/**
* Overridden to return a legacy record if the value is not found in the
* regular store.
*
* This method will only work if both stores have been loaded.
*/
,findRecord: function(field, value) {
var ls = this.legacyStore,
record = this.callParent(arguments);
if (record) {
// found in current store
return record;
} else if (ls) {
// try legacy value store
var idx = ls.findExact(field, value);
return idx !== -1 ? ls.getAt(idx) : false;
} else {
return false;
}
}
/**
* This method is overridden to support the case where the legacy store
* loads after the regular store.
*/
,setValue: function(value) {
var store = this.store,
legacyStore = this.legacyStore;
if (legacyStore) {
// If the legacy store has not been loaded, trigger it.
if (!legacyStore.lastOptions) {
if (!legacyStore.loading) {
legacyStore.load();
}
}
// Legacy store is loading, we must wait for it to be done before
// processing the value.
if (legacyStore.loading) {
// This is done in the parent setValue method, but we are not
// sure it will be called in time so we must replicate this
// code here.
this.value = value;
this.setHiddenValue(value);
// If the regular store is loading, then this method will be
// called again when it is done. We will see at this time if we
// still have to wait for the legacy store. In the other case,
// we have to simulate a call to onLoad when the legacy store
// has loaded.
if (!store.loading) {
legacyStore.on({
single: true
,scope: this
,load: function(legacyStore, records, success) {
this.onLoad(store, store.getRange(), success);
}
});
}
}
// Legacy store is loaded, that's OK to continue.
else {
this.callParent(arguments);
// Implements restoreOriginalValue.
if (this.restoreOriginalValue) {
this.lastSelection = this.originalValue;
}
}
// setValue must return this
return this;
}
// No legacy store, just defer to the super method.
else {
return this.callParent(arguments);
}
}
});
最後に、この ux が同期ストアと非同期ストアのすべての組み合わせで機能することを示す多くの例を含むライブ デモforceSelection
がありますsetRawValue
。