Framework Notes (apps)

While it would be nice to move the framework around get it arranged, maybe we can look into the framework itself to start getting some context of specifically what’s running and where. We have a couple of options of do we go alphabetically or do we just jump in and start looking at what’s interesting? To start out we don’t need to pick and choose because the first file App.js looks extremely interesting.

/* ***** LICENSE BLOCK *****
 * Version: GPL 3
 *
 * This program is Copyright (C) 2007-2008 Aptana, Inc. All Rights Reserved
 * This program is licensed under the GNU General Public license, version 3 (GPL).
 *
 * This program is distributed in the hope that it will be useful, but
 * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or
 * NONINFRINGEMENT. Redistribution, except as permitted by the GPL,
 * is prohibited.
 *
 * You can redistribute and/or modify this program under the terms of the GPL, 
 * as published by the Free Software Foundation.  You should
 * have received a copy of the GNU General Public License, Version 3 along
 * with this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 * 
 * Aptana provides a special exception to allow redistribution of this file
 * with certain other code and certain additional terms
 * pursuant to Section 7 of the GPL. You may view the exception and these
 * terms on the web at http://www.aptana.com/legal/gpl/.
 * 
 * You may view the GPL, and Aptana's exception and additional terms in the file
 * titled license-jaxer.html in the main distribution folder of this program.
 * 
 * Any modifications to this file must keep this entire header intact.
 *
 * ***** END LICENSE BLOCK ***** */

(function() {

    var log = Log.forModule("App");


    function App(props) {

        var defaultApp = Jaxer.Config.DEFAULT_APP || {};

        if (!props) throw new Exception("When constructing a new App, you must specify the props argument");

        if (log.getLevel() == Log.TRACE) log.trace("Constructing App from props: " + uneval(props) + " and a default app of: " + uneval(defaultApp));

        this.urlTest = props.urlTest || defaultApp.urlTest;
        this.name = props.name || defaultApp.name;
        this.appKey = props.appKey || props.name || defaultApp.appKey;
        this.pageKey = props.pageKey || defaultApp.pageKey;
        this.db = props.db || defaultApp.db;
        this.path = props.path || defaultApp.path;
        this.handler = props.handler || defaultApp.handler;
        this.init();

        for (var extraProp in props) {
            if (!(extraProp in this)) {
                this[extraProp] = props[extraProp];
            }
        }

    }

    App.prototype.init = function init() {

        var parsedUrl = (Jaxer.request ? Jaxer.request.parsedUrl : null);

        var _NAME = extractFunctionOrString(this.name, [parsedUrl]);
        this.__defineGetter__("NAME", function() {
            return _NAME;
        });

        log.trace("Initializing app '" + _NAME + "' (urlTest: " + this.urlTest + ")");

        var _APP_KEY = parsedUrl ? extractFunctionOrString(this.appKey, [parsedUrl]) : null;
        this.__defineGetter__("APP_KEY", function() {
            return _APP_KEY;
        });
        log.trace("APP_KEY for app '" + this.NAME + "': " + _APP_KEY);

        var _PAGE_KEY = parsedUrl ? extractFunctionOrString(this.pageKey, [parsedUrl]) : null;
        this.__defineGetter__("PAGE_KEY", function() {
            return _PAGE_KEY;
        });
        log.trace("PAGE_KEY for app '" + this.NAME + "': " + _PAGE_KEY);

        var _DB = extractFunctionOrObject(this.db, [_NAME, parsedUrl]);
        this.__defineGetter__("DB", function() {
            return _DB;
        });

        var _PATH = extractFunctionOrString(this.path, [_NAME, parsedUrl]);
        this.__defineGetter__("PATH", function() {
            return _PATH;
        });
        log.trace("PATH for app '" + this.NAME + "': " + _PATH);
        if (_PATH) this.initPath();

    }

    function extractFunctionOrString(obj, args) {
        switch (typeof obj) {
            case "function":
                return String(obj.apply(null, args));
            case "string":
                return obj;
            default:
                return null;
        }
    }

    function extractFunctionOrObject(obj, args) {
        switch (typeof obj) {
            case "function":
                return obj.apply(null, args);
            case "object":
                return obj;
            default:
                return null;
        }
    }

    App.prototype.initPath = function initPath() {
        if (this.PATH) {
            var path = Dir.resolve(this.PATH);
            var dir = new Dir(path);
            if (dir.exists) {
                if (!dir.isDir) {
                    throw new Exception("The path '" + path + "' for app '" + this.NAME + "' exists but is not a folder!");
                }
            } else if (Config.AUTO_CREATE_APP_PATHS) {
                log.trace("Creating or verifying hierarchy for app '" + this.NAME + "': " + path);
                dir.createHierarchy();
            }
        }
    }

    App.prototype.callHandler = function callHandler() {
        var _HANDLER;
        var parsedUrl = (Jaxer.request ? Jaxer.request.parsedUrl : null);
        switch (typeof this.handler) {
            case "function":
                _HANDLER = this.handler(this.NAME, parsedUrl);
                if (typeof _HANDLER != 'string') _HANDLER = null;
                break;
            case "string":
                _HANDLER = this.handler;
                break;
            default:
                _HANDLER = null;
        }
        this.__defineGetter__("HANDLER", function() {
            return _HANDLER;
        });
        log.trace("HANDLER for app '" + this.NAME + "': " + _HANDLER);
        return _HANDLER;
    }

    App.initStatic = function initStatic() {
        for (var appName in Config.appsByName) {
            var app = Config.appsByName[appName];
            app.initPath();
        }
    }

    frameworkGlobal.App = Jaxer.App = App;

    Log.trace("*** App.js loaded");

})();

The source code actually looks mercifully simple. We have a constructor function for the App prototype, which will use the default configuration as a base, and then replace any base functionality (name, urlTest, appKey, pageKey, db, path, handler). This means that it might be a good idea to get rid of the default app configuration from the configuration files and then include it in this function directly. That would simplify the file structure a little bit and the default app configuration is kind of hidden at the bottom of the file it’s defined in anyways, so that change could be made without too much modification to the current configuration.

As for the concept of Apps in general, I’ve very tempted to remove this functionality as much as possible as it seems like a better idea to host a single application on a single Jaxer instance and try not to overload Jaxer too much. However in the case of hosting multiple subdomains on the same server, it might actually be useful functionality.

For the ‘App’ functionality I think it can be minimized and left in for now, as the basic default application that is provided to the user and can be used as-is without changes. And then if this functionality can be taken advantage of in the future, then the ground-work can be there. Though in general I think the main way to take advantage of it is basically to provide a default database implementation for a specific path or URL, so the database connection parameters don’t need to be included under the public directory path.

Though a very similar effect can be achieved by defining a ‘connect.js’ file that is defined as ‘run-at=server’ and placed in a ‘jaxer-include’ folder where it’s needed. And this would provide almost exactly the same functionality as the concept of what Aptana defined as Apps, that would be a lot more simple and intuitive to use as opposed to having to edit the server configuration for the ‘App’ to be implemented. So in general I think it’s generally a good idea to continue to minimize this functionality.