Skip to content

Commit 26b4ef1

Browse files
authored
Merge pull request #1 from brucou/feature/library-specification
Feature/library specification
2 parents 6e10ec2 + c112ebd commit 26b4ef1

29 files changed

+7817
-404
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ node_modules
33
dist
44
dist-node
55
archive
6+
dist-node-test
7+
test/test-bundle.js
8+
gh-md-toc

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
runTestScenario
22
====
3+
TODO : called combinators because https://wiki.haskell.org/Combinator_pattern, it builds complex stuff from simple stuff
4+
5+
# ...
6+
`npm run build test ; npm run test`
37

48
## Table of content
59
* [Context](#context)

examples/todo-list-no-fsm/index.js

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
const sinkNames = ['DOM', 'Store', 'auth$']; // TODO : choose all caps or all mins
2+
import {ALL, ACTIVE, COMPLETED, IS_LOGGED_IN} from './properties';
3+
import {UPDATE, DELETE} from '../../src/drivers/store/properties';
4+
5+
App({sinkNames}, [
6+
OnRoute('/', SwitchCase({
7+
on: 'auth$'
8+
}, [
9+
Case({when: IS_LOGGED_IN}, [
10+
TodoComponent({routeState: ALL}) // actually will require flip or
11+
// curry and R.__
12+
]),
13+
Case({when: complement(IS_LOGGED_IN)}, [
14+
LogIn({redirect: '/'})
15+
])
16+
]
17+
)),
18+
OnRoute('/active', SwitchCase({
19+
on: 'auth$', sinkNames
20+
}, [
21+
Case({when: IS_LOGGED_IN}, [
22+
TodoComponent({routeState: ACTIVE}) // actually will require flip or
23+
// curry and R.__
24+
]),
25+
Case({when: complement(IS_LOGGED_IN)}, [
26+
LogIn({redirect: '/active'})
27+
])
28+
]
29+
)),
30+
OnRoute('/completed', TodoComponent({routeState: COMPLETED})
31+
)
32+
]);
33+
34+
const TodoComponent = ListManager({
35+
namespace: 'TODO'
36+
}, ListFactory({
37+
storeRef: '/'
38+
}, Item));
39+
40+
// TODO : ListManager
41+
// ...
42+
// I am here
43+
// ...
44+
// routeState is in settings
45+
// children DOM to insert in the middle of parent DOM (slot mechanism)
46+
// parent sinks (events, intents, actions) to merge with children sinks
47+
// events : complete all, add new todo, clear completed
48+
// actions : store - update complete all; store : push
49+
// ...
50+
function ListManager(listManagerSettings, component) {
51+
return function listManagerComponent(sources, componentSettings) {
52+
let {Store, DOM} = sources;
53+
let settings = merge(listManagerSettings, componentSettings);
54+
let {sinkNames, namespace, storeRef} = settings;
55+
56+
// should have parent dom as Observable<VNode|Array<VNode>|Null>
57+
// TODO : adjust DOM merge function in consequence
58+
// Also App component should add a div to the array if receives one, and
59+
// filter out null for DOM
60+
// TODO : slots specs
61+
// A slot is a VNode which is undefined everywhere except key? or data?
62+
// hence it has the index of the child to copy there, or a function
63+
// which takes the number of children and returns an array of those to copy
64+
function mergeDOMs(parentDOM, childrenDOM, settings) {
65+
childrenDOM
66+
}
67+
68+
function makeListManagerSinks(sources, settings) {
69+
70+
}
71+
72+
return m({
73+
makeOwnSinks: makeListManagerSinks,
74+
mergeSinks: {
75+
DOM: mergeDOMs
76+
}
77+
}, settings, [component]);
78+
79+
// TODO : I am here - merge childrenDOM and my DOM
80+
81+
}
82+
}
83+
84+
//// List
85+
// storeRef : ref where the items will be located
86+
function ListFactory(listSettings, component) {
87+
return function listFactoryComponent(sources, componentSettings) {
88+
let {Store, DOM} = sources;
89+
// TODO : could also keep settings separate instead of propagating downsream
90+
let settings = merge(listSettings, componentSettings);
91+
let {namespace, sinkNames, storeRef} = settings;
92+
93+
// @type Stream<Array<ItemState>>
94+
let listState$ = Store.in(namespace).get(storeRef);
95+
96+
// TODO !!!
97+
//// !! how to use todoItemData -> switch only on delete and insert not UPDATE!!
98+
return SwitchForEachNew({
99+
on: listState$, eqFn: onlyDeleteOrInsert, to: 'itemsData', sinkNames
100+
}, List({
101+
valueId: 'todoItemData',
102+
itemId: 'itemRef',
103+
}, component))
104+
}
105+
}
106+
107+
// TODO : List combinator implementation, use m? or directly inline it?
108+
// maybe inline it first
109+
function List(listSettings, component) {
110+
return function ListComponent(sources, componentSettings) {
111+
let settings = merge(listSettings, componentSettings);
112+
// beware of namespace problem : value could come from anywhere up
113+
let {itemsData, valueId, itemId} = settings; // array on which list is based
114+
115+
// listSinks :: [Sinks], itemsStates :: Array<ItemState>
116+
let listSinks = itemsData.map((itemData, index) => {
117+
let componentSettings = merge(
118+
settings,
119+
{[itemId]: index},
120+
{[valueId]: itemData}
121+
);
122+
return component(sources, componentSettings)
123+
});
124+
125+
// This following merge should be the default merge in utils
126+
const sinkNames = getSinkNames(listSinks);
127+
// Every sink but DOM
128+
let finalSinks = sinkNames.reduce((finalSinks, sinkName) => {
129+
finalSinks[sinkName] = $.merge(listSinks.map(sinks => sinks[sinkName]));
130+
return finalSinks
131+
}, {});
132+
// TODO : Add DOM treatment $.merge => $.combineLatest(Array Obs
133+
// VNode, div)
134+
// TODO : not div, discriminate case of array of size 0 or 1 or 2+
135+
136+
return finalSinks
137+
}
138+
}
139+
140+
//// Item
141+
// itemRef = storeRef + index item, passed by ListFactory parent
142+
// in Store : {completed: Boolean, text : String}
143+
function Item(sources, settings) {
144+
let {Store, DOM} = sources;
145+
let {namespace, itemRef, todoItemData} = settings;
146+
// Note : todoItemData is not used here as it already fethced with state$
147+
let state$ = Store.in(namespace).get(itemRef);
148+
149+
// TODO : replace events and selectors by actual values from view template
150+
let events = {
151+
'dbl_click': DOM.select('input area').events('double-click'),
152+
'radio_click': DOM.select('radio button').events('click'),
153+
'enter_keypress': DOM.select('input area').events('enter keypress'),
154+
'delete': DOM.select('cross').events('click'),
155+
};
156+
let intents = {
157+
'edit_item': events.dbl_click.map(_ => true),
158+
'toggle_completed': events.radio_click.map(ev => TODO), // TODO
159+
'update_item_text': events.enter_keypress.flatMap(ev => TODO), // TODO
160+
'delete_item': events.delete.map(_ => ({itemRef}))
161+
};
162+
let actions = {
163+
DOM: state$.combineLatest(intents.edit_item, combineFn).map(view), // TODO
164+
Store: {
165+
'toggle_completed': intents.toggle_completed.map(val => ({
166+
command: UPDATE,
167+
ref: itemRef,
168+
payload: {completed: val},
169+
})),
170+
'update_item_text': intents.update_item_text.map(text => ({
171+
command: UPDATE,
172+
ref: itemRef,
173+
payload: {text: text}
174+
})),
175+
'delete_item': intents.delete.map(itemRef => ({
176+
command: DELETE,
177+
ref: itemRef,
178+
payload: {}
179+
}))
180+
}
181+
};
182+
183+
return {
184+
DOM: actions.DOM,
185+
Store: merge(
186+
actions.toggle_completed,
187+
actions.update_item_text,
188+
actions.delete_item
189+
)
190+
}
191+
}
192+
193+
// TODO : other version with mergeAll and takeUntil to dynamically manage
194+
// insertion and deletiong
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export const ALL = 'all';
2+
export const ACTIVE = 'active';
3+
export const COMPLETED = 'completed';
4+
export const IS_LOGGED_IN = true;
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
const sinks = ['DOM', 'Store', 'auth$']; // TODO : choose all caps or all mins
2+
import {ALL, ACTIVE, COMPLETED, IS_LOGGED_IN} from './properties';
3+
4+
App({sinks: sinks}, [
5+
OnRoute('/', SwitchCase({
6+
on: 'auth$'
7+
}, [
8+
Case({when: IS_LOGGED_IN}, [
9+
TodoComponent({routeState: ALL}) // actually will require flip or
10+
// curry and R.__
11+
]),
12+
Case({when: complement(IS_LOGGED_IN)}, [
13+
LogIn({redirect: '/'})
14+
])
15+
]
16+
)),
17+
OnRoute('/active', SwitchCase({
18+
on: 'auth$', sinks: sinks
19+
}, [
20+
Case({when: IS_LOGGED_IN}, [
21+
TodoComponent({routeState: ACTIVE}) // actually will require flip or
22+
// curry and R.__
23+
]),
24+
Case({when: complement(IS_LOGGED_IN)}, [
25+
LogIn({redirect: '/active'})
26+
])
27+
]
28+
)),
29+
OnRoute('/completed', TodoComponent({routeState: COMPLETED})
30+
)
31+
]);
32+
33+
TodoComponent = curry(flip(
34+
StateChart({
35+
sinks: [sinkNames],
36+
//some UNIQUE (vs. action requests) identifier for the state chart
37+
namespace: 'TODO_SC',
38+
// changed -> to compute x items left, removed -> to compute x items left
39+
intents: {ADD_ITEM, COMPLETE_ALL, CHILD_CHANGED, CHILD_REMOVED},
40+
actions: [ADD_ITEM, COMPLETE_ALL],
41+
responses: ['Store'],
42+
model$: 0,//{Store.get(namespace)(null), showOnlyActive : settings.routeState},
43+
transitions: [
44+
//INIT -> INIT -> ERROR: ?? -> NO GUARD -> ENTRY :
45+
(event, model$) => {
46+
// Can have action requests with/out a response if FSM does not care
47+
// Can be DOM updates, route changes for example, but not ADD_ITEM
48+
SwitchForEach(model$, List(prepare(model), TodoItemComponent))
49+
},
50+
// INIT -> SUCCESS:INIT -> ERROR: ?? -> NO GUARD -> ADD_ITEM :
51+
(event, model$) => ({
52+
Store: $.of({command: ADD_TODO, namespace, ref, payload: todoText}),
53+
// If one wants optimistic update, can also add:
54+
DOM: model$.map(ADD_ITEM_model_update).map(view)
55+
}),
56+
// INIT -> SUCCESS:INIT -> ERROR: ?? -> NO GUARD -> COMPLETE_ALL :
57+
(event, model$) => ({
58+
Store: model$.map(childIndex => ({
59+
namespace,
60+
reference: childRef(childIndex, parentRef),
61+
command: UPDATE,
62+
payload: refValue => setCompleted(refValue)
63+
// setCompleted = refValue => (refValue.completed = true, refValue)
64+
}))
65+
}),
66+
// ADD_ITEM_ERROR -> ??? -> ERROR: ?? -> NO_GUARD -> AUTO :
67+
// could do multiple retry through this mechanism
68+
],
69+
})
70+
));
71+

package.json

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,26 @@
66
"license": "Apache-2.0",
77
"repository": "https://github.com/brucou/rxcc.git",
88
"main": "dist-node/index.js",
9+
"module" : "src/index.js",
910
"jsnext:main": "src/index.js",
1011
"jspm": {
1112
"main": "dist/rxcc.js"
1213
},
1314
"scripts": {
1415
"build-node": "babel src --out-dir dist-node",
15-
"build-node-test": "babel test --out-dir dist-node-test",
16+
"build-node-test": "babel test --source-maps --out-dir dist-node-test",
17+
"build-node-test2": "cp test dist-node-test",
1618
"build-browser": "npm run browserify && npm run uglify",
1719
"browserify": "browserify -d --standalone rxcc dist-node | derequire > dist/rxcc.js",
1820
"uglify": "uglifyjs -mc < dist/rxcc.js > dist/rxcc.min.js",
1921
"clean": "rimraf dist dist-node && mkdirp dist dist-node",
2022
"clean-test": "rimraf dist-node-test && mkdirp dist-node-test",
2123
"build": "npm run clean && npm run build-node && npm run build-browser",
2224
"prepublish": "npm run build",
23-
"build-test2": "npm run clean-test && npm run build-node-test && npm run test",
24-
"build-test": "npm run build-node-test && npm run test",
25-
"test": "browserify -d --transform babelify dist-node-test | derequire > test/test-bundle.js"
25+
"build-test2": "npm run clean-test && npm run build-node-test && npm run test",
26+
"build-test": "npm run build-node-test2 && npm run test",
27+
"test-no-watch": "browserify -d --transform babelify dist-node-test | derequire > test/test-bundle.js",
28+
"test": "watchify -d --transform babelify dist-node-test -o test/test-bundle.js -v"
2629
},
2730
"devDependencies": {
2831
"babel-preset-es2015": "^6.18.0",
@@ -44,11 +47,15 @@
4447
"babel-preset-es2015": "^6.18.0",
4548
"babel-register": "^6.18.0",
4649
"babelify": "^7.3.0",
50+
"cycle-snabbdom": "^3.0.0",
51+
"fast-json-patch": "^1.1.3",
52+
"mermaid": "^6.0.0",
4753
"mocha": "^3.1.2",
4854
"ramda": "^0.22.1",
49-
"snabbdom": "^0.5.0",
50-
"cycle-snabbdom": "^3.0.0",
5155
"rx": "^4.1.0",
52-
"mermaid": "^6.0.0"
56+
"snabbdom": "^0.5.0",
57+
"standard-error": "^1.1.0",
58+
"watchify": "^3.8.0",
59+
"route-matcher" : "^0.1.0"
5360
}
5461
}

0 commit comments

Comments
 (0)