I have built an app with SammyJs. It currently works perfectly in the browser. However, when I package it to Android using PhoneGap, the routes does not work anymore.
I have found this SO question. However, the solution given does not work :
(function($) {
var app = $.sammy('[role=main]', function() {
this.disable_push_state = true;
...
});
}
Has anyone ever experienced the same issue?
EDIT
I am also using jquery mobile with the following script to disable its routing :
<script type="text/javascript">
// DISABLE JQM ROUTER
$(document).bind("mobileinit", function () {
$.mobile.ajaxEnabled = false;
$.mobile.linkBindingEnabled = false;
$.mobile.hashListeningEnabled = false;
$.mobile.pushStateEnabled = false;
$.mobile.changePage.defaults.changeHash = false;
});
</script>
I created a gist with my app sammy javascript (including routes).
I think the problem is with this around clause:
this.around(function(callback) {
var context = this;
url = 'http://localhost:3000/api.json?school=' + localStorage.school
this.load(url)
.then(function(data) {
parsed = JSON.parse(data);
//if (parsed.meta != undefined) {
// alert(parsed.meta.message);
//}
context.products = parsed.products;
context.places = parsed.places;
context.school = parsed.school;
context.title = $('[data-role=header] h1');
})
.then(callback); // *** this won't get called if load() rejects promise
});
As I understand it, the around clause is called with a callback(), which will continue loading the route when it is called.
I think there is a problem with your promise chain. If load() returns a rejected promise (which probably does, as there is no localhost:3000 on your phone), then neither of your then() functions will load. As such, callback() isn't called and the app "stops". I would advise (a) adding some error handling there, so you can see what it happening, and definitely (b) executing callback no matter the result of load(). Also - JSON.parse(data) will throw an error if data is not a proper JSON-encoded string - you want a try/catch around that, too.
I would try this:
this.load(url)
.then(function(data) {
try {
parsed = JSON.parse(data);
} catch(e) {
console.log('error decoding json!: '+errorMsg);
}
//if (parsed.meta != undefined) {
// alert(parsed.meta.message);
//}
context.products = parsed.products;
context.places = parsed.places;
context.school = parsed.school;
context.title = $('[data-role=header] h1');
},function(errorMsg){
console.log('error loading json!: '+errorMsg);
})
.fin(callback); // *** fin() is meant to execute on both success and error, like a "finally".
If your promises implementation does not support fin(), look up what it is calling its equivalent. It is essentially shorthand for: .then(callback).otherwise(callback)
Long story short - you want to make sure that the callback passed to around will be executed no matter what, or you app will not continue loading the route, which is what your unexpected behaviour seems to be.
As for the point about not being able to see the console, I am not sure what your environment looks like, but I have had success with Eclipse and ADT in the past - I can see console logs and errors just fine.
Related
I'm seeing unexpected behavior when setting window.location.href. My understanding is that the current page will be navigated away from immediately -- effectively ignoring subsequent JavaScript in the containing script. However, this is not what I'm seeing in practice (Firefox, Chrome and mobile Safari). I'm setting window.location.href when I encounter an error condition (e.g. missing some data) and yet the script continues to run and spew a bunch of errors because of said error condition. (This also applies to window.location.assign.)
Example:
function handleError() {
window.location.href = "https://example.com"
}
function doWork(id) {
if (!id) {
handleError();
}
var oops = id.split("-");
// a bunch of errors spill into the console, onerror listeners, etc.
}
Why not simply add a return after the guard clause?
function handleError() {
window.location.href = "https://example.com"
}
function doWork(id) {
if (!id) {
handleError();
return
}
var oops = id.split("-");
// a bunch of errors spill into the console, onerror listeners, etc.
}
I'd like to log these automatically generated errors and warnings in my application so I can debug issues my users have, but thus far I've not been able to find a solution for logging these. Does anyone know of a package or method?
Thank you.
The basic idea is to listen for errors by subscribing to window.onerror:
window.onerror = function (msg, url, lineNo, columnNo, error) {
// log to your server
return false;
}
But some browsers don't always provide you with a stacktrace, so you have to wrap functions calls that are at the top of the stack chain with a try catch :
function wrapErrors(fn) {
// don't wrap function more than once
if (!fn.__wrapped__) {
fn.__wrapped__ = function () {
try {
return fn.apply(this, arguments);
} catch (e) {
logErrorToYourServer(e); // report the error
throw e; // re-throw the error
}
};
}
return fn.__wrapped__;
}
var invoke = wrapErrors(function(obj, method, args) {
return obj[method].apply(this, args);
});
invoke(Math, 'highest', [1, 2]); // no method Math.highest
This function has to be called:
At the start of your application (e.g. in $(document).ready if you use jQuery)
In event handlers, e.g. addEventListener or $.fn.click
Timer-based callbacks, e.g. setTimeout or requestAnimationFrame
For example:
$(wrapErrors(function () { // application start
doSynchronousStuff1(); // doesn't need to be wrapped
setTimeout(wrapErrors(function () {
doSynchronousStuff2(); // doesn't need to be wrapped
});
$('.foo').click(wrapErrors(function () {
doSynchronousStuff3(); // doesn't need to be wrapped
});
}));
(code shamelessly copied from sentry's blog post)
You can find more information in the following links:
GlobalEventHandlers.onerror
sentry's blog post
This Gist on how to overwrite AddEventListener
There are also commercial products
Sentry that is also open source
TrackJs
I've created this custom command for my UI testing in Nightwatch. Here it is in full:
exports.command = function(element, callback) {
var self = this;
try {
this.waitForElementVisible('body', 15000);
console.log("trying..");
window.addEventListener('load', function() {
var selects = document.getElementsByName("select");
console.log(selects);
}, false);
} catch (err) {
console.log("code failed, here's the problem..");
console.log(err);
}
this
.useXpath()
// click dropdowns
.waitForElementVisible(element, 15000)
.click(element)
.useCss()
.waitForElementVisible('option[value="02To0000000L1Hy"]', 15000)
// operation we want all select boxes to perform
.setValue('option[value="02To0000000L1Hy"]', "02To0000000L1Hy")
.useXpath()
.click(element + '/option[4]');
if (typeof callback === "function") {
callback.call(self);
}
return this; // allows the command to be chained.
};
What I'm attempting to do is after I load the page, I want to retrieve all the select boxes and perform the same operation on them. Everything is working correctly except for the code in the try/catch block. I keep getting '[ReferenceError: window is not defined]' and am unsure of how to get past that.
The 'window' property is undefined in the global scope because it's being run via command line Node and not in the browser as one might assume initially.
You could try to use this.injectScript from the Nightwatch API but I would suggest using the Selenium Protocol API 'elements'
Hey there #logan_gabriel,
You could also use the execute command which I use when I need to inject a bit of JavaScript on the actual page. As #Steve Hyndig pointed out, your tests are running in the Node instead of on an actual browser window (somewhat confusing, since a window is generally open while tests are being run! Except if using PhantomJS to headless test, of course).
Here is an example custom command which will inject some JavaScript onto the page based on your original post:
exports.command = function(callback) {
var self = this;
this.execute(function getStorage() {
window.addEventListener('load', function() {
let selects = document.getElementsByName('select');
return selects;
}
},
// allows for use of callbacks with custom function
function(result) {
if (typeof callback === 'function') {
callback.call(self, selects);
}
});
// allows command to be chained
return this;
};
Which you could call from your test using the below syntax, including an optional callback to do something with the result:
client
.setAuth(function showSession(result) {
console.log(result);
});
You could opt to just do the work inside of the custom function, but I run into problems due to the async nature of Nightwatch sometimes if I don't nest stuff inside of callbacks, so it's more a safety thing.
Good luck!
I have a page with multiple YouTube embedded players that I need to listen for events on. I am trying to use the solution posted as the answer to Using Youtube's javascript API with jQuery, but I am getting a strange error: (in Chrome 18.0.1025.137 beta-m)
Uncaught SyntaxError: Unexpected token %
That is the extent of the error, including stacktrace. My code is like this:
var onYouTubePlayerReady = function (id) {
var evt = '(function(){})';
alert(eval(evt)); //just to verify that the snippet is syntactically correct
var ytplayer = document.getElementById(id);
ytplayer.addEventListener("onStateChange", evt);
};
(see the other question for more context)
The error is thrown when the onStateChange event is fired. If I make evt "" or a function name, then it doesn't throw the error (but I also get no state information).
Clearly, the error message is bogus, but anyone know if what I'm trying to do is possible?
Actually, turns out it's incredibly stupid. Running the code in Firefox yielded a real error message:
syntax error
try { __flash__toXML(%28function%28%29%7B%7D%29(5)) ; } catch (e) { "<undefined/...
---------------------^
So, apparently, it needs to be serializable to XML in order to be used as a callback. Which is really kind of annoying.
My solution was something like this:
var ytCallbackID = 1;
var makeYTCallback = function (func) {
var ret = "ytCallback" + (ytCallbackID++);
window[ret] = func;
return ret;
}
var onYouTubePlayerReady = function (id) {
var evt = makeYTCallback(function(state) {
onYouTubePlayerStateChange(id, state);
});
var ytplayer = document.getElementById(id);
ytplayer.addEventListener("onStateChange", evt);
};
I haven't fully tested it (since I did this right at the end of the day yesterday), but I think it'll work.
Got around to testing it, and it works like a charm! It's a bit fugly, both in idea and implementation, but it works and that's what's important :)
Is there a way to make it so Sammy.JS does not automatically call runRoute when you call app.run()?
My code currently initializes Sammy on the first load, but does not want it to actually call any sammy routes until the user actually clicks a link.
You could try passing in a non-operational route to the run method. It might ignore any route in the hash in that case.
Otherwise, you could set a listener on the document root to listen for clicks in the document and run the application then. But this solution seems "less clean."
(assuming jQuery)
$(function () {
var app = Sammy();
$("a").live(function () {
if (!app.isRunning()) {
app.run();
}
});
});
Question is old but I used the following solution that I find a little cleaner.
The page is loaded normally and Sammy doesn't call the current route when using .run().
http://sammyjs.org/docs/api/0.7.4/Sammy.Application.methods.before
var appIsRunning = false;
var app = Sammy(function() {
this.before('.*', function() {
if (this.path == document.location.pathname && appIsRunning == false) {
return false;
}
});
// The routes...
...
}
// start the application
app.run();
if (app.isRunning()) { appIsRunning = true; }