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