ExtJs - Checkbox selection model, disable checkbox per row and lose clearing all the selections - checkbox

I have a grid with a checkbox selection model.
There are some rows that not be selectable, based on a value in a field. It's work.
My problem is that clearing all the selections by clicking the checkbox in the column header doesn't work.
On this link I see that costa was faced the same problem as me: ExtJs - Checkbox selection model, disable checkbox per row.
This listener is worked, but it's break checkbox clearing.
Code:
xtype: 'grid',
border: false,
selModel: {
selType: 'checkboxmodel',
listeners: {
beforeselect: function(grid, record) {
if (!record.get('supplier')) {
return false;
}
}
}
colums:[
....
],
....
Does anyone have an idea how to do this?
Thank you.

The header checkbox is checked only if all the records are checked. In your case it is impossible to check all the records => header checkbox will be never checked => it is impossible to uncheck.
To implement new logic, you can extend the checkbox model and write your own one with custom logic (by overriding the updateHeaderState method). Something like this:
Ext.define('CustomCheckboxModel', {
alias: 'selection.custom_checkboxmodel',
extend: 'Ext.selection.CheckboxModel',
allowDeselect: true,
updateHeaderState: function () {
// check to see if all records are selected
var me = this,
store = me.store,
storeCount = store.getCount(),
views = me.views,
hdSelectStatus = true,
selectedCount = 0,
selected, len, i;
if (!store.isBufferedStore && storeCount > 0) {
selected = me.selected;
hdSelectStatus = true;
store.each(function (record) {
if (!record.get('supplier')) {
return true;
}
var found = false;
for (i = 0, len = selected.getCount(); i < len; ++i) {
if (record.getId() == selected.getAt(i).id) {
found = true;
break;
}
}
if (!found) {
hdSelectStatus = found;
return false;
}
}, this);
}
if (views && views.length) {
me.column.setHeaderStatus(hdSelectStatus);
}
},
});
Ext.application({
name: 'Fiddle',
launch: function () {
var store = Ext.create('Ext.data.Store', {
fields: ['name', 'email', 'phone'],
data: [{
name: 'Lisa',
email: 'lisa#simpsons.com',
phone: '555-111-1224',
supplier: true
}, {
name: 'Bart',
email: 'bart#simpsons.com',
phone: '555-222-1234',
supplier: false
}, {
name: 'Homer',
email: 'homer#simpsons.com',
phone: '555-222-1244',
supplier: true
}, {
name: 'Marge',
email: 'marge#simpsons.com',
phone: '555-222-1254',
supplier: false
}]
});
Ext.create('Ext.grid.Panel', {
title: 'Simpsons',
store: store,
columns: [{
text: 'Name',
dataIndex: 'name'
}, {
text: 'Email',
dataIndex: 'email',
flex: 1
}, {
text: 'Phone',
dataIndex: 'phone'
}, {
xtype: 'checkcolumn',
text: 'Supplier',
dataIndex: 'supplier'
}],
height: 400,
renderTo: Ext.getBody(),
selModel: {
selType: 'custom_checkboxmodel',
listeners: {
beforeselect: function (selectionCheckboxModel, record, index, eOpts) {
console.log(record);
if (!record.get('supplier')) {
return false;
}
return true;
}
}
}
});
}
});

Related

ExtJS 7 How to select grid row on rightclick

I'd like to both open a context menu and select the right-clicked row in a ExtJS 7 modern grid. The context menu works with the code below. However, I cannot find a way to select the row. The grid.getSelectionModel() seems to be no longer available in ExtJS 7.
// Listener in my Ext.app.ViewController manages to update and show context menu but not to select the row.
onContextMenu: function (e) {
const grid = this.getView();
const target = e.getTarget(grid.itemSelector);
if (target) {
e.stopEvent();
const item = Ext.getCmp(target.id);
if (item) {
// Would like to select row here with something like grid.getSelectionModel().selectRow(rowindex);
this.updateMenu(item.getRecord(), item.el, e);
}
}
}
Have a look at the following fiddle sample (Modern toolkit 7.3.1)
Ext.application({
name: 'Fiddle',
launch: function () {
const menu = new Ext.menu.Menu({
items: [{
text: 'Menu Item 1'
}, {
text: 'Menu Item 2'
}]
});
Ext.Viewport.add({
xclass: 'Ext.grid.Grid',
store: Ext.create('Ext.data.Store', {
fields: ['name', 'email', 'phone'],
data: [{
name: 'Lisa',
email: 'lisa#simpsons.com',
phone: '555-111-1224'
}, {
name: 'Bart',
email: 'bart#simpsons.com',
phone: '555-222-1234'
}]
}),
columns: [{
text: 'Name',
dataIndex: 'name',
width: 200
}, {
text: 'Email',
dataIndex: 'email',
width: 250
}, {
text: 'Phone',
dataIndex: 'phone',
width: 120
}],
listeners: {
childcontextmenu: function (grid, location) {
const {
record,
event
} = location;
grid.deselectAll();
grid.setSelection(record);
menu.showAt(event.getX(), event.getY());
event.stopEvent()
}
}
})
}
});

ExtJS - Grid multiple value filter in same column

Needed help in filtering grid with multiple values.
I am trying to create a menucheckitem with many phone value.
And filter the grid based on the phone Checked.
By using below code, i am able to filter grid based on single value.
store.filter([{
property: 'type',
value: value
}]);
Now i wanted to filter grid, even if i select many phone checkBoxs.
I tried using store.filterBy(). But, not working properly, i do not know what i am doing wrong.
var test = ["111-222-333","111-222-334","111-222-335"]
store.filterBy(function(record, val){
return test.indexOf(record.get('phone')) != -1
}
});
This filters the first value only i.e. "111-222-333" value only.. Not filtering all other value in test.
find sample code in here -
https://fiddle.sencha.com/#view/editor&fiddle/2ll7
So i forked your fiddle, remade it and i think i have achieved what you wanted. First of all your definition of menucheckitem in bbar was kind of strange. I think you needed a list of phones which would be checkboxes, but the list is depends on the store records, so it needs to be build dynamically (as i did it in afterrender). Actually this must be inside of store's load event, but in example it didn't fire(maybe bcz it is a memory type of store). Anyway when you copy the code you'll need to put all of the afterrender content inside the store load event.
FIDDLE
Ext.application({
name: 'Fiddle',
launch: function () {
Ext.create('Ext.grid.Panel', {
title: 'Simpsons',
store: {
fields: ['name', 'email', 'phone', 'type'],
data: [{
name: 'Marge',
email: 'marge#simpsons.com',
phone: '111-222-334',
type: 'Foo'
}, {
name: 'Homer',
email: 'homer#simpsons.com',
phone: '111-222-333',
type: 'Foo'
}, {
name: 'Marge',
email: 'marge#simpsons.com',
phone: '111-222-334',
type: 'Foo'
}, {
name: 'Bart',
email: 'bart#simpsons.com',
phone: '111-222-335',
type: 'Bar'
}, {
name: 'Bart',
email: 'bart#simpsons.com',
phone: '111-222-335',
type: 'Bar'
}, {
name: 'Lisa',
email: 'lisa#simpsons.com',
phone: '111-222-336',
type: 'Bar'
}]
},
columns: [{
text: 'Name',
dataIndex: 'name'
}, {
text: 'Email',
dataIndex: 'email'
}, {
text: 'Phone',
dataIndex: 'phone'
}, {
text: 'Type',
dataIndex: 'type'
}],
listeners: {
afterrender: function (grid) {
var store = grid.store;
var phones = store.getData().items.map(function (r) { //get the phones
return r.get('phone');
});
var phonesFiltered = [];
phones.forEach(function (p) { //filter them to make records unique
if (!phonesFiltered.includes(p)) {
phonesFiltered.push(p);
}
});
var items = [];
phonesFiltered.forEach(function (p) { //create `menucheckitem` items with phone names and attaching `checkchange` event
items.push({
xtype: 'menucheckitem',
text: p,
listeners: {
checkchange: function (checkbox, checked, eOpts) {
var menu = checkbox.up('menu');
var filterPhones = [];
menu.items.items.forEach(function (c) { //get all checked `menucheckitem`-s
if (c.checked) {
filterPhones.push(c.text);
}
});
var store = checkbox.up('grid').store;
store.clearFilter();
if (filterPhones.length > 0) {
store.filterBy(function (record) {
return this.filterPhones.indexOf(record.get('phone')) !== -1;
}, {
filterPhones: filterPhones
});
}
}
}
});
});
//
Ext.getCmp('toolbarId').add({
xtype: 'menu',
// height: 120,
floating: false,
items: items
});
}
},
bbar: {
xtype: 'toolbar',
height: 200,
id: 'toolbarId'
},
renderTo: Ext.getBody()
});
}
});

ExtJS: Issue with scope in class

I'm keep facing with a issue to choice exact component with scope. As you'll notice below I've created 2 different functions inside gridpanel. One of those creates a Ext.MessageBox for confirm. And other function creates a Ext.window.Window depends on button click of MessageBox.
The thing here is; It should destroy related component with cancel and no buttons. Both buttons always point to gridpanel because of var me = this state and destroys the gridpanel itself.
How can I point destroy method directly to related component?
Ext.define('MyApp.FooGrid', {
extend: 'Ext.grid.Panel',
reference: 'fooGrid',
getGridMenu: function () {
// Here is the 'Update' function; with right-click user being able to see `contextmenu`
var me = this;
var ret = [
{
text: 'Update',
listeners: {
click: me.onUpdate,
scope: me
}
}
];
return me.callParent().concat(ret);
},
onUpdate: function () {
var me = this,
gridRec = this.getSelectionModel().getSelection(); // Here being able to retrieve row data.
Ext.MessageBox.confirm(translations.confirm, translations.confirmChange, me.change, me);
return gridRec;
},
change: function (button) {
var me = this;
var selectedRec = me.onUpdate();
var selectedRecEmail = selectedRec[0].data.email; //Retrieves selected record's email with right-click action
if (button === "yes") {
return new Ext.window.Window({
alias: 'updateWin',
autoShow: true,
title: translations.update,
modal: true,
width: 350,
height: 200,
items: [
{
xtype: 'container',
height: 10
},
{
xtype: 'textfield',
width: 300,
readOnly: true,
value: selectedRecEmail //Display selected record email
},
{
xtype: 'textfield',
width: 300,
fieldLabel: translations.newPassword
}
],
dockedItems: [
{
xtype: 'toolbar',
dock: 'bottom',
items: [
{
xtype: 'tbfill'
},
{
xtype: 'button',
text: translations.cancel,
listeners: {
click: function () {
me.destroy(); // Here is the bug: When user clicks on this button; should destroy current window but it destroys 'gridpanel' itself
}
}
},
{
xtype: 'button',
text: translations.save,
listeners: {
click: function () {
console.log("I'll save you!");
}
}
}
]
}
]
});
} else {
console.log('this is no!');
me.destroy(); // Another bug raises through here: If user will click on No then 'messagebox' should destroy. This one is destroys the gridpanel as well.
}
}
});
How can I point destroy method directly to related component?
Firstly on confirmation box button's(No) click, you don't need to destroy it will automatically hide the box whenever you click into No.
And for update window instead of using me.destroy() you need to use directly button.up('window').destroy() so it will only destroy your update window not the grid.
And also you don't need to again call me.onUpdate() inside of change function otherwise it will again show the confirmation box. You can directly get selected record on the change function like this me.getSelection().
In this Fiddle, I have created a demo using your code and I have put my efforts to get result.
CODE SNIPPET
Ext.application({
name: 'Fiddle',
launch: function () {
Ext.create('Ext.data.Store', {
storeId: 'demostore',
fields: ['name', 'email', 'phone'],
data: [{
name: 'Lisa',
email: 'lisa#simpsons.com',
phone: '555-111-1224'
}, {
name: 'Bart',
email: 'bart#simpsons.com',
phone: '555-222-1234'
}, {
name: 'Homer',
email: 'homer#simpsons.com',
phone: '555-222-1244'
}, {
name: 'Marge',
email: 'marge#simpsons.com',
phone: '555-222-1254'
}]
});
Ext.create('Ext.grid.Panel', {
title: 'Demo GRID',
store: 'demostore',
columns: [{
text: 'Name',
dataIndex: 'name'
}, {
text: 'Email',
dataIndex: 'email',
flex: 1
}, {
text: 'Phone',
dataIndex: 'phone'
}],
height: 200,
listeners: {
itemcontextmenu: function (grid, record, item, index, e, eOpts) {
e.stopEvent();
grid.up('grid').getGridMenu().showAt(e.getXY());
}
},
renderTo: Ext.getBody(),
getGridMenu: function () {
var me = this;
if (!me.contextMenu) {
me.contextMenu = Ext.create('Ext.menu.Menu', {
width: 200,
items: [{
text: 'Update',
handler: me.onUpdate,
scope: me
}]
});
}
return me.contextMenu;
},
onUpdate: function () {
var me = this;
Ext.MessageBox.confirm('Confirmation ', 'Are your sure ?', me.change, me);
},
change: function (button) {
var me = this,
selectedRecEmail = me.getSelection()[0].data.email; //Retrieves selected record's email with right-click action
if (button === "yes") {
return new Ext.window.Window({
autoShow: true,
title: 'Update',
modal: true,
width: 350,
height: 200,
items: [{
xtype: 'tbspacer',
height: 10
}, {
xtype: 'textfield',
width: 300,
readOnly: true,
value: selectedRecEmail //Display selected record email
}, {
xtype: 'textfield',
inputType:'password',
width: 300,
fieldLabel: 'New Password'
}],
dockedItems: [{
xtype: 'toolbar',
dock: 'bottom',
items: [{
xtype: 'tbfill'
}, {
xtype: 'button',
text: 'cancel',
listeners: {
click: function (btn) {
btn.up('window').destroy(); // Here is the bug: When user clicks on this button; should destroy current window but it destroys 'gridpanel' itself
}
}
}, {
xtype: 'button',
text: 'save',
listeners: {
click: function () {
console.log("I'll save you!");
}
}
}]
}]
});
}
}
});
}
});

Form with textfield and grid: send all values to the server

In create and update forms, it is sometimes necessary to give the user the ability to dynamically add fields to values of the same type (more than one phone, more than one address, etc.).
I'm exploring several possibilities to do this.
One of them is to use a grid as a form field.
However, I have doubts about how best to implement this idea, especially on how to send all the form field values (textfield and grid) to the server (and then how to load them later in the form to edit).
Fiddles with some ideas:
One with cellediting plugin https://fiddle.sencha.com/#view/editor&fiddle/2ftp
Another one with roweditin gplugin a https://fiddle.sencha.com/#view/editor&fiddle/2fto
Not sure about the "best to implement", but I have seen so many requirements for multivalue input, that for reusability I have in my toolbox a gridfield similar to the following one:
Ext.define('Ext.ux.GridField', {
extend: 'Ext.form.FieldContainer',
alias: 'widget.gridfield',
initComponent: function () {
var me = this;
if(!me.columns) me.columns = {
dataIndex: 'field1'
};
if(!me.mapFn) me.mapFn = function(value) {
if(Ext.isObject(value)) return value;
return {
field1: value
};
};
if(!me.unmapFn) me.unmapFn = function(record) {
return record.get('field1');
};
me.grid = Ext.widget(Ext.apply({
xtype: 'grid',
viewConfig: {
markDirty: false
},
store: me.store || Ext.create('Ext.data.Store', {
fields: me.columns.map(function(column) {
return {
name: column.dataIndex,
type: column.dataType || 'auto',
defaultValue: column.defaultValue
};
}),
listeners: {
update: me.updateValue,
datachanged: me.updateValue,
scope: me
}
}),
columns: [{
xtype: 'actioncolumn',
getClass: function () {
return 'x-fa fa-times'
},
handler: function(grid, rowIndex, colIndex, item, e, record) {
grid.getStore().remove(record);
},
width: 35
}].concat(me.columns),
bbar: [{
xtype: 'button',
iconCls: 'x-fa fa-pencil',
text: 'Add',
handler: function(btn) {
var grid = btn.up('grid'),
store = grid.getStore(),
record = store.add(Ext.clone(me.emptyRecord) || {})[0];
grid.getPlugin('editor').startEditByPosition({
row: store.indexOf(record),
column: 1
});
}
}],
plugins: [
Ext.create('Ext.grid.plugin.CellEditing', {
pluginId: 'editor',
clicksToEdit: 1
})
]
}, me.gridConfig)); // "gridConfig" config can override everything on each instance.
me.hiddenField = Ext.widget({
xtype: 'hiddenfield',
name: me.name,
value: '',
allowNull: false,
rawToValue: function (raw) {
return raw;
},
valueToRaw: function (value) {
return value;
},
getRawValue: function () {
return Ext.valueFrom(this.rawValue, '')
},
isEqual: function (a, b) {
return Ext.encode(a) == Ext.encode(b)
},
listeners: {
change: function(field, nV, oV) {
if(!Ext.isArray(nV)) nV = [nV];
var store = me.grid.getStore();
store.removeAll();
store.add(nV.map(me.mapFn));
}
}
});
Ext.apply(me, {
layout: 'fit',
items: [{
xtype:'container',
border: 1,
style: {
borderColor: '#d0d0d0',
borderStyle: 'solid'
},
items: [me.grid]
}, me.hiddenField]
});
me.callParent(arguments);
},
updateValue: function() {
var me = this,
grid = me.grid,
hiddenField = me.hiddenField,
nV = grid.getStore().getRange().map(me.unmapFn, me),
oV = me.hiddenField.getValue();
if(!oV || Ext.isArray(oV) && Ext.encode(nV) != Ext.encode(oV)) {
hiddenField.suspendCheckChange++;
hiddenField.setValue(nV);
hiddenField.suspendCheckChange--;
me.fireEvent('change', me, nV, oV);
}
}
});
which can then be used like this:
},{
xtype: 'gridfield',
fieldLabel: 'Contacts',
name: 'contacts',
columns: [{
text: 'Type',
dataIndex: 'type',
editor:{
xtype: 'combobox',
name: 'type',
valueField: 'name',
displayField: 'name',
store: combostore,
queryMode: 'local'
},
flex: 0.7
},{
text: 'Description',
dataIndex: 'description',
editor:{
xtype: 'textfield',
name: 'description'
},
flex: 1
}],
mapFn: function(value) {
return value;
},
unmapFn: function(record) {
return record.getData();
}
}, {
I have made a fiddle for you based on your fiddle, including working load and save operations on the form, but in ExtJS 6.x. And I have checked that it works with ExtJS 5 as well, although you have to add working icons.

Best way to block CTRL-V against a grid

Using the following fiddler https://fiddle.sencha.com/#fiddle/1frn
You can select cell, do CTRL-C, then select different cell, and do CTRL-V and you see the values have been copied.
How do I block CTRL-V?
Is overriding clipboard.validateAction the best way?
privates : {
validateAction : function(event) {
var view = this.getCmp().getView();
If(view.actionableMode){
return false;
}
}
}
Its not clear to me why a common function like validateAction would be private...
You can use this override/sample code to control the ability to paste depending on the current state of the grid:
Ext.define('Fiddle.grid.plugin.Clipboard',{
override: 'Ext.grid.plugin.Clipboard',
beforepaste: Ext.emptyFn,
mixins: [
'Ext.mixin.Observable'
],
constructor: function(config) {
var me = this;
me.callParent([config]);
me.mixins.observable.constructor.call(me);
},
privates : {
onPaste: function (keyCode, event) {
var me = this,
sharedData = me.shared.data,
source = me.getSource(),
i, n, s;
if (me.validateAction(event) === false) {
return;
}
if (me.fireEvent('beforepaste',keyCode,event,me.cmp) !== false) {
if (source) {
for (i = 0, n = source.length; i < n; ++i) {
s = source[i];
if (s === 'system') {
// get the format used by the system clipboard.
s = me.getSystem();
me.pasteClipboardData(s);
break;
} else if (sharedData && (s in sharedData)) {
me.doPaste(s, sharedData[s]);
break;
}
}
}
}
}
}
});
Ext.define('UserController', {
extend : 'Ext.app.ViewController',
alias: 'controller.users',
onBeforePaste:function(keyCode,event,grid){
//Perform custom logic
console.log(grid)
return false;
}
});
Ext.application({
name: 'Fiddle',
launch: function() {
var store = Ext.create('Ext.data.Store', {
fields: ['name', 'email', 'phone'],
data: [{
name: 'Lisa',
email: 'lisa#simpsons.com',
phone: '555-111-1224'
}, {
name: 'Bart',
email: 'bart#simpsons.com',
phone: '555-222-1234'
}, {
name: 'Homer',
email: 'homer#simpsons.com',
phone: '555-222-1244'
}, {
name: 'Marge',
email: 'marge#simpsons.com',
phone: '555-222-1254'
}]
});
Ext.create('Ext.grid.Panel', {
title: 'Simpsons',
store: store,
controller:'users',
width: 400,
renderTo: Ext.getBody(),
columns: [{
text: 'Name',
dataIndex: 'name'
}, {
text: 'Email',
dataIndex: 'email',
flex: 1
}, {
text: 'Phone',
dataIndex: 'phone'
}],
plugins: {
ptype: 'cellediting',
clicksToEdit: 2
},
selModel: {
type: 'spreadsheet',
rowNumbererHeaderWidth: 0
},
plugins: [{
ptype: 'clipboard',
listeners: {
beforepaste: 'onBeforePaste'
}
}],
listeners: {
selectionchange: function(grid, selection, eOpts) {
var store = grid.getStore();
}
}
});
}
});

Resources