Ok, I just stepped into something tricky. I need to fire an event on my app everytime a major update happens (because I want multiple windows opened, therefore they need to be updated). The thing is, with that event, I wish to pass an argument, something like this:
Ti.App.fireEvent('updateViews', {data: my_data});
So far so good, but when I receive this event I want to be able to access its data. The only way I found out to do so is to create an anonymous function, like this:
Ti.App.addEventListener('updateViews', function(data)
{
var name = data.name;
});
Great! That works! Now the big problem.. when I close this window, I need to remove that listener... Otherwise I'll end up with a memory leak. The thing is, if I change the anonymous function and pass it to a handler, I'm unable to access the data - but if I don't, I cant reference the function to successfully remove it. Please, help!
PS: There IS a way to do this, of course I could pass the data using Alloy.Globals (or the equivalent in the standard Ti), but I want a CLEAN, elegant solution that does not involve using Alloy.Globals. Thanks in advance.
Is there a reason you need to use global app events? They make code more difficult to maintain and result in tighter coupled dependencies that are brittle when something changes. It also increases side effects when attempting to understand the code (Your future self will forget and you will get lost and confused).
The problem your experiencing is probably from the assumption that by assigning to a data property that it becomes the argument. The object you pass into fireEvent will be the object passed to the callback argument.
Ti.App.fireEvent('updateView', {data: {name: "foo"}});
Ti.App.addEventListener('updateView', function(e) {
var name = e.data.name;
});
That being said, events in general can easily pass data via the fireEvent as you demonstrated. I find in cases of 'click' events I'm more interested in static data then in dynamic data. I use partial applications for this: (example uses underscore provided by Alloy, but the functionality can easily be polyfilled)
var _ = Alloy._;
var titles = ['foo', 'bar', 'baz'];
var messages = [
'This ia s foo message.',
'But a bar message is better.',
'Then again a baz message trumps them all.'
];
function onClick(message, e) {
e.cancelBubble = true; // Not required but shows how to access the event object
alert(message);
}
var tableData = _(titles).map(function(title, index) {
var row = Ti.UI.createTableViewRow({title: title});
row.addEventListener('click', _.partial(onClick, messages[index]));
return row;
});
var table = Ti.UI.createTableView({
data: tableData
});
So.. I figured it out. You can actually do that.. The problem was that I was registering the handler the wrong way. It will work if you just only set the handler to receive data. Example for the above code:
var handler = function(data) {
var name = data.name;
alert(name);
}
Ti.App.addEventListener('updateViews', handler);
Then, to remove it:
Ti.App.removeEventListener('updateViews', handler);
Related
Can I retrieve and modify a previously assigned event function?
For example I originally add an onclick event handler to a node, like this :
var someNode = document.getElementByID('someNode');
someNode.onclick = function(){
//some stuff
};
Ideally later I would need to get back this event and modify the "some stuff" code content.
Is it doable in javascript?
You can modify the onclick event. Simply assign that to a new function will do. However, similar to most dynamic languages, function is not a data structure that you can easily modify. So keeping the same function but modifying it is AFAIK impossible.
The way I would suggest is to create a new function for your use, and assign it to the onclick property. However, JS is a very nice language that provide closure to your variables. So you can make a function that return a function to fit your need if you need some flexibility.
It's not really clear what you are trying to accomplish. But do you mean something like that ?
var someNode = document.getElementByID('someNode');
let customizablePart = function (e) {
// do some stuff
}
someNode.onclick = function (e) {
// unchangeable instructions
customizablePart.call(this, e)
}
// later ...
customizablePart = function (e) {
// do new stuff
}
Let's say I'm creating a chat system in javascript.
var ChatController = function() {
this.receiveMessageInteractor = new ReceiveMessageInteractor(this);
// ReceiveMessageInteractor delegate
this.didReceiveMessage = function(message) {
// ...
};
};
The ChatController also does some other stuff related to creating the html for the messages, but that's not important here.
The ChatController sets himself as a delegate of the ReceiveMessageInteractor, which will call the didReceiveMessage when a new message arrives.
var ReceiveMessageInteractor = function(delegate) {
this.observer = NotificationCenter.addObserver('DidReceiveMessageNotification' , function(data) {
var message = data['message'];
// format some message data
delegate.didReceiveMessage(message)
});
};
The ReceiveMessageInteractor just subscribes to a notification (NotificationCenter here is similar to the iOS one), does some formatting with the data and passes a message object to the delegate;
When the chat view goes of the screen (html gets deleted), my MenuController stops holding a pointer to ChatController, in which case I'd like it to be deleted, along with ReceiveMessageInteractor and observer.
The problem is that Javascript has no weak references, so ReceiveMessageInteractor is holding a pointer to ChatController, and even if ChatController wasn't holding a pointer to ReceiveMessageInteractor, my ChatController would still be alive, because the notification callback is holding a pointer to it (delegate).
So even if ReceiveMessageInteractor stopped existing, my ChatController would still not go away when the MenuController stops holding a pointer to it (because I can't have a weak reference inside the notification callback).
How do I solve this problem?
How do I solve this problem?
By understanding JavaScript. The problem is not that "Javascript has no weak references", the problem is that you don't know how to work without them because you come from a language that has them.
How would you remove that reference in any other language that doesn't have weak refs natively? Let's say C++. You would do like everyone does, including the implementors of the compiler/garbage collector/weak refs you're used to: you clean up after yourself.
function ChatController() {
this.receiveMessageInteractor = new ReceiveMessageInteractor(this);
// ReceiveMessageInteractor delegate
this.didReceiveMessage = function didReceiveMessage(message) {
// ...
};
this.destroy = function destroy() {
this.receiveMessageInteractor.destroy();
};
};
function ReceiveMessageInteractor(delegate) {
function callback(data) {
var message = data.message;
// format some message data
delegate.didReceiveMessage(message);
}
this.observer = NotificationCenter.addObserver('DidReceiveMessageNotification', callback);
this.destroy = function destroy() {
// Or however you NotificationCenter works, I don't know
NotificationCenter.removeObserver('DidReceiveMessageNotification', callback);
};
};
The Observer pattern implies resource management, even though it's not obvious (how is an "observation" relationship a resource??). Acquire and release. No hand-holding.
Also, notice the change in style. And please, learn the language, use prototypes, and, although not everyone will agree with me on this point, do not assign methods in the constructor.
edit: I forgot to add: ReceiveMessageInteractor? Really? What's wrong with MessageReceiver or something in that vein?
Your problem is not with the absence of weak references. All of your objects continue to have a hard reference, all originating from your NotificationCenter.
The NotificationCenter has a reference to the data handler, which has closure access to it's parent ReceiveMessageInteractor instance, as well as access to the delegate variable. Removing a reference to delegate from elsewhere won't break the anonymous function's access to it, therefore it stays.
What you'll need to do is add a .cleanup() method to each Controller that is called when it is removed.
In the ChatController.cleanup() method, you would want to call a method to remove the observer, something along the lines of this.receiveMessageInteractor.observer.unsubscribe().
The .unsubscribe() method should be defined in the NotificationCenter and remove the function(data) { ... } method you defined in .addObserver() from whatever data structure is holding it (or further down the line).
This is the same kind of pattern Facebook utilized in it's React framework + Flux architecture. Each component has a componentWillUnmount() method that assists in cleaning up data event handlers just like yours.
What I want in html code:
<a onclick="DeleteImage('javascript:this.id', <?=$_SESSION['UserID']?>)"></a>
which passes the var userid from $_session to the javascript function:
function DeleteImage(aid,userid){}
This worked when i didnt had to pass the $_session variable and only had this function
function DeleteImage(aid){}
Then i could create the a element in javascript like this:
cross = document.createElement('a');
cross.onclick = function() { DeleteImage(this.id) };
How can I create the a element so it generates the html code i want?
I want something like:
cross.onclick = function() { DeleteImage(this.id, "<?=$_SESSION['UserID']?>") };
which obviously does not work. Any help appreaciated :)
I think you should change the way you retrieve an information like $_SESSION['UserID']. An interesting option would be creating a data attribute, for example in the <html> tag itself:
<html data-user-id="<?=$_SESSION['UserID']?>">
Then all you have to do is to retrieve that value in the DeleteImage function itself:
function DeleteImage(aid) {
var userId = document.documentElement.getAttribute("data-user-id");
...
}
And you're done, you can call DeleteImage with the old way. (Note: you can also use document.documentElement.dataset.userId in Chrome, Firefox and Opera. There are partial polyfills for IE, but I guess they're not worth the effort in your case.)
You can even save some extra cycles retrieving the value at the beginning of the script and storing it in a scoped variable:
var userId = document.documentElement("data-user-id");
function DeleteImage(aid) {
...
}
Slightly off topic, may I suggest to using some more modern way to attach event listeners? And maybe considering some kind of event delegation?
After discovering about Javascript namespaces, I tried to implement them but I run into a problem while trying to attach a namespace method to an element's onclick.
I used this method to wrap up my functions/methods/classes (a simplified concept, not my actual code):
;(function(window, undefined) {
//my namespace
var NS = {};
NS.test = {
f : function(param) {
alert(param);
}
}
NS.test.('test 2');
})(window);
Inside, everything works fine and "test 2" is prompted.
However, when I try to attach that function to a click event, by doing something like this:
<a href-"#" onclick="NS.test.f('test');">Click me!</a>
it doesn't work, just like it doesn't work when I call that function after the })(window); part.
I tried it calling it window.NS.test.f('test'); but with no effect.
How can I make an onclick event call my function?
I could attach an event listener inside my wrapper, like I do for other html elements with no difficulty, but it would be problematic in this case since I'm generating the links with javascript and I find it easier and simpler to just add onclick="doSomething" for all my links, instead of creating them, then cache them and add event listeners.
Call me lazy, but in this particular case I prefer to do
someDiv.innerHTML = my_Generated_Html_Code_With_OnClick;
instead of
//demo code, ignore the flaws and the fact it won't work on IE
someDiv.innerHTML = my_generated_Html_code;
myLink = document.getElementById(id);
myLink.addEventListener('mousedown', NS.test.f('test'));
I do not use any framework nor do I wish to, since I'm trying to get a better understanding of the so-called vanilla javascript first.
I set up a jsfiddle here.
P.S. I must admit I didn't understand namespaces completely so if I'm doing something wrong here or applying the concept in a way I am not supposed to, I would appreciate any tips or corrections
That's because NS is declared inside and hence only exists inside the function:
function(window, undefined) {
var NS = {};
// NS exists here ...
}
// ... but not here
If you want to make it available to the rest of the page, then you can do:
function(window, undefined) {
var NS = window.NS = {};
// NS and window.NS exist here ...
}
// ... and window.NS exists here.
I'd like to have something like this work:
var Events=require('events'),
test=new Events.EventEmitter,
scope={
prop:true
};
test.on('event',function() {
console.log(this.prop===true);//would log true
});
test.emit.call(scope,'event');
But, unfortunately, the listener doesn't even get called. Is there any way to do this w/ EventEmitter? I could Function.bind to the listener, but, I'm really hoping EventEmitter has some special (or obvious ;) way to do this...
Thanks for the help!
No, because the this value in the listener is the event emitter object.
However what you can do is this
var scope = {
...
};
scope._events = test._events;
test.emit.call(scope, ...);
The reason your event handler did not get called is because all the handlers are stored in ._events so if you copy ._events over it should work.
That won't work, and emit only has a convenient way to pass parameters, but none for setting this. It seems like you'll have to do the binding stuff yourself. However, you could just pass it as a parameter:
test.on('event',function(self) {
console.log(self.prop===true);//would log true
});
test.emit('event', scope);
I came across this post when Google searching for a package in NPM which handles this:
var ScopedEventEmitter = require("scoped-event-emitter"),
myScope = {},
emitter = new ScopedEventEmitter(myScope);
emitter.on("foo", function() {
assert(this === myScope);
});
emitter.emit("foo");
Full disclosure, this is a package I wrote. I needed it so I could could have an object with an EventEmitter property which emits for the containing object. NPM package page: https://www.npmjs.org/package/scoped-event-emitter