Extjs grid with multiselect feature to retrieve value of selected lists - forms

Let's say I have a grid with multiselect option on, when user selects 4 lists and wants to get the values ( alerted on screen) how would I do that? And how would I disable buttons untill at least one list is selected?

All questions you've asked are answered many times already. Also there are good ExtJS examples on sencha.com. For example list view grid shows multiple select and editable grid with writable store shows button enable on click. But THE MOST important is documentation! Let me explain functionality on following code. Most of it is from list view example.
This grid gets JSON from list.php which has following structure
{"authors":[{"surname":"Autho1"},{"surname":"Autho2"}]}
And the grid:
Ext.require([
'Ext.grid.*',
'Ext.data.*',
'Ext.panel.*'
]);
Ext.onReady(function(){
// Here i've definned simple model with just one field
Ext.define('ImageModel', {
extend: 'Ext.data.Model',
fields: ['surname']
});
var store = Ext.create('Ext.data.JsonStore', {
model: 'ImageModel',
proxy: {
type: 'ajax',
url: 'list.php',
reader: {
type: 'json',
root: 'authors'
}
}
});
store.load();
var listView = Ext.create('Ext.grid.Panel', {
id: 'myPanel', // Notice unique ID of panel
width:425,
height:250,
collapsible:true,
renderTo: Ext.getBody(),
store: store,
multiSelect: true,
viewConfig: {
emptyText: 'No authors to display'
},
columns: [{
text: 'File',
flex: 50,
// dataIndex means which field from model to load in column
dataIndex: 'surname'
}],
dockedItems: [{
xtype: 'toolbar',
items: [{
// This button will log to console authors surname who are selected
// (show via firebug or in chrome js console for example)
text: 'Show selected',
handler: function() {
// Notice that i'm using getCmp(unique Id of my panel)
// to get panel regerence. I could also use
// this.up('toolbar').up('myPanel')
// see documentation for up() meaning
var selection = Ext.getCmp('myPanel').getSelectionModel().getSelection();
for (var i=0; i < selection.length; i++) {
console.log(selection[i].data.surname);
}
}
},{
text: 'Disabled btn',
id: 'myHiddenBtn', // Notice unique ID of my button
disabled: true // disabled by default
}]
}]
});
// Here i'm waiting for event which is fired
// by grid panel automatically when you click on
// any item of grid panel. Then I lookup
// my button via unique ID and set 'disabled' property to false
listView.on('itemclick', function(view, nodes){
Ext.getCmp('myHiddenBtn').setDisabled(false);
});
});
I didn't knew how to do this from top of my head, but I used documentation and the result works ;-). See Grid panel docs for more information.

Related

ag-Grid Master / Detail - Row Selection

Goals
I am trying to add row selection functionality which is connected between the master grid and detail grid.
When a checkbox on a master grid row is selected, I want to select all the checkboxes in the associated detail grid.
When a checkbox on a master grid row is deselected, I want to deselect all the checkboxes in the associated detail grid.
When only partial checkboxes inside of a detail grid are selected, I want the master grid row to show a partial selection icon in the checkbox.
I want to keep count of how many items were selected in all of the detail grids
Questions/Problems
All of this functionality already exists for Groups, but I cannot get it to work for Master/Detail. I've tried adding in the line that I believe should add this for Groups cellRendererParams: {checkbox: true}, but that does nothing for the detail grid.
I've also tried implementing the functionality myself, but I hit a couple of roadblocks.
I don't know how to set the partial-checkbox icon manually or if that's even possible.
I implemented the onSelectionChanged event handler for the detail grid, but inside of the function it looks like I can't access the Angular component context to update the count of selected items. Not sure how to pass in the context this.
Code that I have working so far:
When a checkbox on a master grid is selected/deselected, the checkboxes in the associated detail grid are all selected/deselected.
public onMasterRowSelected() {
this.gridOptions.api.forEachNode(masterRowNode => {
if(masterRowNode.isSelected()) {
this.gridOptions.api.getDetailGridInfo(`detail_${masterRowNode.id}`).api.selectAll();
} else {
this.gridOptions.api.getDetailGridInfo(`detail_${masterRowNode.id}`).api.forEachNode(detailRowNode => detailRowNode.setSelected(false));
}
});
}
Added the onSelectionChanged() event listener for the detail grid.
constructor() {
this.gridOptions = {
...
detailCellRendererParams: {
getDetailRowData: function(params) {
params.successCallback(params.data.items); // Defines where to find the detail fields out of the rowData object
},
detailGridOptions: {
rowSelection: 'multiple',
defaultColDef: {
sortable: true
},
getRowNodeId: function (rowData) {
return rowData.id; // detail items are indexed by id field
},
onSelectionChanged: function (params) {
console.log(this); // I expected this to print out my Angular component object, but instead it prints out this.gridOptions.detailCellRendererParams.detailGridOptions
console.log("TEST"); // I know that this function is successfully being called because this line is printed
this.selectedItems = 0;
params.api.forEachNode(detailRowNode => {
if(detailRowNode.isSelected()) {
this.selectedObs++; // I believe that this would work if I had access to the correct "this" context
}
});
},
columnDefs: [
{headerName: 'Column 1', field: 'column1', checkboxSelection: true},
{headerName: 'Column 2', field: 'column2'},
]
}
}
};
}
Here is an Angular example where you can push the parent component as a reference to the detail grid and set it as its context. Doing this allows you to register a component method as a callback for the detail grid's onRowSelected method.
https://stackblitz.com/edit/ag-grid-detailrow-selectcallback?file=src/app/app.component.ts
Move the onSelectionChanged(params) function to the same level as the detailCellRendererParams object.
internalOnSelectionChanged(params)
{
this.detailGridApi = params.api;
this.detailGridColumnApi = params.columnApi;
console.log(this);
}
Then, on the onSelectionChanged, do this:
onSelectionChanged: this.internalOnSelectionChanged.bind(this);
This solved my issues that were very similar to yours.

agGrid with Angular, using agRichSelectCellEditor

I have an agGrid populated with Employee records in JSON format from my web service.
[
{
id: 123,
firstName: 'Mike',
lastName: 'Jones',
countryId: 1001,
DOB: '1980-01-01T00:00:00',
. . .
}
I have a second web service returning a list of country codes:
[
{ id: 1000, name: 'France' },
{ id: 1001, name: 'Spain' },
{ id: 1002, name: 'Belguim' }
]
What I'm trying to do is get my agGrid to have a column showing the user's details, including the name of their country, and when they edit this cell, a list of country codes will appear, where they can select one, and it'll update the record with the id of that country.
Basic stuff, no ?
But has anyone managed to get agGrid to successfully use the "agRichSelectCellEditor" to do this successfully ?
{ headerName: 'Country', width: 120, field: 'countryId', editable: true,
cellEditor:'agRichSelectCellEditor',
cellEditorParams: {
// This tells agGrid that when we edit the country cell, we want a popup to be displayed
// showing (just) the names of the countries in our reference data
values: listOfCountries.map(s => s.name)
},
// The "cellRenderer" tells agGrid to display the country name in each row, rather than the
// numeric countryId value
cellRenderer: (params) => listOfCountries.find(refData => refData.id == params.data.countryId)?.name,
valueSetter: function(params) {
// When we select a value from our drop down list, this function will make sure
// that our row's record receives the "id" (not the text value) of the chosen selection.
params.data.countryId = listOfCountries.find(refData => refData.name == params.newValue)?.id;
return true;
}
},
My code seems to be almost correct.. it manages to:
display the country name in each row of the agGrid
display a popup, listing the country names, from our "list of countries" array
when I select an item in the popup, it successfully updates the countryId field with the (numeric) id value of my chosen country
The only problem is that at the top of the popup, it shows the countryId value, rather than the user's current country name.
Has anyone managed to get this to work ?
The only workaround I could come up with was to override the CSS on this popup and hide that top element:
.ag-rich-select-value
{
display: none !important;
}
It works... but you no longer get to see what your previously selected value was.
(I really wish the agGrid website had some decent, real-life, working Angular examples... or at least let developers post comments on there, to help each other out.)
The solution was to use a valueGetter, rather than a cellRenderer:
{
headerName: 'Country', width: 120, field: 'countryId', editable: true,
cellEditor:'agRichSelectCellEditor',
cellEditorParams: {
// This tells agGrid that when we edit the country cell, we want a popup to be displayed
// showing (just) the names of the countries in our reference data
values: listOfCountries.map(s => s.name)
},
valueSetter: function(params) {
// When we select a value from our drop down list, this function will make sure
// that our row's record receives the "id" (not the text value) of the chosen selection.
params.data.countryId = listOfCountries.find(refData => refData.name == params.newValue)?.id;
return true;
},
valueGetter: function(params) {
// We don't want to display the raw "countryId" value.. we actually want
// the "Country Name" string for that id.
return listOfCountries.find(refData => refData.id == params.data.countryId)?.name;
}
},
I hope this is useful...
I was able to get my similar situation (id:name pairs in a list, but not using Angular though) working without the problem you mentioned above, and without a valueGetter/valueSetter and only a renderer. The benefit is that you don't need to double click the cell to see the list, the cell appears as a selection box always, and you avoid a bug should the user double click the cell when the list is displayed.
The renderer is a lot clunkier than I was wanting (one line like yours) and it didn't seem that aggrid had built in support for this pretty basic function (and I already have spent enough time on this).
Anyway, this is what I had, which at least works, but keen to see further improvements on it. (You will need to at least change 2 lines for the option related code since my defaultValue object is specific to me).
The column definition:
{field: 'defaultValueID', headerName: "Default Value", cellEditor:'agRichSelectCellEditor', cellRenderer: defaultValueRenderer}
And the renderer code:
function defaultValueRenderer(params) {
var input = document.createElement("select");
// allow it to be cleared
var option = document.createElement("option");
option.innerHTML = '[None]';
option.value = null;
input.appendChild(option);
for (var i=0; i < defaultValueList.length; i++) {
var option = document.createElement("option");
option.innerHTML = defaultValueList[i].name;
option.value = defaultValueList[i].gltID;
input.appendChild(option);
}
input.value = params.value;
input.onchange = function() {
params.setValue(this.value);
params.data.defaultValueID = this.value;
}
input.style="width: 100%; height: 100%"; // default looks too small
return input;
}
Here Is Example Of agRichSelectCellEditor...
{
headerName: 'Dropdown', field: 'dropdown',
cellEditor: 'agRichSelectCellEditor',
width: 140,
editable: true,
cellEditorParams: (params) => {
values: Get All Dropdown List Like ["Hello","Hiii","How Are You?"]
},
valueSetter: (params) => {
if (params.newValue) {
params.data.dropdown= params.newValue;
return true;
}
return false;
}
}
Much simpler solution: use cellEditorParams formatValue, along with valueFormatter
{
field: 'foo',
cellEditor: 'agRichSelectCellEditor',
cellEditorParams: {
values: [1,2,3, 4, other ids... ],
formatValue: (id: number): string => this.getLabelFromId(value)
},
valueFormatter: (params: ValueFormatterParams): string => this.getLabelFromId(params.value as number)
}

Dynamically updating a TinyMCE 4 ListBox

I'm trying to modify the TinyMCE 4 "link" plugin to allow users to select content from ListBox elements that are dynamically updated by AJAX requests.
I'm creating the ListBox elements in advance of editor.windowManager.open(), so they are initially rendered properly. I have an onselect handler that performs the AJAX request, and gets a response in JSON format.
What I need to do with the JSON response is to have it update another ListBox element, replacing the existing items with the new results.
I'm baffled, and the documentation is terribly unclear. I don't know if I should replace the entire control, or delete items and then add new ones. I don't know if I need to instantiate a new ListBox control, or render it to HTML, etc.
Basically, I have access to the original rendered ListBox (name: "module"} with
win.find('#module');
I have the new values from the AJAX request:
var data = tinymce.util.JSON.parse(text).data;
And I've tried creating a new Control configuration object, like
newCtrlconfig = {
type: 'listbox',
label: 'Class',
values: data
};
but I wouldn't know how to render it, much less have it replace the existing one.
I tried
var newList = tinymce.ui.Factory.create(newCtrlconfig);
and then
newList.renderHtml()
but even then, the rendered HTML did not contain any markup for the items. And examining these objects is just frustrating: there are "settings", "values", "_values", "items" all of which will happily store my values, but it isn't even clear which of them will work.
Since it's a ListBox and not a simple SELECT menu, I can't even easily use the DOM to manipulate the values.
Has anyone conquered the TinyMCE ListBox in 4.x?
I found this on the TinyMCE forum and I have confirmed that it works:
tinymce.PluginManager.add('myexample', function(editor, url) {
var self = this, button;
function getValues() {
return editor.settings.myKeyValueList;
}
// Add a button that opens a window
editor.addButton('myexample', {
type: 'listbox',
text: 'My Example',
values: getValues(),
onselect: function() {
//insert key
editor.insertContent(this.value());
//reset selected value
this.value(null);
},
onPostRender: function() {
//this is a hack to get button refrence.
//there may be a better way to do this
button = this;
},
});
self.refresh = function() {
//remove existing menu if it is already rendered
if(button.menu){
button.menu.remove();
button.menu = null;
}
button.settings.values = button.settings.menu = getValues();
};
});
Call following code block from ajax success method
//Set new values to myKeyValueList
tinyMCE.activeEditor.settings.myKeyValueList = [{text: 'newtext', value: 'newvalue'}];
//Call plugin method to reload the dropdown
tinyMCE.activeEditor.plugins.myexample.refresh();
The key here is that you need to do the following:
Get the 'button' reference by taking it from 'this' in the onPostRender method
Update the button.settings.values and button.settings.menu with the values you want
To update the existing list, call button.menu.remove() and button.menu = null
I tried the solution from TinyMCE forum, but I found it buggy. For example, when I tried to alter the first ListBox multiple times, only the first time took effect. Also first change to that box right after dialogue popped up didn't take any effect.
But to the solution:
Do not call button.menu.remove();
Also, the "hack" for getting button reference is quite unnecessary. Your job can be done simply using:
var button = win.find("#button")[0];
With these modification, my ListBoxes work just right.
Whole dialogue function:
function ShowDialog() {
var val;
win = editor.windowManager.open({
title: 'title',
body: {type: 'form',
items: [
{type: 'listbox',
name: 'categorybox',
text: 'pick one',
value: 0,
label: 'Section: ',
values: categories,
onselect: setValuebox(this.value())
},
{type: 'listbox',
name: 'valuebox',
text:'pick one',
value: '',
label: 'Page: ',
values: pagelist[0],
onselect: function(e) {
val = this.value();
}
}
]
},
onsubmit: function(e) {
//do whatever
}
});
var valbox = win.find("#valuebox")[0];
function setValuebox(i){
//feel free to call ajax
valbox.value(null);
valbox.menu = null;
valbox.settings.menu = pagelist[i];
// you can also set a value from pagelist[i]["values"][0]
}
}
categories and pagelist are JSONs generated from DB before TinyMCE load. pagelist[category] = data for ListBox for selected category. category=0 means all.
Hope I helped somebody, because I've been struggling this for hours.
It looks like the tinyMCE version that is included in wordpress 4.3 changed some things, and added a state object that caches the initial menu, so changing the menu is not enough anymore.
One will probably have to update the state object as well. Here is an example of updating the menu with data coming from an ajax request:
editor.addButton('shortcodes', {
icon: 'icon_shortcodes',
tooltip: 'Your tooltip',
type: 'menubutton',
onPostRender: function() {
var ctrl = this;
$.getJSON( ajaxurl , function( menu) {
// menu is the array containing your menu items
ctrl.state.data.menu = ctrl.settings.menu = menu;
});
}
});
As far as I can tell, these other approaches are broken in TinyMCE 4.9.
After spending most of the day tinkering to fix my own usage of these approaches, this is the working function I've found:
function updateListbox(win, data) { // win is a tinymce.ui.Window
listbox = win.find('#listbox'); // Substitute your listbox 'name'
formItem = listbox.parent();
listbox.remove();
formItem.append({
label: 'Dynamic Listbox',
type: 'listbox',
name: 'listbox',
values: data
});
}

Sencha Touch 2: Loading pushed data into form

I got some problems loading data into a form which I pushed onSelect.
(loading details for a specific list item)
onProductSelect: function(_dataView, _record)
{
Ext.getCmp('products').push({
title: _record.data.name,
data: _record.data,
styleHtmlContent: true,
xtype: 'productDetails'
});
}
I am pushing another view (productDetails) onto my productView (which is a navigationView). The data (_record.data) is available in the new pushed view via
tpl: '{someField}'
Now I'd like to know how to get this data (the fields data) into a textfield or a button (or sth like this) of a form.
If someone has a better idea how to get the data into the view/form or how to change the views (from list to detail) please let me know :)
here are some suggestions to your code:
1.use of underbar ('_') inside Sencha Touch is meant for variables which have get/set/apply/update. Although you are using local variables, it is best practice.
2.the word '_record' hopefully is a record. If so then you should use:
name = _record.get('name');
data = _record.getData();
The best way to fill a form is to use a formpanel and add the values to the formpanel, while all fields have a popper name assigned:
If your data are:
data = {name: 'Kurt001', password: '12er51wfA!'}
You could use this:
Ext.define('App.view.ProductDetails', {
extend: 'Ext.form.Panel',
xtype: 'productdetails',
config: {
cls: 'product-details',
scrollable: null,
layout: 'vbox',
defaults: {
labelAlign: 'top',
clearIcon: false
},
items: [{
xtype: 'textfield',
name: 'name'
}, {
xtype: 'passwordfield',
name: 'password'
}, {
xtype: 'button',
itemId: 'btnLogin'
}]
}
});
And to add the data simply use:
Ext.Viewport.down('.productdetails').setValues(data);
Alternative:
var view = Ext.Viewport.down('.productdetails')
view.down('.textfield').setValue(data.name);
view.down('.passwordfield').setValue(data.password);
Alternative
view.down('.field[name=name]').setValue(data.name);
view.down('.field[name=password]').setValue(data.password);
To get the data from one view to the next you can follow different options:
1.Set the data to the current view and grab them from that view. It looks like you have a list. So you can apply the data to the list:
view.down('.list').myData = data;
Extended version would be to create a custom list with myData inside the config.
That way you could use:
view.down('.list').setMyData(data);
or in your case
_dataview.setMyData(data);
2.Use a store. As you are passing a record already you might want to add a selected field to your store model and simply set the flag.

ExtJs 4 dynamic form fields in nested panels aren't submitted

Let's say I have multiple nested panels (plain ones, not form.Panel) containing form fields.
The user can copy such a panel and its fields to a different panel.
I somehow need to add those newly created fields to a main formpanel so they get submitted, but don't know how.
I can't do
formpanel.add(fields)
because then they're rendered to the formpanel's body and not the panel they were in in the first place. Setting the fields' renderTo property doesn't help either.
So basically I need a way of adding a field to a normal panel (or any other component for that matter), but also adding it to a specific formpanel so its values are submitted on form submit.
Has anyone done this or could at least point me in the right direction?
You can wrap all your panels inside one form panel and all the fields inside will be submitted. I did this way with accordeons, tabpanel and normal panels deeply nested inside each other. Example:
var form = Ext.create('Ext.form.Panel', {
// form attributes goes here...
// ...
initComponent: function(){
// all your panels goes here.
this.items = [
{
xtype:'panel',
title: 'First panel',
layout: 'anchor',
frame: true,
defaults: {anchor: '100%'},
items: [
{xtype:'textfield', name: 'foo',fieldLabel: 'Foo'},
{xtype:'textfield', name: 'foo',fieldLabel: 'Bar'}
]
},
{
xtype:'panel',
title: 'Second panel',
layout: 'anchor',
frame: true,
defaults: {anchor: '100%'},
items: [
{xtype:'textfield', name: 'baz',fieldLabel: 'Baz'},
{
xtype: 'panel',
items: [/* another set of fields */]
}
]
},
];
this.buttons = [/* ... your buttons here ...*/];
this.callParent(arguments);
}
});