Everything we do here today is recorded in a 40 minute Webcast available at: mms://ttvv.tv/users/publicftp/justinangel/intellisense.wmv
Let's create a new Silverlight 1.0 project in Expression Blend 2 August preview, change the background color of our first page to "LightGreen" and draw a small rectangle. This is how our XAML file looks like right now:
<Canvas
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="640" Height="480"
Background="#FF90EE90" <-- LightGreen
x:Name="Page" <-- Name of Canvas
>
<Rectangle x:Name="myRectangle" Width="240" Height="144" Stroke="#FF000000"
Canvas.Left="200" Canvas.Top="144"> <-- Small Rectangle
</Rectangle>
</Canvas>
This basic XAML file has a Canvas
and a rectangle
on it. Let's change the rectangle
so it will look like this:
<Rectangle x:Name="myRectangle" Width="240" Height="144" Stroke="#FF000000"
Canvas.Left="200" Canvas.Top="144">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
<GradientStop Color="#FF1F4AB5" Offset="0.053"/>
<GradientStop Color="#FFD97F38" Offset="0.779"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
And a Storyboard
so the rectangle
changes background colors over a period of two seconds.
<Canvas.Resources>
<Storyboard x:Name="changeBackground">
<ColorAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="myRectangle"
Storyboard.TargetProperty=
"(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#FFD97F38"/>
<SplineColorKeyFrame KeyTime="00:00:02.1000000" Value="#FF6BD938"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="myRectangle"
Storyboard.TargetProperty=
"(Shape.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#FF1F4AB5"/>
<SplineColorKeyFrame KeyTime="00:00:02.1000000" Value="#FF5B0E54"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</Canvas.Resources>
Finally we want a JavaScript function called CanvasLoaded
to fire when the Canvas
is loaded.
<Canvas
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="640" Height="480"
Background="#FF90EE90"
x:Name="Page"
Loaded="CanvasLoaded"
>
<Canvas.Resources>
...
</Canvas.Resources>
<Rectangle x:Name="myRectangle" Width="240" Height="144" Stroke="#FF000000"
Canvas.Left="200" Canvas.Top="144">
...
</Rectangle>
</Canvas>
Let's see how this screen looks like in Expression Blend 2 August Preview:
Pretty straight forward. Now let's see our CanvasLoaded
function that's located in our Default_html.js file using Visual Studio 2008 Beta2.
Well, this is the canvas
loaded event so the sender must be of type Canvas
!
We are sure to get intellisense for Canvas
.
Wait. What??? No intellisense for Canvas
?
Well, I'd like to print out the background color of our Canvas
.
So what will we write?
Hmm.. well, I guess I can work without intellisense and write something up.
Ok, We need Silverlight JavaScript Intellisense!
- Go here and download the latest "JavaScript Silverlight Intellisense" release.
- Open the zip file. We get two files:
- Drag & Drop these two files into Visual Studio 2008 Beta 2 project.
- Add a script reference at the top of our JS file.
/// <reference path="intellisense.js" />
- Add a script src to intellisense.js in our HTML page.
<script type="text/javascript" src="intellisense.js"></script>
That's It!
We're done.
You now have JavaScript Intellisense for Silverlight 1.0.
The first thing we have to do is convert our sender
to a strongly-typed Canvas
object.
Now let's see what we got from this strange Convert.ToCanvas
function.
We get full intellisense for Silverlight objects in our JavaScript code!
We get properties, we get events, we get methods, we get collections, we get indexers, and they are all strongly typed!
Working with Properties
So let's print out the background of our Canvas
.
We even get SDK comments inside our JavaScript!
But what's this?... This background property gives us a Brush
? What's a brush
?
Wait, we used a SolidColorBrush
, so what's the relation between that and this Brush
thing?
Ok, so let's Convert
our Brush
to SolidColorBrush
.
And we got the new Color
property!
Let's print it out.
On our screen we get:
We got -7278960 which is an RGB value that stand for LightGreen.
So let's do something a little more tricky, let's change the background for our Canvas
.
We'll create a new SolidColorBrush
.
And our background color has changed!
Working with Methods
Do you remember we added an animation so our Storyboard
will change it's background colors?
Let's start that Storyboard
.
<Canvas.Resources>
<Storyboard x:Name="changeBackground">
...
</Storyboard>
</Canvas.Resources>
Let's use the findName
method on our canvas
.
Did you notice our FindName
method returns a DependencyObject
type class?
We need to convert our general DependencyObject
back to a Storyb
oard
.
Before
After
Working with Events & Global Typed Variables
Let's catch a left mouse button click and cause our canvas
to change it's opacity to 0.3.
function CanvasLoaded(sender, eventArgs)
{
var someElement = Convert.ToCanvas(sender);
...
someElement.add_MouseLeftButtonDown(OnMouseLeftButtonDown);
}
function OnMouseLeftButtonDown(sender, eventArgs)
{
}
We can see that our Canvas
element is out of scope in the new OnMouseLeftButtonDown
method, so we'll have to move someElement
to a higher scope.
var someElement;
function CanvasLoaded(sender, eventArgs)
{
someElement = Convert.ToCanvas(sender);
...
}
function OnMouseLeftButtonDown(sender, eventArgs)
{
}
Let's see how are we dealing intellisense wise.
We get intellisense for someElement
in CanvasLoaded
after we converted sender
to Canvas
, but not inside OnMouseLeftButtonDown
.
We need to define someElement
as a global variable of type Canvas
.
Not let's change the opacity to 0.3 like we wanted.
And the end result...
Before Clicking the Left Mouse Button
After
Working with Inline Functions (Anonymous Delegates) and Attached Properties
Let's make our rectangle
move around the Canvas
as we press the arrow keys on the keyboard.
This is very similar syntax to that used by C# 2.0 Anonymous delegates.
In order to get which key was pressed we need our eventArgs.Key
property, so let's convert our eventArgs
.
I happen to know (because Jon Galloway told us) that "15" stands for the Up Arrow keyboard button.
Well, now we need to move our Rectangle
up... So let's change it's Canvas.Top
attached property.
Please note we are using setValue
and getValue
of DependencyObject
and they aren't exclusive for Rectangle
.
Let's create our Canvas.Top
dependency property.
someElement.add_KeyUp(function(sender, eventArgs)
{
var e = Convert.ToKeyboardEventArgs(eventArgs);
if (e.get_key() == 15)
{
var rect = someElement.findName("myRectangle");
var top = Convert.ToDependencyProperty("Canvas.Top");
rect.setValue(top, rect.getValue(top) - 10);
}
});
Before We Click the Up Arrow Button
After
Polymorphism and the Is/As Pattern
If you've ever used C#, you'll be very familiar with the Is/As patterns for type conversion.
The Is Type Conversion Pattern
The As Type Conversion Pattern
What we're seeing here is basic Polymorphism. We're getting a type of Perctangle, Changing it's reference type back to it's parent class DependencyObject
and then back To Rectangle
.
We have similar patterns to the C# Is/As patterns in our JavaScript code.
The JavaScript Is Pattern
The Javascript As Pattern
The "Convert.IsXXX" method returns a value whatever the element is convertible to XXX.
The "Convert.ToXXX" method returns null
if the value is not convertible to XXX.
Working with Extension Methods
Let's say it's very common for us to change the Canvas.Top
attached property we've seen earlier on Rectangle
s.
So common in fact, it should be an extension method.
We'd like to add it to the intellisense for the Rectangle
Class without changing the original class code.
The C# 3.0 syntax looks like this:
We added a new method to our Rectangle
class without even changing it's code!
Let's do the same in JavaScript Silverlight objects.
We'll create a new file called Extensions.js (just because it's a good name):
We need to add a reference to our intellisense.js file.
I'll do that by selecting the intellisense.js file in the Solution explorer and drag & drop into our Extensions.js file.
Let's add our Extension
method to Rectangle
.
We need to make sure ChangeByHowMuch
is a known Number
type so we'll add a param
statement.
Now let's change the Canvas.Top
of our Rectangle
.
Now let's script tag reference this Extensions.js file to our HTML page.
And we need to reference our Extensions.js file wherever we need intellisense.
Please note that we no longer reference the intellisense.js file directly. Our extensions.js references it so we don't need to.
After running this code we can see the rectangle is at the bottom of our Canvas
:
Type Safety, Element Hidden Field & Javascript Debugging
Let's take a look at this method:
It's expecting a Number
of type Double
. So this argument will be acceptable:
canvas.set_opacity(0.5);
But what about...
canvas.set_opacity("really shiny opacity please!");
Let's first run this code directly to the Canvas
Silverlight CLR type.
We can access it by using the Element
hidden field on our Silverlight JavaScript classes.
Notice that we don't get any intellisense for our element types.
var canvas = Convert.ToCanvas(sender);
canvas.element.opacity = "really shiny opacity please!";
We get this somewhat cryptic error message straight for the Silverlight 1.0 CLR:
Let's try this with our Silverlight JavaScript Intellisense objects.
var canvas = Convert.ToCanvas(sender);
canvas.set_opacity("really shiny opacity please!");
And we get:
Let's open our Call-stack window for debugging JavaScript.
And in the call stack we can see:
We can see it's a TypeSafety
check that failed.
Let's double click on the second line:
And we can see in our editor:
Now, just for the heck of it, let's open our Quick Watch Windows (CRTL + ALT + Q since VS2003) and evaluate this
.
We can see all the properties, methods and events on our Silverlight JavaScript Intellisense objects.
Let's examine this.element
which is the normal Silverlight CLR 1.0 object.
Nothing, we get no JavaScript debugging watch for native Silverlight objects.
Now, let's try to add a string to our Canvas.Children
collection.
var canvas = Convert.ToCanvas(sender);
canvas.get_children().add("blue shiny rectangle");
And we get the following runtime error:
Let's try something more subtle. Remember this code?
We used polymorphism to send a SolidColorBrush
which inherits from Brush
as a Brush
parameter.
Let's convert the SolidColorBrush
back to a DependencyObject
and send it to the method:
This works fine and no errors are raised.
Let's try sending a Rectangle
which is referenced as a DependencyObject
.
We get the following error:
We need a Brush
.
We can get a SolidColorBrush
since it inherits from Brush
.
We can get a DependencyObject
of SolidColorBrush
since SolidColorBrush
still inherits from Brush
.
We cannot Convert
a DependencyObject
of Rectangle
to a Brush
.
Deployment
Using the custom intellisense JavaScript syntax for development causes a deployment issue.
Do we deploy intellisense.js to our client's browser? or do we strip intellisense features from our JavaScript code?
We can do whichever suits us.
Deployment with Intellisense.js File
Have a look at the file size of intellisense.js:
It's more than 1 MB! That's huge!
And most of it is essentially comments, spaces and variable names anyhow.
So with the download of the intellisense.js file you get a called intellisense.compressed.js which is a stripped down version of the intellisense.js file.
You can change the script tag HTML reference to this compressed JavaScript file and your clients will only download the compressed version while you still get full intellisense.
As part of the generation of the Intellisense.js file we use Atif Aziz's JavaScript compressor to create this compressed version.
You can see that it only compresses to about 40% of the full intellisense.js file.
There are literally hundreds of JavaScript compressors out there and some compressed our intellisense.js file to a mere 100 KB file.
If you want to find them just Google the phrase: "JavaScript compressor" OR "JavaScript shrink".
However, with the download I can only ship a compressed file from a .NET based software.
But feel free to use your own compressor on this file and deploy it as you please.
If you find a compressor with better results and it's written in .NET, I'll be glad to use it.
Deployment without Intellisense.js File
There are two downloads on the project's codeplex page here.
The first contains only the two JavaScript files mentioned previously.
The second download contains the code for the .NET software which generates the intellisense.js.
Don't worry you don't need it.
Inside that software, there's the following screen:
(What you're probably thinking right now is: "So, this is the guy that's telling me how to use Silverlight? That has to be the ugliest screen ever!")
Let's take the following JavaScript code we've written today:
var someElement = Convert.ToCanvas(null);
function CanvasLoaded(sender, eventArgs)
{
someElement = Convert.ToCanvas(sender);
var brush = Convert.ToSolidColorBrush(someElement.get_background());
alert(brush.get_color();
var newBrush = SolidColorBrush.createFromXaml(sender.getHost());
newBrush.set_color(Convert.ToColor("Aqua"));
var newBrushAsDependencYObject = Convert.ToDependencyObject(newBrush);
someElement.set_background(newBrushAsDependencYObject);
Convert.ToStoryboard(someElement.findName("changeBackground")).begin();
someElement.add_MouseLeftButtonDown(OnMouseLeftButtonDown);
someElement.add_KeyUp(function(sender, eventArgs)
{
var e = Convert.ToKeyboardEventArgs(eventArgs);
if (e.get_key() == 15)
{
var rect = someElement.findName("myRectangle");
var top = Convert.ToDependencyProperty("Canvas.Top");
rect.setValue(top, rect.getValue(top) - 10);
}
});
}
And copy it into the "JavaScript with intellisense" TextBox.
We get the same JavaScript code but without any of the intellisense features.
It uses the plain Silverlight CLR objects.
Here's the full translated code:
var someElement = null;
function CanvasLoaded(sender, eventArgs)
{
someElement = sender;
var brush = someElement.background;
alert(brush.color);
var newBrush = sender.getHost().content.createFromXaml("<SolidColorBrush />");
newBrush.color = "Aqua";
someElement.background = newBrush;
someElement.findName("changeBackground").begin();
someElement.addEventListener("MouseLeftButtonDown", OnMouseLeftButtonDown);
someElement.addEventListener("KeyUp", function(sender, eventArgs)
{
var e = eventArgs;
if (e.key == 15)
{
var rect = someElement.findName("myRectangle");
var top = "Canvas.Top";
rect.setValue(top, rect.getValue(top) - 10);
}
});
}
}
So we don't even need to include any JavaScript file in our deployment scenario.
This little tool is only a small example of removing intellisense code from JavaScript.
We can use the same mechanism for a Visual Studio 2008 Add-in that translates code back and forth between intellisense code and non-intellisensed code.
We can create a small .NET IHttpHandler
which will automatically serve to a browser request only the JavaScript files after they have been stripped of intellisense code.
So regarding deployment, it's up to you.
If you need any help from me, you've got it.
Questions, Follow-up and Suggestions
Bugs
You will find bugs with this intellisense.
Seriously, I'm not that smart that I can build a Type-system in 10 hours and it will work perfectly.
Please go to the project's Codeplex page here and Create a new Issue.
Write what error you received, add the appropriate minimal and relevant code.
If something isn't working as you expect it to, tell me what you expect and what actually happens.
Attach a print screen if possible.
Feature Requests
Additionally, you might want additional features like the HttpHandler
I talked about or more things to be strongly typed (DependencyProperties
or Keys
come to mind).
Same goes, open a new issue and I'll do my best.
Check for Updates
This project will surely undergo constant changes in the first 30-60 days after publishing.
If you're using this project, please signup to our RSS feed so you get notices.
RSS feed can be found here.
Here are my personal details just in case you feel the Codeplex page isn't sufficient:
Justin-Josef Angel,
Senior .NET consultant, Microsoft C# MVP
Email: J@JustinAngel.Net
Phone: +972 546 567789
I'm serious about this, don't hesitate to contact me.
Well, that's about it.
History
- December 20, 2007: Published on The Code Project
- August 01, 2007: Published on CodePlex