Silverlight Tip: How to reflect ScriptObject content in runtime
Today I want to show how Silverlight application could "understand" which objects form HTML DOM from hosting page it deals with. The minute before I start, let me show why many of Silverlight developers need it.
Lets assume we have some simple HTML/ASPX page, with some JavaScript functionality and JavaScript objects, like follows:
1: <html xmlns="http://www.w3.org/1999/xhtml" style="height:100%;">
2: <head runat="server">
3: <title>Test Page For ScriptReflector</title>
4:
5: <SCRIPT language="JavaScript"> 1:
2: //Some JS variables...
3: var BOARD = {}; 4: var elmBoard;
5: var b = BOARD;
6:
7: //JS Function, which will intialize JS variables, but also will call SL functionality
8: function foo()
9: { 10: elmBoard = document.getElementById("Xaml1"); 11:
12: b.TestPropery = "Alexg@sela.co.il";
13: b.extObj = elmBoard;
14: b.extDoc = b.extObj.document;
15: b.extBody = b.extDoc.body;
16: b.extWin = b.extDoc.parentWindow;
17:
18: //Execute SL functionality
19: var sl = document.getElementById("Xaml1").content; 20: sl.SL.DoSomething(b);
21:
22: }
23:
</SCRIPT>
6: <style type="text/css">
7: #Text1
8: { 9: width: 300px;
10: }
11: </style>
12: </head>
13: <body style="height:100%;margin:0;">
14: <form id="form1" runat="server" style="height:100%;">
15: <input id="Text1" type="text" value="Some HTML text is here..."/><input id="Button1" type="button" value="Click Me!" onclick="foo();" />
16: <asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
17: <div style="height:80%;">
18: <asp:Silverlight ID="Xaml1" runat="server" Source="~/ClientBin/ScriptReflector.xap" MinimumVersion="2.0.30523" Width="100%" Height="100%" />
19: </div>
20: </form>
21: </body>
22: </html>
When user clicks on the button, it does something with JavaScript objects, and then executes some Silverlight function (Silverlight class was already marked as [ScriptableType] and corresponding function was marked as [ScriptableMember]). Assuming we trying to debug Silverlight application and inspect the received object, what we will see?
Not very informative...
But what if we need to know what is in this ScriptObject? Which properties it has? Which functions? Could it be done?
Sure it could be done... Lets start by adding two simple functions to our JavaScript section:
1: //ScriptReflector Helper function - for Properties Reflection
2: ReflectProperties = function(obj)
3: { 4: var props = new Array();
5:
6: for (var s in obj)
7: { 8: if (typeof(obj[s]) != "function")
9: { 10: props[props.length] = s;
11: }
12: }
13:
14: return props;
15: }
16:
17: //ScriptReflector Helper function - for Functions Reflection
18: ReflectMethods = function(obj)
19: { 20: var methods = new Array();
21:
22: for (var s in obj)
23: { 24: if (typeof(obj[s]) == "function")
25: { 26: methods[methods.length] = s;
27: }
28: }
29:
30: return methods;
31: }
Those functions will help us to "reflect" JavaScript properties at runtime, and they also could be executed from managed code.
Now lets have two managed code functions, which will execute those JavaScript helper functions:
1: //Properties reflector
2: public static T ReflectProperties<T>(ScriptObject obj)
3: { 4: T retVal = default(T);
5:
6: ScriptObject properties = HtmlPage.Window.Invoke("ReflectProperties", obj) as ScriptObject; 7: if (null != properties)
8: if (int.Parse(properties.GetProperty("length").ToString()) > 0) 9: retVal = properties.ConvertTo<T>();
10:
11: return retVal;
12: }
13:
14: //Methods reflector
15: public static T ReflectMethods<T>(ScriptObject obj)
16: { 17: T retVal = default(T);
18:
19: ScriptObject methods = HtmlPage.Window.Invoke("ReflectMethods", obj) as ScriptObject; 20: if (null != methods)
21: if (int.Parse(methods.GetProperty("length").ToString()) > 0) 22: retVal = methods.ConvertTo<T>();
23:
24: return retVal;
25: }
We almost done. I've added function, which will use one of those functions and will return Dictionary<string, object>:
1: //Deep reflection for ScriptObject proprties
2: //recursionDepth & maxRecursionDepth are two int variables to control recursion depth
3: //in case we will want to get complex properties of JavaScript object
4: public static Dictionary<string, object> DeepReflectProperties(ScriptObject obj)
5: { 6: Dictionary<string, object> retVal = new Dictionary<string, object>();
7:
8: string[] props = ReflectProperties<string[]>(obj);
9:
10: if (null != props)
11: { 12: foreach (string prop in props)
13: { 14: if (obj.GetProperty(prop) is ScriptObject)
15: { 16: if (recursionDepth < maxRecursionDepth)
17: { 18: recursionDepth++;
19: retVal.Add(prop, DeepReflectProperties(obj.GetProperty(prop) as ScriptObject));
20: recursionDepth--;
21: }
22: }
23: else
24: retVal.Add(prop, obj.GetProperty(prop));
25: }
26: }
27:
28: if (retVal.Count == 0)
29: retVal = null;
30:
31: return retVal;
32: }
All left to do is to execute those functions. To do it, I've built simple Silverlight Page, with TextBox for input HTML object element IDs, and button to start reflection of the HTML element properties. In my case I've bounded result of DeepReflectProperties function to some ListBox, and ReflectMethods to some TextBox value.
Here is result for "Button1" (see topmost HTML block):
"+"/"-" buttons controls the recursion level.
Execution of HTML Button with "b" JavaScript object (see topmost HTML block) gives following output:
We done - got properties and method of JavaScript objects in runtime. Now we could use this information to communicate with hosting HTML page more efficient.
Full sources here
Enjoy,
Alex