Showing posts with label javascript. Show all posts
Showing posts with label javascript. Show all posts

Wednesday, June 02, 2021

Porting https://apps.frickjack.com to hugo

Problem and Audience

A web site may be architected in various ways: from a simple collection of static html, javascript, and css files behind a web server; to a site administered by a content management system; to a web application built on custom server or client side software.

The appropriate design for a particular site is the one that best balances the requirements of the site's different stakeholders. For example, the marketing team may primarily view the site as one part of customer relationship management (CRM). The customer support team might want to publish documentation to the site, or provide tools for a customer to request support. The product team may want the site to provide access to the product's user console application.

Each stakeholder may need to update the site in different ways. The marketing and customer support teams may require a simple mechanism to submit edits for review and publication. The product development team may want to build and test code updates with a CICD pipeline. Neither of those teams may be well versed in graphic design.

apps.frickjack.com and hugo

We just completed a project to transition https://apps.frickjack.com to the hugo static site generator. The https://apps.frickjack.com property acts both as my personal site and as a sandbox for experimenting with the littleware software stack. It is a static multi-page site served from an S3 bucket with a few small javascript web applications and some early integrations with web API's.

The hugo transition allowed us to move the content and theme management for https://apps.frickjack.com from an idiosynchratic templating system to the well documented and community supported process that hugo implements. Hugo's theme design also pushed us to think about what we want the site to provide to its visitors, and whether the landing page clearly conveys those use cases. For example, https://www.salesforce.com/ has a straight forward explanation of what the company is, "the #1 CRM ...", and a call to action "sign up for your free account".

The content management process is still developer oriented in that site updates are managed via github pull requests, and a codebuild CI job updates the site, but the content markdown and theme templates are now managed in their own hugo directory hierarchy. The site's github repo includes more details at https://github.com/frickjack/little-apps/blob/master/Notes/howto/devTest.md.

Summary

We transitioned https://apps.frickjack.com to the hugo static site generator to further decouple the site's content and theme management from the javascript code implementing the dynamic services and applications on the site. We also reorganized the site to better support the experiences we want the site to provide to visitors.

Wednesday, January 20, 2021

Asynchronous Toolbox Pattern

A javascript application designer must structure her app to asynchronously load configuration and bootstrap its modules at startup. The asynchronous toolbox pattern addresses this challenge with a simple dependency injection framework.

The Asynchronous Bootstrap Problem

Consider an application with three modules in addition to modMain: modA, modB, and modC that export classA, classB, and classC respectively where modC imports (depends on) modA and modB, and classC is a singleton, and each module asynchronously loads configuration at startup. How should a developer structure the code in each module?

If each module's startup (configuration load) were synchronous, then the application could be structured like this.

// modC

import classA from "modA";
import classB from "modB";

function loadConfig() { ... }

class C {
    a;
    b;
    config;

    constructor(a, b, config) {
        this.a = a;
        this.b = b;
        this.config = config;
    }
    ...

    static get providerName() {
        return "driver/myApp/modA/classA";
    }
}

let lazySingleton = null;

export getC() {
    if (! lazySingleton) {
        lazySingleton = new C(new A(), new B(), loadConfig());
    }
    return lazySingleton;
}
// modB

function loadConfig() {}

const sharedConfig = loadConfig();

export class B {}
// modA

function loadConfig() {}

const sharedConfig = loadConfig();

export class A {}
// modMain
import getC from "modC";

function go() {
    const c = getC();
    ...
}

go();

If the mod*.loadConfig functions are asynchronous, then bootstrapping this simple application becomes more complicated as the asynchrony propogates through the call graph to the class constructors. This simple implementation also has a few other shortcomings that can become troublesome in a larger application.

  • First, the various loadConfig functions can be factored out to a single configuration module, modConfig, that supports default and override configurations and loading configuration from external data sources.
  • Second, in some cases it is useful to decouple an interface from its implementation, so that different applications or application deployments can use an implementation that fits its environment. Dependency injections frameworks like guice and Spring are one way to decouple interface from implementation in java applications.
  • Finally, an application that composes multiple modules that load external configuration and connect to external systems may benefit from a framework that manages the application lifecycle - or at least startup and shutdown.

Asynchronous Toolbox

The little-elements package includs an appContext module that provides an "asynchronous toolbox" framework for managing application configuration and bootstrap and decoupling interfaces from implementation.

Each module participating in the asynchronous toolbox provides zero or more asynchronous tool factories, and consumes a toolbox of zero or more tool factories from other modules. For example, modA in the example above (with typescript types) would look something like this.

// modA

import { Logging } from "../../@littleware/little-elements/common/logging.js";
import AppContext, { getTools, ConfigEntry } from "../../@littleware/little-elements/common/appContext.js";
import { SharedState } from "../../@littleware/little-elements/common/sharedState.js";
import { LazyProvider } from "../../@littleware/little-elements/common/provider.js";

/**
 * The configuration that classA consumes
 */
interface Config {
   foo: string;
   endpoint: string;
}

export const configKey = "config/myApp/modA";


/**
 * The tools that classA consumes - including config
 */
interface Tools {
    config: Config;
    log: Logger;
    state: SharedState;
}

class A {
    tools: Tools;

    constructor(tools:Tools) {
        this.tools = tools;
    }
    ...
}

AppContext.get().then(
    (cx) => {
        // register a default config. 
        // other modules can provide overrides.
        // the runtime also loads overrides from
        // registered configuration sources 
        // like remote or local json files
        cx.putDefaultConfig(configKey, { foo: "foo", endpont: "endpont" });

        // register a new provider
        cx.putProvider(
            classC.providerName,
            {
                config: configKey,
                log: Logger.providerName,
                state: StateManager.providerName,
            },
            async (toolBox) => {
                // the injected toolBox is full of asynchronous 
                // factories: async get():Tool
                // the `getTools()` helper below invokes get()
                // on every factory in the toolbox
                const tools: Tools = await getTools(toolBox);
                // the configuration factory
                // returns a ConfigEntry that includes 
                // both the defaults and overrides
                tools.config = { ...tools.config.defaults, ...tools.config.overrides };
                return LazyProvider(() => new C(tools as Tools));
            }
        );
    }
);

// Also export an asynchronous provider
// that modules not participating in the
// app context can use
export async function getC(): Promise<C> {
    return AppContext.get().then(
        (cx) => cx.getProvider(C.providerName),
    ).then(
        (provider: Provider<C>) => provider.get(),
    );
}

Then the main module for the application does something like this.

// mainMod

class Tools {
    c: classC;
}

function go(tools:Tools) { ... }

appContext.get().then(
    cx => cx.onStart( 
        // register a callback to run once the app
        // main module triggers start
        { c: classC.providerName },
        (tools:Tools) => go(tools),
).then(
    cx => cx.start();  // start the app
);

Example

The little-elements package leverages the asynchronous toolbox pattern in its authentication UX (signin, signout). The authn system relies on interactions between several modules.

The lw-auth-ui custom element builds on a lw-drop-down custom element to populate a drop-down menu with items that trigger internal navigation events. The drop-down component loads its menu contents (keys and navigation targets) as configuration from its asynchronous toolbox. The toolbox also contains helpers for internationalization and shared state. The UI listens for changes to the "logged in user" key in the shared state store.

The lw-auth-controller custom element consumes a toolbox that includes configuration, historyHelper, and sharedState tools. The controller listens for the internal navigation events triggered by the UI, then redirects the user's browser to the appropriate OIDC endpoint. The controller also polls the backend user-info endpoint to maintain the user data in the shared state store that the UI consumes.

Summary

The asynchronous toolbox is a flexible approach to manage asynchronous application bootstrap and configuration. It is adaptable to different situations like injecting dependencies into HTML custom elements. Unfortunately this pattern introduces boiler plate into the codebase. We hope to streamline the framework as it evolves over time.

Friday, August 03, 2018

jasminejs tests with es2015 modules

Running jasminejs tests of javascript modules in a browser is straight forward, but requires small adjustments, since a module loads asynchronously, while jasmine's default runtime assumes code loads synchronously. The approach I take is the same as the one used to test requirejs AMD modules:

  • customize jasmine boot, so that it does not automatically launch test execution on page load
  • write a testMain.js script that imports all the spec's for your test suite, then launches jasmine's test execution

For example, this is the root test suite (testMain.ts) for the @littleware/little-elements module:

import './test/spec/utilSpec.js';
import './arrivalPie/spec/arrivalPieSpec.js';
import './styleGuide/spec/styleGuideSpec.js';
import {startTest} from './test/util.js';

startTest();

The startTest() function is a little wrapper that detects whether karmajs is the test runtime, since the test bootstrap process is a little different in that scenario. The karmajs config file should also annotate javascript module files with a module type. For example, here is an excerpt from little-element's karma.conf.js:

    files: [
      'lib/test/karmaAdapter.js',
      { pattern: 'lib/arrivalPie/**/*.js', type: 'module', included: false },
      { pattern: 'lib/styleGuide/**/*.js', type: 'module', included: false },
      { pattern: 'lib/test/**/*.js', type: 'module', included: false },
      { pattern: 'lib/testMain.js', type: 'module', included: true },
      { pattern: 'node_modules/lit-html/*.js', type: 'module', included: false }
    ],

Feel free to import the @littleware/little-elements module into your project if it will be helpful!

Sunday, October 16, 2016

Dynamic karma.conf.js for Jenkins

Karma-runner is a great way to run jasmine javascript test suites. One trick to make it easy to customize karma's runtime behavior is to take advantage of the fact that karma's config file is javascript - not just json, so it's easy to wire up karma.conf.js to change karma's behavior based on an environment variable.

For example, when run interactively karma can watch files, and rerun the test suite in Chrome when a file changes; but when Jenkins runs karma, karma should run through the test suite once in phantomJs, then exit. Here's one way to set that up.

First, if you generate your karma config.js file using karma init, and wire up the file to run your test suite in Chrome and watch files for changes, then you wind up with a karma.conf.js (or whatever.js) file structured like this:

module.exports = function(config) {
    config.set( { /* bunch of settings */ } );
}

To wire up the jenkins-phantomjs support - just pull the settings object out to its own variable, and wire up a block of code to change the settings when your favorite environment variable is set, and wire up Jenkins to set that environment variable ...

module.exports = function(config) {
    var settings = { /* bunch of settings */ },
        i, overrides = {};
    if ( process.env.KARMA_PHANTOMJS ) {  // jenkins is running karma ...
        overrides = {
            singleRun: true,
            reporters: ['dots', 'junit' ],
            junitReporter: {
                outputFile: 'test-result.xml'
            },
            browsers: [ 'PhantomJS', 'PhantomJS_custom'],
            customLaunchers: {
                PhantomJS_custom: {
                    flags: [ '--load-images=true'], 
                    options: {
                        windowName: 'my-window'
                    },
                    debug:true
                }
            }
         };
    }
    for ( i in overrides ) {
        settings[i] = overrides[i];
    }
    config.set( settings );
}

Jenkins can run Karma test suites with a shell script, and Jenkins' JUnit plugin harvests and publishes the test results; works great!

Sunday, January 31, 2016

javascript decomposition with bower's shorthand resolver


We started using bower to manage third party dependencies in our projects, and realized that we could also use bower to help decompose our javascript applications into re-usable independently tested components. We started out with a lazy approach where we setup a jscommon/ folder under which we installed our different components (jscommon/A/, jscommon/B/, jscommon/C/, ...) where each component might have its own build and test scripts - whatever it needs.

We started out representing dependencies between components with file URL's in bower.json files, so if C depended on B and A, then it might have a bower.json file like this:

  {
     ...
     "dependencies" : {
            "A" : "../A",
            "B" : "../B"
     }
   }

Of course - that quickly falls apart when an application's bower.json file has a different relative path to the jscommon/ folder, but using a shorthand resolver solves the problem. An application (or test or whatever) registers a shorthand resolver in a .bowerrc file with the appropriate relative path like this:

{
    ...
    "shorthand-resolver" : "../../{{shorthand}}"
}

, and we specify local dependencies in bower.json with short-hands like this:

  {
     ...
     "dependencies" : {
            "A" : "jscommon/A",
            "B" : "jscommon/B"
     }
   }

Sunday, March 15, 2015

jQuery in custom namespace

A re-usable javascript component that depends on jQuery may need to repackage jQuery in a custom namespace to avoid overriding other versions of jQuery loaded onto a third party host page. Fortunately the jQuery project makes it easy to move the core API to a namespace. Just download the uncompressed developer version of jQuery, and replace the following block of code at the top of the file:


    if ( typeof module === "object" && typeof module.exports === "object" ) {
  // For CommonJS and CommonJS-like environments where a proper `window`
  // is present, execute the factory and get jQuery.
  // For environments that do not have a `window` with a `document`
  // (such as Node.js), expose a factory as module.exports.
  // This accentuates the need for the creation of a real `window`.
  // e.g. var jQuery = require("jquery")(window);
  // See ticket #14549 for more info.
  module.exports = global.document ?
   factory( global, true ) :
   function( w ) {
    if ( !w.document ) {
     throw new Error( "jQuery requires a window with a document" );
    }
    return factory( w );
   };
 } else {
  factory( global );
 }
with something like this:

        global.myNameSpace = global.myNameSpace || (global.myNameSpace = {});
        global.myNameSpace.jQuery = factory( global, true );

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.

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, June 03, 2013

compiling typescript with rhino javascript engine

I spent way too long the last couple days patching the typescript compiler (tsc) to run with java's rhino javascript engine. I mentioned in an earlier post how my dell laptop died on me. I wound up buying an hp envy m4 laptop on clearance at BestBuy to replace it, but before that I did that I was goofing around for a couple days getting by with my Android phone and the old PowerBook G4 I pulled off the shelf that amazingly boot up for me. Anyway - I thought I'd try to get tsc running from the command-line. The compiler is javascript compiled from typescript code, so I first tried to build node from source, but node's build setup requires newer versions of python, make, and gcc than what the powerbook had, so then I got the idea to try to run tsc with rhino, since the powerbook did have java 1.5 installed (java 1.6+ ships with rhino and a jrunscript command line tool). Of course it didn't "just work", and I let myself get sucked into banging my head on it - even after I got the new laptop. Ugh.

Anyway - I eventually got tsc running with rhino (the patch listing is further below). The easy part of the project was implementing a rhino version of tsc's IIO interface for file IO. The rhino implementation just calls through to java.io classes, and tsc runs a few feature tests to figure out which javascript engine it's running under:

    if (typeof ActiveXObject === "function")
        return getWindowsScriptHostIO();
    else if (typeof require === "function")
        return getNodeIO();
    else if ( typeof java != "undefined" )
        return getRhinoIO();
    else
        return null; // Unsupported host

The IO code was straight forward; the painful part was working around rhino's quirks. The first quirk I ran into was that rhino appears to treat the names of java's primitive types as reserved words, so things like:
var byte = 0;
or option.short = "v";
are illegal. Fortunately - that only popped up a couple places in the tsc code, but it's an unfortunate "feature" for rhino to have.

Another problem I ran into was invoking "delete" on an instance of java.io.File. Javascript includes delete in its collection of reserved words, but it should be legal to include a "delete()" method on some class. Rhino's javascript grammar probably just needs some love. The workaround was to access the method via f["delete"]() instead of f.delete().

C:\Users\Reuben\Documents\Code\typescript\src\compiler
> node
> var Foo = function() { return this; }
undefined
> Foo.prototype.delete = function() { return "bla"; }
[Function]
> (new Foo()).delete();
'bla'

...


> C:\Users\Reuben\Documents\Code\typescript\src\compiler
> jrunscript
js> var Foo = function() { return this; }
js> Foo.prototype.delete = function() { return "bla"; }
script error: sun.org.mozilla.javascript.internal.EvaluatorException: missing name after . operator (<STN> at line number 1
js>

Another problematic feature of rhino is that it does not hide the distinction between javascript's string type (which tsc expects), and java's java.lang.String. I discovered that Rhino has methods for converting between java types and javascript types - including the String() method mentioned here:

    resolvePath: function (path) {
        return <string> String( (new java.io.File(path)).getCanonicalPath() );
    },

There were one or two other small rhino quirks to work out, but the big one that surprised me was that rhino's regular expression objects apparently don't respect javascript's normal scoping rules. The tsc compiler was failing under rhino with various undefined types that weren't properly pulled in via the file-reference comments (see section 9.1.1 in the typescript spec). I eventually had a test setup, and found rhino would load every other referenced file, so a tsc run with rhino had this output:

(1)Reading code from C:/Users/Reuben/Documents/Code/typescript/src/compiler/typescript.ts
Found code at C:/Users/Reuben/Documents/Code/typescript/src/compiler/typescript.ts
 file reference: diagnostics.ts
 file reference: nodeTypes.ts
 file reference: ast.ts
 file reference: astWalkerCallback.ts
 file reference: astLogger.ts
 file reference: base64.ts
 file reference: emitter.ts
 file reference: parser.ts
 file reference: scanner.ts
 file reference: scopeWalk.ts
 file reference: symbols.ts
 file reference: tokens.ts
 file reference: typeCollection.ts
 file reference: types.ts
 file reference: referenceResolution.ts
 file reference: incrementalParser.ts

The output with nodejs was:

   (1)Reading code from C:/Users/Reuben/Documents/Code/typescript/src/compiler/typescript.ts
   Found code at C:/Users/Reuben/Documents/Code/typescript/src/compiler/typescript.ts
    file reference: diagnostics.ts
    file reference: flags.ts
    file reference: nodeTypes.ts
    file reference: hashTable.ts
    file reference: ast.ts
    file reference: astWalker.ts
    file reference: astWalkerCallback.ts
    file reference: astPath.ts
    file reference: astLogger.ts
    file reference: binder.ts
    file reference: base64.ts
    file reference: sourceMapping.ts
    file reference: emitter.ts
    file reference: errorReporter.ts
    file reference: parser.ts
    file reference: printContext.ts
    file reference: scanner.ts
    file reference: scopeAssignment.ts
    file reference: scopeWalk.ts
    file reference: signatures.ts
    file reference: symbols.ts
    file reference: symbolScope.ts
    file reference: tokens.ts
    file reference: typeChecker.ts
    file reference: typeCollection.ts
    file reference: typeFlow.ts
    file reference: types.ts
    file reference: pathUtils.ts
    file reference: referenceResolution.ts
    file reference: precompile.ts
    file reference: incrementalParser.ts
    file reference: declarationEmitter.ts

Anyway, long story short, it turned out that the reference strings were each processed by a function with a regular expression, and rhino had this crazy behavior where regular expression objects appear to be global.

with rhino:

js> function doTest( s ) { var rx =  /^\s*(\/\/\/\s*/gim;
return (rx.exec(s) == null); }

js> doTest(comment);
false
js> doTest(comment);
true
js> doTest(comment);
false
js> doTest(comment);
true

with node:
> function doTest(s) {
... var rx =  /^\s*(\/\/\/\s*/gim;
... return (rx.exec(s) == null);
... }
undefined
> comment
'///<reference path=\'sourceMapping.ts\' />'
> doTest(comment);
false
> doTest(comment);
false
> doTest(comment);
false
> doTest(comment);
false
> doTest(comment);
false

Unbelievable. Anyway - the work around is to reset the rx.lastIndex before each run, so:

    function getFileReferenceFromReferencePath(comment: string): IFileReference {
        var referencesRegEx = /^(\/\/\/\s*<reference\s+path=)('|")(.+?)\2\s*(static=('|")(.+?)\2\s*)*\/>/gim;
        referencesRegEx.lastIndex = 0;  // work around ridiculous bug in rhino ...
        var match = referencesRegEx.exec(comment);
        ...

Finally - I just tested this stuff by running the compiler on itself. The typescript repo on codeplex includes a bunch of test cases and an nmake based Makefile, but I was too lazy to download visual studio and get that working. In the end - rhino compiled tsc in 2 minutes, and node did it in 3 seconds. Ugh!

C:\Users\Reuben\Documents\Code\typescript\src\compiler
> date; jrunscript tsc.js --out tsc2.js tsc.ts; date

Monday, June 3, 2013 12:49:41 PM
Monday, June 3, 2013 12:51:39 PM


C:\Users\Reuben\Documents\Code\typescript\src\compiler
> date; node tsc2.js --out tsc3.js tsc.ts; date

Monday, June 3, 2013 12:58:40 PM
Monday, June 3, 2013 12:58:43 PM

Update 2013/06/29: I tried java 8's new nashorn javascript engine (in a jdk8 pre-release) to see how it did. Nashorn currently runs the tsc compile about 10% faster than rhino - still a lot slower than node. Doh!

> date; & 'C:\Program Files\Java\jdk1.8.0\bin\jrunscript.exe' .\tsc2.js --out tsc2.js tsc.ts; date;

Saturday, June 29, 2013 5:29:11 PM
Saturday, June 29, 2013 5:30:57 PM

Anyway - I'll check to see if the typescript maintainers will accept this patch, but I'll be surprised if they want anything to do with rhino after reading this sad tale ...


diff --git a/src/compiler/base64.ts b/src/compiler/base64.ts
index ee2d3c5..b4fc315 100644
--- a/src/compiler/base64.ts
+++ b/src/compiler/base64.ts
@@ -67,20 +67,21 @@ module TypeScript {
 
             var shift = 0;
             for (var i = 0; i < inString.length; i++) {
-                var byte = Base64Format.decodeChar(inString[i]);
+                // note: "byte" is reserved in java Rhino javascript environment - ugh
+                var bite = Base64Format.decodeChar(inString[i]);
                 if (i === 0) {
                     // Sign bit appears in the LSBit of the first value
-                    if ((byte & 1) === 1) {
+                    if ((bite & 1) === 1) {
                         negative = true;
                     }
-                    result = (byte >> 1) & 15; // 1111x
+                    result = (bite >> 1) & 15; // 1111x
                 } else {
-                    result = result | ((byte & 31) << shift); // 11111
+                    result = result | ((bite & 31) << shift); // 11111
                 }
 
                 shift += (i == 0) ? 4 : 5;
 
-                if ((byte & 32) === 32) {
+                if ((bite & 32) === 32) {
                     // Continue
                 } else {
                     return { value: negative ? -(result) : result, rest: inString.substr(i + 1) };
diff --git a/src/compiler/io.ts b/src/compiler/io.ts
index a5eb1ad..6e75bf2 100644
--- a/src/compiler/io.ts
+++ b/src/compiler/io.ts
@@ -13,6 +13,9 @@
 // limitations under the License.
 //
 
+declare var arguments:any;
+var javaArgs:any = arguments; // Rhino sets global arguments ... bla
+
 interface IResolvedFile {
     content: string;
     path: string;
@@ -105,7 +108,10 @@ declare class Enumerator {
     constructor (o: any);
 }
 declare function setTimeout(callback: () =>void , ms?: number);
+
 declare var require: any;
+declare var java: any;
+
 declare module process {
     export var argv: string[];
     export var platform: string;
@@ -123,7 +129,7 @@ declare module process {
 }
 
 var IO = (function() {
-
+    
     // Create an IO object for use inside WindowsScriptHost hosts
     // Depends on WSCript and FileSystemObject
     function getWindowsScriptHostIO(): IIO {
@@ -533,12 +539,251 @@ var IO = (function() {
             },
             quit: process.exit
         }
-    };
+    }
+    ;
+        
+    
+    function getRhinoIO():IIO {
+        var utf8 = java.nio.charset.Charset.forName( "UTF-8" );
+        var jscriptArgs = [];
+   
+        for( var i=0; i < javaArgs.length; ++i ) {
+            //
+            // convert java string to javascript string (so javascript string methods work - ugh!)
+            // see https://groups.google.com/forum/?fromgroups#!topic/mozilla.dev.tech.js-engine.rhino/FV15_KJVLGM
+            //
+            jscriptArgs.push( String( javaArgs[i] ) );
+        }
+        
+        
+        /**
+         * Byte-order-mark detector - ugh.
+         * @param streamInn java.io.InputStream
+         * @return java.io.Reader
+         * @see http://blog.publicobject.com/2010/08/handling-byte-order-mark-in-java.html
+         */
+       function inputStreamToReader(streamIn) {
+         // buffered stream supports mark and reset
+         var stream = new java.io.BufferedInputStream( streamIn );
+         stream.mark(3);
+         var byte1 = stream.read();
+         var byte2 = stream.read();
+         if (byte1 == 0xFF && byte2 == 0xFE) {
+           return new java.io.InputStreamReader(stream, "UTF-16LE");
+         } else if (byte1 == 0xFF && byte2 == 0xFF) {
+           return new java.io.InputStreamReader(stream, "UTF-16BE");
+         } else {
+           var byte3 = stream.read();
+           if (byte1 == 0xEF && byte2 == 0xBB && byte3 == 0xBF) {
+             return new java.io.InputStreamReader(stream, "UTF-8");
+           } else {
+             stream.reset();
+             return new java.io.InputStreamReader(stream);
+           }
+         }
+       };
+
+          return {
+            readFile: function (file):string {
+                try  {
+                    var f = new java.io.File( file );
+                    if( (! f.exists()) || (! f.isFile()) ) { return ""; }
+                    var buffer = java.lang.reflect.Array.newInstance( java.lang.Character.TYPE, f.length() + 128 ); // 128 fudge
+                    var reader = new java.io.BufferedReader( 
+                       inputStreamToReader(
+                              new java.io.FileInputStream( f ) 
+                         ) 
+                    );
+                    try {
+                     var offset = 0;
+                        for( var step = reader.read( buffer, offset, buffer.length - offset ); 
+                             step >= 0; step = reader.read( buffer, offset, buffer.length - offset ) ) {
+                             offset += step;
+                             //java.lang.System.out.println( "Just read num bytes: " + step );
+                        }
+                        var javaString = new java.lang.String( buffer, 0, offset )
+                        //java.lang.System.out.println( "Read: " + javaString );
+                        // convert java string to javascript string ... ugh
+                        return <string> String( new java.lang.String( buffer, 0, offset ) );
+                    } catch (ex) { 
+                        reader.close();
+                        ex.printStackTrace( java.lang.System.err );
+                        throw ex; 
+                    }
+                } catch (e) {
+                    IOUtils.throwIOError("Error reading file \"" + file + "\" - " + e.toString(), e );
+                }
+            },
+            writeFile: function( path, content ) {
+               var f = new java.io.File( path );
+               if( f.exists() && f.isFile() ) {
+                   var writer = new java.io.OutputStreamWriter(
+                         new java.io.FileOutputStream( f ), utf8
+                    );
+                    writer.write( content );
+                    writer.close();
+               }
+            },
+            deleteFile: function (path) {
+               var f = new java.io.File( path );
+               if( f.exists() && f.isFile() ) {
+                   // delete is reserved in javascript - confused Rhino parser - ugh
+                   f["delete"]();
+                }
+            },
+            fileExists: function (path) {
+                var result:bool = (new java.io.File( path )).exists();
+                return result;
+            },
+            createFile: function (path, useUTF8?) {
+                var f = new java.io.File( path );
+                if ( f.exists() && (! f.isFile()) ) {
+                    IOUtils.throwIOError("Error creating file \"" + path + "\".", null ); 
+                } else if ( ! f.exists() ) {
+                    var dir = f.getParentFile();
+                    dir.mkdirs();
+                }
+                try  {
+                    var writer = new java.io.OutputStreamWriter(
+                             new java.io.FileOutputStream( f ), utf8
+                            );
+                } catch (e) {
+                    IOUtils.throwIOError("Couldn't write to file '" + path + "'.", e);
+                }
+                return new IOUtils.BufferedTextWriter( {
+                    Write: function (str:string ) {
+                        writer.write( str );
+                    },
+                    WriteLine: function (str:string) {
+                        writer.write( str + "\n" );
+                    },
+                    Close: function () {
+                        writer.close();
+                        writer = null;
+                    }
+                } );
+            },
+            dir: function dir(path, spec?, options?):string[] {
+                options = options || {
+                };
+                function filesInFolder(folder:any):string[] {
+                    var paths = [];
+                    var files = folder.listFiles();
+                    for(var i = 0; i < files.length; i++) {
+                        var f = files[i];
+                        if(options.recursive && f.isDirectory()) {
+                            paths = paths.concat(filesInFolder(f));
+                        } else if(f.isFile() && (!spec || f.getName().match(spec))) {
+                            paths.push( String( f.getPath() ) ); // convert to javascript String
+                        }
+                    }
+                    return paths;
+                }
+                return filesInFolder( new java.io.File( path ) );
+            },
+            createDirectory: function (path) {
+                try  {
+                    if(!this.directoryExists(path)) {
+                       (new java.io.File( path )).mkdirs();
+                    }
+                } catch (e) {
+                    IOUtils.throwIOError("Couldn't create directory '" + path + "'.", e);
+                }
+            },
+            directoryExists: function (path) {
+                var f = new java.io.File( path );
+                var result:bool = f.exists() && f.isDirectory();
+                return result;
+            },
+            resolvePath: function (path) {
+                return <string> String( (new java.io.File(path)).getCanonicalPath() );
+            },
+            dirName: function (path) {
+                return <string> String( (new java.io.File( path )).getCanonicalFile().getParent() );
+            },
+            findFile: function (rootPath, partialFilePath) {
+                var scan = new java.io.File( rootPath + "/" + partialFilePath ).getCanonicalFile();
+                while(true) {
+                    if( scan.exists() ) {
+                        try  {
+                            var content = this.readFile( scan.getPath() );
+                            return {
+                                content: <string> content,
+                                path: <string> String( scan.getPath() )
+                            };
+                        } catch (err) {
+                        }
+                    } else {
+                        // climb up the file system ... ?
+                        var parent = (new java.io.File( rootPath )).getParent();
+                        if( parent == null ) {
+                            return null;
+                        } else {
+                            scan = new java.io.File( parent, partialFilePath );
+                        }
+                    }
+                }
+            },
+            print: function (str) {
+                java.lang.System.out.print( str );
+            },
+            printLine: function (str) {
+                java.lang.System.out.println( str );
+            },
+            arguments: <string[]> jscriptArgs, 
+            stderr: {
+                Write: function (str) {
+                    java.lang.System.err.print(str);
+                },
+                WriteLine: function (str) {
+                    java.lang.System.err.println(str );
+                },
+                Close: function () {
+                }
+            },
+            stdout: {
+                Write: function (str) {
+                    java.lang.System.out.print(str);
+                },
+                WriteLine: function (str) {
+                    java.lang.System.out.println(str );
+                },
+                Close: function () {
+                }
+            },
+            
+            /**
+             * Could implement watchFile() with java-7 nio2 code, but too lazy to bother,
+             * since WindowsScriptHost skips this method too ... :)
+             * @see http://docs.oracle.com/javase/tutorial/essential/io/notification.html 
+             */
+            watchFile: null,
+            run: function(source, filename) {
+                try {
+                    eval(source);
+                } catch (e) {
+                    IOUtils.throwIOError("Error while executing file '" + filename + "'.", e);
+                }
+            },
+            getExecutingFilePath: function () {
+                return this.arguments[0];
+            },
+            quit: function (exitCode? : number = 0) {
+                try {
+                    java.lang.System.lang.exit(exitCode);
+                } catch (e) {
+                }
+            }
+        };
+    }
+    ;
 
     if (typeof ActiveXObject === "function")
         return getWindowsScriptHostIO();
     else if (typeof require === "function")
         return getNodeIO();
+    else if ( typeof java != "undefined" )
+        return getRhinoIO();
     else
         return null; // Unsupported host
 })();
diff --git a/src/compiler/optionsParser.ts b/src/compiler/optionsParser.ts
index a10fb8f..7a7eb73 100644
--- a/src/compiler/optionsParser.ts
+++ b/src/compiler/optionsParser.ts
@@ -18,7 +18,7 @@
 interface IOptions {
     name?: string;
     flag?: bool;
-    short?: string;
+    shorty?: string;  // note: "short" is reserved in java
     usage?: string;
     set?: (s: string) => void;
     type?: string;
@@ -34,7 +34,7 @@ class OptionsParser {
 
         for (var i = 0; i < this.options.length; i++) {
 
-            if (arg === this.options[i].short || arg === this.options[i].name) {
+            if (arg === this.options[i].shorty || arg === this.options[i].name) {
                 return this.options[i];
             }
         }
@@ -89,8 +89,8 @@ class OptionsParser {
             var usageString = "  ";
             var type = option.type ? " " + option.type.toUpperCase() : "";
 
-            if (option.short) {
-                usageString += this.DEFAULT_SHORT_FLAG + option.short + type + ", ";
+            if (option.shorty) {
+                usageString += this.DEFAULT_SHORT_FLAG + option.shorty + type + ", ";
             }
 
             usageString += this.DEFAULT_LONG_FLAG + option.name + type;
@@ -110,27 +110,27 @@ class OptionsParser {
         }
     }
 
-    public option(name: string, config: IOptions, short?: string) {
+    public option(name: string, config: IOptions, shorty?: string) {
         if (!config) {
-            config = <any>short;
-            short = null;
+            config = <any>shorty;
+            shorty = null;
         }
 
         config.name = name;
-        config.short = short;
+        config.shorty = shorty;
         config.flag = false;
 
         this.options.push(config);
     }
 
-    public flag(name: string, config: IOptions, short?: string) {
+    public flag(name: string, config: IOptions, shorty?: string) {
         if (!config) {
-            config = <any>short;
-            short = null;
+            config = <any>shorty;
+            shorty = null;
         }
 
         config.name = name;
-        config.short = short;
+        config.shorty = shorty;
         config.flag = true
 
         this.options.push(config);
diff --git a/src/compiler/precompile.ts b/src/compiler/precompile.ts
index 88adf32..c66f937 100644
--- a/src/compiler/precompile.ts
+++ b/src/compiler/precompile.ts
@@ -131,6 +131,7 @@ module TypeScript {
 
     function getFileReferenceFromReferencePath(comment: string): IFileReference {
         var referencesRegEx = /^(\/\/\/\s*<reference\s+path=)('|")(.+?)\2\s*(static=('|")(.+?)\2\s*)*\/>/gim;
+        referencesRegEx.lastIndex = 0;  // work around ridiculous bug in rhino ...
         var match = referencesRegEx.exec(comment);
 
         if (match) {
@@ -294,6 +295,7 @@ module TypeScript {
             
             if (!comment.isBlock) {
                 var referencedCode = getFileReferenceFromReferencePath(comment.getText());
+                //CompilerDiagnostics.debugPrint( "Considering comment as possible reference (" + (referencedCode ? "ok" : "no") + "): " + comment.getText() );
                 if (referencedCode) {
                     referencedCode.minChar = comment.startPos;
                     referencedCode.limChar = referencedCode.minChar + comment.value.length;
diff --git a/src/compiler/referenceResolution.ts b/src/compiler/referenceResolution.ts
index 442d8ec..52ae47c 100644
--- a/src/compiler/referenceResolution.ts
+++ b/src/compiler/referenceResolution.ts
@@ -102,7 +102,7 @@ module TypeScript {
                 // if the path is relative, or came from a reference tag, we don't perform a search
                 if (isRelativePath || isRootedPath || !performSearch) {
                     try {
-                        CompilerDiagnostics.debugPrint("   Reading code from " + normalizedPath);
+                        CompilerDiagnostics.debugPrint("   (1)Reading code from " + normalizedPath);
                             
                         // Look for the .ts file first - if not present, use the .ts, the .d.str and the .d.ts
                         try {
@@ -116,19 +116,19 @@ module TypeScript {
                                 else if (isTSFile(normalizedPath)) {
                                     normalizedPath = changePathToSTR(normalizedPath);
                                 }
-                                CompilerDiagnostics.debugPrint("   Reading code from " + normalizedPath);
+                                CompilerDiagnostics.debugPrint("   (2)Reading code from " + normalizedPath);
                                 resolvedFile.content = ioHost.readFile(normalizedPath);
                             }
                             catch (err) {
                                 normalizedPath = changePathToDSTR(normalizedPath);
-                                CompilerDiagnostics.debugPrint("   Reading code from " + normalizedPath);
+                                CompilerDiagnostics.debugPrint("   (3)Reading code from " + normalizedPath);
 
                                 try {
                                     resolvedFile.content = ioHost.readFile(normalizedPath);
                                 }
                                 catch (err) {
                                     normalizedPath = changePathToDTS(normalizedPath);
-                                    CompilerDiagnostics.debugPrint("   Reading code from " + normalizedPath);
+                                    CompilerDiagnostics.debugPrint("   (4)Reading code from " + normalizedPath);
                                     resolvedFile.content = ioHost.readFile(normalizedPath);
                                 }
                             }
@@ -148,7 +148,10 @@ module TypeScript {
 
                     // if the path is non-relative, we should attempt to search on the relative path
                     resolvedFile = ioHost.findFile(parentPath, normalizedPath);
-
+                    CompilerDiagnostics.debugPrint("   Attempting to resolve (" + parentPath + ", " + normalizedPath + ") got: " + 
+                         (resolvedFile == null) ? "null" : resolvedFile.path 
+                    );
+                    
                     if (!resolvedFile) {
                         if (isSTRFile(normalizedPath)) {
                             normalizedPath = changePathToTS(normalizedPath);
@@ -187,6 +190,10 @@ module TypeScript {
                     var resolvedFilePath = ioHost.resolvePath(resolvedFile.path);
                     sourceUnit.referencedFiles = preProcessedFileInfo.referencedFiles;
 
+                    for (var i = 0; i < preProcessedFileInfo.referencedFiles.length; i++) {
+                        var fileReference = preProcessedFileInfo.referencedFiles[i];
+                        CompilerDiagnostics.debugPrint("    file reference: " + fileReference.path);
+                    }
                     // resolve explicit references
                     for (var i = 0; i < preProcessedFileInfo.referencedFiles.length; i++) {
                         var fileReference = preProcessedFileInfo.referencedFiles[i];

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.

Friday, May 17, 2013

Updating Web-login for Javascript Webapp

I've been coding away for several days on some old web-login logic I had implemented in littleware to work in web applications with JSF-managed UX. I'm updating the code for use in apps where a javascript program manages the user experience. I've been thinking lately about the similarities and differences between the two application architectures.

The first difference between the two designs is in deployment. The old JSF architecture relied on JSF and JSP templates managed by the server, so I would usually bundle up the entire application in a .war file, and deploy it to some glassfish or tomcat or whatever container. The user interacted with the application by clicking links and submitting forms that moved the browser between web pages rendered by the server.

I'm trying to structure the newer code so that most client-side assets (javascript, CSS, hadlebar templates, ...) are served as static files from S3. The user's in-browser interactions with the application trigger events that are handled by javascript event-handlers. The javascript code running in the user's browser can access remote services via cross-origin AJAX requests. There are various CORS servlet filters open sourced online that take care of setting the HTTP response headers that browsers look for; today I grabbed this one posted by some Ebay coders to github - hopefully it works.

In the login code, both the old and new setups rely on a request filter (javax.servlet.Filter) to intercept unauthenticated client requests. The old code would intercept an unauthenticated page-load request, and forward the request on the server to a login page via RequestDispatcher.forward.

The new code intercepts unauthenticated AJAX requests to JSON services, and responds to the javascript client with an HTTP 401 (unauthorized) response, and relies upon the javascript code on the client to initiate a login process that eventually authenticates with the server by submitting credentials to a login AJAX service.

Finally - in the older applications I had the bad habit of tracking session state on the server via beans stashed in container-managed in-memory session. Tracking user data in-memory on a particular server required that a client continued to interact with the same server it began a session with. In-memory session state also made it difficult to partition a server's functionality between different endpoints. In other words - if I had a server that implemented some functionality "ABC", and I later decided that I would like to split the functionality on that server (or server cluster) between separate "A", "B", and "C" servers (or clusters), then it would be hard to do that if the code relied on shared in-memory session state.

Anyway, the new code avoids session state on the server - instead it relies on the client-side javascript code to track session state in most cases, and transient cookies in a few others.

In the end - I get to throw away a bunch of crazy server-side JSF beans and templates, and replace them with javascript modules and REST services.

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 ...

Sunday, July 24, 2011

Giving GWT a try

When I decided to implement a webapp browser for littleware's node database I began a debate with myself whether to implement the project in javascript or to give GWT a try. I have a little experience with javascript - I've enjoyed working with YUI on several projects, but javascript really sucks - no strict type checking, no module system, weirdo prototype object system ...

On the other hand - I feel like if I'm going to build a browser-based application, then I should develop with web technologies - javascript, HTML, CSS, ... It's great that GWT let's me develop in java, but it's a weird technology - not javascript, not really java - it's its own thing, but GWT is open source, has a community, is used and sponsored by Google (this blogger editor uses GWT), so might as well give it a try.

Anyway, I downloaded GWT and Eclipse, and was quickly running the demo application that the GWT Eclipse plugin creates in a new project. The demo is great for demonstrating GWT's remote service infrastructure and asyncrhonous support, but the simple UI assembly does not take advantage of GWT's new declarative UIBinder infrastructure. I want to use that UIBinder mojo, so I decided a good first task would be to refactor the demo to use UIBinder. I had a few false starts and a few different parts, but one part of the code replaces this html segment:


...
    <table align="center">
      <tr>
        <td colspan="2" style="font-weight:bold;">Please enter your name:</td>        
      </tr>
      <tr>
        <td id="nameFieldContainer"></td>
        <td id="sendButtonContainer"></td>
      </tr>
      <tr>
        <td colspan="2" style="color:red;" id="errorLabelContainer"></td>
      </tr>
    </table>
    ...
, and this java code:
	public void onModuleLoad() {
		final Button sendButton = new Button("Send");
		final TextBox nameField = new TextBox();
		nameField.setText("GWT User");
		final Label errorLabel = new Label();

		// We can add style names to widgets
		sendButton.addStyleName("sendButton");

		// Add the nameField and sendButton to the RootPanel
		// Use RootPanel.get() to get the entire body element
		RootPanel.get("nameFieldContainer").add(nameField);
		RootPanel.get("sendButtonContainer").add(sendButton);
		RootPanel.get("errorLabelContainer").add(errorLabel);
                ...
         }
with this UIBinder XML:
...
	<g:HTMLPanel>
		
    <table align="center">
      <tr>
        <td colspan="2" style="font-weight:bold;">Please enter your name:</td>        
      </tr>
      <tr>
        <td> <g:TextBox ui:field="nameField" /></td>
        <td> <g:Button ui:field="sendButton" /></td>
      </tr>
      <tr>
        <td colspan="2" style="color:red;"> <g:Label ui:field="errorLabel" /></td>
      </tr>
    </table>
		
	</g:HTMLPanel>
   ...
that pairs with this code:
public class DemoPanelView extends Composite {
	private static DemoPanelViewUiBinder uiBinder = GWT
			.create(DemoPanelViewUiBinder.class);

	interface DemoPanelViewUiBinder extends UiBinder<Widget, DemoPanelView> {
	}

	@UiField
	public Label errorLabel;
	@UiField
	public Button sendButton;	
	@UiField
	public TextBox  nameField;
...
Anyway I'm having fun with GWT, so I'll try to implement the database browser in GWT until I run into something that stops me ...