Replace Renderer with DOMUser, allowing Views to be event agnostic.
This breaking change version implements #73. From now on, instead of using onclick: 'click$'
in properties in Views, you instead select which events you are interested in, with User.event$('.myelement', 'click')
inside Intents.
This also brings an interesting twist to the overall MVI architecture. It's now MVUI: Model-View-User-Intent. There is no more Renderer (a DataFlowSink), now we have DOMUser (a DataFlowNode). This means that the DOMUser acts as a function: takes the View's vtree$ as input, renders that to the DOM as a side-effect, and outputs DOM events. You can select which DOM event stream by calling User.event$(selector, eventName)
, and of course, the output is an RxJS Observable.
Migration guide:
BEFORE
var HelloView = Cycle.createView(Model =>
({
vtree$: Model.get('name$').map(name =>
h('div', [
h('label', 'Name:'),
h('input', {
attributes: {'type': 'text'},
oninput: 'inputText$'
}),
h('h1', 'Hello ' + name)
])
)
})
);
var HelloIntent = Cycle.createIntent(View =>
({changeName$: View.get('inputText$').map(ev => ev.target.value)})
);
AFTER
var View = Cycle.createView(Model =>
({
vtree$: Model.get('name$').map(name =>
h('div', [
h('label', 'Name:'),
h('input.field', {attributes: {type: 'text'}}),
// notice no more oninput here
h('h1', 'Hello ' + name)
])
)
})
);
// Notice this, replaces Renderer, but has basically the same API
var User = Cycle.createDOMUser('.js-container');
// Notice Intent from now on should always take a DOMUser as input, NOT a View
var Intent = Cycle.createIntent(User =>
// Notice the usage of User.event$()
({changeName$: User.event$('.field', 'input').map(ev => ev.target.value)})
);
And since DOMUser is now a DataFlowNode in the middle of the cycle, the injection part becomes:
User.inject(View).inject(Model).inject(Intent).inject(User);
Custom elements
Are now slightly simpler to define. No need for a View as an implementation, and it doesn't need to return vtree$ anymore since it will have it's own internal DOMUser.
// function, not createView. And notice element here
var HelloComponent = function(element, Properties) {
var HelloModel = Cycle.createModel((Properties, Intent) =>
({
name$: Properties.get('name$')
.merge(Intent.get('changeName$'))
.startWith('')
})
);
var HelloView = Cycle.createView(Model =>
({
vtree$: Model.get('name$')
.map(name =>
h('div', [
h('label', 'Name:'),
h('input.myinput', {attributes: {type: 'text'}}),
h('h1', 'Hello ' + name)
])
)
})
);
// Notice DOMUser internal here. It should use the element from above
var HelloUser = Cycle.createDomUser(element);
var HelloIntent = Cycle.createIntent(User =>
({changeName$: User.events('.myinput', 'click').map(ev => ev.target.value)})
);
HelloIntent
.inject(HelloUser)
.inject(HelloView)
.inject(HelloModel)
.inject(Properties, HelloIntent);
return {
// notice no more vtree$ exported here
// this part is only used for custom events exported out of this component
};
};
Cycle.registerCustomElement('hello', HelloComponent);
Basically, v0.12 is the user as a function in your UI architecture, and the View focusing only on what it does best: rendering.