fix path, set, load, ws & escape todo

This commit is contained in:
Mark Nadal 2015-02-26 16:19:00 -07:00
parent 8eb5b2fd48
commit a91bd57791
4 changed files with 169 additions and 55 deletions

View File

@ -15,14 +15,14 @@
var todoHTML = '';
for(key in val) {
if(!val[key] || key == '_') continue;
todoHTML += '<li style="width:400px;height:2em;">' + val[key] +
todoHTML += '<li style="width:400px;height:2em;">' + (val[key]||'').toString().replace(/\</ig, '&lt;') +
'<button style="float:right;" onclick=removeToDo("'+key+'")>X</button></li>';
}
$("#todos").innerHTML = todoHTML;
});
$("#addToDo").onsubmit = function(){
var id = randomId();
gun.path(id).set($("#todoItem").value);
gun.path(id).set(($("#todoItem").value||'').toString().replace(/\</ig, '&lt;'));
$("#todos").innerHTML += "<li>"+$("#todoItem").value+"</li>";
$("#todoItem").value = "";
return false;

127
gun.js
View File

@ -237,7 +237,7 @@
var gun = this.chain();
gun.shot.next(function(next){
opt = opt || {};
cb = cb || function(){};
cb = cb || function(){}; cb.c = 0;
cb.soul = Gun.is.soul(key); // is this a key or a soul?
if(cb.soul){ // if a soul...
cb.node = gun.__.graph[cb.soul]; // then attempt to grab it directly from cache in the graph
@ -248,12 +248,13 @@
if(!opt.force && cb.node){ // if it was in cache, then...
console.log("load via gun");
gun._.node = cb.node; // assign it to this context
return cb.call(gun, null, Gun.obj.copy(gun._.node)), next(); // frozen copy
return cb.call(gun, null, Gun.obj.copy(gun._.node)), cb.c++, next(); // frozen copy
}
// missing: hear shots! I now hook this up in other places, but we could get async/latency issues?
// We need to subscribe early? Or the transport layer handle this for us?
if(Gun.fns.is(gun.__.opt.hooks.load)){
gun.__.opt.hooks.load(key, function(err, data){
if(cb.c++){ return Gun.log("Warning: Load callback being called", cb.c, "times.") }
if(err){ return cb.call(gun, err) }
if(!data){ return cb.call(gun, null, data), next() }
var context = gun.union(data); // safely transform the data into the current context
@ -267,7 +268,7 @@
}, opt);
} else {
root.console.log("Warning! You have no persistence layer to load from!");
return cb.call(gun), next();
return cb.call(gun), cb.c++, next();
}
});
return gun;
@ -321,14 +322,14 @@
Chain.path = function(path, cb){ // Follow the path into the field.
var gun = this.chain(); // create a new context, changing the focal point.
cb = cb || function(){};
path = (path || '').split('.');
path = (Gun.text.ify(path) || '').split('.');
gun.shot.next(cb.done = function(next){ // let the previous promise resolve.
if(next){ cb.next = next }
if(!cb.next || !cb.back){ return }
cb = cb || function(){}; // fail safe our function.
(function trace(){ // create a recursive function, and immediately call it.
gun._.field = Gun.text.ify(path.shift()); // where are we at? Figure it out.
if(gun._.node && path.length && (cb.soul = Gun.is.soul(gun._.node[gun._.field]))) { // if we need to recurse more
if(gun._.node && path.length && Gun.is.soul(cb.soul = gun._.node[gun._.field])) { // if we need to recurse more
return gun.load(cb.soul, function(err){ // and the recursion happens to be on a relation, then load it.
if(err){ return cb.call(gun, err) }
trace(gun._ = this._); // follow the context down the chain.
@ -339,6 +340,9 @@
}(gun._.node = gun.back._.node)); // immediately call trace, setting the new context with the previous node.
});
gun.back.shot.next(function(next){
if(gun.back && gun.back._ && gun.back._.field){
path = [gun.back._.field].concat(path);
}
cb.back = true;
cb.done();
next();
@ -395,32 +399,71 @@
*/
Chain.set = function(val, cb, opt){ // TODO: need to turn deserializer into a trampolining function so stackoverflow doesn't happen.
var gun = this;
opt = opt || {};
cb = cb || function(){};
if(!gun.back){
gun = gun.chain(); // create a new context
}
gun.shot.next(function(next){
opt = opt || {};
cb = cb || function(){};
gun._.field = Gun.text.ify(gun._.field);
gun.shot.next(function(next){ // How many edge cases are there to a set?
if(!gun._.node){
if(Gun.is.value(val) || !Gun.obj.is(val)){
return cb.call(gun, {err: Gun.log("No field exists to set the " + (typeof val) + " on.")});
if(Gun.is.value(val) || !Gun.obj.is(val)){ // 1. Context: null, set: value. Error, no node exists.
return cb.call(gun, {err: Gun.log("No node exists to set " + (typeof val) + " in.")});
}
if(Gun.obj.is(val)){ // 2. Context: null, set: node. Set.
return set(next);
}
} else {
if(!gun._.field){
if(Gun.is.value(val) || !Gun.obj.is(val)){ // 3. Context: node, set: value. Error, no field exists.
return cb.call(gun, {err: Gun.log("No field exists to set " + (typeof val) + " on.")});
}
if(Gun.obj.is(val)){ // 4. Context: node, set: node. Merge.
return set(next);
}
} else {
if(Gun.is.value(val) || !Gun.obj.is(val)){ // 5. Context: node and field, set: value. Merge and replace.
var partial = {}; // in case we are doing a set on a field, not on a node.
partial[gun._.field] = val; // we create a blank object with the field/value to be set
val = partial;
return set(next);
}
if(Gun.obj.is(val)){
if(Gun.is.soul(gun._.node[gun._.field])){ // 6. Context: node and field of relation, set: node. Merge.
return gun.load(gun._.node[gun._.field], function(err){
if(err){ return cb.call(gun, {err: Gun.log(err)}) } // use gun not this to preserve intent?
set(next, this);
});
} else { // 7. Context: node and field, set: node. Merge and replace.
var partial = {}; // in case we are doing a set on a field, not on a node.
partial[gun._.field] = val; // we create a blank object with the field/value to be set
val = partial;
return set(next);
}
}
}
} else
if(gun._.field){ // if a field exists, it should always be a string
var partial = {}; // in case we are doing a set on a field, not on a node
partial[gun._.field] = val; // we create a blank node with the field/value to be set
val = partial;
}
});
function set(next, as){
as = as || gun;
cb.states = Gun.time.is();
Gun.ify(val, function(raw, context, sub, soul){
if(val === raw){ return soul(Gun.is.soul.on(gun._.node)) }
if(gun._.node && sub && sub.path){
return gun.path(sub.path, function(err, node, field){
if(val === raw){ return soul(Gun.is.soul.on(as._.node)) }
if(as._.node && sub && sub.path){
return as.path(sub.path, function(err, node, field){
if(err){ cb.err = err + " (while doing a set)" } // let .done handle calling this, it may be slower but is more consistent.
if(this._.node && (field = Gun.is.soul(this._.node[this._.field]))){
return soul(field);
} soul(); // else call it anyways
if(node = this._.node){
if(field = this._.field){
if(field = Gun.is.soul(node[field])){
return soul(field);
}
} else
if(Gun.is.soul.on(node) !== Gun.is.soul.on(as._.node)){
if(field = Gun.is.soul.on(node)){
return soul(field);
}
}
}
soul(); // else call it anyways
});
} soul(); // else call it anyways
}).done(function(err, set){
@ -432,11 +475,11 @@
set = Gun.ify.state(set.nodes, cb.states); // set time state on nodes?
if(set.err){ return cb.call(gun, set.err) }
gun.union(set.nodes); // while this maybe should return a list of the nodes that were changed, we want to send the actual delta
gun._.node = gun.__.graph[cb.root._[Gun._.soul]] || cb.root;
if(!gun._.field){
Gun.obj.map(gun._.keys, function(yes, key){
as._.node = as.__.graph[cb.root._[Gun._.soul]] || cb.root;
if(!as._.field){
Gun.obj.map(as._.keys, function(yes, key){
if(yes){ return }
gun.key(key); // TODO: Feature? what about these callbacks?
as.key(key); // TODO: Feature? what about these callbacks?
});
}
if(Gun.fns.is(gun.__.opt.hooks.set)){
@ -450,7 +493,7 @@
}
next();
});
});
};
return gun;
}
Chain.map = function(cb, opt){
@ -936,23 +979,23 @@
tab.prefix = tab.prefix || opt.prefix || 'gun/';
tab.prekey = tab.prekey || opt.prekey || '';
tab.prenode = tab.prenode || opt.prenode || '_/nodes/';
tab.load = tab.load || function(key, cb, opt){
tab.load = tab.load || function(key, cb, o){
if(!key){ return }
cb = cb || function(){};
opt = opt || {};
opt.url = opt.url || {};
opt.headers = tab.headers;
o = o || {};
o.url = o.url || {};
o.headers = Gun.obj.copy(tab.headers);
if(key[Gun._.soul]){
opt.url.query = key;
o.url.query = key;
} else {
opt.url.pathname = '/' + key;
o.url.pathname = '/' + key;
}
Gun.log("gun load", key);
(function local(key, cb){
var node, lkey = key[Gun._.soul]? tab.prefix + tab.prenode + key[Gun._.soul]
: tab.prefix + tab.prekey + key
if((node = store.get(lkey)) && node[Gun._.soul]){ return local(node, cb) }
if(cb.node = node){ setTimeout(function(){cb(null, node)},0) }
if(cb.node = node){ Gun.log('via cache', key); setTimeout(function(){cb(null, node)},0) }
}(key, cb));
Gun.obj.map(gun.__.opt.peers, function(peer, url){
request(url, null, function(err, reply){
@ -960,15 +1003,23 @@
if(err || !reply || (err = reply.body && reply.body.err)){
cb({err: Gun.log(err || "Error: Load failed through " + url) });
} else {
if(!key[Gun._.soul] && Gun.is.soul(reply.body)){
if(!key[Gun._.soul] && (cb.soul = Gun.is.soul.on(reply.body))){
var meta = {};
meta[Gun._.soul] = Gun.is.soul(reply.body);
meta[Gun._.soul] = cb.soul;
store.set(tab.prefix + tab.prekey + key, meta);
}
if(cb.node){ gun.union(reply.body) }
if(cb.node){
if(!cb.graph && (cb.soul = Gun.is.soul.on(cb.node))){ // if we have a cache locally
cb.graph = {}; // we want to make sure we did not go offline while sending updates
cb.graph[cb.soul] = cb.node; // so turn the node into a graph, and sync the latest state.
tab.set(cb.graph, function(e,r){ Gun.log("Stateless handshake sync:", e, r) });
if(!key[Gun._.soul]){ tab.key(key, cb.soul, function(e,r){}) }//TODO! BUG: this is really bad implicit behavior!
}
return gun.union(reply.body);
}
cb(null, reply.body);
}
}, opt);
}, o);
cb.peers = true;
}); tab.peers(cb);
}

View File

@ -1,6 +1,7 @@
var Gun = Gun || require('../gun');
if(typeof window !== 'undefined'){ root = window }
describe('Gun', function(){
var Gun = require('../gun')
, t = {};
var t = {};
describe('Utility', function(){
it('verbose console.log debugging', function(done) {
@ -339,7 +340,7 @@ describe('Gun', function(){
describe('API', function(){
require('../lib/file');
(typeof window === 'undefined') && require('../lib/file');
var gun = Gun({file: 'data.json'});
it('set key get', function(done){
@ -501,5 +502,74 @@ describe('Gun', function(){
}, 50);
});
it('path path', function(done){
var deep = gun.set({some: {deeply: {nested: 'value'}}});
deep.path('some.deeply.nested').get(function(val){
expect(val).to.be('value');
});
deep.path('some').path('deeply').path('nested').get(function(val){
expect(val).to.be('value');
done();
});
});
it('context null set value get error', function(done){
gun.set("oh yes",function(err){
expect(err).to.be.ok();
done();
});
});
var foo;
it('context null set node', function(done){
foo = gun.set({foo: 'bar'}).get(function(obj){
expect(obj.foo).to.be('bar');
done();
});
});
it('context node set val', function(done){
foo.set('banana', function(err){
expect(err).to.be.ok();
done();
});
});
it('context node set node', function(done){
foo.set({bar: {zoo: 'who'}}).get(function(obj){
expect(obj.foo).to.be('bar');
expect(Gun.is.soul(obj.bar)).to.ok();
done();
});
});
it('context node and field set value', function(done){
var tar = foo.path('tar');
tar.set('zebra').get(function(val){
expect(val).to.be('zebra');
done();
});
});
var bar;
it('context node and field of relation set node', function(done){
bar = foo.path('bar');
bar.set({combo: 'double'}).get(function(obj){
expect(obj.zoo).to.be('who');
expect(obj.combo).to.be('double');
done();
});
});
it('context node and field, set node', function(done){
bar.path('combo').set({another: 'node'}).get(function(obj){
expect(obj.another).to.be('node');
bar.get(function(node){
expect(Gun.is.soul(node.combo)).to.be.ok();
expect(Gun.is.soul(node.combo)).to.be(Gun.is.soul.on(obj));
done();
});
});
});
});
});
});

View File

@ -3,23 +3,16 @@
<title>Gun Tests</title>
<link rel="stylesheet" href="./mocha.css"/>
<script src="./mocha.js"></script>
<script>mocha.setup({ui:'bdd',globals:['SockJS']});</script>
<script>mocha.setup({ui:'bdd',globals:[]});</script>
<script src="./expect.js"></script>
<script></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<script src="../gate/amplify.store.js"></script>
<!--
<script src="../gate/sock.js"></script>
<script src="../../jquery.js"></script>
-->
<script src="../deps/theory.js"></script>
<script src="./server.js"></script>
<script src="./tests.js"></script>
<script src="../gun.js"></script>
<script src="./common.js"></script>
<style>
</style>
</head>
</head>
<body>
<body onload="mocha.run()">
Gun Tests
<div id="debug"></div>
<div id="mocha"></div>