Friday, February 26, 2010

Emacs Fun

Since life has been a bit slow lately, I've returned to playing with Emacs.  I first delved into it during my sophomore year at CSU.  Since I was using Linux full-time at home (Windows having been relegated to its proper status as a gaming platform), I had decided it would be beneficial to learn a decent text editor.

Yes, insert any jokes here about Emacs being a great OS with a crappy text-editor :)

Anyway, I had already used VI and I decided it was a little too cumbersome for me.  Yes, I recognize the insanity of Emacs key-strokes (and the nasty Emacs-pinky syndrome), but for some reason it made more sense to me than VI did.

Anyway, I used Emacs full-time in several classes (C++ being one of them); when I started my first professional tech job (as a software tester), I abandoned Emacs for Eclipse.

Of course, apples and oranges; Eclipse is nothing short of amazing for programming Java, and really impressive for other languages (namely Python and Perl).

Since I've moved onto full-time software development, my inclination to use Emacs has further waned.  I'm working in FlexBuilder and Eclipse full-time, and I don't have much use for it.  Except at home where I recently have done quite a bit of Perl and Python work, as well as text-editing.

As a result, I decided to delve back into the murky waters of Emacs and setup my environment so it does everything I need.  It's been a ton of fun, and my environment has been pretty simple.

Here's my .emacs file.  Note, much of this functionality is not my own.  I've tried to give credit where it's due, but there are a few function calls I wouldn't have known without looking stuff up.  Regardless, this is what I've come up with so far.  I'm running Emacs23, so some of these tweaks might be specific to this version.


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SETUP THE BASIC LAYOUT/USE OF EMACS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; set font size
(set-face-attribute 'default nil :height 110)

; turn on line numbers
(global-linum-mode 1)
(show-paren-mode 1)

; Parenthesis balancing
(setq show-paren-delay .25)
(show-paren-mode t)
(set-face-background 'show-paren-match-face "#aaaaaa")
(set-face-attribute 'show-paren-match-face nil
  :weight 'bold :underline nil :overline nil :slant 'normal)

; inhibit startup stuff
(setq initial-scratch-message nil)
(setq inhibit-startup-screen t)

; don't load default.el
(setq inhibit-default-init t)

; show a visible highlight
(setq transient-mark-mode t)

; show the buffer name in the window title
(setq frame-title-format '(buffer-file-name "%f" ("%b")))

; remove menus, toolbars, and scrollbars
(if (fboundp 'tool-bar-mode) (tool-bar-mode -1))
(if (fboundp 'menu-bar-mode) (menu-bar-mode -1))
(if (fboundp 'scroll-bar-mode) (scroll-bar-mode -1))

; Modify where backups/bookmarks are stored
(setq backup-directory-alist '(("" . "~/.emacs.d/backups")))

; Replace M-x with C-x C-m or C-x C-c
(global-set-key "\C-x\C-m" 'execute-extended-command)
(global-set-key "\C-c\C-m" 'execute-extended-command)

; don't continue to scroll at bottom of file
(setq next-line-add-newlines nil)
; ask y/n and yes/no
(fset 'yes-or-no-p 'y-or-n-p)

; Enable upcase-/downcase-region
(put 'downcase-region 'disabled nil)
(put 'upcase-region 'disabled nil)

; make text-mode default
(setq default-major-mode 'text-mode)

; don't blink cursor
(blink-cursor-mode nil)

; Highlight regions while searching/replacing
(setq search-highlight t
  query-replace-highlight t)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SETUP CUSTOM KEYSTROKES FOR DOING MUNDANE TASKS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(global-unset-key "\C-xd")
(global-unset-key "\C-xm")
(global-unset-key "\C-z")
(global-unset-key "\C-x\C-z")
(global-unset-key "\C-l")

(global-set-key "\C-l"   'goto-line)
(global-set-key "\C-xdd" 'kill-whole-line)
(global-set-key "\C-xcw" 'kill-word)
(global-set-key "\C-x%"  'forward-list)
(global-set-key "\C-c%"  'backward-list)
(global-set-key "\C-c."  'command-history)
(global-set-key "\C-x:"  'cmd)
(global-set-key "\C-z"   'zap-to-char)
(global-set-key "\C-cc"  'calendar)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ALIASES
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defalias 'sh    'shell)
(defalias 'rr    'replace-string)
(defalias 'qrr   'query-replace-regexp)
(defalias 'cmp   'compile)
(defalias 'rcmp  'recompile)
(defalias 'trunc 'toggle-truncate-lines)
(defalias 'll    'load-library)
(defalias 'ind   'indent-region)
(defalias 'cmd   'shell-command)
(defalias 'cmdr  'shell-command-region)
(defalias 'lbf   'list-buffers)
(defalias 'sbf   'switch-to-buffer)
(defalias 'kbf   'kill-buffer)
(defalias 'spell 'ispell)
(defalias 'moon  'phases-of-moon)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SPELLING
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(setq ispell-program-name "aspell"
  ispell-extra-args '("--sug-mode=ultra"))

; When we're in certain modes, turn on flyspell
(add-hook 'text-mode 'flyspell-mode)

(add-hook 'org-mode  'flyspell-mode)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; CALENDAR STUFF
;; Taken from:
;;   http://www.mygooglest.com/fni/dot-emacs.html
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(setq calendar-christian-all-holidays-flag t)
(setq holiday-islamic-holidays nil)
(setq holiday-oriental-holidays nil)
(setq holiday-bahai-holidays nil)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; CUT/COPY AND PASTE BEHAVIOR
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; modify cut/copy behavior: if no line is selected, cut/copy entire line
(defadvice kill-ring-save (before slick-copy activate compile) "When called
  interactively with no active region, copy a single line instead."
  (interactive (if mark-active (list (region-beginning) (region-end)) (message
  "Copied line") (list (line-beginning-position) (line-beginning-position
  2)))))

(defadvice kill-region (before slick-cut activate compile)
  "When called interactively with no active region, kill a single line instead."
  (interactive
    (if mark-active (list (region-beginning) (region-end))
      (list (line-beginning-position)
        (line-beginning-position 2)))))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; LOAD THE ~/.emacs.d PATH RECURSIVELY
;; Taken from: http://www.djcbsoftware.nl/dot-emacs.html
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(let* ((my-lisp-dir "~/.emacs.d/")
       (default-directory my-lisp-dir))
  (setq load-path (cons my-lisp-dir load-path))
  (normal-top-level-add-subdirs-to-load-path))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; RECOMPILE ~/.emacs ON SAVE
;; Taken from:
;;   http://www.emacswiki.org/emacs/DotEmacsChallenge
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun autocompile nil
  "This function compiles the current file iff it is ~/.emacs"
  (interactive)
  (require 'bytecomp)
  (if (string= (buffer-file-name) (expand-file-name (concat default-directory ".emacs")))
      (byte-compile-file (buffer-file-name))))

(add-hook 'after-save-hook 'autocompile)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; COLOR THEME STUFF
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(require 'color-theme)
(color-theme-charcoal-black)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TABBAR SETUP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(dolist (func '(tabbar-mode tabbar-forward-tab tabbar-forward-group tabbar-backward-tab tabbar-backward-group))
  (autoload func "tabbar" "Tabs at the top of buffers and easy control-tab navigation"))
    
(defmacro defun-prefix-alt (name on-no-prefix on-prefix &optional do-always)
  `(defun ,name (arg)
      (interactive "P")
        ,do-always
        (if (equal nil arg)
          ,on-no-prefix
          ,on-prefix)))
    
(defun-prefix-alt shk-tabbar-next (tabbar-forward-tab) (tabbar-forward-group) (tabbar-mode 1))
(defun-prefix-alt shk-tabbar-prev (tabbar-backward-tab) (tabbar-backward-group) (tabbar-mode 1))

(global-set-key [(control tab)] 'shk-tabbar-next)
(global-set-key [(control shift tab)] 'shk-tabbar-prev)

Sunday, February 21, 2010

PureMVC Tutorial

I've been wanting to put together a tutorial showing how PureMVC can be used to partition an application into manageable segments, along with separating business logic and concerns from view-related actions. This tutorial has been long-coming, particularly since it's so easy to put together :)

As mentioned in my other blog, PureMVC is a framework which utilizes the MVC pattern for setting up an application. It allows you to fully segregate view-logic from business-logic, making it relatively easy to change underlying functionality without side-effecting unrelated processes.

Since the other write-up contains an explanation of PureMVC, let's delve into the mechanics.

When using PureMVC, it's best to start by dividing up your application into the separate pieces of logic. In this example, we will implement two very basic user-controls which allow for fetching/displaying content. As a result, the separation of concerns is relatively straightforward.

The view will be the main application and the controls therein. The controller will direct user-inspired requests to the correct business-logic functionary. The business-logic layer will provision the data requested by the controller. Note: in this example, neither view (user/business) will have any concept of how a request is made or provisioned. PureMVC will handle everything.

Let's first decide which request this application will need to use:

  1. Fetch Data: the application will need a way to fetch data. This could be from a remote location, or from a processing function. Frankly, it doesn't matter. Either way, we need a way to ask for data to be acquired; we don't need to send anything into this request: we will rely on the data-fetch process to let us know what data has been fetched.
  2. Data Fetched: the application will need a way to let any interested parties know data has been fetched. When this happens, we will need to bundle the acquired data into the notification, so the interested parties will be updated.
  3. Bootstrap: the application will need a way to "bootstrap" itself, setting up any requisite PureMVC components which will be needed for the previous notifications.
With these notifications in mind, we can start building our PureMVC app.

First, let's start building the Facade. Remember, the facade binds the entire notification system together in the PureMVC world. This component receives notifications from all mediators and proxies and forwards them on to each other. As such, it acts as a type of event bus. We'll call our class ApplicationFacade, and first setup all notification names we will need. We will also setup the "Singleton" pattern used to maintain the facade instance:

package com.goss.tutorial {

import org.puremvc.as3.interfaces.IFacade;
import org.puremvc.as3.patterns.facade.Facade;

public class ApplicationFacade extends Facade implements IFacade {
private static var _inst:Facade;

//CONSTANT NAMES FOR EACH NOTIFICATION
public static const STARTUP:String = "startup";
public static const FETCH_LIST_DATA:String = "fetchListData";
public static const DATA_FETCHED:String = "listDataFetched";

//Accessor for "singleton" instance -- this is only
//enforced by convention, not by the compiler
public static function getInstance():Facade {
if(!_inst) {
_inst = new ApplicationFacade();
}
return _inst;
}
}
}


The ApplicationFacade can now be used for sending notifications, but we have a way to go. Let's build the main view and from there work on the boostrap process.

Our View will be extremely simple for this example. In this view, we need a single drop-down list and a button to fetch its data. Though this would likely be a subset of a View accessed by a Mediator, it should suffice for our example. Our View will be the main MXML application file:


<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="vertical" horizontalAlign="center" verticalAlign="middle"
creationComplete="init()">
<mx:Script>
<![CDATA[
import org.puremvc.as3.patterns.facade.Facade;
import mx.controls.Alert;
import com.goss.tutorial.ApplicationFacade;

public static const FETCH_DATA:String = "fetchData";
private var facade:Facade = ApplicationFacade.getInstance();

private function init():void {
facade.sendNotification(ApplicationFacade.STARTUP, this);
}
private function onClick():void {
dispatchEvent(new Event(FETCH_DATA));
}
]]>
</mx:Script>
<mx:ComboBox id="addresses" />
<mx:Button label="Fetch Data" click="onClick()" />
</mx:Application>


Let's break this class down a little more. This class does contain a reference to the ApplicationFacade we created -- didn't we (at some point) say the View is completely unaware of the PureMVC framework underneath? Yes, and no. Since we are combining the View into the main class, this coupling is somewhat inevitable. When the application launches, we need to run the bootstrap process. This is done using the "STARTUP" notification. As a result, when the main.mxml class is launched and created, it will request the facade send out this notification.

If our View was less coupled to the main application, we wouldn't see this confusion. Let's move on.

We now have the primary View and a Facade established for using PureMVC. Notice the event dispatch for the View. When the user clicks the "Fetch Data" button, all the View does is dispatch a custom event. Anyone listening (i.e. a Mediator) will need to handle this event and translate it into the appropriate notification.

This said, let's build the Mediator, Controllers, and Proxies, and then come back to the bootstrapping process.

The ComboBoxMediator is a bit more complicated:

package com.goss.tutorial.view {
import com.goss.tutorial.ApplicationFacade;

import flash.events.Event;

import mx.collections.ArrayCollection;
import mx.controls.Alert;
import mx.controls.ComboBox;

import org.puremvc.as3.interfaces.IMediator;
import org.puremvc.as3.interfaces.INotification;
import org.puremvc.as3.patterns.mediator.Mediator;

public class ComboBoxMediator extends Mediator implements IMediator {
public static const NAME:String = "MainMediator";

public function ComboBoxMediator(viewComponent:Object) {
super(NAME, viewComponent);
app.addEventListener(main.FETCH_DATA, fetchData, false, 0, true);
}

//Inform PureMVC of the notifications this class is interested in
override public function listNotificationInterests():Array {
return [
ApplicationFacade.LIST_DATA_FETCHED
];
}

//When the notifications are received, this function will handle them
override public function handleNotification(notification:INotification):void {
switch(notification.getName()) {
case ApplicationFacade.LIST_DATA_FETCHED: {
app.addresses.dataProvider = notification.getBody() as ArrayCollection;
}
}
}

//Ask the application to fetch the list of addresses
private function fetchData(event:Event):void {
sendNotification(ApplicationFacade.FETCH_LIST_DATA);
}

//Convenience function for accessing the "view" component as its true type
private function get app():main {
return viewComponent as main;
}
}
}

Here's where we start to get a little more understanding of how PureMVC works. The Mediator has a list of Notifications it's interested in. This function is used by the Facade (after everything is setup) to decide which notifications will be sent to this Mediator. In this case, we are only concerned with receiving the LIST_DATA_FETCHED notification. There's a subtle distinction here: the Mediator will use the FETCH_LIST_DATA notification, but it will not receive it.

Within the constructor, notice our reference to the viewComponent. This is a class variable whose value is passed in when the class is instantiated. When the bootstrap process runs, it will hand the Mediator whatever View components it should handle. In this case, we will give it the main.mxml instance, from which it can access the button click events and the drop-down data provider.

Upon instantiation, the Mediator adds an Event Listener to the main instance, listening for the CLICK event. Remember how we programmed the View to dispatch this event? Here's where we translate it. When the event arrives, we will invoke the fetchData function, sending out the FETCH_LIST_DATA notification. This will be relayed by the Facade to whatever party will handle the fetch. No data is bundled into the notification, since it's a request-only operation.

Our Mediator finished, let's focus on the Command which will relay the fetch request to the appropriate business-layer object.


package com.goss.tutorial.controller {
import com.goss.tutorial.model.ComboBoxProxy;

import org.puremvc.as3.interfaces.ICommand;
import org.puremvc.as3.interfaces.INotification;
import org.puremvc.as3.patterns.command.SimpleCommand;

public class ComboBoxController extends SimpleCommand implements ICommand {

//Request the proxy modify the state of the dropdown
override public function execute(notification:INotification):void {
(facade.retrieveProxy(ComboBoxProxy.NAME) as ComboBoxProxy).fetchDataProvider();
}
}
}

Controllers (a.k.a. Commands) are pretty easy to implement. Each controller has a single point-of-entry for the Facade to interact with it: execute(). When the Facade sends a notification to a Command, it does so by invoking its execute function and passing in the Notification object. This function may handle more than one notification type (specified by their name property), and would need to determine how to handle each one. In this example, the ComboBoxProxy only needs a single function call: it requests a reference to a Proxy object from the facade and invokes the appropriate function to fetch the data.

It's important to note how the Proxy is retrieved from the Facade. When each Mediator and Proxy is registered with the Facade, it does so using a unique ID -- this is its NAME. Though there are a number of ways we can associate the Mediator/Proxy with its NAME, in this example we are simply maintaining this ID within the class itself. When the Mediator and Proxy is instantiated, it will send this ID into the parent class so the Facade can associate the class with its ID.

Therefore, when the execute() function is called, we ask for the required Proxy from the facade and ask it to perform whatever business operations it needs to do to retrieve the data.

Note: there are examples where business logic is shared between Command and Proxy objects; I'm not going to discuss the pros/cons of allowing business logic to be maintained in these layers. I prefer to think of all business logic as being maintained in the Proxy, since it keeps all data logic at the lowest layer possible.

Let's look at the Proxy next:

package com.goss.tutorial.model {
import com.goss.tutorial.ApplicationFacade;

import mx.collections.ArrayCollection;

import org.puremvc.as3.interfaces.IProxy;
import org.puremvc.as3.patterns.proxy.Proxy;

public class ComboBoxProxy extends Proxy implements IProxy {
public static const NAME:String = "ComboBoxProxy";

public function ComboBoxProxy():void {
super(NAME);
}

//Create a new ArrayCollection of addresses and push them to the rest of the
//application
public function fetchDataProvider():void {
//Note: this function could fetch data in ANY way we need; it could fetch it from
//a server, or build it dynamically...in this example, we are only interested in
//creating a simple collection, so it's hard-coded.
this.data = new ArrayCollection(["foo@foo.com", "bar@foo.com", "baz@foo.com",
"foo@bar.com", "bar@bar.com", "baz@foo.com"]);
sendNotification(ApplicationFacade.LIST_DATA_FETCHED, data as ArrayCollection);
}
}
}

The Proxy's constructor is very similar to the Mediator's; it sends the unique ID (it's NAME) to the parent, though it doesn't have an associated View component. The Proxy does, however, have a data object which can be used to maintain data the class is interested in. This may be useful for some Proxies where data is maintained, modified, and returned upon request. In our example, we will set the data object when we are asked for data.

The Proxy has a single function whose purpose should be fairly obvious. The Command uses this function to request data be sent back to its appropriate destination. In fetchDataProvider(), we build a new ArrayCollection and bundle it into a notification which is sent via the Facade.

At this point, we have a View with a Mediator, a Facade, a Command, and a Proxy. In PureMVC terms, we have a Model-View-Controller setup along with the Facade to facilitate communication within them. So how do we set up all of these components? Let's move on to the final process: bootstrapping.

Recall the main.mxml's init() function. When this function runs (after the app has been fully created), it asks the Facade to send a notification, STARTUP. This STARTUP notification will be used to kick off the bootstrapping process; to do this, we need to add one more function to our ApplicationFacade:
//When Facade is started, register the STARTUP notification
//so we can bootstrap the application
override protected function initializeController():void {
super.initializeController();
registerCommand(ApplicationFacade.STARTUP, StartupCommand);
}

When the Facade is being instantiated, it will invoke the function initializeController() (there's also one for the Mediator and Proxy objects). When this happens, we want the Facade to associate the STARTUP notification with a new Command. We will call this class StartupCommand, and within it we will dictate the bootstrapping process.

Here's our new Controller class:

package com.goss.tutorial.controller {
import com.goss.tutorial.ApplicationFacade;
import com.goss.tutorial.model.ComboBoxProxy;
import com.goss.tutorial.view.ComboBoxMediator;

import org.puremvc.as3.interfaces.ICommand;
import org.puremvc.as3.interfaces.INotification;
import org.puremvc.as3.patterns.command.SimpleCommand;

public class StartupCommand extends SimpleCommand implements ICommand {

/**
* This function bootstraps the PureMVC framework. This function is
* executed when the ApplicationFacade dispatches the SETUP notification.
* This function registers Mediators w/their respective view components,
* Commands and their respective notifications, and Proxies.
**/
override public function execute(notification:INotification):void {
var app:Object = notification.getBody();

facade.registerMediator(new ComboBoxMediator(app));
facade.registerCommand(ApplicationFacade.FETCH_LIST_DATA, ComboBoxController);
facade.registerProxy(new ComboBoxProxy());
}
}
}

This is the final class in our example, and it performs all binding functionality for the PureMVC framework. When the STARTUP notification is sent, it will be received by this class, which will then register all new instances of the Mediators and Proxies, as well as associating each notification with the appropriate Command.

When we dispatched the STARTUP notification from main.mxml, we also bundled a reference to the main application within the notification. Notice how the StartupCommand extracts this from the notification and passes it on to the Mediator class. This reference will be maintained as the viewComponent in the Mediator.

(Since I don't have an online storage for Flash files, I can't provide a running example of this. You'll have to copy & paste the code and build it yourself -- sorry about that.)

We've built a fair number of classes for such a short example (1 Facade + 2 Commands + 1 Proxy + 1 Mediator + 1 View = 6 classes, where 1 would suffice), but remember this is just an example. PureMVC provides a streamlined way of decoupling classes from each other, while facilitating communication between the view and business logic. Though we've introduced several new classes, their purpose and functionality is relatively simple and straightforward. Moreover, if we are ever asked to swap out views or business logic, we will be able to do so without severely impacting the rest of the application.