foam-framework
Version:
MVC metaprogramming framework
312 lines (256 loc) • 8.97 kB
JavaScript
/**
* @license
* Copyright 2014 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var $documents = [];
if ( window ) $documents.push(window.document);
// TODO: clean this up, hide $WID__ in closure
var $WID__ = 0;
function $addWindow(w) {
w.window.$WID = $WID__++;
$documents.push(w.document);
}
function $removeWindow(w) {
for ( var i = $documents.length - 1 ; i >= 0 ; i-- ) {
if ( ! $documents[i].defaultView || $documents[i].defaultView === w )
$documents.splice(i,1);
}
}
/** Replacement for getElementById **/
// TODO(kgr): remove this is deprecated, use X.$ instead()
var $ = function (id) {
console.log('Deprecated use of GLOBAL.$.');
for ( var i = 0 ; i < $documents.length ; i++ ) {
if ( document.FOAM_OBJECTS && document.FOAM_OBJECTS[id] )
return document.FOAM_OBJECTS[id];
var ret = $documents[i].getElementById(id);
if ( ret ) return ret;
}
return undefined;
};
/** Replacement for getElementByClassName **/
// TODO(kgr): remove this is deprecated, use X.$$ instead()
var $$ = function (cls) {
console.log('Deprecated use of GLOBAL.$$.');
for ( var i = 0 ; i < $documents.length ; i++ ) {
var ret = $documents[i].getElementsByClassName(cls);
if ( ret.length > 0 ) return ret;
}
return [];
};
var FOAM = function(map, opt_X, seq) {
var obj = JSONUtil.mapToObj(opt_X || X, map, undefined, seq);
return obj;
};
/**
* Register a lazy factory for the specified name within a
* specified context.
* The first time the name is looked up, the factory will be invoked
* and its value will be stored in the named slot and then returned.
* Future lookups to the same slot will return the originally created
* value.
**/
FOAM.putFactory = function(ctx, name, factory) {
ctx.__defineGetter__(name, function() {
console.log('Bouncing Factory: ', name);
delete ctx[name];
return ctx[name] = factory();
});
};
var USED_MODELS = {};
var UNUSED_MODELS = {};
var NONMODEL_INSTANCES = {}; // for things such as interfaces
FOAM.browse = function(model, opt_dao, opt_X) {
var Y = opt_X || X.sub(undefined, "FOAM BROWSER");
if ( typeof model === 'string' ) model = Y[model];
var dao = opt_dao || Y[model.name + 'DAO'] || Y[model.plural];
if ( ! dao ) {
Y[model.name + 'DAO'] = [].dao;
}
var ctrl = Y.foam.ui.DAOController.create({
model: model,
dao: dao,
useSearchView: false
});
if ( ! Y.stack ) {
var w = opt_X ? opt_X.window : window;
Y.stack = Y.foam.ui.StackView.create();
var win = Y.foam.ui.layout.Window.create({ window: w, data: Y.stack }, Y);
document.body.insertAdjacentHTML('beforeend', win.toHTML());
win.initHTML();
Y.stack.setTopView(ctrl);
} else {
Y.stack.pushView(ctrl);
}
};
var arequire = function(modelName) {
// If arequire is called as a global function, default to the
// top level context X.
var THIS = this === GLOBAL ? X : this;
var model = THIS.lookup(modelName);
if ( ! model ) {
if ( ! THIS.ModelDAO ) {
// if ( modelName !== 'Template' ) console.warn('Unknown Model in arequire: ', modelName);
return aconstant();
}
// check whether we have already hit the ModelDAO to load the model
if ( ! THIS.arequire$ModelLoadsInProgress ) {
THIS.set('arequire$ModelLoadsInProgress', {} );
} else {
if ( THIS.arequire$ModelLoadsInProgress[modelName] ) {
return THIS.arequire$ModelLoadsInProgress[modelName];
}
}
var future = afuture();
THIS.arequire$ModelLoadsInProgress[modelName] = future.get;
THIS.ModelDAO.find(modelName, {
put: function(m) {
// Contextualize the model for THIS context
m.X = THIS;
m.arequire()(function(m) {
THIS.arequire$ModelLoadsInProgress[modelName] = false;
THIS.GLOBAL.X.registerModel(m);
if ( ! THIS.lookupCache_[m.id] )
THIS.lookupCache_[m.id] = m;
future.set(m);
});
},
error: function() {
var args = argsToArray(arguments);
console.warn.apply(console, ['Could not load model: ', modelName].concat(args));
THIS.arequire$ModelLoadsInProgress[modelName] = false;
future.set(undefined);
}
});
return future.get;
}
return model.arequire();
}
var FOAM_POWERED = '<a style="text-decoration:none;" href="https://github.com/foam-framework/foam/" target="_blank">\
<font size=+1 face="catull" style="text-shadow:rgba(64,64,64,0.3) 3px 3px 4px;">\
<font color="#3333FF">F</font><font color="#FF0000">O</font><font color="#FFCC00">A</font><font color="#33CC00">M</font>\
<font color="#555555" > POWERED</font></font></a>';
/** Lookup a '.'-separated package path, creating sub-packages as required. **/
function packagePath(X, path) {
function packagePath_(Y, path, i) {
if ( i === path.length ) return Y;
if ( ! Y[path[i]] ) {
Y[path[i]] = {};
// console.log('************ Creating sub-path: ', path[i]);
if ( i == 0 ) GLOBAL[path[i]] = Y[path[i]];
}
return packagePath_(Y[path[i]], path, i+1);
}
return path ? packagePath_(X, path.split('.'), 0) : GLOBAL;
}
function registerModel(model, opt_name, fastMode) {
var root = model.package ? this : GLOBAL;
var name = model.name;
var pack = model.package;
if ( opt_name ) {
var a = opt_name.split('.');
name = a.pop();
pack = a.join('.');
}
var modelRegName = (pack ? pack + '.' : '') + name;
if ( root === GLOBAL || root === X ) {
var path = packagePath(root, pack);
if ( fastMode )
path[name] = model;
else
Object.defineProperty(path, name, { value: model, configurable: true });
}
if ( ! Object.hasOwnProperty.call(this, 'lookupCache_') ) {
this.lookupCache_ = Object.create(this.lookupCache_ || Object.prototype);
}
this.lookupCache_[modelRegName] = model;
this.onRegisterModel(model);
}
var CLASS = function(m) {
// Don't Latch these Models, as we know that we'll need them on startup
var EAGER = {
'Method': true,
'BooleanProperty': true,
'Action': true,
'FunctionProperty': true,
'Constant': true,
'Message': true,
'ArrayProperty': true,
'StringArrayProperty': true,
'Template': true,
'Arg': true,
'Relationship': true,
'ViewFactoryProperty': true,
'FactoryProperty': true,
'foam.ui.Window': true,
'StringProperty': true,
'foam.html.Element': true,
'Expr': true,
'AbstractDAO': true
};
/** Lazily create and register Model first time it's accessed. **/
function registerModelLatch(path, m) {
var id = m.package ? m.package + '.' + m.name : m.name;
if ( EAGER[id] ) {
USED_MODELS[id] = true;
var work = [];
var model = JSONUtil.mapToObj(X, m, Model, work);
if ( work.length > 0 ) {
model.extra__ = aseq.apply(null, work);
}
X.registerModel(model, undefined, true);
return model;
}
GLOBAL.lookupCache_[id] = undefined;
UNUSED_MODELS[id] = true;
var triggered = false;
//console.log("Model Getting defined: ", m.name, X.NAME);
Object.defineProperty(m.package ? path : GLOBAL, m.name, {
get: function triggerModelLatch() {
if ( triggered ) return null;
triggered = true;
// console.time('registerModel: ' + id);
USED_MODELS[id] = true;
UNUSED_MODELS[id] = undefined;
var work = [];
// console.time('buildModel: ' + id);
var model = JSONUtil.mapToObj(X, m, Model, work);
// console.timeEnd('buildModel: ' + id);
if ( work.length > 0 ) {
model.extra__ = aseq.apply(null, work);
}
X.registerModel(model);
// console.timeEnd('registerModel: ' + id);
return model;
},
configurable: true
});
}
if ( document && document.currentScript ) m.sourcePath = document.currentScript.src;
registerModelLatch(packagePath(X, m.package), m);
}
var MODEL = CLASS;
function INTERFACE(imodel) {
// Unless in DEBUG mode, just discard interfaces as they're only used for type checking.
// if ( ! DEBUG ) return;
var i = JSONUtil.mapToObj(X, imodel, Interface);
packagePath(X, i.package)[i.name] = i;
var id = i.package ? i.package + '.' + i.name : i.name;
NONMODEL_INSTANCES[id] = true;
}
/** Called when a Model is registered. **/
function onRegisterModel(m) {
// NOP
}