Came across this little gotcha whilst creating a custom image button control in client side JavaScript. Big thanks to Adam for helping out on this one.
Thought that I would create a custom atlas client side image button control. No worries, added the required event handlers to include an onclick handler, a mouse over handler and a mouse out handler. Rather than use a table I used div's and created the HTML from JavaScript.
var _clickHandler;
var _mouseOverHandler;
var _mouseOutHandler;
Setup the click event.
this.click = this.createEvent;
In the initialise method wire up the events using the Function.createDelegate method.
_clickHandler = Function.createDelegate(this, this.onClick);
this.element.attachEvent(“onclick”, _clickHandler);
And finally add the method to handle the callback and declare the method in the descriptor.
this.onClick = function() {
// do something
}
In the calling atlas client side class add the creation of the image button control and call the initialise button to setup the onclick event handler. I have included the code that I used to manually create the div.
this.createImageButtonElement = function(id, className, value, object) {
var element = this.createHtmlElement("myButton", id, className, value);
var imageButton = new AtlasStuff.UI.ImageButton(element);
imageButton.set_ImageUrl(baseUrl + "Images/buttons/mybutton.gif");
imageButton.set_Id(object.Id);
imageButton.initialize();
return element;
}
this.createDivElement = function(id, className, innerText) {
var element = document.createElement("div");
element.id = id;
element.className = className;
element.innerText = innerText;
return element;
}
Now the image button control is not really all that reusable due the onClick method performing a specific operation, so this is a onetime only control. As I am sure you will agree not very reusable. However, it did the job and performed the specific task that was required. As with all agile projects we embrace change, so when the user story changed this meant that I happily needed to refactor the very simple image button control to a more reusable control. Originally the image button control was being rendered in a div to have a 1 – to – 1 relationship between the button and the item, before the button was initialised I used a property to set the data associated with the button . The new design meant that the button had to be removed from the div and a table used instead with a checkbox replacing the button, a single button was required to handle all the items presented to the user in the table.
Rolling up the sleeves I decided to refactor the image button control first. The first change was to update the creation of the event source from this.createEvent to Type.Event(null) allowing the registration of handlers to perform actions. There is no need to change the declaration in the descriptor or the initialise method so we can dive into the event handler. Here we need to make a change to call the invoke method on the click object that we defined earlier.
this.onClick = function() {
this.click.invoke(this, null);
}
Something that I eluded to earlier was how I render the image button on screen. Now I also needed to make changes to the wiring up of the events to the element.
this.render = function() {
var imageButtonElement = document.createElement(“img”);
imageButtonElement.src = this.element.ImageUrl;
imageButtonElement.attachEvent(“onclick”, this._clickHandler);
this.element.appendChild(imageButtonElement);
}
So what is going on here, well the important line is the attaching of the click event handler with the delegate that was created in the initialise method. This will provide all the plumbing of the click event to the control and allow a subscriber to hook up to the published event onClick by the image button control.
So lets move on to the target that will subscribe and create the image button control. In the subscriber we need to write some code to create the image button control and we need to provide a callback method to perform the required actions when the user clicks on the image button control. So lets start by coding up the creation of the control.
Create a method to render the button so that it can be reused in other areas of the atlas client side class rembering to call the initialise method on the control’s class.
this.renderImageControlButton = function() {
var myImageButton = new ImageButtonControl(“htmlelementid”);
myImageButton.set_ImageUrl(“image path for control”);
myImageButton.click.add(this, this.myImageButtonCallback);
myImageButton.initialize();
}
Create the event handler and add the code you want to run when the callback is called.
this. myImageButtonCallback = function() {
// myImageButton has been clicked do something groovy
}
All that is left to do is add some clean up code and we are done.
//Destructor
this.dispose = function() {
_buttonClickHandler = null;
}
So there we are not all that painful. Add the code to the JavaScript files and maybe some alerts so that you can see what is happening and then you may spot the gotcha……
Did you see it ???
Lets dig a little deeper here, but before I do I am going to provide a little an analogy with winforms so bear with me. In windows when there is a long running process or an action that is heavy on the processor we tend to kick off a background thread to perform the operation and free up the UI thread so that the User Experience continues to be a good one. To do this we need to write marshalling code so that we update the UI on the UI thread and not on the background thread. If we don’t do this then bad things happen and at worse there is a thread out there with no home to return to. So we can do something that looks like the code below.
delegate void RefreshMeDelegate();
/// <summary>
/// Refresh the work queue
/// </summary>
public void refreshMe()
{
if (this.InvokeRequired)
{
try
{
Invoke(new RefreshMeDelegate(refreshMe), null);
return;
}
catch (NullReferenceException)
{
//handle the exception and log it
}
}
//do something with the ui on the ui thread
}
Ok so why am I going on about a simple pattern that I use in winforms when we are talking about atlas. Well what I found is that when I do
myImageButton.click.add(this, this.myImageButtonCallback);
the object that is associated with “this” is not what I had expected, it is actually the event object so we get a barf when the runtime tries to execute the callback. Very similar to our marshalling code that we wrote above. Because atlas does not do the marshalling for us we have to plumb it in ourselves. By adding a fix to how we wire up the event handlers subscription to the event.
myImageButton.click.add(Function.createDelegate(this, this.myImageButtonCallback));
So here we have to force atlas to create a delegate so that it knows where the callback lives which is very similar to how we
Invoke(new RefreshMeDelegate(refreshMe), null);
create a delegate to fire the event that we want on the correct thread and execute the correct method on the correct object. In Atlas we are registering the delegate that we want to execute our callback ensuring that the correct object and method are called.
If you would like the full source code for the custom image button control then please contact me at richard.griffin@conchango.com.