Savory
The Scalable Prudence/MongoDB
Web Development Framework

It is now 11:16:40.379

Savory's Sencha Integration Library

Ext Direct

Ext Direct is Ext JS's super straightforward RPC mechanism. It generates a client-side namespace for you full of asynchronous methods, and all you have to do is call them. Operations are batched for maximum efficiency, and errors are handled as elegantly as can be.

With Savory, you get a seamless flow from client to server and back again, because you're always in JavaScript: the client call and the server function definition match exactly. If you close your eyes, you can even imagine that the client is calling the server function directly.

Ext Direct is convenient and elegant but, like all web RPC mechanisms, it works by tunneling through HTTP, instead of working fully with it. That means that you don't get the advantages of conditional HTTP: if you call the same Ext Direct method twice, you will generate an action on the server. Logically, it's what you want happening with a method call: such calls are always "non-idempotent." However, if your intention is to use Ext Direct in order to access data, it is strongly recommended that you use REST instead: even a simple Ext.Ajax.request call (with cache override disabled: "disableCaching: false") may scale better.

Refer to the Savory.Sencha API documentation for more details.

Demo

We've implemented a server-side stored shopping cart. Nothing is stored on the client here, and it's accessed with a very convenient client-side API. The server uses a hashset to make sure items cannot be added more than once.

Current Shopping Cart Contents

New item:

Try using Firebug to see all the communication with the server. It's neat!

Behind the Scene

The server side code:

var ShoppingCart = function() {
	var Public = {
		addItem: function(x) {
			return items.add(x)
		},
		
		getItems: function() {
			return JVM.fromCollection(items)
		}
	}
	
	Sencha.exportMethods({
		methods: Public,
		clientParentNamespace: 'Savory',
		clientNamespace: 'ShoppingCart',
		dependencies: '/about/integration/sencha/shopping-cart/'
	})
	
	var items = JVM.newSet(true)
	
	return Public
}()

Note the use of Sencha.exportMethods(): we simply give it an object with our methods, and it handles the Ext Direct wiring. The exports are registered into a simple MongoDB collection. The 'dependencies' field is important, because it tells Savory how to find our methods when the client wants to call them: they are document.executed. And, of course, these functions are entirely normal and can be called on the server as is, with the same namespaces. A single API for servers and clients. Hooray for Ext Direct!

Also note our use of a JVM thread-safe set: this implementation is fully concurrent.

The client side code:

function refresh() {
	Savory.ShoppingCart.getItems(function(provider, response) {
		if (response.type == 'exception') {
			Ext.Msg.alert('Shopping Cart', 'Exception: ' + response.message);
		}
		else {
			var items = response.result;
			Ext.fly('shopping-cart').update(items.join('; '));
		}
	});
}

function init() {
	refresh();
	
	Ext.fly('add-item').set({disabled: null}, false).on('click', function() {
		var item = Ext.fly('item').getValue();
		if (!item) {
			Ext.Msg.alert('Shopping Cart', 'Enter an item first!');
			return;
		}
		Savory.ShoppingCart.addItem(item, Ext.bind(function(provider, response) {
			if (response.type == 'exception') {
				Ext.Msg.alert('Shopping Cart', 'Exception: ' + response.message);
			}
			else {
				if (response.result) {
					Ext.Msg.alert('Shopping Cart', 'Added "' + this + '" to your cart!');
					refresh();
				}
				else {
					Ext.Msg.alert('Shopping Cart', 'You already have "' + this + '" in your cart!');
				}
			}
		}, item));
	});
	
	Ext.fly('get-items')set({disabled: null}, false).on('click', refresh);
}

Savory.loadRemoteProvider({
	namespace: 'Savory',
	success: init
});

Note that we use Savory.loadRemoteProvider() to dynamically initialize our client-side namespace, although we could also hard-code the description into our client file. Because we're loading it dyamically, we're being careful to not actually use the namespace until we have it initialized, which will call our init(). Even the buttons that use this API will be disabled until the API is loaded.

Also note that (obviously) all function calls are asynchronous, and that we are careful to deal with exceptions and failures. This is the one significant difference between server- and client-side calls to the same methods. However, we agree with Ext JS's decision to enforce this: RPC is always asynchronous, and you should code for that situation. You cannot afford to be lazy with the very real possibility of network failure.