1
0
Fork 0
fgdata/Phi/lib/knockprops.js

279 lines
8.9 KiB
JavaScript
Raw Normal View History

/**
###########################################################################
# knockprops - knockout.js <-> flightgear properties bridge
# (c) 2015 Torsten Dreyer
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
#of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
############################################################################
*/
define(['knockout'], function(ko) {
function KnockProps() {
var self = this;
self.initWebsocket = function() {
self.ws = new WebSocket('ws://' + location.host + '/PropertyListener');
self.ws.onclose = function(ev) {
var msg = 'Lost connection to FlightGear. Should I try to reconnect?';
if (confirm(msg)) {
// try reconnect
self.initWebsocket();
} else {
throw new Error(msg);
}
}
self.ws.onerror = function(ev) {
var msg = 'Error communicating with FlightGear. Please reload this page and/or restart FlightGear.';
alert(msg);
throw new Error(msg);
}
self.ws.onmessage = function(ev) {
try {
self.fire(JSON.parse(ev.data));
} catch (e) {
}
};
self.openCache = [];
self.ws.onopen = function(ev) {
// send subscriptions when the socket is open
var c = self.openCache;
delete self.openCache;
c.forEach(function(e) {
self.addListener(e.prop, e.koObservable);
});
for ( var p in self.listeners) {
self.addListener(p, self.listeners[p]);
}
};
}
self.initWebsocket();
self.fire = function(json) {
var value = json.value;
var listeners = self.listeners[json.path] || [];
listeners.forEach(function(koObservable) {
koObservable(value)
});
koObservable(json.value);
}
function resolvePropertyPath(self, pathOrAlias) {
if (pathOrAlias in self.aliases)
return self.aliases[pathOrAlias];
if (pathOrAlias.charAt(0) == '/')
return pathOrAlias;
return null;
}
self.listeners = {}
self.removeListener = function(pathOrAlias, koObservable) {
var path = resolvePropertyPath(self, pathOrAlias);
if (path == null) {
console.log("can't remove listener for " + pathOrAlias + ": unknown alias or invalid path.");
return self;
}
var listeners = self.listeners[path] || [];
var idx = listeners.indexOf(koObservable);
if (idx == -1) {
console.log("can't remove listener for " + path + ": not a listener.");
return self;
}
listeners.splice(idx, 1);
if (0 == listeners.length) {
self.ws.send(JSON.stringify({
command : 'removeListener',
node : path
}));
}
return self;
}
self.addListener = function(alias, koObservable) {
if (self.openCache) {
// socket not yet open, just cache the request
self.openCache.push({
"prop" : alias,
"koObservable" : koObservable
});
return self;
}
var path = resolvePropertyPath(self, alias);
if (path == null) {
console.log("can't listen to " + alias + ": unknown alias or invalid path.");
return self;
}
var listeners = self.listeners[path] = (self.listeners[path] || []);
if (listeners.indexOf(koObservable) != -1) {
console.log("won't listen to " + path + ": duplicate.");
return self;
}
koObservable.fgPropertyPath = path;
koObservable.fgBaseDispose = koObservable.dispose;
koObservable.dispose = function() {
if (this.fgPropertyPath) {
self.removeListener(this.fgPropertyPath, this);
}
this.fgBaseDispose.call(this);
}
listeners.push(koObservable);
koObservable.fgSetPropertyValue = function(value) {
self.setPropertyValue(this.fgPropertyPath, value);
}
if (1 == listeners.length) {
self.ws.send(JSON.stringify({
command : 'addListener',
node : path
}));
}
self.ws.send(JSON.stringify({
command : 'get',
node : path
}));
return self;
}
self.aliases = {};
self.setAliases = function(arg) {
if( Object.prototype.toString.call( arg ) === '[object Array]' ) {
// [
// [ shortcut, propertypath ],
// [ othercut, otherproperty ],
// ]
arg.forEach(function(a) {
self.aliases[a[0]] = a[1];
});
} else {
self.aliases = arg;
}
}
self.makeObservablesForAllProperties = function(target) {
for( var p in self.aliases ) {
if( self.aliases.hasOwnProperty(p) ) {
target[p] = ko.observable().extend({
fgprop : p
});
}
}
}
self.props = {};
self.get = function(target, prop) {
if (self.props[prop]) {
return self.props[prop];
}
return (self.props[prop] = self.observedProperty(target, prop));
}
self.observedProperty = function(target, prop) {
var reply = ko.pureComputed({
read : target,
write : function(newValue) {
if (newValue == target())
return;
target(newValue);
target.notifySubscribers(newValue);
}
});
self.addListener(prop, reply);
return reply;
}
self.write = function(prop, value) {
var path = this.aliases[prop] || "";
if (path.length == 0) {
console.log("can't write " + prop + ": unknown alias.");
return;
}
self.setPropertyValue(path, value);
}
self.setPropertyValue = function(path, value) {
this.ws.send(JSON.stringify({
command : 'set',
node : path,
value : value
}));
}
self.propsToObject = function(prop, map, result) {
result = result || {}
prop.children.forEach(function(prop) {
var target = map[prop.name] || null;
if (target) {
if (typeof (result[target]) === 'function') {
result[target](prop.value);
} else {
result[target] = prop.value;
}
}
});
return result;
}
}
ko.utils.knockprops = new KnockProps();
ko.extenders.fgprop = function(target, prop) {
return ko.utils.knockprops.get(target, prop);
};
ko.extenders.observedProperty = function(target, prop) {
return ko.utils.knockprops.observedProperty(target, prop);
};
/*
ko.extenders.fgPropertyGetSet = function(target, option) {
fgCommand.getPropertyValue(option, function(value) {
target(value);
}, self);
var p = ko.pureComputed({
read : target,
write : function(newValue) {
if (newValue == target())
return;
target(newValue);
target.notifySubscribers(newValue);
fgCommand.setPropertyValue(option, newValue);
}
});
return p;
}
*/
// don't return anything, use ko.extenders or ku.utils.knockprops
});