Related
I have a store configured with memory proxy with enablePaging: true. Store's remoteFilter and remoteSort set to true so filtering and sorting requests handled by proxy.
When I filter my store by multiple fields I want to use OR condition, not AND.
Without memory proxy paging I could use remoteFilter: false and custom filter function like:
store.filterBy(function (record)
{
for (var i = 0; i < searchFields.length; ++i) {
if (record.get(searchFields[i]).toLowerCase().indexOf(searchText) !== -1) {
return true;
}
}
return false;
});
But how I can achieve this with enabled paging? Override memory proxy?
Ext.data.proxy.Memory.read use Ext.util.Filter.createFilterFn to create a filter function based on passed Ext.util.Filter[] (with property-value configs):
// Filter the resulting array of records
if (filters && filters.length) {
// Total will be updated by setting records
resultSet.setRecords(records = Ext.Array.filter(records, Ext.util.Filter.createFilterFn(filters)));
resultSet.setTotal(records.length);
}
So, if you want to use different filter logic, you can override memroy proxy read method and use custom function to create filter function:
Ext.define('MyApp.extensions.FilterMemoryProxy', {
extend: 'Ext.data.proxy.Memory',
alias: 'proxy.filtermemory',
createCustomFilterFn: function (filters)
{
if (!filters) {
return Ext.returnTrue;
}
return function (candidate)
{
var items = filters.isCollection ? filters.items : filters,
length = items.length,
i, filter;
for (i = 0; i < length; i++) {
filter = items[i];
if (!filter.getDisabled() && candidate.get(filter.getProperty()).toLowerCase().indexOf(
filter.getValue().toLowerCase()) !== -1) {
return true;
}
}
return false;
};
},
read: function (operation)
{
...
if (operation.process(resultSet, null, null, false) !== false) {
// Filter the resulting array of records
if (filters && filters.length) {
// Total will be updated by setting records
resultSet.setRecords(records = Ext.Array.filter(records, me.createCustomFilterFn(filters)));
resultSet.setTotal(records.length);
}
...
}
}
});
You can override the memory proxy to suit your needs, or, if you only wish to find a string in one of the fields specified in searchFields, you can add a 'virtual' field to the model with the convert method specified.
For example:
// Extend Ext.data.Model or create it. Dealer's choice.
Ext.define('MyLocalModel', {
extend: 'Ext.data.model',
fields: [
...
{
name: 'searchField',
type: 'string',
convert: function(value, record) {
// Get search fields somewhere
var resultArray = [];
for (var i = 0; i < searchFields.length; ++i) {
var searchField = searchFields[i];
var fieldValue = record.get(searchField);
resultArray.push(fieldValue);
}
// Join by a string that would not normally be searched
// so that field values 'foo' and 'bar' are not valid when searching 'ooba'
var result = resultArray.join('**');
return result;
}
}
]
});
All you need to do in the store then is:
store.filter('searchField', searchText);
It's not exactly what you are searching for, but it should get the job done.
ExtJs is a new world for me. I hope I can get the problem explained very well.
I have a grid with different editor columns. If I change one and save it, I have to change the output.
I do it with
e.row.cells[e.colIdx].childNodes[0].innerText = '{s name=column/express/yes}JA{/s}';
e.row.cells[e.colIdx].classList.add("warning");
If I change 2 different fields at the same time, e.colIdx does not fit anymore to the right column.
Is there a getter or anything else to get the column of a special grid column ?
Here is the complete code
onSavePosition: function(editor, e, store, options) {
var me = this,positionStore;
if (((e.newValues.md_express != e.originalValues.md_express) && e.record.internalId > 0)
|| e.newValues.md_supplier != e.originalValues.md_supplier) {
var customCallback = function (position, success) {
Ext.Ajax.request({
method: 'POST',
url: '{url controller=AttributeData action=saveData}',
params: {
_foreignKey: e.record.internalId,
_table: 's_order_details_attributes',
__attribute_md_express: Number(e.newValues.md_express),
__attribute_md_supplier: e.newValues.md_supplier
},
success:function(response){
var t=this;
me.getOrderList().store.load();
var selectedRowIndex=e.grid.getSelectionModel().getCurrentPosition().row;
var selectedRowRecord=e.grid.getStore().getAt(selectedRowIndex);
selectedRowRecord.set('md_express',e.newValues.md_express);
selectedRowRecord.set('md_supplier',e.newValues.md_supplier);
selectedRowRecord.commit();
debugger;
if((e.newValues.md_express != e.originalValues.md_express)) {
if (e.newValues.md_express == true) {
e.row.cells[e.colIdx].childNodes[0].innerText = '{s name=column/express/yes}JA{/s}';
e.row.cells[e.colIdx].classList.add("warning");
} else {
e.row.cells[e.colIdx].childNodes[0].innerText = '{s name=column/express/no}NEIN{/s}';
e.row.cells[e.colIdx].classList.remove("warning");
}
}
}
});
};
options.callback = customCallback;
}
me.callParent([editor, e, store, options]);
}
my solution is
var colIdx = me.getColumnIndex(editor.grid,'md_express');
getColumnIndex: function(grid, dataIndex) {
gridDataIndices = Ext.Array.pluck(grid.columns, 'dataIndex');
return Ext.Array.indexOf(gridDataIndices, dataIndex);
}
this sends back the column index of the given column in my grid.
Found here
how to find column index using dataIndex Extjs 4
I have created a custom xtype for multiselect, but i am not able to understand what changes i need to perform to save the values as a string array instead of comma delimited string.
Currently it is storing the values as follows
Property industry
Type String
Value government,healthcare
Instead, i want to save the information as follows
Property industry
Type String[]
Value government,healthcare
Any suggestions, pointers highly appreciated.
CQ.Ext.form.Multiselect = CQ.Ext.extend(CQ.Ext.form.Field, {
store:null,
storeUrl:'',
displayField:'text',
valueField:'value',
allowBlank:true,
minLength:0,
blankText:CQ.Ext.form.TextField.prototype.blankText,
copy:false,
allowDup:false,
allowTrash:false,
legend:null,
focusClass:undefined,
delimiter:',',
view:null,
dragGroup:null,
dropGroup:null,
tbar:null,
appendOnly:false,
sortField:null,
sortDir:'ASC',
defaultAutoCreate : {tag: "div"},
initComponent: function(){
CQ.Ext.form.Multiselect.superclass.initComponent.call(this);
this.addEvents({
'dblclick' : true,
'click' : true,
'change' : true,
'drop' : true
});
},
onRender: function(ct, position){
var fs, cls, tpl;
CQ.Ext.form.Multiselect.superclass.onRender.call(this, ct, position);
cls = 'ux-mselect';
fs = new CQ.Ext.form.FieldSet({
renderTo:this.el,
title:this.legend,
height:this.height,
width:this.width,
style:"padding:1px;",
tbar:this.tbar
});
if(!this.legend){
//fs.el.down('.'+fs.headerCls).remove();
fs.body.addClass(cls);
}
tpl = '<tpl for="."><div class="' + cls + '-item';
if(CQ.Ext.isIE || CQ.Ext.isIE7 || CQ.Ext.isOpera )tpl+='" unselectable=on';
else tpl+=' x-unselectable"';
tpl+='>{' + this.displayField + '}</div></tpl>';
this.store = new CQ.Ext.data.JsonStore({
autoload:true,
url: CQ.HTTP.externalize(this.storeUrl),
fields:['value','text']
});
this.store.load();
this.view = new CQ.Ext.ux.DDView({
multiSelect: true, store: this.store, selectedClass: cls+"-selected", tpl:tpl,
allowDup:this.allowDup, copy: this.copy, allowTrash: this.allowTrash,
dragGroup: this.dragGroup, dropGroup: this.dropGroup, itemSelector:"."+cls+"-item",
isFormField:false, applyTo:fs.body, appendOnly:this.appendOnly,
sortField:this.sortField, sortDir:this.sortDir
});
fs.add(this.view);
this.view.on('click', this.onViewClick, this);
this.view.on('beforeClick', this.onViewBeforeClick, this);
this.view.on('dblclick', this.onViewDblClick, this);
this.view.on('drop', function(ddView, n, dd, e, data){
return this.fireEvent("drop", ddView, n, dd, e, data);
}, this);
this.hiddenName = this.name;
var hiddenTag={tag: "input", type: "hidden", value: "", name:this.name};
if (this.isFormField) {
this.hiddenField = this.el.createChild(hiddenTag);
} else {
this.hiddenField = CQ.Ext.get(document.body).createChild(hiddenTag);
}
fs.doLayout();
},
initValue:CQ.Ext.emptyFn,
onViewClick: function(vw, index, node, e) {
var arrayIndex = this.preClickSelections.indexOf(index);
if (arrayIndex != -1)
{
this.preClickSelections.splice(arrayIndex, 1);
this.view.clearSelections(true);
this.view.select(this.preClickSelections);
}
this.fireEvent('change', this, this.getValue(), this.hiddenField.dom.value);
this.hiddenField.dom.value = this.getValue();
this.fireEvent('click', this, e);
this.validate();
},
onViewBeforeClick: function(vw, index, node, e) {
this.preClickSelections = this.view.getSelectedIndexes();
if (this.disabled) {return false;}
},
onViewDblClick : function(vw, index, node, e) {
return this.fireEvent('dblclick', vw, index, node, e);
},
getValue: function(valueField){
var returnArray = [];
var selectionsArray = this.view.getSelectedIndexes();
if (selectionsArray.length == 0) {return '';}
for (var i=0; i<selectionsArray.length; i++) {
returnArray.push(this.store.getAt(selectionsArray[i]).get(((valueField != null)? valueField : this.valueField)));
}
return returnArray;
},
setValue: function(values) {
var index;
var selections = [];
this.view.clearSelections();
this.hiddenField.dom.value = '';
if (!values || (values == '')) { return; }
if (!(values instanceof Array)) { values = values.split(this.delimiter); }
for (var i=0; i<values.length; i++) {
index = this.view.store.indexOf(this.view.store.query(this.valueField,
new RegExp('^' + values[i] + '$', "i")).itemAt(0));
selections.push(index);
}
this.view.select(selections);
this.hiddenField.dom.value = values;
for (var i=0; i<values.length; i++) {
this.listOfIndustries=values[i];
alert(values[i]);
}
this.validate();
},
getRawValue: function(valueField) {
var tmp = this.getValue(valueField);
if (!tmp) {
tmp = [];
}
return tmp;
},
setRawValue: function(values){
setValue(values);
},
validateValue : function(value){
if (value.length < 1) { // if it has no value
if (this.allowBlank) {
this.clearInvalid();
return true;
} else {
this.markInvalid(this.blankText);
return false;
}
}
if (value.length < this.minLength) {
this.markInvalid(String.format(this.minLengthText, this.minLength));
return false;
}
if (value.length > this.maxLength) {
this.markInvalid(String.format(this.maxLengthText, this.maxLength));
return false;
}
return true;
}
});
CQ.Ext.reg("industriesmultiselect", CQ.Ext.form.Multiselect);
Envionment CQ 5.5
Short answer:
Instead of using the one hidden field to store your values, you need to use multiple underlying input elements, each having the same name property for the Sling Post Servlet to interpret the output as a multi-valued property. See the multifield widget's setValue and addItem methods at /libs/cq/ui/widgets/source/widgets/form/MultiField.js for an example of dynamically adding new fields.
Longer explanation:
It looks like your getValue does what you expect, but the problem is that that method isn't getting called to provide the value that gets submitted. If you're using this widget in a standard dialog, the parent form panel submits the values that are specified in the input elements beneath it in the DOM hierarchy.
In other words, you need to apply your multiple values to DOM elements.
The CQ.Ext.form.Field that you're extending only defines one underlying input element, which you're trying to set with your values array in setValue:
this.hiddenField.dom.value = values;
and in onViewClick
this.hiddenField.dom.value = this.getValue();
Since hiddenField is an input tag of type 'hidden', it holds a string value and when you try to set it this way, you're actually storing the result of calling toString() on your values array. This is why you end up with one string of comma separated values getting submitted.
You'll need to maintain a whole set of hidden fields if you want this widget to work with the standard form submission infrastructure. Alternatively, you could implement your own submit event listener wherever appropriate and use Ext or jQuery to POST an AJAX request with your array (directly from getValue()) as one of the parameters.
I currently have the code below that overrides the sort for all stores. What I need to do is create a sort for an individual store. How do I do that?
Ext.override(Ext.data.Store, {
// override
createSortFunction: function (field, direction) {
direction = direction || "ASC";
var directionModifier = direction.toUpperCase() == "DESC" ? -1 : 1;
var sortType = this.fields.get(field).sortType;
//create a comparison function. Takes 2 records, returns 1 if record 1 is greater,
//-1 if record 2 is greater or 0 if they are equal
return function (r1, r2) {
var v1;
var v2;
if (field == 'Registered') {
v1 = sortType(r1.data['AircraftNeedsRegistered']);
v2 = sortType(r2.data['AircraftNeedsRegistered']);
if (r1.data['AircraftNeedsRegistered'])
r1.data['Register'] = !r1.data['Register'];
if (r2.data['AircraftNeedsRegistered'])
r2.data['Register'] = !r2.data['Register'];
//store.getAt(rowIndex).data['Registered'] = true;
}
else {
v1 = sortType(r1.data[field]);
v2 = sortType(r2.data[field]);
}
// To perform case insensitive sort
if (v1.toLowerCase) {
v1 = v1.toLowerCase();
v2 = v2.toLowerCase();
}
return directionModifier * (v1 > v2 ? 1 : (v1 < v2 ? -1 : 0));
};
}
What I want to implement is something like this:
function AircraftStore() {
return new Ext.data.JsonStore(Ext.apply({
url: AVRMS.ROOT_CONTEXT + "/ssl/json/general/GetAircraftByOwnerId.aspx",
idProperty: 'OwnerOid',
baseParams: { OwnerOid: 0 },
fields: ['AircraftOid', 'NNumber', 'Make', 'Model', 'Seats', 'RegistrationType', 'Airworthy', 'IsFaaAirport', 'AirworthyString', 'IsFaaAirportString', 'Airport', 'AircraftNeedsRegistered', 'Register'],
sort: function (field, direction) {
return customSort(field, direction);
}
}));
};
function customSort(field,direction) {
//What do I put here?
}
I am not sure if I understood the purpose of your code correctly but I believe the same effect can be achieved by using the following code (sorry, I haven't tested it but it should work).
AircraftStore = Ext.extend(Ext.data.JsonStore, {
url: AVRMS.ROOT_CONTEXT + "/ssl/json/general/GetAircraftByOwnerId.aspx",
idProperty: 'OwnerOid',
baseParams: {
OwnerOid: 0
},
fields: ['AircraftOid', 'NNumber', 'Make', 'Model', 'Seats', 'RegistrationType', 'Airworthy', 'IsFaaAirport', 'AirworthyString', 'IsFaaAirportString', 'Airport', 'AircraftNeedsRegistered', 'Register'],
sort: function (field, direction) {
if (field == 'Registered') {
field = 'AircraftNeedsRegistered';
this.query('AircraftNeedsRegistered', true).each(function(record) {
record.data['Register'] = !record.data['Register'];
});
}
return AircraftStore.superclass.sort.call(this, field, direction);
}
});
If this does not achieve your desired behaviour because I missed something, you can just override the createSortFunction instead of sort. However, since createSortFunction is supposed to be private, it is better to handle custom logic by overriding sort. Anyway, if you prefer to stay with your first approach, your AircraftStore should look like this:
AircraftStore = Ext.extend(Ext.data.JsonStore, {
url: AVRMS.ROOT_CONTEXT + "/ssl/json/general/GetAircraftByOwnerId.aspx",
idProperty: 'OwnerOid',
baseParams: {
OwnerOid: 0
},
fields: ['AircraftOid', 'NNumber', 'Make', 'Model', 'Seats', 'RegistrationType', 'Airworthy', 'IsFaaAirport', 'AirworthyString', 'IsFaaAirportString', 'Airport', 'AircraftNeedsRegistered', 'Register'],
createSortFunction: function (field, direction) {
// copy your createSortFunction here
};
});
I need to display a read only view of data. I've chosen the DisplayField component to do this. My problem is that I would like an easy way to call BasicForm.setValues(values) and have a date string automagically render correctly in one of the displayFields. I haven't found anything that will do this for me (e.g. a renderer function), and am about to just format the date string manually prior to calling setValues(values). Is there some slick way to do this?
Thanks!
Ok if you are using a direct form load then you need to listen for the form's BasicForm 'actioncomplete' event. When this event fires the handler is supplied two arguments. The first is the BasicForm and the second argument is an Ext.form.Action object. We are specifically looking for an Ext.form.Action.Load object. From here we get access to the action's result.data object and we can massage the data values before this handler returns and the values are loaded into the form.
function fmtDate(sf, rec) {
if ( rec[sf] ) {
var dt = new Date(); dt.setTime(rec[sf] * 1000); return dt.format('l j F Y');
}
};
myForm.getForm().on({
actioncomplete: function(form, action) {
if (action.type === 'load') {
if (action.result.success) {
var data = action.result.data;
data.someFormattedDate = fmtDate('myDateTS', data);
} else {
//handle an error here
}
}
}
});
Now all you need in your form is a displayField named 'someFormattedDate' and Bob's your uncle (Aussie slang for it's all good). You can also achieve exactly the same thing by providing a 'success:' function to your myForm.getForm().load() call. See the ExtJS docs for Ext.form.Action.Load.
Cheers, t00bs.
I ended up subclassing displayField. That seems to work best, but I wish there was an out-of-the-box fix for something as basic as displaying a formatted date. This is my first pass at it, so it is just an example.
FormattableDisplayField = Ext.extend(Ext.form.DisplayField, {
constructor:function(config) {
var config = config || {};
Ext.applyIf(config, {
dateFormat:'c',
type:null,
displayFormat:'M d, Y'
});
FormattableDisplayField.superclass.constructor.call(this, config);
},
setValue: function(value) {
if (! this.type) {
FormattableDisplayField.superclass.setValue(value);
}
else if (this.type == 'date') {
var parsedDate = Date.parseDate(value, this.dateFormat);
if (Ext.isDate(parsedDate)) {
this.setRawValue(parsedDate.format(this.displayFormat));
}
else {
this.setRawValue(value);
}
}
else if (this.formatter) {
var formattedValue = this.formatter(value);
this.setRawValue(formattedValue);
}
}
});Ext.reg('formattabledisplayfield', FormattableDisplayField);
I came across this same problem because I like to pass my dates around as Unix timestamps and I had a requirement to display them using various formats depending on context. Here's how I did it.
If you are loading the data via a store then you can use the convert function provided by Ext.data.Field. For example:
var fields = [
{name: 'sysTestedDateObj', mapping: 'sysTestedDateTS', type: 'date', dateFormat: 'timestamp'},
/** Converted Fields **/
{name: 'sysTestedDate', convert: function(v, rec){
return fmtDate('sysTestedDateTS', rec);
}},
{name: 'targetChangeStartDate', convert: function(v, rec){
return fmtDate('targetChangeStartDateTS', rec);
}},
{name: 'createDateTime', convert: function(v, rec){
return fmtDateTime('createDateTS', rec);
}},
{name: 'modifyDateTime', convert: function(v, rec){
return fmtDateTime('modifyDateTS', rec);
}},
];
var store = new Ext.data.JsonStore({
...
fields: fields
});
Here's some conversion functions:
function fmtDate(sf, rec) {
if ( rec[sf] ) {
var dt = new Date(); dt.setTime(rec[sf] * 1000); return dt.format('l j F Y');
}
};
function fmtDateShort(sf, rec) {
if ( rec[sf] ) {
var dt = new Date(); dt.setTime(rec[sf] * 1000); return dt.format('D j M Y');
}
};
function fmtDateTime(sf, rec) {
if ( rec[sf] ) {
var dt = new Date(); dt.setTime(rec[sf] * 1000); return dt.format('l j F Y h:i a');
}
};
function fmtDateTimeShort(sf, rec) {
if ( rec[sf] ) {
var dt = new Date(); dt.setTime(rec[sf] * 1000); return dt.format('D j M Y h:i a');
}
};
Where sf is the source field we are deriving the formatted date string from.
Note the following, it's important. The convert() function is presented with a copy of the data record as read by the reader (this is in the ExtJS docs). This means you can't use any mapped fields in your conversions. In the fields array above I have a field defined as,
{name: 'sysTestedDateObj', mapping: 'sysTestedDateTS', type: 'date', dateFormat: 'timestamp'}
So I'm creating the sysTestedDateObj date object from the sysTestedDateTS field and I've told the reader that I want it to give me a date object derived from an object containing a Unix timestamp. This is a nice object to have for later on but it won't be part of the data record passed to our conversion function.
Also note that a conversion function can reference fields in the record that are not defined for use by the store. In the example above I an using the field sysTestedDateTS in a conversion function because I know the server is supplying it in it's JSON response, yet because I haven't defined it in the fields array it won't be available via the store to the consuming component.
http://dev.sencha.com/deploy/dev/docs/?class=Ext.util.Format
I think dateRenderer is the renderer function that you are looking for?
Ext.form.field.Display.html#cfg-renderer
A function to transform the raw value for display in the field.
Ext.Date.html#method-format
Formats a date given the supplied format string.
var data = {
"OrderNo": "2017071200000246",
"Createtime": "2017/7/12 13:16:42"
}; // your read only data; or use bind store
var form = Ext.create({
xtype: 'form',
defaultType: 'displayfield',
defaults: {
labelWidth: 120,
labelSeparator: ':'
},
items: [
{ fieldLabel: 'Order Number', value: data.OrderNo },
{ fieldLabel: 'Create Time', value: data.Createtime,
renderer: function (value, field) {
var date = new Date(value);
var newVal = Ext.Date.format(date, 'Y-m-d H:i:s');
return newVal;
}
}
]
});