Showing posts with label YUI. Show all posts
Showing posts with label YUI. Show all posts

Saturday, September 14, 2013

little to do

I finally have an initial version of a little to do application running using the "tree control flow" stuff I described earlier.

LittleToDo is a simple app, but it took me a little while to wire it all up, and I think it has a couple interesting features. First - littleToDo implements nested to-do's, so each to-do item can itself have a list of child to-do's, and grand children, etc. The UI currently only allows four or five generations, but increasing that limit just requires changing a constant in the code.

The other interesting features are under the hood. Most of the code is implemented as typescript YUI modules. I had to tweak the YUI typescript definition file I'm using to make the typescript YUI module trick work with typescript's 0.9.1.1 release (which tightens up some syntax rules that were more relaxed in typescript 0.8), but I eventually wound up with code like this:

...

import importY = require("../../libts/yui");
importY; // workaround for typescript bug: https://typescript.codeplex.com/workitem/1531
import Y = importY.Y;
Y = exports;

import littleAsset = require("littleAsset");
littleAsset;
import ax = littleAsset.littleware.asset;


/**
 * @module littleware-asset-base
 * @namespace littleware.asset
 */
export module littleware.asset.manager {

...

    /**
     * API tool for interacting with the asset repo.
     * @class AssetManager
     */
    export interface AssetManager {
        /**
         * @method addListener
         * @param listener {function}
         * @return YUI subscription with "detach" method to unsubscribe
         */
        addListener(listener: (ev: RefEvent) => void ): Y.EventHandle;

        /**
         * @method saveAsset
         * @return {Y.Promise[AssetRef]}
         */
        saveAsset(value: ax.Asset, updateComment: string): Y.Promise<AssetRef>;
        /**
         * @method deleteAsset
         * @return {Y.Promise[void]}
         */
        deleteAsset(id: string, deleteComment: string): Y.Promise<void>;

        /**
         * Return a reference to the asset with the given id if any - otherwise null.
         * @method loadAsset
         * @return {Y.Promise[AssetRef]}
         */
        loadAsset(id: string): Y.Promise<AssetRef>;

        /**
         * Load the child of the given parent with the given name -
         * every child has a unique name within its set of siblings.
         * Usually implemented as a shortcut for loadAsset( listChildren( parentId )[name] ).
         * @method loadChild
         * @param parentId {string}
         * @param name {string}
         * @return {Y.Promise{AssetRef}}
         */
        loadChild(parentId: string, name: string): Y.Promise<AssetRef>;

        /**
         * Load the asset at the given path if any - same as loadSubpath( null, path )
         * @method loadPath
         * @param path {string}
         * @return {Y.Promise{AssetRef}}
         */
        loadPath(path: string): Y.Promise<AssetRef>;

        /**
         * Load the asset at the given path below the given root
         * @method loadSubPath
         * @param rootId {string} id of the asset to treat as the root, or null to use forest root
         * @param path {string}
         * @return {Y.Promise{AssetRef}}
         */
        loadSubpath(rootId: string, path: string): Y.Promise<AssetRef>;

        /**
         * Like loadSubpath, but allows specification of a builder function 
         * for each segment of the path to create a missing asset if it doesn't exist -
         * buildBranch sets the builder's fromId and name properties.
         * @method buildBranch
         * @param rootId {string} id of the asset to treat as the root, or null to use forest root
         * @param branch {{name:string,builder:(parent:Asset)=>AssetBuilder}[]}
         * @return {Y.Promise{AssetRef[]}}
         */
        buildBranch(rootId: string, branch: { name: string; builder: (parent: ax.Asset) => ax.AssetBuilder; }[]): Y.Promise<AssetRef[]>;


        /**
         * List the children if any under the given parent node
         * @method listChildren
         * @param parentId {string}
         * @return {Y.Promise{NameIdListRef}}
         */
        listChildren(parentId: string): Y.Promise<NameIdListRef>;

        /**
         * List the root (littleware.HOME-TYPE) nodes - shortcut for listChildren(null)
         * @method listRoots
         * @return {Y.Promise{NameIdListRef}}
         */
        listRoots(): Y.Promise<NameIdListRef>;
    }

...

The current implementation of the above AssetManager interface (which underlies the data-management for the app) just saves assets (nodes in a data tree) to the browser's local storage, but the API returns promises, so it will be easy in the future to swap in an implementation that saves data via AJAX to a remote service. The AssetManager also closely resembles littleware's asset manager and asset search API's (that javadoc is out of date, but that's about right), so I'm hoping to implement a littleware backend for the next release of "little to do" with littleId based authentication. We'll see how it goes ...

Thursday, September 05, 2013

promises, promises

YUI recently added a cool Promises module that I've been using the last couple weeks to implement client-side API's for a webapp. I like the way the code has come together, and I have a couple observations to record.

First, code that uses Promises is viral - it's natural for code that uses a promise-based API to itself use promises. Promises are a kind of monad like Haskell's IO() construct and Scala's Option class - which are also viral in my experience. For example, one of the first promise-based methods I wrote retrieved and compiled a handlebars template from the server:

        /**
         * Little helper loads and compiles a handlebar template via an AJAX call
         * 
         * @method loadHandlebar
         * @param url {string}
         * @return {Promise} Y.Promise delivers compiled template function or error info
         * @static
         */
        function loadHandlebar( url ) {            
            var promise = new Y.Promise( function(resolve,reject) {
                var ioconf = {
                  method:"GET",
                  on: {
                    success:function( id, resp, args ){
                        resolve( Y.Handlebars.compile( resp.responseText ) );
                    },
                    failure:function( id, resp, args ) {
                        log.log( "Failed to load " + url + ": " + resp.status + ", " + resp.statusText );
                        reject( resp );
                    }
                  },
                  timeout:60000
                };
                Y.io( url, ioconf );
            });
            return promise;
        }

That worked great, and the next thing I wanted was to dynamically load a template into a view with something like
littleUtil.loadHandlebar( url ).then( function(template){ view.template = template; } )
, but then the view couldn't use the template at render time until the promise was fulfilled. Rather than do something like
render: function() { this.templatePromise.then( function(template) { ... do render } ) ... }
, I setup a factory method for allocating views that itself returns a promise, and let the factory's client decide how to resolve the promise - which worked well in the little application I've been building that runs several asynchronous data-setup operations at start-up anyway:

    /**
     * Factory for PageView instances - maries YUI class inheritance with Typescript type system
     * @class PageViewFactory
     */
    export var PageViewFactory = {
        /**
         * Returns a Promise - the view loads various configuration 
         * information (templates, whatever) at the template, and 
         * fulfills the promise when the data is ready.
         * @method newView
         * @static
         * @return {Promise{PageView}}
         */
        newView: function (): Y.Promise {
            return templatePromise.then((template) => {
                var view = new SimplePageView();
                view.template = template;
                return view;
            });
        }
    };

This code is written in typescript - which extends javascript with support for static types. I'm only just in the process of migrating to typescript 0.9.1 which includes generic types. Hopefully after the upgrade I'll be able to modify the newView signature to something like:
newView: function(): Y.Promise<PageView> { ... }
.

Finally, it's interesting to compare yui's Promises implementation to the akka Futures implementation in scala. In javascript's single-threaded runtime promises usually coordinate callback execution between operations involving asynchronous IO. In scala's multithreaded runtime futures (promises by another name) are also used to simplify the coordination of concurrently running computations.

Tuesday, July 30, 2013

easy slideshow with YUI transitions

Using CSS transitions to animate the opacity of images is a great way to implement a web slideshow, but implementing an animation-based fallback for old browsers is a pain. Fortunately - yui includes a transition module that takes care of the fallback magic; which made it easy for me to code up an Android and iOS-friendly HTML replacement for a Flash .swf banner in a project I'm helping with.

The banner's markup leverages the absolute position in a relative position container trick:

div.banner {
    position:relative;
    height:230px;
}

div.banner img {
    position: absolute;
    opacity: 0;
}

div.banner img.logo { /* overlay logo on banner */
    opacity:1;
    bottom:25px;
    right:0;
}

<div id="banner" data-anim-period-secs="5" class="yui3-u-1 banner">
    <img src="/myrwa/resources/img/banner/lichen.jpg"/>
    <img src="/myrwa/resources/img/banner/Herringrun.jpg"/>
    <img src="/myrwa/resources/img/banner/TuftsSailingTeam.jpg"/>
    <img src="/myrwa/resources/img/banner/canoe.jpg"/>
    
    <img id="logo" class="logo" src="/myrwa/resources/img/myRWA_logo_2010.gif" />
</div>

The banner's javascript module implements a simple yui view that runs a setinterval loop (Y.later wraps setinterval) that applies an opacity transition to make the current image in the banner's slideshow opaque, and the last image transparent.

/*
 * Copyright 2013 http://mysticriver.org
 *
 * The contents of this file are freely available subject to the 
 * terms of the Apache 2.0 open source license.
 */


/**
 * see http://yuiblog.com/blog/2007/06/12/module-pattern/
 * YUI doc comments: http://developer.yahoo.com/yui/yuidoc/
 * YUI extension mechanism: http://developer.yahoo.com/yui/3/yui/#yuiadd
 *
 * @module myrwa-banner
 * @namespace myrwa
 */
YUI.add( 'myrwa-banner', function(Y) {
    Y.namespace('myrwa');

    function log( msg, level ) {
        level = level || 2;
        Y.log( msg, level, "myrwa/banner.js" );
    }
    
    /**
     * View abstraction for the header banner which animates with
     * transitions between a set of gallery images.  
     * Initialize with container
     * selector for markup initialized with banner images
     * ready for progressive enhancement.
     * 
     * @class BannerView
     */
    var BannerView = Y.Base.create( 'bannerView', Y.View, [], 
        {
            initializer:function(config) {
            },
                    
            render:function() {
                // do not setup the animation than once
                if ( this.rendered ) return;
                var container = this.get( "container" );
                var logoNode = container.one( "img.logo" );
                var imageNodes = container.all( "img" 
                         ).filter( function(img) { return ! Y.Node(img).hasClass( "logo" ); } );
                
                Y.assert( "Found image nodes", imageNodes.size() > 0 );
                
                // initialize the banner to show the first image
                imageNodes.each( function(n) { n.setStyle( "opacity", 0 ); } );
                imageNodes.item(0).setStyle( "opacity", 1 );
                
                if ( imageNodes.size() == 1 ) return;  // no need to animate
                
                var animPeriod = this.get( "animPeriodSecs" );
                if ( animPeriod < 2 ) {
                    // check for an attribute on the container if period not set in js code
                    var tmp = parseInt( container.getAttribute( "data-anim-period-secs" ) );
                    if ( tmp ) { animPeriod = tmp; }
                }
                if ( animPeriod < 2 ) animPeriod = 2;
                
                var currentImageIndex = 0;
                log( "Launching banner animation ..." );
                //
                // Every animPeriod seconds ease the current node out, and the new node in
                //
                Y.later( animPeriod * 1000, this, 
                    function(){
                        var nextImageIndex = (currentImageIndex + 1) % imageNodes.size();
                        var inNode = imageNodes.item( nextImageIndex );
                        var outNode = imageNodes.item( currentImageIndex );
                        //log( "banner transition from " + currentImageIndex + " to " + nextImageIndex );

                        inNode.setStyle( "opacity", 0 );
                        outNode.setStyle( "opacity", 1 );
                        inNode.show();
                        outNode.show();

                        outNode.transition( 
                                {
                                 easing: 'ease-out',
                                 duration: 0.75, // seconds
                                 opacity: 0
                                 }, 
                             function() { outNode.hide(); } 
                         );
                         inNode.transition(
                                 {
                                  easing: 'ease-in',
                                  duration: 0.75,
                                  opacity: 1
                                 }
                             );
                         currentImageIndex = nextImageIndex;
                    }, 
                    [], true
                );

                this.rendered = true;
            }
        }, 
        {
            ATTRS: {
                animPeriodSecs: {
                    value:0
                }
            }
        } 
    );

    //---------------------------------------

    Y.myrwa.banner = {
        BannerView:BannerView
    };
}, '0.1.1' /* module version */, {
    requires: [ 'node', 'test', 'transition', 'view']
});

A page bootstraps the banner animation with code like this:

YUI().use('myrwa-banner', 'test', function(Y){
 
    Y.log( "Hello, World!", 2, "myrwa/main.js" );
    var banner = new Y.myrwa.banner.BannerView( 
            {
                container:"#banner"
            }
        );
    banner.render();
});

Anyway - I was pretty happy with how that all came together. This little banner slideshow lacks the nice controls in bootstrap's slideshow, but it was good enough for what I needed, and I avoided pulling in bootstrap's dependencies (jquery, whatever). The code is available on github, and a page I used for testing is also online for now.

Thursday, July 11, 2013

testing YUI apps with phantomjs

It turns out that wiring up phantomjs to process a web page that runs a test suite based on yui's test module is pretty easy to do. I wish I did this sooner. Here's what I got working.

First, I'm already in the habit of setting up pages like this that run a javascript module through some tests. I just use yui's test framework, since I use yui for everything else, and yui test works basically the same way as junit - which I use when coding java. I wind up with the following code (on github here):

    littleware.littleYUI.bootstrap().use( 
 'littleware-littleUtil', 'test',
 function (Y) {
     // The modules are loaded and ready to use.
     // Your code goes here!
     var util = Y.littleware.littleUtil;
     var log = new util.Logger( "littleUtilTestSuite.html" );
     var suite = util.buildTestSuite();
     
     Y.Test.Runner.add( suite );
     
     if ( typeof( window.callPhantom ) != 'undefined' ) {
  // phantomjs environment!
  console.log( "Phantomjs detected!" );
  Y.Test.Runner.subscribe( Y.Test.Runner.COMPLETE_EVENT, window.callPhantom );
     }

     Y.Test.Runner.run();          
    });

The thing to notice is the window.callPhantom feature detection. Phantomjs integrates the V8 javascript engine with Webkit's DOM magic in a cool command-line javascript console. There's probably a better way to say that, but the important thing for me is that phantomjs offers a great way to run and evaluate the results of test web pages from the command line.

The phantomjs web site has a great list of references on its "headless testing" page, and I think my setup is pretty similar to what others do. My setup takes advantage of one trick that I wanted to mention - listening for YUI's test-run complete event with phantomjs' webpage.onCallback function to pass the web-page test results through to the phantomjs script (below) that runs through a series of test web pages. Ugh - that may not make much sense, but hopefully the "phantomTestRunner.js" script below (and in github) helps clarify what I mean.

/**
 * Little phantomjs script that opens a list of urls given
 * in the first argument as a comma-separated list.
 * Intended for running YUI test suites that callback to
 * phantom at test completion via:
 *    
 *    <pre>
 *       if ( typeof( window.callPhantom ) != 'undefined' ) {
 *           // phantomjs environment!
 *           console.log( "Phantomjs detected!" );
 *           Y.Test.Runner.subscribe( Y.Test.Runner.COMPLETE_EVENT, window.callPhantom );
 *       }
 *
 *    </pre>
 */

var system = require( 'system' );

if ( (system.args.length < 2) || system.args[1].match( /^-+/ ) ) {
    // use comma-separated list - easier to integrate with ant that way
    console.log( "script takes exactly one argument: a comma-separated list of test urls" );
    phantom.exit(1);
}

var urlList = system.args[1].split( /,+/ );
/*
for ( var i = 1; i < system.args.length; ++i ) {
    urlList.push( system.args[i] );
}
*/

var resultList = [];
var currentTest = 0;

var page = require( 'webpage' ).create();
page.onConsoleMessage = function(msg) { console.log( msg ); };

function runTest( url ) {
  page.open( url,
     function( status ) { 
         console.log( url + " status: " + status ); 
         if ( status != "success" ) {
             phantom.exit(1);
         } 
         page.evaluate( function() { console.log( "page location: " + window.location.href ); } );
     }
 );    
}

page.onCallback = function( testResult ) {
    resultList.push( testResult );
    currentTest += 1;
    if ( currentTest < urlList.length ) {
        runTest( urlList[currentTest] );
    } else {
        console.log( "-------------------------------------------------------------" );
        console.log( "-------------------------------------------------------------" );
        console.log( resultList.length + " Tests complete: " );
        console.log( JSON.stringify( resultList ) );
        phantom.exit( 0 );
    }
};


runTest( urlList[0] );

Anyway, long story short, running something like:
phantomjs phantomTestRunner.js http://apps.frickjack.com/littleware_apps/testsuite/littleUtilTestSuite.html
prints out the web-console messages from the page's test suite (the script can run multiple pages), and collects all the results at the end (I need to do more work with that). Pretty cool!

Wednesday, July 10, 2013

tree control-flow apps on a responsive grid

I wired up a proof of concept responsive browser UI that implements an application's views as a series of panels. A single panel holds the UI focus at any given time, and each panel is compact enough to render on a smart phone, so on a phone sized device the UI renders a single panel at a time, but on a larger device the UI may render 2, 3, or 4 adjoining panels depending on the device size. For example, the demo, application just navigates through a tree of panels (a decision-tree control flow is common in apps). On a typical laptop the UI renders up to 4 adjoining panels where the right-most panel is the "focus" of the app, and a panel's parent is to its left. If we make the browser window smaller, then the UI eventually removes the left-most panel from the screen, and continues removing the left panel as the window shrinks until only the focus-panel remains.

I started on the demo while thinking about how to design an application that from the beginning behaves in a reasonable way on a phone, tablet, or PC. I like the idea of using a responsive design to implement one web app that works well across devices. It's too much work to build a bunch of separate "apps" for iOS, Android, etc. with custom UI designs for phone and tablet. I recently started playing around with purecss (a CSS framework forked from yui) that includes an implementation of a "responsive grid". Responsive grids like those in Twitter bootstrap and purecss leverage CSS3 media queries to implement a web page layout that behaves differently on phone, tablet, and PC sized screens.

I implemented the demo with typescript and YUI - the code is on github. The UI manager follows patterns like those in YUI's app framework. The application registers views (panels) with the manager, and associates each panel with a route based on the panel's parent and a basename filter. The view manager then takes care of deciding which panels to render where when the application triggers a change in route. The view manager also registers a click-handler, so a.little-route links trigger a route change, but the application can also manage the YUI router directly.

Eventually we wind up with application code like this:

    // home page info
    var versionInfo = Y.Node.create("<div id='app-info'><span class='app-title'>LittleEvents</span><br>Version 0.0 2013/06/21</p></div>");
    var homePage = new Y.View();
    homePage.get("container").append(versionInfo);

    // router
    var router = new Y.Router(
 { root: "/littleware_apps/blog/gridDemo.html" }
 );

    // inject homepage and router into view manager
    var app = appModule.ViewManager.getFactory().create("app", "div#app", homePage, router);

    //
    // passed to registerPanel (below) - notifies panel of new path on view change.
    // could also just add a listener on the router ...
    //
    var viewListener = function (panelStatus) {
 if (panelStatus.state.name == "VISIBLE") {
     var oldPath = panelStatus.panel.view.get("pathParts").join("/");
     if (oldPath != panelStatus.path) {
  //log.log("Setting new path: " + oldPath + " != " + panelStatus.path);
  panelStatus.panel.view.set("pathParts", panelStatus.path.split("/"));
  app.markPanelDirty(panelStatus.panel.id);
     }
 }
    };

    // register panels according to position in control-flow tree
    app.registerRootPanel(panel1, panel1.id, viewListener);
    // panel2 is a "child" of panel1
    app.registerPanel(panel2, panel1.id, function () { return true; }, viewListener );
    app.registerPanel(panel3, panel2.id, function () { return true; }, viewListener );
    app.show();

..., and a library interface like this:

    export module ViewManager {
      ...
        /**
         * Little helper manages the display of panels based
         * on the routes triggered in router and size of display
         * (phone, tablet, whatever).
         * Assumes tree-based panel app.
         *
         * @class Manager
         */
        export interface Manager {
            name: string;

            /**
             * Root div under which to manage the panel UI
             */
            container: Y.Node;

            /**
             * Info panel embedded in home page at path "/" - splash screen,
             * version info, whatever placed above the "route index"
             * @property homePage {Y.View}
             */
            homePage: Y.View;
            router: Y.Router;

            /**
             * List of routes to sort and display in the "index" on the home page
             * along with the "root" panel paths.
             */
            routeIndex: string[];


            /**
             * Child panel of homePage - "config"
             * is reserved for internally managed configuration panels.
             */
            registerRootPanel(
                panel: LittlePanel,
                baseName: string,
                listener: (PanelStatus) => void
                );

            /**
             * Register panels along with id of its parent, and a baseFilter
             * that either accepts or rejects a route basename.
             * For example, given some route /path/to/parent/bla/foo/frick,
             * then for each element of the path [path,to,parent,bla,foo,frick],
             * test that element against the children of a parent panel
             * to determine which panel to associate with that route.
             *
             * @method registerPanel
             */
            registerPanel(
                    panel: LittlePanel,
                    parentId: string,
                    routeFilter: (string) => bool,
                    listener: (PanelStatus) => void
                );

            /**
             * By default the manager does not re-render a panel when it
             * becomes "visible" unless the panel is "dirty".
             * If the panel is already visible, then re-render panel once
             * call stack clears: Y.later( 0, () => render() ).
             *
             * @method markPanelDirty
             */
            markPanelDirty(panelId: string);

            /**
             * Triggers the manager to render its initial view (depends on the active route),
             * and begin responding to routing and dirty-panel notifications in the "Active" 
             * ManagerState.  NOOP if already active.
             *
             * @method show
             */
            show();

        }


        export interface Factory {
            /**
             * Create a new view manager that manipulates DOM in the given selector
             *
             * @param name alphanumeric to associate with this manager - used as a key
             *               in persistence store
             * @param selector CSS selector for div under which to build view
             */
            create(name: string, selector: string, homePage: Y.View, router: Y.Router): Manager;
            //create( name:string, selector: string): Manager;

            /**
             * Load manager state from persistent storage
             */
            load( name:string ): Manager;
        }

       ...
   }

Anyway - I'm pretty happy with how things fit together in the demo, but I won't really know how well this works until I use it with a couple apps. We'll see how it goes.

Monday, May 27, 2013

ant rules for typescript compile

I extended littleware's ant template with rules to compile typescript (*.ts) files in web projects using this ant-macro posted on stackoverflow. The typescript and yuidoc rules are listed further below.

Littleware's build system just extends netbeans' ant templates with IVY support. Netbeans' ant scripts are designed to support extension hooks that run before and after the different build stages (initialization, compile, package, ...). This is from the comment at the top of a new netbeans project's build.xml file:


    There exist several targets which are by default empty and which can be 
    used for execution of your tasks. These targets are usually executed 
    before and after some main targets. They are: 

      -pre-init:                 called before initialization of project properties 
      -post-init:                called after initialization of project properties 
      -pre-compile:              called before javac compilation 
      -post-compile:             called after javac compilation 
      -pre-compile-single:       called before javac compilation of single file
      -post-compile-single:      called after javac compilation of single file
      -pre-compile-test:         called before javac compilation of JUnit tests
      -post-compile-test:        called after javac compilation of JUnit tests
      -pre-compile-test-single:  called before javac compilation of single JUnit test
      -post-compile-test-single: called after javac compilation of single JUunit test
      -pre-dist:                 called before archive building 
      -post-dist:                called after archive building 
      -post-clean:               called after cleaning build products 
      -pre-run-deploy:           called before deploying
      -post-run-deploy:          called after deploying

...

Anyway - I'm glad for netbeans' extensible ant templates. Littleware's build system could benefit from another rework to better support scala and IVY, but it's good enough for now.

Finally - I also wanted to mention that Ian Obermiller posted on github a typescript brush for Alex Gorbatchev's syntax highlighter. It seems to work great ...

Here are those ant rules I mentioned earlier ...

<target name="config-check">
     <!--
    <available file="${ivy.jar.dir}/littlesettings.xml" property="settings.exists"/>
    -->
    <available file="${ivy.jar.file}" property="ivyjar.exists"/>
    <condition property="skip.download">
        <and>
            <isset property="ivyjar.exists" />
        </and>
    </condition>
    <available file="ivy/test" property="ivy.resolve.test.exists" />
    <available file="ivy/compile" property="ivy.resolve.compile.exists" />

    ...
    
    <condition property="ivy.resolve.exists">
        <and>
            <isset property="ivy.resolve.test.exists" />
            <isset property="ivy.resolve.compile.exists" />
        </and>
    </condition>
    
    <condition property="typescript.ready">
        <and>
            <isset property="build.web.dir" />
            <isset property="typescript.compiler.path" />
            <available file="${typescript.compiler.path}" />
        </and>
    </condition>    
    
    <condition property="ydoc.ready">
        <and>
            <isset property="build.web.dir" />
            <isset property="ydoc.path" />
            <available file="${ydoc.path}" />
        </and>
    </condition>    
    
</target>

<!-- web project stuff -->

<property name="typescript.compiler.path" value="${user.home}/AppData/Roaming/npm/tsc.cmd" />
<property name="ydoc.path" value="${user.home}/Documents/Code/yuidoc/bin/ydoc.bat" />


<target name="ydoc" depends="config-check" if="ydoc.ready" 
    description="run ydoc on build/ js"
    >
  <exec executable="${ydoc.path}">
    <arg value="${build.web.dir}/resources/js/littleware"/>
 </exec>    
</target>

    
<!--
    Recursively read a source directory for TypeScript files, generate a compile list in the
    format needed by the TypeScript compiler adding every parameters it take.
    Thanks Tekool.net:
       http://stackoverflow.com/questions/12799237/how-to-watch-and-compile-all-typescript-sources
-->
<macrodef name="TypeScriptCompileDir">

    <!-- required attribute -->
    <attribute name="src" />

    <!-- optional attributes -->
    <attribute name="out" default="" />
    <attribute name="module" default="" />
    <attribute name="comments" default="" />
    <attribute name="declarations" default="" />
    <attribute name="nolib" default="" />
    <attribute name="target" default="" />

    <sequential>

        <!-- local properties -->
        <local name="out.arg"/>
        <local name="module.arg"/>
        <local name="comments.arg"/>
        <local name="declarations.arg"/>
        <local name="nolib.arg"/>
        <local name="target.arg"/>
        <local name="typescript.file.list"/>
        <local name="tsc.compile.file"/>

        <property name="tsc.compile.file" value="@{src}compile.list" />

        <!-- Optional arguments are not written to compile file when attributes not set -->
        <condition property="out.arg" value="" else='--out "@{out}"'>
            <equals arg1="@{out}" arg2="" />
        </condition>

        <condition property="module.arg" value="" else="--module @{module}">
            <equals arg1="@{module}" arg2="" />
        </condition>

        <condition property="comments.arg" value="" else="--comments">
            <equals arg1="@{comments}" arg2="" />
        </condition>

        <condition property="declarations.arg" value="" else="--declarations">
            <equals arg1="@{declarations}" arg2="" />
        </condition>

        <condition property="nolib.arg" value="" else="--nolib">
            <equals arg1="@{nolib}" arg2="" />
        </condition>

        <!-- Could have been defaulted to ES3 but let the compiler uses its own default is quite better -->
        <condition property="target.arg" value="" else="--target @{target}">
            <equals arg1="@{target}" arg2="" />
        </condition>

        <!-- Recursively read TypeScript source directory and generate a compile list -->
        <pathconvert property="typescript.file.list" dirsep="\" pathsep="${line.separator}">

            <fileset dir="@{src}">
                <include name="**/*.ts" />
                <exclude name="**/*.d.ts" />
            </fileset>

            <!-- In case regexp doesn't work on your computer, comment <mapper /> and uncomment <regexpmapper /> -->
            <mapper type="regexp" from="^(.*)$" to='"\1"' />
            <!--regexpmapper from="^(.*)$" to='"\1"' /-->

        </pathconvert>


        <!-- Write to the file -->
        <echo message="Writing tsc command line arguments to : ${tsc.compile.file}" />
        <echo file="${tsc.compile.file}" message="${typescript.file.list}${line.separator}${out.arg}${line.separator}${module.arg}${line.separator}${comments.arg}${line.separator}${declarations.arg}${line.separator}${nolib.arg}${line.separator}${target.arg}${line.separator}--sourcemap" append="false" />

        <!-- Compile using the generated compile file --> 
        <echo message="Calling ${typescript.compiler.path} with ${tsc.compile.file}" />
        <exec dir="." executable="${typescript.compiler.path}">
            <arg value="@${tsc.compile.file}"/>
        </exec>

        <!-- Finally delete the compile file 
        <echo message="${tsc.compile.file} deleted" />
        <delete file="${tsc.compile.file}" />
        -->

    </sequential>

</macrodef>


<target name="typescript" depends="config-check" if="typescript.ready" 
    description="compile typescript files build/*.ts"
    >
      <TypeScriptCompileDir
          src="${build.web.dir}/resources/js/"
          module="amd"
      />      
      
    <!-- out="${build.web.dir}/resources/js" -->
</target>


<!-- post-compile rule kicks in if build.web.dir netbeans property is defined -->
<target name="-post-compile" depends="ydoc,typescript" if="build.web.dir">
      <copy todir="${build.web.dir}/WEB-INF/lib">
        <fileset dir="ivy/test"/>
      </copy>
</target>

Sunday, May 26, 2013

YUI modules with Typescript

I finally made time this week to play with typescript - a statically typed javascript extension developed at Microsoft. I like Typescript a lot. I prefer working with statically typed code for most applications, and Typescript also improves on javascript's syntax. I also think Typescript's approach to extending javascript while maintaining direct interoperability with existing javascript code (typescript can call javascript code directly, and vice versa) is better than the "rebuild the universe" strategy taken by dart and gwt.

I've enjoyed using yui in the few small javascript modules I've written (tracked in google code), so one of my first typescript tasks was to publish typescript code as a yui module, and to use other yui modules from typescript. I'm new to typescript, so the following explanation may be wrong, but here it goes.

Typescript supports modules via module, export, and import keywords. A typescript module compiles to either an AMD or CommonJS style javascript module depending on a compiler flag (the typescript specification covers all this). So a typescript file like this:


/// <reference path="yui" />
/// <reference path="toyA" />


export module littleware.toy.b {
    
    export import modA = module( "toyA" );

    export class Greeter {
        delegate:modA.littleware.toy.a.Greeter;
    
        constructor(message: string) {
            this.delegate = new modA.littleware.toy.a.Greeter( message );
        }
        greet() {
            return this.delegate.greet();
        }
    }
    
    export function runGreeter( greeter:Greeter ) {
        var button = document.createElement('button');
        button.innerText = "Say Hello";
        button.onclick = function() {
            alert(greeter.greet());
        }
        
        document.body.appendChild(button);
    }

}

Compiles as commonjs like this:


(function (littleware) {
    (function (toy) {
        (function (b) {
            var modA = require("./toyA")
            var Greeter = (function () {
                function Greeter(message) {
                    this.delegate = new modA.littleware.toy.a.Greeter(message);
                }
                Greeter.prototype.greet = function () {
                    return this.delegate.greet();
                };
                return Greeter;
            })();
            b.Greeter = Greeter;            
            function runGreeter(greeter) {
                var button = document.createElement('button');
                button.innerText = "Say Hello";
                button.onclick = function () {
                    alert(greeter.greet());
                };
                document.body.appendChild(button);
            }
            b.runGreeter = runGreeter;
        })(toy.b || (toy.b = {}));
        var b = toy.b;
    })(littleware.toy || (littleware.toy = {}));
    var toy = littleware.toy;
})(exports.littleware || (exports.littleware = {}));
var littleware = exports.littleware;

This is the amd output for the same code:

define(["require", "exports", "toyA"], function(require, exports, __modA__) {
    (function (littleware) {
        (function (toy) {
            (function (b) {
                var modA = __modA__;

                var Greeter = (function () {
                    function Greeter(message) {
                        this.delegate = new modA.littleware.toy.a.Greeter(message);
                    }
                    Greeter.prototype.greet = function () {
                        return this.delegate.greet();
                    };
                    return Greeter;
                })();
                b.Greeter = Greeter;                
                function runGreeter(greeter) {
                    var button = document.createElement('button');
                    button.innerText = "Say Hello";
                    button.onclick = function () {
                        alert(greeter.greet());
                    };
                    document.body.appendChild(button);
                }
                b.runGreeter = runGreeter;
            })(toy.b || (toy.b = {}));
            var b = toy.b;
        })(littleware.toy || (littleware.toy = {}));
        var toy = littleware.toy;
    })(exports.littleware || (exports.littleware = {}));
    var littleware = exports.littleware;
})

The yui module system expects a module to register itself via a call to YUI.add with a closure function that accepts a YUI instance variable "Y" to use as the root of the module namespace. A YUI module for our typescript code could be implemented using the amd definition something like this:


YUI.add('littleware-littleUtil', function(Y) {
    // call the lambda passed as 2nd argument to amd define
    lambda( Y, Y, Y );
}, '0.1.1', { 'requires' : [ 'littleware-toy-a' ] } );

I was able to hack something to incorporate the amd module-definitions output from the typescript compiler into a YUI module system - there are just a few tricks.

The first trick is to introduce our own global define method that connects the amd module definitions with the YUI module system:


function define( argNames, moduleThunk ) {
  var name = null;
  // hacky way to get the YUI module name ...
  try { moduleThunk( null, null ); } catch ( v ) { name = v; }
  YUI.add(name, function(Y) {
    var thunkArgs = [];
    for( var i=0; i < argNames.length; ++i ) {
      thunkArgs.push( Y );
    }
    moduleThunk.apply( Y, thunkArgs );
  }, '0.1.1' );

Second trick - the YUI.add call that registers a module's closure with YUI requires a module name. The hack I came up with is to call into the amd module-definition with null arguments, and add a block of code to the typescript that checks for nulls, and throws a "module name" exception:


declare var exports:Y;

//declare module littleware.toy.a;
if ( null == exports ) {
    // Hook to communicate out to YUI module system a YUI module-name for this typescript file
    throw "littleware-toy-toyB";
}

var Y:Y = exports;

This is a complete hack that exposes the javascript compiler output to the typescript code, but it works. The
var Y:Y = exports;
exposes the YUI instance "Y" to the typescript code. Juan Dopazo posted his node script that generates typescript type-definitions for YUI. I saved the output file he posted as "yui.d.ts", cleaned it up a bit, and included that in my build, so the type-script compiler can perform type-checking on YUI calls. I modified the typescript toy to take advantage of YUI DOM utilities just to verify all this module magic worked:

  var counter = 0;

  export function runGreeter( greeter:Greeter ) {
      var toyNode = Y.one( "div.toy" );
      var messageNode = toyNode.one( "p" );
      var button = document.createElement('button');
      button.innerText = "Say Hello";
      button.onclick = function() {
          messageNode.setHTML( "" + counter + ": " + greeter.greet()  );
          counter += 1;
      }

      toyNode.append(button);
  }

The "YUI.add" module registration includes options for specifying a module's dependencies on other YUI modules, but I prefer to specify that information in the YUI-load configuration that tells YUI which javascript file contains which module. I include that code along with my global "define" amd-hack function in a little bootstrap.js file that I include in the html host file via a <script> tag.

/*
 * Copyright 2011 catdogboy at yahoo.com
 *
 * The contents of this file are subject to the terms of the
 * Lesser GNU General Public License (LGPL) Version 2.1.
 * http://www.gnu.org/licenses/lgpl-2.1.html.
 */

if( window.littleware == undefined ) {
    window.littleware = {};
}

/**
 * littleYUI module, see http://yuiblog.com/blog/2007/06/12/module-pattern/
 * YUI doc comments: http://developer.yahoo.com/yui/yuidoc/
 * YUI extension mechanism: http://developer.yahoo.com/yui/3/yui/#yuiadd
 * Provides convenience method for loading YUI with the littleware extension modules,
 * so client javascript code just invokes:
 *      littleware.littleYUI.bootstrap()... 
 *
 * @module littleware.littleYUI
 * @namespace auburn.library
 */
littleware.littleYUI = (function() {
    
    /**
     * Get the YUI.config groups entry that registers the littleware javascript
     * modules with - YUI( { ..., groups: { littleware: getLittleModules(), ... } )
     * @method getLittleModules
     * @return dictionary ready to add to YUI config's groups dictionary
     */
    var getLittleModules = function() {
        return {
            combine: false,
            base: '/btrack/resources/js/littleware/',
            modules:  { 
                'littleware-littleUtil': {
                    path: "littleUtil.js",
                    requires: [ "array-extras" ]
                },
                'littleware-littleId': {
                    path: "littleId.js",
                    requires: ['anim', 'base', 'node-base', 'node']
                },
                'littleware-littleTree': {
                    path: "littleTree.js",
                    requires: ['anim', 'base', 'node', 'node-base', 'test']
                },
                'littleware-littleMessage': {
                    path: "littleMessage.js",
                    requires: [ 'io-base', 'node', 'node-base', 
                           'littleware-littleUtil', 'test']
                },
                'littleware-feedback-model': {
                    path: "feedback/littleFeedback.js",
                    requires: [ 'node', 'base', 'littleware-littleUtil', 'test']
                },
                'littleware-feedback-view': {
                    path: "feedback/FbWidget.js",
                    requires: [ 'node', 'base', 'littleware-littleUtil', 
                       'littleware-feedback-model', 'test']
                },
                'littleware-toy-toyA': {
                    path: "toy/toyA.js",
                    requires: [ 'node', 'base', 'littleware-littleUtil', 'test']
                },
                'littleware-toy-toyB': {
                    path: "toy/toyB.js",
                    requires: [ 'littleware-toy-toyA']
                }                                                
            }
        }    ;
    };

    
    /**
     * littleYUI - wrapper around YUI3 YUI() method that
     * registers local littleware modules.
     */
    var bootstrap = function () {
        return YUI({
            //lang: 'ko-KR,en-GB,zh-Hant-TW', // languages in order of preference
            //base: '../../build/', // the base path to the YUI install.  Usually not needed because the default is the same base path as the yui.js include file
            //charset: 'utf-8', // specify a charset for inserted nodes, default is utf-8
            //loadOptional: true, // automatically load optional dependencies, default false
            //combine: true, // use the Yahoo! CDN combo service for YUI resources, default is true unless 'base' has been changed
            filter: 'raw', // apply a filter to load the raw or debug version of YUI files
            timeout: 10000, // specify the amount of time to wait for a node to finish loading before aborting
            insertBefore: 'yuiInsertBeforeMe', // The insertion point for new nodes

            // one or more groups of modules which share the same base path and
            // combo service specification.
            groups: {
                // Note, while this is a valid way to load YUI2, 3.1.0 has intrinsic
                // YUI 2 loading built in.  See the examples to learn how to use
                // this feature.
                littleware: getLittleModules()
            }
        });
    };
    return {
        bootstrap: bootstrap,
        getLittleModules: getLittleModules
    };
})();


/**
 * AMB module hook - still needs work ...
 * @param argNames {Array[String]}
 * @param moduleThunk {function(requires,exports,import1, import2, ...)}
 */
function define( argNames, moduleThunk ) {
  var name = null;
  // hacky way to get the YUI module name ...
  try { moduleThunk( null, null ); } catch ( v ) { name = v; }
  YUI.add(name, function(Y) {
    var thunkArgs = [];
    for( var i=0; i < argNames.length; ++i ) {
      thunkArgs.push( Y );
    }
    moduleThunk.apply( Y, thunkArgs );
  }, '0.1.1' );
}

Anyway - that's a typical javascript mess, but it lets me bootstrap a javascript "main" in a new page with code like this:

   littleware.littleYUI.bootstrap().use( 
                'node', 'node-base', 'event', 'test', 'scrollview',
                'littleware-toy-toyB',
                function (Y) {
                    var util = Y.littleware.littleUtil;
                    var log = new util.Logger( "events.html" );
                    
                    log.log( "main() running" );
                    var toy = Y.littleware.toy.b
                    var greeter = new toy.Greeter( "Dude" );
                    toy.runGreeter( greeter );
                }
        );

Hopefully that makes some sense. I pushed the code up to the littleware repo I've been committing to lately. That repo is a mess, but feel free to take a look.

Saturday, April 20, 2013

511 labor contraction counter app

I coded up a little 511 app to help track the duration and period of labor contractions. Childbirth extends over several hours for most women, so the rule of thumb for a woman trying to decide when to head into the hospital (or whatever) is to wait until after contractions occur every 5 minutes averaging one minute in length for one hour - the "5-1-1" rule.

Anyway - the "5-1-1" rule was news to me when I learned about it watching this goofy video where the expecting parents were tracking contractions on a pad of paper. I thought, "why don't they track the contractions on their smart phone?". Such a genius!

I poked around looking for a 511 app, and didn't find one, so I decided to take it on as a project. After I started a friend mentioned that there is an iPhone app - doh! By then I was already committed to the project. There's one for Android now too - ugh.

Anyway - in addition to demonstrating my complete incompetence in web design, the app allowed me to play around with some "HTML5" tech that is pretty cool. I used YUI - which I like a lot. The app is wired with a manifest, so our poor pregnant woman can pull up the app even when she's offline, and an apple-touch icon, so she can "pin" the app to her start screen as a standalone app.

Unfortunately, I made some newb mistakes too. I designed the app to just maintain its state in memory - assuming that when a user starts having contractions, then she'll pull up the app, use it until she goes into full labor or not, and that's that. I expected a webapp launched from the start-screen would maintain its state if the user switched between apps - the same way a browser tab maintains its state if the user switches tabs. Bad assumption! It turns out that a pinned webapp loses its in-memory state whenever the user switches between apps - at least the iPhone behaves that way. I went back and added code to save and restore the apps state to local cache, but it would have been better if I had designed the code to work that way from the start.

The app could of course benefit from several improvements. First, I currently have the thing wired to save state to local cache after every UX event, because I couldn't figure out how to detect a "user is leaving this page" event, and only save then. I recently found this thread on stackoverflow that describes the "leaving page" event, so I'd like to give that a try.

I'd also like to wire-up the app to somehow auto-suggest that the user pin the app to her home screen, and make that easy to do (it's kind of a hassle in Android Chrome for example). I'm pretty sure some apps do that kind of thing, but it may be more trouble than I'm willing to go through - we'll see.

Anyway - I'm pretty happy with how the project came out - even though the woman I built it for didn't use it, because her water broke (doh!), but the baby came out healthy, so all's well that ends well. The source code is online under my dev-clone of my littleware mercurial repo on google code - mixed in with a bunch of other code I've been playing with. At some point I started making breaking changes to the littleware code, so I forked off to that clone, but still haven't merged back to the main repo - which is stupid, because nobody uses that code but me. Anyway - I need to clean up the repo, and probably setup a clone on github - which is where all the cool kids track code these days, but I'll save that project for another day ...