react-siesta
Version:
React mixins & support for Siesta
262 lines (255 loc) • 8.61 kB
JSX
// Just makes the code a tad cleaner if no window.Q available.
var FAKE_DEFERRED = {promise: null, reject: function () {}, resolve: function () {}};
var isArray = Array.isArray || function (obj) {
return _.toString.call(obj) === '[object Array]';
};
var isString = function (str) {
return typeof str == 'string' || str instanceof String;
};
/**
* Wrap a callback and a deferred in a convienience function.
* @param [cb]
* @param deferred
* @returns {Function}
* @private
*/
function _done(cb, deferred) {
return function (err, o) {
if (err) {
if (cb) cb(err);
deferred.reject(err);
}
else {
if (cb) cb();
deferred.resolve(o);
}
};
}
var SiestaMixin = {
componentWillMount: function () {
this.listeners = [];
},
_cancelListeners: function () {
for (var i = 0; i < this.listeners.length; i++) {
var cancelListener = this.listeners[i];
cancelListener();
}
this.listeners = [];
},
componentWillUnmount: function () {
this._cancelListeners();
},
_listenToModel: function (field, Model, handler, cb) {
if (Model.singleton) {
Model.one(function (err, instance) {
console.log('singleton', instance);
if (!err) {
if (field) this.listen(field, instance, function (n) {handler(n)});
else this.listen(instance, function (n) {handler(n)});
cb(err, instance);
}
else {
handler(err);
}
}.bind(this));
}
else throw new Error('Cannot listen to a Model if it is not a singleton');
},
wrapCancelListen: function (cancelListen) {
var wrappedCancelListen;
if (typeof cancelListen == 'function') {
wrappedCancelListen = function () {
var idx = this.listeners.indexOf(cancelListen);
this.listeners.splice(idx, 1);
cancelListen();
}.bind(this)
}
return wrappedCancelListen;
},
listen: function () {
var field, listenee, handler, cb;
if (isString(arguments[0])) {
field = arguments[0];
listenee = arguments[1];
handler = arguments[2];
cb = arguments[3];
}
else {
listenee = arguments[0];
handler = arguments[1];
cb = arguments[2];
}
cb = cb || function () {};
var deferred = window.Q ? window.Q.defer() : FAKE_DEFERRED,
done = _done(cb, deferred);
if (listenee instanceof siesta._internal.Model) {
this._listenToModel(field, listenee, handler, done);
}
else {
var cancelListen;
if (field) cancelListen = listenee.listen(field, handler);
else cancelListen = listenee.listen(handler);
this.listeners.push(cancelListen);
done(null, listenee);
}
return deferred.promise;
},
query: function (model, query, prop, cb) {
cb = cb || function () {};
var deferred = window.Q ? window.Q.defer() : FAKE_DEFERRED,
done = _done(cb, deferred);
model.query(query, function (err, res) {
console.log('_done');
if (!err) {
var state = {};
state[prop] = res;
this.setState(state, function () {
done(null, res);
});
}
else {
done(err);
}
}.bind(this));
return deferred.promise;
},
all: function (model) {
var query, prop, cb;
if (arguments[1] instanceof String || typeof arguments[1] == 'string') {
prop = arguments[1];
cb = arguments[2];
}
else {
query = arguments[1];
prop = arguments[2];
cb = arguments[3];
}
return this.query(model, query, prop, cb);
},
isReactiveQuery: function (o) {
// TODO: Wishy washy. Do instanceof check instead once ReactiveQuery is available on siesta object
return typeof o.terminate == 'function';
},
_listenAndSetStateForModelInstance: function (instance, opts, prop, callback) {
var state = {};
opts = opts || {};
if (!opts.fields && !prop) throw Error('Must either specify fields or prop');
if (opts.fields) {
var fields;
if (isArray(opts.fields)) {
fields = {};
opts.fields.forEach(function (f) {
if (isString(f)) {
fields[f] = f;
}
else {
for (var prop in f) {
if (f.hasOwnProperty(prop)) fields[prop] = f[prop];
}
}
//state[f] = instance[f];
});
}
else {
fields = opts.fields;
}
console.log('fields', fields);
Object.keys(fields).forEach(function (f) {
var custom = fields[f];
state[custom] = instance[f];
});
}
else {
state[prop] = instance;
}
console.log('state', state);
this.setState(state);
this.listen(instance, function (e) {
if (opts.fields) {
var custom = e.field,
doesContain;
if (isArray(opts.fields)) {
doesContain = opts.fields.indexOf(e.field) > -1;
}
else {
doesContain = e.field in opts.fields;
if (doesContain) custom = opts.fields[e.field];
}
if (doesContain) {
var state = {};
state[custom] = e.new;
this.setState(state);
}
}
else {
this.setState();
}
}.bind(this));
callback(null, instance);
},
listenAndSetState: function (o) {
var opts, prop, cb;
if (typeof arguments[1] == 'object') {
opts = arguments[1];
cb = arguments[2];
}
else {
opts = {};
prop = arguments[1];
cb = arguments[2];
}
var deferred = window.Q ? window.Q.defer() : FAKE_DEFERRED;
cb = cb || function () {};
var done = _done(cb, deferred);
var state = {};
var updateWithResults = function () {
state[prop] = o.results;
this.setState(state, function () {
this.listen(o, function () {
if (this.isReactiveQuery(o)) {
var state = {};
state[prop] = o.results;
this.setState(state);
}
}.bind(this));
});
}.bind(this);
if (this.isReactiveQuery(o)) {
if (o.initialised) {
updateWithResults.call(this);
done(null, o.results);
}
else {
o.init(function () {
updateWithResults.call(this);
done(null, o.results);
}.bind(this));
}
}
else if (o instanceof siesta._internal.Model) {
if (o.singleton) {
o.one(function (err, instance) {
if (err) {
cb(err);
deferred.reject(err);
}
else {
this._listenAndSetStateForModelInstance(instance, opts, prop, done);
}
}.bind(this));
}
else {
throw new Error('Can only listenAndSetState singleton models');
}
}
else if (o instanceof siesta._internal.siestaModel) {
this._listenAndSetStateForModelInstance(o, opts, prop, done);
}
else {
throw new Error('Cannot listenAndSetState objects of that type');
}
return deferred.promise;
}
};
if (typeof module !== 'undefined') module.exports = {SiestaMixin: SiestaMixin};
if (typeof window !== 'undefined') window.SiestaMixin = SiestaMixin;