Saving state of hbox widths after splitter move - extjs

I'm trying to save the visual state of a panel. The Panel consists of two child containers, with hbox/flex layout and a splitter between them.
{
xtype:'panel',
layout: {
align: 'stretch',
type: 'hbox'
},
items:[
{
xtype:'container',
title: 'left panel',
flex:1
},
{
xtype:'splitter'
},
{
xtype:'container',
title: 'left panel',
flex:2
}
]
}
I already have a working state manager. The "left panel" contains a grid, and the grid is storing it's column states just fine. The state manager fires from a controllers init function by:
var stateProvider=Ext.create('Ext.state.LocalStorageProvider');
Ext.state.Manager.setProvider(stateProvider);
The grid uses the framework standard approach for storing it's state by setting stateId: 'GridState' and stateful:true.
However I'm unable to figure out how ExtJS want's me to do the same with the flex values of the main layout. I've tried setting stateful and stateId on the splitter. I've tried without events, and with stateEvents: ['move']. I've also tried setting stateful on the leftpanel and with stateEvents to resize. Finally I've tried setting stateful on the parent panel, with and without stateEvents: afterlayout.
I do know that it's possible to fetch the event after the splitter is moved. Then I could store the flex values as custom states and manually look for them somewhere in the layout/render process, however I guess there must be a more standard approach for a problem that seems so trivial.
What is the standard framework approach for storing the "splitter position" / "flex values" of a hbox/vbox layout?

Yes there is a standard way to add states. And there is no default state for your splitter so you'll have to add these functions:
Example state:
getState: function () {
var me = this;
var state = {};
state.yourCustomState = 'state'; //you can save what you want
return state;
},
applyState: function (state) {
var me = this;
if (state && state.yourCustomState) {
//Do stuff
}
}
And you need your state to be triggered, you can use stateEvents: http://docs.sencha.com/extjs/4.1.3/#!/api/Ext.resizer.Splitter-method-addStateEvents
You can add the resize event to your stateEvents.

Related

Need to set class/id values on buttons in Extjs MessageBox

Our testing team require IDs or class values to be set on the HTML elements in our message popup boxes. This is for their automated tests.
I can pass in a class value for the dialog panel by passing in a cls value like so:
Ext.Msg.show({
title:'Reset Grid Layout',
msg: 'Are you sure that you want to reset the grid layout?',
cls:'Reset-Grid-Layout-Message',
buttons: Ext.Msg.YESNO,
fn: function (response) {
if (response == 'yes') {
}
},
icon: Ext.window.MessageBox.QUESTION
});
Now we also need it on the buttons, and also on the text being displayed. Is there some way of getting a cls value onto the buttons?
I was thinking it may be possible to expand the button parameter into something like :
buttons : [{name:'but1', cls:'asdf'}, {name:'but2', cls:'asdf2'}]
But google is not giving me back anything useful.
If your testing team uses Selenium for their automated test, adding ids/classes in every component could be difficult for both of you.
Overriding components in Ext is a good solution, but I don't recommend this because it will affect all your components. Unless you know what you're doing.
I suggest, extend Ext.window.MessageBox and generate classes for your buttons based on your parent cls.
// Put this somewhere like /custom/messagebox.js
Ext.define('App.MyMessageBox', {
extend: 'Ext.window.MessageBox'
,initConfig: function(config) {
this.callParent(arguments);
}
,makeButton: function(btnIdx) {
var me = this;
var btnId = me.buttonIds[btnIdx];
return new Ext.button.Button({
handler: me.btnCallback
,cls: me.cls + '-' + btnId
,itemId: btnId
,scope: me
,text: me.buttonText[btnId]
,minWidth: 75
});
}
});
To use:
App.Box = new App.MyMessageBox({
cls:'reset-grid-layout'
}).show({
title:'Reset Grid Layout'
,msg: 'Are you sure that you want to reset the grid layout?'
,buttons: Ext.Msg.YESNO
,icon: Ext.window.MessageBox.QUESTION
});
Your buttons will have reset-grid-layout-yes and reset-grid-layout-no class.
You can do the same with other components you have. Check out the Fiddle. https://fiddle.sencha.com/#fiddle/7qb
You should refer to the API
cls : String A CSS class string to apply to the button's main element.
Overrides: Ext.AbstractComponent.cls
You can also use the filter on right side (not the one in the right top corner). Just type cls and you will see all properties, methods and events containing cls (note that you see by default just public members, use the menu on the right of this searchfield to extend this)
Edit
If you just need it for testing purpose I would recommend to override the responsible method. This should work (untested!)
Ext.window.MessageBox.override({
buttonClasses: [
'okCls', 'yesCls', 'noCls', 'cancelCls'
],
makeButton: function(btnIdx) {
var btnId = this.buttonIds[btnIdx];
var btnCls = this.buttonClasses[btnIdx];
return new Ext.button.Button({
handler: this.btnCallback,
cls: btnCls,
itemId: btnId,
scope: this,
text: this.buttonText[btnId],
minWidth: 75
});
}
});

ExtJS: keyboard focus specific context menu item

Currently I'm opening a context menu following the strike of a keyboard shortcut.
How do I focus on (and select/highlight) a particular menu item of the context menu? So that then the item's handler can be executed by hitting the return key. I'm running ExtJS 4.1.
This what I'm currently doing:
myMenu.showBy(divElement); // divElement is a DOM object
myMenu.items.items[2].focus(); // focus on 3rd menu item
myMenu.doConstrain(); // move floating component into a constrain region
Still, focus is maintained on the menu element itself.
A look into the source reveals that this is done using the setActiveItem() method:
var the_menu = Ext.create('Ext.menu.Menu', {
items: [
{
itemId: 'foo',
text: 'Foo'
},
{
itemId: 'bar',
text: 'Bar'
}
]
}).showBy(document.getElementById('some_div'));
the_menu.setActiveItem(the_menu.down('#bar'));
Note that this method is private, however canActivateItem(item) and deactivateActiveItem(andBlurFocusedItem) are public, so it's probably just an oversight.

Switch from textfield to displayfield with ExtJS4

I have created a form that displays values in plain displayfields.
There is an "edit" button next to the form and once clicked by the user, the displayfields should switch to being textfields and will, therefore, make the data editable.
This, I am guessing, would be achieved by having two identical forms, one editable and one not and one or the other would be visible, based on the user having clicked the button. Another way, perhaps, is to have the xtype dynamically selected upon clicking the button.
Can anybody point me towards a certain direction in order to do this? I am a complete newbie to ExtJS and only just started learning ExtJS4.
Thank you in advance.
M.
Start by rendering all fields as input fields with disabled:true. Then use this for the Edit button handler:
...
form.getForm().getFields().each(function(field) {
field.setDisabled( false); //use this to enable/disable
// field.setVisible( true); use this to show/hide
}, form );//to use form in scope if needed
Ext.getCmp('yourfieldid').setFieldStyle('{color:black; border:0; background-color:yourcolor; background-image:none; padding-left:0}');
Ext.getCmp('yourfieldid').setReadOnly(true);
You can toggle based on a property isEditable. Then when you click the button you change the property and just remove and add the form. It makes it cleaner if you are switching back and forth.
Ext.define('E.view.profile.information.Form', {
extend: 'Ext.form.Panel',
xtype: 'form',
title: 'Form',
layout: 'fit',
initComponent: function () {
this.items = this.buildItems();
this.callParent();
},
buildItems: function () {
return [this.buildInvestmentPhilosophy()];
},
buildInvestmentPhilosophy: function () {
var field = {
name: 'investmentPhilosophy',
xtype: 'displayfield',
editableType: 'textarea',
grow: true,
maxLength: 6000,
value: '---',
renderer: E.Format.textFormatter
};
this.toggleEditingForForm(field);
return field;
},
toggleEditingForForm: function (form) {
if (this.isEditable) {
Ext.Array.each(form, this.configureFieldForEditing, this);
}
},
configureFieldForEditing: function (field) {
if (field.editableType) {
field.xtype = field.editableType;
}
}
});
You can also try to have two items : a displayfield and a textfield with the same data source and you could hide/show the right item with your button handler.
You should not have any CSS problems
(If you did not have CSS problems I would enjoy to see you code)

Extjs Dynamic Grid

I'm trying to create a dynamic grid using ExtJS. The grid is built and displayed when a click event is fired then an ajax request is sent to the server to fetch the columns, records and records definition a.k.a store fields.
Each node could have different grid structure and that depends on the level of the node in the tree.
The only way I came up with so far is :
function showGrid(response, request) {
var jsonData = Ext.util.JSON.decode(response.responseText);
var grid = Ext.getCmp('contentGrid' + request.params.owner);
if (grid) {
grid.destroy();
}
var store = new Ext.data.ArrayStore({
id: 'arrayStore',
fields: jsonData.recordFields,
autoDestroy: true
});
grid = new Ext.grid.GridPanel({
defaults: {
sortable: true
},
id: 'contentGrid' + request.params.owner,
store: store,
columns: jsonData.columns,
//width:540,
//height:200,
loadMask: true
});
store.loadData(jsonData.records);
if (Ext.getCmp('tab-' + request.params.owner)) {
Ext.getCmp('tab-' + request.params.owner).show();
} else {
grid.render('grid-div');
Ext.getCmp('card-tabs-panel').add({
id: 'tab-' + request.params.owner,
title: request.params.text,
iconCls: 'silk-tab',
html: Ext.getDom('grid-div').innerHTML,
closable: true
}).show();
}
}
The function above is called when a click event is fired
'click': function(node) {
Ext.Ajax.request({
url: 'showCtn',
success: function(response, request) {
alert('Success');
showGrid(response, request);
},
failure: function(results, request) {
alert('Error');
},
params: Ext.urlDecode(node.attributes.options);
}
});
}
The problem I'm getting with this code is that a new grid is displayed each time the showGrid function is called. The end user sees the old grids and the new one. To mitigate this problem, I tried destroying the grid and also removing the grid element on each request, and that seems to solve the problem only that records never get displayed this time.
if (grid) {
grid.destroy(true);
}
The behaviour I'm looking for is to display the result of a grid within a tab and if that tab exists replaced the old grid.
Any help is appreciated.
When you are trying to add your grid to the tab like this:
html:Ext.getDom('grid-div').innerHTML,
Ext is not aware of it being a valid grid component. Instead, you are simply adding HTML markup that just happens to look like a grid, but the TabPanel will not be aware that it is a grid component.
Instead you should add the grid itself as the tab (a GridPanel is a Panel and does not need to be nested into a parent panel). You can do so and also apply the needed tab configs like this:
Ext.getCmp('card-tabs-panel').add({
Ext.apply(grid, {
id: 'tab-' + request.params.owner,
title: request.params.text,
iconCls: 'silk-tab',
closable: true
});
}).show();
BTW, constantly creating and destroying grids is not an ideal strategy if you can avoid it. It might be better to simply hide and re-show grids (and reload their data) based on which type of grid is needed if that's possible (assuming the set of grid types is finite).
A potential option is to use the metaData field on the JsonStore that allows dynamic reconfiguring of the grid columns as per new datasets.
From
One of the most helpful blog posts about this that Ive found is this one:
http://blog.nextlogic.net/2009/04/dynamic-columns-in-ext-js-grid.html and the original info is well documented at http://docs.sencha.com/ext-js/3-4/#!/api/Ext.data.JsonReader

Extended ExtJS Class can't find custom listener function - "oe is undefined"

I'm adding a custom context menu to a TreePanel.
This was all working when I had a separate function for the context menu, but I was having problems where the context menu items would end up doubled/tripling up if I clicked on one of the options and then viewed the context menu again.
I had a look around for other contextmenu examples and came up with this one by Aaron Conran I pretty much "stole" it wholesale with a few additions, tacking the function directly into the Ext.ext.treePanel config. This gave me an error about "oe is undefined" which seemed to refer to "contextmenu: this.onContextMenu" in the tree config.
I figured it was probably something to do with the way I was defining all of this, so I decided to look at extending Ext.ext.TreePanel with my function in it as a learning exercise as much as anything.
Unfortunately, having managed to sort out extending TreePanel I'm now back to getting "oe is undefined" when the page tries to build the TreePanel. I've had a look around and I'm not really sure whats causing the problem, so any help or suggestions would be greatly appreciated.
Here is the code that is used to define/build the tree panel. I hope its not too horrible.
siteTree = Ext.extend(Ext.tree.TreePanel,{
constructor : function(config){
siteTree.superclass.constructor.call(this, config);
},
onContextMenu: function(n,e){
if (!this.contextMenu){
console.log('treeContextMenu',n,e);
if (n.parentNode.id == 'treeroot'){
var menuitems = [{text:'Add Child',id:'child'}];
} else {
var menuitems =
[{text:'Add Child',id:'child'},
{text:'Add Above',id:'above'},
{text:'Add Below',id:'below'}];
}
this.contextMenu = new Ext.menu.Menu({
id:'treeContextMenu',
defaults :{
handler : treeContextClick,
fqResourceURL : n.id
},
items : menuitems
});
}
var xy = e.getXY();
this.contextMenu.showAt(xy);
}
});
var treePanel = new siteTree({
id: 'tree-panel',
title : 'Site Tree',
region : 'center',
height : 300,
minSize: 150,
autoScroll: true,
// tree-specific configs:
rootVisible: false,
lines: false,
singleExpand: true,
useArrows: true,
dataUrl:'admin.page.getSiteTreeChildren?'+queryString,
root: {
id: 'treeroot',
nodeType: 'async',
text: 'nowt here',
draggable: false
},
listeners:{
contextmenu: this.onContextMenu
}
});
As a total aside; Is there a better way to do this in my context menu function?
if (n.parentNode.id == 'treeroot') {
Basically, if the clicked node is the top level I only want to give the user an add Child option, not add above/below.
Thanks in advance for your help
In your instantiation of your siteTree class you have:
listeners: {
contextmenu: this.onContextMenu
}
However, at the time of the instantiation this.onContextMenu is not pointing to the onContextMenu method you defined in siteTree.
One way of fixing it is to call the method from within a wrapper function:
listeners: {
contextmenu: function() {
this.onContextMenu();
}
}
Assuming you don't override the scope in the listeners config 'this' will be pointing to the siteTree instance at the time the listener is executed.
However, since you are already defining the context menu in the siteTree class, you may as well define the listener there:
constructor: function( config ) {
siteTree.superclass.constructor.call(this, config);
this.on('contextmenu', this.onContextMenu);
}
Ensuring the context menu is removed with the tree is also a good idea. This makes your siteTree definition:
var siteTree = Ext.extend(Ext.tree.TreePanel, {
constructor: function( config ) {
siteTree.superclass.constructor.call(this, config);
this.on('contextmenu', this.onContextMenu);
this.on('beforedestroy', this.onBeforeDestroy);
},
onContextMenu: function( node, event ) {
/* create and show this.contextMenu as needed */
},
onBeforeDestroy: function() {
if ( this.contextMenu ) {
this.contextMenu.destroy();
delete this.contextMenu;
}
}
});
I had this problem yesterday. The issue with the duplicate and triplicate items in the context menu is due to extjs adding multiple elements to the page with the same ID. Each time you call this.contextMenu.showAt(xy) you are adding a div with the ID 'treeContextMenu' to the page. Most browsers, IE especially, deal with this poorly. The solution is to remove the old context menu before adding the new one.
Here is an abridged version of my code:
var old = Ext.get("nodeContextMenu");
if(!Ext.isEmpty(old)) {
old.remove();
}
var menu = new Ext.menu.Menu({
id:'nodeContextMenu',
shadow:'drop',
items: [ ... ]
});
menu.showAt(e.xy);
I suggest never using hardcoded IDs. #aplumb suggests cleaning the DOM to reuse an existing ID. OK, but I suggest you cleanup the DOM when you no longer need the widgets/elements in the DOM and you should never reuse an ID.
var someId = Ext.id( null, 'myWidgetId' );
var someElement = new SuperWidget({
id: someId,
...
});
Just to add to owlness's answer
This bit here:
listeners: {
contextmenu: this.onContextMenu
}
Gets executed when the javascript file is loaded. this at that stage is most likely pointing to the window object.
A simple way to fix it is adding the listener on hide event of context menu, so you destroy him.
new Ext.menu.Menu(
{
items:[...],
listeners: { hide: function(mn){ mn.destroy(); } }
}
).show(node.ui.getAnchor());
;)

Resources