/**
* @copyright 2015 Tridium, Inc. All Rights Reserved
* @author Logan Byam
*/
/**
* @module bajaux/mixin/batchSaveMixin
*/
define([ 'baja!',
'bajaux/Widget',
'Promise',
'underscore' ], function (
baja,
Widget,
Promise,
_) {
'use strict';
var MIXIN_NAME = 'batchSave',
COMMIT_READY = 'commitReady';
function batchSave(ed, 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.save(params) ];
}
/**
* Applies the `batchSave` mixin to the target Widget.
*
* The `batchSave` 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 `save()` method (thus allowing
* multiple `Widget`s to save BajaScript values in a single network call).
*
* It states:
*
* - If my `save()` method does receive a `batch` parameter, and does add
* a transaction to it (say, by passing it to `Component#set`), 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 `save()` 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 `batchSaveMixin` to a widget that does not
* actually use a batch.)
*
* Widgets that append transactions to a `batch` parameter in the `save()`
* function, _without_ marking themselves with this mixin, should be expected
* have those saves fail. Likewise, passing a batch to a Widget's `save()`
* function without checking whether it has the `batchSave` mixin can also
* fail.
*
* Why is this contract necessary? When passing a batch to `save()`, you
* aren't guaranteed that `save()` 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, `batchSaveMixin.saveWidgets` handles a lot of this
* workflow for you.
*
* @class
* @alias module:bajaux/mixin/batchSaveMixin
* @param {module:bajaux/Widget} target
*
* @example
* <caption>Example implementation of the batchSave contract.</caption>
* MyWidget.prototype.doSave = function (component, params) {
* var batch = params && params.batch,
* progressCallback = params && params.progressCallback,
* promise = component.set({ slot: 'saved', value: true, batch: batch });
*
* //I'm done with the batch - let the caller know they can commit it
* if (progressCallback) {
* progressCallback(batchSaveMixin.COMMIT_READY);
* }
*
* return promise;
* };
*/
var batchSaveMixin = function batchSaveMixin(target) {
if (!(target instanceof Widget)) {
throw new Error("batchSave mixin only applies to instances or sub-classes of Widget");
}
var mixins = target.$mixins;
if (!_.contains(mixins, MIXIN_NAME)) {
mixins.push(MIXIN_NAME);
}
};
/**
* Saves the given widgets, passing one `Batch` into the `save()` method for
* each one.
*
* Widgets that make use of the `Batch` are expected to have `batchSaveMixin`.
* See documentation for the mixin itself for contractual details.
*
* @param {Array.<module:bajaux/Widget>} widgets the widgets to save
* @param {Object} [params]
* @param {baja.comm.Batch} [params.batch] a batch to pass into each widget's
* `save` 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
* saving
*/
batchSaveMixin.saveWidgets = function saveWidgets(widgets, params) {
var batchParam = params && params.batch,
progressCallback = params && params.progressCallback,
batch = batchParam || new baja.comm.Batch();
var results = _.map(widgets, function (kid) {
return batchSave(kid, batch);
}),
savePromises = _.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.
Promise.all(commitPromises)
.then(function () {
if (!batchParam) {
batch.commit();
}
if (progressCallback) {
progressCallback(COMMIT_READY);
}
})
.catch((err) => baja.error(err));
return Promise.all(savePromises);
};
/**
* Value to be passed to a `progressCallback` parameter to indicate that
* a batch given to the `save()` function can be safely committed.
* @constant
* @type {string}
*/
batchSaveMixin.COMMIT_READY = COMMIT_READY;
return batchSaveMixin;
});