UNPKG

foam-framework

Version:
1,720 lines (1,593 loc) 46.8 kB
/** * @license * Copyright 2013 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 FeatureSet = { create: function() { var obj = Object.create(this); obj.a_ = []; obj.version_ = 1; obj.parentVersion_ = 0; obj.names_ = {}; return obj; }, where: function(p) { return { __proto__: this, forEach: function(iterator) { return this.__proto__.forEach.call(this, function(f) { if ( p(f) ) iterator(f); }); }, localForEach: function(iterator) { return this.__proto__.localForEach.call(this, function(f) { if ( p(f) ) iterator(f); }); }, }; }, forEach: function(iterator) { var self = this; if ( this.parent ) this.parent.where(function(f) { return !f.name || !self.names_[f.name]; }).forEach(iterator); this.localForEach(iterator); }, localForEach: function(iterator) { for ( var i = 0; i < this.a_.length; i++ ) { var f = this.a_[i]; if ( f.name && f !== this.names_[f.name] ) continue; iterator(this.a_[i]); } }, add: function(a) { if ( a.name ) this.names_[a.name] = a; this.a_.push(a); this.version_++; }, get parent() { return this.parent_; }, set parent(p) { this.parent_ = p; }, get version() { return this.version_; } }; function defineLocalProperty(cls, name, factory) { Object.defineProperty(cls, name, { get: function() { if ( this == cls ) return null; var value = factory.call(this); Object.defineProperty(this, name, { value: value }); return value; } }); } Object.defineProperty(Object.prototype, 'addFeature', { configurable: true, enumerable: false, writable: true, value: function(f) { var model = Installer.create(this); var proto = Installer.create(this.prototype || this); f.install(model, proto); } }); Object.defineProperty(Object.prototype, 'defineFOAMProperty', { configurable: true, enumerable: false, writable: true, value: function(name, definition) { definition.configurable = true; Object.defineProperty(this, name, definition); } }); function trampoline(name, valueFn) { return { configurable: true, get: function() { this.defineFOAMProperty(name, valueFn.call(this)); return this[name]; }, set: function(value) { this.defineFOAMProperty(name, valueFn.call(this)); this[name] = value; } }; }; var Installer = { create: function(target) { return { __proto__: this, target: target }; }, define: function(name, definition) { definition.configurable = true; this.target.defineFOAMProperty(name, definition); }, setParent: function(proto) { this.target.__proto__ = proto; }, addMethod: function(name, code) { this.define(name, { writable: true, value: code }); } }; var LazyInstaller = { __proto__: Installer, create: function(delegate) { return { __proto__: this, delegate: delegate }; }, define: function(name, definition) { this.delegate.define(name, trampoline(name, function() { console.log("Trampolining ", name); return definition })); }, setParent: function(parent) { // No lazy way to do this. this.delegate.setParent(parent); } }; var LoggingInstaller = { __proto__: Installer, create: function(delegate) { return { __proto__: this, delegate: delegate }; }, define: function(name, definition) { console.log("Installing: ", name); this.delegate.define(name, definition); }, setParent: function(parent) { console.log("Setting parent ", parent); this.delegate.setParent(parent); } }; /** * Override a method, making calling the overridden method possible by * calling this.SUPER(); **/ function override(super_, method) { var SUPER = function() { return super_.apply(this, arguments); }; var f = function() { var OLD_SUPER = this.SUPER; this.SUPER = SUPER; try { return method.apply(this, arguments); } finally { this.SUPER = OLD_SUPER; } }; f.super_ = super_; return f; } function bootstrap(scope) { function simpleProperty(obj, name) { Object.defineProperty(obj, name, { configurable: true, enumerable: false, get: function() { return this.instance_[name]; }, set: function(v) { this.instance_[name] = v; } }); } // Make function objects act like Method instances. Object.defineProperty(Function.prototype, 'code', { configurable: true, enumerable: true, get: function() { return this; }, }); var Model = { instance_: {} }; scope.set('Model', Model); Model.prototype_ = { model_: Model, addFeature: function(f) { this.propertyMap_ = null; if ( ! this.prototype_ ) { this.prototype_ = { model_: this, name_: this.name, TYPE: this.name + "Prototype", }; var doAll = true; } if ( ! this.modelInstaller_ ) { this.modelInstaller_ = Installer.create(this); this.modelInstaller_.setParent = function(parent) { self.__proto__ = parent; self.features.parent = parent.features; }; } if ( ! this.protoInstaller_ ) this.protoInstaller_ = Installer.create(this.prototype_); var self = this; var model = this.modelInstaller_; var proto = this.protoInstaller_; var prototype = this.prototype_; if ( doAll ) { this.features.localForEach(function(f) { f.install(model, proto, prototype); }); } this.features.add(f); f.install(model, proto, this.prototype_); }, create: function(args) { var proto = this.getPrototype(); if ( this.model_ === this ) return proto.__proto__.create.call(proto, args); return proto.create(args); }, getPrototype: function() { return this.prototype_; } }; Model.__proto__ = Model.prototype_; simpleProperty(Model.prototype_, "features"); Model.features = FeatureSet.create(); Model.prototype_.version_ = Model.features.version; var FObject = { instance_: {} }; scope.set('FObject', FObject); FObject.__proto__ = Model.getPrototype(); FObject.features = FeatureSet.create(); FObject.features.add({ name: 'HACK FEATURE, add property change support to FObject, these should all be features later.', install: function(model, proto) { proto.setParent(PropertyChangeSupport); } }); FObject.prototype_ = { __proto__: PropertyChangeSupport, model_: FObject, version_: FObject.features.version, create: function(args, opt_X) { var obj = Object.create(this); obj.instance_ = {}; if ( opt_X ) obj.X = opt_X; if ( args instanceof Object ) obj.copyFrom(args); obj.init(args); return obj; }, copyFrom: function(args) { var self = this; this.model_.features.forEach(function(f) { f.copy && f.copy(self, args); }); return this; }, init: function() { var self = this; this.model_.features.forEach(function(f) { f.initialize && f.initialize(self); }); } }; var Extends = { instance_: {} }; scope.set('Extends', Extends); Extends.__proto__ = Model.getPrototype(); Extends.features = FeatureSet.create(); Extends.prototype_ = { __proto__: FObject.getPrototype(), version_: Extends.features.version, model_: Extends, install: function(model, proto) { var parent = get(this.parent); proto.setParent(parent.getPrototype()); model.setParent(parent); } }; simpleProperty(Extends.prototype_, 'parent'); var Method = { instance_: {} }; scope.set('Method', Method); Method.__proto__ = Model.getPrototype(); Method.features = FeatureSet.create(); Method.prototype_ = { __proto__: FObject.getPrototype(), model_: Method, version_: Method.features.version, install: function(model, proto, prototype) { var code = this.code; if ( prototype.__proto__[this.name] ) code = override(prototype.__proto__[this.name], this.code); proto.addMethod(this.name, code); } }; simpleProperty(Method.prototype_, "name"); simpleProperty(Method.prototype_, "code"); var Property = { instance_: {} }; scope.set('Property', Property); Property.__proto__ = Model.getPrototype(); Property.features = FeatureSet.create(); Property.prototype_ = { __proto__: FObject.getPrototype(), model_: Property, version_: Property.features.version, install: function(model, proto) { var name = this.name; var factory = this.factory; model.define(this.name.constantize(), { value: this, }); proto.define(this.name, { configurable: true, enumerable: true, get: function() { if ( ! this.instance_[name] ) { if ( factory ) return this.instance_[name] = factory.call(this); return ""; } return this.instance_[name]; }, set: function(v) { this.instance_[name] = v; } }); }, initialize: function initialize(obj) { if ( this.factory && ! obj.instance_[this.name] ) obj[this.name] = this.factory.call(obj); }, copy: function(obj, args) { // Don't copy default values. if ( !args ) return; if ( this.name === "property" ) debugger; if ( args.instance_ && !args.instance_.hasOwnProperty(this.name) ) return; if ( this.name in args ) obj[this.name] = args[this.name]; } }; simpleProperty(Property.prototype_, "name"); simpleProperty(Property.prototype_, "factory"); Property.prototype_.copyFrom = override( FObject.prototype_.copyFrom, function(args) { this.SUPER(args); for ( var key in args ) { if ( key === "property" ) debugger; this.instance_[key] = args[key]; } }); function forceInstall(feature, model) { model.addFeature(feature); } // Set all initial models to extends FObject. var tmp = Extends.create(); tmp.parent = 'FObject'; forceInstall(tmp, Model); tmp = Extends.create(); tmp.parent = 'FObject'; forceInstall(tmp, Property); tmp = Extends.create(); tmp.parent = 'FObject'; forceInstall(tmp, Extends); tmp = Extends.create(); tmp.parent = 'FObject'; forceInstall(tmp, Method); // Bootstreap the real property and method features we put in earlier. tmp = Property.create(); tmp.name = "name" forceInstall(tmp, Property); tmp = Property.create({ name: 'factory' }); forceInstall(tmp, Property); tmp = Property.create({ name: 'name', }); forceInstall(tmp, Model); tmp = Property.create({ name: 'parent' }); forceInstall(tmp, Extends); tmp = Property.create({ name: 'features', factory: function() { var features = FeatureSet.create(); features.add(Extends.create({ parent: 'FObject' })); return features; } }); forceInstall(tmp, Model); Model.name = 'Model'; FObject.name = 'FObject'; Property.name = 'Property'; Method.name = 'Method'; Extends.name = 'Extends'; tmp = Property.create({ name: 'name', }); forceInstall(tmp, Method); tmp = Property.create({ name: 'code', }); forceInstall(tmp, Method); function upgradeMethod(model, name) { var tmp = Method.create({ name: name, code: model.prototype_[name] }); forceInstall(tmp, model); } upgradeMethod(Method, 'install'); upgradeMethod(Model, 'create'); upgradeMethod(Model, 'getPrototype'); upgradeMethod(Model, 'addFeature'); upgradeMethod(Property, 'install'); upgradeMethod(Property, 'initialize'); upgradeMethod(Property, 'copy'); upgradeMethod(Extends, 'install'); upgradeMethod(FObject, 'create'); upgradeMethod(FObject, 'copyFrom'); upgradeMethod(FObject, 'init'); }; var featureDAO = [ ['Model', 'Method', function install(model, proto) { proto.define(this.name, { value: this }); }], ['Model', 'Method', function isSubModel(model) { try { return model && ( model === this || this.isSubModel(model.getPrototype().__proto__.model_) ); } catch (x) { return false; } }], ['Model', 'Method', function getPropertyWithoutCache_(name) { var p = null; this.features.forEach(function(f) { if ( Property.isInstance(f) && f.name === name ) p = f; }); return p; }], ['Model', 'Method', function getProperty(name) { if ( ! Object.prototype.hasOwnProperty.call(this, 'propertyMap_') || ! this.propertyMap_ ) { var m = {}; this.features.forEach(function(f) { if ( Property.isInstance(f) && f.name === name ) { m[f.name] = f; } }); this.propertyMap_ = m; } return this.propertyMap_[name]; }], ['Model', 'Method', function hashCode() { var string = ""; var self = this; this.features.forEach(function(f) { string += f.f(self.toString()); }); return string.hashCode(); }], ['Model', 'Method', function isInstance(obj) { return obj && obj.model_ && this.isSubModel(obj.model_); }], ['FObject', 'Method', function toString() { return this.model_.name + "Prototype" }], ['FObject', 'Method', function hasOwnProperty(name) { return typeof this.instance_[name] !== 'undefined'; }], ['FObject', 'Method', function equals(other) { return this.compareTo(other) == 0; }], ['FObject', 'Method', function compareTo(other) { var ps = this.model_.properties; for ( var i = 0 ; i < ps.length ; i++ ) { var r = ps[i].compare(this, other); if ( r ) return r; } return 0; }], ['FObject', 'Method', function diff(other) { var diff = {}; for ( var i = 0, property; property = this.model_.properties[i]; i++ ) { if ( Array.isArray(property.f(this)) ) { var subdiff = property.f(this).diff(property.f(other)); if ( subdiff.added.length !== 0 || subdiff.removed.length !== 0 ) { diff[property.name] = subdiff; } continue; } if ( property.f(this).compareTo(property.f(other)) !== 0) { diff[property.name] = property.f(other); } } return diff; }], ['FObject', 'Method', function clearProperty(name) { delete this.instance_[name]; }], ['FObject', 'Method', function hashCode() { var hash = 17; for ( var i = 0; i < this.model_.properties.length ; i++ ) { var prop = this[this.model_.properties[i].name]; var code = ! prop ? 0 : prop.hashCode ? prop.hashCode() : prop.toString().hashCode(); hash = ((hash << 5) - hash) + code; hash &= hash; } return hash; }], ['FObject', 'Method', function clone() { var c = Object.create(this.__proto__); c.instance_ = {}; for ( var key in this.instance_ ) { var value = this[key]; c[key] = Array.isArray(value) ? value.clone() : value; } return c; // return ( this.model_ && this.model_.create ) ? this.model_.create(this) : this; }], ['FObject', 'Method', function deepClone() { var cln = this.clone(); // now clone inner collections for ( var key in cln.instance_ ) { var val = cln.instance_[key]; if ( Array.isArray(val) ) { for ( var i = 0 ; i < val.length ; i++ ) { var obj = val[i]; obj = obj.deepClone(); } } } return cln; }], ['FObject', 'Method', function output(out) { return JSONUtil.output(out, this); }], ['FObject', 'Method', function toJSON() { return JSONUtil.stringify(this); }], ['FObject', 'Method', function toXML() { return XMLUtil.stringify(this); }], ['FObject', 'Method', function write(document) { var view = DetailView.create({model: this.model_, showActions: true}); document.writeln(view.toHTML()); view.value.set(this); view.initHTML(); }], ['Property', 'Property', { name: 'defaultValue' }], ['Property', 'Property', { name: 'factory' }], ['Property', 'Property', { name: 'scope', defaultValue: 'instance_' }], ['Property', 'Property', { name: 'defaultValueFn' }], ['Property', 'Property', { name: 'scopeName' }], ['Property', 'Property', { name: 'postSet' }], ['Property', 'Property', { name: 'preSet' }], ['Property', 'Property', { name: 'getter' }], ['Property', 'Property', { name: 'setter' }], ['Property', 'Method', function install(model, proto, prototype) { var prop = this; var parent = model.extendsModel; if ( parent && (parent = get(parent)) ) { var superProp = parent.getProperty(this.name); if ( superProp ) { prop = superProp.clone().copyFrom(prop); } } var scope = prop.scope || 'instance_'; var scopeName = prop.scopeName || prop.name; var name = prop.name; var defaultValueFn = prop.defaultValueFn; var defaultValue = prop.defaultValue; var factory = prop.factory; var preSet = prop.preSet; var postSet = prop.postSet; // TODO: add caching? if ( ! prototype.__lookupGetter__(name + '$') ) { proto.define(name + '$', { configurable: true, get: function() { return this.propertyValue(name); }, set: function(value) { Events.link(value, this.propertyValue(name)); } }); } model.define(scopeName.constantize(), { value: prop, writable: true }); var definition = {}; if ( prop.getter ) { definition.get = prop.getter; } else { var get = defaultValueFn ? (function() { if ( this[scope][scopeName] === undefined ) { if ( factory ) return this[scope][scopeName] = factory.call(this); return defaultValueFn.call(this, prop); } return this[scope][scopeName]; }) : (function() { if ( this[scope][scopeName] === undefined ) { if ( factory ) return this[scope][scopeName] = factory.call(this); return defaultValue; } return this[scope][scopeName] }); definition.get = (function(get) { var stack = Events.onGet.stack; return function() { var value = get.call(this); var f = stack[0]; f && f(this, name, value); return value; }; })(get); } if ( prop.setter ) { definition.set = prop.setter; } else { var set = function(oldValue, newValue) { this.instance_[name] = newValue; }; if ( prop.type === 'int' || prop.type === 'float' ) { set = (function(set) { return function(oldValue, newValue) { set.call(this, oldValue, typeof newValue !== 'number' ? Number(newValue) : newValue); }; })(set); } if ( prop.postSet ) { set = (function(set, postSet) { return function(oldValue, newValue) { set.call(this, oldValue, newValue); postSet.call(this, oldValue, newValue) }; })(set, prop.postSet); } var propertyTopic = PropertyChangeSupport.propertyTopic(name); set = (function(set) { return function(oldValue, newValue) { set.call(this, oldValue, newValue); this.propertyChange_(propertyTopic, oldValue, newValue); }; })(set); if ( prop.preSet ) { set = (function(set, preSet) { return function(oldValue, newValue) { set.call(this, oldValue, preSet.call(this, oldValue, newValue, prop)); }; })(set, prop.preSet); } set = (function(set) { return function(newValue) { set.call(this, this[name], newValue); }; })(set); set = (function(set) { var stack = Events.onSet.stack; return function(newValue) { var f = stack[0]; if ( f && ! f(this, name, newValue) ) return; set.call(this, newValue); }; })(set); definition.set = set; } proto.define(name, definition); if ( scope === "static_" && prop.factory ) { prototype[prop.name] = prop.factory(); } }], ['Model', 'Property', { name: 'ids', type: 'Array', subType: 'String', defaultValueFn: function() { var prop; this.features.forEach(function(f) { if ( !prop && Property.isInstance(f) ) { prop = f; } }); return prop ? [prop.name] : []; } }], [null, 'Model', { name: 'IdFeature' }], ['IdFeature', 'Method', function install(model, proto, prototype) { proto.define('id', trampoline('id', function() { var primaryKey = this.model_.ids; if ( primaryKey.length === 0 ) return []; if ( primaryKey.length === 1 ) { return { get: function() { return this[primaryKey[0]]; }, set: function(val) { this[primaryKey[0]] = val; } }; } else if (primaryKey.length > 1) { return { get: function() { return primaryKey.map(function(key) { return this[key]; }); }, set: function(val) { primaryKey.map(function(key, i) { this[key] = val[i]; }); } }; } })); }], ['FObject', 'IdFeature'], [null, 'Model', { name: 'ContextFeature' }], ['ContextFeature', 'Method', function install(model, proto) { proto.define('X', { value: X, writable: true }); }], ['FObject', 'ContextFeature'], [null, 'Model', { name: 'Constant' }], ['Constant', 'Property', { name: 'name' }], ['Constant', 'Property', { name: 'value' }], ['Constant', 'Method', function install(model, proto) { var value = this.value; proto.define(this.name, { enumerable: true, get: function(v) { return value; }, set: function(v) { console.warn('Changing constant value'); value = v; } }); }], ['Extends', 'Method', function create(args) { if ( typeof args === "string" ) return this.SUPER({ parent: args }); return this.SUPER(args); }], /* [null, 'Model', { name: 'Extends' }], ['Extends', 'Property', { name: 'parent' }], ['Extends', 'Method', function install(model, proto) { var parent = get(this.parent); proto.__proto__ = parent.getPrototype(); model.features.parent = parent.features; // TODO: Is this correct? model.__proto__ = parent; }],*/ ['Property', 'Method', function f(obj) { return obj[this.name] || obj; }], ['Property', 'Property', { name: 'label' }], ['Property', 'Property', { name: 'tableLabel' }], ['Property', 'Property', { name: 'type' }], ['Property', 'Property', { name: 'javaType', defaultValueFn: function() { return this.type; } }], ['Property', 'Property', { name: 'javascriptType', defaultValueFn: function() { return this.type; } }], ['Property', 'Property', { name: 'shortName' }], ['Property', 'Property', { name: 'aliases', factory: function() { return []; } }], ['Property', 'Property', { name: 'mode', defaultValue: 'read-write' }], ['Property', 'Property', { name: 'subType' }], ['Property', 'Property', { name: 'units' }], ['Property', 'Property', { name: 'required', defaultValue: true }], ['Property', 'Property', { name: 'hidden', defaultValue: false }], ['Property', 'Property', { name: 'transient', defaultValue: false }], ['Property', 'Property', { name: 'displayWidth', defaultValue: 30 }], ['Property', 'Property', { name: 'displayHeight', defaultvalue: 1 }], ['Property', 'Property', { name: 'view', defaultValue: 'foam.ui.TextFieldView' }], ['Property', 'Property', { name: 'detailViewPreRow', defaultValue: function() { return ""; } }], ['Property', 'Property', { name: 'defaultViewPostRow', defaultValue: function() { return ""; } }], ['Property', 'Property', { name: 'tableFormatter' }], ['Property', 'Property', { name: 'summaryFormatter' }], ['Property', 'Property', { name: 'tableWidth' }], ['Property', 'Property', { name: 'prototag' }], ['Property', 'Property', { name: 'actionFactory' }], ['Property', 'Property', { name: 'compareProperty', defaultValue: function(o1, o2) { return (o1.localeCompare || o1.compareTo).call(o1, o2); } }], ['Property', 'Method', function compare(o1, o2) { return this.compareProperty(this.f(o1), this.f(o2)); }], ['Property', 'Method', function toSQL() { return this.name; }], ['Property', 'Method', function toMQL() { return this.name; }], // Types [null, 'Model', { name: 'StringProperty' }], ['StringProperty', 'Extends', 'Property'], ['Property', 'StringProperty', { name: 'help', help: 'Help text associated with the property.' }], ['Property', 'StringProperty', { name: 'name', help: 'The coding identifier for the property.' }], ['Property', 'StringProperty', { name: 'label', help: 'The display label for the feature.', defaultValueFn: function() { return this.name.labelize(); } }], ['Model', 'StringProperty', { name: 'tableLabel', help: 'The table display label for the feature.', defaultValueFn: function() { return this.name.labelize(); } }], ['Property', 'StringProperty', { name: 'type', required: true, help: 'The FOAM type of this property.' }], ['StringProperty', 'StringProperty', { name: 'type', defaultValue: 'String', }], ['Property', 'StringProperty', { name: 'javaType', help: 'The Java type of this property.' }], ['StringProperty', 'StringProperty', { name: 'javaType', defaultValue: 'String' }], [null, 'Model', { name: 'IntProperty' }], ['IntProperty', 'Extends', 'Property'], ['IntProperty', 'StringProperty', { name: 'type', defaultValue: 'Int' }], ['IntProperty', 'StringProperty', { name: 'javaType', defaultValue: 'int' }], ['IntProperty', 'IntProperty', { name: 'defaultValue', defaultValue: 0 }], ['Property', 'IntProperty', { name: 'displayWidth', defaultValue: 30, help: 'The display width of the property.' }], ['Property', 'IntProperty', { name: 'displayHeight', defaultValue: 1, help: 'The display height of the property.' }], ['IntProperty', 'IntProperty', { name: 'displayWidth', defaultValue: 8 }], ['StringProperty', 'IntProperty', { name: 'displayHeight', displayWidth: 8, defaultValue: 1, }], [null, 'Model', { name: 'BooleanProperty' }], ['BooleanProperty', 'Extends', 'Property'], ['BooleanProperty', 'StringProperty', { name: 'type', defaultValue: 'Boolean' }], ['BooleanProperty', 'StringProperty', { name: 'javaType', defaultValue: 'Boolean' }], ['BooleanProperty', 'StringProperty', { name: 'view', defaultValue: 'foam.ui.BooleanView' }], ['Property', 'BooleanProperty', { name: 'required', defaultValue: false }], [null, 'Model', { name: 'FunctionProperty' }], ['FunctionProperty', 'Extends', 'Property'], ['Property', 'FunctionProperty', { name: 'defaultValueFn', help: 'The property\'s default value function.' }], ['Property', 'FunctionProperty', { name: 'factory', help: 'Factory for creating inital value when object instantiated.' }], ['FunctionProperty', 'StringProperty', { name: 'type', defaultValue: 'Function' }], ['FunctionProperty', 'StringProperty', { name: 'view', defaultValue: 'foam.ui.FunctionView' }], ['Property', 'FunctionProperty', { name: 'preSet', help: 'An adapter function called before normal setter logic.' }], ['Property', 'FunctionProperty', { name: 'postSet', help: 'A function called after normal setter logic, but before property change event fired.' }], ['Property', 'FunctionProperty', { name: 'setter', help: 'The property\'s setter function.' }], ['Property', 'FunctionProperty', { name: 'getter', help: 'The prpoerty\'s getter function.' }], ['Property', 'FunctionProperty', { name: 'tableFormatter', label: 'Table View Cell Formatter', help: 'Function to format value for display in TableView' }], ['Property', 'FunctionProperty', { name: 'summaryFormatter', label: 'Summary View Formatter', help: 'Function to format value for display in SummarView' }], [null, 'Model', { name: 'ArrayProperty' }], ['ArrayProperty', 'Extends', 'Property'], ['ArrayProperty', 'StringProperty', { name: 'type', defaultValue: 'Array' }], ['ArrayProperty', 'StringProperty', { name: 'subType', help: 'The FOAM sub-type of this property' }], ['ArrayProperty', 'FunctionProperty', { name: 'preSet', defaultValue: function(oldValue, value, prop) { var m = get(prop.subType); if ( ! m || ! m.model_ ) { return value; } for ( var i = 0; i < value.length; i++ ) { value[i] = value[i].model_ ? FOAM(value[i]) : m.create(value[i]); } return value; } }], ['ArrayProperty', 'StringProperty', { name: 'javaType', defaultValueFn: function(p) { return p.subType + '[]'; } }], ['ArrayProperty', 'StringProperty', { name: 'view', defaultvlaue: 'foam.ui.ArrayView' }], ['ArrayProperty', 'FunctionProperty', { name: 'factory', defaultValue: function() { return []; } }], [null, 'Model', { name: 'ReferenceProperty', help: 'A foreign key reference to another Entity.' }], ['ReferenceProperty', 'Extends', 'Property'], ['ReferenceProperty', 'StringProperty', { name: 'type', defaultValue: 'Reference' }], ['ReferenceProperty', 'StringProperty', { name: 'javaType', // TODO: should obtain primary-key type from subType defaultValueFn: function(p) { return 'Object'; }, }], ['ReferenceProperty', 'StringProperty', { name: 'view', // TODO: should be 'KeyView' defaultValue: 'foam.ui.TextFieldView' }], [null, 'Model', { name: 'StringArrayProperty', help: 'An array of String values.' }], ['StringArrayProperty', 'Extends', 'ArrayProperty'], ['StringArrayProperty', 'StringProperty', { name: 'subType', defaultValue: 'String' }], ['StringArrayProperty', 'StringProperty', { name: 'view', defaultValue: 'foam.ui.StringArrayView' }], [null, 'Model', { name: 'DateProperty' }], ['DateProperty', 'Extends', 'Property'], ['DateProperty', 'StringProperty', { name: 'type', defaultValue: 'Date', }], ['DateProperty', 'StringProperty', { name: 'javaType', defaultValue: 'Date' }], ['DateProperty', 'StringProperty', { name: 'view', defaultValue: 'DateFieldView' }], ['DateProperty', 'FunctionProperty', { name: 'preSet', defaultValue: function(d) { return typeof d === 'string' ? new Date(d) : d; } }], ['DateProperty', 'FunctionProperty', { name: 'tableFormatter', defaultValue: function(d) { var now = new Date(); var seconds = Math.floor((now - d)/1000); if (seconds < 60) return 'moments ago'; var minutes = Math.floor((seconds)/60); if (minutes == 1) { return '1 minute ago'; } else if (minutes < 60) { return minutes + ' minutes ago'; } else { var hours = Math.floor(minutes/60); if (hours < 24) { return hours + ' hours ago'; } var days = Math.floor(hours / 24); if (days < 7) { return days + ' days ago'; } else if (days < 365) { var year = 1900+d.getYear(); var noyear = d.toDateString().replace(" " + year, ""); return /....(.*)/.exec(noyear)[1]; } } return d.toDateString(); } }], [null, 'Model', { name: 'DateTimeProperty' }], ['DateTimeProperty', 'Extends', 'DateProperty'], ['DateTimeProperty', 'StringProperty', { name: 'type', defaultValue: 'datetime' }], ['DateTimeProperty', 'StringProperty', { name: 'view', defaultValue: 'foam.ui.DateTimeFieldView' }], [null, 'Model', { name: 'FloatProperty' }], ['FloatProperty', 'Extends', 'Property'], ['FloatProperty', 'StringProperty', { name: 'type', defaultValue: 'Float' }], ['FloatProperty', 'StringProperty', { name: 'javaType', defaultValue: 'double' }], ['FloatProperty', 'IntProperty', { name: 'displayWidth', defaultValue: 15 }], [null, 'Model', { name: 'ReferenceArrayProperty' }], ['ReferenceArrayProperty', 'Extends', 'StringArrayProperty'], [null, 'Model', { name: 'EMailProperty' }], ['EMailProperty', 'Extends', 'StringProperty'], [null, 'Model', { name: 'URLProperty' }], ['URLProperty', 'Extends', 'StringProperty'], ['Property', 'FunctionProperty', { name: 'detailViewPreRow', defaultValue: function() { return ""; }, help: 'Inject HTML before row in DetailView.' }], ['Property', 'FunctionProperty', { name: 'detailViewPostRow', defaultValue: function() { return ""; }, help: 'Inject HTML before row in DetailView.' }], ['Model', 'StringProperty', { name: 'label', defaultValueFn: function() { return this.name.labelize(); } }], ['Model', 'StringProperty', { name: 'plural', help: 'The plural form of this model\'s name.', defaultValueFn: function() { return this.name + 's'; } }], // mm4Methods [null, 'Model', { name: 'Action' }], ['Action', 'StringProperty', { name: 'name', help: 'The coding identifier for the action.', }], ['Action', 'StringProperty', { name: 'label', help: 'The display label for the action.', defaultValueFn: function() { return this.name.labelize(); } }], ['Action', 'StringProperty', { name: 'help', label: 'Help Text', help: 'Help text associated with the action.' }], ['Action', 'BooleanProperty', { name: 'default', defaultValue: false, help: 'Indicates if this is the default action.' }], ['Action', 'FunctionProperty', { name: 'isAvailable', label: 'Available', defaultValue: function() { return true; }, help: 'Function to determine if action is enabled.' }], ['Action', 'FunctionProperty', { name: 'isEnabled', label: 'Enabled', defaultValue: function() { return true; }, help: 'Function to determine if action is enabled' }], ['Action', 'FunctionProperty', { name: 'labelFn', label: 'Label Function', defaultValue: function(action) { return action.label; }, help: "Function to determine label. Defaults to 'this.label'." }], ['Action', 'URLProperty', { name: 'iconUrl', defaultValue: undefined, help: 'Provides a url for an icon to render for this action.' }], ['Action', 'BooleanProperty', { name: 'showLabel', defaultValue: true, help: 'Property indicating whether the label should be rendered along side the icon.' }], ['Action', 'ArrayProperty', { name: 'children', subType: 'Action', help: 'Child actions of this action.', persistent: false }], ['Action', 'ReferenceProperty', { name: 'parent', help: 'The parent action of this action.' }], ['Action', 'FunctionProperty', { name: 'action', displayWidth: 80, displayHeight: 20, help: 'Function to implement action.', }], ['Action', 'Method', function maybeCall(that) { if ( this.isEnabled.call(that) ) this.action.call(that, this); }], ['Action', 'Method', function install(model, proto) { var a = this; proto.define(this.name, { value: function() { a.maybeCall(this); }, writable: true }); model.define(this.name.constantize(), { value: this, writable: true }); }], [null, 'Model', { name: 'Arg' }], ['Arg', 'Property', { name: 'type', type: 'String', required: true, displayWidth: 30, displayHeight: 1, defaultValue: 'Object', help: 'The type of this argument.' }], ['Arg', 'Property', { name: 'javaType', type: 'String', required: false, defaultValueFn: function() { return this.type; }, help: 'The java type that represents the type of this property.' }], ['Arg', 'Property', { name: 'javascriptType', type: 'String', required: false, defaultValueFn: function() { return this.type; }, help: 'The javascript type that represents the type of this property.' }], ['Arg', 'Property', { name: 'name', type: 'String', required: true, displayWidth: 30, displayHeight: 1, defaultValue: '', help: 'The coding identifier for the entity.' }], ['Arg', 'Property', { model_: 'BooleanProperty', name: 'required', defaultValue: true }], ['Arg', 'Property', { name: 'defaultValue', help: 'Default Value if not required and not provided.' }], ['Arg', 'Property', { name: 'description', type: 'String', displayWidth: 70, displayHeight: 1, defaultValue: '', help: 'A brief description of this topic.' }], ['Arg', 'Property', { name: 'help', label: 'Help Text', type: 'String', displayWidth: 70, displayHeight: 6, defaultValue: '', help: 'Help text associated with the entity.' }], ['Arg', 'Method', function decorateFunction(f, i) { if ( this.type === 'Object' ) return f; var type = this.type; return this.required ? function() { if ( arguments[i] === undefined ) { console.assert(false, 'Missing required argument# ' + i); debugger; } if ( typeof arguments[i] !== type ) { console.assert(false, 'argument# ' + i + ' type expected to be ' + type + ', but was ' + (typeof arguments[i]) + ': ' + arguments[i]); debugger; } return f.apply(this, arguments); } : function() { if ( arguments[i] !== undefined && typeof arguments[i] !== type ) { console.assert(false, 'argument# ' + i + ' type expected to be ' + type + ', but was ' + (typeof arguments[i]) + ': ' + arguments[i]); debugger; } return f.apply(this, arguments); } ; }], [null, 'Model', { name: 'Listener' }], ['Listener', 'Extends', 'Method'], ['Listener', 'Property', { name: 'isMerged', help: 'Should this listener be merged?' }], ['Listener', 'BooleanProperty', { name: 'isFramed', help: 'As a listener, should this be animated?', defaultValue: false }], ['Listener', 'Method', function install(model, proto) { var name = this.name; var fn = this.code; var isFramed = this.isFramed; var isMerged = this.isMerged; proto.define(name, trampoline(name, function() { var l = fn.bind(this); if ( isFramed ) l = EventService.framed(l); else if ( isMerged ) l = EventService.merged(l, (isMerged === true) ? undefined : isMerged); return { value: l, writable: true }; })); }], [null, 'Model', { name: 'Template', tableProperties: [ 'name', 'description' ] }], ['Template', 'Property', { name: 'name', type: 'String', required: true, displayWidth: 30, displayHeight: 1, defaultValue: '', help: 'The template\'s unique name.' }], ['Template', 'Property', { name: 'description', type: 'String', required: true, displayWidth: 70, displayHeight: 1, defaultValue: '', help: 'The template\'s unique name.' }], ['Template', 'Property', { name: 'template', type: 'String', displayWidth: 180, displayHeight: 30, rows: 30, cols: 80, defaultValue: '', view: 'foam.ui.TextAreaView', help: 'Template text. <%= expr %> or <% out(...); %>' }], /*['Template', 'Property', { name: 'templates', type: 'Array[Template]', subType: 'Template', view: 'foam.ui.ArrayView', help: 'Sub-templates of this template.' }]*/ ['Template', 'Method', function install(model, proto, prototype) { var t = this; if ( ! t.template ) { var future = afuture(); t.futureTemplate = future.get; var path = document.currentScript.src; path = path.substring(0, path.lastIndexOf('/') + 1); path += prototype.name_ + '_' + t.name + '.ft'; var xhr = new XMLHttpRequest(); xhr.open("GET", path); xhr.asend(function(data) { t.template = data; future.set(data); t.futureTemplate = undefined; }); } var code = TemplateUtil.lazyCompile(this); if ( prototype.__proto__[this.name] ) { code = override(proto, this.name, code); } proto.addMethod(this.name, code); }], // Model pseudo-properties for backwards compatability. ['Model', 'Property', { name: 'properties', getter: function() { if ( ! Object.prototype.hasOwnProperty.call(this, 'properties_') || ! this.properties_ ) { var props = this.properties_ = []; this.features.forEach(function(f) { if ( Property.isInstance(f) ) { props.push(f); } }); } return this.properties_; }, setter: function(value) { this.properties_ = null; for ( var i = 0; i < value.length; i++ ) { if ( ! Property.isInstance(value[i]) ) value[i] = Property.create(value[i]); this.addFeature(value[i]); } } }], ['Model', 'Property', { name: 'extendsModel', getter: function() { var value; this.features.localForEach(function(f) { if ( Extends.isInstance(f) ) value = f.parent; }); return value; }, setter: function(value) { var feature = Extends.create({ parent: value }); this.addFeature(feature); } }], ['Model', 'Property', { name: 'methods', getter: function() { var ret = []; this.features.localForEach(function(f) { if ( Method.isInstance(f) ) { ret.push(f); } }); return ret; }, setter: function(methods) { if ( Array.isArray(methods) ) { for ( var i = 0; i < methods.length; i++ ) { if ( ! Method.isInstance(methods[i]) ) methods[i] = Method.create(methods[i]); this.addFeature(methods[i]); } } else { for ( var method in methods ) { var m = Method.create({ name: method, code: methods[method] }); this.addFeature(m); } } } }], ['Model', 'Property', { name: 'listeners', getter: function() { var ret = []; this.features.forEach(function(f) { if ( Listener.isInstance(f) ) { ret.push(f); } }); return ret; }, setter: function(listeners) { if ( Array.isArray(listeners) ) { for ( var i = 0; i < listeners.length; i++ ) { if ( ! Listener.isInstance(listeners[i]) ) listeners[i] = Listener.create(listeners[i]); this.addFeature(listeners[i]); } } else { for ( var method in listeners ) { var m = Listener.create({ name: method, code: listeners[method] }); this.addFeature(m); } } } }], ['Model', 'StringArrayProperty', { name: 'tableProperties', factory: function() { return this.properties.map(Property.NAME.f.bind(Property.NAME)); } }], ['Model', 'StringArrayProperty', { name: 'searchProperties', defaultValueFn: function() { return this.tableProperties; }, help: 'Properties display in a search view. Defaults to table properties.' }], ['Model', 'Property', { name: 'actions', type: 'Array', subType: 'Action', getter: function() { var ret = []; // TODO sould this be a local forEach? this.features.localForEach(function(f) { if ( Action.isInstance(f) ) ret.push(f); }); return ret; }, setter: function(value) { for ( var i = 0; i < value.length; i++ ) { if ( ! Action.isInstance(value[i]) ) value[i] = Action.create(value[i]); this.addFeature(value[i]); } } }], ['Model', 'ArrayProperty', { name: 'models', subType: 'Model', getter: function() { var ret = []; // TODO sould this be a local forEach? this.features.localForEach(function(f) { if ( Model.isInstance(f) ) ret.push(f); }); return ret; }, setter: function(value) { for ( var i = 0; i < value.length; i++ ) { if ( ! Model.isInstance(value[i]) ) value[i] = Model.create(value[i]); this.addFeature(value[i]); } } }], ['Model', 'ArrayProperty', { name: 'templates', subType: 'Template', view: 'foam.ui.ArrayView', getter: function() { var ret = []; // TODO sould this be a local forEach? this.features.localForEach(function(f) { if ( Template.isInstance(f) ) ret.push(f); }); return ret; }, setter: function(value) { for ( var i = 0; i < value.length; i++ ) { if ( ! Template.isInstance(value[i]) ) value[i] = Template.create(value[i]); this.addFeature(value[i]); } } }], ]; function lookup(address, scope) { if ( ! address ) return scope; var split = address.split('.'); for ( var i = 0; i < split.length && scope; i++ ) { scope = scope.get ? scope.get(split[i]) : scope[split[i]]; } return scope; } function build(scope, features) { for ( var i = 0 ; i < features.length ; i++ ) { var f = features[i]; // if (f[3]) debugger; var model = lookup(f[0], scope); if ( ! model ) throw "Model not found: " + f[0]; var feature = lookup(f[1], scope); if ( !feature ) throw "Feature not found: " + f[1]; var args = f[2]; var feature = feature.create(args); model.addFeature(feature); } } (function() { var scope = window; scope.set = function(key, value) { this[key] = value; }; scope.get = function(key) { return this[key]; }; scope.features = FeatureSet.create(); bootstrap(scope); build(scope, featureDAO); })();