ajax

Ext JS 4: Mocking Success and Failure AJAX Responses

Often when building an Ext JS app, I will create a functioning mock-up and work out the design and most client-side issues in my mock-ups. This has a lot to do w/the environment I am working on and it gets down to the fact that it is much faster for me to work with the mock-ups than my real app. Recently I’ve been creating a utility class for my app that has constants and utility methods and one thing that is helpful for me is my “random success/fail” ajax response method. I’ve found this is a great way for me to make sure I’m handling errors everywhere in my app, whether it is through a global error handler or on each AJAX request.

My stripped down utility class looks like this (usually I have a bit more in there, such as constants, app-specific vtypes, and logging.):

Ext.define('MyApp.Util', {
	singleton: true,

	UNKNOWN_ERROR: 'An unknown error occurred',

	randomSuccessFail : function (u) {
		var random = Math.floor(Math.random()*11), url;
		if (random > 8) {
			url = './data/dne.json'
		} else if (random > 3) {
			url = u ? u : '../util/successTrue.json';
		} else {
			url = '../util/successFalse.json';
		}
		return url;
	}

});

“dne.json” is a file that does not exist, so I make sure my mock-ups are handling 404s and other server errors. The other response files look like this:

//successTrue.json
{
	"success":true,
	"message":"success message here, if any"
}

// successFalse.json
{
	"success":false,
	"message":"Failure message goes here and make it a little bit longer and even longer"
}

And it gets called like so in a controller:

// A method inside a controller.  Ext.Ajax request, 
// returning default success/fail response
	saveUser: function() {
		var userDetail = this.getUserDetail(),
			params;

		if (userDetail.getForm().isValid()) {
			this.getDismissAlert().hide();

			params = userDetail.getValues();

			Ext.Ajax.request({
				url: MyApp.Util.randomSuccessFail(),
				jsonData: params,
				scope: this,
				callback: this.saveCallback
			});
		}
	},

// And here's another example, this time on a store load 
// and expecting a different success response
	loadUserIds: function () {
		var combo = this.getUserIdCombo(),
			store = combo.getStore();

		store.proxy.url = MyApp.Util.randomSuccessFail('./data/userids.json');
		store.load({
			scope: this,
			callback: this.loadUserIdsCallback
		});
	},

// Sample callback, shown here... just because
	saveCallback : function (options, success, response) {
		var responseJson, form;
		if (success) {
			responseJson = Ext.decode(response.responseText);
			if (responseJson.success === true) {
				// whatever stuff needs to happen on success
				this.showSuccessResults("Updates saved");
			} else {
				this.showErrorResults(responseJson.message);
			}
		} else {
			this.showErrorResults(MyApp.Util.UNKNOWN_ERROR);
		}
	},

And that’s it, a helpful utility.

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: 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