Skip to content

Commit

Permalink
Change API of driver responses per driver
Browse files Browse the repository at this point in the history
  • Loading branch information
Andre Medeiros committed Jun 15, 2015
1 parent 6d08e85 commit eefbf88
Show file tree
Hide file tree
Showing 24 changed files with 347 additions and 372 deletions.
6 changes: 3 additions & 3 deletions README.md
Expand Up @@ -20,7 +20,7 @@ let {h} = Cycle;

function main(drivers) {
return {
DOM: drivers.get('DOM', '.myinput', 'input')
DOM: drivers.DOM.get('.myinput', 'input')
.map(ev => ev.target.value)
.startWith('')
.map(name =>
Expand All @@ -39,8 +39,8 @@ Cycle.run(main, {
});
```

The input of `main` is `drivers`, a queryable collection of Observables from the "external world", containing for instance user events happening on elements on the DOM.
`drivers.get('DOM', selector, eventType)` returns an Observable of `eventType` events happening on elements specified by `selector`. This goes through a series of RxJS operations to produce an Observable of virtual DOM elements, which is returned and tagged `DOM`. Function `Cycle.run()` will take your `main` function and circularly connect it to the specified "driver functions". The DOM driver function acts on behalf of the user: takes the tagged `DOM` Observable of virtual elements returned from `main()`, shows that on the screen as a side-effect, and outputs Observables of user interaction events. The result of this is Human-Computer Interaction, i.e. a dialogue between `main()` and the DOM driver function, happening under the container element selected by `'.js-container'`.
The input of `main` is `drivers`, a collection of Observables from the "external world", containing for instance user events happening on elements on the DOM.
`drivers.DOM` is a queryable collection of Observable events happening from the DOM. Query it using a getter: `get(selector, eventType)` returns an Observable of `eventType` events happening on elements specified by `selector`. This goes through a series of RxJS operations to produce an Observable of virtual DOM elements, which is returned and tagged `DOM`. Function `Cycle.run()` will take your `main` function and circularly connect it to the specified "driver functions". The DOM driver function acts on behalf of the user: takes the tagged `DOM` Observable of virtual elements returned from `main()`, shows that on the screen as a side-effect, and outputs Observables of user interaction events. The result of this is Human-Computer Interaction, i.e. a dialogue between `main()` and the DOM driver function, happening under the container element selected by `'.js-container'`.

For advanced examples, check out [TodoMVC implemented in Cycle.js](https://github.com/staltz/todomvc-cycle) and [RxMarbles](https://github.com/staltz/rxmarbles).

Expand Down
145 changes: 60 additions & 85 deletions dist/cycle.js
Expand Up @@ -14772,12 +14772,9 @@ var Cycle = {
*
* The `app` function expects a collection of "driver response" Observables as
* input, and should return a collection of "driver request" Observables.
* The driver response collection can be queried using a getter function:
* `responses.get(driverName, ...params)`, returns an Observable. The
* structure of `params` is defined by the API of the corresponding
* `driverName`. The driver request collection should be a simple object where
* keys match the driver names used by `responses.get()` and defined on the
* second parameter given to `run()`.
* A "collection of Observables" is a JavaScript object where
* keys match the driver names registered by the `drivers` object, and values
* are Observables or a collection of Observables.
*
* @param {Function} app a function that takes `responses` as input
* and outputs a collection of `requests` Observables.
Expand All @@ -14794,15 +14791,19 @@ var Cycle = {
* A factory for the DOM driver function. Takes a `container` to define the
* target on the existing DOM which this driver will operate on. All custom
* elements which this driver can detect should be given as the second
* parameter.
* parameter. The output of this driver is a collection of Observables queried
* by a getter function: `domDriverOutput.get(selector, eventType)` returns an
* Observable of events of `eventType` happening on the element determined by
* `selector`. Also, `domDriverOutput.get(':root')` returns an Observable of
* DOM element corresponding to the root (or container) of the app on the DOM.
*
* @param {(String|HTMLElement)} container the DOM selector for the element
* (or the element itself) to contain the rendering of the VTrees.
* @param {Object} customElements a collection of custom element definitions. The key of each
* property should be the tag name of the custom element, and the value should
* be a function defining the implementation of the custom element. This
* function follows the same contract as the top-most `app` function: input
* are driver responses, output are requests to drivers.
* @param {Object} customElements a collection of custom element definitions.
* The key of each property should be the tag name of the custom element, and
* the value should be a function defining the implementation of the custom
* element. This function follows the same contract as the top-most `app`
* function: input are driver responses, output are requests to drivers.
* @return {Function} the DOM driver function. The function expects an
* Observable of VTree as input, and outputs the response object for this
* driver, containing functions `get()` and `dispose()` that can be used for
Expand All @@ -14817,17 +14818,14 @@ var Cycle = {
* the custom element registry to detect custom element on the VTree and apply
* their implementations.
*
* @param {Object} customElements a collection of custom element definitions. The key of each
* property should be the tag name of the custom element, and the value should
* be a function defining the implementation of the custom element. This
* function follows the same contract as the top-most `app` function: input
* are driver responses, output are requests to drivers.
* @param {Object} customElements a collection of custom element definitions.
* The key of each property should be the tag name of the custom element, and
* the value should be a function defining the implementation of the custom
* element. This function follows the same contract as the top-most `app`
* function: input are driver responses, output are requests to drivers.
* @return {Function} the HTML driver function. The function expects an
* Observable of Virtual DOM elements as input, and outputs the response
* object for this driver, containing functions `get()` and `dispose()` that
* can be used for debugging and testing. To get the Observable of strings as
* the HTML renderization of the virtual DOM elements, call simply
* `get(htmlDriverName)` on the responses object returned by Cycle.run();
* Observable of Virtual DOM elements as input, and outputs an Observable of
* strings as the HTML renderization of the virtual DOM elements.
* @function makeHTMLDriver
*/
makeHTMLDriver: makeHTMLDriver,
Expand Down Expand Up @@ -14880,29 +14878,6 @@ function callDrivers(drivers, requestProxies) {
return responses;
}

function makeGet(rawResponses) {
return function get(driverName) {
for (var _len = arguments.length, params = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
params[_key - 1] = arguments[_key];
}

if (!rawResponses.hasOwnProperty(driverName)) {
throw new Error('get(' + driverName + ', ...) failed, no driver function ' + ('named ' + driverName + ' was found for this Cycle execution.'));
}

var driverResponse = rawResponses[driverName];
if (typeof driverResponse.subscribe === 'function') {
return driverResponse; // is an Observable
} else if (typeof driverResponse === 'object' && typeof driverResponse.get === 'function') {
return rawResponses[driverName].get.apply(null, params);
} else if (typeof driverResponse === 'object' && params.length > 0 && typeof params[0] === 'string' && driverResponse.hasOwnProperty(params[0])) {
return rawResponses[driverName][params[0]];
} else {
throw new Error('get(' + driverName + ', ...) failed because driver was ' + 'not able to process parameters. Report this bug to the driver ' + 'function author.');
}
};
}

function makeDispose(requestProxies, rawResponses) {
return function dispose() {
for (var x in requestProxies) {
Expand All @@ -14919,10 +14894,11 @@ function makeDispose(requestProxies, rawResponses) {
}

function makeAppInput(requestProxies, rawResponses) {
return {
get: makeGet(rawResponses),
dispose: makeDispose(requestProxies, rawResponses)
};
Object.defineProperty(rawResponses, 'dispose', {
enumerable: false,
value: makeDispose(requestProxies, rawResponses)
});
return rawResponses;
}

function replicateMany(original, imitators) {
Expand Down Expand Up @@ -14969,6 +14945,9 @@ module.exports = run;

},{"rx":58}],110:[function(require,module,exports){
'use strict';

function _defineProperty(obj, key, value) { return Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); }

var Rx = require('rx');
var ALL_PROPS = '*';
var PROPS_DRIVER_NAME = 'props';
Expand Down Expand Up @@ -15031,10 +15010,12 @@ function makePropertiesDriver() {
});
Object.defineProperty(propertiesDriver, 'get', {
enumerable: false,
value: function get() {
var streamKey = arguments[0] === undefined ? ALL_PROPS : arguments[0];
value: function get(streamKey) {
var comparer = arguments[1] === undefined ? defaultComparer : arguments[1];

if (typeof streamKey === 'undefined') {
throw new Error('Custom element driver `props.get()` expects an ' + 'argument in the getter.');
}
if (typeof this[streamKey] === 'undefined') {
this[streamKey] = new Rx.ReplaySubject(1);
}
Expand Down Expand Up @@ -15065,21 +15046,9 @@ function throwIfVTreeHasPropertyChildren(vtree) {
}

function makeCustomElementInput(domOutput, propertiesDriver, domDriverName) {
return {
get: function get(driverName) {
for (var _len = arguments.length, params = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
params[_key - 1] = arguments[_key];
}
var _ref;

if (driverName === domDriverName) {
return domOutput.get.apply(null, params);
} else if (driverName === PROPS_DRIVER_NAME) {
return propertiesDriver.get.apply(propertiesDriver, params);
} else {
throw new Error('No such internal driver named \'' + driverName + '\' for ' + ('custom elements. Use \'' + domDriverName + '\' or ') + ('\'' + PROPS_DRIVER_NAME + '\' instead.'));
}
}
};
return (_ref = {}, _defineProperty(_ref, domDriverName, domOutput), _defineProperty(_ref, PROPS_DRIVER_NAME, propertiesDriver), _ref);
}

function makeConstructor() {
Expand Down Expand Up @@ -15234,8 +15203,9 @@ module.exports = {
makeInit: makeInit,
updateCustomElement: updateCustomElement,
destroyCustomElement: destroyCustomElement,
makeCustomElementInput: makeCustomElementInput,

ALL_PROPS: ALL_PROPS,
makeCustomElementInput: makeCustomElementInput,
makeWidgetClass: makeWidgetClass
};

Expand Down Expand Up @@ -15438,7 +15408,7 @@ function makeRootElemToEvent$(selector, eventName) {
};
}

function makeGet(rootElem$) {
function makeResponseGetter(rootElem$) {
return function get(selector, eventName) {
if (typeof selector !== 'string') {
throw new Error('DOM driver\'s get() expects first argument to be a ' + 'string as a CSS selector');
Expand Down Expand Up @@ -15468,7 +15438,7 @@ function makeDOMDriverWithRegistry(container, CERegistry) {
var rootElem$ = fixRootElem$(rawRootElem$, container);
var disposable = rootElem$.connect();
return {
get: makeGet(rootElem$),
get: makeResponseGetter(rootElem$),
dispose: disposable.dispose.bind(disposable)
};
};
Expand Down Expand Up @@ -15500,7 +15470,7 @@ module.exports = {
makeDiffAndPatchToElement$: makeDiffAndPatchToElement$,
getRenderRootElem: getRenderRootElem,
renderRawRootElem$: renderRawRootElem$,
makeGet: makeGet,
makeResponseGetter: makeResponseGetter,
validateDOMDriverInput: validateDOMDriverInput,
makeDOMDriverWithRegistry: makeDOMDriverWithRegistry,

Expand All @@ -15520,11 +15490,16 @@ var makeCustomElementsRegistry = _require.makeCustomElementsRegistry;
var _require2 = require('./custom-element-widget');

var makeCustomElementInput = _require2.makeCustomElementInput;
var ALL_PROPS = _require2.ALL_PROPS;

function makePropertiesDriverFromVTree(vtree) {
return {
get: function get(propertyName) {
return Rx.Observable.just(vtree.properties[propertyName]);
if (propertyName === ALL_PROPS) {
return Rx.Observable.just(vtree.properties);
} else {
return Rx.Observable.just(vtree.properties[propertyName]);
}
}
};
}
Expand Down Expand Up @@ -15575,27 +15550,27 @@ function convertCustomElementsToVTree(vtree$, CERegistry, driverName) {
return vtree$.map(makeReplaceCustomElementsWithVTree$(CERegistry, driverName)).flatMap(transposeVTree);
}

function makeResponseGetter() {
return function get(selector) {
if (selector === ':root') {
return this;
} else {
return Rx.Observable.empty();
}
};
}

function makeHTMLDriver() {
var customElementDefinitions = arguments[0] === undefined ? {} : arguments[0];

var registry = makeCustomElementsRegistry(customElementDefinitions);
return function htmlDriver(vtree$, driverName) {
var vtreeLast$ = vtree$.last();
return {
get: function get() {
for (var _len2 = arguments.length, params = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
params[_key2] = arguments[_key2];
}

if (params.length === 0) {
return convertCustomElementsToVTree(vtreeLast$, registry, driverName).map(function (vtree) {
return toHTML(vtree);
});
} else {
return Rx.Observable.empty();
}
}
};
var output$ = convertCustomElementsToVTree(vtreeLast$, registry, driverName).map(function (vtree) {
return toHTML(vtree);
});
output$.get = makeResponseGetter();
return output$;
};
}

Expand Down
2 changes: 1 addition & 1 deletion dist/cycle.min.js

Large diffs are not rendered by default.

38 changes: 18 additions & 20 deletions docs/api.md
Expand Up @@ -15,12 +15,9 @@ of driver functions.

The `app` function expects a collection of "driver response" Observables as
input, and should return a collection of "driver request" Observables.
The driver response collection can be queried using a getter function:
`responses.get(driverName, ...params)`, returns an Observable. The
structure of `params` is defined by the API of the corresponding
`driverName`. The driver request collection should be a simple object where
keys match the driver names used by `responses.get()` and defined on the
second parameter given to `run()`.
A "collection of Observables" is a JavaScript object where
keys match the driver names registered by the `drivers` object, and values
are Observables or a collection of Observables.

#### Arguments:

Expand All @@ -39,15 +36,19 @@ can be used for debugging or testing.
A factory for the DOM driver function. Takes a `container` to define the
target on the existing DOM which this driver will operate on. All custom
elements which this driver can detect should be given as the second
parameter.
parameter. The output of this driver is a collection of Observables queried
by a getter function: `domDriverOutput.get(selector, eventType)` returns an
Observable of events of `eventType` happening on the element determined by
`selector`. Also, `domDriverOutput.get(':root')` returns an Observable of
DOM element corresponding to the root (or container) of the app on the DOM.

#### Arguments:

- `container :: String|HTMLElement` the DOM selector for the element (or the element itself) to contain the rendering of the VTrees.
- `customElements :: Object` a collection of custom element definitions. The key of each property should be the tag name of the custom element, and the value should
be a function defining the implementation of the custom element. This
function follows the same contract as the top-most `app` function: input
are driver responses, output are requests to drivers.
- `customElements :: Object` a collection of custom element definitions. The key of each property should be the tag name of the custom element, and
the value should be a function defining the implementation of the custom
element. This function follows the same contract as the top-most `app`
function: input are driver responses, output are requests to drivers.

#### Return:

Expand All @@ -66,18 +67,15 @@ their implementations.

#### Arguments:

- `customElements :: Object` a collection of custom element definitions. The key of each property should be the tag name of the custom element, and the value should
be a function defining the implementation of the custom element. This
function follows the same contract as the top-most `app` function: input
are driver responses, output are requests to drivers.
- `customElements :: Object` a collection of custom element definitions. The key of each property should be the tag name of the custom element, and
the value should be a function defining the implementation of the custom
element. This function follows the same contract as the top-most `app`
function: input are driver responses, output are requests to drivers.

#### Return:

*(Function)* the HTML driver function. The function expects an Observable of Virtual DOM elements as input, and outputs the response
object for this driver, containing functions `get()` and `dispose()` that
can be used for debugging and testing. To get the Observable of strings as
the HTML renderization of the virtual DOM elements, call simply
`get(htmlDriverName)` on the responses object returned by Cycle.run();
*(Function)* the HTML driver function. The function expects an Observable of Virtual DOM elements as input, and outputs an Observable of
strings as the HTML renderization of the virtual DOM elements.

- - -

Expand Down
2 changes: 1 addition & 1 deletion examples/hello/hello.js
Expand Up @@ -3,7 +3,7 @@ var h = Cycle.h;

function main(ext) {
return {
DOM: ext.get('DOM', '.myinput', 'input')
DOM: ext.DOM.get('.myinput', 'input')
.map(function (ev) { return ev.target.value; })
.startWith('')
.map(function (name) {
Expand Down
4 changes: 2 additions & 2 deletions examples/isomorphic/app.js
Expand Up @@ -31,11 +31,11 @@ function renderAboutPage() {
}

function app(ext) {
let routeFromClick$ = ext.get('DOM', '.link', 'click')
let routeFromClick$ = ext.DOM.get('.link', 'click')
.doOnNext(ev => ev.preventDefault())
.map(ev => ev.currentTarget.attributes.href.value);

let ongoingContext$ = ext.get('context')
let ongoingContext$ = ext.context
.merge(routeFromClick$).scan((acc, x) => {
acc.route = x;
return acc;
Expand Down
8 changes: 1 addition & 7 deletions examples/isomorphic/client.js
Expand Up @@ -2,13 +2,7 @@
let Cycle = require('../../lib/core/cycle');
let {app} = require('./app');

function contextDriver() {
return {
get: () => Cycle.Rx.Observable.just(window.appContext)
};
}

Cycle.run(app, {
DOM: Cycle.makeDOMDriver('.app-container'),
context: contextDriver
context: () => Cycle.Rx.Observable.just(window.appContext)
});

0 comments on commit eefbf88

Please sign in to comment.