requires vs importsThis document explains the differences between requires and imports in the FOAM3 framework and when each should be used in the codebase.
FOAM3 provides multiple mechanisms for managing dependencies between classes and accessing runtime context. The two primary mechanisms are:
requires: - For declaring class dependencies (like “requires” in JS and “imports” in Java)imports: - For dependency injection of runtime services and valuesFOAM is designed to be cross-language compatible, which means it can’t easily adopt conventions from any single language. This leads to some naming that might seem counterintuitive at first:
requires: works like “requires” in JavaScript and “imports” in Java - it declares which other code/classes you want to useimports: is about dependency injection - getting live runtime instances of services or objectsrequires: - Class DependenciesDeclares class dependencies so you can refer to classes by their short name rather than their fully qualified name. requires requires classes.
When you requires: 'com.acme.Foo', you can refer to it as just Foo rather than its fully qualified name com.acme.Foo. This is similar to:
require() statements in JavaScriptimport statements in Java#include in C++foam.CLASS({
name: 'MyClass',
requires: [
'foam.u2.DetailView', // Available as this.DetailView
'foam.dao.MDAO', // Available as this.MDAO
'foam.net.web.XMLHTTPRequest as HTTPRequest' // Available as this.HTTPRequest
]
});
requires become available as factory methodsthis.ClassName (e.g., this.DetailView, this.MDAO)this.DetailView.create()as keywordthis.Abc.create(args), it automatically calls this.Abc.create(args, this) - the current context (this) is added as the second argumentcreate() is either a context or an FObject whose __subContext__ is usedcom.acme.Abc directly you would need to manually pass this as the second argument, but this.Abc.create() adds it automaticallyBasic Usage:
foam.CLASS({
package: 'com.example.models',
name: 'Transaction',
requires: [
'com.example.views.TransactionFormDetailView'
]
// Instead of: com.example.views.TransactionFormDetailView.create()
// You can use: this.TransactionFormDetailView.create()
});
Complex View with Multiple Dependencies:
// File: foam3/src/foam/u2/wizard/IncrementalStepWizardView.js
foam.CLASS({
package: 'foam.u2.wizard',
name: 'IncrementalStepWizardView',
extends: 'foam.u2.View',
requires: [
'foam.lang.Action',
'foam.log.LogLevel',
'foam.u2.detail.SectionView',
'foam.u2.dialog.Popup',
'foam.u2.dialog.SimpleActionDialog'
]
// All classes available without full package names
});
requiresimports: - Dependency InjectionDeclares that you want a live instance of a service or value for use in your class. This is dependency injection or inversion of control.
You get runtime objects/services, not classes.
Instead of your class going fishing around the system looking for services (which leads to complexity and tight-coupling), you simply declare what services you want to import and they’re provided to you - even possibly from across the network.
This follows the dependency injection pattern:
foam.CLASS({
name: 'MyView',
imports: [
'currentUser', // A user object instance
'userDAO', // A live DAO instance
'log', // A logging service instance
'optionalService?', // Optional service (no warning if missing)
'someDAO as myDAO' // Aliased service
]
});
this.propertyName? suffixas keywordBasic Context Injection:
foam.CLASS({
package: 'com.example.views',
name: 'MenuView',
extends: 'foam.core.menu.VerticalMenu',
imports: [
'currentProgram', // Live program object
'isMenuOpen' // Boolean state value
]
// Access: this.currentProgram (the actual object)
// this.isMenuOpen (the actual boolean value)
});
Service Injection:
// File: foam3/src/foam/u2/wizard/debug/WizardInspector.js
foam.CLASS({
package: 'foam.u2.wizard.debug',
name: 'WizardInspector',
extends: 'foam.u2.Controller',
imports: [
'wizardController', // Live controller instance
'crunchController' // Live controller instance
]
// These are actual running controller objects, not classes
});
imports?)exports: - Making Services AvailableThe flip side of imports: - declares what objects or values you want to make available to descendants in the context chain.
foam.CLASS({
name: 'MyService',
exports: [
'userDAO', // Export userDAO property to children
'as myService', // Export entire instance
'log as logger' // Export with alias
]
});
import these exported values| Aspect | requires |
imports |
|---|---|---|
| Purpose | Class name resolution | Service injection |
| Result | Factory methods | Direct property access |
| Type | Classes/constructors | Instances/values/services |
| Source | Class looked up in the object’s context on first use | Runtime execution context |
| Analogy | import in Java, require() in JS |
Dependency injection frameworks |
| Creation | this.ClassName.create() |
this.serviceName |
| Coupling | Class dependencies, replaceable through the context | Runtime loose coupling |
| Testability | Register a replacement class in a sub-context | Easy to inject test doubles |
| Network | Classes are resolved locally | Can import across network |
| Optional | No | Yes (with ?) |
// BAD: Tight coupling - knowing application architecture explicitly
foam.CLASS({
name: 'BadExample',
methods: [
function init() {
// Tight coupling - requires knowing the exact architecture path
this.userDAO = this.frame.controller.session.daoAccessor.userDAO;
// If the structure changes, all client code breaks
}
]
});
// STILL LOOSE but more work:
foam.CLASS({
name: 'LooseCouplingExample',
methods: [
function init() {
// Context lookup is still loose-coupling, just more work
this.userDAO = this.__context__.userDAO;
this.logger = this.__context__.log;
// What if they're not there? What if they move?
}
]
});
// GOOD: Dependencies are injected
foam.CLASS({
name: 'GoodExample',
imports: [
'userDAO', // I need a user DAO - don't care where it comes from
'log' // I need logging - don't care how it's implemented
],
methods: [
function someMethod() {
// Just use the injected dependencies
this.userDAO.find(id).then(user => {
this.log.info('Found user:', user.name);
});
}
]
});
| Language | Class Import | Service Injection |
|---|---|---|
| FOAM | requires: |
imports: |
| Java | import com.acme.Foo |
@Inject annotations |
| JavaScript | require('./Foo') |
Manual DI or frameworks |
| Python | from acme import Foo |
DI frameworks |
| C# | using Acme |
[Inject] attributes |
javaImports: - Java Code GenerationUsed when generating Java code from FOAM classes:
foam.CLASS({
package: 'com.example.services',
name: 'DataProcessor',
javaImports: [
'foam.dao.AbstractSink',
'java.util.ArrayList',
'java.util.List'
]
// Generates: import foam.dao.AbstractSink; etc.
});
require()For CommonJS modules in Node.js environments:
// File: foam3/tools/build.js
const fs = require('fs'); // Node.js module
const path = require('path'); // Node.js module
Use ? suffix for optional context values:
imports: [
'requiredService', // Missing: debug builds report 'Missing required import', and access logs a warning
'optionalService?' // Missing: no warning, the value is undefined
]
Both requires and imports support aliasing:
requires: [
'foam.u2.DetailView as DetailView',
'very.long.package.name.SomeClass as ShortName'
],
imports: [
'userDAO as dao',
'currentUser as user'
]
imports: can work across networks:
foam.CLASS({
name: 'ClientView',
imports: [
'userService' // Could be a remote service accessed over HTTP
],
methods: [
function loadUser() {
// userService might be running on a different server
return this.userService.getCurrentUser();
}
]
});
requires when:imports when:exports when:imports for services and runtime data - it creates more flexible, testable coderequires for class instantiation and when you need to create multiple instances? in importsfoam.CLASS({
name: 'UserService',
imports: [
'userDAO', // DAO injected from context
'log' // Logging service
],
exports: [
'as userService' // Export self for other components
],
methods: [
function getCurrentUser() {
return this.userDAO.find(this.currentUserId);
}
]
});
foam.CLASS({
name: 'UserController',
imports: [
'userService', // Service injected (loose coupling)
'notify' // Notification service
],
requires: [
'foam.u2.DetailView', // View class for creating forms
'foam.core.auth.User' // Model class for data validation
],
methods: [
function createUserForm() {
// Use required class to create view
return this.DetailView.create({
data: this.User.create() // Use required model
});
},
function saveUser(user) {
// Use injected service
return this.userService.save(user).then(() => {
this.notify.success('User saved!');
});
}
]
});
foam.CLASS({
name: 'UserView',
extends: 'foam.u2.View',
imports: [
'theme', // Theming service
'userService' // Business logic service
],
requires: [
'foam.u2.tag.Button', // UI components
'foam.u2.TextField'
],
methods: [
function render() {
this.add(this.TextField.create({
data$: this.data.username$
}));
this.start(this.Button, { label: 'Save' })
.on('click', () => this.userService.save(this.data))
.end();
}
]
});
Understanding the difference between requires and imports is crucial for effective FOAM development:
requires: = “I need these classes so I can create instances”imports: = “I need these services/values injected into me”exports: = “I’m making these services/values available to my children”This design enables loose coupling, testability, and the ability to work across networks - core principles of the FOAM architecture.