Cross Domain Error on Localhost on Any Error in Google Apps Script

I noticed an odd-feeling error while playing with a frontend AJAX call from a localhost server that fetched data from a Google Sheet via a Google Apps Script middleware script.

Any time there was any kind of error in the Google Apps Script code. The JSON-P-based front end call was receiving this message (using the Chrome dev tools Console):

Cross-Origin Read Blocking (CORB) blocked cross-origin response https://script.google.com/macros/s/-yourScriptID-/exec?action=rd with MIME type text/html. See https://www.chromestatus.com/feature/5629709824032768 for more details.</code>

Technically, of course, it was a showing up as a CORS (Cross Domain Resource Sharing) or CORB (Cross Domain Read Blocking) error.

I bet the Google Apps Script error message automatically sends data back as HTML/Text and this is messing with the JSON-P callback, which is expecting the data type to be “javascript”.

DC Metro API – Show Real-Time Train Arrivals with Javascript

I’ve worked with Twitter, Instagram and Facebook APIs. Those are always a bundle of unpredictable joy/not, as APIs usually are. The WMATA (DC Metro) API is fairly easy to work with by comparison. They don’t have to worry about anyone’s personally identifiable information (PII), hence, less headaches for developers. They provide an adequate level of documentation and are reasonably responsive over email. All issues I had while working with this code were easy enough to resolve, using the ol’ Googley machine and common sense. A business lead I worked with was extra enthusiastic about the project, so he did email the API developers a few times and they responded on the same day.

The rate limit is 10 calls/second and 50,000 calls per day, which should work just fine for most uses. I’d pay extra attention here if you have multiple versions of the code during development (using the live end point) or, if you have a live version out and are working on changes, while using the live API (which is sometimes unavoidable, since there’s no dev end point).

In general, if you call it 3 times a minute or every 20 seconds, as the WMATA’s own real time arrivals web app does, you’ll be more than ok. Here’s a screenshot:

You can see their list of real time arrival stations here. It works rather well and uses their Real-Time Rail Predictions API, but one thing I’d certainly do differently – it’s literally reloading the whole page every 20 seconds to update the data. I’m guessing a backend developer put this together real quick, while working under several deadlines. A simple AJAX call can fix this (there are multitudes of examples of this on StackOverflow alone).

Note: if their app/API feed is down, their real time arrivals link just shows a mostly blank page:

Again, I’m guessing this was done in a hurry.

Playing with MakeyMakey and Phillips Hue API

It’s useless #prototyping Friday. Used my #makeymakey , w/ spacebar clip hooked up to a paint can, with chicken scratch Javascript code w/ a keydown() event handler that listens for keyCode=32 (spacebar) and sends an ajax PUT request to the #hueapi #phillipshue which turns light 1 along with an html body tag background-color prop to red.

Unity3D for iOS: Relative Positioning with UIToolkit

Here’s one way to horizontally center a UIToolkit object, such as a UIButton, using positionCenterX(). Should work pretty good on the various iPad sizes, iPhone 4, iPhone 5. The one thing to keep in mind is the scale of the actual graphic will need to be adjusted, either via UIToolkit’s UI.cs class that listens for HD, SD or supper HD screen sizes or manually via your own code.

Javascript version

#pragma strict
public var buttonToolkit:UIToolkit;
private var newGameBtn:UIButton;

function Start() {
    newGameBtn = UIButton.create( buttonToolkit, "playBtnOff.png", "playBtnOn.png", 0, 0 );	
	newGameBtn.zIndex = 0;

	//centers it horizontally on iPad and iPhone: 
	newGameBtn.positionCenterX();
	//relatively positions it along y axis; slightly differnt actual position on iPads, iPhone 4, iPhone 5 
	newGameBtn.position.y = UIRelative.yPercentFrom( UIyAnchor.Bottom, 1.25f );	

	newGameBtn.highlightedTouchOffsets = new UIEdgeOffsets( 10 );	
	newGameBtn.onTouchUpInside += newGameBtnHandler;	
	newGameBtn.alphaFrom( 1.0f, 0.0f, Easing.Quintic.easeOut );
}

function newGameBtnHandler( sender:UIButton ) 
{
	Debug.Log("newGameBtn clicked!");
}

C# version

using UnityEngine;
using System;
using Prime31;

public class ExampleManagerScript : MonoBehaviour {

	//set via Inspector panel
	public UIToolkit buttonToolkit; 

    private UIButton newGameBtn;

	void Start() {	

		newGameBtn = UIButton.create( buttonToolkit, "playBtnOff.png", "playBtnOn.png", 0, 0 );	
		newGameBtn.zIndex = 0;

		//centers it horizontally on iPad and iPhone: 
		newGameBtn.positionCenterX();
		//relatively positions it along y axis; slightly differnt actual position on iPads, iPhone 4, iPhone 5 
		newGameBtn.position.y = UIRelative.yPercentFrom( UIyAnchor.Bottom, 1.25f );	

		newGameBtn.highlightedTouchOffsets = new UIEdgeOffsets( 10 );	
		newGameBtn.onTouchUpInside += newGameBtnHandler;	
		newGameBtn.alphaFrom( 1.0f, 0.0f, Easing.Quintic.easeOut );
	}

	void newGameBtnHandler( UIButton sender ) 
	{
		Debug.Log("newGameBtn clicked!");
	}
}

Titanium for iOS: Changing System Date breaks the Simulator Build Process – “fatal error: file” ‘_Prefix.pch’ “has been modified since the precompiled header was built”

Got this “fatal error” with Titanium Studio, build: 2.1.2.201208301612 and Titanium SDK version 2.1.3 after I had to change my OS X system Date via Date & Time Preferences manually while trying to test badge activation functionality that involved keeping track of an activity being performed several consecutive days in a row. All of the usual solutions, like Clean project, delete DerivedData, delete /build/ folder didn’t work this time.
Changing the system Date back to the correct one, as implied here, didn’t help either.

[INFO] One moment, building ...
[INFO] Titanium SDK version: 2.1.3 (10/02/12 16:16 15997d0)
[INFO] iPhone Device family: universal
[INFO] iPhone SDK version: 5.1
[INFO] iPhone simulated device: ipad
[INFO] Minimum iOS version: 4.0 Linked iOS Version 5.1
[INFO] Compiling localization files
[ERROR] fatal error: file '/Users/yourUserName/Desktop/SVN/CompanyName/trunk/yourProject/build/iphone/yourProjectName_Prefix.pch' has been modified since the precompiled header was built
[ERROR] fatal error: file '/Users/yourUserName/Desktop/SVN/CompanyName/trunk/yourProject/build/iphone/yourProjectName_Prefix.pch' has been modified since the precompiled header was built
[ERROR] fatal error: file '/Users/yourUserName/Desktop/SVN/CompanyName/trunk/yourProject/build/iphone/yourProjectName_Prefix.pch' has been modified since the precompiled header was built
[ERROR] fatal error: file '/Users/yourUserName/Desktop/SVN/CompanyName/trunk/yourProject/build/iphone/yourProjectName_Prefix.pch' has been modified since the precompiled header was built
[ERROR] 
[ERROR] Error: Traceback (most recent call last):
  File "/Users/yourUserName/Library/Application Support/Titanium/mobilesdk/osx/2.1.3.GA/iphone/builder.py", line 1325, in main
    execute_xcode("iphonesimulator%s" % link_version,["GCC_PREPROCESSOR_DEFINITIONS=__LOG__ID__=%s DEPLOYTYPE=development TI_DEVELOPMENT=1 DEBUG=1 TI_VERSION=%s %s %s" % (log_id,sdk_version,debugstr,kroll_coverage)],False)
  File "/Users/yourUserName/Library/Application Support/Titanium/mobilesdk/osx/2.1.3.GA/iphone/builder.py", line 1231, in execute_xcode
    output = run.run(args,False,False,o)
  File "/Users/yourUserName/Library/Application Support/Titanium/mobilesdk/osx/2.1.3.GA/iphone/run.py", line 41, in run
    sys.exit(rc)
SystemExit: 65

Lame workaround

Let Titanium IDE generate the build folder w/ the Xcode project. Launch Xcode by double clicking the yourProjectName.xcodeproj and Build from there.

What’s even worse, is that building to a device works out of Titanium IDE in this case but not from Xcode 4.3.3. In XCode, I get this error: “Apple Mach-O Linker error… linker command failed with exit code 1 (use -v to see invocation)”

Solution

My buddy Charles, suggested this approach:

  1. Instead of Run > iPad Simulator, try Debug > iPad Simulator. This should get the Simulator out of its stupor and running again.
  2. Go to the Simulator’s equivalent of DerivedData and delete all project folders (titled with what looks like random strings):
    /Users/yourUserName/Library/Application Support/iPhone Simulator/5.1
  3. Go to Run > Run Configurations, select your simulator configuration, like Titanium iOS Simulator > Titanium iPad Simulator.
  4. Check the box next to “Re-build project on launch” and hit Apply

The problem re-appeared a few times again on the same day after the above fix took care of it. Going back into Run Configurations and unchecking and then re-checking “Re-build project on launch” fixed it each time. UPDATE, 12.07.2012: the weird part was that sometimes just doing the Run Configuration checkbox thing didn’t always work when the problem re-appeared, so I had to run through all the steps above again.

Titanium for iOS: POST a JSON Object to a Web Service

Assume I’m using the boilerplate Ti.Network.createHTTPClient call with an “onload” & “onerror”.

...
    var xhr = Ti.Network.createHTTPClient({ ... });    
 
    myObj = { mydata:  
        { myitems: [ 
                { mainID:7452, someOption:87, theDate:'2012-10-23' },
                { mainID:2342, someOption:27, theDate:'2011-06-03' },
                { mainID:1914, someOption:43, theDate:'2012-02-14' }
            ]
        }
    };
...
    var strMyObj = JSON.stringify(myObj);
	Ti.API.info("strMyObj = " + strMyObj);

    xhr.open("POST", "http://www.yourPathHere.com/yourWebServiceName");
	xhr.setRequestHeader('Accept','application/json'); 
	xhr.setRequestHeader('Content-Type','application/json'); 				
	xhr.send( strMyObj ); 
...

Works in CURL, not in Titanium

I ran into this issue first. The following CURL command worked in Terminal:

curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d "{mydata:{myitems:[{mainID:7452, someOption:87, theDate:'2012-10-23'}]}}"
 http://www.yourPathHere.com/yourWebServiceName

In Titanium I kept getting HTTP response status “403” because I made stupid mistake, using “PUT” instead of “POST” in xhr.open(). Good times.
The “-i” stands for “include”, “-H” specifies an HTTP header. The “-X” in front of “POST” tells curl to use something other than “GET”. “-d” stands for data to include in your HTTP POST, mimicking an HTML form request. Here’s a list of CURL flags.

JSON.stringify

After fixing that, I got HTTP response status “500”, by just doing “xhr.send(myObj);”.

Thanks to some debugging by my backend dev Wes, it turned out the service simply wanted the data formatted as a String. He tried, taking the quotes off the {mydata…} line in the CURL command and got the same status 500 error in Terminal and via Chrome’s console:

curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d {mydata:{myitems:[{mainID:7452, someOption:87, theDate:'2012-10-23'}]}} 
 http://www.yourPathHere.com/yourWebServiceName

curl: (6) Could not resolve host: someOption:87,; nodename nor servname provided, or not known
curl: (3) [globbing] unmatched close brace/bracket at pos 19
HTTP/1.1 500 Internal Server Error
Date: Fri, 05 Dec 2012 18:58:48 GMT
Server:...
jsonerror: true
Cache-Control: private
Content-Type: application/json; charset=utf-8
Content-Length: 1761
{"Message":"Invalid object passed in, \u0027:\u0027 or \u0027}\u0027 expected. (31): {mydata:{entries:[{mainID:7452,","StackTrace":"   at..."}

Titanium for iOS: Faking a Lightbox style Modal Window in your App

This is an example of how to do a Lightbox style modal window (a View posting as a Window) in a Titanium 2.x SDK iOS app.

Why bother?

Sometimes it’s useful for in app messaging, when you want to avoid opening an actual Window via Ti.UI.createWindow and stay in the same execution context as recommended by the best practices section of Titanium docs. You can also use this approach when the look & feel of the default iOS alert window just isn’t your style.

Sample usage

Here’s how you’d use it:

   var FakeModalWindow = require('/ui/FakeModalWindow');	
  
    var someText = "OK, some new text. Blah Blah Blah Blah Blah . \n\nThis new one here. \n\nYou new copy. \n\n Some More stuff and stuff.."; 
  	var fmw = new FakeModalWindow();
	self.add(fmw); 		
		
	//to change the copy in the fmw's TextArea: 
	fmw.fireEvent('setCopy_fake_modal_win', {newCopy:someText}); 
			
	self.addEventListener('close_fake_modal_win', function(e){		
		fmw.hide();		
	});
	
	self.addEventListener('open_fake_modal_win', function(e){		
		fmw.show();		
	});	

Source

Here’s the actual FakeModalWindow.js module code:


module.exports = function FakeModalWindow() {

	var theCopy = "Lorem Headline" + 
		           "\n\nLorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "+
				   "\n\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " + 
				   "\n\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur." +				  
				   "\n\nExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. "; 
	}
	
	// some variables we'll need
	var OS = Ti.Platform.osname;	
	var SLASH = Ti.Filesystem.separator;
	var imgPath = SLASH + 'images'; 	
	
	//create component instance
	var self = Ti.UI.createView({
		width: Ti.UI.FILL,
		height: Ti.UI.FILL,
		backgroundColor:'#65000000',
		zIndex : '50',
	})	
	
	/**		
	 * Main container w/ white background & rounded corners
	 */	
	var content_win = Ti.UI.createView({
		backgroundColor:'#ffffff',	
		layout: 'vertical',
		width: (OS==='ipad') ? '542dip' : '270dip',	//'542dip', //
		height: Ti.UI.SIZE, //'526dip' (OS==='ipad') ? '526dip' : '15dip',	
		borderRadius: (OS==='ipad') ? 6 : 2	
	});	
	
	/**		
	 * Horizontal container 1
	 */	
	var title_bar = Ti.UI.createView({
		layout: 'horizontal',
		height: Ti.UI.SIZE,
		//borderColor: '#FF00FF',						
		//borderWidth: 1				
	});	
	
	/**
	 * Horizontal container 2
	 */	
	var content_bar = Ti.UI.createView({
		layout: 'horizontal',
		height: Ti.UI.SIZE,
		//borderColor: '#FF0000',
		//borderWidth: 1	
	});		
	
	/**
	 * Horizontal container 3
	 */	
	var footer_bar = Ti.UI.createView({
		layout: 'horizontal',
		height: (OS==='ipad') ? '20dip' : '10dip',
		//borderColor: '#FF0000',
		//borderWidth: 1	
	});			
		
	/**
	 * Begin content pieces  
	 */
	var orange_bar = Ti.UI.createView({		
		backgroundColor: '#f37416',	
		width: Ti.UI.FILL, 
		height: (OS==='ipad') ? '57dip' : '26dip',	
		borderRadius: (OS==='ipad') ? 6 : 2,
		borderColor: '#white',
		borderWidth: (OS==='ipad') ? 5 : 2,
	});			
	
	var close_button = Ti.UI.createView({		
		backgroundImage: '/images/shared/btn_close.png',	
		left: (OS==='ipad') ? '485dip' : '5dip',	 
		width: (OS==='ipad') ? '37dip' : '18dip', 
		height: (OS==='ipad') ? '37dip' : '18dip' 	
	});	
		
	var body_copy = Ti.UI.createTextArea({
		//borderWidth: 2,
	    //borderColor: '#CCFF33',	    
		value: theCopy,		
		editable: false,
		textAlign: 'left',
		top: (OS==='ipad') ? '15dip' : '7dip',
		left: (OS==='ipad') ? '20dip' : '10dip',
		color: '#000000',		
		font: { fontSize: (OS==='ipad') ?'20dip' : '12dip' },
		width: Ti.UI.FILL,
		height: Ti.UI.SIZE 				
	});			
	content_win.updateLayout({height:Ti.UI.SIZE});
		
	orange_bar.add(close_button);			
	title_bar.add(orange_bar);		
	content_bar.add(body_copy);			
	contentWin.add(title_bar);	
	contentWin.add(content_bar);	
	contentWin.add(footer_bar);
	self.add(content_win);

	/**
	 * Event listeners 
	 */
  	self.addEventListener('setCopy_fake_modal_win', function(e){			
		body_copy.setValue(e.newCopy);				
		body_copy.updateLayout( {height: Ti.UI.SIZE } );				 		
	});	
	
	self.addEventListener('click', function(e){  // if the user interacts anywhere, close the modal window
		self.fireEvent('close_fake_modal_win');	
	});
		
	return self;
}  

Titanium for iOS: Manipulating Width, Height, Left, Top of Views in the iOS Simulator Using the Debug View

I learned this useful technique from Charles Loflin, a brilliant developer I’ve had a chance to work with recently. This approach lets you reduce the amount of guess work involved in trying to set the x, y positioning and width, height of elements on screen. Instead of having to Run after every time you make a change to the left, top or width properties of a View, you can just Run once, figure out the correct values with live preview in the Simulator and update your code once.

Using Eclipe IDE’s DEBUG View to set width, height of a Ti.UI.View via the Simulator

The steps involved:

  1. add footr.fireEvent(‘MYDEBUG_Event’) in ApplicationWindow.js,right after you declare ‘myv’ as an instance of MyNestedView.js.
  2. add listener for ‘MYDEBUG_Event’ in MyNestedView.js after you declare the visual subview you’re trying to position (var headerImage).
  3. set break point for a Ti.API.info line inside the ‘MYDEBUG_Event’ listener – this will give you access to module’s variable (‘var headerImage’) in the Variables View of the Debug perspective (Window > Open Perspective > Debug).
  4. Select the class member whose width/height you’re trying to manipulate in the Variables View. In this case, select ‘headerImage’ var and drill down to it’s ‘width’ property. Select it ‘width’. It’s value should show up in the bottom input text panel of the Variables View. Change the value to something new (don’t use quotes, it’ll add it for you) and hit Cmd+S to Assign Value. You should see the view’s width change in the Simulator.

Here’s the fireEvent call for Step 1:

//Application Window Component Constructor
module.exports = function ApplicationWindow() {
	
	//load component dependencies	
	var MyNestedView = require('ui/MyNestedView');	
    ... 
	var myv = new MyNestedView();
	self.add(myv);
	
	//FOR DEBUG ONLY: 
	myv.fireEvent('MYDEBUG_Event');
    ...
    return self;
}

ApplicationWindow.js is loaded in from app.js in the same way the Single Window application template does in the Titanium IDE.

Here’s the listener call for Step 2:

module.exports = function MyNestedView() {
	
	var OS = Ti.Platform.osname;
	var SLASH = Ti.Filesystem.separator;
	var imgPath = SLASH + 'images';	
					
	//create component instance
	var self = Ti.UI.createView({
		bottom: (OS==='ipad') ? '10dip' : '20dip', 
		//backgroundColor: '#FF00FF', //for debug only
		width: Ti.UI.FILL,
		height: Ti.UI.SIZE //keep the view only as tall as needed to fit the text, otherwise it'll cover up links below 		
	});

	var headerImage = Ti.UI.createView({			
		left:'11dip', 
		width: (OS==='ipad') ? '751' : '314dip',   // 314/751 = x/100 --> 31400 = 751x --> x = 31400/751 --> x = 41.81 ~ 42%
		height: (OS==='ipad') ? '135dip' : '57dip', // 42% of 135dip = 56.7 ~ 57		
		backgroundImage: imgPath + SLASH + 'shared' + SLASH + 'my_header_image.png' 
	});		
	self.add(headerImage);

                //FOR DEBUG ONLY: 
		self.addEventListener('MYDEBUG_Event', function(){ 		
			Ti.API.info("break point point in MyNestedView"); //set breakpoint on this line
		});
	
...

    return self;
}

The fireEvent and its associated listener allow us to access the scope of MyNestedView module after MyNestedView’s “return self;” fires. Once we’re in this scope, we have access to it’s “var headerImage” member.

Titanium SDK for iOS: ScrollableView for Displaying Several Pages of Photo Thumbnails

Here’s a quick example of one way to dynamically populate a ScrollableView using Titanium SDK 2.x. I started with an example from this pastebin sample.

The below code displays a horizontally ScrollableView with default iOS page controls. Each page displays 6 photo thumbnails, while the last page displays the remainder (less than 6). This should work on Android, but I haven’t had time to test it yet (famous last words, I know).

SideScrollPagedView() is a CommonJS module (one of the recommended ways to organize code in Titanium).

module.exports = function SideScrollPagedView() {
	
    var OS = Ti.Platform.osname;	

	// in real life this might come from a database or web service or be dynamically populated from a local directory 
	var data = [ { PhotoID: 1, Photo_Title: 'my first photo', Thumb_Path: 'photothumb_1.jpg'},
				 { PhotoID: 2, Photo_Title: 'my second photo', Thumb_Path: 'photothumb_2.jpg'},  		
                 { PhotoID: 3, Photo_Title: 'my third photo', Thumb_Path: 'photothumb_3.jpg'}, 
                 { PhotoID: 4, Photo_Title: 'my fourth photo', Thumb_Path: 'photothumb_4.jpg' }, 
                 { PhotoID: 5, Photo_Title: 'my fifth photo', Thumb_Path: 'photothumb_5.jpg'}, 
                 { PhotoID: 6, Photo_Title: 'my sixth photo', Thumb_Path: 'photothumb_6.jpg'}, 
                 { PhotoID: 7, Photo_Title: 'yeah, 7th photo', Thumb_Path: 'photothumb_7.jpg'}, 
                 { PhotoID: 8, Photo_Title: 'ok, 8th photo', Thumb_Path: 'photothumb_8.jpg'}, 	
                 { PhotoID: 9, Photo_Title: 'this is the 9th photo', Thumb_Path: 'photothumb_9.jpg'}, 
                 { PhotoID: 10, Photo_Title: 'photo number 10', Thumb_Path: 'photothumb_10.jpg'}, 
                 { PhotoID: 11, Photo_Title: 'photo number 11', Thumb_Path: 'photothumb_11.jpg'}, 
                 { PhotoID: 12, Photo_Title: 'photo number 12', Thumb_Path: 'photothumb_12.jpg'},
                 { PhotoID: 13, Photo_Title: 'photo number 13', Thumb_Path: 'photothumb_13.jpg'},
                 { PhotoID: 14, Photo_Title: 'photo number 14', Thumb_Path: 'photothumb_14.jpg'},
                 { PhotoID: 15, Photo_Title: 'photo number 15', Thumb_Path: 'photothumb_15.jpg'},
                 { PhotoID: 16, Photo_Title: 'photo number 16', Thumb_Path: 'photothumb_16.jpg'} ];
	
	//main view 		
	var self = Ti.UI.createView({
		layout: 'vertical',
		width: '100%',
		height: Ti.UI.SIZE
	});			
	
	//you can stack another one of these above or below this one	
	var mainContentBar = Ti.UI.createView({
		layout: 'horizontal',
		height: Ti.UI.SIZE,
		//borderColor: '#CCCCCC',
		//borderRadius: 2
	});
	self.add(mainContentBar);				
   
	var views = []; //array that holds a collection of "pages" (views) to be assigned to ScrollableView's .view property
    var gameData = data; //copy of 'data' array, lets us slice it up per page, while keeping original list in tact	
	var itemsPerPage =  (OS==='ipad') ? 6 : 4; //number of photos to display per page inside ScrollableView 
	var gutterSpace = (OS==='ipad') ? 16 : 9;  
    var halfOfItems = itemsPerPage * 0.5;
	var buttonWidth = (OS==='ipad') ? 204 : 132; 
	var buttonHeight = (OS==='ipad') ? 152 : 99; 		
	var scrollablePgW = (OS==='ipad') ? '644dip' : '273dip'; 
	var scrollablePgH = (OS==='ipad') ? '360dip' : '230dip'; 	
	var numPages = Math.ceil(data.length/itemsPerPage);	//numPages determined by number of times TotalFavorites / itemsPerPage, rounded up for whole pages		
	var itemsOnLastPage = data.length % itemsPerPage;

	var lastInRow1 = (OS==='ipad') ? 2 : 1;
	var lastInRow2 = (OS==='ipad') ? 5 : 3;
	var sidePadding = true;		
 
	for ( var i = 0 ; i < numPages; i++ )
	{
	      var view = createView(views.length);
    		
          //modify itemsPerPage for last page	           
      	  if(i == (numPages-1)) { itemsPerPage = itemsOnLastPage; }
      	 
      	  var perPageData = gameData.splice(0, itemsPerPage);  
       	 
		  //for each set of itemsPerPage (6 or less), add an icon subview to view 	
  		  for(var h = 0 ; h < itemsPerPage; h++) 
  		  {
  		      //remove gutter space from last icon in each row to prevent the parent view (w/ layout:'horizontal') from forcing the next row too early  		  	 
  			 ( h === lastInRow1 || h === lastInRow2 ) ? sidePadding = false : sidePadding = true;  
  			 
  	   	  	 var subview = createSubView( sidePadding );	      
  	   	  	 var item = perPageData[h]; 
  	   	  	   	   	  	 
  	   	  	 var iconItem = Ti.UI.createView({
				backgroundImage: '/images/photos/thumbs/' + item['Thumb_Path'],
				layout : 'composite',
				width : buttonWidth,
				height : buttonHeight,
				left : 0, 
				top : 0,
				visible : true,
				photoData : item //can be used to later grab property values like photoData.Photo_Title
			});			  	   	  	
  	   	  	iconItem.addEventListener('singletap', function(evt) { 				
				this.fireEvent('scrollItem_Clicked', {data : this.photoData	});				
			}); 			

  	   	  	//add additional subviews to iconItem here as needed

  	   	  	subview.add(iconItem);
  	   	  	 			
  			view.add(subview);
  		  }	      
      	   	  
	      views.push(view);
	}

	function createSubView(xval, yval)
	{
		var rightPadding = (OS==='ipad') ? '16dip' : '8dip';
		var btmPadding = (OS==='ipad') ? '17dip' : '8dip'; 
		if(!sidePadNum) {
		     rightPadding = 0;
		}
		
		var view = Ti.UI.createView({			
			width: buttonWidth, 
			height: buttonHeight, 			
			bottom: btmPadding, 
			right: rightPadding,
			//borderColor: '#FF00FF'			
		});		
		
		return view;
	}	

	function createView(i)
	{
		var view = Ti.UI.createView({
			backgroundColor: '#FFFFFF',
			text: i, 
			layout: 'horizontal'
		});
		return view;
	}	
	 	 
	var scrollView = Titanium.UI.createScrollableView({
			top: (OS==='ipad') ? '25dip' : '15dip',
			left: (OS==='ipad') ? '50dip' : '20dip',
			width: scrollablePgW, 
			height: scrollablePgH, 
			//borderColor: '#AACC00', //for debugging		
			views:views,
		  	showPagingControl:true,		
		  	pagingControlColor: '#666666',
	        currentPage:0
	});	 
	
	mainContentBar.add(scrollView);	        	          	
	
	return self;
}

This assumes that the actual thumbnail images are stored under /Resources/images/photos/thumbs/.

ScrollableView and clickable items – avoiding click/swipe conflicts

Notice how the “iconItem.addEventListener(‘singletap’, function(evt){})” is listening for a ‘singletap’ event and NOT a ‘click’ event. I tried using ‘click’ first and ran into the problem of the photo icons being accidentally clicked while I’m trying to swipe (scroll) the ScrollableView.

Usage

To use the above module inside another file, you can do something like this, depending on what you’re doing:

//Application Window Component Constructor
module.exports = function ApplicationWindow() {
     ...	
	//create component instance
	var self = Ti.UI.createWindow({
		layout: 'composite',
		width: Ti.UI.FILL,
		height: Ti.UI.FILL,
		backgroundColor:'#ffffff',
		navBarHidden:true,
		exitOnClose:true
	})
     ...
    var SideScrollPagedView = require('/ui/SideScrollPagedView');
    var sspv = new SideScrollPagedView();
     self.add(sspv);

    return self;
}

The above code assumes that the scrollable photos component .js file is located in this location: /Resources/ui/SideScrollPagedView.js.

The above ApplicationWindow.js component would be called by app.js in the same way it is in the SingleWindow application template that comes with the Titanium 2.x IDE, like so:

//bootstrap and check dependencies
if (Ti.version &lt; 1.8 ) {
	alert('Sorry - this application template requires Titanium Mobile SDK 1.8 or later');
}
else if (Ti.Platform.osname === 'mobileweb') {
	alert('Mobile web is not yet supported by this template');
}
else {	
	...
	
	//require and open top level UI component	
	var AppWin = require('/ui/ApplicationWindow');
	var appWin = new AppWin();
	appWin.open();	
}