How to remove L.Routing.line in leaflet with Leaflet Routing Machine? - javascript

I am trying to get a function to work that updates a L.Routing.line with Leaflet Routing Machine whenever a user adds or deletes a waypoint.
My code to date (slight adaptation from http://www.liedman.net/leaflet-routing-machine/interaction.html):
function routeImplement(){
if (routewpts.length >= 2) {
router.route(routewpts, function(err, routes) {
if (routeline) {
map.removeLayer(routeline);
};
if (err) {
alert(err);
} else {
routeline = L.Routing.line(routes[0]).addTo(map);
};
});
}
else{
if (routeline) {
map.removeLayer(routeline);
};
};
}
routewpts is an array of latLngs, routeline is supposed to be the L.Routing.line, router=L.Routing.osrm();, and map is the leaflet map (all globals). The function works fine for creating a line.
The issue that I am having is that the map.removeLayer(routeline); doesn't seem to work. There are two issues it seems: one is that L.Routing.line doesn't seem to return anything, so routeline is remaining undefined. Second, if I dispense with the use of a handle, and try to use map.removeLayer directly on L.Routing.line, I get some crazy error about a bad request to OSRM.org.
Thanks for any suggestions.

the first thing that i mooved forward is to use arrow function
function routeImplement(){
if (routewpts.length >= 2) {
router.route(routewpts, (err, routes) => { //arrow function
if (routeline) {
map.removeLayer(routeline);
};
if (err) {
alert(err);
} else {
routeline = L.Routing.line(routes[0]).addTo(map);
};
});
}
else{
if (routeline) {
map.removeLayer(routeline); // but still this line doesnt work for me
};
};
}

Well... It turns out the way to get this to work is to break up the L.Routing.line call:
routeline = L.Routing.line(routes[0]);
routeline.addTo(map);
With that the routeline handle works and everything else functions fine. I don't understand Leaflet (or the Routing Machine) well enough to explain why it gave up on returning anything if the line is immediately added to the map.

Related

forcing completion of an rxjs observer

I've got an rxjs observer (really a Subject) that tails a file forever, just like tail -f. It's awesome for monitoring logfiles, for example.
This "forever" behavior is great for my application, but terrible for testing. Currently my application works but my tests hang forever.
I'd like to force an observer change to complete early, because my test code knows how many lines should be in the file. How do I do this?
I tried calling onCompleted on the Subject handle I returned but at that point it's basically cast as an observer and you can't force it to close, the error is:
Object # has no method 'onCompleted'
Here's the source code:
function ObserveTail(filename) {
source = new Rx.Subject();
if (fs.existsSync(filename) == false) {
console.error("file doesn't exist: " + filename);
}
var lineSep = /[\r]{0,1}\n/;
tail = new Tail(filename, lineSep, {}, true);
tail.on("line", function(line) {
source.onNext(line);
});
tail.on('close', function(data) {
console.log("tail closed");
source.onCompleted();
});
tail.on('error', function(error) {
console.error(error);
});
this.source = source;
}
And here's the test code that can't figure out how to force forever to end (tape style test). Note the "ILLEGAL" line:
test('tailing a file works correctly', function(tid) {
var lines = 8;
var i = 0;
var filename = 'tape/tail.json';
var handle = new ObserveTail(filename);
touch(filename);
handle.source
.filter(function (x) {
try {
JSON.parse(x);
return true;
} catch (error) {
tid.pass("correctly caught illegal JSON");
return false;
}
})
.map(function(x) { return JSON.parse(x) })
.map(function(j) { return j.name })
.timeout(10000, "observer timed out")
.subscribe (
function(name) {
tid.equal(name, "AssetMgr", "verified name field is AssetMgr");
i++;
if (i >= lines) {
handle.onCompleted(); // XXX ILLEGAL
}
},
function(err) {
console.error(err)
tid.fail("err leaked through to subscriber");
},
function() {
tid.end();
console.log("Completed");
}
);
})
It sounds like you solved your problem, but to your original question
I'd like to force an observer change to complete early, because my test code knows how many lines should be in the file. How do I do this?
In general the use of Subjects is discouraged when you have better alternatives, since they tend to be a crutch for people to use programming styles they are familiar with. Instead of trying to use a Subject I would suggest that you think about what each event would mean in an Observable life cycles.
Wrap Event Emitters
There already exists wrapper for the EventEmitter#on/off pattern in the form of Observable.fromEvent. It handles clean up and keeping the subscription alive only when there are listeners. Thus ObserveTail can be refactored into
function ObserveTail(filename) {
return Rx.Observable.create(function(observer) {
var lineSep = /[\r]{0,1}\n/;
tail = new Tail(filename, lineSep, {}, true);
var line = Rx.Observable.fromEvent(tail, "line");
var close = Rx.Observable.fromEvent(tail, "close");
var error = Rx.Observable.fromEvent(tail, "error")
.flatMap(function(err) { return Rx.Observable.throw(err); });
//Only take events until close occurs and wrap in the error for good measure
//The latter two are terminal events in this case.
return line.takeUntil(close).merge(error).subscribe(observer);
});
}
Which has several benefits over the vanilla use of Subjects, one, you will now actually see the error downstream, and two, this will handle clean up of your events when you are done with them.
Avoid *Sync Methods
Then this can be rolled into your file existence checking without the use of readSync
//If it doesn't exist then we are done here
//You could also throw from the filter if you want an error tracked
var source = Rx.Observable.fromNodeCallback(fs.exists)(filename)
.filter(function(exists) { return exists; })
.flatMap(ObserveTail(filename));
Next you can simplify your filter/map/map sequence down by using flatMap instead.
var result = source.flatMap(function(x) {
try {
return Rx.Observable.just(JSON.parse(x));
} catch (e) {
return Rx.Observable.empty();
}
},
//This allows you to map the result of the parsed value
function(x, json) {
return json.name;
})
.timeout(10000, "observer timed out");
Don't signal, unsubscribe
How do you stop "signal" a stop when streams only travel in one direction. We rarely actually want to have an Observer directly communicate with an Observable, so a better pattern is to not actually "signal" a stop but to simply unsubscribe from the Observable and leave it up to the Observable's behavior to determine what it should do from there.
Essentially your Observer really shouldn't care about your Observable more than to say "I'm done here".
To do that you need to declare a condition you want to reach in when stopping.
In this case since you are simply stopping after a set number in your test case you can use take to unsubscribe. Thus the final subscribe block would look like:
result
//After lines is reached this will complete.
.take(lines)
.subscribe (
function(name) {
tid.equal(name, "AssetMgr", "verified name field is AssetMgr");
},
function(err) {
console.error(err)
tid.fail("err leaked through to subscriber");
},
function() {
tid.end();
console.log("Completed");
}
);
Edit 1
As pointed out in the comments, In the case of this particular api there isn't a real "close" event since Tail is essentially an infinite operation. In this sense it is no different from a mouse event handler, we will stop sending events when people stop listening. So your block would probably end up looking like:
function ObserveTail(filename) {
return Rx.Observable.create(function(observer) {
var lineSep = /[\r]{0,1}\n/;
tail = new Tail(filename, lineSep, {}, true);
var line = Rx.Observable.fromEvent(tail, "line");
var error = Rx.Observable.fromEvent(tail, "error")
.flatMap(function(err) { return Rx.Observable.throw(err); });
//Only take events until close occurs and wrap in the error for good measure
//The latter two are terminal events in this case.
return line
.finally(function() { tail.unwatch(); })
.merge(error).subscribe(observer);
}).share();
}
The addition of the finally and the share operators creates an object which will attach to the tail when a new subscriber arrives and will remain attached as long as there is at least one subscriber still listening. Once all the subscribers are done however we can safely unwatch the tail.

Making async call dropboxjs

I have problem when I want to read directories from the Dropbox API, dropbox.js https://github.com/dropbox/dropbox-js
The read directories function from the the API looks something like this and I want to assign the value with angular to easily reach it in my HTML.
UPDATE AFTER HELPFUL QUESTION
client.readdir("/", function(error, entries, stat1, stat2) {
if (error) {
return showError(error);
}
$scope.dbItems = stat1;
$scope.$digest();
});
The HTML-code:
<ul ng-repeat="dbItem in dbItems">
<li><input type="image" src="img/folder.png">{{dbItem.name}}</input></li>
</ul>
My problem is that the Dropbox call takes some milliseconds so I have to reload the page to print the data collected. I've tried some things with both angular and JQuery promises but I can't get i to work. All the examples I find about promises has a setTimeout-function and that is fairly easy to implement but when I try to implement it with Dropbox it doesn't work. Anyone who has tried something similar?
UPDATE WITH MY PROBLEM
Now the HTML is updated correctly but I also want to join all of my Dropbox directories to be exported as JSON. I've updated the function above to look like:
$scope.listDB = function()
{
client.readdir(dirToString(), function(error, entries, stat1, stat2) {
if (error) {
return showError(error); // Something went wrong.
}
$scope.dbItems = stat2;
$scope.$digest();
testTemp = stat1._json;
setFolders(testTemp);
function setFolders(current)
{
for(var i=0,folders = current.contents;i<folders.length;i++)
{
if(folders[i].is_dir)
{
folders[i].name = folders[i].path.replace(/\/([^)]+)\//,"");
folders[i].name = folders[i].name.replace('/',"");
$scope.listDBinner(folders[i].name);
var inner = $scope.innerItems;
folders[i].contents = inner;
setFolders(folders[i]);
}
}
}
});
};
With listDBinner:
$scope.listDBinner = function(name)
{
client.readdir(name, function(error, entries, stat1, stat2) {
if (error) {
return showError(error); // Something went wrong.
}
$scope.innerItems = stat1;
$scope.$digest();
console.log($scope.innerItems);
});
console.log($scope.innerItems);
};
The problem is that the console.log of $scope.innerItems inside of client.readdir is correct and the one outside is just empty. I know this should probably be solved with Promises of some kind but I really can't get it to work.

throwing a debug from chrome extension content script

Short version
Trying to write a debug command that returns the call stack, minus the current position. I thought I'd use:
try {
throw new Error(options["msg"])
} catch (e) {
e.stack.shift;
throw (e);
}
but I don't know how to do it exactly. apparently I can't just e.stack.shift like that. Also that always makes it an Uncaught Error — but these should just be debug messages.
Long version
I decided I needed a debug library for my content scripts. Here it is:
debug.js
var debugKeys = {
"level": ["off", "event", "function", "timeouts"],
"detail": ["minimal", "detailed"]
};
var debugState = { "level": "off", "detail": "minimal" };
function debug(options) {
if ("level" in options) {
if (verifyDebugValue("level", options["level"]) == false)
return
}
if ("detail" in options) {
if (verifyDebugValue("detail", options["detail"]) == false)
return
}
console.log(options["msg"]);
}
function verifyDebugValue(lval, rval){
var state = 10; // sufficiently high
for (k in debugKeys[lval]) {
if (debugKeys[lval][k] == rval) {
return true;
}
if (debugKeys[lval][k] == debugState[lval]) { // rval was greater than debug key
return false;
}
}
}
When you using it, you can change the debugState in the code to suit your needs. it is still a work in progress but it works just fine.
To use it from another content script, just load it in the manifest like:
manifest.json
"content_scripts": [
{
"js": ["debug.js", "foobar.js"],
}
],
and then call it like:
debug({"level": "timeouts", "msg": "foobar.js waitOnElement() timeout"});
which generates:
foobar.js waitOnElement() timeout debug.js:17
And there is my problem. At the moment, it is using the console log and so all the debug statements come from the same debug.js line. I'd rather return the calling context. I imagine I need something like:
try {
throw new Error(options["msg"])
} catch (e) {
e.stack.shift;
throw (e);
}
but I don't know how to do it exactly. apparently I can't just e.stack.shift like that. Also that always makes it an Uncaught Error — but these should just be debug messages.
You can't avoid mentioning the line in your debug.js, because either using throw (...) or console.log/error(...) your debug.js will be issuing the command.
What you can do, is have some try-catch blocks in your code, then in the catch block pass the error object to your debug function, which will handle it according to its debugState.
In any case, it is not quite clear how you are using your debug library (and why you need to remove the last call from the stack-trace, but you could try something like this:
Split the stack-trace (which is actually a multiline string) into lines.
Isolate the first line (corresponding to the last call) that is not part of the error's message.
Put together a new stack-trace, with the removed line.
E.g.:
function removeLastFromStack(stack, errMsg) {
var firstLines = 'Error: ' + errMsg + '\n';
var restOfStack = stack
.substring(firstLines.length) // <-- skip the error's message
.split('\n') // <-- split into lines
.slice(1) // <-- "slice out" the first line
.join('\n'); // <-- put the rest back together
return firstLines + restOfStack;
}
function myDebug(err) {
/* Based on my `debugState` I should decide what to do with this error.
* E.g. I could ignore it, or print the message only,
* or print the full stack-trace, or alert the user, or whatever */
var oldStack = err.stack;
var newStack = removeLastFromStack(oldStack, err.message);
console.log(newStack);
//or: console.error(newStack);
}
/* Somewhere in your code */
function someFuncThatMayThrowAnErr(errMsg) {
throw new Error(errMsg);
}
try {
someFuncThatMayThrowAnErr('test');
} catch (err) {
myDebug(err);
}
...but I still don't see how removing the last call from the trace would be helpful

wd waitForElementBy... and asserter usage

I'm using the library provided here successfully in several tests, but am now stuck trying to use asserters as per their docs as a passed function in the waitForElementByLinkText API method.
Here is where I've defined the Asserter:
function Asserter(_assert){
this.assert = _assert;
}
/**
* asserters.isVisible
*
* #asserter
*/
var isDisplayed = new Asserter(
function(el,cb) {
el.isDisplayed(function(err, displayed) {
if(err) { return cb(err); }
cb(null, displayed);
});
}
);
module.exports = {
Asserter: Asserter,
isDisplayed: isDisplayed
};
Then in my chained script, I am attempting to use it as follows, but the console.log executes before the element is visible:
.get('http://mydomain.com/mypage')
.elementByLinkText('Reset', function(err, el){
browser.next('clickElement', el, noop);
})
.waitForElementByLinkText('This is the link text', isDisplayed, 10000, 100, function(err){
console.log('The page has updated!');
})
I believe my code is using a deprecated version of the chaining syntax which is needed to support legacy code coming out of SeBuilder, but makes it hard to follow the samples which all use the new method.
You don't need to redefine commonly used asserters, please refer to the new example here:
https://github.com/admc/wd/blob/master/examples/deprecated/wait-for-simple.js
If you need more help, please provide some html/js sample, otherwise it's hard to figure out what you are actually trying to achieve.

Second part of an IF statement for Dropbox oauth athentication isn't triggered

I tried a simple if statement to avoid having to run the code below on every pageload, but the second part with dropbox_authStatus === 1 is not triggered although alert("authStatus: "+dropbox_authStatus); tells me that dropbox_authStatus is 1. What's wrong with my code?
$('document').ready(function() {
dropbox_authStatus = localStorage.getItem('dropbox_authstatus');
alert("authstatus: "+dropbox_authStatus);
if(!dropbox_authStatus) {
localStorage.setItem('dropbox_authstatus',1);
//initialization
var client = new Dropbox.Client({
key: "hm4c58qp6rpysot", secret: "w7cdx6o8p2hyubj"
});
alert("initialized");
//preset driver to the dropbox page
client.authDriver(new Dropbox.Drivers.Redirect());
//authentication
client.authenticate(function(error, client) {
if (error) {
return showError(error); // Something went wrong.
}
});
} else if (dropbox_authStatus === 1) {
localStorage.setItem('dropbox_authstatus',2);
//initialization
var client = new Dropbox.Client({
key: "hm4c58qp6rpysot", secret: "w7cdx6o8p2hyubj"
});
alert("continued");
//preset driver to the dropbox page
client.authDriver(new Dropbox.Drivers.Redirect());
//authentication
client.authenticate(function(error, client) {
if (error) {
return showError(error); // Something went wrong.
}
client.getUserInfo(function(error, userInfo) {
if (error) {
return showError(error); // Something went wrong.
}
alert("hello: "+userInfo.name);
});
});
//Save Dropbox credentials
localStorage.setItem('dropbox_auth', JSON.stringify(client.credentials()));
alert("credentials saved:"+JSON.stringify(client.credentials()));
}
});
Thanks in advance! The code inside the if-statements mainly belongs to the dropbox.js library hosted on github: https://github.com/dropbox/dropbox-js/blob/master/doc/getting_started.md
Answer derived from comments on original question
I'm just guessing, but if the log seems correct, but the condition isn't met, maybe dropbox_authStatus is a string instead of a number.
Recent versions of dropbox.js support an interactive: false option on the client.authenticate() method. You can use this public API to achieve the same goal, and your code won't break on library updates.
Code snippet: https://github.com/dropbox/dropbox-js/blob/master/doc/snippets.md#sign-into-dropbox-button
authenticate documentation: http://coffeedoc.info/github/dropbox/dropbox-js/master/classes/Dropbox/Client.html#authenticate-instance

Categories