How do you attach to events on a ScriptObject in Silverlight? - javascript

The HtmlObject provides all the necessary functionality to register managed event handlers for script and DOM events, but what if the class you need to listen to doesn't exist as a DOM element, but a scripting variable (referenced via ScriptObject) instead?

A javascript object doesn't support the concept of attached events. However it may support the concept of a property holding a reference to function that if assigned will be called at a certain point.
I take you have such an object?
If so you use the ScriptObject SetProperty method using the name of the property that should hold a reference to a function and a delegate to Managed method matches the signature that the Javascript object will call.
Caveat the following is untested at this point but should put you on the right path.
//Javascript in web page.
var myObj = new Thing();
function Thing()
{
this.doStuff = function()
{
if (this.onstuff) this.onstuff("Hello World");
}
}
// C# code in a Silverlight app.
class SomeClass
{
private ScriptObject myObject;
public SomeClass(ScriptObject theObject)
{
myObject = theObject;
myObject.SetProperty("onstuff", (Action<string>)onstuff);
}
function void onstuff(string message)
{
//Do something with message
}
}

As stated by AnthonyWJones, Silverlight can't attached to JavaScript events. The right thing to do in this situation is to do the following:
Enable scripting access in Silverlight:
Mark the class with the
ScriptableType attribute, or mark
the specific methods with
ScriptableMember
Call
HtmlPage.RegisterScriptableObject in
the constructor.
Once everything is set up in the Silverlight code, here's what you do in JavaScript:
Obtain a reference to the JavaScript
object and register an event handler
Use document.getElementById to get
the Silverlight control
Call .Content.. in the
JavaScript event handler. For
example,
silverlight.Content.Page.UpdateText(text).
So basically, all event handling is performed in JavaScript, but the JavaScript event handlers can be used to call functions in Silverlight.

Related

JavaScript - Correct way to remove event listener added by my class [duplicate]

I am trying to remove an eventListener but it looks like I miss something.
Why is the following code not working, it doesn't remove the event listener from the button.
I also tried binding this to pass the scope, but that didn't work either
class Test {
eventHandler(e) {
console.log(e.target.id)
alert()
// no effect
e.target.removeEventListener("click", this.eventHandler)
// no effect either
document.getElementById(e.target.id).removeEventListener("click", this.eventHandler)
}
constructor() {
let b = document.getElementById("b")
b.addEventListener("click", this.eventHandler)
//b.addEventListener("click", this.eventHandler.bind(this) )
}
}
new Test()
<button id="b">
click me
</button>
Prototype methods as event handlers are a bit problematic, specifically when you need both, the this value bound to the instance, and the reference to the actual event handler function.
By default, the event queue calls the handler in the context of the element the event was bound to. It's easy to change the context, but that provides you to create a new function, which then is used as the event handler, and that function is not the method in the prototype anymore.
If you want to keep the compact class structure, one way is to define the event handler methods as own properties of the instance, they simply can't be inherited. The simplest way would be to define the methods as arrow functions in the constructor.
class Test {
constructor() {
this.eventHandler = e => {
console.log(e.target.id);
e.target.removeEventListener("click", this.eventHandler);
};
let b = document.getElementById("b");
b.addEventListener("click", this.eventHandler);
}
}
new Test();
<button id="b">Click me!</button>
The arrow function keeps the reference to the lexical environment it was defined in, and the event queue can't override the context. Now this in the handler function is correctly bound to the instance, and this.eventHandler refers to the function, which was attached to the event.
A slightly less memoryconsuming option would be to use bind when creating the own property, like this:
class Test {
constructor() {
this.eventHandler = this.eventHandler.bind(this);
let b = document.getElementById("b");
b.addEventListener("click", this.eventHandler);
}
eventHandler (e) {
console.log(e.target.id);
e.target.removeEventListener("click", this.eventHandler);
}
}
Here bind creates a new function object, which then calls the method in the prototype, the actual code of the method is not duplicated. This is loosely similar if you wrote:
this.eventHandler = e => Test.prototype.eventHandler.call(this, e);
It's notable, that when defining an own property with the same name an underlying prototype property has, the prototype property is not overridden, it's only shadowed in the instance, and multiple instances of the class will still work as intended.
Another option is to create your own "event model", which creates a wrapper function (like in the very last code example above) for all events, and stores the reference to that function. The wrapper calls the actual handler with call, which can bind the wanted this value to the event handler. The stored function references are used to remove events. Building such a model is not extremely complex, but it provides a bit knowledge of how the this binding and native event model work.
The OP's code does not work for two reasons.
in one case the prototypal eventHandler misses the correct this context.
for a second case of running this.eventHandler.bind(this) one creates a new (handler) function with no saved reference to it. Thus with removeEventHandler one never refers to the correct event handler.
Possible solution ...
function handleTestClickEvent(evt) {
console.log(evt.currentTarget);
console.log(this);
console.log(this.eventHandler);
// remove the instance specific (`this` context) `eventHandler`.
evt.currentTarget.removeEventListener('click', this.eventHandler);
}
class Test {
constructor() {
// create own eventHandler with bound `this` context.
this.eventHandler = handleTestClickEvent.bind(this);
document
.querySelector('#b')
.addEventListener('click', this.eventHandler);
}
}
new Test();
<button id="b">click me</button>
Another possible approach was the usage of an arrow-function based, thus instance-specific, event-handler. Arrow-functions do not support an explicit this binding. They always refer to the context where they are implemented in.
class Test {
constructor() {
// arrow-function based, thus instance-specific event-handler.
this.eventHandler = evt => {
console.log(evt.currentTarget);
console.log(this);
evt.currentTarget.removeEventListener('click', this.eventHandler);
}
document
.querySelector('#b')
.addEventListener('click', this.eventHandler);
}
}
new Test();
<button id="b">click me</button>
Nevertheless, both approaches show, that the prototypal implementation of a reference-specific event-handler is not the path one should follow.
For the scenario provided by the OP I would pick the 1st solution for it provides code reuse by the locally implemented handleTestClickEvent. It also comes with a smaller footprint regarding the instance specific this.eventHandler which for the former gets created from handleTestClickEvent.bind(this) whereas the 2nd solution provides a full handler implementation to every single instance.

Javascript * is not a function (prototype function)

Coming from a C++ background, trying to work with an OO language that doesn't have explicit typing is a little more than a headache.
So I have dynamic elements for a webpage that are "controlled" by objects since there are tons of stuff I need to manage on each for it to work. The element is just the visual output of the data inside of the object itself, that's all I really need it for.
Except that I need the object to perform an internal function when it's clicked. That seems to be the biggest source of my headache thus far.
Javascript:
function onClick(file) //The external onClick function I use to try to get it to call from.
{
file.state = INUSE;
file.checkState();
}
function fileObject () { //The file object itself
this.element;
this.newElement();
//initialize stuff for the object
}
fileObject.prototype.newElement = function() { //creates a new element and sets its event listener
this.element.click(function() {onClick(this)});
}
fileObject.prototype.checkState = function() {/*does stuff*/} //apparently this is "not a function"
The error I get exactly is "file.checkState is not a function" from Firefox's console panel.
I'm still new to javascript, but after doing some debugging, I've come to find out that it's explicitly the onClick(this) function that is causing all of the errors. When used with something else, the onClick function works perfectly, but for some reason, the this keyword doesn't appear to actually be sending the reference to the fileObject since all checks show file being undefined when inside of the onClick scope.
Is there something fundamentally wrong about the way I'm trying to do this or am I just missing a step (or adding something that I don't need) that will help get this snippet working.
So you know, your initial problem isn't actually handling the action, but listening to it. click will trigger a synthetic click event, rather than liste for one.
You want ... .element.addEventListener("click", callback); that said, you face a second problem, immediately thereafter.
I will leave my example code as you've written it to not confuse the matter...
But when you see click( ) know that I mean subscribing with addEventListener, if element really does mean a browser DOM element. If it's not a standard browser element, and your own API, then ignore the previous portion, and carry on.
this is dynamically bound at the invocation time of the function (not at definition time).
The nearest function, scoped above, is your callback function that you are passing into .click( ... ).
Which is entirely different than the this which you mean outside of the callback.
Whatever is on the left-hand side of the dot is the this context for the duration of that particular invocation.
Needless to say, click() doesn't know enough to bind the this you mean, to the left-hand side of your callback.
The solution (or one of many) is to use lexical scoping and/or closure to retain the value of the object you mean.
// easy but messier
var fileObject = this;
... .click(function () { onClick(fileObject); });
// Cleaner with thunks:
function clickHandler (onClick, obj) {
return function () { onClick(obj); };
}
... .click(clickHandler(this));
Coming from c++ the way Javascript handles this will seem a little crazy, it looks like here you need to tell the function you've defined what this is - like so:
this.element.click(function() {onClick(this)}.bind(this));

Removing event handlers in the destroy method of vanilla JavaScript plugins

What's the best way of unbinding event handlers in the destroy method of a plain JS plugin? The following (non working) code shall demonstrate what I mean:
var myPlugin = (function(){
function myPlugin(selector){
var elems = document.querySelectorAll(selector);
for (var i=0; i<elems.length; i++) {
function _handler(){ console.log('Hello'); }
elems[i].addEventListener("click", _handler);
}
this.destroy = function(){
document.removeEventListener("click", _handler);
};
}
return myPlugin;
})();
So, I iterate over a set of elements and do something with them, including attaching an event handler function. The problem: In plain JS, I need a reference to the original handler in order to remove it when the plugin instance gets destroyed.
This snippet naturally cannot work, because the event handler function is written over and over again with each selected element.
One way of handling this: Creating functions with a dynamic/unique name, as described here: Creating functions dynamically in JS.
The function needs to be globally set on the window object. Then, I just need to remember the name (e.g. by using a data attribute on the selected element) and with that, it's possible to unbind the event later on.
However, this approach is clumsy and I run into issues on IE8, when using such function with attachEvent. Is there a better way or any best practice for that?

Javascript Object Inheritence

I'm creating a control for Google maps v2. While creating my control I've found a design challenge and want to find an appropriate solution. Here's the goods.
A custom Google control inherits from GControl;
myControl.prototype = new GControl();
Next I need to overload the initializer so here it is.
myControl.prototype.initilize = function (map) {
//do some work and return stuff
};
Now within my custom controls initlize function I create a couple elements which, using the GEvent class, I subscribe to various events. To make my callback functions managable, I included them into the controls prototype.
myControl.prototype.onEvent = function(e){
//do some work;
//modify the members of the current myControl instance
};
Within my callback function "onEvent" I want to modify members within my control. What is the best way to access my control from the function? The keyword "this" cannot be used because that is a reference to the element that was clicked, in my case a div. And I can't access the members through the prototype because I need a specific instance of the object. The only viable solution I've considered is to create my control globally in one of my scripts. Is this the best method?
The easiest thing that I can think, it to define your onEvent method within your constructor, there you will have quick access to the current object instance, and you will not have to modify your public API:
function MyControl () {
var instance = this; // store a reference to 'this'
this.onEvent = function (e) {
// use the instance variable
instance.otherMethod();
};
}
Note that in this approach, the onEvent property will exist physically in your object instances (obj.hasOwnProperty('onEvent') = true).
Edit: You can simply use the GEvent.bind function:
GEvent.bind(map, "click", myObj, myObj.onEvent);
The above bind method will enforce the context, so the this keyword inside myObj.onEvent will point to the myObj object instance when the event is triggered, it will work with your code without problems.
I'm not familiar with how you subscribe to events using GEvent, so I'll make that part up. Do this:
myControl.prototype.onEvent = function(e, instance) {
// do some work
// modify members of 'instance'
};
function wrap(handler, instance) {
return function(e) {
handler(e, instance);
}
}
GEvent.Register('Event', wrap(instance.onEvent, instance));

Invoke a javascript function with VB.net through COM Interop

I have a VB.net class registered for COM interop which I'm instantiating within an HTML page using the following code:
<script type="text/javascript">
var MyClass = new ActiveXObject("Namespace.TestClass");
</script>
I can call methods on it just fine, but suppose I want to set a javascript function as a property, like so:
MyClass.TestFunction = function () { alert("It worked!"); }
How would I set my vb.net code up to be able to fire that function? This is how MSXML works in javascript for XMLHttpRequest objects, you can set
XHR.onreadystatechange = function () {}
I'm looking for a similar implementation in my class.
You have to expose a COM event, and assign the JavaScript method to that event. This way, when you invoke the event in your code, the JavaScript method will be called.
Example -
C# Code
[ComVisible(false)]
public delegate void OperationCompleted(string message); //No need to expose this delegate
public event OperationCompleted OnOperationCompleted;
if(OnOperationCompleted != null)
OnOperationCompleted("Hello World!");
JavaScript
comObject.OnOperationCompleted = function(message) { alert(message); }
Note: I have done this before. And I guess there was some COM related error. To resolve it I had to attach some attribute somewhere in the code (I don't remember it exactly right now). But you'll be able to figure it out or google it.
After trying for a while, we managed to figure out a solution that worked pretty well. Since we're setting a javascript function to the property, all the properties and methods on that function are made available to the VB.net, including the javascript standard methods, call and apply:
(function () { alert("Hello"); }).call();
The solution was to just invoke the call method in the VB.net code and it seems to work pretty well.

Categories

Resources