Im currently using KnockOut JS and i conducted some research about when the observable is notified it will fire a function which is like this
function FunctionToSubscribe()
{
}
var TestObservable = ko.observableArray([]);
TestObservable.subscribe(FunctionToSubscribe);
i am subscribing FunctionToSubscribe in this event
im currently thinking is there a way to unsubscribe it? like we do in c#? when unsubscribing events anyone have an idea regarding this???
The subscribe function returns the "subscription" object which has a dispose method what you can use to unsubscribe:
var TestObservable = ko.observableArray([]);
var subscription = TestObservable.subscribe(FunctionToSubscribe);
//call dispose when you want to unsubscribe
subscription.dispose();
See also in the documentation: Explicitly subscribing to observables
You can use dispose method.
function FunctionToSubscribe()
{
}
var TestObservable = ko.observableArray([]);
// subscribe
var subscriber = TestObservable.subscribe(FunctionToSubscribe);
// unsubscribe
subscriber.dispose();
In my case I needed to temporarily pause the subscription and do some work so I ended up doing this:
ko.subscribable.fn.suspendableSubscribe = function (callback, callbackTarget, event) {
var isSuspended = false;
return ko.utils.extend(this.subscribe(function () {
if (!isSuspended)
return callback.apply(this, arguments);
}, callbackTarget, event), {
suspend: function () { isSuspended = true; },
resume: function () { isSuspended = false; }
});
};
Usage
var name = ko.observable('CodingYoshi');
var subscription = name.suspendableSubscribe(function(){ // code... });
subscription.suspend();
name('CodingYoshi2');
subscription.resume();
Related
I'm currently developing a WebSocket. For that purpose I've created a JavaScript class. In this class I've 2 methods: subscribe() and bind(). The first method subscribes to a channel and the next one should listen to it.
But when I call my methods this way:
let socket = new CustomWebSocket();
socket.subscribe("my-channel").bind("my-event", function (response) {
console.log(response);
});
I'm getting an error message in my console:
Uncaught TypeError: socket.subscribe(...).bind is not a function
This is how I wrote my functions inside my CustomWebSocket class:
subscribe(channelName) {
let self = this;
let subMsg = {
type: "subscribe",
channel: channelName
};
self.ws.send(JSON.stringify(subMsg));
return channelName;
}
bind(eventName, callback) {
let self = this;
self.ws.onmessage = function (event) {
let eventData = JSON.parse(event.data);
if (eventData.type === "channelMessage") {
callback(eventData.payload);
}
}
}
What did I wrong? It thought it can work this way...
You're returning channelName; from subscribe. So you're effectively trying to call bind on channelName.
To be able to call bind on the return value of subscribe, you need to return self or this from subscribe.
I want to detect if the method of an object is called.
I have a video player in my page and when it is done playing, I want to show some contents.
For example:
function videoSet(){
var instance = this;
this.video = $('#video')
this.video.bind("ended", function() {
instance.endVideo()
});
}
videoSet.prototype.endVideo = function(){
$('#test1').css('visibility','visible');
}
//more methods...
function main(){
this.init();
}
main.prototype.init = function(){
this.video = new videoSet() //init an video object.
// more code...
//I need to know if the video is ended...
}
var mainObj = new main();
Inside my endVideo method, I have $('#test1').css('visibility','visible'); but I have so much code in my main object and I want to be able to detect if the video has ended in my main object.
Is that possible?
You can have multiple eventListeners on DOM objects...
var Video = function () { this.video = document.querySelector("#my-video"); };
var Main = function () {
var myVideo = new Video();
myVideo.video.addEventListener("ended", function () { console.log("It's over!"); });
myVideo.video.addEventListener("ended", function () {
console.log("Play something else.");
});
};
Main();
There's nothing stopping you from adding an event-listener to the object from inside of main.
Moreover, this leads to custom event systems -- Publisher/Subscriber or Observer or "Emitters".
If you can implement one of these, on an object, then your object can create/fire custom events, and pass custom data, and any time you have access to that object, you can subscribe (as long as you know what the events are called, and how to handle the data you will get back).
For example, you might want to have a video-playing system that loads the next film (or a countdown screen, until the next film, et cetera, for continuous playback, with a playlist that highlights the current film).
var VideoPlayer = function (id) {
var player = this;
player.video = document.getElementById(id);
// attach an emitter-system with "on", "off" and "emit", or whatever you choose
addEmitter(player);
player.load = function (video) { player.video.src = video.src; };
player.init = function () {
player.video.addEventListener("ended", function () {
// fire custom-event
player.emit("video-ended");
});
player.video.addEventListener("canplay", function () {
// auto-play video, fire event
player.video.play();
player.emit("video-playing");
});
};
},
VideoPlaylist = function (id, videos) {
var playlist = this;
playlist.root = document.getElementById(id);
playlist.videos = videos;
playlist.addVideo = function (video) { /* attach each video to the root */ };
playlist.currentVideoIndex = 0;
playlist.currentVideo = playlist.videos[playlist.currentVideoIndex];
playlist.select = function (i) {
playlist.currentVideoIndex = i;
playlist.currentVideo = playlist.videos[i];
// fire a custom event
playlist.emit("load-video", playlist.currentVideo);
};
playlist.nextVideo = function () {
var i = (playlist.currentVideoIndex + 1) % playlist.videos.length; // loops
playlist.select(i);
};
addEmitter(playlist);
};
var Main = function () {
var video_player = new VideoPlayer("my-player"),
video_playlist = new VideoPlaylist("my-playlist", [{ src : "...", title : "A" }, { src : "...", title : "B" }]);
video_player.on("video-ended", video_playlist.next);
video_playlist.on("load-video", video_player.load );
// add another listener for another component, to handle on-screen controls
video_player.on("video-playing", video_controller.show_playing);
// add another listener for another component, to display the data about the video
video_playlist.on("load-video", video_description.display);
// add another listener for another component to load comments
video_playlist.on("load-video", video_comments.load);
};
Main();
This isn't a particularly Java-like way of writing programs, but JavaScript isn't particularly Java-like (though you can make it look similar).
You'll notice that inside of the Main function all I'm doing is wiring behaviours together, rather than writing out custom logic.
Of course, you can take this way further...
...and I haven't shown you how my emitter is made, but they're not hard to make, either.
Publisher/Subscriber or Observer or Emitter implementations are great practice for JS (and very easy in JS compared to other languages).
But as you can see, with a little thinking, this is a really simple and versatile way of dispatching code.
You can use an ended flag in the videoSet object like
function videoSet() {
var instance = this;
this.ended=false;
this.video = $('#video')
this.video.bind("ended", function () {
instance.endVideo()
});
}
videoSet.prototype.endVideo = function () {
$('#test1').css('visibility', 'visible');
this.ended=true;
}
videoSet.prototype.isEnded = function () {
return this.ended;
}
//more methods...
function main() {
this.init();
//later
if(myVideoSet.isEnded()){
console.log('completed')
}
}
I was wondering if anyone can help me understand how exactly to create different Custom event listeners.
I don't have a specific case of an event but I want to learn just in general how it is done, so I can apply it where it is needed.
What I was looking to do, just incase some folks might need to know, was:
var position = 0;
for(var i = 0; i < 10; i++)
{
position++;
if((position + 1) % 4 == 0)
{
// do some functions
}
}
var evt = document.createEvent("Event");
evt.initEvent("myEvent",true,true);
// custom param
evt.foo = "bar";
//register
document.addEventListener("myEvent",myEventHandler,false);
//invoke
document.dispatchEvent(evt);
Here is the way to do it more locally, pinpointing listeners and publishers:
http://www.kaizou.org/2010/03/generating-custom-javascript-events/
Implementing custom events is not hard. You can implement it in many ways. Lately I'm doing it like this:
/***************************************************************
*
* Observable
*
***************************************************************/
var Observable;
(Observable = function() {
}).prototype = {
listen: function(type, method, scope, context) {
var listeners, handlers;
if (!(listeners = this.listeners)) {
listeners = this.listeners = {};
}
if (!(handlers = listeners[type])){
handlers = listeners[type] = [];
}
scope = (scope ? scope : window);
handlers.push({
method: method,
scope: scope,
context: (context ? context : scope)
});
},
fireEvent: function(type, data, context) {
var listeners, handlers, i, n, handler, scope;
if (!(listeners = this.listeners)) {
return;
}
if (!(handlers = listeners[type])){
return;
}
for (i = 0, n = handlers.length; i < n; i++){
handler = handlers[i];
if (typeof(context)!=="undefined" && context !== handler.context) continue;
if (handler.method.call(
handler.scope, this, type, data
)===false) {
return false;
}
}
return true;
}
};
The Observable object can be reused and applied by whatever constructor needs it simply by mixng the prototype of Observable with the protoype of that constructor.
To start listening, you have to register yourself to the observable object, like so:
var obs = new Observable();
obs.listen("myEvent", function(observable, eventType, data){
//handle myEvent
});
Or if your listener is a method of an object, like so:
obs.listen("myEvent", listener.handler, listener);
Where listener is an instance of an object, which implements the method "handler".
The Observable object can now call its fireEvent method whenever something happens that it wants to communicate to its listeners:
this.fireEvent("myEvent", data);
Where data is some data that the listeners my find interesting. Whatever you put in there is up to you - you know best what your custom event is made up of.
The fireEvent method simply goes through all the listeners that were registered for "myEvent", and calls the registered function. If the function returns false, then that is taken to mean that the event is canceled, and the observable will not call the other listeners. As a result the entire fireEvent method will return fasle too so the observable knows that whatever action it was notifying its listeners of should now be rolled back.
Perhaps this solution doesn't suit everybody, but I;ve had much benefit from this relatively simple piece of code.
From here:
https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
// create the event
const event = new Event('build');
// elem is any element
elem.dispatchEvent(event);
// later on.. binding to that event
// we'll bind to the document for the event delegation style.
document.addEventListener('build', function(e){
// e.target matches the elem from above
}, false);
Here is a really simple (TypeScript/Babelish) implementation:
const simpleEvent = <T extends Function>(context = null) => {
let cbs: T[] = [];
return {
addListener: (cb: T) => { cbs.push(cb); },
removeListener: (cb: T) => { let i = cbs.indexOf(cb); cbs.splice(i, Math.max(i, 0)); },
trigger: (<T> (((...args) => cbs.forEach(cb => cb.apply(context, args))) as any))
};
};
You use it like this:
let onMyEvent = simpleEvent();
let listener = (test) => { console.log("triggered", test); };
onMyEvent.addListener(listener);
onMyEvent.trigger("hello");
onMyEvent.removeListener(listener);
Or in classes like this
class Example {
public onMyEvent = simpleEvent(this);
}
If you want plain JavaScript you can transpile it using TypeScript playground.
I am struggling with what seems to be a simple concept which makes me think what I am doing can't be done.
In nodejs, if class objectA.emits('hey there'), can class objectB.on('hey there') repsond with 'yo'?
Object A and B have nothing to do with eachother other than they both extend EventEmitter and are in the same nodejs app.
Sorry if this question has been asked before, I can't find it.
Yes
That's pretty much it.
When dealing with Observer/Publisher-Subscriber patterns (or Mediator Patterns), the point is that it really doesn't matter what type of class it is that's doing the "emitting".
Assuming that A is an emitter:
var B = { doStuff : function () { console.log("Yo!"); } };
A.addListener("someEvent", B.doStuff);
A.emit("someEvent");
If you actually want them to talk back and forth, then you need to manually subscribe them to one another...
Assuming that both A AND B are emitters:
B.doStuff = function () { this.emit("B's event", "Yo!"); };
A.doThing = function (param) { console.log(param); };
B.addListener("B's event", A.doThing);
A.addListener("A's event", B.doStuff.bind(B));
A.emit("A's event");
Alternatively, you should look into a Mediator pattern (which also "emits", but is intended to be 1 object which mediates between many objects who don't know one another, but use the same event-names and pass well-defined data-structures, like a good API should).
Assuming that Mediator is an emitter, and A, B and C aren't:
var A = {
getData : function (request) { /* network call */ this.dataCallback(data); },
dataCallback : function (data) { Mediator.emit("data-recieved", data); }
},
B = {
display : document.getElementById("data-display"),
showData : function (data) { /* make DOM representation */ }
},
C = {
input : document.getElementById("request-input"),
button : document.getElementById("request-button"),
getRequest : function () {
var request = this.input.value;
this.requestData(request);
this.disableButton();
},
requestData : function (request) { Mediator.emit("data-request", request); },
disableButton : function () { this.button.disabled = true; },
enableButton : function () { this.button.disabled = false; }
};
Mediator.addListener("data-request", A.getData.bind(A));
Mediator.addListener("data-received", B.showData.bind(B));
Mediator.addListener("data-received", C.enableButton.bind(C));
C.button.addEventListener("click", C.getRequest.bind(C), false);
So now you've got 3 classes who know nothing about one another, each has its own special purpose, and the only expectations that they have of "one another" are that event-names and data-types are appropriate.
They all know about Mediator.
If you want Mediator to be further abstracted, then you can pass a reference to it when you're making your class:
function A (param1, param2) {
var emitter = null;
this.setEmitter = function (myEmitter) { emitter = myEmitter; };
this.emit = function (evt, data) {
if (!emitter) { return; }
emitter.emit(evt, data);
};
this.subscribe = function (evt, callback) {
if (!emitter) { return; }
emitter.addListener(evt, callback);
};
/* rest of the object */
};
var a = new A();
var b = new A();
a.setEmitter(Mediator);
a.subscribe("some-evt", a.doSomething.bind(a));
b.setEmitter(Mediator);
b.subscribe("other-evt", b.doSomethingElse.bind(b));
a.emit("other-evt", { /* data */ });
a and b don't have to be the same class, here, at all.
And now they DO work in the way that you're imagining.
Both have used Dependency Injection ("Inversion of Control") to point to the same emitter (Moderator), so they're both working off of the same event-list, without even knowing it, and using their own methods to subscribe to Moderators events.
In JavaScript, what is the best way to remove a function added as an event listener using bind()?
Example
(function(){
// constructor
MyClass = function() {
this.myButton = document.getElementById("myButtonID");
this.myButton.addEventListener("click", this.clickListener.bind(this));
};
MyClass.prototype.clickListener = function(event) {
console.log(this); // must be MyClass
};
// public method
MyClass.prototype.disableButton = function() {
this.myButton.removeEventListener("click", ___________);
};
})();
The only way I can think of is to keep track of every listener added with bind.
Above example with this method:
(function(){
// constructor
MyClass = function() {
this.myButton = document.getElementById("myButtonID");
this.clickListenerBind = this.clickListener.bind(this);
this.myButton.addEventListener("click", this.clickListenerBind);
};
MyClass.prototype.clickListener = function(event) {
console.log(this); // must be MyClass
};
// public method
MyClass.prototype.disableButton = function() {
this.myButton.removeEventListener("click", this.clickListenerBind);
};
})();
Are there any better ways to do this?
Although what #machineghost said was true, that events are added and removed the same way, the missing part of the equation was this:
A new function reference is created after .bind() is called.
See Does bind() change the function reference? | How to set permanently?
So, to add or remove it, assign the reference to a variable:
var x = this.myListener.bind(this);
Toolbox.addListener(window, 'scroll', x);
Toolbox.removeListener(window, 'scroll', x);
This works as expected for me.
For those who have this problem while registering/removing listener of React component to/from Flux store, add the lines below to the constructor of your component:
class App extends React.Component {
constructor(props){
super(props);
// it's a trick! needed in order to overcome the remove event listener
this.onChange = this.onChange.bind(this);
}
// then as regular...
componentDidMount (){
AppStore.addChangeListener(this.onChange);
}
componentWillUnmount (){
AppStore.removeChangeListener(this.onChange);
}
onChange () {
let state = AppStore.getState();
this.setState(state);
}
render() {
// ...
}
}
It doesn't matter whether you use a bound function or not; you remove it the same way as any other event handler. If your issue is that the bound version is its own unique function, you can either keep track of the bound versions, or use the removeEventListener signature that doesn't take a specific handler (although of course that will remove other event handlers of the same type).
(As a side note, addEventListener doesn't work in all browsers; you really should use a library like jQuery to do your event hook-ups in a cross-browser way for you. Also, jQuery has the concept of namespaced events, which allow you to bind to "click.foo"; when you want to remove the event you can tell jQuery "remove all foo events" without having to know the specific handler or removing other handlers.)
jQuery solution:
let object = new ClassName();
let $elem = $('selector');
$elem.on('click', $.proxy(object.method, object));
$elem.off('click', $.proxy(object.method, object));
We had this problem with a library we could not change. Office Fabric UI, which meant we could not change the way event handlers were added. The way we solved it was to overwrite the addEventListener on the EventTarget prototype.
This will add a new function on objects element.removeAllEventListers("click")
(original post: Remove Click handler from fabric dialog overlay)
<script>
(function () {
"use strict";
var f = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function (type, fn, capture) {
this.f = f;
this._eventHandlers = this._eventHandlers || {};
this._eventHandlers[type] = this._eventHandlers[type] || [];
this._eventHandlers[type].push([fn, capture]);
this.f(type, fn, capture);
}
EventTarget.prototype.removeAllEventListeners = function (type) {
this._eventHandlers = this._eventHandlers || {};
if (type in this._eventHandlers) {
var eventHandlers = this._eventHandlers[type];
for (var i = eventHandlers.length; i--;) {
var handler = eventHandlers[i];
this.removeEventListener(type, handler[0], handler[1]);
}
}
}
EventTarget.prototype.getAllEventListeners = function (type) {
this._eventHandlers = this._eventHandlers || {};
this._eventHandlers[type] = this._eventHandlers[type] || [];
return this._eventHandlers[type];
}
})();
</script>
Here is the solution:
var o = {
list: [1, 2, 3, 4],
add: function () {
var b = document.getElementsByTagName('body')[0];
b.addEventListener('click', this._onClick());
},
remove: function () {
var b = document.getElementsByTagName('body')[0];
b.removeEventListener('click', this._onClick());
},
_onClick: function () {
this.clickFn = this.clickFn || this._showLog.bind(this);
return this.clickFn;
},
_showLog: function (e) {
console.log('click', this.list, e);
}
};
// Example to test the solution
o.add();
setTimeout(function () {
console.log('setTimeout');
o.remove();
}, 5000);
As others have said, bind creates a new function instance and thus the event listener cannot be removed unless it is recorded in some way.
For a more beautiful code style, you can make the method function a lazy getter so that it's automatically replaced with the bound version when accessed for the first time:
class MyClass {
activate() {
window.addEventListener('click', this.onClick);
}
deactivate() {
window.removeEventListener('click', this.onClick);
}
get onClick() {
const func = (event) => {
console.log('click', event, this);
};
Object.defineProperty(this, 'onClick', {value: func});
return func;
}
}
If ES6 arrow function is not supported, use const func = (function(event){...}).bind(this) instead of const func = (event) => {...}.
Raichman Sergey's approach is also good, especially for classes. The advantage of this approach is that it's more self-complete and has no separated code other where. It also works for an object which doesn't have a constructor or initiator.
If you want to use 'onclick', as suggested above, you could try this:
(function(){
var singleton = {};
singleton = new function() {
this.myButton = document.getElementById("myButtonID");
this.myButton.onclick = function() {
singleton.clickListener();
};
}
singleton.clickListener = function() {
console.log(this); // I also know who I am
};
// public function
singleton.disableButton = function() {
this.myButton.onclick = "";
};
})();
I hope it helps.
can use about ES7:
class App extends React.Component {
constructor(props){
super(props);
}
componentDidMount (){
AppStore.addChangeListener(this.onChange);
}
componentWillUnmount (){
AppStore.removeChangeListener(this.onChange);
}
onChange = () => {
let state = AppStore.getState();
this.setState(state);
}
render() {
// ...
}
}
It's been awhile but MDN has a super explanation on this. That helped me more than the stuff here.
MDN :: EventTarget.addEventListener - The value of "this" within the handler
It gives a great alternative to the handleEvent function.
This is an example with and without bind:
var Something = function(element) {
this.name = 'Something Good';
this.onclick1 = function(event) {
console.log(this.name); // undefined, as this is the element
};
this.onclick2 = function(event) {
console.log(this.name); // 'Something Good', as this is the binded Something object
};
element.addEventListener('click', this.onclick1, false);
element.addEventListener('click', this.onclick2.bind(this), false); // Trick
}
A problem in the example above is that you cannot remove the listener with bind. Another solution is using a special function called handleEvent to catch any events: