Introduction
Being a consultant, makes me very sensitive to the newest trends in technology.
Several years ago, I came across WPF and fell in love with it. WPF introduced brand new concepts that enable the
developers to achieve almost total separation
between the visual design of the UI application and the underlying non-visual implementation. At the same time WPF provides great features for visual development.
Silverlight brought the WPF power to multiple platforms.
Recent developments, however, indicate that Microsoft soured on both WPF and Silverlight in favor
of HTML5/JavaScript.
This is, probably, the correct strategy for Microsoft in the current tough situation. Over a period of
several years, Microsoft lost its status as the largest software company and lost its superiority in the internet browser market. The primary reason behind it is that Microsoft overlooked and came too late
to the smart phone and tablet markets. Being a follower is different from being the trend setter and currently
Microsoft has to beat everyone else in their own game, which is HTML5 and JavaScript (and based
on the previous history I am confident it is going to become a leader again).
These developments caused me to put a lot of effort into learning and working with HTML5 and JavaScript.
I am also trying to understand how to apply the concepts I learned while working on
WPF/Silverlight in JavaScript development.
The purpose of these series of articles is to make it easier for WPF/Silverlight developers
to master programming HTML5/JavaScript utilizing WPF and Silverlight concepts.
These articles assume that the reader already has some basic familiarity with HTML and JavaScript,
so people who are completely new to it, should start with something else.
I also highly recommend pluralsight.com as a resource for
HTML5/JavaScript learning.
Good and Bad about JavaScript and HTML
The following are the advantages of JavaScript and HTML:
- HTML/JavaScript is a must if you want your application to reach many different platforms,
including mobile platforms
(IPhone, Android and Windows 8), especially if you want to avoid the hustle of going through
putting your application on Apple or Window store.
- Contrary to my experience 10 years ago, one can easily write a JavaScript code that is truly
cross browser without much extra effort by utilizing JavaScript libraries that absorb the browser differences. The same
cannot be said, though about HTML5 and CSS3 features. HTML5 and CSS3 specifications have not
been finalized yet and
different browsers implement different subsets of it. One can visit
caniuse.com web site to see what features are implemented by which
browsers. According to this web site Google chrome is ahead of every other browser in terms
of HTML5 and CSS3 implementation.
- JavaScript is sufficiently powerful to implement WPF features resulting in separation between
visual and non-visual functionality, namely observable properties and bindings. Knockout.js library
is doing precisely that (and in some respect even more).
- JavaScript as a loosely typed language is more powerful than C# - there are no restrictions on what
object you can pass to which function. The flip side of it is, of course, that
things can go wrong much easier in JavaScript and will be more difficult to detect.
- You can use Ajax to send only data in JSON format between the server and the client instead of transferring
large parts of HTML code.
- You can prototype very fast with JavaScript.
- HTML5 provides powerful visual capabilities comparable to those of WPF and Silverlight.
- As browsers mature, the HTML5 features will become more and more hardware accelerated which will
greatly improve the speed of rendering visuals and animating them.
JavaScript script has all the disadvantages of a loosely typed language:
- The structure of its objects is not
defined by classes, in fact, its objects are built by the functions when the application is running
so that when you see e.g. an argument passed to a function you'll have a difficult task trying to
figure out what information it contains. This makes code analysis quite difficult and requires much more
documentation. Good documentation should also specify the implied structure of the objects
passed to functions.
- While in C#, Java and C++, the architects and senior developers can enforce certain
uniformity of the code by providing a bunch of interfaces and requiring the rest of the team to
work with them, no such ability exists in JavaScript. So when you have several people
with different level of experience on a team, you can only rely on code reviews, tight
code acceptance process, extensive documentation and tests to ensure the code quality.
- Every JavaScript object is just a Dictionary (or Map or Bag) of fields and functions. The functions
resolve the fields by names (not by offsets as in strongly typed languages). As a result, JavaScript
language is slower than strongly typed languages.
- Since the object type is difficult to infer, support for intellisense is not as good in JavaScript as it
is in strongly typed managed languages.
- The central object in JavaScript is a function, so JavaScript is actually related to functional languages just like F# and Lisp.
The generic modules like JQuery.js or Knockout.js are written as a number of different functions calling each other without many
intermediary results. This makes it difficult to analyze and debug the code, even
though JavaScript code can also be written in a more object-oriented style.
Creating a Simple HTML Project in MS Visual Studio
Let us start by creating the simplest possible JavaScript project in Microsoft Visual Studio.
While I am using VS 2010 SP1, one can create an
HTML/JavaScript project in VS 2012 in exactly the same fashion.
When you are trying to create an HTML or JavaScript project in Visual Studio, you will discover that Microsoft does
not provide any template for HTML or JavaScript. When creating a new project you should choose "ASP.NET Empty Web Application"
project template:
Name the project "HelloWorldJS" and click OK button to create the project.
Add an empty HTML file called HelloWorld.html to the project by right clicking on the project within
Solution Explorer and then choosing Add->New Item. Within the open dialog choose "HTML Page" for the item
type and name the file HelloWorld.html.
Within the HTML "body" tag, add the following code <h1>Hello World</h1>
. You can now run the project and it will display "Hello World" in large letters.
Important note: Before running the project, make sure that HelloWorld.html file has the key board focus within
Visual Studio before you press "Run" icon. You can run different HTML files by changing the keyboard focus between them.
Adding a Reference to JQuery
JQuery is a cross-browser JavaScript library for parsing HTML DOM. It greatly simplifies the development effort.
You can download the latest version of JQuery script from jquery.com and add
it manually to the project, or, even simpler, you can download a reference to JQuery as a NuGet package.
Before you can use Visual Studio to install NuGet packages, you need to install NuGet Package Manager VS extension. You do it once for your Visual Studio and it will enable you to install NuGet packages for any project you need.
To install NuGet Package Manager, go to "Tools" menu within your Visual Studio
and choose "Extension Manager" menu item. Choose "Online Gallery" option on the left of the window. Type "nuget package manager" in the search area located at the top right corner of the Extension Manager window and press the "Download" button next to "NuGet Package Manager" item to download and install it:
After the installation restart your Visual Studio so that you could start using NuGet Package Manager.
To install JQuery for your project, right mouse click on the "References" within the Solution Explorer and choose "Manage NuGet Packages" option (which should be there after the installation of NuGet Package Manager). Within the search area on the right
hand side at the top of the dialog, type "jquery". Click install button within JQuery entry:
After the installation, you can see that "Scripts" folder was created under the project and 3 files were added to it:
jquery-1.8.1.min.js (optimized for transfer over the internet), jquery-1.8.1.js - for debugging JQuery on your machine if
needed and jquery-1.8.1-vsdoc.js for intellisense.
You can now, mouse drag jquery-1.8.1.min.js to any place within HelloWorld.html file, e.g. to its end. It results in
the following line added to your file:
<script src="Scripts/jquery-1.8.1.min.js" type="text/javascript"></script>
JavaScript code can be placed in the HTML file within <script type="text/javascript">
HTML tag or
it can be placed in a separate file with extension .js. For this simple example, we'll put the JavaScript code in the HTML file.
Since we want to use JQuery in our JavaScript code, we need to place this code after the JQuery reference.
Our JavaScript code will modify the displayed string to "Hello Wonderful World".
Since JQuery code interacts with the DOM, we need to make sure that the code is called only after the DOM is created.
To do this, we place the whole code within the $(document).ready
function.
$(document).ready(
function () {
}
);
Since we want to refer to some specific HTML DOM node, we want to identify that node in a way. The best way to do it
is by giving it an id: so let us change our HTML line to <h1 id="TheText">Hello World</h1>
.
JQuery can find the DOM element by id by using '#' selector: $("#TheText");
. Finally we can use text
function to replace the text within the tag. Here is how the code looks:
<script type="text/javascript">
$(document).ready(
function () {
$("#TheText").
text("Hello Wonderful World");
}
);
</script>
Running the application now will result in "Hello Wonderful World" text being displayed.
In the sample above, we have shown how to use JQuery and write a basic JavaScript. Note that just like many
other JQuery functions, function text
returns the current text within a tag if no argument
is passed to it and modifies the text within a tag when an argument is passed to it.
The code for this sample is contained within HelloWorldJS project.
Note on Debugging JavaScript within Visual Studio 2010 or 2012
In my experience Visual Studio 2010 and 2012 debugging works only if you are running
your application in Internet Explorer. If your application pops up in a different browser,
the VS debugger won't work. The easiest way to convince your Visual Studio to start your HTML/JavaScript application in
Internet Explorer is to set Internet Explorer as your default browser. For more complex ways to achieve the same,
without changing your PC's default browser,
see
How to change the default browser in Visual Studio programmatically with PowerShell and possibly poke yourself in the eye by Scott Hansleman.
The JavaScript debugger works very much the same as a C# debugger:
you can add a breakpoint; once the program stops on breakpoint, you can check the variables, the call stack etc.
Samples Highlighting JavaScript Features
While this article is not intended to be a JavaScript reference, I would like to present several samples highlighting
different JavaScript features.
To display the results of these samples, you can simply put them within
$(document).ready()
function
of the HelloWorldJS project discussed above and plug the result into
text()
function instead of "Hello Wonderful World" string.
Local Variables and Scope
A JavaScript variable can be declared within a function or outside of it in the global scope.
The most common way to declare a variable is to use the
keyword
var
, e.g.
var i = 10;
. Note that curly bracket do not change the variable scope in JavaScript:
var i = 52;
and
{
var i = 52;
}
are two exactly equivalent ways to define variable i and set it to 52. The following JavaScript will work and will still
display 52 on the screen:
$(document).ready(
function () {
{
var i = 52;
}
$("#TheText").text(i);
}
);
Functions and Arguments
Functions can be named or anonymous. A Named function can be called by name. Anonymous functions can be called
right after their construction, or by using a reference to them just like anonymous functions and
lambda expressions in C#.
Functions can return one value by using
return
keyword, just like the functions in C#, Java and C++.
Here is an example of defining and calling a named function called "MyFunction":
function MyFunction()
{
return 20;
}
var i = MyFunction();
Note that even if the function does not return any values, one can still write
var i = MyNonReturningFunction()
.
In that case, variable
i
will be set to "undefined".
Here is an example of creating an anonymous function and calling it at once:
function() {
var i = 20;
}();
Parenthesis after the function definition will ensure that it is called right away.
Here is an example of storing a reference to an anonymous function at the time of its creation and calling it later:
var f = function() {
return 25;
};
...
var i = f();
Functions can have arguments defined, e.g. function MyFunction(a, b, c)
,
but the number of arguments passed does not have to match the number of arguments within the function
definition, e.g. MyFunction
defined above can be called without arguments:
MyFunction()
. In that case, the arguments that are not passed will be
assumed in "undefined" state within the function. One can also pass more arguments to the
function than those defined within the function definition. In that case only the first arguments are matched
with the defined arguments within a function. Another way to access all the arguments within a function
is by using the arguments
variable e.g.
function MyFunction()
{
var result = "";
for(var i = 0; i < arguments.length; i++)
{
result += arguments[i];
}
return result;
}
var concatenatedString = MyFunction("Hi ", "World");
The function above will concatenate all the arguments string passed to it, so that the result will be "Hi World".
Since the argument list is flexible, there is no function
overloading in JavaScript, and the function should have a unique name within its scope, otherwise the
later definition of a function will override the previous one of the same name.
Child Functions, Function Scope, Closures, Global Variables
As we saw above, we can define a function within a different function. e.g.
function MyFunction()
{
var MyChildFunction()
{
...
};
...
MyChildFunction();
}
A function defined within a function is called a child function.
The functions define a scope, in a sense that if you define a variable using var
within a function,
it will only be visible within the function itself or within descendants of this function and won't be visible outside of it:
function() {
var i = 20;
var f = function() {
}
}
Just like lambdas in C#, the functions have closures, i.e. the function records reference to variables
from the higher scope, not the values:
function () {
var i = 10;
var square = function () {
i = i * i;
}
i = 40;
var result = square();
}
A function can define a global variable (analogous to a static variable in C# sense).
In order to create such a variable one should assign the variable a name without a var
keyword in front of it.
In that case,
it is assigned to the global windows
object representing the browser window and is accessible from any other function:
function () {
globalVariable = 10;
}
Objects and Arrays
There are 3 types of variables in JavaScript:
- Built in value types - numbers, strings, booleans etc.
- Objects - reference types that have a bunch of fields
- Arrays - collections of objects or built in types accessible by index.
Let us further discuss the Object type. Essentially, it is a Dictionary/Map/Bag of name-value pairs
accessible by name. Unlike in C# or other strongly typed languages,
there are no classes that predefine what an object contains; instead
the fields within an object are added dynamically during the program run. One can add a field
to an object simply by assigning to it:
var myObject = new Object();
myObject.someField = 5;
The fields within an object can also be set or accessed by name: myObject.someField = 5;
is
equivalent to myObject["someField"] = 5;
. Also, one can write
var myField = myObject["someField"];
to retrieve a field from an object.
The object's fields can be built-in types, other objects, arrays or references to functions.
Now, let us briefly discuss the Arrays. Below is an example of Array creation and manipulation:
var myArray = new Array();
myArray[0] = 1;
myArray[10] = "hello world";
myArray.push(2);
var myVar = myArray.pop();
JSON Object Notations
JSON is a compact way of storing and transferring data. It is considerably more compact than XML.
JavaScript objects and
arrays can be created via JSON notations. Here is an example:
var student =
{
firstName:"Joe",
lastName:"Doe",
age: 20,
friends : [
{firstName:"John", lastName:"Smith"},
{firstName:"Dan", lastName:"Brown"}
]
};
The code above creates an object
student
with firstName field set to "Joe", lastName "Doe", age 20 and
an array of friends.
Curly brackets in JSON delineate object boundaries while square brackets
- array boundaries. Multiple fields within an object or cells within an array are separated by commas. Colon is used to separate the name from the value for the
Object
's name value pairs.
JSON notation also provide a more concise way of initializing empty objects and arrays:
var myObj = {};
var myArray = [];
Mapping Object Oriented Concepts into JavaScript
Here we show how to translate OO concepts such as classes, interfaces, virtual functions and inheritance into JavaScript language.
Classes and Constructors
Classes in C# and other object-oriented languages specify objects of the same "shape", e.i.
having similar members but possibly different data. In JavaScript we can write a function
for creating an object, populating it with some values,
and then returning
it to the caller. This function will play a role of a C# constructor by churning out objects of
similar structure. Here is an example:
function createStudent(firstName, lastName)
{
var student = new Object();
student.firstName = firstName;
student.lastName = lastName;
student.getFullName = function()
{
return firstName + " " + lastName;
}
return student;
}
Now we can call this function to create students:
var student1 = createStudent("Joe", "Doe");
var student1FullName = student1.getFullName();
var student2 = createStudent("John", "Smith");
var student2FullName = student2.getFullName();
The objects student1
and student2
will have the same structure but different data,
as if they are created by new'ing the same class in C#.
There is even a better way to create objects of the same type, which we describe below. Every function
has a "this" object that specifies the function's context. One can assign to it any fields and any values.
When a function is called prepended by the operator new
: new <FunctionName>(...)
the "this" object is 'newed' in the beginning of the function and returned by the function to the caller. Such functions are called
JavaScript constructors, similar to constructors in C++, Java and C#. So let us rewrite the function above
using "this" object (note that I changed the function name to Student
):
function Student(firstName, lastName, age) {
this.firstName = firstName;
this.lastName = lastName;
this.getFullName = function () {
return this.firstName + " " + this.lastName;
}
}
Here is how we can call such a function with operator
new
:
var student1 = new Student("Joe", "Doe");
var student1FullName = student1.getFullName();
Note that now we can check if the student
object was created by Student
constructor by using
instanceof
operator:
var student1 = new Student("Joe", "Doe");
var isStudent = student1 instanceof Student;
The constructor functions can be applied to already existing objects by using the <FunctionName>.call()
method, but then instanceof
operator will now show that the object came from the constructor:
var student1 = new Object();
Student.call(student1, "Joe", "Doe");
var student1FullName = student1.getFullName();
var isStudent = student1 instanceof Student;
More about JavaScript Constructors and "this" Variable
As we mentioned above, every function contains
this
variable. It provides a context in which the function is called. The function called outside
of an object would have
this
variable set to the browser
window
object:
AFunction();
If a function is called within object's context this
variable within the function
will be set to the object in which it is called:
var anObj = {
aVar: 1,
aFunction: function () {
var canAccessObjectContext = this.aVar === 1;
}
};
anObj.aFunction();
Unlike in C#, Java and C++, this
variable within a JavaScript function can be changed by one of the following means:
- The function can be called using
call()
or apply
methods. In that case, this
variable within the function is set to the first argument passed:
var anObj = {
aVar: 1,
aFunction: function () {
var canAccessObjectContext = this.aVar === 1;
}
};
var anotherObj = {};
anObj.aFunction.call(anotherObj, arg1, arg2);
- One can clone the function and provide a different context to it using
bind()
method:
var anObj = {
aVar: 1,
aFunction: function () {
var canAccessObjectContext = this.aVar === 1;
}
};
var anotherObj = {};
var clonedFunction = anObj.aFunction.bind(anotherObj);
clonedFunction();
Unfortunately IE8 does not support bind()
.
- Operator
new
used to call the constructors, changes the context of the function to a new empty object.
A reasonable question arises of what is going to happen if someone forgets to put
new
in front of a constructor. The answer is that some context represented by this
can become corrupted because of the assignments within the constructor.
This case (of a developer forgetting to put new
in front of a constructor) is considered in detail in JavaScript: Using a constructor without new. It turns out that the condition can be detected by adding the following check in the beginning of the constructor:
function Student(firstName, lastName, age) {
if (!(this instanceof Student))
{
alert("Programming Error: You forgot to put 'new' in front of the constructor");
return;
}
this.firstName = firstName;
this.lastName = lastName;
this.getFullName = function () {
return this.firstName + " " + this.lastName;
}
}
I prefer to alert the software developers or QA if something is wrong in the
program. The solution in JavaScript: Using a constructor without new, however, shows how to create a correct object even if the constructor is called without new
.
Prototype Chain of Responsibility and Inheritance
Every object in JavaScript has a hidden field called "prototype". When you try to access a field or a function and it cannot be found on
the main object, it checks its prototype field for that field or function. If it cannot find a field or method on the prototype,
it will check the prototype of the prototype and so on, until it hits an object without any prototype. This pattern is called
"chain of responsibility".
Prototype field can be used in order to have only one instance of some functions or fields for multiple objects (these objects will have
to share the prototype). Prototype can also be used to implement functionality similar to inheritance.
The field "prototype" is hidden from the developers and can only be set via JavaScript constructors. Here is an example of
how to move the getFullName() function to the prototype and make is shared among all the object created by Student
constructor
function Student(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
Student.prototype.getFullName = function() {
return this.firstName + " " + this.lastName;
}
var student1 = new Student("Joe", "Doe");
var student1FullName = student1.getFullName();
Instead of defining some function on the prototype,
we can set the prototype to be a whole new object obtained with
new
operator. This will imitate inheritance:
function Student(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.getFullName = function () {
return this.firstName + " " + this.lastName;
}
}
function HonorStudent(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}
HonorStudent.prototype = new Student();
var student1 = new HonorStudent("Joe", "Doe");
var student1FullName = student1.getFullName();
BTW, the fact that the full name of the student is returned as "Joe Doe",
also means that the JavaScript functions are "virtual" in a sense that
they work with the "derived class" object's fields and not with the "base
class" ones. Indeed the prototype object's firstName
and lastName
fields are not defined
(since the prototype object is shared between different objects, there is no
way to define them to satisfy all possible combinations). However, the function
getFullName
defined in the "base class" is returning a correct result working with the objects of the "derived class".
To completely convince you that the functions are "virtual" in the C# sense,
let us introduce two more functions in the "base class", one calling another,
override the called function in the "derived class" and make sure that
the caller called on the "derived class" returns the correct value:
function Student(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.getFullName = function () {
return this.firstName + " " + this.lastName;
}
this.getLongDescription = function() {
return "this is " + this.ShortDescription();
}
this.getShortDescription = function() {
return "a Student";
}
}
function HonorStudent(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
this.ShortDescription = function() {
return "an Honor Student";
}
}
HonorStudent.prototype = new Student();
var student = new HonorStudent("Joe", "Doe");
var studentLongDescription = student.getLongDescription();
The instanceof
operator propagates up the prototype property, i.e. if the current object has
not been created by a constructor specified on the right of instanceof
operator, it will check its
prototype and the prototype of the prototype and so on, untill it finds a prototype created by the constructor,
or comes to the end of the chain. So in the above example we have both student instanceof HonorStudent
and student instanceof Student
returning true
.
Calling call()
function on the constructor does not set the prototype and does not
make the object to be an instanceof
the constructor, i.e. if in the code above we called
var student = new Object();
HonorStudent.call(student, "Joe", "Doe");
The student would have been set to have first an last name but would not be
instanceof
HonorStudent
or
Student
constructors and also would not have functions
getFullName
or
getLongDescription
that are coming from the prototype.
Extending an Array to Have Methods Similar to C#
Prototype can also be used to extend objects without modifying their constructors. All you need to do is to add
the functions you want to the prototype of the constructor of the objects and these functions will appear within those objects.
Built in types also can be extended this way. For example, I miss collection related methods, like
clear()
,
remove()
etc. that come with C# or Java collections, so I created a JavaScript file ArrayExtensions.js and added those
functions to the Array functionality:
Array.prototype.remove = function (arrayElement) {
var currentIndex = 0;
do {
if (this[currentIndex] === arrayElement) {
this.splice(currentIndex, 1);
}
else {
currentIndex++;
}
} while (currentIndex < this.length);
};
Array.prototype.insert = function (idxToInsertAfter, arrayElement) {
this.splice(idxToInsertAfter, 0, arrayElement);
};
Array.prototype.firstIndexOf = function (arrayElement) {
var currentIndex = 0;
do {
if (this[currentIndex] === arrayElement) {
return currentIndex;
}
currentIndex++;
} while (currentIndex < this.length);
}
Array.prototype.lastIndexOf = function (arrayElement) {
var currentIndex = this.length - 1;
do {
if (this[currentIndex] === arrayElement) {
return currentIndex;
}
currentIndex--;
} while (currentIndex >= 0);
}
Array.prototype.clear = function () {
this.length = 0;
}
Array.prototype.copy = function (beginIdx, numberElements) {
if (!beginIdx) {
beginIdx = 0;
}
var endIdx;
if (!numberElements) {
endIdx = this.length;
}
else
{
endIdx = beginIdx + numberElements;
if (endIdx > this.length)
{
endIdx = this.length;
}
}
var copiedArray = new Array();
for(var i = beginIdx; i < endIdx; i++)
{
copiedArray.push(this[i]);
}
return copiedArray;
};
ArrayExtensions.js file can be found under the Scripts folder within SimpleEventsJS project.
Interfaces
There are no interfaces in JavaScript and they are not needed. Think about it, the interfaces do not provide
new functionality; they restrict the functionality by forcing the developers to program to them and by adding compile time
compatibility checking. In JavaScript one can pass any arguments to any functions - there are no compile time compatibility
tests, so the interfaces are not needed.
Mapping C# LINQ Functionality into JavaScript
There is a JavaScript library Underscore.js that provides functionality similar to LINQ. You can download the
library from
Underscore.js or install it using NuGet.
The web site also contains excellent documentation.
The Underscore functionality sample is located under UnderscoreTests project. You have to run it in the debugger
in order to check the states of the variables, since the project does not change any visual information.
Here is the JavaScript code:
function Student(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.getFullName = function () {
return firstName + lastName;
};
}
$(document).ready(function () {
var collectionOfStudents =
[
new Student("Joe", "Doe"),
new Student("Jim", "Smith"),
new Student("Jane", "Smith"),
];
var studentsWithLastNameSmith =
_(collectionOfStudents).filter(function (student) { return student.lastName === "Smith"; });
var fullNamesOfStudentsWithLastNameSmith = _.chain(collectionOfStudents)
.filter(function (student) { return student.lastName === "Smith"; })
.map(function (student) { return student.getFullName(); })
.value();
});
You can see that the filter(...)
function is very similar to the Where(...)
function in
C#, while the map(...)
function is the same as Select(...)
.
The chain(...)
function allows to chain the Underscore.js operators one after the other
the way it is usually done in LINQ.
For more details about Underscore.js functionality, please look at the online documentation.
Mapping C# and WPF Event Functionality into JavaScript
There are two types of events in WPF/Silverlight/C# - simple events and routed events.
Routed events can propagate up and down the visual tree and can be mimicked by
document events in JavaScript. There is no built-in JavaScript feature corresponding to simple C#
Events, but such a feature can be added very easily. We name such events - Simple Events in order to
distinguish them from the document events built into JavaScript.
Simple Events
The SimpleEventsJS project contains a Simple Event sample.
The script SimpleEvent.js adds Simple Event functionality to JavaScript. It is located under Scripts
folder within SimpleEventsJS project. Here is how HelloWorld.html uses the functionality
from that script:
$(document).ready(
function () {
var mySimpleEvent = new SimpleEvent();
mySimpleEvent.addSimpleEventHandler(function () {
$("#TheText").text("Yes, the first event handler is fired indeed!");
});
mySimpleEvent.addSimpleEventHandler(function () {
$("body").
append("<div>Yes, the second event handler also fired!</div>");
});
$("#TheText").
text("Hello Wonderful World");
mySimpleEvent.fire();
}
);
Note that if you comment out mySimpleEvent.fire()
line, you will get the same message
"Hello Wonderful World" displayed as in the previous HelloWorldJS sample. If, however, you
let the mySimpleEvent
fire, the messages will be replaced by the event handlers,
and this is what you are going to see:
Here is the code for the SimpleEvent
functionality:
function SimpleEvent() {
this.eventHandlers = new Array();
};
SimpleEvent.prototype = {
addSimpleEventHandler: function (eventHandler) {
this.eventHandlers.push(eventHandler);
},
removeSimpleEventHandler: function (eventHandler) {
this.eventhandlers.remove(eventHandler);
},
clearSimpleEventHandlers: function () {
this.eventHandlers.clear();
},
setSimpleEventHandler: function (eventHandler) {
this.clearEventHandlers();
this.eventHandlers.addEventHandler(eventHandler);
},
fire: function (context, anyOtherArguments) {
var result;
var context;
if (arguments.length > 0) {
context = arguments[0];
}
var argsToPassToEvents = Array.prototype.copy.call(arguments, 1);
for (var i = 0; i < this.eventHandlers.length; i++) {
result = this.eventHandlers[i].apply(context, argsToPassToEvents);
}
return result;
}
};
Of course, unlike in C#, there is no way to ensure that one of the developers does not access eventHandlers
array and erases all of the event handlers from it.
DOM Events
DOM events just like WPF's routed events can propagate up the DOM tree (bubble).
There is no concept of tunneling events in JavaScript, but the tunneling-like functionality
can be added if required.
Built-In Event Sample
A simple DOM event sample is located under the BuiltInDomEvents project.
Here is the relevant HTML part of the code
<body>
<h1 id="TheText">Hello World/<h1>
<!--
<div id="clickmeDiv"
style="width:200px; height:200px; background:red; float:left; font-size:60px">
Click Me
</div>
</body>
As you can see, we've added a <div> element called "clickmeDiv" 200x200 px and colored it red.
Here is the JavaScript code that uses a JQuery's bind()
function to attach an action to the
built-in JavaScript click
event at the level of the <div> tag itself and also
at the higher level of the <body> tag:
$(document).ready(
function () {
$("body").bind("click", function () {
alert("Clicked within body");
});
$("#clickmeDiv").bind("click", function (event) {
alert("Clicked within 'clickmeDiv'");
});
}
);
Why use JQuery's bind()
function to set handlers to the events, and not the native browser JavaScript functionality? Because the native browser JavaScript functionality for handling the events is slightly different on different browsers while JQuery conveniently provides a common multi-platform way of doing it.
Now, if you run the sample, and click on the red square, you will see
two alerts popping up - one at the <div> level and the other
one at the <body> level and that means the event bubbles up the DOM.
If the <div> level event returns false
, however,
the event will be prevented from further bubbling and you will only see one alert.
JQuery's bind()
functionality can also disable default action on a DOM element as the event fires.
Default action is an HTML action performed when someone e.g. clicks on an element. For example, for a
hyperlink that would be changing the HTML page to the link's target. An event handler can prevent it
by returning false
or by calling the event.preventDefault()
function.
Custom DOM Events
Newer browsers (but unfortunately not IE 8) also support creating custom DOM events using
document.createEvent(...)
function.
WPF and Silverlight software developer can consider custom DOM events to be analogous to
custom routed events.
A sample demonstrating
custom DOM events is located under the CustomDOMEvents project. Its HTML part is almost the same as the one of the
previous sample. Here is the JavaScript code for the sample:
$(document).ready(
function () {
if (!document.createEvent) {
alert("this browser does not support custom event functionality. Please use a different browser");
return;
}
var myCustomEvent = document.createEvent("Event");
myCustomEvent.initEvent("MyCustomEvent", true, true);
$("body").bind("MyCustomEvent", function () {
alert("Custom event is handled at the body level");
});
$("#TheDiv").bind("MyCustomEvent", function () {
alert("Custom event is handled at the TheDiv level'");
});
$("#TheDiv")[0].dispatchEvent(myCustomEvent);
}
);
Running it with a browser that supports custom events, results in two
alert messages stating that the event is handled at the corresponding DOM level.
JQuery
I'd like to end this part of the guide with a brief overview of basic JQuery. JQuery is a
cross-browser library for parsing the DOM and manipulating the DOM elements. For WPF and Silverlight
developers, there is some similarity between JQuery and
LINQ to Visual Tree library
by Colin Eberhardt.
For a detailed documentation on JQuery
please look at JQuery.com and api.jquery.com.
There are also a number of JQuery courses on
pluralsight.com, which I highly recommend.
JQuery Selectors and DOM Manipulations
JQuery has a number of string patterns allowing to select some elements from within the DOM.
These string patterns are called Selectors.
The function for selecting a bunch of DOM elements looks like this: $(selector)
,
where "selector" is some string. This function always returns an array of DOM elements,
even if you know for sure that only one element is returned, so in order to access an individual
DOM element within the array you have to use an indexer, e.g. $("#theDiv")[0]
.
All of the selectors are listed at
JQuery Selectors link. Here we consider
only the most important ones.
JQuery sample is located under JQueryTests project. Here is the how DOM of the application looks:
<body>
<h1 id="header"></h1>
<div class="MyClass" style="width:700px; height:100px;font-size:40px;background-color:Yellow">
</div>
<div class="MyClass" style="width:700px; height:100px;font-size:40px;background-color:Aqua">
</div>
<div>
<button style="width:250px;height:50px;"></button>
</div>
<div data-test="Test" style="width:800px; height:100px;font-size:30px;background-color:Gray">
</div>
<div id="ReplaceChildTest" style="width:200px; height:200px;font-size:30px;background-color:Green">
<div id="ChildDiv" style="width:100px; height:100px;font-size:30px;background-color:Blue">
</div>
</div>
</body>
Here is the JavaScript:
$(document).ready(function () {
$("#header").text("Test ID Selector $(\"#header\")");
$(".MyClass").text(function (index, txt) {
return "Test class selector $(\".MyClass\") " + index;
});
$("button").text("Test tag selector $(\"button\") ");
$("div[data-test='Test']").text("Test attribute selector $(\"div[data-test='Test']\")");
$("#ChildDiv").remove();
$("#ReplaceChildTest").append( "<div style='width:100px; height:100px;font-size:30px;background-color:Red'>");
});
As you can see in the JavaScript comments, we give usages examples of the following selectors:
- $("#header") - selects all DOM elements whose id equals to "header" (there can only be one such element)
- $(".MyClass") - selects all DOM elements whose class is "MyClass"
- $("button") - selects all DOM elements whose tag is "button"
- $("div[data-test='Test'") - selects all DOM elements whose "data-test" attribute is set to "Test"
We also see that using the text(...)
function you can change text under every element returned by JQuery.
Additionally you can use a different version of the text(...)
function to utilize the indices of
DOM elements within the array passed to this function by JQuery:
$(".MyClass").text(function (index, txt) {
return "Test class selector $(\".MyClass\") " + index;
});
Using these indices, we can e.g. change the text to end with the index,
(as we did in the sample) or we can have some index dependent processing e.g.
styling the entry differently depending on whether the index is odd or even
(though JQuery provides a better functionality to achieve that).
Lastly, we also showed how to remove or add an element to the DOM:
$("#ChildDiv").remove();
$("#ReplaceChildTest").append( "<div style='width:100px; height:100px;font-size:30px;background-color:Red'>");
Summary
In this article, I discussed JavaScript and some of the design patterns that people with a WPF/Silverlight/C# background
would find familiar. Here are some of the most important points:
- Prototyping is related to inheritance
- Simple C# events can be easily added to JavaScript functionality as was shown above
- Underscore.js library provides LINQ functionality.
- DOM maps to Visual tree
- JQuery loosely maps into LINQ to Visual Tree functionality
- DOM events map to routed events
In this part, I was concentrating primarily on the non-visual aspects of JavaScript.
In subsequent parts, I plan to further discuss the visuals - SVG, JQueryui library,
using MVVM with knockout.js library, HTML5 Canvas, building lookless custom controls etc.
History
Sept 11, 2012 - based on Colin Eberhardt's comment added information on the preferred way to initialize objects and arrays. Also added more information about
this
variable and on what happens if a there is no
new
operator in front of a call to a constructor.