/**
* @copyright 2015 Tridium, Inc. All Rights Reserved.
* @author Logan Byam
*/
/**
* @module bajaux/mixin/batchLoadMixin
*/
define([ 'baja!',
'bajaux/Widget',
'Promise',
'underscore' ], function (
baja,
Widget,
Promise,
_) {
'use strict';
var MIXIN_NAME = 'batchLoad',
COMMIT_READY = 'commitReady';
function batchLoad(ed, value, batch) {
var readyToCommit,
params = { batch: batch };
if (ed.hasMixIn(MIXIN_NAME)) {
// eslint-disable-next-line promise/avoid-new
readyToCommit = new Promise(function (resolve, reject) {
params.progressCallback = function (msg) {
if (msg === COMMIT_READY) { resolve(); }
};
});
}
return [ readyToCommit, ed.load(value, params) ];
}
/**
* Applies the `batchLoad` mixin to the target Widget.
*
* The `batchLoad` mixin does not alter the behavior of the target Widget,
* but instead defines a behavioral contract. It defines the way it will
* handle a `baja.comm.Batch` passed to the `load()` method (thus allowing
* multiple `Widget`s to load and subscribe BajaScript values in a single
* network call).
*
* It states:
*
* - If my `load()` method does receive a `batch` parameter, and does add
* a transaction to it (say, by passing it to
* `baja.comm.Subscriber#subscribe`), then I must notify the caller after I
* am through adding transactions to that `Batch` and it is safe to commit.
* I do this by checking for a `progressCallback` parameter, and passing
* `COMMIT_READY` to it.
* - If my `load()` method does not make use of the batch, it must still
* emit `COMMIT_READY`, but can do so at any time. (Due to this constraint,
* it does not make sense to add `batchLoadMixin` to a widget that does not
* actually use a batch.)
*
* Widgets that append transactions to a `batch` parameter in the `load()`
* function, _without_ marking themselves with this mixin, should be expected
* have those loads fail. Likewise, passing a batch to a Widget's `load()`
* function without checking whether it has the `batchLoad` mixin can also
* fail.
*
* Why is this contract necessary? When passing a batch to `load()`, you
* aren't guaranteed that `load()` will not perform some other asynchronous
* work before appending transactions to the batch. If you don't wait for
* the transactions to complete, you run the risk of committing the batch
* prematurely. Then when the widget gets around to appending transactions
* to the already-committed batch, it will fail.
*
* To make this easier, `batchLoadMixin.loadWidgets` handles a lot of this
* workflow for you.
*
* @class
* @alias module:bajaux/mixin/batchLoadMixin
* @param {module:bajaux/Widget} target
*
* @example
* <caption>Example implementation of the batchLoad contract.</caption>
* MyWidget.prototype.doLoad = function (component, params) {
* var batch = params && params.batch,
* progressCallback = params && params.progressCallback,
* promise = this.getSubscriber().subscribe({
* comps: component,
* batch: batch
* });
*
* //I'm done with the batch - let the caller know they can commit it
* if (progressCallback) {
* progressCallback(batchLoadMixin.COMMIT_READY);
* }
*
* return promise;
* };
*/
var batchLoadMixin = function (target) {
if (!(target instanceof Widget)) {
throw new Error("batchLoad mixin only applies to instances or sub-classes of Widget");
}
var mixins = target.$mixins;
if (!_.contains(mixins, MIXIN_NAME)) {
mixins.push(MIXIN_NAME);
}
};
/**
* Loads values into the given widgets, passing one `Batch` into the `load()`
* method for each one.
*
* Widgets that make use of the `Batch` are expected to have `batchLoadMixin`.
* See documentation for the mixin itself for contractual details.
*
* @param {Array.<module:bajaux/Widget>} widgets the widgets to load
* @param {Array.<*>} values values to load into the widgets
* @param {Object} [params]
* @param {baja.comm.Batch} [params.batch] a batch to pass into each widget's
* `load` method. If none is given, a new batch will be created and committed.
* @param {Function} [params.progressCallback] This callback function itself
* will receive `COMMIT_READY` when the input batch is ready to commit.
* The callback will not be fired if no batch is input.
* @returns {Promise} promise to be resolved when all widgets have completed
* loading
*/
batchLoadMixin.loadWidgets = function (widgets, values, params) {
if (widgets.length !== values.length) {
return Promise.reject(new Error('different numbers of widgets and values'));
}
var batchParam = params && params.batch,
progressCallback = params && params.progressCallback,
batch = batchParam || new baja.comm.Batch();
var results = _.map(widgets, function (kid, i) {
return batchLoad(kid, values[i], batch);
}),
loadPromises = _.map(results, function (arr) { return arr[1]; }),
commitPromises = _.map(results, function (arr) { return arr[0]; });
//widgets will tell us when they've registered network calls with
//the batch and are ready for us to commit it.
return Promise.all([
Promise.all(commitPromises)
.then(function () {
if (!batchParam) {
batch.commit();
}
if (progressCallback) {
progressCallback(COMMIT_READY);
}
}),
Promise.all(loadPromises)
]);
};
/**
* Value to be passed to a `progressCallback` parameter to indicate that
* a batch given to the `load()` function can be safely committed.
* @constant
* @type {string}
*/
batchLoadMixin.COMMIT_READY = COMMIT_READY;
return batchLoadMixin;
});