Sunday, June 24, 2012

CoffeeScript Intro


So I'm learning CoffeeScript at work since the project I'll be working on uses that for most of its JavaScript generation. My initial thought was, "Why learn a language that generates JS"?  I mean, I can just write the JS myself.  Well, the more I look into it, the more I appreciate CoffeeScript. Yes, the syntax is a bit odd compared to traditional JS, but it's in line with Ruby.  The concise nature is really appealing.

To get started you need a working copy of Node.js. Install that, then run the following command:
$> npm install coffee-script

You can check your installation by running:
$> coffee -v
CoffeeScript version 1.3.1

Now you have two ways to use CoffeeScript. You can start the interactive REPL by typing:
$> coffee
coffee>
From here you can enter CoffeeScript commands to execute.

 The more common approach would be to author a .coffee file and compile it via:
$> coffee -c foo.coffee

This will produce a foo.js file that can be executed via:
$> node foo.js

You also have the option of just running a CoffeeScript file from the command line by dropping the '-c' option.  This will execute the script without compiling the extra JS file.

Now let's take a look at some actual CoffeeScript. Create a file called foo.coffee and enter the following:
# Base class
class Automobile
  constructor: (options) ->
    @make = options?.make ? "unknown"
    @model = options?.model ? "unknown"
    @trimLevel = options?.trimLevel ? "unknown"
    @displacement = options?.displacement ? "unknown"
    @cylinders = options?.cylinders ? "unknown"
    @transmission = options?.transmission ? "automatic"
  
  toString: () ->
    console.log("          make: " + @make + "\n" +
      "         model: " + @model + "\n" +
      "     trimLevel: " + @trimLevel + "\n" +
      "  displacement: " + @displacement + "\n" +
      "     cylinders: " + @cylinders + "\n" +
      "  transmission: " + @transmission + "\n")

# Car class
class Car extends Automobile
  constructor: (options, @bodyStyle="sedan") ->
    super(options)
  
  toString: () ->
    super console.log("     bodyStyle: " + @bodyStyle)

# Truck class
class Truck extends Automobile
  constructor: (options, @towingCapacity=2000) ->
    super(options)
  
  toString: () ->
    super console.log("towingCapacity: " + @towingCapacity)

# Create an array of autos, some with properties set
c1 = new Car({make:"Nissan", model:"Altima", trimLevel:"SL", displacement:3.5, cylinders:6})
c2 = new Car({make:"Volvo", model:"T5", displacement:3.5, cylinders:5, transmission:"5-speed"})
t1 = new Truck ({make:"Ford", model:"F150", trimLevel:"Laredo", displacement:5, cylinders:6, towingCapacity:9500})
autos = [ c1, c2, t1]

# Loop through the 'cars' array and print out each object
auto.toString() for auto in autos
This is 42 lines of code including comments and blank lines.  This compiles to:
// Generated by CoffeeScript 1.3.1
(function() {
  var Automobile, Car, Truck, auto, autos, c1, c2, t1, _i, _len,
    __hasProp = {}.hasOwnProperty,
    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };

  Automobile = (function() {

    Automobile.name = 'Automobile';

    function Automobile(options) {
      var _ref, _ref1, _ref2, _ref3, _ref4, _ref5;
      this.make = (_ref = options != null ? options.make : void 0) != null ? _ref : "unknown";
      this.model = (_ref1 = options != null ? options.model : void 0) != null ? _ref1 : "unknown";
      this.trimLevel = (_ref2 = options != null ? options.trimLevel : void 0) != null ? _ref2 : "unknown";
      this.displacement = (_ref3 = options != null ? options.displacement : void 0) != null ? _ref3 : "unknown";
      this.cylinders = (_ref4 = options != null ? options.cylinders : void 0) != null ? _ref4 : "unknown";
      this.transmission = (_ref5 = options != null ? options.transmission : void 0) != null ? _ref5 : "automatic";
    }

    Automobile.prototype.toString = function() {
      return console.log("          make: " + this.make + "\n" + "         model: " + this.model + "\n" + "     trimLevel: " + this.trimLevel + "\n" + "  displacement: " + this.displacement + "\n" + "     cylinders: " + this.cylinders + "\n" + "  transmission: " + this.transmission + "\n");
    };

    return Automobile;

  })();

  Car = (function(_super) {

    __extends(Car, _super);

    Car.name = 'Car';

    function Car(options, bodyStyle) {
      this.bodyStyle = bodyStyle != null ? bodyStyle : "sedan";
      Car.__super__.constructor.call(this, options);
    }

    Car.prototype.toString = function() {
      return Car.__super__.toString.call(this, console.log("     bodyStyle: " + this.bodyStyle));
    };

    return Car;

  })(Automobile);

  Truck = (function(_super) {

    __extends(Truck, _super);

    Truck.name = 'Truck';

    function Truck(options, towingCapacity) {
      this.towingCapacity = towingCapacity != null ? towingCapacity : 2000;
      Truck.__super__.constructor.call(this, options);
    }

    Truck.prototype.toString = function() {
      return Truck.__super__.toString.call(this, console.log("towingCapacity: " + this.towingCapacity));
    };

    return Truck;

  })(Automobile);

  c1 = new Car({
    make: "Nissan",
    model: "Altima",
    trimLevel: "SL",
    displacement: 3.5,
    cylinders: 6
  });

  c2 = new Car({
    make: "Volvo",
    model: "T5",
    displacement: 3.5,
    cylinders: 5,
    transmission: "5-speed"
  });

  t1 = new Truck({
    make: "Ford",
    model: "F150",
    trimLevel: "Laredo",
    displacement: 5,
    cylinders: 6,
    towingCapacity: 9500
  });

  autos = [c1, c2, t1];

  for (_i = 0, _len = autos.length; _i < _len; _i++) {
    auto = autos[_i];
    auto.toString();
  }

}).call(this);
This JavaScript is 99 lines including blank lines.

Now admittedly, this example doesn't really do much, but it does show how easy it is to define classes, extend them, override methods, and perform some simple operations upon them.  I think that the CoffeeScript is really easy to read, and the intent of the code is pretty straightforward.  That's one of the points of CoffeeScript; The code should be self documenting.  I think that might be true for people familiar with the CoffeeScript syntax, but a complete novice would still need some comments so that's why I added a few.

Like I stated before, I really like the terse nature of the language.  Gone are all of the unnecessary parenthesis and semicolons.  This does mean though that your indentation are significant as that's how blocks of code are grouped.  The CoffeeScript file is 57 less lines of code, and that alone is reason to switch to CoffeeScript in my book!

I encourage you to stop by coffeescript.org and check it out.

Thursday, January 20, 2011

Tween Runtime Errors with RSLs

If you decide to use Runtime Shared Libraries and get a missing Tween class RTE as your application is starting, you need to swap the order that the RTEs are loaded. What's happening is that the datavisualization.swc relies upon the framework.swc, but it loads before the framework.swc, causing the RTE.

The fix is simple. In the Flex Library path tab, move the datavisualization.swc below the framework.swc (see picture). This will load the framework before the datavisualization.

If you are compiling via the command line, make sure the datavisualization RSL entry in your flex-config.xml file is after the framework RSL entry.

Sunday, January 2, 2011

MaxCharacterStatus Component

The MaxCharacterStatus control provides a mechanism to make the user aware of the fact that a TextInput or TextArea has a limit on the amount of characters that can be entered. It shows a visual clue in the form of a bar that fills the length of the monitored control as the maxChars limit is approached.

By default, the MaxCharacterStatus will be invisible until the monitoredComponent gains focus. This behavior can be overridden by setting the showOnFocusOut property to true.

The status bar will lay on top of the border unless you add padding. This is only important if you want the status bar to be fully within the bounds of the monitored component, or if you're using custom borders.

Since the MaxCharacterStatus class is not an extension of TextArea or TextInput, it was intended to be attached to the displayList of the monitoredComponent. Why did I do it this way? Mainly to see if I could. I think in production I would simply override TextArea and TextInput, adding the bits to display the status bar. However, given that it's meant to be injected into it's host component, it is recommended to be instantiated in a creationComplete event handler like so:

private function onCreationComplete( event:FlexEvent ):void
{
var mcs:MaxCharacterStatus = new MaxCharacterStatus(myTextInput);
myTextInput.addChild(mcs);
}

The <bell:MaxCharacterStatus> tag inherits all of the tag attributes of its superclass (UIComponent), and adds the following tag attributes:
Properties 
monitoredComponent="{TextArea|TextInput}"
showOnFocusOut="false"
 
Styles
paddingBottom="0"
paddingLeft="0"
paddingRight="0"
paddingTop="0"
statusBarColor="0xFF0000"
verticalAlign="top|bottom"

A demo.
The MaxCharacterStatus source for download.

Monday, September 20, 2010

DataGridColumn widths not honored

While working a bug, I came across something very interesting in regard to a DataGrid's horizontalScrollPolicy. This was the bug: A DataGrid had a few columns, some with specific widths. Oddly though, the width properties seemed to be ignored, and the columns widths were sized in odd proportions. They weren't evenly spaced as you'd expect if the width was totally ignored. It seemed like the width was just picked at random because they made no sense.

I tracked it down to the horizontalScrollPolicy of the DataGrid.

The horizontalScrollPolicy is significant because when the policy is set to ‘off’, individual DataGridColumn widths are ignored. This prevents you from setting the width of a column, and having the rest proportional. To set specific column widths, you must set the policy to ‘auto’ or ‘on’. It you set it to ‘on’, the scrollbar is shown even if one is not needed. Setting the policy to ‘auto’ will show/hide the scrollbar if needed.

I think that this would have been an important point to address in the ASDocs. I should also point out that this was in reference to the 3.5 SDK. Perhaps 4.x is different.

Friday, March 20, 2009

Stack Funkiness

Don't know how it happened, but my stacks (OSX 10.5.6) got into this state where if I clicked on the 'open in finder' button, it would open Apple Loops Utility instead. I solved this by typing the following in Terminal:

iMac-G5% /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -kill -r -domain local -domain system -domain user
iMac-G5% killall Dock

That solved it. Like I said, don't know how it happened, but finally got annoyed enough with it to do something about it.

Sunday, October 5, 2008

TextInputPrompted Class

If you don't have a lot of space for a label next to a TextInput, then you may want to try using my TextInputPrompted class.

You use this class just like you would any other TextInput, but there's an extra prompt property. Whatever you set prompt to is displayed in the TextInput in light grey. Once you start typing something in the TextInput, the prompt will go away, and the text turns dark.
View a demo.



 /**  
* Copyright (c) 2008 Geoffrey T. Bell
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package
{
import flash.events.FocusEvent;
import flash.events.KeyboardEvent;

import mx.controls.TextInput;

public class TextInputPrompted extends TextInput
{
private var __prompt:String = null;
private var __firstTime:Boolean = true;

/**
* Getter for prompt
*/
public function get prompt():String
{
return __prompt;
}

/**
* Setter for prompt
*/
public function set prompt( prompt:String ):void
{
__prompt = prompt;
showPrompt();
}

/**
* Getter for text
*/
override public function get text():String
{
return super.text;
}

/**
* Setter for text
*/
override public function set text( text:String ):void
{
hidePrompt();
super.text = text;
}

/**
* Constructor
*/
public function TextInputPrompted()
{
super();

if (prompt != null && prompt != "")
{
showPrompt();
}

// Event listeners
this.addEventListener(FocusEvent.FOCUS_IN, onFocusIn);
this.addEventListener(FocusEvent.FOCUS_OUT, onFocusOut);
}

private function showPrompt():void
{
// If promopt is set, add it to the text property and set style
if (prompt != null && prompt != "" && text.length <= 0)
{
super.text = prompt;

// Style
this.setStyle("color", "#AAAAAA");
}
}

private function hidePrompt():void
{
// Remove prompt from text string and set style to default
super.text = null;

// Style
this.setStyle("color", "#333333");
}

private function onFocusIn( e:FocusEvent ):void
{
// Add listener for keys so we can hide prompt
this.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}

private function onFocusOut( e:FocusEvent ):void
{
// Remove listener for keys
this.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);

// Show prompt if text is blank
if (super.text == null || super.text == "")
{
showPrompt();
}

__firstTime = true;
}

private function onKeyDown( e:KeyboardEvent ):void
{
// Remove prompt if keyCode is a valid text character
if ((e.keyCode >=48 && e.keyCode <=57) ||
(e.keyCode >=65 && e.keyCode <=90) ||
(e.keyCode >=96 && e.keyCode <=107) ||
(e.keyCode >=109 && e.keyCode <=111) ||
(e.keyCode >=186 && e.keyCode <=192) ||
(e.keyCode >=219 && e.keyCode <=222))
{
hidePrompt();

// Remove listener for keys
this.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}
}
}
}