Ext JS 4: Labelable Before and After Quick Ref

There are a handful of config options available on the Labelable mixin to add strings or XTemplates before or after the label/element. Here is a quick visual reference for which option will end up where on a textfield.

labelbeforeafter

For some reason, my “afterBodyEl” would not show, but I assume it would be before “afterSubTpl”.

Version Info:
Ext JS 4.1.1

Advertisements

Ext JS 4: Adding ComboBoxes via Remote Configuration in MVC

In an Ext JS 4 MVC application I am currently working on, I need to programatically add comboboxes to a form.  The list of comboboxes varies depending on the selected user.  In addition, the store backing each of the comboboxes is different.  Here is my approach.

The Model
In my case, although the stores might have different data, they have the same fields.  It is a generic label/value set up.

Ext.define('MyApp.model.LabelValue', {
	extend: 'Ext.data.Model',
	fields: [
		'label',
		'value'
	]
});

The Store
Because the stores are so similar for each combo, I created a store that was specific to my case, but generic enough to be used for all the combo boxes. The important part is the alias that is part of the store definition ‘store.mycombostore’. This will allow me to reference the store type, but ensure a new store is created for each combobox. This helpful piece of information is from the comments on the store config for ComboBox, found at Ext.form.field.ComboBox.

Ext.define('MyApp.store.MyComboStore', {
	extend: 'Ext.data.Store',
	alias: 'store.mycombostore',
	model: 'MyApp.model.LabelValue',
	autoLoad: false,

	proxy: {
		type: 'ajax',
		url : 'put/your/url/here',
		reader: {
			type: 'json',
			root: 'data'
		}
	}
});

The View
To reduce the amount of remote configuration that I need to generate I defined a custom combobox with all the static configurations that I wanted. I have a few other things going on in my combobox, like using my Remote To Local plugin. I also overrode beforeBlur to allow the user to delete and basically re-empty the combobox, and I’m setting some params before the store loads. I initially listened for the before store load event in the controller after adding the combobox configs to my form, but putting it right in the custom combobox was cleaner. Not sure if it violates MVC or not…

Ext.define('MyApp.view.MyCombo', {
	extend: 'Ext.form.field.ComboBox',
	alias: 'widget.mycombo',

	requires: [
		'MyApp.store.MyComboStore',
		'Ext.ux.form.field.RemoteToLocalComboBox'
	],

	allowBlank: true,
	forceSelection: true,
	queryMode: 'remote',
	minChars : 1, //begin query after user types 1 char
	msgTarget: 'side',
	queryDelay: 10,
	triggerAction: 'all',
	typeAhead: true,
	typeAheadDelay: 250,

	labelWidth: 200,
	width: 450,

	name: 'roleTemplates',
	displayField: 'label',
	valueField: 'value',

	plugins: [
		{ptype:'remotetolocalcombo'}
	],

	beforeBlur: function () {
		if (this.getRawValue().length === 0) {
			this.lastSelection = [];
		}
		this.doQueryTask.cancel();
		this.assertValue();
	},

	initComponent: function() {
		var me = this;

		Ext.applyIf(me, {
			emptyText: 'Process Access Request...',
			loadingText: 'Loading...',
			store: {type: 'roletemplatesremote'}
		});
		me.callParent(arguments);
		me.getStore().on('beforeload', this.beforeTemplateLoad, this);
	},

	beforeTemplateLoad: function(store) {
		store.proxy.extraParams = {
			moduleId: this.moduleId
		}
	}
});

The Controller
So, in my controller I’m loading all my “user” data to populate the form. As part of my AJAX JSON response, I have the remote configuration:

[
	{
		"xtype":"mycombo",
		"fieldLabel": "Module One",
		"moduleId":"module1"
	},
	{
		"xtype":"mycombo",
		"fieldLabel": "Module Two",
		"moduleId":"module2"
	}
]

And here is the AJAX request and response handling. The Ext.container.Container doc has a section on “Adding via remote configuration” that explains what to do.

Ext.define('MyApp.controller.UserDetail', {

	//...  all the standard controller stuff
	
	loadUser: function() {
		Ext.Ajax.request({
			url: 'your/url/here',
			jsonData: {
				userId: userId
			},
			scope: this,
			callback: this.loadUserCallback
		});
	}
		
	loadUserCallback: function(options, success, response) {
		// userDetail is the view where I am adding the comboboxes.  The
		// new comboboxes will be added to a fieldContainer.
		var userDetail = this.getUserDetail(), 
			responseJson,newCombos;
		if (success) {
			responseJson = Ext.decode(response.responseText);
			if (responseJson.success === true) {
				try {
					newCombos = eval(responseJson);
					userDetail.down('#comboFieldContainer').add(newCombos);
				} catch (e) {
					userDetail.down('#comboFieldContainer').add({
						xtype: 'component',
						html:'No pending access requests'
					});
				}
			} else {
				// showErrorResults is a function that I have to 
				// display an error message on my view
				this.showErrorResults(responseJson.message);
			}

		} else {
			// showErrorResults is a function that I have to 
			// display an error message on my view
			this.showErrorResults('Something went wrong.');
		}
	},
	
	//...
});

I have a bit more going on so this is a little stripped down, but looking at the Ext JS docs should clarify what is needed. Basically the AJAX request returns JSON w/the configuration objects. In the response handling, the JSON is eval’d to objects which are then added to the view. By creating a few custom components I am able to dynamically add to objects to my view, without having tons of configuration determined server-side.

Here is a picture. It’s not too exciting, but it’s exactly what I want, two user-specific comboboxes determined server-side at runtime.

RemoteConfigCombos

Version Info:
Ext JS 4.1.1

Ext JS 4: Empty Value in a ComboBox

Often, in an Ext JS combobox, it is difficult to go back to an empty value once you have selected an item, particularly if “forceSelection” is set to true.  Here is my roundup of alternative solutions found from around the web…

1. Override beforeBlur

The solution from  http://www.sencha.com/forum/showthread.php?182119-How-To-Re-Empty-ComboBox-when-forceSelection-is-Set-To-TRUE overrides beforeBlur on the combbox to clear out lastSelection.  Here is a copy of the override from the thread:

Ext.create('Ext.form.field.ComboBox', {
    ...
    allowBlank: true,
    forceSelection: true,
    beforeBlur: function(){
        var value = this.getRawValue();
        if(value == ''){
            this.lastSelection = [];
        }</span>
        this.doQueryTask.cancel();
       this.assertValue();
    }</span>
});

Or to apply the override to all combo boxes:

Ext.require("Ext.form.field.ComboBox", function () {
    Ext.override(Ext.form.field.ComboBox, {
        beforeBlur: function () {
            if (this.getRawValue().length === 0) {
                this.lastSelection = [];
            }
            this.callOverridden();
        }
    });
});

2. Add Clear Trigger

Another solution is to add a ‘clear’ trigger when the combobox has a value.  That solution is found at Combobox Extjs4 – Empty line.

3. ClearButton UX

On a related note, I really like the ClearButton plugin found at Ext.ux.form.field.ClearButton – Small clear button/icon over field.  I use this extensively in one of my applications.  It works well, except you can’t access the clear icon from the keyboard.

4. Empty Row

The other solution is to add an empty row to the data store backing the combobox, usually as the first record.

Version Info:
Ext JS 4.1.1

Ext JS 4: Updating Ext.ux.data.PagingStore

I am in the process of updating an Ext JS 3 application to use Ext JS 4. So far so good, until I got to one grid that uses Condor’s Ext.ux.data.PagingStore. I tried to get away with using the Ext.ux.data.PagingMemoryProxy, but it was not to be since I want to add and remove records from the store. I tried to find an updated version among the thread in the forum, but had no luck. Therefore, I tried my hand at updating Ext.ux.data.PagingStore for Ext JS 4.

The code for Ext.ux.data.PagingStore is at https://github.com/aghuddleston/Ext.ux.data.PagingStore. I have also included some unit tests, using Siesta as the testing tool. I’m just starting to integrate this into my application, so it’s not well tested yet… I’ll be updating the code as changes are needed.

The main difference from the previous version is that in Ext JS 4 is that “start”, “limit” and “page” are no longer set on the params in the load request, but are their own properties on the Ext.data.Operation config object. A request is only made to load data if the params or extraParams change from the previous load request. Otherwise, it will just page the data. Any proxy should be usable, but I have only tested “ajax” and “memory”. Samples of configuring the data store and loading data are in the comments in the code.

Helpful links:

Version Info:
Ext JS 4.1.1

Ext JS 4: State Management

I recently updated an Ext JS 3 application to use Ext JS 4. It’s really more of a page than a full blown app, which uses Ext JS state management to remember values for some of the fields for browser forward/back navigation. Some things I experienced along the way…

1. Default state events
When you make a field, such as a text field, stateful, you get some default state events, whether or not you want them. A text field for example, adds the state events “resize” and “change”. I didn’t want those events. I only want to save state when a certain button is clicked. Getting rid of those state events proved to be a major pain. I wanted to add a “removeStateEvents” method to the Ext.state.Stateful mixin, but I could never get it added to the prototype before the mixin was added to the text field definition.

I ended up adding the method to “Ext.form.field.Base”. I figured this was okay because the Stateful mixin is inherited by this class and in my case, everything I wanted to save state on was form field. “removeStateEvents” is almost the same as Ext.state.Stateful.addStateEvents, except that it deletes the event name from the stateEventsByName object and removes the doSaveState listener for the given event.

Ext.form.field.Base.addMembers({
  removeStateEvents: function (events) {
    var me = this,
        i, event, stateEventsByName;

    if (me.stateful && me.getStateId()) {
      if (typeof events == 'string') {
        events = Array.prototype.slice.call(arguments, 0);
      }

      stateEventsByName = me.stateEventsByName || (me.stateEventsByName = {});

      for (i = events.length; i--; ) {
        event = events[i];

        if (stateEventsByName[event]) {
          delete stateEventsByName[event];
          me.un(event, me.onStateChange, me);
        }
      }
    }
  }
});

And then I called it on my text field in the initComponent of my extended container.

initComponent: function () {
    //...
    this.callParent(arguments);
    this.down('#searchText').removeStateEvents(['change','resize']);
}

This whole solution is annoying. I would prefer to just avoid adding the state events in the first place, but that would mean some aggressive overriding and therefore I ended up with the “remove before you can do anything” solution.

Seems like turning on stateful for a field should allow the field to specify exactly what events to listen for, instead of giving you some defaults (and potentially very frequently fired defaults at that). Something like this post on the remote storage provider extension, where defaultStateEvents can be configured would be nice.

2. Timing Matters
I could see that the state info I wanted for my fields was being saved correctly, but I had issues with the values setting on comboboxes. It turns out that value of the combobox was being initialized from the state manager prior to loading the stores for the comboboxes. So early that “isLoading” was false and instead the combobox went down the path of being set with an unknown value, and since those aren’t allowed for my comboboxes… well.

To solve this issue I moved some stuff around and loaded the combobox stores earlier, taking advantage of a store id and store manager. But then I also delayed the loading of my viewport a few milliseconds, to give the AJAX requests for the data stores a chance to kick off.

// Create data stores for the comboboxes, with autoLoad true like this:
Ext.create('Ext.data.Store', {
	storeId: 'comboboxStore',
	fields: ['label', 'value'],
	autoLoad: true,
	proxy: {
		type: 'ajax',
		url: 'resourc/url/goes/here',
		reader: {
			type: 'json',
			root: 'data'
		}
	}
});

Ext.state.Manager.setProvider(Ext.create('My.state.Provider'));
var stateReset = this.resetState();
// Delay the UI build a little bit, so the data stores get a chance to start loading
var task = new Ext.util.DelayedTask(function(stateReset){
	this.buildViewPort();
	if ( !stateReset ) {
		// Must be from a back button, run the find to pre-populate the grid
		this.doFindUsers();
	}
}, this, [stateReset]);
task.delay(100);

The Ext JS 3 version is not MVC and the code is already embedded in a legacy jsp page, so this was part of my “init” function that runs at Ext.onReady.

Version Info: Ext JS 4.1.1

Ext JS 4: Bind Keys to a Form

Here is one way to bind a key to a form/component/element in Ext JS 4. The link below also shows examples of key bindings when using MVC.

Ext.define('My.App.QuickFindContainer', {
	extend: 'Ext.container.Container',
	alias: 'widget.quickfindcontainer',
	//all the needed configs, items and initComponent

	afterRender: function () {
		this.callParent(arguments);
		this.nav = Ext.create('Ext.util.KeyNav', this.el, {
			enter : this.doFind,
			scope: this
		});
	}
});

Helpful links: http://www.sencha.com/forum/showthread.php?131596-Form-submit-on-Enter

Version Info:
Ext JS 4.1.1

Adventures with PowerMock

Recently I had the opportunity/challenge/task to get unit tests working for some legacy code. Various tests were written, but very few worked in our automated build environment. I spent the time to get the tests working. The tests were a hodgepodge of unit and integration tests and probably orginally only worked locally on one person’s machine. I converted the current set of tests into unit tests (maybe I’ll get to integration tests some day) that could be run through Eclipse, through Ant and through our automated build environment. Turned out, I used Mockito and PowerMockito quite a bit. Here are some of my lessons learned.

Mocking the Statics

A bunch of the legacy code classes are similar to this:

public class MyClass {

	public MyClass(MyClassTO to) {
		super(to);
	}

	public void create() {
		MyDAO dao = getDAO();
		List list = dao.find(/*params here*/);
		// do something
	}
	
	public static void delete(Object pk) {
		getDAO().delete(pk);
		// do something
	}

	private static MyDAO getDAO() throws DAOException {
        	return (MyDAO) DAOFactory.getDAO(MyDAO.class);
    }
}

And, I wanted to unit test any of the methods that are part of the class that were worth testing. The challenge was the “private static getDAO()” method. DAOFactory needs to be run in the container to work. So, my first thought was “I want to mock the getDAO() method and have it return a mock dao.” I tried a ton of things to follow through with this idea with zero results. Turns out, it was better to think of my DAOFactory as a System Class. And then these directions https://code.google.com/p/powermock/wiki/MockSystem worked very well.

My test class looks like:

@RunWith(PowerMockRunner.class)
@PrepareForTest({ DAOFactory.class })
public class MyClassTest {

	private MyDAO mockDao;
	private MyClassTO to;

	@Before
	public void setUp() throws Exception {
		to = new MyClassTO(/*params here*/);
		to.setDescription("description");

		mockDao = mock(MessageDAO.class);
		PowerMockito.mockStatic(DAOFactory.class);
		PowerMockito.when(DAOFactory.class, "getDAO", MyDAO.class).thenReturn(mockDao);
	}

	@Test
	public void testCreateCreate() {
		List<MyClassTO> myList = new ArrayList<MyClassTO>();
		Mockito.when(mockDao.find(/*params here*/)).thenReturn(myList);

		MyClass bo = new MyClass(to);
		bo.create();
		verify(mockDao).create(bo);
		verify(mockDao, never()).update(bo);
	}
}

By saying, mock the DAOFactory and make it return my mockDao, I can then mock out whatever else is needed to test the class.

Preventing an Instantiation
Next pattern I had to handle was when my class under test instantiated an object that eventually needed something in the container. Usually that DAOFactory. In this case, my class under test looked something like this (much simplified).:

public final class MyDelegate  {
    private MyDelegate() { }

    public static void doSomeStuff(MyTO to) {
		MyMappingBO bo = new MyMappingBO();
		List mappings = bo.find(to);
		bo.save();
    }
}

I needed to not instantate the MyMappingBO class during the test. This is what I did using PowerMockito.

@RunWith(PowerMockRunner.class)
@PrepareForTest({ MyDelegate.class })
public class MyDelegateTest {

    @Test
    public void testCreate() throws Exception {
        List<MyTO> myList = new ArrayList<MyTO>();
        // populate the list
       
        MyTO to = new MyTO();
        MyMappingBO mockMyMappingBo = mock(MyMappingBO.class);
        PowerMockito.whenNew(MyMappingBO.class).withNoArguments().thenReturn(mockMyMappingBo);
        PowerMockito.when(mockUserMappingBo.find(to)).thenReturn(myList);
		
        MyDelegate.doSomeStuff(to);
        //asserts and verifies here
    }
}

It does what it reads. When the MyMappingBO class is newed up, return the mock instead. Then, when a specific method on the mock is called, return the myList object instead. The tricky thing here is which class to put in the @PrepareForTest. Actually, if you read the PowerMock documentation closely, it says it pretty clearly. It is the class instantiates the object you are mocking, not the mocked class itself.

Version Info:
JUnit 4.11
Mockito 1.9.5
PowerMock 1.5

Ext JS 4: A File Browser with Spring 3 Server-side

I recently needed to create a file browser to access log files for my application. I don’t have access to the machines where the log files reside, so by creating a file browsing application I could ensure access controls and still get to the logs. From within my file browser I can open the log file in another browser tab/window or download the file. This was also my first time working with trees…

Here is what it looks like:
FileBrowser
(more…)

Ext JS 4: Two Ways to Configure Selection Model in Grid

There are two ways to configure the selection model on the grid. The two configuration options are selModel and selType. Below is an example of using both. Note, the configurations that I put in are just for demonstrations purposes, not necessarily recommended.

Example 1 – selType
This only uses configuration to set up the selection model. If using all the default configs for the selection model, the configuration options in selModel are not needed.

{
	xtype: 'grid',
	title: 'My Grid',
	store: myStore //configured elsewhere
	selType: 'checkboxmodel',
	selModel: {
		checkOnly: true,
		injectCheckbox: 1
	}
	columns: [
		// configure columns here
	]
}

Example 2 – selModel
This creates the selection model and only uses selModel.

{
	xtype: 'grid',
	title: 'My Grid',
	store: myStore //configured elsewhere
	selModel: Ext.create('Ext.selection.CheckboxModel, {
		checkOnly: true,
		injectCheckbox: 1
	}),
	columns: [
		// configure columns here
	]
}

Versions: Ext JS 4.1.1

Ext JS 4: Loading Reference Data Prior to Starting Application

I have recently found that I either need to load reference data or meta data to support role-based navigation before building the viewport.

In the first example below, I’m populating a variety of data stores prior to actually loading my application. This example does not use the MVC pattern.

In the second example, I’m loading toolbar configurations and other meta-data such as titles that are role-based before loading my application.

Loading Reference Data – Example 1

Ext.define('MyApp', {

	requires: [
		'Ext.Viewport',
	],

    launch: function() {

		this.storeOne = Ext.create('Ext.data.Store', {
			storeId: 'storeOne',
			model: 'MyApp.MyModel',
			autoLoad: false
		});
		
		this.storeTwo = Ext.create('Ext.data.Store', {
			storeId: 'storeTwo',
			model: 'MyApp.AnotherModel',
			autoLoad: false
		});
		
		Ext.Ajax.request({
			url: './url/for/refdata',
			jsonData: storeList,
			scope: this,
			success: this.onRefDataLoaded,
			failure: this.onRefDataLoadError
		});
	},

	onRefDataLoaded : function (response) {
		var json = Ext.decode(response.responseText), msg, item,
			success = false;

		if (typeof json.success !== "undefined" && json.success === false) {
			msg = json.message;
		} else {
			try {
				this.storeOne.loadRawData(json.storeOneData);
				this.storeTwo.loadRawData(json.storeTwoData);
				success = true;
			} catch (err) {
				msg = "An unexpected error occurred.";
			}
		}

		if (success) {
			item = {
				// put the application config stuff here.
				// i have a panel with a form on it
			};
		} else {
			// build a panel that will show the error message
			item = {
				xtype: 'panel',
				autoScroll: true,
				html: msg,
				bodyPadding: 10
			};
		}

		this.mainView = Ext.create('Ext.container.Viewport', {
            layout: 'border',
            items : [
				{
					id: 'center-container',
					xtype: 'container',
					region: 'center',
					layout: 'fit',
					items: [item]
				}
			]
		});
	},

	// Couldn't properly load the reference data, the AJAX request failed
	// Build the viewport with the error message
	onRefDataLoadError : function () {
		this.mainView = Ext.create('Ext.container.Viewport', {
			layout: 'border',
			items : [
				{
					id: 'center-container',
					xtype: 'container',
					region: 'center',
					layout: 'fit',
					items: {
						xtype: 'panel',
						autoScroll: true,
						html: "Something went wrong",
						bodyPadding: 10
					}
				}
			]
		});
	}
});

The JSON response for the ref data load looks like this:

{
	"storeOneData" : {
		"success" : true,"rows" : [
			{"id":1, "type":"UserType1"},
			{"id":2, "type":"UserType2"}
		]
	},
	"storeTwoData" : {
		"success" : true,"rows" : [
			{"id":1, "genre":"Jazz"},
			{"id":2, "genre":"Rock"},
	        {"id":3, "genre":"Pop"},
	        {"id":4, "genre":"Hip Hop"}
		]
	},
}

Loading Navigation Configurations – Example 2
In this example, instead of populating data stores, I’m querying the server to get configs for a toolbar and other data for the specific user. This example is using the MVC pattern, but dynamically loading the the controllers, following the pattern described here: An ExtJS MVC application with dynamically loaded views and controllers. The interesting part are the launch and onLoadNavButtons methods.

Ext.application({
	autoCreateViewport: false,
	name: 'MyApp',

	requires: [
	],

	appFolder: 'myapp/app',

	launch : function () {
		// Load the app navigation buttons
		Ext.Ajax.request({
			url: './url/to/get/refdata',
			scope:this,
			success:this.onLoadNavButtons,
			failure:function () {
				Ext.msg.alert("My App Initialization Failed", "Initial Loading Error");
			}
		});
	},

	onLoadNavButtons : function(xhr) {
		var viewPortData = Ext.decode(xhr.responseText),
			i, tot;
		this.navButtons = viewPortData.navButtons;
		this.defaultAction = viewPortData.defaultAction;
		this.viewportMetaData = viewPortData.viewportMetaData;
		this.buildViewPort();
	},

	buildViewPort : function () {
		this.viewport = Ext.create( "MyApp.view.Viewport", {
			viewportMetaData : this.viewportMetaData,
			navButtons: this.navButtons
		});
		var c = this.getController('Viewport'),
			da = this.defaultAction;
		c.init();
		this.runAction(da.controllerName, da.actionName, da.extra);
	},

	runAction : function (controllerName, actionName) {
		Ext.log(controllerName + ' ' + actionName);
		var controller = this.getController(controllerName),
			viewPort = this.getController('Viewport');
		controller.init(this);
		controller['action'+actionName].apply(
		    controller, Array.prototype.slice.call(arguments, 2));
		viewPort.toggleButtons.apply(viewPort,Array.prototype.slice.call(arguments, 2));
	},

	setMainView: function(view) {
		var center =  this.viewport.layout.centerRegion;
		if(center.items.indexOf(view)===-1){
			center.add(view);
        	}
		center.getLayout().setActiveItem(view);
	}
});

And the JSON that is returned from the server is

{
	"viewportMetaData" : {
		"title" : "My App Title Here",
		"hideToolbar" : false
	},
	"defaultAction" : {
		"controllerName" : "Home",
		"actionName" : "Show",
		"extra": "homeBtn"
	},
	"navButtons":[
		{
			"text": "Home",
			"itemId": "homeBtn",
			"iconCls": "icon-house"
		},
		"-",
		{
			"text":"Another Button",
			"itemId":"anotherBtn",
			"iconCls":"icon-door-in"
		},
		"-",
		{
			"text":"One More Button",
			"itemId":"moreButton",
			"iconCls":"icon-arrow-redo"
		}
	]
}

And then, in my Viewport “view” I use the navigation button configs when building the toolbar for the viewport.

Helpful Links:

Version Info: Ext JS 4.1.1