/**
* @copyright 2015 Tridium, Inc. All Rights Reserved.
* @author Gareth Johnson
*/
/* eslint-disable camelcase */
/* global niagara_wb_util_getSessionOrdInHost: false */
/**
* @module baja/boxcs/BoxComponentSpace
* @private
*/
define([
"bajaScript/bson",
"bajaScript/baja/boxcs/BoxCallbacks",
"bajaScript/baja/boxcs/syncUtil",
"bajaScript/baja/comp/ComponentSpace",
"bajaScript/baja/ord/Ord",
"bajaScript/baja/boxcs/AddKnobOp",
"bajaScript/baja/boxcs/AddOp",
"bajaScript/baja/boxcs/FireTopicOp",
"bajaScript/baja/boxcs/LoadOp",
"bajaScript/baja/boxcs/RemoveKnobOp",
"bajaScript/baja/boxcs/RemoveOp",
"bajaScript/baja/boxcs/RenameOp",
"bajaScript/baja/boxcs/ReorderOp",
"bajaScript/baja/boxcs/SetOp",
"bajaScript/baja/boxcs/SetFacetsOp",
"bajaScript/baja/boxcs/SetFlagsOp",
"bajaScript/baja/boxcs/AddRelationKnobOp",
"bajaScript/baja/boxcs/RemoveRelationKnobOp",
"bajaScript/baja/comm/Callback",
"bajaPromises" ], function (
baja,
BoxCallbacks,
syncUtil,
ComponentSpace,
Ord,
AddKnobOp,
AddOp,
FireTopicOp,
LoadOp,
RemoveKnobOp,
RemoveOp,
RenameOp,
ReorderOp,
SetOp,
SetFacetsOp,
SetFlagsOp,
AddRelationKnobOp,
RemoveRelationKnobOp,
Callback,
bajaPromises) {
"use strict";
var subclass = baja.subclass,
callSuper = baja.callSuper,
bajaDef = baja.def,
serverDecodeContext = baja.$serverDecodeContext,
importUnknownTypes = baja.bson.importUnknownTypes,
bsonDecodeValue = baja.bson.decodeValue,
syncVal = syncUtil.syncVal,
eventsQueue = [],
syncOps = {};
syncOps[AddKnobOp.id] = AddKnobOp;
syncOps[AddOp.id] = AddOp;
syncOps[FireTopicOp.id] = FireTopicOp;
syncOps[LoadOp.id] = LoadOp;
syncOps[RemoveKnobOp.id] = RemoveKnobOp;
syncOps[RemoveOp.id] = RemoveOp;
syncOps[RenameOp.id] = RenameOp;
syncOps[ReorderOp.id] = ReorderOp;
syncOps[SetOp.id] = SetOp;
syncOps[SetFacetsOp.id] = SetFacetsOp;
syncOps[SetFlagsOp.id] = SetFlagsOp;
syncOps[AddRelationKnobOp.id] = AddRelationKnobOp;
syncOps[RemoveRelationKnobOp.id] = RemoveRelationKnobOp;
/**
* BOX Component Space.
*
* A BOX Component Space is a Proxy Component Space that's linked to another
* Component Space in another host elsewhere.
*
* @class
* @name baja.BoxComponentSpace
* @extends baja.ComponentSpace
* @private
*
* @param {String} name
* @param {String} ordInHost
* @param host
*/
function BoxComponentSpace(name, ordInHost, host) {
callSuper(BoxComponentSpace, this, arguments);
this.$callbacks = new BoxCallbacks(this);
}
subclass(BoxComponentSpace, ComponentSpace);
/**
* Call to initialize a Component Space.
*
* @name baja.BoxComponentSpace#init
* @function
*
* @private
*
* @param {baja.comm.Batch} batch
*/
BoxComponentSpace.prototype.init = function (batch) {
// Any events are sync ops so process then in the normal way
var that = this,
id = that.getServerHandlerId(),
cb;
function eventHandler(events, callback) {
that.$fw("commitSyncOps", events, callback);
}
try {
// Make the server side Handler for this Component Space
baja.comm.makeServerHandler(id, // The id of the Server Session Handler to be created
"box:ComponentSpaceSessionHandler", // Type Spec of the Server Session Handler
id, // Initial argument for the Server Session Handler
eventHandler,
new Callback(function (resp) {
that.$isReadonly = resp.isReadonly;
}, baja.fail, batch),
/*makeInBatch*/true);
// Load Root Component of Station
cb = new Callback(function ok(resp) {
// Create the root of the Station
that.$root = baja.$(resp.t);
// Set the core handle of the Station
that.$root.$handle = resp.h;
// Mount the local Station root
that.$fw("mount", that.$root);
},
baja.fail, batch);
// Ensure type is imported before we create the root component
cb.addOk(function (ok, fail, resp) {
baja.importTypes({
typeSpecs: [ resp.t ],
ok: function () {
ok(resp);
},
fail: fail
});
});
// Make a call on the Server Side Handler
baja.comm.serverHandlerCall(this, "loadRoot", null, cb, /*makeInBatch*/true);
} catch (err) {
baja.fail(err);
}
return cb ? cb.promise() : bajaPromises.resolve();
};
/**
* Sync the Component Space.
*
* This method will result in a network call to sync the master Space with this one.
*
* An Object Literal is used for the method's arguments.
*
* @name baja.BoxComponentSpace#sync
* @function
*
* @private
*
* @param {Object} [obj] the Object Literal for the method's arguments.
* @param {Function} [obj.ok] (Deprecated: use Promise) the ok callback.
* Called once the Component Space has been successfully synchronized with the
* Server.
* @param {Function} [obj.fail] (Deprecated: use Promise) the fail callback.
* Called If the Component Space can't be synchronized.
* @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be
* batched into this object
* @returns {Promise} a promise that will be resolved once the component space
* is synced.
*/
BoxComponentSpace.prototype.sync = function (obj) {
obj = baja.objectify(obj, "ok");
var cb = new Callback(obj.ok, obj.fail, obj.batch);
try {
this.$callbacks.poll(cb);
} catch (err) {
cb.fail(err);
}
return cb.promise();
};
/**
* Find the Component via its handle (null if not found).
*
* An Object Literal is used for the method's arguments.
*
* @name baja.BoxComponentSpace#resolveByHandle
* @function
*
* @private
*
* @param {Object} [obj] the Object Literal for the method's arguments.
* @param {Function} [obj.ok] (Deprecated: use Promise) the ok callback.
* Called if the Component is resolved. The Component instance will be passed
* to this function.
* @param {Function} [obj.fail] (Deprecated: use Promise) the fail callback.
* Called if there's an error or the Component can't be resolved.
* @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be
* batched into this object.
* @returns {Promise.<baja.Component|null>} a promise that will be resolved
* once the component is resolved.
*/
BoxComponentSpace.prototype.resolveByHandle = function (obj) {
obj = baja.objectify(obj);
var handle = obj.handle,
cb = new Callback(obj.ok, obj.fail, obj.batch),
that = this,
comp;
try {
comp = this.findByHandle(handle);
if (comp !== null) {
cb.ok(comp);
} else {
// Intermediate callback to resolve the SlotPath into the target Component
cb.addOk(function (ok, fail, slotPath) {
// Resolve the SlotPath ORD
Ord.make(slotPath.toString()).get({
"base": that,
"ok": ok,
"fail": fail
});
});
this.$callbacks.handleToPath(handle, cb);
}
} catch (err) {
cb.fail(err);
}
return cb.promise();
};
/**
* Resolve to a list of enabled mix-in Types for the Component Space.
*
* An Object Literal is used for the method's arguments.
*
* @name baja.BoxComponentSpace#toEnabledMixIns
* @function
*
* @param {Object} [obj] the Object Literal for the method's arguments.
* @param {Function} [obj.ok] (Deprecated: use Promise) Callback handler
* invoked once the enabled mix-in Types have been resolved.
* @param {Function} [obj.fail] (Deprecated: use Promise) the fail callback.
* @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be
* batched into this object.
* @returns {Promise.<Array.<Object>>} a promise that will be resolved once
* the mixin information has been retrieved.
*/
BoxComponentSpace.prototype.toEnabledMixIns = function (obj) {
obj = baja.objectify(obj);
var cb = new Callback(obj.ok, obj.fail, obj.batch);
try {
// Intermediate callback to resolve the types from enabled mix-in
// call.
cb.addOk(function (ok, fail, typeSpecs) {
baja.importTypes({
typeSpecs: typeSpecs,
ok: ok,
fail: fail
});
});
this.$callbacks.toEnabledMixIns(cb);
} catch (err) {
cb.fail(err);
}
return cb.promise();
};
/**
* Commit Slots to the Component Space.
*
* @param {BoxComponentSpace} boxSpace
* @param {Array} slotInfo.
*
* @private
*/
function commitSlotInfo(boxSpace, slotInfo) {
var comp, cx, newVal, slot, bson, i;
for (i = 0; i < slotInfo.length; ++i) {
bson = slotInfo[i];
// Attempt to find the Component
comp = boxSpace.findByHandle(bson.h);
// Only load a singular Slot if the Component isn't already loaded
// TODO: Ensure we sync with master before loadSlot is processed in
// ORD resolution
if (comp !== null && !comp.$bPropsLoaded) {
// What about mounting a Component???
// Decode the Value
newVal = bsonDecodeValue(bson.v, serverDecodeContext);
// Force any Component to be stubbed
if (newVal.getType().isComponent()) {
newVal.$bPropsLoaded = false;
}
cx = {
commit: true,
serverDecode: true,
fromLoad: true
};
// Add the display name if we've got one
if (bson.dn) {
cx.displayName = bson.dn;
}
// Add the display string if we've got one
if (bson.d) {
cx.display = bson.d;
}
// TODO: What if the Component is already fully loaded?
// Does the Slot currently exist?
slot = comp.getSlot(bson.n);
if (slot === null) {
// Add the Slot if it doesn't currently exist
comp.add({
"slot": bson.n,
"value": newVal,
"flags": baja.Flags.decodeFromString(bajaDef(bson.f, "")),
"facets": baja.Facets.DEFAULT.decodeFromString(bajaDef(bson.x, ""), baja.Simple.$unsafeDecode),
"cx": cx
});
} else {
if (bson.dn) {
slot.$setDisplayName(bson.dn);
}
// Synchronize the value
syncVal(newVal, comp, slot, bson.d);
}
}
}
}
/** @returns {Promise} */
function processNext() {
var events = eventsQueue[0],
space = events.space,
callback = events.cb,
syncOpsArray = events.data.ops;
/** @returns {Promise} */
function doDecodeAndCommit(syncOpData) {
var comp, parentProp;
try {
// Get Component from SyncOp
if (syncOpData.h !== undefined) { // Was the handle encoded?
comp = space.findByHandle(syncOpData.h);
parentProp = comp && comp.getPropertyInParent();
// Update the display name of the component
if (parentProp) {
parentProp.$setDisplayName(syncOpData.cdn);
parentProp.$setDisplay(syncOpData.cd);
}
} else {
comp = null;
}
var syncOp = syncOps[syncOpData.nm];
// Look up SyncOp, decode and Commit
return bajaPromises.resolve(syncOp && syncOp.decodeAndCommit(comp, syncOpData));
} catch (e) {
return bajaPromises.reject(e);
}
}
function finish() {
for (var i = 0; i < eventsQueue.length; ++i) {
if (eventsQueue[i] === events) {
eventsQueue.splice(i, 1);
break;
}
}
if (eventsQueue.length > 0) {
return processNext();
}
}
function commit() {
return (function decodeAtIndex(i) {
var syncOp = syncOpsArray[i];
if (syncOp) {
return doDecodeAndCommit(syncOp)
.then(function () {
return decodeAtIndex(++i);
});
} else {
return bajaPromises.resolve();
}
}(0))
.then(callback)
.then(finish, function (err) {
baja.error('SyncOp failed to decode:');
baja.error(err);
return finish(); // keep going even if one SyncOp fails to decode
});
}
return doImportTypes(syncOpsArray)
.then(commit, commit);
}
function doImportTypes(bson) {
var df = bajaPromises.deferred();
importUnknownTypes(bson, df.resolve);
return df.promise();
}
function eventsQueueSort(a, b) {
return parseInt(a.data.id, 10) - parseInt(b.data.id, 10);
}
/**
* Commit the sync ops to the Component Space.
*
* @private
*
* @param {BoxComponentSpace} boxSpace
* @param {Object} eventData the event data that contains the Sync Ops to commit.
* @param {Function} callback the function callback for when everything has been processed.
*/
function commitSyncOps(boxSpace, eventData, callback) {
// Add to queue
eventsQueue.push({
space: boxSpace,
data: eventData,
cb: callback
});
// If this is the only thing in the queue to process then process it.
if (eventsQueue.length === 1) {
processNext().catch(baja.error);
} else if (eventsQueue.length > 1) {
eventsQueue.sort(eventsQueueSort);
}
}
/**
* Private framework handler for a Component Space.
*
* This is a private internal method for framework developers.
*
* @name baja.BoxComponentSpace#$fw
* @function
*
* @private
*/
BoxComponentSpace.prototype.$fw = function (x, a, b, c) {
if (x === "commitSyncOps") {
// Process sync ops
commitSyncOps(this, /*SyncOps*/a, /*callback*/b);
} else if (x === "commitSlotInfo") {
// Commit a singular Slot
commitSlotInfo(this, /*Slot Information to Commit*/a);
} else {
// Else call super framework handler
callSuper("$fw", BoxComponentSpace, this, arguments);
}
};
/**
* Get the default ORD to a component space, relative to the host. If running
* in the browser, this will simply be `station:`. If Workbench interop is
* present, then Java-JS interop may indicate that BOX is using a remote
* connection to a station via FOX; in that case, this ORD will include the
* appropriate session query.
* @private
* @returns {baja.Ord}
*/
BoxComponentSpace.$getDefaultOrdInHost = function () {
var sessionOrdInHost;
// eslint-disable-next-line camelcase
if (typeof niagara_wb_util_getSessionOrdInHost === 'function') {
sessionOrdInHost = niagara_wb_util_getSessionOrdInHost();
}
if (sessionOrdInHost) {
return Ord.make({ base: sessionOrdInHost, child: 'station:' });
} else {
return Ord.make('station:');
}
};
return BoxComponentSpace;
});