Phi: refactor knockprops into separate module
Get the knockoutjs to flightgear bridge into a separate module to make it reusable. First step for providing a simple usecase.
This commit is contained in:
parent
63f4642584
commit
0187820627
2 changed files with 263 additions and 236 deletions
261
Phi/lib/knockprops.js
Normal file
261
Phi/lib/knockprops.js
Normal file
|
@ -0,0 +1,261 @@
|
|||
/**
|
||||
###########################################################################
|
||||
# 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) {
|
||||
arg.forEach(function(a) {
|
||||
self.aliases[a[0]] = a[1];
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
});
|
238
Phi/main.js
238
Phi/main.js
|
@ -14,6 +14,7 @@ require.config({
|
|||
flottime : '3rdparty/flot/jquery.flot.time',
|
||||
fgcommand : 'lib/fgcommand',
|
||||
props : 'lib/props2',
|
||||
knockprops : 'lib/knockprops',
|
||||
sammy : '3rdparty/sammy-latest.min',
|
||||
aircraft : '../aircraft-dir',
|
||||
pagedown : '3rdparty/pagedown',
|
||||
|
@ -25,244 +26,9 @@ require.config({
|
|||
|
||||
require([
|
||||
'knockout', 'jquery', 'sammy', 'fgcommand', 'themeswitch', 'kojqui/button', 'kojqui/buttonset', 'kojqui/selectmenu',
|
||||
'jquery-ui/sortable', 'flot', 'leaflet'
|
||||
'jquery-ui/sortable', 'flot', 'leaflet', 'knockprops'
|
||||
], function(ko, jquery, Sammy, fgcommand) {
|
||||
|
||||
function KnockProps(aliases) {
|
||||
|
||||
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 = aliases || {};
|
||||
self.setAliases = function(arg) {
|
||||
arg.forEach(function(a) {
|
||||
self.aliases[a[0]] = a[1];
|
||||
});
|
||||
}
|
||||
|
||||
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.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;
|
||||
}
|
||||
|
||||
ko.utils.knockprops = new KnockProps();
|
||||
|
||||
ko.utils.knockprops.setAliases([
|
||||
// time
|
||||
[
|
||||
|
|
Loading…
Reference in a new issue