Please excuse the ugly code this is here just as the simplest possible version of my actual code that I can use to reproduce my error. I am basically using a WebView in titanium to open a locally held .htm file so that I can leverage HTML5 graphics capabillities. What I am doing works fine. The problem is that I need to pass some data to the htm file which I am doing exactly as the docs recommend - using Ti.App.fireEvent - and this works ... once. But if I navigate away from the window and then navigate back again it fails and gives me a NS_ERROR_NOT_AVAILABLE. I have tried this code in firefox as a web preview and on Android device and emulator with the same issue in each. Clearly there is some issue with it not loading the same way if the view is called back, I am guessing it is pulled back off the stack which is messing with the 'load' event listener or something, but I have no idea how to fix it. Here is a simplified version of my code, just to demonstrate the issue:
app.js
Titanium.UI.setBackgroundColor('#000');
var win = Ti.UI.createWindow({
layout: 'vertical',
});
var wv = Ti.UI.createWebView({
url: 'test.htm',
height: '50%'
});
var but = Ti.UI.createButton({
width: 100,
height: 50,
title: 'Press',
});
var wvopen = false;
but.addEventListener('click', function() {
if (wvopen === false) {
win.add(wv);
wvopen = true;
} else {
win.remove(wv);
wvopen = false;
}
});
wv.addEventListener('load', function() {
Ti.App.fireEvent('go');
});
win.add(but);
win.open();
And the .htm file:
test.htm
<!doctype html>
<html>
<head>
<title>Test</title>
</head>
<body>
<p>A Little Test</p>
<script>
var Ti = window.parent.Ti;
Ti.App.addEventListener('go', function(){
alert(1);
});
</script>
</body>
</html>
I found the answer myself eventually. It is in the docs but realising what the problem actually is and why it is happening is not always simple so I feel it is worth me answering here myself for others use.
The key point is this:
"Keep in mind that app-level events are global, which means they remain in context the entire time your app is running (unless you remove them). This also means that any objects they reference will also remain in scope while your app runs. This could prevent those objects from being garbage collected. See the Managing Memory and Finding Leaks chapter for more information."
~ Titanium docs.
link: https://wiki.appcelerator.org/display/guides2/Event+Handling#EventHandling-Application-LevelEvents
So basically the event listener will exist even if it is not loaded and you try to delete the context in which it exists. So you must delete both the event listener and nullify the view that holds it.
In practice the implementation may vary a little depending on your specifics, but this is what I came up with.
NB ... there may be more efficient methods to do this in which case please let me know.
app.js
/*
* Build window and buttons
*/
var win = Ti.UI.createWindow({
layout: 'vertical',
backgroundColor:'black'
});
var but = Ti.UI.createButton({
top: 20,
width: 200,
height: 50,
title: 'Toggle WV',
});
var but2 = Ti.UI.createButton({
top: 20,
width: 200,
height: 50,
title: 'Fire Event'
});
var wv;
function newWv(){
wv = Ti.UI.createWebView({
top:20,
right: 20,
left: 20,
height: '50%',
url: 'test.htm',
});
}
win.add(but);
win.add(but2);
/*
* Main functionality goes here of tests goes here.
*/
var isVisible = false;
but.addEventListener('click', function() {
if (isVisible) {
win.remove(wv);
Ti.App.fireEvent('close');
wv = null;
isVisible = false;
} else {
newWv();
win.add(wv);
isVisible = true;
}
});
but2.addEventListener('click', function() {
try{
Ti.App.fireEvent('go');
} catch(e) {
alert(e);
}
});
win.open({modal:true});
And then a few changes in the htm file:
test.htm
<!doctype html>
<html>
<head>
<title>Test</title>
</head>
<body>
<p>A Little Test</p>
<script>
var Ti = window.parent.Ti;
var go = function() {
alert('called by Titanium app');
};
var close = function() {
Ti.App.removeEventListener('go',go);
Ti.App.removeEventListener('close',close);
};
window.addEventListener('load', function() {
Ti.App.addEventListener('go', go);
Ti.App.addEventListener('close', close);
});
</script>
</body>
</html>