I have tried this different ways, but still can't get the filter to work. My ext app lets user to choose a single state from a combobox, and the grid below displays more data on that selected "value"=state.. On select, the combobox fires a function that filters the store of the grid and updates the store...
this is my store for the grid...
var store = Ext.create('Ext.data.Store', {
autoLoad: true,
id: 'OurData',
pageSize: 20,
pageNumber: 1,
remoteSort: true,
fields: [
{ name: 'States' },
{ name: 'FullName' },
{ name: 'Capital' },
{ name: 'Population' }
],
proxy: {
type: 'ajax',
url: 'GetState/getS',
reader: {
type: 'json',
root: 'myTable',
idProperty: 'States',
totalProperty: '#count'
}
}
});
store.loadPage(1);
this is my combobox
xtype: 'combo',
id: 'iccombo',
scope: this,
store: this.Combostore,
fieldLabel: 'Short State',
displayField: 'States',
valueField: 'States',
typeAhead: true,
triggerAction: 'all',
queryMode: 'remote',
name: 'State',
labelWidth: 125,
anchor: '95%',
listeners: {
scope: this,
select: this.fireFilter
}
and this is where the filter should take place...
fireFilter: function (value) {
// Get passed value
this.selectedC = value.getValue();
console.log('selectedValue: ' + this.selectedC);
// Clear existing filters
this.store.clearFilter(false);
// Build filter
var myfilter = Ext.create('Ext.util.Filter', {
scope: this,
filterFn: function (item) {
var fieldNames = item.fields.keys;
for (var j = 0; j < fieldNames.length; j++) {
var fieldName = fieldNames[j];
if (item.data[fieldName] != null) {
var stringVal = item.data[fieldName].toString();
if (stringVal != null && stringVal.toLowerCase().indexOf(value.toLowerCase()) != -1) {
return true;
}
}
}
return false;
}
});
// Apply filter to store
this.store.filter(myfilter);
}
when I run the code, it display all data in the grid, and on selection of combobox, it still shows the same data..
For some reason, the code never runs through the filterFn... because my console.log doesn't show up
this is what I got in firebug's response
_dc 1352902173425
filter [{"property":null,"value":null}]
limit 20
page 1
start 0
as you can clearly see, the selected 'value' is null, but my 'console.log' prints the value selected... I think the way I am getting the passed value and applying the filter is incorrect... can someone please take a look... thanks
UPDATE... I am able to get inside the function and my console.log shows all the fields... but once I get to the last if statement... I get this error
TypeError: value.toLowerCase is not a function
What am I doing wrong here? Thanks
In addition to dbrin's anwser I also can't understand why you are using remoteSort but not remoteFilter? You may also have a scope issue by using this.
Anyway I would recommend you to extend a new combo type so that you are also be able to clear your filter if you have the need to. Here is an extension I have written for my own use. Note that the filtering itself needs to be implemented in the onSearch method, which can be either a remote or a local sort.
Ext.define('Ext.ux.form.field.FilterCombo', {
extend: 'Ext.form.field.ComboBox',
alias: 'widget.filtercombo',
/**
* #cfg {string} recordField
* #required
* The fieldname of the record that contains the filtervalue
*/
/**
* #cfg {string} searchField
* #required
* The fieldname on which the filter should be applied
*/
/**
* #cfg {boolean} clearable
* Indicates if the clear trigger should be hidden. Defaults to <tt>true</tt>.
*/
clearable: true,
initComponent: function () {
var me = this;
if (me.clearable)
me.trigger2Cls = 'x-form-clear-trigger';
else
delete me.onTrigger2Click;
me.addEvents(
/**
* #event clear
*
* #param {Ext.ux.form.field.FilterCombo} FilterCombo The filtercombo that triggered the event
*/
'clear',
/**
* #event beforefilter
*
* #param {Ext.ux.form.field.FilterCombo} FilterCombo The filtercombo that triggered the event
* #param {String/Number/Boolean/Float/Date} value The value to filter by
* #param {string} field The field to filter on
*/
'beforefilter'
);
me.callParent(arguments);
// fetch the id the save way
var ident = me.getId();
me.on('select', function (me, rec) {
var value = rec[0].data[me.recordField],
field = me.searchField;
me.fireEvent('beforefilter', me, value, field)
me.onShowClearTrigger(true);
me.onSearch(value, field);
}, me);
me.on('afterrender', function () { me.onShowClearTrigger(); }, me);
},
/**
* #abstract onSearch
* running a search on the store that may be removed separately
* #param {String/Number/Boolean/Float/Date} val The value to search for
* #param {String} field The name of the Field to search on
*/
onSearch: Ext.emptyFn,
/**
* #abstract onFilterRemove
* removing filters from the the
* #param {Boolean} silent Identifies if the filter should be removed without reloading the store
*/
onClear: Ext.emptyFn,
onShowClearTrigger: function (show) {
var me = this;
if (!me.clearable)
return;
show = (Ext.isBoolean(show)) ? show : false;
if (show) {
me.triggerEl.each(function (el, c, i) {
if (i === 1) {
el.setWidth(el.originWidth, false);
el.setVisible(true);
me.active = true;
}
});
} else {
me.triggerEl.each(function (el, c, i) {
if (i === 1) {
el.originWidth = el.getWidth();
el.setWidth(0, false);
el.setVisible(false);
me.active = false;
}
});
}
// Version specific methods
if (Ext.lastRegisteredVersion.shortVersion > 407) {
me.updateLayout();
} else {
me.updateEditState();
}
},
/**
* #override onTrigger2Click
* eventhandler
*/
onTrigger2Click: function (args) {
this.clear();
},
/**
* #private clear
* clears the current search
*/
clear: function () {
var me = this;
if (!me.clearable)
return;
me.onClear(false);
me.clearValue();
me.onShowClearTrigger(false);
me.fireEvent('clear', me);
}
});
Here is an untested implementation of your combo. Please note that I cleaned up your filterFn but I didn't make any further check.
{
xtype: 'filtercombo',
id: 'iccombo',
scope: this,
store: this.Combostore,
fieldLabel: 'Short State',
displayField: 'States',
valueField: 'States',
typeAhead: true,
triggerAction: 'all',
queryMode: 'remote',
name: 'State',
labelWidth: 125,
anchor: '95%',
// begin new parts
recordField: 'States',
searchField: '',
clearable: false,
onSearch: function (me, value, field) {
// New part!
var store = Ext.StoreMgr.lookup('YourStoreIdName');
// Clear existing filters
store.clearFilter(false);
// Build filter
var myfilter = Ext.create('Ext.util.Filter', {
scope: this,
filterFn: function (item) {
var fieldNames = item.fields.keys,
fieldName, stringVal,
len = fieldNames.length,
j = 0;
for (; j < len; j++) {
fieldName = fieldNames[j];
if (item.data[fieldName] != null) {
stringVal = item.data[fieldName].toString();
if (stringVal != null && stringVal.toLowerCase().indexOf(value.toLowerCase()) != -1) {
return true;
}
}
}
return false;
}
});
// Apply filter to store
store.filter(myfilter);
}
}
I guess this should work too
var myfilter = Ext.create('Ext.util.Filter', {
scope: this,
filterFn: function (rec) {
var fieldValue = rec[this.fieldName];
if (fieldValue && fieldValue === this.value)
return true;
return false;
}
});
I set this before two vars to mark them as from a external scope.
i see 2 issues
store should have remoteFilter: true set
in JavaScript all variables declarations are picked out and hoisted to the beginning of the function. so any variables declared inside a loop should be taken out and declared at the top of the function. JS has no block scope (like Java).
Related
I defined a text area field in an extJS window as follows:
me.myTextArea = Ext.create({
xtype: 'textareafield',
width: 500,
height: 500,
editable: true,
selectOnFocus: false,
listeners: {
afterrender: function() {
this.focus(true);
let cursorPos = this.getValue().length;
this.selectText(cursorPos, cursorPos);
}
}
});
I added the text area field to a panel contained within a window, and I set the text area field as focus element. I prevented the text there to be selected after the textarea field's being rendered. I would like to add the following feature. On closing the window, I will be able to get the position the cursor has within the text area field. So far, my attemps at resolving the problem were withou success. I set up an alert as follows:
listeners: {
'close': function(me) {
alert(me.getCaretPos(cmp.myTextArea.getEl().getId()));
}
},
Now the function named "getCaretPos" is designed to get the cursor position. I did not invent the function, but I found in on the net. Haplessly, the function does not work, it always returns -1:
getCaretPos: function(id){
var el = document.getElementById(id);
var rng, ii=-1;
if(typeof el.selectionStart=="number") {
ii=el.selectionStart;
} else if (document.selection && el.createTextRange){
rng=document.selection.createRange();
rng.collapse(true);
rng.moveStart("character", -el.value.length);
ii=rng.text.length;
}
return ii;
}
Especially, I do not undertsand, why "el.selectionStart" is always undefined in the function. I would be very glad if somebody could help me in resolving this mystery.
In this FIDDLE, I have created a custometextarea using extend:Ext.form.field.TextArea and putted some custom method. I hope this will help/guide you to achieve your requirements.
I have checked this code is working in ExtJS 4.x and later.
CODE SNIPPET
Ext.application({
name: 'Fiddle',
launch: function () {
Ext.define('CustomeTextArea', {
extend: 'Ext.form.field.TextArea',
xtype: 'customtextarea',
inputTypeSelectionSupported: /text|password|search|tel|url/i,
selectDir: {
b: 'backward',
back: 'backward',
f: 'forward'
},
/*
* this event will call get the cursoe postion inside of field
* #return {NUMBER}
*/
getCaretPos: function () {
var dom = this.inputEl.dom,
pos, selection;
if (this.inputTypeSelectionSupported.test(dom.type)) {
pos = dom.selectionStart;
selection = (typeof pos !== 'number') && this.getTextSelection();
if (selection) {
pos = selection[0];
}
} else {
Ext.raise('Input type of "' + dom.type + '" does not support selectionStart');
}
return pos;
},
/*
* this event will call selectText event
* #params {NUMBER} pos
*/
setCaretPos: function (pos) {
this.selectText(pos, pos);
},
/*
* #params {NUMBER} start
* #params {NUMBER} end
* #params {STRING} direction to select it is optional
*/
selectText: function (start, end, direction) {
var me = this,
range,
dom = me.inputEl.dom,
len;
if (dom && me.inputTypeSelectionSupported.test(dom.type)) {
start = start || 0;
len = dom.value.length;
if (end === undefined) {
end = len;
}
direction = me.selectDir[direction] || direction || 'forward';
if (dom.setSelectionRange) {
dom.setSelectionRange(start, end, direction);
} else if (dom.createTextRange) {
range = dom.createTextRange();
range.moveStart('character', start);
range.moveEnd('character', -(len - end));
range.select();
}
} else if (!me.inputTypeSelectionSupported.test(dom.type)) {
Ext.raise('Input type of "' + dom.type + '" does not support setSelectionRange');
}
return me;
},
//This event will select the text inside of textfield/textarea
getTextSelection: function () {
var dom = this.inputEl.dom;
if (this.inputTypeSelectionSupported.test(dom.type)) {
return [
dom.selectionStart,
dom.selectionEnd,
dom.selectionDirection
];
} else {
Ext.raise('Input type of "' + this.dom.type + '" does not support selectionStart, selectionEnd and selectionDirection');
return [];
}
}
});
Ext.create('Ext.window.Window', {
title: 'cursor position',
height: 200,
width: 400,
layout: 'fit',
items: [{
xtype: 'customtextarea',
margin: 5,
grow: true,
name: 'message',
fieldLabel: 'Message',
labelAlign: 'top',
value: 'How can I retrieve the cursor position in an ExtJS text area field?',
anchor: '100%',
listeners: {
afterrender: function (cmp) {
Ext.defer(function () {
cmp.focus(false); //if you pass true then it will automatically select text inside of field
let cursorPos = cmp.getValue().length;
cmp.selectText(0, cursorPos - 5, 'b');
}, 50)
}
}
}],
listeners: {
beforeclose: function (win) {
var textArea = win.down('customtextarea'),
pos = textArea.getCaretPos();
Ext.Msg.alert('Success', `This is your cursor postion <b>${pos}</b>`)
}
}
}).show();
}
});
http://2gears.com/2011/05/combobox-editor-remote-and-renderer-for-extjs-editorgridpanel/comment-page-2/#comment-11050?
Please see the function.
I am not using the 'combocolumn' completely. However i have used the major components. Please advise if there is a possibility it should work. As of now the grid refuses to render the column itself and I am unable to pinpoint what the bug is as the code is simply the same?
I did this not to act smart :) - but to simplify and run it on 1 column before standardizing the component common to the project.
The COMBO RENDERING fn()
<code>
function ComboBoxRenderer(combo, gridId) {
var getValue = function (value) {
var idx = combo.store.find(combo.valueField, value);
var rec = combo.store.getAt(idx);
if (rec) {
return rec.get(combo.displayField);
}
return value;
};
return function (value) {
if (combo.store.getCount() === 0 && gridId) {
console.log(combo.store.getCount()+gridId);
combo.store.on(
'load',
function () {
var grid = Ext.getCmp(gridId);
if (grid) {
grid.getView().refresh();
}
},
{
single: true
}
);
return value;
}
return getValue(value);
};
// Ext.getCmp(gridId).getView().refresh();
}
</code>
Editor - in Grid Column Model
{
header: 'Plan Type',
dataIndex: 'plan_id',
editor: {
// allowBlank: 'false',
xtype: 'combobox',
queryMode: 'local',
store: planTypeStore,
displayField: 'plan_name',
valueField: 'plan_id'
}
,renderer: ComboBoxRenderer(this.editor, 'gridPanelId')
/*this is the docs based renderrer - which wont work */
//, renderer: function (val) {
// var rec = planTypeStore.findRecord('plan_id', val);
// return (rec !== null ? rec.get("plan_name") : '');
// }
I'm using selection Xtype and checkbox type property (CQ.form.Selection) to create a checkbox group in CQ5 (API docs at http://docs.adobe.com/docs/en/cq/5-6/widgets-api/index.html?class=CQ.form.Selection).
But I want to override setValue, getValue and validate functions of it in order to meet my requirement. How can I do that via JCR node declaration ?
many thanks and appreciate.
Not sure what you mean by "do that via JCR node declaration". But if you need to make a few additional steps with standard xtype you just need to create a custom xtype, which wraps the standard one, and use it.
Here is an example of JS-code that creates and registers new xtype (combines values of 2 different fields into a single value).
Ejst.CustomWidget = CQ.Ext.extend(CQ.form.CompositeField, {
/**
* #private
* #type CQ.Ext.form.TextField
*/
hiddenField: null,
/**
* #private
* #type CQ.Ext.form.ComboBox
*/
allowField: null,
/**
* #private
* #type CQ.Ext.form.TextField
*/
otherField: null,
constructor: function(config) {
config = config || { };
var defaults = {
"border": false,
"layout": "table",
"columns":2
};
config = CQ.Util.applyDefaults(config, defaults);
Ejst.CustomWidget.superclass.constructor.call(this, config);
},
// overriding CQ.Ext.Component#initComponent
initComponent: function() {
Ejst.CustomWidget.superclass.initComponent.call(this);
this.hiddenField = new CQ.Ext.form.Hidden({
name: this.name
});
this.add(this.hiddenField);
this.allowField = new CQ.form.Selection({
type:"select",
cls:"ejst-customwidget-1",
listeners: {
selectionchanged: {
scope:this,
fn: this.updateHidden
}
},
optionsProvider: this.optionsProvider
});
this.add(this.allowField);
this.otherField = new CQ.Ext.form.TextField({
cls:"ejst-customwidget-2",
listeners: {
change: {
scope:this,
fn:this.updateHidden
}
}
});
this.add(this.otherField);
},
// overriding CQ.form.CompositeField#processPath
processPath: function(path) {
console.log("CustomWidget#processPath", path);
this.allowField.processPath(path);
},
// overriding CQ.form.CompositeField#processRecord
processRecord: function(record, path) {
console.log("CustomWidget#processRecord", path, record);
this.allowField.processRecord(record, path);
},
// overriding CQ.form.CompositeField#setValue
setValue: function(value) {
var parts = value.split("/");
this.allowField.setValue(parts[0]);
this.otherField.setValue(parts[1]);
this.hiddenField.setValue(value);
},
// overriding CQ.form.CompositeField#getValue
getValue: function() {
return this.getRawValue();
},
// overriding CQ.form.CompositeField#getRawValue
getRawValue: function() {
if (!this.allowField) {
return null;
}
return this.allowField.getValue() + "/" +
this.otherField.getValue();
},
// private
updateHidden: function() {
this.hiddenField.setValue(this.getValue());
}
});
// register xtype
CQ.Ext.reg('ejstcustom', Ejst.CustomWidget);
Creating custom xtype in AEM6's touch UI
I have a combobox in ExtJS4 with this initial config
xtype: 'combobox',
name: 'myCombo',
store: 'MyStore',
editable: false,
displayField: 'name',
valueField: 'id',
emptyText: 'Select an Option'
I don't know if there is an easy way to tell the combo to add an option so the user can deselect the combo (first he select an option and then he want to not select anything... so he wants to return to "Select an Option")
I solved this before by adding an extra option to the fetched data so I can simulate to have the "Select an Option" as a valid option in the combo but I think there should be a better way.
You do not need any new design or graphics or any complex extensions. ExtJS has is all out of the box.
You should be able to use this:
Ext.define('Ext.ux.form.field.ClearCombo', {
extend: 'Ext.form.field.ComboBox',
alias: 'widget.clearcombo',
trigger2Cls: 'x-form-clear-trigger',
initComponent: function () {
var me = this;
me.addEvents(
/**
* #event beforeclear
*
* #param {FilterCombo} FilterCombo The filtercombo that triggered the event
*/
'beforeclear',
/**
* #event beforeclear
*
* #param {FilterCombo} FilterCombo The filtercombo that triggered the event
*/
'clear'
);
me.callParent(arguments);
me.on('specialkey', this.onSpecialKeyDown, me);
me.on('select', function (me, rec) {
me.onShowClearTrigger(true);
}, me);
me.on('afterrender', function () { me.onShowClearTrigger(false); }, me);
},
/**
* #private onSpecialKeyDown
* eventhandler for special keys
*/
onSpecialKeyDown: function (obj, e, opt) {
if ( e.getKey() == e.ESC )
{
this.clear();
}
},
onShowClearTrigger: function (show) {
var me = this;
if (show) {
me.triggerEl.each(function (el, c, i) {
if (i === 1) {
el.setWidth(el.originWidth, false);
el.setVisible(true);
me.active = true;
}
});
} else {
me.triggerEl.each(function (el, c, i) {
if (i === 1) {
el.originWidth = el.getWidth();
el.setWidth(0, false);
el.setVisible(false);
me.active = false;
}
});
}
// ToDo -> Version specific methods
if (Ext.lastRegisteredVersion.shortVersion > 407) {
me.updateLayout();
} else {
me.updateEditState();
}
},
/**
* #override onTrigger2Click
* eventhandler
*/
onTrigger2Click: function (args) {
this.clear();
},
/**
* #private clear
* clears the current search
*/
clear: function () {
var me = this;
me.fireEvent('beforeclear', me);
me.clearValue();
me.onShowClearTrigger(false);
me.fireEvent('clear', me);
}
});
Here's a JSFiddle
And a JSFiddle without a default combo.
Note that both examples don't require any new graphic or style
To the best of my knowledge there is no better way of doing this. Although others may have a hack to do what you do by means of a plugin.
If I'm perfectly honest, no selection option in comboboxes is somewhat of a user interface paradox - users are required to select 'no selection'. A blank option is not very clear - looks a bit like a bug.
From a user interface perspective I think the preferred solution to this scenario is to have a button next to the combobox saying 'clear selection' (or just one with an X icon). Once you manage to put a button there, you can just call clearValue().
HI,
I have a pretty simple Ext JS combobox that I'm just trying to bind to an array.
Here is the config for the combo:
BPM.configs.ViewsCombo = {
xtype: 'combo',
emptyText: 'Select View',
disableKeyFilter: true,
triggerAction: 'all',
displayField: 'name',
mode: 'remote',
render: function(combo) {
this.store.load();
}
},
store: new Ext.data.SimpleStore({
proxy: new Ext.data.HttpProxy({
url: '/Service.svc/GetUserViewNames',
method: 'POST'
}),
root: 'GetUserViewNamesResult',
fields: ['name']
})
};
Here is response/json from Ajax call:
{"GetUserViewNamesResult":["something","tree"]}
But when i go to view the combo items, all I see is the letter 's' and 't' in the list.
What gives ? is my returning array in the wrong format ?
Thanks so much.
well i figured out that the result needs to look like this :
{"GetUserViewNamesResult":[["something"],["tree"]]}.
which kinda sucks because now I have to change how my server side objects serialize :(
use this to change your array in to the required format
for ( var i = 0, c = cars.length; i < c; i++ ) {
cars[i] = [cars[i]];
}
referring to this how-to-bind-array-to-arrystore-in-order-to-populate-combo-in-extjs
Yes ExtJs still does not have a reader capable of dealing with lists of strings. On the server side (at least in Java, C#, etc.) this is often what you'll get when marshalling ENUM types.
I had to write my own class which is used in ExtJs 4.1 MVC style:
/**
* This extends basic reader and is used for converting lists of enums (e.g. ['a', 'b', 'c']) into lists of objects:
* [ {name: 'a'}, {name:'b'}, {name:'c'}]. All models using this type of reader must have a single field called name. Or you can
* pass a config option call 'fieldName' that will be used.
*
* This assumes that the server returns a standard response in the form:
* { result: {...., "someEnum" : ['a', 'b', 'c']},
* total: 10,
* success: true,
* msg: 'some message'
* }
*/
Ext.define('MY.store.EnumReader', {
extend: 'Ext.data.reader.Json',
alias: 'reader.enum',
//we find the Enum value which should be a list of strings and use the 'name' property
getData: function(data) {
var me = this;
//console.dir(data);
var prop = Ext.isEmpty(this.fieldName) ? 'name' : this.fieldName;
console.log('Using the model property: \''+ prop +'\' to set each enum item in the array');
try {
var enumArray = me.getRoot(data);
//console.dir(enumArray);
if (!Ext.isArray(enumArray)){
console.error("expecting array of string (i.e. enum)");
throw new Exception('not an array of strings - enum');
}
var enumToObjArray = Array.map(enumArray, function(item){
var obj = {};
obj[prop] = item;
return obj;
}
);
//console.dir(enumToObjArray);
var nodes = me.root.split('.');
var target = data;
var temp = "data";
Array.forEach(nodes, function(item, index, allItems){
temp += "['" + item + "']";
});
temp += " = enumToObjArray";
//console.log('****************' + temp + '*****************');
//evil 'eval' method. What other choice do we have?
eval(temp);
//console.dir(data);
return data;
}
catch(ex){
console.error('coudln\'t parse json response: ' + response.responseText);
return this.callParent(response);
}
}
}, function() {
console.log(this.getName() + ' defined');
});
Then, if you want to use this type of reader in your store you add:
requires: ['My.store.EnumReader'],
...
proxy: {
type: 'ajax',
url: ...
reader: {
type: 'enum',