Change Kendo grid row on click - select

I'm hoping someone can offer help in this. I have a Kendo grid in a html document (no MVC), and am wanting to change the class of the entire row on row select. I have tried various approaches, still with no luck. I am currently at:
// within kendo grid definition - grid called '#grid'
change: function (e) {
$("#grid tbody").find("tr[k-state-selected]").css("color", "black");
var id = $("#grid").closest("tr").css("color", "black");
CallDocument(this._data[0]);
},
The function CallDocument is being fired, and so I know I can at least get to the function.
EDIT: Here is the solution that I came up with, and thanks to everyone
change: function (e) {
$("#grid tbody").find("tr.k-state-selected").attr("class", "detail read k-state-selected");
},
I needed to use the 'tr.k-state-selected' form, and change using attr in order to change the set of classes.

To mark every visited row as selected, you might add a CSS class on change event.
var grid = $("#grid").kendoGrid({
dataSource: ds,
editable : false,
pageable : true,
selectable: true,
columns :
[
{ field: "FirstName", width: 90, title: "First Name" },
{ field: "LastName", width: 200, title: "Last Name" },
{ field: "City", width: 200 }
],
change : function (e) {
this.select().addClass("ob-selected");
}
}).data("kendoGrid");
The class ob-selected stays when you move to another cell since this does nothing to do with KendoUI.
Example here : http://jsfiddle.net/2TGLp/1/
The only question is that it does not stay selected if you apply filters, change to a different page... but not sure if this is important for you.

I override my Kendo styles using both css and javascript (depending on the scenario).
CSS:
.k-state-selected {
color: black;
}
Javascript/jQuery:
$('k-state-selected').css('color', '#000000')

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)
}

Vizframe Datalabel show for one Line

I'm using Vizframe component where there is two lines, when I try to show the Datalabel, the modifications is applied to both lines, I want to apply it just to one of them (A line with Datalabel set to True and a line with DataLabel set to False.)
Here's how it looks
This is how I change DataLabel property:
plotArea: {
dataLabel: {
visible: false
},
},
Well I know that this question might be older, but I had a similar issue recently. You can use the renderer function to customize the data labels to your needs.
For me, it worked, when I checked the context data for the correct measure and decide then, if I return nothing, which shows the label normally or return an empty DOM node.
dataLabel: {
visible: true,
style: {
color: "#000"
},
renderer: function (ctx) {
var node = document.createElement("text");
if (ctx.ctx.measureNames === "Optimum") {
var text = document.createTextNode("");
node.appendChild(text);
return node;
}
}
},
As you can see in the picture below, the label for the orange line (Optimum) is gone.
You can hide the values when they are overlapped using this:
plotArea: {
dataLabel: {
visible: true,
hideWhenOverlap: true
}
}
Julian's answer is useful yet in order for the change to take effect on the document we need to attach the returned node to the parent data point node.
The renderer function does not provide data regarding the graph itself but only the selected label, which leaves us clueless on how to render the node effectively.
Something like this would make me happier:
renderer: function(...) {
var parent = document.getElementById(some_given_data_point_id);
parent.appendChild(text_node);
return parent;
}

Dynamic Carousel Content does not show

I have been working on this for a number of days now, but my limited JS knowledge seems to hurt me.
I am creating a dynamic Ext.Carousel component in my ST2 application, which is based on the contents of a Store file.
That all works fine, but I will show the code anyway, so that nothing is left to imagination:
Ext.getStore('DeviceStore').load(
function(i) {
Ext.each(i, function(i) {
if (i._data.name == 'Audio Ring') {
var carousel = Ext.ComponentManager.get('speakerCarousel');
var items = [];
Ext.each(i.raw.speakers, function(speaker) {
items.push({
sci: Ext.create('SmartCore.view.SpeakerCarouselItem', {
speakerId: speaker.speakerid,
speakerName: speaker.speakername,
speakerEnabled: speaker.speakerenabled
})
});
});
carousel.setItems(items);
}
});
})
Now, this adds me the appropriate number of items to the carousel. They display, but without the content I specified:
This is the Carousel itself:
Ext.define('SmartCore.view.SpeakerCarousel', {
extend: 'Ext.Carousel',
xtype: 'speakerCarousel',
config: {
id: 'speakerCarousel',
layout: 'fit',
listeners: {
activeitemchange: function(carousel, item) {
console.log(item);
}
}
}
});
This is the item class, that I want to fill the data from the store into:
Ext.define("SmartCore.view.SpeakerCarouselItem", {
extend: Ext.Panel,
xtype: 'speakerCarouselItem',
config: {
title:'SpeakerCarouselItem',
styleHtmlContent: true,
layout: 'fit'
},
constructor : function(param) {
this.callParent(param);
this.add(
{
layout: 'panel',
style: 'background-color: #759E60;',
html: 'hello'
}
)
}
});
Again, the right number of items shows in the carousel (11), but the content is not visible, nor is the background colour changed.
When I check the console.log(item) in the browser, the items show as innerItems inside the carousel object.
Any help is greatly appreciated!!
Well, I fixed it myself, or better, I found a workaround that seems to be what I want.
I ended up ditching the constructor all together.
Instead I overwrote the apply method for the 'speakerName' key-value pair.
From there, I can use:
this._items.items[0]._items.items[i].setWhatever(...)
to set the content inside the item.
If anyone knows the "real" way to do this, I would still greatly appreciate input!

How to create custom ExtJS form field component?

I want to create custom ExtJS form field components using other ExtJS components in it (e.g. TreePanel). How can I do it most easily?
I've read docs of Ext.form.field.Base but I don't want to define field body by fieldSubTpl. I just want to write code which creates ExtJS components and maybe some other code which gets and sets values.
Update: Summarized purposes are the followings:
This new component should fit in the
form GUI as a field. It should have
label and the same alignment (label,
anchor) of other fields without need
of further hacking.
Possibly, I have
to write some getValue, setValue
logic. I'd rather embed it into this component than making separated code which copies things into further hidden form fields that I also have to manage.
To extend #RobAgar 's answer, following a really simple Date Time field that I wrote for ExtJS 3 and it's quickport that I made for ExtJS 4. The important thing is the use of the Ext.form.field.Field mixin. This mixin provides a common interface for the logical behavior and state of form fields, including:
Getter and setter methods for field values
Events and methods for tracking value and validity changes
Methods for triggering validation
This can be used for combining multiple fields and let act them as one. For a total custom fieldtype I recommend to extend Ext.form.field.Base
Here is the example that I mentioned above. It should shoe how easy this can be done even for something like a date object where we need to format the data within the getter and setter.
Ext.define('QWA.form.field.DateTime', {
extend: 'Ext.form.FieldContainer',
mixins: {
field: 'Ext.form.field.Field'
},
alias: 'widget.datetimefield',
layout: 'hbox',
width: 200,
height: 22,
combineErrors: true,
msgTarget: 'side',
submitFormat: 'c',
dateCfg: null,
timeCfg: null,
initComponent: function () {
var me = this;
if (!me.dateCfg) me.dateCfg = {};
if (!me.timeCfg) me.timeCfg = {};
me.buildField();
me.callParent();
me.dateField = me.down('datefield')
me.timeField = me.down('timefield')
me.initField();
},
//#private
buildField: function () {
var me = this;
me.items = [
Ext.apply({
xtype: 'datefield',
submitValue: false,
format: 'd.m.Y',
width: 100,
flex: 2
}, me.dateCfg),
Ext.apply({
xtype: 'timefield',
submitValue: false,
format: 'H:i',
width: 80,
flex: 1
}, me.timeCfg)]
},
getValue: function () {
var me = this,
value,
date = me.dateField.getSubmitValue(),
dateFormat = me.dateField.format,
time = me.timeField.getSubmitValue(),
timeFormat = me.timeField.format;
if (date) {
if (time) {
value = Ext.Date.parse(date + ' ' + time, me.getFormat());
} else {
value = me.dateField.getValue();
}
}
return value;
},
setValue: function (value) {
var me = this;
me.dateField.setValue(value);
me.timeField.setValue(value);
},
getSubmitData: function () {
var me = this,
data = null;
if (!me.disabled && me.submitValue && !me.isFileUpload()) {
data = {},
value = me.getValue(),
data[me.getName()] = '' + value ? Ext.Date.format(value, me.submitFormat) : null;
}
return data;
},
getFormat: function () {
var me = this;
return (me.dateField.submitFormat || me.dateField.format) + " " + (me.timeField.submitFormat || me.timeField.format)
}
});
Now that's cool. The other day, I created a fiddle to answer another question before realizing I was off-topic. And here your are, finally bringing to my attention the question to my answer. Thanks!
So, here are the steps required in implementing a custom field from another component:
Creating the child component
Render the child component
Ensuring the child component is sized and resized correctly
Getting and setting value
Relaying events
Creating the child component
The first part, creating the component, is easy. There's nothing particular compared to creating a component for any other usage.
However, you must create the child in the parent field's initComponent method (and not at rendering time). This is because external code can legitimately expect that all dependent objects of a component are instantiated after initComponent (e.g. to add listeners to them).
Furthermore, you can be kind to yourself and create the child before calling the super method. If you create the child after the super method, you may get a call to your field's setValue method (see bellow) at a time when the child is not yet instantiated.
initComponent: function() {
this.childComponent = Ext.create(...);
this.callParent(arguments);
}
As you see, I am creating a single component, which is what you'll want in most case. But you can also want to go fancy and compose multiple child components. In this case, I think it would be clever to back to well known territories as quickly as possible: that is, create one container as the child component, and compose in it.
Rendering
Then comes the question of rendering. At first I considered using fieldSubTpl to render a container div, and have the child component render itself in it. However, we don't need the template features in that case, so we can as well bypass it completely using the getSubTplMarkup method.
I explored other components in Ext to see how they manage the rendering of child components. I found a good example in BoundList and its paging toolbar (see the code). So, in order to obtain the child component's markup, we can use Ext.DomHelper.generateMarkup in combination with the child's getRenderTree method.
So, here's the implementation of getSubTplMarkup for our field:
getSubTplMarkup: function() {
// generateMarkup will append to the passed empty array and return it
var buffer = Ext.DomHelper.generateMarkup(this.childComponent.getRenderTree(), []);
// but we want to return a single string
return buffer.join('');
}
Now, that's not enough. The code of BoundList learns us that there's another important part in component rendering: calling the finishRender() method of the child component. Fortunately, our custom field will have its own finishRenderChildren method called just when that needs to be done.
finishRenderChildren: function() {
this.callParent(arguments);
this.childComponent.finishRender();
}
Resizing
Now our child will be rendered in the right place, but it will not respect its parent field size. That is especially annoying in the case of a form field, because that means it won't honor the anchor layout.
That's very straightforward to fix, we just need to resize the child when the parent field is resized. From my experience, this is something that was greatly improved since Ext3. Here, we just need to not forget the extra space for the label:
onResize: function(w, h) {
this.callParent(arguments);
this.childComponent.setSize(w - this.getLabelWidth(), h);
}
Handling value
This part will, of course, depend on your child component(s), and the field you're creating. Moreover, from now on, it's just a matter of using your child components in a regular way, so I won't detail this part too much.
A minima, you also need to implement the getValue and setValue methods of your field. That will make the getFieldValues method of the form work, and that will be enough to load/update records from the form.
To handle validation, you must implement getErrors. To polish this aspect, you may want to add a handful of CSS rules to visually represent the invalid state of your field.
Then, if you want your field to be usable in a form that will be submitted as an actual form (as opposed to with an AJAX request), you'll need getSubmitValue to return a value that can be casted to a string without damage.
Apart from that, as far as I know, you don't have to worry about the concept or raw value introduced by Ext.form.field.Base since that's only used to handle the representation of the value in an actual input element. With our Ext component as input, we're way off that road!
Events
Your last job will be to implement the events for your fields. You will probably want to fire the three events of Ext.form.field.Field, that is change, dirtychange and validitychange.
Again, the implementation will be very specific to the child component you use and, to be honest, I haven't explored this aspect too much. So I'll let you wire this for yourself.
My preliminary conclusion though, is that Ext.form.field.Field offers to do all the heavy lifting for you, provided that (1) you call checkChange when needed, and (2) isEqual implementation is working with your field's value format.
Example: TODO list field
Finally, here's a complete code example, using a grid to represent a TODO list field.
You can see it live on jsFiddle, where I tries to show that the field behaves in an orderly manner.
Ext.define('My.form.field.TodoList', {
// Extend from Ext.form.field.Base for all the label related business
extend: 'Ext.form.field.Base'
,alias: 'widget.todolist'
// --- Child component creation ---
,initComponent: function() {
// Create the component
// This is better to do it here in initComponent, because it is a legitimate
// expectationfor external code that all dependant objects are created after
// initComponent (to add listeners, etc.)
// I will use this.grid for semantical access (value), and this.childComponent
// for generic issues (rendering)
this.grid = this.childComponent = Ext.create('Ext.grid.Panel', {
hideHeaders: true
,columns: [{dataIndex: 'value', flex: 1}]
,store: {
fields: ['value']
,data: []
}
,height: this.height || 150
,width: this.width || 150
,tbar: [{
text: 'Add'
,scope: this
,handler: function() {
var value = prompt("Value?");
if (value !== null) {
this.grid.getStore().add({value: value});
}
}
},{
text: "Remove"
,itemId: 'removeButton'
,disabled: true // initial state
,scope: this
,handler: function() {
var grid = this.grid,
selModel = grid.getSelectionModel(),
store = grid.getStore();
store.remove(selModel.getSelection());
}
}]
,listeners: {
scope: this
,selectionchange: function(selModel, selection) {
var removeButton = this.grid.down('#removeButton');
removeButton.setDisabled(Ext.isEmpty(selection));
}
}
});
// field events
this.grid.store.on({
scope: this
,datachanged: this.checkChange
});
this.callParent(arguments);
}
// --- Rendering ---
// Generates the child component markup and let Ext.form.field.Base handle the rest
,getSubTplMarkup: function() {
// generateMarkup will append to the passed empty array and return it
var buffer = Ext.DomHelper.generateMarkup(this.childComponent.getRenderTree(), []);
// but we want to return a single string
return buffer.join('');
}
// Regular containers implements this method to call finishRender for each of their
// child, and we need to do the same for the component to display smoothly
,finishRenderChildren: function() {
this.callParent(arguments);
this.childComponent.finishRender();
}
// --- Resizing ---
// This is important for layout notably
,onResize: function(w, h) {
this.callParent(arguments);
this.childComponent.setSize(w - this.getLabelWidth(), h);
}
// --- Value handling ---
// This part will be specific to your component of course
,setValue: function(values) {
var data = [];
if (values) {
Ext.each(values, function(value) {
data.push({value: value});
});
}
this.grid.getStore().loadData(data);
}
,getValue: function() {
var data = [];
this.grid.getStore().each(function(record) {
data.push(record.get('value'));
});
return data;
}
,getSubmitValue: function() {
return this.getValue().join(',');
}
});
Heh. After posting the bounty I found out that Ext.form.FieldContainer isn't just a field container, but a fully fledged component container, so there is a simple solution.
All you need to do is extend FieldContainer, overriding initComponent to add the child components, and implement setValue, getValue and the validation methods as appropriate for your value data type.
Here's an example with a grid whose value is a list of name/value pair objects:
Ext.define('MyApp.widget.MyGridField', {
extend: 'Ext.form.FieldContainer',
alias: 'widget.mygridfield',
layout: 'fit',
initComponent: function()
{
this.callParent(arguments);
this.valueGrid = Ext.widget({
xtype: 'grid',
store: Ext.create('Ext.data.JsonStore', {
fields: ['name', 'value'],
data: this.value
}),
columns: [
{
text: 'Name',
dataIndex: 'name',
flex: 3
},
{
text: 'Value',
dataIndex: 'value',
flex: 1
}
]
});
this.add(this.valueGrid);
},
setValue: function(value)
{
this.valueGrid.getStore().loadData(value);
},
getValue: function()
{
// left as an exercise for the reader :P
}
});
I've done this a few times. Here is the general process/pseudo-code I use:
Create an extension of field that provides the most useful re-use (typically Ext.form.TextField if you just want to get/set a string value)
In the afterrender of the field, hide the textfield, and create a wrapping element around this.el with this.wrap = this.resizeEl = this.positionEl = this.el.wrap()
Render any components to this.wrap (e.g. using renderTo: this.wrap in the config)
Override getValue and setValue to talk to the component(s) you rendered manually
You may need to do some manually sizing in a resize listener if your form's layout changes
Don't forget to cleanup any components you create in the beforeDestroy method!
I can't wait to switch our codebase to ExtJS 4, where these kinds of things are easy.
Good luck!
Since the question was asked rather vague - I only can provide the basic pattern for ExtJS v4.
Even if it's not too specific, it has the advance that it's rather universal like this:
Ext.define('app.view.form.field.CustomField', {
extend: 'Ext.form.field.Base',
requires: [
/* require further components */
],
/* custom configs & callbacks */
getValue: function(v){
/* override function getValue() */
},
setValue: function(v){
/* override function setValue() */
},
getSubTplData: [
/* most likely needs to be overridden */
],
initComponent: function(){
/* further code on event initComponent */
this.callParent(arguments);
}
});
The file /ext/src/form/field/Base.js provides the names of all configs and functions that can be overridden.
Following the documentation at http://docs.sencha.com/ext-js/4-0/#/api/Ext.form.field.Base
This code will create a reusable TypeAhead/Autocomplete style field for selecting a language.
var langs = Ext.create( 'Ext.data.store', {
fields: [ 'label', 'code' ],
data: [
{ code: 'eng', label: 'English' },
{ code: 'ger', label: 'German' },
{ code: 'chi', label: 'Chinese' },
{ code: 'ukr', label: 'Ukranian' },
{ code: 'rus', label: 'Russian' }
]
} );
Ext.define( 'Ext.form.LangSelector', {
extend: 'Ext.form.field.ComboBox',
alias: 'widget.LangSelector',
allowBlank: false,
hideTrigger: true,
width: 225,
displayField: 'label',
valueField: 'code',
forceSelection: true,
minChars: 1,
store: langs
} );
You can use the field in a form simply by setting the xtype to the widget name:
{
xtype: 'LangSelector'
fieldLabel: 'Language',
name: 'lang'
}
Many of the answers either use the Mixin Ext.form.field.Field or just extends on some already made class that suits their needs - which is fine.
But I do not recommend fully overwriting the setValue method, that is IMO really bad form!
A lot more happens than just setting and getting the value, and if you fully overwrite it - well you will for instance mess up the dirty state, processing of rawValue etc..
Two options here I guess, one is to callParent(arguments) inside the method you declare to keep things streamlined, or to at the end when you are done apply the inherited method from where ever you got it (mixin or extend).
But do not just overwrite it with no regards for what that already made method does behind the scenes.
Also remember that if you use other field types in your new class - then do set the isFormField property to false - otherwise your getValues method on the form will take those values and run with em!
Another solution could be this tree-field implementation.
It behaves just like a normal form field:
https://github.com/wencywww/Ext.ux.form.field.Tree
Here is an example of a custom panel that extends an Ext Panel. You can extend any component, check the docs for the fields, methods and events you can play with.
Ext.ns('yournamespace');
yournamespace.MyPanel = function(config) {
yournamespace.MyPanel.superclass.constructor.call(this, config);
}
Ext.extend(yournamespace.MyPanel, Ext.Panel, {
myGlobalVariable : undefined,
constructor : function(config) {
yournamespace.MyPanel.superclass.constructor.apply(this, config);
},
initComponent : function() {
this.comboBox = new Ext.form.ComboBox({
fieldLabel: "MyCombo",
store: someStore,
displayField:'My Label',
typeAhead: true,
mode: 'local',
forceSelection: true,
triggerAction: 'all',
emptyText:'',
selectOnFocus:true,
tabIndex: 1,
width: 200
});
// configure the grid
Ext.apply(this, {
listeners: {
'activate': function(p) {
p.doLayout();
},
single:true
},
xtype:"form",
border: false,
layout:"absolute",
labelAlign:"top",
bodyStyle:"padding: 15px",
width: 350,
height: 75,
items:[{
xtype:"panel",
layout:"form",
x:"10",
y:"10",
labelAlign:"top",
border:false,
items:[this.comboBox]
},
{
xtype:"panel",
layout:"form",
x:"230",
y:"26",
labelAlign:"top",
border:false,
items:[{
xtype:'button',
handler: this.someAction.createDelegate(this),
text: 'Some Action'
}]
}]
}); // eo apply
yournamespace.MyPanel.superclass.initComponent.apply(this, arguments);
this.comboBox.on('select', function(combo, record, index) {
this.myGlobalVariable = record.get("something");
}, this);
}, // eo function initComponent
someAction : function() {
//do something
},
getMyGlobalVariable : function() {
return this.myGlobalVariable;
}
}); // eo extend
Ext.reg('mypanel', yournamespace.MyPanel);
Could you describe the UI requirements that you have a bit more? Are you sure that you even need to do build an entire field to support the TreePanel? Why not set the value of a hidden field (see the "hidden" xtype in the API) from a click handler on a normal tree panel?
To answer your question more fully, you can find many tutorials on how to extend ExtJS components. You do this by leveraging the Ext.override() or Ext.Extend() methods.
But my feeling is that you may be over-complicating your design. You can achieve what you need to do by setting a value to this hidden field. If you have complex data, you can set the value as some XML or JSON string.
EDIT Here's a few tutorials. I highly recommend going with the KISS rule when it comes to your UI design. Keep It Simple Stupid!
Extending components using panels