When an Atlas enabled page is loaded, several things need to be set up. In a nutshell, it goes like this:
- Based on the browser, load the first browser compatibility layer
- Load the Atlas core
- Based on the browser, load the second browser compatibility layer
- Process the Atlas markup
- Initialize all components
Step 1: Load the first browser compatibility layer
One of the key features of Atlas is to provide a unified library where you no longer have to worry about the capabilities of the browser when it comes to core features, such as attaching event handlers. Having such a unification allows for more compact and more readable code. Therefore, it makes sense to have this unification in place as soon as possible when the page is loaded, so that you (and Atlas) can rely on this practically everywhere. For this specific reason, the first compatibility layer is the first thing that is loaded. It is only loaded when the browser does not support certain "API's", such as HTMLElement.attachEvent, that is supported by IE and is commonly used. Since IE is still the most used (and targetted) browser, it probably makes sense to optimize for IE and provide compatibility layers for other, less used browsers instead. As a result, you don't see compatibility layers for IE in the current release (let's forget about the _loadIECompatLayer in AtlasCore.js, which really only determines which XMLHTTP object it should use).
The server-side ScriptManager control takes care of including the browser compatibility layers, by looking at what browser the client uses. If you don't use the server controls, you could use the Script server-control, which has a Browser property that you can set to conditionally include a script.
Step 2: Load the Atlas core
Another key feature of Atlas is that it brings certain object-oriented programming concepts to the client, such as classes, interfaces, delegates and events. If you think this allows for certain techniques to work in the same way as you may be used to in other languages (such as C#), you thought right. Right after the first level of unification is in place, the AtlasCore is loaded by the browser. Inside this script, Atlas will extend certain JavaScript objects, such as Function and String, to practically create a type system, and to add a level of convenience (for example strings get a format function which is similar to .NET's String.Format). The emulated type system is mostly built on top of JavaScript objects Function and Object, and includes functions for registering namespaces/classes and testing whether a type implements a certain interface.
Once the type system and other extensions are in place, the rest of the core library is loaded. This includes registering several classes, such as Web.Component and Web.Net.WebRequest (the core for all 'AJAX' related things in Atlas). It also includes instantiating several (singleton) objects that will be available until the page is unloaded, such as a debugger (with basic debugging/tracing capabilities) and an application which parses the markup once the page is completely loaded (i.e. window's onload event is raised).
Lastly, there is a TypeDescriptor that is used to keep track of all types that can be used declaratively.
Step 3: Based on the browser, load the second browser compatibility layer
In the current release of Atlas, there is another browser compatiblity layer, which is really only used for Safari at this point. This layer takes some 'side effects'/'features' of Safari into account. For example, Safari converts all tags to uppercase when a string is parsed into a DOM. Atlas (and JavaScript) is case sensitive, which means that Atlas markup would not work in Safari, unless a workaround would be implemented. In the second browser compatibility layer, the registration functions for registering declaratively usable types and properties are extended. Specifically, Atlas will start registering stuff twice, in both original casing and the casing that Safari converts to, to allow for Safari's intelligence.
After the extensions have been made, all types that were already registered will be registered again, to ensure that the new registration process is applied to previously registered types as well.
Step 4: Process the Atlas markup
After the core is loaded, and most browser features have been unified, the browser is practically done loading the page. It therefore will raise window's onload event. As I mentioned before, the application handles this event by parsing the Atlas markup.
It does so by finding the first Atlas script tag. When this script tag is found, all the declaratively defined script references are resolved. After all references have been resolved, the markup of the objects will be parsed.
The application will go through the children of the top level components element, and do a lookup in a table with mappings between tag names and type names. This table was populated with all types that called Web.TypeDescriptor.addType to allow themselves to be used in markup. For each type, Atlas will look if the type has a (static) 'parseFromMarkup' implementation. If it does not, it recursively does the same for the base type. Once it found a parse function, it will call it and provide the current xml element and the markup context. From there on, the function is responsible for processing the entire markup in this element.
The markup context stores all object references and is used to find objects within a specific scope. This is also the basis for resolving references between objects. At this moment, there is a top-level markup context (with all global objects), and a markup context for each template instance (for example in a ListView scenario). Because sometimes you want to be able to reference global objects from within a template, markup contexts can be parented. When an object inside an item template references a global object, Atlas will resolve this reference by first trying to find this object in the item template's markup context, and if it's not there, recursively try to find it in the parent markup context.
Generally, an implementation of 'parseFromMarkup' starts by instantiating the type based on the tag name of the xml node. When a type implements Web.ISupportBatchedUpdates, it will call its 'beginUpdate' function. This gives the instance a way of knowing if it is being initialized, so it can potentially delay certain logic in for example a property's setter. Right after that, all properties/events will be set. The attributes that were declared, will be parsed as property values or event handlers. Properties that can reference other objects in markup will not be set directly. Instead, the instance will be registered with the property in the markup context, and the reference will be resolved once all markup has been processed.
Properties that are declared as elements, will be parsed using logic similar to the way the application started parsing markup. Each property will be set to the instance (or instances if the property is an array) that are a result of parsing the child nodes of the property element.
Events will be parsed in a similar way, except that child nodes should result in action instances, that will be added as actions to the event.
After the instance has been (recursively) processed, the instance will be registered in the markup context as an object where the Web.ISupportBatchedUpdates's 'endUpdate' function still needs to be called. Most objects implement this function by actually initializing themselves. Therefore, the 'endUpdate' function of each object will be called after all markup has been parsed, and after all object references have been resolved.
Templates are not part of the initial parsing process. Controls such as ListView can use a template instance to create new instances of the types defined in the markup of the template. Ofcourse, the same parsing logic (and rules) applies when templates are parsed.
While this covers most of the markup parsing logic, there is one more thing I should mention. In some cases, there are non-user defined references between objects. For example, a behavior has a reference to its control, which it needs when it is initialized. To ensure that an object is guaranteed to have a reference to a different object, Atlas provides a component collection object. An instance of a component collection can be created by calling Web.Component's static 'createCollection' function. As an argument, you can pass an owner object. Web.UI.Control for example passes a reference to itself when it creates the behavior collection. Whenever an object is added to this collection, the owner object will be passed to the setOwner function of the added object.
Step 5: Initialize all components
In the previous step, all the objects were instantiated, and only references to elements were resolved. References to objects in markup were not resolved, because that would limit declaratively defined objects to only refer to objects that were previously declared. After all the objects have been instantiated, all references can be resolved.
Finally, the 'endUpdate' function will be called on all objects that implemented Web.ISupportBatchedUpdates, to actually initialize each object.
Hopefully this post gave you a better idea of how an Atlas page is constructed. Please leave a comment if you have feedback and/or questions.