Category Archives: JavaScript

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();
	}	
    }
});
...

XTemplates and Sheets in Sencha Touch

24 Feb 2012

I was recently working on a project where there was specific help text that proved to be quite long in many cases. The original plan was to use Ext.Msg but that quickly became impractical due to the sheer amount of text. Next thing to use was Ext.Sheet which has the flexibility and options that I needed.

The Sheet ended up configured to have a toolbar and footer, docked to the top and bottom respectively. I further configured it to float in the center of the screen with about a 40 pixel margin around the sides. The lone item in the Sheet was a panel configured to use an XTemplate so that I could update it as needed with whatever help text came across the help web service.

The problem I encountered was that I could only populate the content panel within the Sheet once. It turns out that the activate listener for the content panel only ever fires one time – the very first time that the Sheet is displayed which is the first time the panel is activated.

After some thought this behavior makes sense. The panel is rendered the first time and the activate event fires. Thereafter, it doesn’t need to fire because it is always the lone active item of the sheet – it has already been activated/rendered. If I were to add another item to the sheet and switch between the two I would then get the activate event firing for both panels every time they are switched. As it is, the panel is activated the very first time out of necessity, activate naturally fires, and that is pretty much the end of it.

Back to the task at hand, I don’t have anything bound to stores, no proxies… I didn’t want to mess with that, I just wanted to push an object into the panel and I could only do it once with the panel’s activate event. Every other call to show the Sheet would not update the content with new text – only the very first item was ever displayed and all further use of the Sheet would only show the content that it was initially updated with.

See the following code which fits this scenario:

...
var helpSheet_tpl = new Ext.XTemplate(
    '',
        '
{data}
', '
', { compiled:true } ); var helpContent = new Ext.Panel({ id:'helpContent', layout:'card', scroll:'vertical', tpl:helpSheet_tpl, listeners:{ activate:function(e,o){ // this event will only ever fire once!!! helpContent.update(DATA); } } }); var helpSheet = new Ext.Sheet({ id:'helpSheet', cardSwitchAnimation:{type:'pop',duration:1000}, height: window.innerHeight - 40, width: window.innerWidth - 40, centered:true, floating:'true', layout: { type: 'card', align: 'stretch' }, items:[ helpContent ], dockedItems:[ { xtype:'toolbar', layout:'card', dock:'top', id:'help_header', title:'Help', }, { xtype:'button',text:'OK', layout:'card', dock:'bottom', listeners:{ tap:function(o,e){ helpSheet.hide(); } } } ] }); ...

The learning here is that the activate event for the above content panel will only fire once because it is the lone item – there is no item-switching happening and so therefore no opportunities for activate to fire again. Also, Ext.Sheet doesn’t have any events so you can’t put an activate event listener on it. At least, when I attempted this nothing happened at all. The Sencha docs imply that it is inherited but if you check the option to hide inherited you will see that there is nothing left event-wise for Ext.Sheet.

If you want to push an object into a panel in the above scenario by using the panel update() method you will have to do so outside of the panel, and not rely on the panel’s activate event. In my case I placed it within the JSONP callback. First I use the show() method to show the sheet and then I follow that up with the update() method on the panel.

Making a Phone Call from Within PhoneGap in Android and iOS

22 Feb 2012

Today I came across a feature request that I had not done before – dialing a number from within an app. Some quick research shows that its possible using a specific URI scheme.

What are URI schemes? Honestly Wikipedia does a better job than I ever could in describing them but I think of them as something that allows a specific piece of functionality to happen over the internet, and thus they are usually referred to as protocols. You probably have already seen them – the most common ones are http: and https: (for web browsing), and ftp:, among others. Some are unique to an application and really don’t qualify as schemes and are definitely not a “protocol”, such as mailto: (to open up the mail client on a person’s computer), javascript: or about: – in fact, try typing about: in the address bar of your browser and hit “enter” on your keyboard, notice what happens…

In our case where we want to dial a number from within our app we need a way of telling the mobile phone that we want to make a call. There is a scheme for this purpose called tel:. A sample number using this scheme would look like this: “tel:+1-800-555-1234”. If you wanted a number to work around the world you would use an international number which includes the country code.

Implementing this is simple, we could do this within our mobile html5 app like so:

...
call this number
...

Ideally though we would delegate the event and fire a function to call our mythical phone number. To send the url (the “tel” url) to the browser we would write the following:

...
document.location.href = 'tel:+1-800-555-1234';
...

As of PhoneGap 3.6 all schemes are subject to whitelists. This means you have to add the tel scheme to a second whitelist that will allow your app to launch external applications. To do this you need to edit your config.XML to include the following (a mailto example is included):



Go here for more information: Cordova 3.6.0 Whitelist Guide.

Of interest to this topic is getting Android to treat phone numbers (as well as URLs and mailto schemes) as clickable links in text fields. I’ve not tested it but try adding the following to your config.xml.


Additional information on this can be found here: http://developer.android.com/reference/android/widget/TextView.html#attr_android:autoLink.

[EDIT: Note that what follows no longer applies but remains here for historical purposes.]

When we run the above code in Android 2.3.6 the phone dialer appears and does so with our number pre-populated ready to be dialed. Unfortunately on iOS 5 this doesn’t happen. A quick review of iOS documentation implies that it should work – so I suppose its just broken.
No need to panic, there is a PhoneGap plugin available which will take care of things. The plugin can be downloaded from here:

Click here to download the iOS Phone Dialer PhoneGap plugin

Its simple to install – just drag and drop the “m” and “h” files on to the classes folder of your xcode project. When you do this a dialog will appear with some options – be sure to click the radio button for copying “…files if needed..”.

Next, update the PhoneGap.plist file to reflect that you are adding a new plugin. The link for downloading the plugin explains the plist values as being “phonedialer > PhoneDialer”… but I think its easier to explain with an image:

The final step is to place the “PhoneDialer.js” javascript file somewhere within the root of your project and then to add it to your index.html file via a script tag.

Now that the Phone Dialer plugin is installed you’ll naturally want to know how to use it:

...
window.plugins.phoneDialer.dial('1-800-555-1234');
...

All in all pretty easy and straight forward, however now you have two methods of dialing a number within a single project. What you want is to use the tel: url scheme in Android and the Phone Dialer plugin in iOS.

Within Sencha Touch we have something called the Ext.is object whose attributes reflect everything that you could possibly want to know about the environment that your mobile app is living within.

For our purposes all we want to know is if we are in iOS or if we are in Android. These two lines provide us the answer:

...
Ext.is.Android // boolean, "true" for android, false otherwise
Ext.is.iOS // boolean, "true" for iOS, false otherwise
...

Thats all we need to impliment phone dialing across the two platforms within our mobile app. Lets build a function that makes use of one of the above (we don’t need both) and we should also give the user a choice in the matter, so the code below includes a message to the user to see if they really do want to suspend the app in favor of the device’s phone dialer:

...
function callSomeone(){
    var msg = Ext.Msg.confirm('Please Confirm','Are you sure you want to make a phone call?',
        function(r){
            if (r == 'yes'){
                if (Ext.is.Android){
                    document.location.href = 'tel:+1-800-555-1234';
		} else { // we assume the device is running iOS
		    window.plugins.phoneDialer.dial('1-800-555-1234');
		}
	    }   
	});
    msg.doComponentLayout();
}
...

All done… I suppose the very last thing to do here is to provide a complete working Sencha Touch example, and some screen captures…

...
Ext.setup({
    onReady: function(){
	
        var rootPanel = new Ext.form.FormPanel({
	    fullscreen: true,
	        items: [
		    {
			xtype:'button',
			text:'Call 1-800-555-1234',
			scope:this,
			handler: callSomeone
		    }
		],
		dockedItems:[
		    {
			xtype:'toolbar',
			dock:'top',
			title:'Phone Dialer Example'
		    }          
		]
	    }
	);
	
	function callSomeone(){
	    var msg = Ext.Msg.confirm('Please Confirm','Are you sure you want to make a phone call?',
		function(r){
		    if (r == 'yes'){
		        if (Ext.is.Android){
		            document.location.href = 'tel:+1-800-555-1234';
			} else { // we assume the device is running iOS
			    window.plugins.phoneDialer.dial('1-800-555-1234');
			}
		    }   
		}
	    );
	    msg.doComponentLayout();
	}

    }
});
...

The final product on a Samsung Galaxy S2:

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);
			}
		}
	}
});