2021-06-03 16:50:58 +03:00
|
|
|
# `features.js`
|
2016-08-23 17:21:35 +03:00
|
|
|
|
2021-06-04 23:59:37 +03:00
|
|
|
`features.js` organizes sets of [actions] or _object methods_ into features,
|
|
|
|
|
applies them, manages merging of features via inter-feature dependencies and
|
|
|
|
|
external criteria.
|
2016-08-23 18:04:43 +03:00
|
|
|
|
|
|
|
|
|
2021-06-06 13:59:43 +03:00
|
|
|
## Contents
|
|
|
|
|
- [`features.js`](#featuresjs)
|
|
|
|
|
- [Contents](#contents)
|
|
|
|
|
- [Basics](#basics)
|
2021-06-06 14:00:53 +03:00
|
|
|
- [Installing and using](#installing-and-using)
|
2021-06-06 13:59:43 +03:00
|
|
|
- [Organizational structure](#organizational-structure)
|
|
|
|
|
- [Lifecycle](#lifecycle)
|
|
|
|
|
- [How features are loaded](#how-features-are-loaded)
|
|
|
|
|
- [The main entities:](#the-main-entities)
|
|
|
|
|
- [`FeatureSet()`](#featureset)
|
|
|
|
|
- [`<feature-set>.Feature(..)`](#feature-setfeature)
|
|
|
|
|
- [`<feature-set>.<feature-tag>` / `<feature-set>[<feature-tag>]`](#feature-setfeature-tag--feature-setfeature-tag)
|
|
|
|
|
- [`<feature-set>.features`](#feature-setfeatures)
|
|
|
|
|
- [`<feature-set>.setup(..)`](#feature-setsetup)
|
|
|
|
|
- [`<feature-set>.remove(..)`](#feature-setremove)
|
|
|
|
|
- [`<feature-set>.gvGraph(..)`](#feature-setgvgraph)
|
|
|
|
|
- [`Feature(..)`](#feature)
|
|
|
|
|
- [Meta-features](#meta-features)
|
|
|
|
|
- [Extending](#extending)
|
|
|
|
|
- [License](#license)
|
|
|
|
|
|
|
|
|
|
|
2021-06-04 23:59:37 +03:00
|
|
|
## Basics
|
|
|
|
|
|
|
|
|
|
If [actions] are a means to organize how methods are extended and called in the
|
|
|
|
|
prototype chain, `features.js` defined how that prototype chain is built.
|
|
|
|
|
|
|
|
|
|
A _feature_ defines define a mixin / action-set and metadata:
|
|
|
|
|
- documentation
|
2021-06-06 13:34:27 +03:00
|
|
|
- applicability testing
|
|
|
|
|
- dependencies both hard and soft
|
2021-06-04 23:59:37 +03:00
|
|
|
- load priority
|
|
|
|
|
|
|
|
|
|
This metadata helps automatically build/rebuild a list of applicable features,
|
|
|
|
|
sort it and mix their actions, configuration into an object.
|
|
|
|
|
|
|
|
|
|
In contrast to the traditional _manual_ inheritance/prototyping, here the _MRO_
|
|
|
|
|
(method resolution order) can self-adapt to the specific runtime requirements
|
|
|
|
|
depending on feature metadata without the need to manually code prototype
|
|
|
|
|
chains for each possible scenario.
|
|
|
|
|
|
|
|
|
|
This makes it trivial to split the desired functionality into _features_
|
|
|
|
|
vertically, a-la MVC. As well as horizontally splitting the core functionality
|
|
|
|
|
and extensions, plugins, etc. into separate features.
|
|
|
|
|
|
|
|
|
|
For example splitting an app into:
|
|
|
|
|
```
|
2021-06-05 00:52:38 +03:00
|
|
|
+-UI--------------------------------------------------------------------+
|
|
|
|
|
| +------------+ +---------+ +--------------+ +-------------+ |
|
|
|
|
|
| | Standalone | | Web App |--->| Web Site API | | Commandline | |
|
|
|
|
|
| +------------+ +---------+ +--------------+ +-------------+ |
|
|
|
|
|
+-------+---------------------------------------------------------------+
|
2021-06-05 00:13:19 +03:00
|
|
|
|
|
|
|
|
|
v
|
2021-06-05 00:52:38 +03:00
|
|
|
+---------------+
|
|
|
|
|
| Data Handling |
|
|
|
|
|
+---------------+
|
2021-06-05 00:13:19 +03:00
|
|
|
```
|
2021-06-05 00:26:31 +03:00
|
|
|
|
|
|
|
|
Each _feature_ extending the same base API but implementing only it's specific
|
2021-06-06 13:34:27 +03:00
|
|
|
functionality and adding new methods where needed. On setup only the relevant
|
2021-06-05 00:43:43 +03:00
|
|
|
features/functionality for a specific runtime are loaded, for example creating
|
2021-06-06 13:34:27 +03:00
|
|
|
one the following prototype chains depending on context:
|
2021-06-05 00:43:43 +03:00
|
|
|
|
2021-06-06 13:34:27 +03:00
|
|
|
<table width="100%"><tr><th>
|
2021-06-05 00:52:38 +03:00
|
|
|
Web site
|
2021-06-06 13:34:27 +03:00
|
|
|
|
|
|
|
|
</th><th>
|
2021-06-05 00:52:38 +03:00
|
|
|
Desktop app
|
2021-06-05 00:43:43 +03:00
|
|
|
|
2021-06-06 13:34:27 +03:00
|
|
|
</th><th>
|
|
|
|
|
Console
|
|
|
|
|
|
|
|
|
|
</th></tr><tr><td>
|
2021-06-05 00:43:43 +03:00
|
|
|
|
2021-06-05 00:26:31 +03:00
|
|
|
```
|
2021-06-05 00:52:38 +03:00
|
|
|
+-UI-----------+
|
|
|
|
|
| Web Site API |
|
|
|
|
|
+----+---------+
|
2021-06-05 00:26:31 +03:00
|
|
|
|
|
|
|
|
|
v
|
2021-06-05 00:52:38 +03:00
|
|
|
+---------------+
|
|
|
|
|
| Data Handling |
|
|
|
|
|
+---------------+
|
2021-06-05 00:43:43 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-06-05 00:26:31 +03:00
|
|
|
```
|
2021-06-06 13:34:27 +03:00
|
|
|
</td><td>
|
2021-06-05 00:43:43 +03:00
|
|
|
|
2021-06-05 00:26:31 +03:00
|
|
|
```
|
|
|
|
|
+-UI----------+
|
|
|
|
|
| Commandline |
|
|
|
|
|
+----+--------+
|
|
|
|
|
|
|
|
|
|
|
v
|
|
|
|
|
+-UI---------+
|
|
|
|
|
| Standalone |
|
|
|
|
|
+----+-------+
|
|
|
|
|
|
|
|
|
|
|
v
|
2021-06-05 00:52:38 +03:00
|
|
|
+---------------+
|
|
|
|
|
| Data Handling |
|
|
|
|
|
+---------------+
|
|
|
|
|
```
|
2021-06-06 13:34:27 +03:00
|
|
|
</td><td>
|
2021-06-05 00:52:38 +03:00
|
|
|
|
|
|
|
|
```
|
|
|
|
|
+-UI----------+
|
|
|
|
|
| Commandline |
|
|
|
|
|
+----+--------+
|
|
|
|
|
|
|
|
|
|
|
v
|
|
|
|
|
+---------------+
|
|
|
|
|
| Data Handling |
|
|
|
|
|
+---------------+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-06-05 00:26:31 +03:00
|
|
|
```
|
2021-06-06 13:34:27 +03:00
|
|
|
</td></tr></table>
|
2021-06-05 00:43:43 +03:00
|
|
|
|
2021-06-05 00:26:31 +03:00
|
|
|
Note that since _JavaScript_ does not support multiple inheritance, the
|
|
|
|
|
feature dependency _graph_ is linearized when creating a prototype/mixin
|
|
|
|
|
chain.
|
|
|
|
|
|
|
|
|
|
Also note that this architecture is in part inspired by [Python]'s multiple
|
2021-06-06 13:34:27 +03:00
|
|
|
inheritance implementation, but though similar to it in some regards,
|
|
|
|
|
`features.js` is quite different in others.
|
2021-06-04 23:59:37 +03:00
|
|
|
|
|
|
|
|
|
2021-06-03 16:50:58 +03:00
|
|
|
|
2021-06-06 14:00:53 +03:00
|
|
|
## Installing and using
|
2021-06-03 16:50:58 +03:00
|
|
|
|
2021-06-06 13:59:43 +03:00
|
|
|
```shell
|
|
|
|
|
$ npm install --save ig-features
|
|
|
|
|
```
|
2021-06-03 16:50:58 +03:00
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
var features = require('ig-features')
|
|
|
|
|
```
|
|
|
|
|
|
2021-06-06 13:59:43 +03:00
|
|
|
|
2021-06-03 16:50:58 +03:00
|
|
|
### Organizational structure
|
|
|
|
|
|
2021-06-04 02:54:27 +03:00
|
|
|
- `FeatureSet`
|
2021-06-06 13:59:43 +03:00
|
|
|
Creates `<feature-set>`
|
|
|
|
|
- `<feature-set>` (`FeatureSet`)
|
|
|
|
|
Contains features, defines the main feature manipulation API and
|
|
|
|
|
`<object-w-features>` constructor/factory.
|
2021-06-04 02:54:27 +03:00
|
|
|
- `Feature`
|
|
|
|
|
Creates a feature in the feature-set, defines the feature metadata, references
|
|
|
|
|
the feature mixin / action set and configuration.
|
|
|
|
|
- `ActionSet` / mixin
|
2021-06-06 13:59:43 +03:00
|
|
|
Contains the actions/methods of the feature mixin.
|
|
|
|
|
See [actions] for more details.
|
|
|
|
|
- `<object-w-features>` (`ActionSet`)
|
|
|
|
|
Instance constructed by `<feature-set>` with all the feature action sets in
|
|
|
|
|
the prototype chain and a merged `.config`.
|
2021-06-04 02:54:27 +03:00
|
|
|
|
2021-06-03 16:50:58 +03:00
|
|
|
<!-- XXX -->
|
|
|
|
|
|
2021-06-04 02:54:27 +03:00
|
|
|
```javascript
|
|
|
|
|
// feature-set...
|
|
|
|
|
var App = new features.FeatureSet()
|
|
|
|
|
|
|
|
|
|
// features...
|
|
|
|
|
App.Feature('A', {
|
|
|
|
|
// ...
|
|
|
|
|
})
|
|
|
|
|
App.Feature('B', {
|
|
|
|
|
// ...
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// meta-features...
|
|
|
|
|
App.Feature('all', [
|
|
|
|
|
'B',
|
|
|
|
|
'C',
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
// init and start the app...
|
|
|
|
|
var app = App.start(['all'])
|
|
|
|
|
```
|
|
|
|
|
|
2021-06-03 16:50:58 +03:00
|
|
|
|
|
|
|
|
### Lifecycle
|
|
|
|
|
|
|
|
|
|
<!-- XXX -->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### How features are loaded
|
|
|
|
|
|
|
|
|
|
<!-- XXX algorithm(s) -->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## The main entities:
|
|
|
|
|
|
2021-06-04 02:54:27 +03:00
|
|
|
### `FeatureSet()`
|
|
|
|
|
|
|
|
|
|
```bnf
|
|
|
|
|
FeatureSet()
|
|
|
|
|
-> <feature-set>
|
|
|
|
|
```
|
2016-08-24 03:36:41 +03:00
|
|
|
|
|
|
|
|
```javascript
|
2021-06-03 16:50:58 +03:00
|
|
|
var feature_set = new features.FeatureSet()
|
2016-08-24 03:48:35 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
// define features...
|
|
|
|
|
// ...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// setup features...
|
|
|
|
|
feature_set
|
2021-06-03 16:50:58 +03:00
|
|
|
.setup([
|
|
|
|
|
'feature-tag',
|
|
|
|
|
//...
|
|
|
|
|
])
|
2016-08-24 03:36:41 +03:00
|
|
|
```
|
2016-08-23 18:04:43 +03:00
|
|
|
|
|
|
|
|
XXX
|
|
|
|
|
|
2021-06-04 23:59:37 +03:00
|
|
|
#### `<feature-set>.Feature(..)`
|
|
|
|
|
|
|
|
|
|
Feature constructor.
|
|
|
|
|
|
|
|
|
|
For more info see: [`Feature(..)`](#feature)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### `<feature-set>.<feature-tag>` / `<feature-set>[<feature-tag>]`
|
|
|
|
|
|
|
|
|
|
<!-- XXX -->
|
|
|
|
|
|
|
|
|
|
|
2021-06-04 02:54:27 +03:00
|
|
|
#### `<feature-set>.features`
|
|
|
|
|
|
|
|
|
|
<!-- XXX -->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### `<feature-set>.setup(..)`
|
|
|
|
|
|
|
|
|
|
<!-- XXX -->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### `<feature-set>.remove(..)`
|
|
|
|
|
|
|
|
|
|
<!-- XXX -->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### `<feature-set>.gvGraph(..)`
|
|
|
|
|
|
2021-06-06 13:59:43 +03:00
|
|
|
Generate a [Graphvis] graph spec for the feature dependency graph.
|
2021-06-04 02:54:27 +03:00
|
|
|
|
|
|
|
|
<!-- XXX -->
|
|
|
|
|
|
|
|
|
|
|
2016-08-23 18:04:43 +03:00
|
|
|
|
2021-06-03 16:50:58 +03:00
|
|
|
### `Feature(..)`
|
|
|
|
|
|
2021-06-04 02:54:27 +03:00
|
|
|
Standalone feature
|
|
|
|
|
```bnf
|
|
|
|
|
Feature({ tag: <tag>, .. })
|
|
|
|
|
Feature(<tag>, { .. })
|
|
|
|
|
Feature(<tag>, [<suggested-tag>, .. ])
|
|
|
|
|
Feature(<tag>, <actions>)
|
|
|
|
|
-> <feature>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Feature-set features
|
|
|
|
|
```bnf
|
|
|
|
|
<feature-set>.Feature({ tag: <tag>, .. })
|
|
|
|
|
<feature-set>.Feature(<tag>, { .. })
|
|
|
|
|
<feature-set>.Feature(<tag>, [<suggested-tag>, .. ])
|
|
|
|
|
<feature-set>.Feature(<tag>, <actions>)
|
|
|
|
|
-> <feature>
|
|
|
|
|
|
|
|
|
|
Feature(<feature-set>, { tag: <tag>, .. })
|
|
|
|
|
Feature(<feature-set>, <tag>, <actions>)
|
|
|
|
|
-> <feature>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Examples:
|
2016-08-24 03:36:41 +03:00
|
|
|
```javascript
|
2021-06-04 02:54:27 +03:00
|
|
|
feature_set.Feature('minimal_feature_example', {})
|
|
|
|
|
```
|
2016-08-24 03:36:41 +03:00
|
|
|
|
2021-06-04 02:54:27 +03:00
|
|
|
```javascript
|
2016-08-24 03:36:41 +03:00
|
|
|
feature_set.Feature({
|
2021-06-04 02:54:27 +03:00
|
|
|
// feature unique identifier (required)...
|
|
|
|
|
tag: 'feature_example',
|
|
|
|
|
|
2021-06-02 11:52:42 +03:00
|
|
|
// documentation (optional)...
|
|
|
|
|
title: 'Example Feature',
|
|
|
|
|
doc: 'A feature to demo the base API...',
|
2016-08-24 03:36:41 +03:00
|
|
|
|
2021-06-02 11:52:42 +03:00
|
|
|
// applicability test (optional)
|
|
|
|
|
isApplicable: function(){ /* ... */ },
|
2016-08-24 03:36:41 +03:00
|
|
|
|
2021-06-02 11:52:42 +03:00
|
|
|
// feature load priority (optional)
|
|
|
|
|
priority: 'medium',
|
2016-08-24 03:36:41 +03:00
|
|
|
|
2021-06-02 11:52:42 +03:00
|
|
|
// list of feature tags to load if available (optional)
|
|
|
|
|
suggested: [],
|
2016-08-24 03:36:41 +03:00
|
|
|
|
2021-06-02 11:52:42 +03:00
|
|
|
// list of feature tags required to load before this feature (optional)
|
|
|
|
|
depends: [],
|
2016-08-24 03:36:41 +03:00
|
|
|
|
2021-06-02 11:52:42 +03:00
|
|
|
// Exclusive tag (optional)
|
|
|
|
|
// NOTE: a feature can be a member of more than one exclusive group,
|
|
|
|
|
// to list more than one use an Array...
|
|
|
|
|
exclusive: 'Example',
|
2016-08-24 03:36:41 +03:00
|
|
|
|
2021-06-02 11:52:42 +03:00
|
|
|
// feature configuration (optional)
|
|
|
|
|
// NOTE: if not present here this will be taken from .actions.config
|
|
|
|
|
// NOTE: this takes priority over .actions.config, it is not recommended
|
|
|
|
|
// to define both.
|
2017-07-04 17:06:10 +03:00
|
|
|
config: {
|
2021-06-04 02:54:27 +03:00
|
|
|
option: 'value',
|
2021-06-02 11:52:42 +03:00
|
|
|
// ...
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// actions (optional)
|
|
|
|
|
actions: Actions({
|
|
|
|
|
// alternative configuration location...
|
|
|
|
|
config: {
|
|
|
|
|
// ...
|
|
|
|
|
},
|
|
|
|
|
// ...
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
// action handlers (optional)
|
|
|
|
|
handlers: [
|
2021-06-04 02:54:27 +03:00
|
|
|
['action.pre',
|
|
|
|
|
function(){ /* ... */ }],
|
2021-06-02 11:52:42 +03:00
|
|
|
// ...
|
|
|
|
|
],
|
2016-08-24 03:36:41 +03:00
|
|
|
})
|
|
|
|
|
```
|
2016-08-23 18:04:43 +03:00
|
|
|
|
2016-08-23 17:21:35 +03:00
|
|
|
XXX
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-06-03 16:50:58 +03:00
|
|
|
### Meta-features
|
|
|
|
|
|
2016-08-24 03:48:35 +03:00
|
|
|
```javascript
|
|
|
|
|
// meta-feature...
|
|
|
|
|
feature_set.Feature('meta-feature-tag', [
|
2021-06-02 11:52:42 +03:00
|
|
|
'suggested-feature-tag',
|
|
|
|
|
'other-suggested-feature-tag',
|
|
|
|
|
// ...
|
2016-08-24 03:48:35 +03:00
|
|
|
])
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
XXX
|
|
|
|
|
|
|
|
|
|
|
2021-06-04 23:59:37 +03:00
|
|
|
## Extending
|
|
|
|
|
|
|
|
|
|
<!-- XXX custom mixins (non-action) -->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## License
|
|
|
|
|
|
|
|
|
|
[BSD 3-Clause License](./LICENSE)
|
|
|
|
|
|
2023-09-09 16:36:32 +03:00
|
|
|
Copyright (c) 2016-2023, Alex A. Naanou,
|
2021-06-04 23:59:37 +03:00
|
|
|
All rights reserved.
|
|
|
|
|
|
|
|
|
|
|
2021-06-05 00:43:43 +03:00
|
|
|
<!--------------------------------------------------------------------LINKS--->
|
2021-06-04 23:59:37 +03:00
|
|
|
|
2021-06-06 13:59:43 +03:00
|
|
|
[ig-actions]: https://github.com/flynx/actions.js
|
2021-06-04 23:59:37 +03:00
|
|
|
[actions]: https://github.com/flynx/actions.js
|
2021-06-06 13:59:43 +03:00
|
|
|
[python]: https://www.python.org/
|
|
|
|
|
[graphvis]: https://graphviz.org/
|
2016-08-24 03:48:35 +03:00
|
|
|
|
2021-06-05 00:43:43 +03:00
|
|
|
|
2016-08-23 17:21:35 +03:00
|
|
|
<!-- vim:set ts=4 sw=4 spell : -->
|