foam3

Table of Contents generated with DocToc

FOAM3 Cheat Sheet (long form)

Models

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

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'
}

Defaults

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;
}}

Expression Behavior

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

Dynamic Get and/or Set

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 : ‘’; }

Property Types (FOAM3 Current)

Numeric Types:

String Types:

Date/Time Types:

Object Types:

Collection Types:

Custom 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

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

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

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.

DAOs

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.

Reactivity

FOAM3 automatically updates UI when data changes using these patterns:

Listen to Changes

this.property$.sub(function(subscription, topic, propertyName, slot) {
  // Do something when property changes; topic is 'propertyChange', slot.get() is the new value
});

Display Properties

// Show property value (updates automatically)
this.start('span').add(this.prop$).end()

Computed Values

// Returns a slot whose value updates when dependencies change
this.slot(function(prop1, prop2) {
  return prop1 + ' ' + prop2;
});

Dynamic Views

// 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.

Context and Dependency Injection

Context and Subcontext

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 and 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
  ]
});

Context and Object Creation

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' });
    }
  ]
});

Context Patterns

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);
    }
  ]
});