Tag Archives: Sencha Touch 1

Sencha Touch: Adding/Removing Asterisk in Required Fields

08 Mar 2012

Another day, another thing learned. Today its those little asterisk’s that appear next to the labels of required fields in Sencha Touch 1. You can set them so that they appear when the component is instantiated via the “required” config option but there is no method to add or remove them after the fact. Also, if you are building your Sencha app the old non-mvc way that means that no components are not being destroyed/created as needed, so you can’t set the value for “required” to be an expression (i.e., something ? true : false) because if you do it will only work once – when the component is created.

Looking at the html that Sencha produces I see that a class is added to the component that is responsible for the appearance of the asterisk. Since Sencha does provide a method to add/remove classes the solution couldn’t be simpler:

...
// too add the asterisk to the form element refer to its div and:
Ext.getCmp('id_of_field').addCls('x-field-required');
// to remove the asterisk from the form element:
Ext.getCmp('id_of_field').removeCls('x-field-required');
...

Keep in mind that this is just a visual thing – no matter how you set the component’s “required” property there is no impact on any form validation.

Custom Message Boxes in Sencha Touch 1.1

08 Mar 2012

Sencha Touch comes with 3 modal message boxes: Alert, Confirm, and Prompt. In all cases the buttons are pretty hard-coded into them. There are many instances where specific button combination or wording is desired yet the defaults for the three canned Message Boxes fall short. I have seen various attempts at getting custom buttons and quite honestly they all seem convoluted to me. All I want is a straight-forward way of creating custom buttons within my Sencha Message Boxes.

IMO the easiest approach is to break away from Alert/Confirm/Prompt and create your own MessageBox via the Ext.MessageBox class.

First, lets declare a new Ext.MessageBox instance like so:

...
var msg = new Ext.MessageBox();
...

I’ve create a variable and assigned an empty Ext.MessageBox to it. What we need to do is to configure it and then show it, both of which can be accomplished with the Ext.MessageBox show() method which itself takes a configuration object which will populate our Message Box with everything that we need before it renders it to the screen. See this next bit of code for an example, which we’ll disect in a moment:

...
msg.show({
    title: 'Hello World',
    msg: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt',
    buttons: [{text:'Sure',itemId:'sure'},{text:'No Way',itemId:'noway'}]
});
...

The above will look like this:

Its easy enough, right? Note the “buttons” config option, this takes an array of configuration objects. Each object has “text” and “itemId” attributes. You can specify whatever you like as the values for these. As well, you can specify just one config object or more if you like. Please note that while there is a “width” config option for Ext.MessageBox setting it has no effect. As a result, the practical limit for custom buttons is 2. If you’re familiar with CSS, you could override the Sencha Touch styles being used on the buttons and squeeze in a third, space permitting.

The next step is to setup a callback function to handle user input. To do so simply assign a function to the fn: config option or do as I did and assign an anonymous function that is setup to receive the “response” from the user. The “response” is the “itemId” that you specified for the button.

...
function custombox(){
    var msg = new Ext.MessageBox();
        msg.show({
        title: 'Hello World',
        msg: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt',
        buttons: [{text:'Sure',itemId:'sure'},{text:'No Way',itemId:'noway'}],
        fn:function(response){
            console.log(response)
        }
    });
}
...

If the above is run and you tap on the “Sure” button you will see the string “sure” print to the console. Same thing for the “No Way” button – its itemId “noway” will also print to the console when its button is tapped. All you need to do is to insert some logic to handle either of the two responses.

Finally, to ensure that your MessageBox conforms to your text always execute a doComponentLayout() on your message boxes after showing them (see line 35 below).

A complete Sencha Touch Custom Message Box example:

...
Ext.setup({
    onReady: function(){
	
        var rootPanel = new Ext.form.FormPanel({
	        fullscreen: true,
	        items: [
		    {
		        xtype:'button',
		        text:'Show Custom Message Box',
		        scope:this,
		        handler: custombox
		    }
	        ],
	        dockedItems:[
		    {
		        xtype:'toolbar',
		        dock:'top',
		        title:'Message Box Example'
		    }          
	        ]
	    }
        );
	
	function custombox(){
	    var msg = new Ext.MessageBox();
	    msg.show({
		title: 'Hello World',
		msg: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt',
		buttons: [{text:'Sure',itemId:'sure'},{text:'No Way',itemId:'noway'}],
		fn:function(response){
		    console.log(response)
		}
	    });
	    msg.doComponentLayout();
	}	
    }
});
...

XHR Post in Sencha Touch 1 & PhoneGap

13 Feb 2012

Sencha Touch has built in support for JSONP via “GET”, but if you have a lot of data to send you may quickly run into the GET character limit which varies across browsers. For a lot of data, “POST” is the preferred method and may be even preferred over GET for security reasons. The rub is that form POSTs will reload a page meaning that your app will reload and you’ll lose whatever state you had. That means we’ll have to do the POST via AJAX.

The astute among you might first ask about the cross domain policy and how it would prevent such a thing from happening (which JSONP by definition allows but is not useful here because its essentially a GET). As PhoneGap loads your Sencha Touch project via the file:// protocol cross domain XHR (XMLHttpRequest) is thus possible. Files loaded in this manner a free of the same domain policy.

With that aside it appears that Sencha Touch 1 just doesn’t have AJAX form posts as part of its framework but we can use Ext.Ajax.request() which allows us to set the desired form method and gives us success and error callbacks and even a timeout – which Ext.util.JSONP.request() lacks (bonus!).

The only additional setup is to add android.permission.INTERNET to the android manifest and to add the domain that you’re posting to to the whitelist in iOS. There has been some discussion about whitelisting in PhoneGap, it might be worthwhile to see how its evolved.

Here is a code snippet showing how the Ext.Ajax.request() method can be utilized within Sencha Touch:

...
Ext.Ajax.request({
	url: 'http://www.somewhere.com',
	method:'POST',
	params: 'configuration object goes here',
	scope:this,
	timeout:15000,
	disableCaching:true,
	failure: function(responseObj, opts){
		// handle your error response object here
	},
	success: function(responseObj, opts){
		// handle your success response object here
	}
});
...

The configuration object is simply something along these lines: {name:value,name:value,name:value,……}

Ext.Picker Slots with A Single Value: showing a Decimal Point

08 Feb 2012

A case has come up where I need a picker with multiple slots that represent a number to two decimal places. A decimal point will be necessary to delineate between whole numbers and the decimals. To achieve this I will need to have a 5 slot Ext.Picker where the middle slot is a decimal point, as shown in the image below:

Sencha Picker with 5 slots, the center having a single value.

At first glance this might seem to be fairly easy. As slots are simple to define we then assume that the center slot just needs to be a single value, right? Actually, that’s not quite true. A few things make this not so clear-cut, among them is that a slot cannot be disabled. There *is* an option to set “draggable” to false but it doesn’t work (Sencha 1.1.1). As a result the slot will always be subject to being dragged by the user. Also an Ext.Picker with a slot consisting of only a single value will actualy throw errors if the slot is dragged up or down – trial and error reveals that a slot must consist of at least two values.

So then, we will create our center slot with two values. Also, I think its pretty tedious to manualy create all of the values for the other slots, so I wrote a function that builds an array of configuration objects that I’ll use to populate the slot data. Here’s what we have at this point:

function returnNumbers(){
	var num = [];
	for (i=0;i<10;i++){
		num[num.length] = {text:i,value:i};
	}
	return num;
}

var taxPicker = new Ext.Picker({
	slots: [
		{
			name : 'whole1',
			title: 'col 1',
			data : returnNumbers()
		},
		{
			name : 'whole2',
			title: 'col 2',
			data : returnNumbers()
		},
		{
			name : 'dot',
			title: 'decimal',
			draggable:false, // doesn't work!!
			data : [{text:'.',value:'.'},{text: '', value: ''}]
		},
		{
			name : 'decimal1',
			title: 'col 3',
			data :  returnNumbers()
		},
		{
			name : 'decimal2',
			title: 'col 4',
			data :  returnNumbers()
		}
	]
});

As you can see the first two slots are a range of numbers, 0 through 9. The center slot is the decimal and an empty string, and then the last two slots like the first pair are also setup to display 0 thru 9.

When the picker is shown via the show() method it appears the way we want - the decimal point is the first selection. Recall though that we have two values for the center slot, and that the user can play with the slot as they wish. If the decimal is dragged down Sencha handily animates it back into position. However if the decimal is dragged up - we get the empty-string as the selection for the slot. Note that we actual don't care what the selection of the center slot is - I don't use it - but visually this looks wrong so it needs to be addressed. If we can't prevent the user from dragging the decimal then we should at least find a way to put it back where we want it.

They key here is that Slots fire "pick" events when they are changed. So that's where we'll start; by adding a listener to the picker. Next, we need to set its value and there is a method that does just that - setSelectedNode(). Lets create the listener:

	...
	listeners:{
		pick:function(pickerO,o,pickerS){
			pickerS.setSelectedNode(0,null);
		}
	}
	...

This is great except for one thing - all of the slots are being animated back to their initial values. We need a way of being more selectful in our approach as this code doesn't discriminate and attacks all of the slots. The answer here is to look at the value of the slot responsible for the event call and do a comparison against it to see if its the slot we're looking for.

The slot object is being passed as "pickerS", so lets grab that and compare the name given to the slot against what we know is the name of the decimal slot (refer to the picker code above - all of the slots have a "name"):

	...
	listeners:{
		pick:function(pickerO,o,pickerS){
			if (pickerS.name == 'dot'){ // send back to the decimal...
				pickerS.setSelectedNode(0,null);
			}
		}
	}
	...

On line 4 above you can see that I have an if statement comparing the passed slot's name to "dot" which is the name I gave to the decimal-having slot. This ensures that we're working with the correct slot when the pick event is fired.

Sweet, that should do it - however, of course there is one last detail... there always is! The above behaves unpredictably. Sometimes the decimal slides back to being selected, sometimes not. In these cases I always wonder if too much is happening at the same time. A simple setTimeout should make all the difference and it does.

        setTimeout(function(){pickerS.setSelectedNode(0,null);},20);

As you can see I've delayed the setSelectedNode() call by 20 milliseconds. Thats all that was needed - if the user drags the center slot to the second "empty" slot we return it to the desired decimal-having slot 20 ms later. Its the best that we can do. Here's the complete source:

function returnNumbers(){
	var num = [];
	for (i=0;i<10;i++){
		num[num.length] = {text:i,value:i};
	}
	return num;
}

var taxPicker = new Ext.Picker({
	slots: [
		{
			name : 'whole1',
			title: 'col 1',
			data : returnNumbers()
		},
		{
			name : 'whole2',
			title: 'col 2',
			data : returnNumbers()
		},
		{
			name : 'dot',
			title: 'decimal',
			draggable:false, // doesn't work!!
			data : [{text:'.',value:'.'},{text: '', value: ''}]
		},
		{
			name : 'decimal1',
			title: 'col 3',
			data :  returnNumbers()
		},
		{
			name : 'decimal2',
			title: 'col 4',
			data :  returnNumbers()
		}
	],
	listeners:{
		pick:function(pickerO,o,pickerS){
			if (pickerS.name == 'dot'){ // send back to the decimal...
				setTimeout(function(){pickerS.setSelectedNode(0,null);},20);
			}
		}
	}
});

Reset Ext.Panel Y Coord to 0

08 Feb 2012

Doubtless there is a way to do this with Ext, but this is my own method of returning a scrolling panel within Sencha Touch to the “top” of the view. I did spend some time reviewing the Sencha Touch docs but the only thing that came close to the desired result was the setPosition() method which only works on floating panels. I did spend some time playing with setting the floating property of the panel and then applying setPosition() but quickly ran into undesired results.

So, as always, time is short and a solution needs to be had quickly. A few minutes later I came up with this:

// setPositionToTop(cmp):void
// This function takes the scrolling area of a panel and sets its y position to 0;
// arguments: cmp:Object, an Ext.Panel
// returns: nothing
function setPositionToTop(cmp){
	var cmpId = cmp.getId();
	var sStr = document.getElementById(cmpId).firstChild.firstChild.getAttribute('style');
	if (sStr && sStr.indexOf('translate3d') != -1){
		var temp = sStr.split(',');
		temp[1] = ' 0px';
		var newStr = temp.join(',');
		//console.log('before: ' + sStr + ', after: ' + newStr);
	document.getElementById(cmpId).firstChild.firstChild.setAttribute('style',newStr)
	cmp.doComponentLayout();
	}
}

The above is based on the following source that Sencha Touch generates:

...

It should be plain that the top-most div is the component, and that I’m traversing the DOM to the third DIV and manipulating the style attribute. In this example the webkit-transform is responsible for the position of the content div and so I’m setting the y value back to 0px thus placing it where I want – back to the top.

The interesting thing here is that without calling doComponentLayout() on the component the space that the scrolling content area occupied before changing its position is still present resulting in a scrolling area larger than desired. In a worse-case scenario you can actually scroll the content area right off the screen – never to get it back. doComponentLayout() fixes this. Simply comment that line out to see this for yourself – scroll your panel to its most extreme y position, nav away, nav back, scroll your panel up as far as you can go…. gone.

To use this function I add it to a beforecardswitch listener for the desired panel and I add an if statement to check if the panel has been rendered yet, which it won’t unless you’ve visited it previously:

beforecardswitch:function(e,o){
	if (transactionsCardDeck.getActiveItem()){
		setPositionToTop(transactionsCardDeck.getActiveItem());
        }
}

The result is that the panel’s y coordinate is reset before you return to it.