Table of Contents generated with DocToc
Model/class definition:
foam.CLASS({
package: 'com.google.project',
name: 'MyModel',
extends: 'com.google.project.MyBaseModel',
implements: [
'com.google.project.MyFirstTrait',
'com.google.project.MyFirstSecondTrait'
],
requires: [
'foam.u2.detail.SectionedDetailView',
'com.google.project.MyOtherModel'
],
imports: ['myOtherModelExportedProperty'],
exports: ['myModelExportedProperty'],
constants: {
CONSTANT1: value,
...
},
topics: [
],
axioms: [
],
properties: [
…
],
methods: [
…
],
templates: [
…
],
actions: [
…
],
listeners: [
…
]
});
Properties are data members on instances.
Name only; properties collection:
properties: [
'myPropertyName',
…
],
Property with class-based typing (FOAM3 current):
{
class: 'String',
name: 'str',
documentation: 'Multiline documentation for this property.',
value: 'Hello world',
trim: true,
width: 50
}
FObject property with ‘of’ typing:
{
class: 'FObjectProperty',
of: 'com.google.project.User',
name: 'user',
factory: function() { return this.User.create(); }
}
Reference property (FOAM3 pattern):
{
class: 'Reference',
of: 'com.google.project.User',
name: 'userId',
targetDAOKey: 'userDAO'
}
value - Static value parsed when model is built.
factory - Function returns default value; runs once, the first time the property is read, if no value has been set.
expression - Dynamic function that recalculates when dependencies change (like factory but reactive).
Example factory vs expression:
// Factory - runs once on first access
{ class: 'Array', name: 'items', factory: function() { return []; } }
// Expression - recalculates when 'firstName' or 'lastName' change
{ class: 'String', name: 'fullName', expression: function(firstName, lastName) {
return firstName + ' ' + lastName;
}}
Expressions have special caching behavior for performance:
{
class: 'String',
name: 'computedValue',
expression: function(inputA, inputB) {
return inputA + inputB;
}
}
// Important: Once an expression is set to a value, it stops recalculating
obj.computedValue = 'manual value'; // Expression stops being reactive
// To re-enable reactive behavior, reset to undefined
obj.computedValue = undefined; // Expression becomes reactive again
Caveat: Custom getters and setters are very low-level.
getter: function() { return this.op(this.leftOperand, this.rightOperand); }
A setter replaces the whole setting process and its return value is ignored. To transform an incoming value, use adapt instead:
adapt: function(oldValue, newValue) { return typeof newValue !== ‘undefined’ ? newValue : ‘’; }
Int - Integer numbers with optional min/max, unitsLong - Long integer numbersShort - Short integer numbers (-32768 to 32767)Byte - Byte numbers (-128 to 127)Float - Decimal numbers with precisionDouble - Double precision decimal numbersUnitValue - Long with unit denomination supportString - Text strings with optional trim, widthI18NString - Internationalized stringsFormattedString - Delimiter separated strings with formatterEMail - Email addresses (auto-lowercase, trim)Password - Protected/hidden text displayCode - Code/programming textPhoneNumber - Phone numbers with country codesColor - Color values with CSS token supportURL - Web links and internet addressesWebsite - Websites requiring http(s)/wwwInternalLink - Internal app/service linksImage - Image data or linksDate - Date values (normalized to noon GMT)DateTime - Date and time valuesTime - Time values (extends String)Object - Any object typeClass - Class references with runtime lookupFObjectProperty - FOAM object properties with ‘of’ typeReference - References to other objects by IDFUIDProperty - FOAM Unique ID propertiesArray - Arrays with factory, helper methods ($push, $remove, etc.)List - Lists (extends Object)StringArray - Arrays of strings with validationIntegerArray - Arrays of integers with adaptationMap - Key-value maps with $set/$remove helpersCustom Property Creation: Properties can be extended by creating new classes that extend the base Property class or specific property types (Int, String, etc.). All properties support common features like adapt, preSet, postSet, factory, expression, etc.
Methods are member functions on instances.
Name and implementation only; methods collection:
methods: [
function addAndMultiply(x, y, z) { return (x + y) * z; },
…
]
Additional information; methods collection:
methods: [
{
name: 'addAndMultiply',
documentation: 'First add first two parameters, then multiply by the third.',
code: function(x, y, z) { return (x + y) * z; },
},
…
]
Listeners are member functions pre-bound to instances. Use them as callback functions that will be passed around in the system, but expect to run with the correct “this” value.
Name and implementation only; methods collection:
Listeners collection:
listeners: [
{
name: 'onClick',
documentation: 'Respond to DOM click events on this HTML view.',
code: function(event) { … },
},
…
]
Actions are user-initiated actions that can be performed on instances.
Actions collection:
actions: [
{
name: 'play',
label: 'Play Video',
help: 'Play the video for this post',
icon: 'http://www.example.com/play_icon.png',
documentation: 'Play the video associated with this instance.',
code: function() { … },
},
…
]
Like methods and listeners, an action’s implementation goes in code.
Data Access Objects (DAOs) are an interface for data storage. Sinks are interfaces for receiving objects.
interface Sink {
void put(obj, sub) // Called for each object; sub.detach() stops the stream.
void remove(obj, sub) // Called for each removed object (listen() only).
void eof() // Called when a select() completes.
void reset(sub) // Called when the result set may have changed (listen() only).
}
interface DAO {
Promise<FObject> put(obj) // Store an object; resolves with the stored object.
Promise<FObject> remove(obj) // Remove a single object from the DAO.
Promise removeAll() // Remove all objects from the DAO. Usually prefixed with .where() to limit the rows removed.
Promise<FObject> find(id) // Look up by primary key; resolves with the object or null.
Promise<Sink> select(sink) // Put all objects in the DAO to sink. Resolves with the passed-in sink when complete.
Detachable listen(sink) // Listen to future changes on this DAO; detach() to stop.
pipe(sink) // Short-hand for select(sink) then listen(sink).
DAO where(predicate) // Construct decorated DAO that only contains objects matching predicate.
DAO limit(count) // Construct decorated DAO that only contains the first count objects.
DAO skip(count) // Construct decorated DAO that skips the first count objects.
DAO orderBy(...comparators) // Construct decorated DAO whose results are sorted by comparators.
}
functions as sinks; function called back on put.
FOAM3 automatically updates UI when data changes using these patterns:
this.property$.sub(function(subscription, topic, propertyName, slot) {
// Do something when property changes; topic is 'propertyChange', slot.get() is the new value
});
// Show property value (updates automatically)
this.start('span').add(this.prop$).end()
// Returns a slot whose value updates when dependencies change
this.slot(function(prop1, prop2) {
return prop1 + ' ' + prop2;
});
// Auto-binds to data properties
this.add(function(firstName, lastName) {
this.start('div').add(`Name: ${firstName} ${lastName}`).end();
});
// For object properties, use this.dynamic() directly
this.start('div')
.add(this.dynamic(function(myProp, otherProp) {
this.add(`Values: ${myProp} ${otherProp}`);
}))
.end();
Note: this.add(function(){}) auto-binds to data properties. For the object itself, use this.dynamic() directly. In both forms the function’s return value is ignored: add content with this.add() or this.start() inside it.
Every FOAM object has two context variables:
__context__ - The main context passed when object was created.
__subContext__ - Created only if object has exports; contains main context + exported services.
Context variables:
// Object with no exports
var obj = SomeClass.create({}, mainContext);
console.log(obj.__context__); // mainContext
console.log(obj.__subContext__); // same as __context__
// Object with exports creates subcontext
foam.CLASS({
name: 'Service',
exports: ['userDAO', 'as notificationService']
});
var service = Service.create({}, mainContext);
console.log(service.__context__); // mainContext
console.log(service.__subContext__); // mainContext + exports (userDAO, notificationService)
Context inheritance:
// Child inherits parent's __subContext__ as its __context__
var child = ChildView.create({}, service.__subContext__);
console.log(child.__context__); // mainContext + parent's exports
imports - Inject dependencies from context into this object.
exports - Make properties/services available to child objects in context.
Context dependency injection:
foam.CLASS({
name: 'UserService',
imports: [
'userDAO', // Injects this.userDAO from context
'notificationService?', // Optional import (won't fail if missing)
'currentUser' // Injects this.currentUser
],
exports: [
'userDAO', // Exports this.userDAO to child context
'as notificationService' // Exports this as notificationService
]
});
Always use requires + this.ClassName.create() for proper context inheritance.
Context-aware object creation:
foam.CLASS({
requires: ['foam.u2.detail.SectionedDetailView', 'com.project.MyModel'],
methods: [
function render() {
// Inherits full context (imports, exports, etc.)
var view = this.SectionedDetailView.create({ data: this.data });
var model = this.MyModel.create({ name: 'test' });
}
]
});
Direct instantiation (loses context):
foam.CLASS({
methods: [
function render() {
// No context inheritance - imports/exports unavailable
var view = foam.u2.detail.SectionedDetailView.create({ data: this.data });
var model = com.project.MyModel.create({ name: 'test' });
}
]
});
View with context injection:
foam.CLASS({
name: 'UserView',
extends: 'foam.u2.View',
imports: ['userDAO', 'stack', 'ctrl'],
exports: ['data as selectedUser'],
requires: ['foam.u2.detail.SectionedDetailView'],
methods: [
function render() {
// SectionedDetailView inherits userDAO, stack, ctrl, selectedUser
this.add(this.SectionedDetailView.create({
data: this.data
}));
}
]
});
Service with dependency injection:
foam.CLASS({
name: 'DataService',
imports: ['userDAO', 'orderDAO', 'logger?'],
exports: ['as dataService'], // Short-form for 'this as dataService'
requires: ['com.project.UserValidator'],
methods: [
function validateUser(user) {
// Validator inherits full context
var validator = this.UserValidator.create();
return validator.validate(user);
}
]
});