UNPKG

foam-framework

Version:
1,131 lines (1,032 loc) 28.2 kB
/** * @license * Copyright 2012 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. */ CLASS({ name: 'StringProperty', extends: 'Property', help: 'Describes a properties of type String.', label: 'Text, including letters, numbers, or symbols', properties: [ { name: 'displayHeight', type: 'int', displayWidth: 8, defaultValue: 1, help: 'The display height of the property.' }, { name: 'type', type: 'String', displayWidth: 20, defaultValue: 'String', help: 'The FOAM type of this property.' }, { name: 'adapt', labels: ['javascript'], defaultValue: function (_, v) { return v === undefined || v === null ? '' : typeof v === 'function' ? multiline(v) : v.toString() ; } }, { name: 'javaType', type: 'String', displayWidth: 70, defaultValue: 'String', help: 'The Java type of this property.' }, { name: 'view', labels: ['javascript'], defaultValue: 'foam.ui.TextFieldView', }, { name: 'pattern', help: 'Regex pattern for property.' }, { name: 'prototag', label: 'Protobuf tag', type: 'Int', required: false, help: 'The protobuf tag number for this field.' } ] }); CLASS({ name: 'BooleanProperty', extends: 'Property', help: 'Describes a properties of type Boolean.', label: 'True/false, yes/no, or on/off', properties: [ { name: 'type', type: 'String', displayWidth: 20, defaultValue: 'Boolean', help: 'The FOAM type of this property.' }, { name: 'swiftType', type: 'String', displayWidth: 70, defaultValue: 'Bool' }, { name: 'javaType', type: 'String', displayWidth: 70, defaultValue: 'boolean', help: 'The Java type of this property.' }, { name: 'view', labels: ['javascript'], defaultValue: 'foam.ui.BooleanView', }, { name: 'toPropertyE', labels: ['javascript'], defaultValue: function(X) { return X.foam.u2.Checkbox.create(null, X); }, }, [ 'defaultValue', false ], { name: 'adapt', defaultValue: function (_, v) { return !!v; }, labels: ['javascript'], }, { name: 'prototag', label: 'Protobuf tag', type: 'Int', required: false, help: 'The protobuf tag number for this field.' }, { name: 'fromString', labels: ['javascript'], defaultValue: function(s, p) { var txt = s.trim(); this[p.name] = txt.equalsIC('y') || txt.equalsIC('yes') || txt.equalsIC('true') || txt.equalsIC('t'); }, help: 'Function to extract value from a String.' } ] }); CLASS({ name: 'DateProperty', extends: 'Property', help: 'Describes a properties of type Date.', label: 'Date, including year, month, and day', properties: [ { name: 'type', type: 'String', displayWidth: 20, defaultValue: 'Date', help: 'The FOAM type of this property.' }, [ 'displayWidth', 50 ], { name: 'javaType', type: 'String', defaultValue: 'java.util.Date', help: 'The Java type of this property.' }, [ 'view', 'foam.ui.DateFieldView' ], { name: 'prototag', label: 'Protobuf tag', type: 'Int', required: false, help: 'The protobuf tag number for this field.' }, { name: 'adapt', defaultValue: function (_, d) { return (typeof d === 'string' || typeof d === 'number') ? new Date(d) : d; } }, [ 'tableFormatter', function(d) { return d ? d.toRelativeDateString() : ''; } ], [ 'compareProperty', function(o1, o2) { if ( ! o1 ) return ( ! o2 ) ? 0: -1; if ( ! o2 ) return 1; return o1.compareTo(o2); } ] ] }); CLASS({ name: 'DateTimeProperty', extends: 'DateProperty', help: 'Describes a properties of type DateTime.', label: 'Date and time, including year, month, day, hour, minute and second', properties: [ { name: 'type', type: 'String', displayWidth: 25, defaultValue: 'datetime', help: 'The FOAM type of this property.' }, { name: 'adapt', defaultValue: function(_, d) { if ( typeof d === 'number' ) return new Date(d); if ( typeof d === 'string' ) return new Date(d); return d; } }, [ 'view', 'foam.ui.DateTimeFieldView' ] ] }); CLASS({ name: 'IntProperty', extends: 'Property', help: 'Describes a properties of type Int.', label: 'Round numbers such as 1, 0, or -245', properties: [ { name: 'type', type: 'String', displayWidth: 20, defaultValue: 'Int', help: 'The FOAM type of this property.' }, [ 'displayWidth', 10 ], { name: 'javaType', type: 'String', displayWidth: 10, defaultValue: 'int', help: 'The Java type of this property.' }, { name: 'view', labels: ['javascript'], defaultValue: 'foam.ui.IntFieldView', }, { name: 'adapt', labels: ['javascript'], defaultValue: function (_, v) { return typeof v === 'number' ? Math.round(v) : v ? parseInt(v) : 0 ; }, }, [ 'defaultValue', 0 ], { name: 'prototag', label: 'Protobuf tag', type: 'Int', required: false, help: 'The protobuf tag number for this field.' }, { name: 'minValue', label: 'Minimum Value', type: 'Int', required: false, help: 'The minimum value this property accepts.' }, { name: 'maxValue', label: 'Maximum Value', type: 'Int', required: false, help: 'The maximum value this property accepts.' }, { name: 'compareProperty', labels: ['javascript'], defaultValue: function(o1, o2) { return o1 === o2 ? 0 : o1 > o2 ? 1 : -1; }, }, ] }); CLASS({ name: 'LongProperty', extends: 'IntProperty', help: 'Describes a properties of type Long.', label: 'Round long numbers such as 1, 0, or -245', properties: [ { name: 'type', defaultValue: 'Long' }, { name: 'displayWidth', defaultValue: 12 }, { name: 'javaType', defaultValue: 'long', } ] }); CLASS({ name: 'FloatProperty', extends: 'Property', help: 'Describes a properties of type Float.', label: 'Decimal numbers such as 1.34 or -0.00345', properties: [ { name: 'type', type: 'String', displayWidth: 20, defaultValue: 'Float', help: 'The FOAM type of this property.' }, { name: 'defaultValue', defaultValue: 0.0 }, { name: 'javaType', type: 'String', displayWidth: 10, defaultValue: 'double', help: 'The Java type of this property.' }, { name: 'displayWidth', defaultValue: 15 }, { name: 'view', defaultValue: 'foam.ui.FloatFieldView' }, { name: 'adapt', defaultValue: function (_, v) { return typeof v === 'number' ? v : v ? parseFloat(v) : 0.0 ; } }, { name: 'minValue', label: 'Minimum Value', type: 'Float', required: false, help: 'The minimum value this property accepts.' }, { name: 'maxValue', label: 'Maximum Value', type: 'Float', required: false, help: 'The maximum value this property accepts.' }, { name: 'prototag', label: 'Protobuf tag', type: 'Int', required: false, help: 'The protobuf tag number for this field.' } ] }); CLASS({ name: 'FunctionProperty', extends: 'Property', help: 'Describes a properties of type Function.', label: 'Code that can be run', properties: [ { name: 'type', type: 'String', displayWidth: 20, defaultValue: 'Function', help: 'The FOAM type of this property.' }, { name: 'javaType', type: 'String', displayWidth: 10, defaultValue: 'Function', help: 'The Java type of this property.' }, { name: 'displayWidth', defaultValue: 15 }, { name: 'view', defaultValue: 'foam.ui.FunctionView' }, { name: 'defaultValue', defaultValue: function() {} }, { name: 'fromElement', defaultValue: function(e, p) { var txt = e.innerHTML.trim(); this[p.name] = txt.startsWith('function') ? eval('(' + txt + ')') : new Function(txt) ; } }, { name: 'adapt', defaultValue: function(_, value) { if ( typeof value === 'string' ) { return value.startsWith('function') ? eval('(' + value + ')') : new Function(value); } return value; } } ] }); CLASS({ name: 'TemplateProperty', extends: 'FunctionProperty', properties: [ { name: 'adapt', defaultValue: function(_, value) { return TemplateUtil.expandTemplate(this, value); } }, { name: 'defaultValue', adapt: function(_, value) { return TemplateProperty.ADAPT.defaultValue.call(this, _, value); } }, { name: 'install', defaultValue: function(prop) { defineLazyProperty(this, prop.name + '$f', function() { var f = TemplateUtil.lazyCompile(this[prop.name]) return { get: function() { return f; }, configurable: true }; }); } } ] }); CLASS({ name: 'ArrayProperty', extends: 'Property', help: 'Describes a property of type Array.', label: 'List of items', properties: [ { name: 'type', type: 'String', displayWidth: 20, defaultValue: 'Array', help: 'The FOAM type of this property.' }, { name: 'swiftType', defaultValue: '[AnyObject]' }, { name: 'swiftDefaultValue', defaultValue: '[]' }, { name: 'singular', type: 'String', displayWidth: 70, defaultValueFn: function() { return this.name.replace(/s$/, ''); }, help: 'The plural form of this model\'s name.', documentation: function() { /* The singular form of $$DOC{ref:'Property.name'}.*/} }, { name: 'subType', type: 'String', displayWidth: 20, defaultValue: '', help: 'The FOAM sub-type of this property.' }, { name: 'protobufType', defaultValueFn: function() { return this.subType; } }, { name: 'adapt', defaultValue: function(_, a, prop) { var m = prop.subType_ || ( prop.subType_ = this.X.lookup(prop.subType) || GLOBAL.lookup(prop.subType) ); if ( m ) { for ( var i = 0 ; i < a.length ; i++ ) { if ( ! m.isInstance(a[i]) ) a[i] = a[i].model_ ? FOAM(a[i]) : m.create(a[i]); } } return a; } }, { name: 'postSet', defaultValue: function(oldA, a, prop) { var name = prop.nameArrayRelay_ || ( prop.nameArrayRelay_ = prop.name + 'ArrayRelay_' ); var l = this[name] || ( this[name] = function() { this.propertyChange(prop.name, null, this[prop.name]); }.bind(this) ); if ( oldA && oldA.unlisten ) oldA.unlisten(l); if ( a && a.listen ) a.listen(l); } }, { name: 'javaType', type: 'String', displayWidth: 10, defaultValueFn: function(p) { return this.subType + '[]'; }, help: 'The Java type of this property.' }, { name: 'view', defaultValue: 'foam.ui.ArrayView' }, { name: 'factory', defaultValue: function() { return []; } }, { name: 'propertyToJSON', defaultValue: function(visitor, output, o) { if ( ! this.transient && o[this.name].length ) output[this.name] = visitor.visitArray(o[this.name]); } }, { name: 'install', defaultValue: function(prop) { defineLazyProperty(this, prop.name + '$Proxy', function() { var proxy = this.X.lookup('foam.dao.ProxyDAO').create({delegate: this[prop.name].dao}); this.addPropertyListener(prop.name, function(_, __, ___, a) { proxy.delegate = a.dao; }); return { get: function() { return proxy; }, configurable: true }; }); this.addMethod('get' + capitalize(prop.singular), function(id) { for ( var i = 0; i < this[prop.name].length; i++ ) { if ( this[prop.name][i].id === id ) return this[prop.name][i]; } }); } }, { name: 'fromElement', defaultValue: function(e, p) { var model = this.X.lookup(e.getAttribute('model') || p.subType); var children = e.children; var a = []; for ( var i = 0 ; i < children.length ; i++ ) { var o = model.create(null, this.Y); o.fromElement(children[i], p); a.push(o); } this[p.name] = a; } }, { name: 'prototag', label: 'Protobuf tag', type: 'Int', required: false, help: 'The protobuf tag number for this field.' } ] }); CLASS({ name: 'ReferenceProperty', extends: 'Property', help: 'A foreign key reference to another Entity.', label: 'Reference to another object', properties: [ { name: 'type', type: 'String', displayWidth: 20, defaultValue: 'Reference', help: 'The FOAM type of this property.' }, { name: 'subType', type: 'String', displayWidth: 20, defaultValue: '', help: 'The FOAM sub-type of this property.' }, { name: 'subKey', type: 'EXPR', displayWidth: 20, defaultValue: 'ID', help: 'The foreign key that this property references.' }, { name: 'javaType', type: 'String', displayWidth: 10, defaultValueFn: function() { return this.X.lookup(this.subType)[this.subKey].javaType; }, help: 'The Java type of this property.' }, { name: 'view', defaultValue: 'foam.ui.TextFieldView' // TODO: Uncomment when all usages of ReferenceProperty/ReferenceArrayProperty fixed. // defaultValue: 'KeyView' }, { name: 'prototag', label: 'Protobuf tag', type: 'Int', required: false, help: 'The protobuf tag number for this field.' } ] }); CLASS({ name: 'StringArrayProperty', extends: 'Property', help: 'An array of String values.', label: 'List of text strings', properties: [ { name: 'type', type: 'String', displayWidth: 20, defaultValue: 'Array', help: 'The FOAM type of this property.' }, { name: 'swiftType', defaultValue: '[String]' }, { name: 'swiftDefaultValue', defaultValue: '[]' }, { name: 'singular', type: 'String', displayWidth: 70, defaultValueFn: function() { return this.name.replace(/s$/, ''); }, help: 'The plural form of this model\'s name.', documentation: function() { /* The singular form of $$DOC{ref:'Property.name'}.*/} }, { name: 'subType', type: 'String', displayWidth: 20, defaultValue: 'String', help: 'The FOAM sub-type of this property.' }, { name: 'displayWidth', defaultValue: 50 }, { name: 'adapt', defaultValue: function(_, v) { return Array.isArray(v) ? v : ((v || v === 0) ? [v] : []); } }, { name: 'factory', defaultValue: function() { return []; } }, { name: 'javaType', type: 'String', displayWidth: 10, defaultValue: 'String[]', help: 'The Java type of this property.' }, { name: 'view', defaultValue: 'foam.ui.StringArrayView' }, { name: 'prototag', label: 'Protobuf tag', type: 'Int', required: false, help: 'The protobuf tag number for this field.' }, { name: 'exclusive', defaultValue: false }, { name: 'fromString', defaultValue: function(s, p) { this[p.name] = s.split(','); } }, { name: 'fromElement', defaultValue: function(e, p) { var val = []; var name = p.singular || 'item'; for ( var i = 0 ; i < e.children.length ; i++ ) if ( e.children[i].nodeName === name ) val.push(e.children[i].innerHTML); this[p.name] = val; } }, { name: 'toMemento', defaultValue: function(o, p) { return o.join(','); } }, { name: 'fromMemento', defaultValue: function(s, p) { return s ? s.split(',') : undefined; } }, ] }); CLASS({ name: 'ModelProperty', extends: 'Property', help: 'Describes a Model property.', label: 'Data Model definition', properties: [ [ 'type', 'Model' ], { name: 'getter', defaultValue: function(name) { var value = this.instance_[name]; if ( typeof value === 'undefined' ) { var prop = this.model_.getProperty(name); if ( prop ) { if ( prop.lazyFactory ) { value = this.instance_[prop.name] = prop.lazyFactory.call(this, prop); } else if ( prop.factory ) { value = this.instance_[prop.name] = prop.factory.call(this, prop); } else if ( prop.defaultValueFn ) { value = prop.defaultValueFn.call(this, prop); } else if ( typeof prop.defaultValue !== undefined ) { value = prop.defaultValue; } else { value = ''; } } else { value = ''; } } if ( typeof value === 'string' ) { if ( ! value ) return ''; var ret = this.X.lookup(value); // console.assert(Model.isInstance(ret), 'Invalid model specified for ' + this.name_); return ret; } if ( Model.isInstance(value) ) return value; return ''; } }, { name: 'propertyToJSON', defaultValue: function(visitor, output, o) { if ( ! this.transient ) output[this.name] = o[this.name].id; } } ] }); CLASS({ name: 'ViewProperty', extends: 'Property', help: 'Describes a View-Factory property.', properties: [ { name: 'adapt', doc: "Can be specified as either a function, a Model, a Model path, or a JSON object.", defaultValue: function(_, f) { if ( typeof f === 'function' ) return f; if ( typeof f === 'string' ) { return function(d, opt_X) { return (opt_X || this.X).lookup(f).create(d, opt_X || this.Y); }.bind(this); } if ( typeof f.create === 'function' ) return f.create.bind(f); if ( typeof f.model_ === 'string' ) return function(d, opt_X) { return FOAM(f, opt_X || this.Y).copyFrom(d); } console.error('******* Unknown view factory: ', f); return f; } }, { name: 'defaultValue', adapt: function(_, f) { return ViewProperty.ADAPT.defaultValue.call(this, null, f); } } ] }); CLASS({ name: 'FactoryProperty', extends: 'Property', help: 'Describes a Factory property.', properties: [ { name: 'preSet', doc: "Can be specified as either a function, a Model, a Model path, or a JSON object.", defaultValue: function(_, f) { // Undefined values if ( ! f ) return f; // A Factory Function if ( typeof f === 'function' ) return f; // A String Path to a Model if ( typeof f === 'string' ) return function(map, opt_X) { return (opt_X || this.X).lookup(f).create(map, opt_X || this.Y); }.bind(this); // An actual Model if ( Model.isInstance(f) ) return f.create.bind(f); // A JSON Model Factory: { factory_ : 'ModelName', arg1: value1, ... } if ( f.factory_ ) return function(map, opt_X) { var X = opt_X || this.X; var m = X.lookup(f.factory_); console.assert(m, 'Unknown Factory Model: ' + f.factory_); return m.create(f, opt_X || this.Y); }.bind(this); console.error('******* Invalid Factory: ', f); return f; } } ] }); CLASS({ name: 'ViewFactoryProperty', extends: 'FactoryProperty', help: 'Describes a View Factory property.', /* Doesn't work yet! constants: { VIEW_CACHE: {} }, */ properties: [ { name: 'defaultValue', preSet: function(_, f) { return ViewFactoryProperty.ADAPT.defaultValue.call(this, null, f); } }, { name: 'defaultValueFn', preSet: function(_, f) { // return a function that will adapt the given f's return var fp = function(prop) { // call the defaultValue function, adapt the result, return it return ViewFactoryProperty.ADAPT.defaultValue.call(this, null, f.call(this, prop)); }; fp.toString = function() { return f.toString(); }; return fp; } }, { name: 'fromElement', defaultValue: function(e, p) { this[p.name] = e.innerHTML_ || ( e.innerHTML_ = e.innerHTML ); } }, { name: 'adapt', doc: "Can be specified as either a function, String markup, a Model, a Model path, or a JSON object.", defaultValue: function(_, f) { // Undefined values if ( ! f ) return f; // A Factory Function if ( typeof f === 'function' ) return f; var ret; // A String Path to a Model if ( typeof f === 'string' ) { // if not a valid model path then treat as a template if ( /[^0-9a-zA-Z$_.]/.exec(f) ) { // Cache the creation of an DetailView so that we don't // keep recompiling the template var VIEW_CACHE = ViewFactoryProperty.VIEW_CACHE || ( ViewFactoryProperty.VIEW_CACHE = {} ); var viewModel = VIEW_CACHE[f]; if ( ! viewModel ) { viewModel = VIEW_CACHE[f] = Model.create({ name: 'InnerDetailView' + this.$UID, extends: 'foam.ui.DetailView', templates:[{name: 'toHTML', template: f}] }); // TODO(kgr): this isn't right because compiling the View template // is async. Should add a READY state to View to handle this. viewModel.arequire(); } ret = function(args, X) { return viewModel.create(args, X || this.Y); }; } else { ret = function(map, opt_X) { var model = (opt_X || this.X).lookup(f); console.assert(!!model, 'Unknown model: ' + f + ' in ' + this.name + ' property'); return model.create(map, opt_X || this.Y); }.bind(this); } ret.toString = function() { return '"' + f + '"'; }; return ret; } // An actual Model if ( Model.isInstance(f) ) return function(args, opt_X) { return f.create(args, opt_X || this.Y) }.bind(this); // A JSON Model Factory: { factory_ : 'ModelName', arg1: value1, ... } if ( f.factory_ ) { ret = function(map, opt_X) { var m = (opt_X || this.X).lookup(f.factory_); console.assert(m, 'Unknown ViewFactory Model: ' + f.factory_); return m.create(f, opt_X || this.Y).copyFrom(map); }; ret.toString = function() { return JSONUtil.compact.stringify(f); }; return ret; } if ( this.X.lookup('foam.ui.BaseView').isInstance(f) ) return constantFn(f); console.error('******* Invalid Factory: ', f); return f; } } ] }); CLASS({ name: 'ReferenceArrayProperty', extends: 'ReferenceProperty', properties: [ { name: 'type', defaultValue: 'Array', displayWidth: 20, help: 'The FOAM type of this property.' }, { name: 'factory', defaultValue: function() { return []; }, }, { name: 'javaType', defaultValueFn: function() { return this.X.lookup(this.subType).ID.javaType + '[]'; } }, { name: 'view', defaultValue: 'foam.ui.StringArrayView', // TODO: Uncomment when all usages of ReferenceProperty/ReferenceArrayProperty fixed. // defaultValue: 'DAOKeyView' } ] }); CLASS({ name: 'EMailProperty', extends: 'StringProperty', label: 'Email address', }); CLASS({ name: 'ImageProperty', extends: 'StringProperty', label: 'Image data or link', properties: [ { name: 'view', labels: ['javascript'], defaultValue: 'foam.ui.md.ImagePickerView', } ] }); CLASS({ name: 'URLProperty', extends: 'StringProperty', label: 'Web link (URL or internet address)', }); CLASS({ name: 'ColorProperty', extends: 'StringProperty', label: 'Color', properties: [ [ 'view', 'foam.ui.md.ColorFieldView' ] ] }); CLASS({ name: 'PasswordProperty', extends: 'StringProperty', label: 'Password that displays protected or hidden text', }); CLASS({ name: 'PhoneNumberProperty', extends: 'StringProperty', label: 'Phone number', }); if ( DEBUG ) CLASS({ name: 'DocumentationProperty', extends: 'Property', help: 'Describes the documentation properties found on Models, Properties, Actions, Methods, etc.', documentation: "The developer documentation for this $$DOC{ref:'.'}. Use a $$DOC{ref:'DocModelView'} to view documentation.", properties: [ { name: 'type', type: 'String', defaultvalue: 'Documentation' }, { // Note: defaultValue: for the getter function didn't work. factory: does. name: 'getter', type: 'Function', labels: ['debug'], defaultValue: function(name) { var doc = this.instance_[name] if (doc && typeof Documentation != 'undefined' && Documentation // a source has to exist (otherwise we'll return undefined below) && ( !doc.model_ // but we don't know if the user set model_ || !doc.model_.getPrototype // model_ could be a string || !Documentation.isInstance(doc) // check for correct type ) ) { // So in this case we have something in documentation, but it's not of the // "Documentation" model type, so FOAMalize it. if (doc.body) { this.instance_[name] = Documentation.create( doc ); } else { this.instance_[name] = Documentation.create({ body: doc }); } } // otherwise return the previously FOAMalized model or undefined if nothing specified. return this.instance_[name]; } }, { name: 'view', defaultValue: 'foam.ui.DetailView', labels: ['debug'] }, { name: 'help', defaultValue: 'Documentation for this entity.', labels: ['debug'] }, { name: 'documentation', factory: function() { return "The developer documentation for this $$DOC{ref:'.'}. Use a $$DOC{ref:'DocModelView'} to view documentation."; }, labels: ['debug'] } ] });