How to Set Width of sap.m.MessagePopover? - sapui5

The control sap.m.MessagePopover has an attribute _oPopover (containing sap.m.Popover inside).
Using this attribute, I could set the popover width:
messagePopover._oPopover.setContentWidth("450px");
However, as SAP attributes starting from _ should not be used, does anybody know a cleaner way?

As of UI5 version 1.46, a more flexible control sap.m.MessageView can be used instead of the old sap.m.MessagePopover.
There is no need to access internal properties or apply custom CSS style classes to manipulate the width as you can put MessageView anywhere you want (Still, Fiori Guideline recommends to use it only within a responsive popover or a dialog).
const popover = new ResponsivePopover({
contentWidth: "450px",
contentHeight: "450px",
content: [
/*myMessageView*/
],
});
// ...
popover.openBy(...);
Compared to MessagePopover, MessageView can group items and more.
Internally, MessagePopover uses MessageView too.

Another solution would be to use CSS class. However, there is a catch. As you can see from below generated DOM of the message popover, inline styling has been used :( .
Only way to override inline-style is by using !important in CSS which is again not recommended approach. However, considering inline CSS has been used, I would go with using !important keyword. Below is the working code:
XML Code ( for adding Class):
<MessagePopover id='myMP' class='myPopoverClass'>
<items>
<MessagePopoverItem title='Title' subTitle='SubTitle'></MessagePopoverItem>
</items>
</MessagePopover>
CSS:
.myPopoverClass {
width:100rem;
}
.myPopoverClass .sapMPopoverCont {
width:100% !important;
}
You can play around with how much width you need for message Popover.
EDIT: This is from the source code:
MessagePopover.prototype.init = function () {
var that = this;
var oPopupControl;
this._oResourceBundle = sap.ui.getCore().getLibraryResourceBundle("sap.m");
this._oPopover = new ResponsivePopover(this.getId() + "-messagePopover", {
showHeader: false,
contentWidth: "440px",
placement: this.getPlacement(),
showCloseButton: false,
modal: false,
afterOpen: function (oEvent) {
that.fireAfterOpen({openBy: oEvent.getParameter("openBy")});
},
afterClose: function (oEvent) {
that._navContainer.backToTop();
that.fireAfterClose({openBy: oEvent.getParameter("openBy")});
},
beforeOpen: function (oEvent) {
that.fireBeforeOpen({openBy: oEvent.getParameter("openBy")});
},
beforeClose: function (oEvent) {
that.fireBeforeClose({openBy: oEvent.getParameter("openBy")});
}
}).addStyleClass(CSS_CLASS);

Related

Bound content in Popover is not shown

I created a popover which is called on mouseover via addEventDelegate.
The popover has a text element with binding to my Model.
The fragment:
<Popover xmlns="sap.m"
id="myCardPopover"
showHeader="false"
contentWidth="260px"
placement="Bottom">
<Text id="myCardPopoverText" text="{propertyModel>Tooltip}" />
</Popover>
Register the browser events:
attachCardPopoverOnMouseover: function () {
var rootHBox = this.byId("myReportCardContent");
rootHBox.addEventDelegate({
onmouseover: function () {
this.timeId = setTimeout(() => that.onOpenCardPopover(this), 600);
},
onmouseout: function () {
clearTimeout(this.timeId) || that.onCloseCardPopover(this);
}
}, rootHBox);
},
The event listener:
onOpenCardPopover: function (oControl) {
this.oCardTooltipPopover.then(function (oPopover) {
var oContext = oControl.getBindingContext("propertyModel");
oPopover.setBindingContext(oContext);
oPopover.openBy(oControl);
});
},
The Popover itself is a dependent of multiple controls created from an aggregation binding and is created in onAfterRendering.
// Fragment required from "sap/ui/core/Fragment"
createCardPopover: function () {
var oView = this.getView();
var oRootHBox = this.byId("myReportCardContent");
if (!this.oCardTooltipPopover) {
this.oCardTooltipPopover = Fragment.load({
id: oView.getId(),
name: "namespace.view.CardPopup"
}).then(function (oPopover) {
oRootHBox.addDependent(oPopover);
return oPopover;
});
}
},
When I hover over one of my controls, I only get an empty popover. The bound text isn't shown.
However, when I lookup the created popover in the UI5 debug tools, the binding seems correct and also the text is shown up there.
The text's <span> element in the DOM is also empty.
[...] multiple controls created from an aggregation binding
With this.byId("myReportCardContent"), you're accessing the template control rather than the actual rendered ones. A template in aggregation binding is usually a static control (i.e. without propagated models and contexts) that the ManagedObject will clone for each data object. After cloning, the parent models and contexts are propagated to those clones, and not to the internal template control.
So if you add the popover to the dependent aggregation of the template (oRootHBox), there is no context to propagate. The binding paths in the fragment definition stay unresolved.
How the issue can be circumvented is up to you. There are many approaches to show something on mouseover. Just avoid manipulating the template after rendering.

The flexible column layout arrow does not work properly

I have created a flexible column layout and unfortunately it does not work properly.
When I want to expand the left part, I have to click on arrow twice instead once:
I am trying to figure out, but unfortunately could not find the error.
The view of Flexible Column Layout:
<mvc:View xmlns="sap.f" xmlns:mvc="sap.ui.core.mvc" xmlns:m="sap.m" displayBlock="true" controllerName="io.example.fclpoc.controller.App"
height="100%">
<FlexibleColumnLayout id="fcl" stateChange="onStateChanged" layout="{/layout}" backgroundDesign="Solid"></FlexibleColumnLayout>
</mvc:View>
and the controller:
sap.ui.define([
"sap/ui/model/json/JSONModel",
"sap/ui/core/ResizeHandler",
"sap/ui/core/mvc/Controller",
"sap/f/FlexibleColumnLayout"
], function (JSONModel, ResizeHandler, Controller, FlexibleColumnLayout) {
"use strict";
return Controller.extend("io.example.fclpoc.controller.App", {
onInit: function () {
this.oRouter = this.getOwnerComponent().getRouter();
this.oRouter.attachRouteMatched(this.onRouteMatched, this);
this.oRouter.attachBeforeRouteMatched(this.onBeforeRouteMatched, this);
},
onBeforeRouteMatched: function (oEvent) {
var oModel = this.getOwnerComponent().getModel();
var sLayout = oEvent.getParameters().arguments.layout;
// If there is no layout parameter, query for the default level 0 layout (normally OneColumn)
if (!sLayout) {
var oNextUIState = this.getOwnerComponent().getHelper().getNextUIState(0);
sLayout = oNextUIState.layout;
}
// Update the layout of the FlexibleColumnLayout
if (sLayout) {
oModel.setProperty("/layout", sLayout);
}
},
_updateLayout: function (sLayout) {
var oModel = this.getOwnerComponent().getModel();
// If there is no layout parameter, query for the default level 0 layout (normally OneColumn)
if (!sLayout) {
var oNextUIState = this.getOwnerComponent().getHelper().getNextUIState(0);
sLayout = oNextUIState.layout;
}
// Update the layout of the FlexibleColumnLayout
if (sLayout) {
oModel.setProperty("/layout", sLayout);
}
},
onRouteMatched: function (oEvent) {
var sRouteName = oEvent.getParameter("name"),
oArguments = oEvent.getParameter("arguments");
this._updateUIElements();
// Save the current route name
this.currentRouteName = sRouteName;
},
onStateChanged: function (oEvent) {
var bIsNavigationArrow = oEvent.getParameter("isNavigationArrow"),
sLayout = oEvent.getParameter("layout");
this._updateUIElements();
// Replace the URL with the new layout if a navigation arrow was used
if (bIsNavigationArrow) {
this.oRouter.navTo(this.currentRouteName, {
layout: sLayout
}, true);
}
},
// Update the close/fullscreen buttons visibility
_updateUIElements: function () {
var oModel = this.getOwnerComponent().getModel();
var oUIState = this.getOwnerComponent().getHelper().getCurrentUIState();
oModel.setData(oUIState);
},
onExit: function () {
this.oRouter.detachRouteMatched(this.onRouteMatched, this);
this.oRouter.detachBeforeRouteMatched(this.onBeforeRouteMatched, this);
}
});
});
I looked also in the debug console:
However no errors occur. I have also compare my code with https://sapui5.hana.ondemand.com/#/entity/sap.f.FlexibleColumnLayout/sample/sap.f.sample.FlexibleColumnLayoutWithTwoColumnStart/code/webapp/controller/FlexibleColumnLayout.controller.js and could not find differences.
What am I doing wrong?
The app can be found here https://github.com/softshipper/fclpoc
Update
I have run the app in my edge browser and it does not have any extension installed. The behavior is the same.
Here is the console output of edge:
This is less a direct answer to the question "why does my app do that". It's more of a help to self-help.
Basically, if you put a break point in each of the methods in your App controller, you will see that the layout is moving in the correct position first, then it is moving back in the incorrect position (it happens so fast that you dont see without debugger).
The layout is being set several times in the whole process. sometimes changing nothing, sometimes not. In the end, one of your methods sets the wrong layout.
PS: you have a semantic error, not a syntactic one (the app does what you asked it to do), so there are no errors in the console.

AG-Grid render Bootstrap-Select as a dropdwon

I have implemented editable AG-Grid. In the grid, one of the column displays country of the player as shown below:
Now in last column, when user click on the cell, I want to display list of available countries as a dropdown.
Here by default AG-Grid displays normal dropdown. Which I want to replace with Bootstrap-select.
To achieve this, I have implemented custom selector and using Bootstrap-select library.
But when cell is clicked, Dropdown is not being rendered. I am not getting any error though in console.
Here is the code of my custom cell-editor:
var selectCellEdior = function () { };
selectCellEdior.prototype = {
init: function (params) {
selector = document.createElement('select');
for(var i = 0; i < params.values.length; i++) {
var option = params.values[i];
$('<option />', { value: option, text: option }).appendTo(selector);
}
this.cellSelector = selector;
$(this.cellSelector).selectpicker({ size: 'auto' });
},
getValue: function () {
return $(this.cellSelector).find('.btn-text').text();
},
getGui: function () {
return this.cellSelector;
},
destroy: function () {
$(this.cellSelector).remove();
}
};
Here is the Sample Code
I dont understand what is the issue.
The problem is the selectpicker jQuery method seems to add a display: none to the select element. Ag-Grid is adding it to the DOM you just can't see it. Additionally the getValue function is not returning the selected option's text.
The following changes to getValue and getGui will make the grid work:
...
getValue: function () {
return $(this.cellSelector).val();
},
getGui: function () {
this.cellSelector.style.display = 'block';
return this.cellSelector;
},
...
Here is the modified Plunker
For anyone looking at this now.... Ag-grid has a method afterGuiAttached that is called after the GUI has been attached to the Grid's cell. If you're noticing that the bootstrap-select still isn't "quite right" after following the instructions above, put ALL of the calls to selectpicker in afterGuiAttached instead of init or getGui. This will place the bootstrap-select into a div with the right classes it needs (dropdown, bootstrap-select, etc), and create all of the bootstrap-select elements that are properly shown/hidden.

Ionic2 Popover Change Width

I want to change the popover width of only 1 page.
I tried this:
I do not want to use this $popover-ios-width in variable.scss files
since it changes the width on all pages.
You can create a custom class for only this popover and pass it in the cssClass on it's options
let's say you have this:
.custom-popover {
width: 60%;
}
In your popover creation you'll just insert it as the cssClass on the options
const customPopOver = this.popover.create({ yourPopoverPage, yourData, { cssClass: 'custom-popover'}});
customPopOver.present();
Hope this helps :D
#gabriel-barreto's answer is totally fine. However, I guess it needs an additional consideration in other versions.
In Ionic 3.6 the custom class is added to the <ion-popover> element. In order to override the default width rule set in .popover-content class, your selector should be:
.custom-popover .popover-content {
width: 60%;
}
Add this below code in variables.scss. works for me in Ionic 4.
ion-popover {
.popover-wrapper {
.popover-content {
width: fit-content;
max-width: 300px;
}
}
}
I do not know if it will still suit you, I found the same problem, but modifying the global variable scss configuration did not help me because I used multiple PopoverControllers in the project the only way I managed to do it is the following:
Place the ion-content as a parent element and add the id attribute
Get Parent Items to the Main Container:
ionViewDidLoad() {
let element = document.getElementById('id')
let parent=element.parentElement
let parent2 = parent.parentElement //popover-content
parent2.parentElement.style['width'] = "92px"
}
Try this...
myPopOver = this.popovercontroller.create({ component: MyCustomComponent,
event:ev,
cssClass: 'my-custom-popover'
})
myPopOver.present();
add my-custom-popover class in global.css file.
const customPopOver = this.popover.create({ yourPopoverPage, yourData, { cssClass: 'custom-popover'}});
You need to create popover and add cssClass prop as on code above, then you can use 'custom-popover' class in variables.scss file like below.
.custom-popover {
.popover-wrapper {
.popover-content {
width: 465px;
}
}
}

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