There may be another way to accomplish this, but I wanted to have a simple commanding system in the Microsoft Ajax Library which worked outside of the templates.
After a number of false starts and frustration brought about by very limited documentation, I discovered a reasonable implementation.
I created a script file, “Commanding.js” (in a Scripts folder):
/// <reference name="MicrosoftAjax.js"/> Type.registerNamespace("WiredPrairie"); WiredPrairie.Commanding = function(element) { WiredPrairie.Commanding.initializeBase(this, [element]); } WiredPrairie.Commanding.prototype = { initialize: function() { WiredPrairie.Commanding.callBaseMethod(this, 'initialize'); // Add custom initialization here }, dispose: function() { //Add custom dispose actions here WiredPrairie.Commanding.callBaseMethod(this, 'dispose'); } } WiredPrairie.Commanding.registerClass('WiredPrairie.Commanding', Sys.UI.Control); if (typeof (Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();
This is just a basic JavaScript class, with the default implementation provided by the Ajax Client Control template in Visual Studio 2008. The reason this is needed is that Controls all support a special event onBubbleEvent that is needed by commands when they’re raised.
In the body of the HTML, I attached an instance of the “WiredPrairie.Commanding” class to the body. Next, I attached a JavaScript function to the onbubbleevent (wpc:onbubbleevent, remember that all attributes need to be all lowercase). Here, I’ve used the special {{ }} syntax to indicate I want JavaScript code to execute when the event is raised.
<body xmlns:sys="javascript:Sys" xmlns:wpc="javascript:WiredPrairie.Commanding" sys:attach="wpc" wpc:onbubbleevent="{{onCommand}}" > <div > <button sys:command="startRunningCommand" sys:commandargument="iargueaboutit">Run</button> </div> </body>
I needed my JavaScript WiredPrairie.Commanding class to load and run at the right time on the page, so I’ve used the new loader classes in the Ajax library.
Sys.loader.defineScripts(null, [ { name: "Commanding" ,releaseUrl: "Scripts/Commanding.js" ,debugUrl: "Scripts/Commanding.js" ,dependencies: ["ComponentModel"] } ]);
First, I needed to declare this new script file and any dependencies. Since I don’t yet have a minified version, I’ve just specified the same file for the releaseUrl and the debugUrl. For the dependencies property, I looked through a few source files to discover that the “ComponentModel” key included Sys.UI.Control, which my class depends on to be created, so I added that here (hopefully this will be documented at some point).
Next, I added code to indicate to the loader which scripts were necessary and let it determine the best way to load them:
Sys.require([Sys.components.dataView, Sys.scripts.jQuery,
Sys.scripts.Commanding]);
Here, you see the “Commanding” key is added to a special namespace, “Sys.scripts.”
In the new onReady event which is raised when the DOM and scripts have been loaded, I’ve added code to activate the control class I wrote:
Sys.onReady(function() {
Sys.activateElements(document.documentElement);
});
Finally, I wired up that event declared above in the onbubbleevent attribute on the body element.
function onCommand(sender, args) { if (typeof (args) !== "undefined") { var commandName = args.get_commandName(); var commandArgument = args.get_commandArgument(); alert(commandName + " " + commandArgument); } }
OK, that’s cool, but you might want to raise a command without it being triggered by a control event (such as a click). So, I added a simple function to my Commanding class:
WiredPrairie.Commanding.raiseCommand = function(sender, commandName, commandArgument, commandSource) { var source = sender || document.body; Sys.UI.DomElement.raiseBubbleEvent(source, new Sys.CommandEventArgs(commandName, commandArgument, commandSource)); } var $sendCommand = WiredPrairie.Commanding.raiseCommand;
Because of the way the raiseBubbleEvent works, it does depend on the source being set to a valid control/element (as raiseBubbleEvent walks through the parent chain until there aren’t any more parents) – in this case, I’ve defaulted to the body element.
Finally, an enhancement to the test case above to demonstrate both receiving and sending a command:
function onCommand(sender, args) { if (typeof (args) !== "undefined") { var commandName = args.get_commandName(); var commandArgument = args.get_commandArgument(); switch (commandName) { case "startRunningCommand": $sendCommand(null, "alertCommand", new Date().toLocaleTimeString(), null); break; case "alertCommand": alert(commandArgument); break; default: break; } } }
One command just sends another command which displays an alert. It’s simple, but functional.
Enjoy.