Monday, October 3, 2011

Hansen8 Developers Group Meeting IX

LINQ

Swathi discussed LINQ and the many advantages it brings. She provided three examples:

var feeType = from p in db.FEETYPEs
  where p.FEEDESC.Contains("Permit")
  select p;

XDocument xmlSource = XDocument.Load("XMLFile1.xml");

var books = from book in xmlSource.Descendants("Table1")
  select new
  {
     Name = book.Element("bookName").Value,
     Author = book.Element("author").Value
  };

//using Query Expression
string[] names = { "Tom", "Rick", "Harry", "Mary", "Jay" };

IEnumerable query = from n in names
    where n.Contains("a") // Filter elements
    orderby n.Length // Sort elements
    select n; // Translate each element (project)
        
//using Lambda Expression
IEnumerable lambdaQuery = names.Where(n => n.Contains("a")).OrderBy(n => n.Length);
Finally, Swathi showed how we can now use LINQ in H8:

var requests = NewComponent();
requests.Load(x => (x.RequestType.RequestType == "Graffiti" && x.IsAssigned == true) || x.RequestType.RequestType == "Dead Animal")

Hansen8 Developers Group Meeting IIX

Windows Workflow Foundation

Anthony lightly touched on WWF and the concept of dehydration and rehydration to persist workflow to a durable medium. He showed examples of the primary events:
  • Completed
  • Terminated
  • Loaded
  • Unloaded
  • Persisted
  • Idled
I've delayed too long in writing this so I cannot elaborate further! He provided this link.

Hansen8 Developers Group Meeting VII

Extension Methods

Tim presented extension methods and used as an example several date extension methods, One of them was DaysUntil:
        /// 
        /// Gets the number of days until the specified date.
        /// 
        /// 
        /// 
        public static double DaysUntil(this DateTime dt)
        {
            DateTime nextDay;

            if (dt > DateTime.Now)
                nextDay = new DateTime(dt.Year, dt.Month, dt.Day);
            else
                nextDay = new DateTime(dt.Year + 1, dt.Month, dt.Day);

            return nextDay.Subtract (DateTime.Now).TotalDays;
        }
Usage:

  var dt = new DateTime(DateTime.Now.Year, cmbMonth.SelectedIndex + 1, cmbDay.SelectedIndex + 1);
  var du = dt.DaysUntil();
He indicated some disadvantages, chief among them being the inability to reflect upon them. There is no way of knowing via reflection that DaysUntil is an extension method. Interestingly if you write an extension method which shares the same functionality of an existing method the extension method will not get called. This means you could write extension methods for future functionality so that when recompiled for a later framework those methods would run natively without the need to delete them. Of course you could also wrap them in compiler directives or simply delete them so that also got filed under a disadvantage. He hinted at some practical extension methods for Enum such as Has, Add, Remove but did not share the source code. I've always been frustrated by the inability to easily detect if an Enum has a certain value (assuming it is decorated with a FlagAttribute. With this extension you could test via myEnum.Has(MyEnum.SomeValue).

Monday, September 26, 2011

Digital Design

Web Based Digital Design


This is the first of what I hope to be several posts related to building a web-based digital design tool. Today I wrote some basic logic operation in terms of "nor" gates.



var logic = {
    nop: function (a) {
        return a;
    },
    nor: function (a, b) {
        return (a | b) ? 0 : 1;
    },
    not: function (a) {
        return this.nor(a, a);
    },
    or: function (a, b) {
        return this.not(this.nor(a, b));
    },
    nand: function (a, b) {
        return this.or(this.not(a), this.not(b));
    },
    and: function (a, b) {
        return this.nor(this.not(a), this.not(b));
    },
    xnor: function (a, b) {
        return this.or(this.and(a, b), this.and(this.not(a), this.not(b)));
    },
    xor: function (a, b) {
        return this.or(this.and(a, this.not(b)), this.and(this.not(a), b));
    }
};

I then defined a gate constructor



var gate = (function () {

    function tick(op, input) {
        this.value = this.next;
        var params = [];
        for (var i = 0; i < input.length; i++) {
            params[i] = input[i].value;
        }
        this.next = op.apply(logic, params);
    }

    return function (opName, input) {
        var that = this;
        clock.hook(function () { tick.call(that, logic[opName], input); });
    }

}).call();

I then created a small test



(function () {
    var zero = { value: 0 };
    var one = { value: 1 };
    var input = [zero, one];
    var g1 = new gate("xor", input);
    var g2 = new gate("and", [g1, input[1]]);
    clock.tick();
    clock.tick();
    assert(g1.value == 1, "0 xor 1");
    clock.tick();
    assert(g2.value == 1, "1 and 1");
    input[0] = one;
    clock.tick();
    clock.tick();
    assert(g1.value == 0, "1 xor 1");
    clock.tick();
    assert(g2.value == 0, "0 and 1");
}).call();

For the time being my clock looks like this



var clock = {
    tick: function () { },
    hook: function (callback) {
        var tick = this.tick;
        this.tick = function () {
            tick();
            callback();
        };
    }
};

Next step is to construct a more robust test by building a JK flip-flop circuit and then a counter.

Tuesday, August 30, 2011

Hansen8 Developers Group Meeting VI

WCF

Pradeep presented WCF and it's ability to, via configuration alone, communicate using different bindings using endpoints. He demonstrated self-hosted and IIS-hosted services. He also demonstrating calling the same method using multiple bindings from the WCF Test Client.

The meeting got interesting when Russell asked Pradeep to create a method which accepts one int parameter and load it as a Web Service. Here is what we learned.

First, create a new "WCF Service Application" it will generate this code for you:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace WcfService1
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together.
    public class Service1 : IService1
    {
        public string GetData(int value)
        {
            return string.Format("You entered: {0}", value);
        }

        public CompositeType GetDataUsingDataContract(CompositeType composite)
        {
            if (composite == null)
            {
                throw new ArgumentNullException("composite");
            }
            if (composite.BoolValue)
            {
                composite.StringValue += "Suffix";
            }
            return composite;
        }
    }
}

Now add a new Console Project to this solution and add this service as a Web Reference and look at the implementation of the GetData proxy:
       [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/IService1/GetData", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
        [return: System.Xml.Serialization.XmlElementAttribute(IsNullable=true)]
        public string GetData(int value, [System.Xml.Serialization.XmlIgnoreAttribute()] bool valueSpecified) {
            object[] results = this.Invoke("GetData", new object[] {
                        value,
                        valueSpecified});
            return ((string)(results[0]));
        }

And it only gets worse. Notice the return is decorated with IsNullable=true so let's modify the return type to be a non-nullable type:

        public int GetData(int value)
        {
            return value + 1;
        }

And regenerate the proxy. Now the implementation of GetData has moved the return value into two out parameters:

        [System.Web.Services.Protocols.SoapDocumentMethodAttribute(
          "http://tempuri.org/IService1/GetData", 
          RequestNamespace="http://tempuri.org/", 
          ResponseNamespace="http://tempuri.org/", 
          Use=Description.SoapBindingUse.Literal,
          ParameterStyle=Protocols.SoapParameterStyle.Wrapped)]
        public void GetData(
          int value, 
          [XmlIgnoreAttribute()] bool valueSpecified, 
          out int GetDataResult, 
          [XmlIgnoreAttribute()] out bool GetDataResultSpecified) {
            object[] results = this.Invoke("GetData", new object[] {
                        value,
                        valueSpecified});
            GetDataResult = ((int)(results[0]));
            GetDataResultSpecified = ((bool)(results[1]));
        }

So now you are force to write code like this:

            int result;
            bool hasResult;
            test.GetData(
                value: 1,
                valueSpecified: false,
                GetDataResult: out result,
                GetDataResultSpecified: out hasResult
                );

I would have assumed that in the above code, where the value:1 is not actually provided (because valueSpecified:false) that this would throw an exception on the server. But instead of a fault it assumes a value of 0! So how do we make it default to a 10?

Obviously what is missing here is the ability to mark method parameters as *optional* to complement the feature of marking a field or property as required. And of course the C# language has syntax for that already:

        public int GetData(int value = 10) // does not work
        {
            return value;
        }

But don't count on this optional parameter defaulting to 10 because if valueSpecified is false it will get 0.

I went out in search of an explanation to this lunacy but only found this forum post dating back to August 24, 2006 with no solution in sight.

Best practices dictate the use of a composite type so all service methods accept one parameter and return one result. It's possible to decorate fields of a composite type as required using [DataMember(IsRequired=true)] but the same is not true for method parameters.

WCF and Microsoft are not perfect...I just wish I understood what they were thinking.

Back to Pradeep's presentation, here is is configuration which contains endpoints for the following bindings:
  • wsHttpBinding
  • mexHttpBinding
  • basicHttpBinding
  • netTcpBinding
  • netNamedPipeBinding

He did not discuss nor am I familiar with the purpose of certificateReference .

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 <system.web>
  <compilation debug="true" />
 </system.web>
 <!-- When deploying the service library project, the content of the config file must be added to the host's
 app.config file. System.Configuration does not support config files for libraries. -->
 <system.serviceModel>
  <services>
   <service behaviorConfiguration="EvalServiceLibrary.Service1Behavior"
    name="EvalServiceLibrary.EvalService">
    <clear />
    <endpoint address="ws" binding="wsHttpBinding" name="wsHttp"
     contract="EvalServiceLibrary.IEvalService" listenUriMode="Explicit">
     <identity>
      <dns value="localhost" />
      <certificateReference storeName="My" storeLocation="LocalMachine"
       x509FindType="FindBySubjectDistinguishedName" />
     </identity>
    </endpoint>
    <endpoint address="mex" binding="mexHttpBinding" name="Mex" contract="IMetadataExchange"
     listenUriMode="Explicit">
     <identity>
      <dns value="localhost" />
      <certificateReference storeName="My" storeLocation="LocalMachine"
       x509FindType="FindBySubjectDistinguishedName" />
     </identity>
    </endpoint>
    <endpoint address="basic" binding="basicHttpBinding" name="basicHttp"
     contract="EvalServiceLibrary.IEvalService" listenUriMode="Explicit">
     <identity>
      <dns value="localhost" />
      <certificateReference storeName="My" storeLocation="LocalMachine"
       x509FindType="FindBySubjectDistinguishedName" />
     </identity>
    </endpoint>
    <endpoint address="net.tcp://localhost:8888/EvalService" binding="netTcpBinding"
     name="netTCP" contract="EvalServiceLibrary.IEvalService" listenUriMode="Explicit">
     <identity>
      <dns value="localhost" />
      <certificateReference storeName="My" storeLocation="LocalMachine"
       x509FindType="FindBySubjectDistinguishedName" />
     </identity>
    </endpoint>
    <endpoint address="net.pipe://localhost/EvalService" binding="netNamedPipeBinding"
     bindingConfiguration="" name="pipe" contract="EvalServiceLibrary.IEvalService" />
    <host>
     <baseAddresses>
      <add baseAddress="http://localhost:8080/EvalService" />
     </baseAddresses>
    </host>
   </service>
  </services>
  <behaviors>
   <serviceBehaviors>
    <behavior name="EvalServiceLibrary.Service1Behavior">
     <!-- To avoid disclosing metadata information,
     set the value below to false and remove the metadata endpoint above before deployment -->
     <serviceMetadata httpGetEnabled="True"/>
     <!-- To receive exception details in faults for debugging purposes,
     set the value below to true. Set to false before deployment
     to avoid disclosing exception information -->
     <serviceDebug includeExceptionDetailInFaults="False" />
    </behavior>
   </serviceBehaviors>
  </behaviors>
 </system.serviceModel>
</configuration>

Friday, August 26, 2011

Browser Based Windows Manager

Managing Parent-Child relationships among Browser Windows


The following code demonstrates how to manage the parent-child relationship among browser windows in a way that does not result in accessing properties on closed windows by managing a global list of windows. Features include:

  • Close child windows
  • Alerts in child, parent, root windows
  • Close all windows




window.privateLab = {
    alert: function (msg) {
        window.alert(msg);
    },
    alertParent: function (msg) {
        if (window.opener) window.opener.alert(msg);
    },
    alertRoot: function (msg) {
        globalLab.alert(msg);
    },
    injectText: function (container, text) {
        window.globalLab.injectText(window.document, container, text);
    },
    closeAll: function () {
        window.globalLab.closeAll(null);
    },
    closeChildren: function () {
        window.globalLab.closeAll(window);
    },
    createTopWindow: function (url) {
        return window.globalLab.createWindow(null, url);
    },
    createChildWindow: function (url) {
        return window.globalLab.createWindow(window, url);
    }
};

window.onunload = function () {
    window.globalLab.removeWindow(window);
    window.globalLab.closeAll(window);
};

if (window.opener) {
    window.globalLab = window.opener.globalLab;
} else {
    window.onbeforeunload = function () {
        var children = window.globalLab.getWindows();
        if (children.length) {
            window.globalLab.closeAll(window);
            return "You will lose globalLab!";
        }
    };
    window.globalLab = {
        count: 0,
        windows: {},
        createWindow: function (parent, url) {
            var count, w, windows;
            parent = parent || window;
            url = url || "WindowLab.htm";
            windows = this.windows;
            var id = "WindowLabChildWindow" + ++this.count;
            w = parent.open(url, id, true);
            windows[id] = w;
            return w;
        },
        removeWindow: function (child) {
            if (!child) return;
            delete this.windows[child.name];
        },
        alert: function (msg) {
            window.alert(msg);
        },
        getWindows: function (parent) {
            var result, windows;
            parent = parent || window;
            windows = this.windows;
            result = [];
            for (var name in windows) {
                if (windows.hasOwnProperty(name)) {
                    w = windows[name];
                    if (!w.closed && w.opener == parent) {
                        result.push(w);
                    }
                }
            }
            return result;
        },
        closeAll: function (parent) {
            var w, i, windows;
            parent = parent || window;
            w = this.getWindows(parent);
            for (i = 0; i < w.length; i++) {
                w[i].close();
            }
        }
    };
}

Tuesday, August 23, 2011

HANGMAN and Regex


Kind of childish but I got home and started looking at 8080 emulators because Daniel, my new colleague and friend, is excited about writing one. Somehow that degraded into playing Hangman. I thought I had the game beat with a few regex and LinqPad but I was mistaken!


Hansen8 Developers Group Meeting V

IIS Express 7.5

Daniel did a fantastic job giving a brief and convincing argument for IIS Express. IIS Express 7.5 replaces Cassini and enables edit-and-continue in an IIS environment.

You can download it here.

He put a nice Power Point presentation together explaining the Benefits, Requirements and specific setup instructions for our Hansen product. I uploaded it here.

He also briefly discussed the Visual Studio Browser Switcher Toolbar, found here, to allow switching browsers without having to right-click the file and select Browser With...

He hinted about a WebMatrix but unfortunately didn't get into any details!

Tuesday, August 16, 2011

Hansen8 Developers Group Meeting IV

Microsoft Enterprise Library


Raja introduced us to Microsoft Enterprise Library via five examples:

  1. Dependency Injection
  2. Exception Handling
  3. Logging
  4. Caching
  5. Validation

To me, the best part was that the app.config file can be edited visually making it a straightforward matter to modify logging, exception handling and validation rules. Russell pointed out that this is an example of Aspect Oriented Programming which allows for the separation of cross-cutting concerns.
Dependency Injection was demonstrated via a UnityContainer which acts as a sort of factory to produce an instance of RateTable containing a strategy:

        using (IUnityContainer container = new UnityContainer())
        {
            container.LoadConfiguration();
            var rt = container.Resolve(); 
            rt.Calculate();
        }

The specific strategy is specified in the app.config:

  <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
    <container>
      <register type="IRateAlgorithm" mapTo="FlatRate"/>
    </container>
  </unity>

Exception Handling requires an ExceptionManager instance:

    exManager = EnterpriseLibraryContainer.Current.GetInstance();
    catch ( Exception e) exManager.HandleException(e, "BillRun"); 
Without the Enterprise Library Configuration Editor installed on my machine it's difficult to describe the app.config, but essentially it maps "BillRun" exceptions (a policy) through a exception type to an exception handler allowing logging, substitution and custom handling of the exception.

Logging is very similar to exceptions. A LogWriter instance is required. Also, a LogEntry object further clarifies the message category:

	private LogWriter writer = EnterpriseLibraryContainer.Current.GetInstance();
	public void DoLogging()
	{
	    LogEntry log = new LogEntry();
	    log.Message = "Delinquency Run Starting....";
	    log.Categories.Add(Category.General);
	    log.Categories.Add(Category.BillRun);
	    log.Priority = Priority.Normal;
	    writer.Write(log);
	}

Caching contains too much code...skipping

Validation requires a ValidatorFactory to create a specific validator:

  valFactory = EnterpriseLibraryContainer.Current.GetInstance();
  customerValidator = valFactory.CreateValidator();

Validation and ValidationResult:

	ValidationResults results = customerValidator.Validate(customer);
	if (!results.IsValid)
	{
		StringBuilder builder = new StringBuilder();
		builder.AppendLine("Customer is not valid:");
		foreach (ValidationResult result in results)
		{
			//do stuff
		}
	}
At a minimum using Microsoft.Practices.EnterpriseLibrary give guidance for basic coding practices.

Thursday, August 11, 2011

Microsoft Web Services, Java Client

Microsoft .asmx?wsdl produces valid WSDL that Java cannot consume. In fact Java produces valid WSDL that Java cannot consume.

Specifically, the Java stub generators (WSDL2Java, JAXWS, etc.) cannot handle method overloading. I have found through trial-and-error that to resolve this you must give the <operation@name> unique values. This involves changing them in both the <porttype> and <binding>.

For example, given this:

<operation name='LoadByKey'>
<input name='LoadByKeySoapIn' />
<output name='LoadByKeySoapOut' />
</operation>

<operation name='LoadByKey'>
<input name='LoadByKey1SoapIn' />
<output name='LoadByKey1SoapOut' />
</operation>

You would need to rename the second operation 'LoadByKey1'. These same <porttype>/<operation> names are found in each <binding>/<operation> and therefore the <operation@name> must change there as well:

<operation name='LoadByKey1'>
<input name='LoadByKey1SoapIn' />
<output name='LoadByKey1SoapOut' />
</operation>

Hansen8 Developers Group Meeting III

Microsoft Kinect

Laszlo took the initiative to get an exciting opportunity to play with the the Kinect. There seems to be no shortage of gesture-based coding going on and Gesture Recognition gets simplified thanks to the Kinect SDK.

Laszlo quickly introduced the Kinect and it's infra-red projector and camera as well as the fact that it was extremely easy to register for data-ready notifications. Depth and vector data is provided at intervals.

He went on vacation before I asked him for code-level details so I had a look at another project for details.

Setup the runtime:
r = new Nui.Runtime()

Register callbacks:

r.DepthFrameReady += DepthFrameReady
r.SkeletonFrameReady += SkeletonFrameReady


Ask for notification:

r.Initialize(UseDepth | UseSkeletalTracking)


Laszlo then demonstrated the limitations of the device (800mm to 4096mm) as well as the limits of the devices (it's not a mouse!).

He is working on a trial-and-error basis to discover what gestures work. He created a pluggable design to switch experiments quickly. He was surprised to discover that some worked better in the large conference room and some worked worse in the visually noisy environment.

He demonstrated navigating a cursor within a box and clicking that box. Beyond that he didn't dream much in the meeting but I did...I'd like to know some wood-chopping stats:
  • How much wood did I chop?
  • How many chops before it split?
  • How long between chops?
But I suspect the wood chips would become the new "bugs".

My take-away was the Kinect as-is cannot replace the mouse or voice but it makes a lot of sense as a gaming device and maybe Laszlo will figure out how to make it reliably click a button.

Thursday, July 28, 2011

Hansen8 Developers Group Meeting II



Porting Dojo Stateful to C#

I'm the sole Dojo advocate in our group so instead of introducing dojo directly I chose to introduce Stateful via a C# port.

What is Stateful?

It is a observable data repository as this code shows:

  var state = new Stateful(null);
  var tokens = new List();
  state.SetValue("user/age", 1.23);
  state.Watch("user/name", 
        (d) => Console.WriteLine("username: {0}", d));
  state.Watch(tokens, "user/age", 
        (d) => Console.WriteLine("age: {0}", d));
  state.SetValue("user/name", "MyUserName");
  state.SetValue("user/age", 4.56);
  state.Unwatch(tokens);

The output when running the above code is:

age: 1.23
username: MyUserName
age: 4.56


The first age (1.23) displays because the value already exists when the Watch is registered. Stateful is a place to store data as well as a place to look for data and be notified of changes to that data.

Why use Stateful?

When writing a user interface you must hook into the control events before you can do anything useful. For example, you are notified that a button was clicked via a click event. You respond to that click via a click event handler. As the product grows the number of UI controls increases as does the number of event handlers. In my experience the code in these handlers becomes complex rather quickly. WinForms places event handler code in a single code-behind file so it would be nice if that code was easy to read -- if it expressed what was desired instead of what to do. Stateful facilitates this decoupling of UI controls and event handlers.

Instead of writing code like this:

void loginToolStripMenuItem_Click(object sender, EventArgs e)
{
  Login();
}

You express your intentions via a topic ("login@show"):

void loginToolStripMenuItem_Click(object sender, EventArgs e)
{
    state.Publish("login@show");
}

And place the actual implementation elsewhere:

topics.Subscribe("login@show", (sender, e) =>
{
    Login();
});

What does Publish and Subscribe do?
My implementation of Stateful is built on top of PubSub, a C# port of
dojo.Publish and dojo.Subscribe. The Publish method is similar to a SetValue without actually persisting the value. In the sample above a value isn't even provided; the Publish("login@show") is used to signal an event and the Subscribe handles the event.

Conclusion

Decoupling the UI from the business logic has known advantages. The MVC pattern describes why and how to do this. Dojo does this using Publish, Subscribe and Stateful. I've found the dojo implementation compelling enough to warrant porting to C# for use in WinForms development.

Links

http://dojotoolkit.org/reference-guide/dojo/Stateful.html
http://dojotoolkit.org/reference-guide/dojo/publish.html
http://dojotoolkit.org/reference-guide/dojo/subscribe.html
http://msdn.microsoft.com/en-us/library/ff649643.aspx
http://www.sitepen.com/blog/2010/05/04/consistent-interaction-with-stateful-objects-in-dojo/
http://doughays.dojotoolkit.org/dojomvc/







Wednesday, July 27, 2011

Hansen8 Developers Group Meeting I

Event Sourcing
In this meeting Russell introduced us to Martin Fowler and the concept of Event Sourcing.

Martin Fowler seems to focus on code refactoring, design patterns and automated testing.

Event Sourcing can be summarized as the process of recording changes to an application state as a sequence of events.

Russell walked us through a code example which demonstrated a book being added to a library, borrowed and returned. The process started with the creation of a session factory and event handler registration. The session is to ensure the operation was fully successful, similar to a database transaction. The handlers were to do any extra business logic, similar to database triggers.
In this example the session was opened
  a book is created
  a book is added to a book repository
The session was closed

Because a BookStateHandler was already registered with the DomainEvents it is called when the session is committed.

In this case the event contain book attributes. This is similar to (sender, e) where e contains all the meaningful parameters to avoid changing the signature. Before calling Append to queue the event, the Book constructor calls Apply. Apply copies the BookRegistered event arguments into the Book object, similar to an UPDATE statement.

At this point the event is in the queue and the book object is fully initialized. The only thing left is to commit the changes via session.SubmitChanges().

The commit process results in publishing (invoking) the queued events. The actual implementation was bogus, I would have liked to seen something maintenance free:


if (@event is BookRegistered)
DomainEvents.Raise((BookRegistered)@event);
if (@event is Event_Sourcing.BookRegistered.BookLent)
DomainEvents.Raise((Event_Sourcing.BookRegistered.BookLent)@event);
if (@event is Event_Sourcing.BookRegistered.BookReturned)
DomainEvents.Raise((Event_Sourcing.BookRegistered.BookReturned)@event);


The BookRegistered handler adds the event arguments to a book state query collection. This is not the same as the event queue. It is a data repository containing the current state of the book object. It could be DataTable with the book represented with a DataRow.

My Thoughts
There is a strong decoupling between the entity (Book), the event arguments (BookRegistered) and the captured state of the book (BookState). In this example the decoupling introduces much redundancy. Notice the duplicate members across these three classes:


 public Book(BookId id, string title, string isbn)
 {
  this.id = id;
  var @event = new BookRegistered(id, title, isbn);
  Apply(@event);
  Append(@event);
 }

 public BookRegistered(BookId id, string title, string isbn)
 {
  Id = id;
  Title = title;
  Isbn = isbn;
 }

 public class BookState
 {
  public BookId Id { get; set; }
  public string Title { get; set; }
  public bool Lent { get; set; }
 }

I would have liked an explanation as to why Book, BookRegistered, BookState did not all reference a fourth object which actually defines the Id, Title and Lent status. I can speculate that it is because in larger projects there becomes a divergence in attribute space among these three classes.

Conclusion
Event Sourcing as presented in the example showed straight-forward transactional code with the details pushed into the event handlers. The book event history and the book state were maintained separately. Based on the definition of Event Sourcing I presume the events would be persisted to permanent storage before allowing the transaction to complete. Likewise I presume the state might be periodically persisted with a replay identifier to avoid parsing the entire event history to recreate the book state.

Additional Thoughts
In this simple example it would be possible to parse the event history backwards until the book state is fully realized. The additional logic may likely result in a performance gain when recreating the state but is likely too complex for real-world object. Simpler to persist.

Wednesday, July 13, 2011

Code for working with CSV files (CREATE and SELECT):

void Main()
{
 System.Data.Common.DbProviderFactories.GetFactoryClasses().Dump();
 var f = System.Data.Common.DbProviderFactories.GetFactory("System.Data.Odbc");
 var csTemplate = "Driver={{Microsoft Text Driver (*.txt; *.csv)}};Dbq={0};Extensions=asc,csv,tab,txt;";
 var cs = string.Format(csTemplate, @"C:\Users\calix\AppData\Roaming\CSV\");
 var ds = new DataSet();
 var c = f.CreateCommand();
 c.Connection = f.CreateConnection();
 c.Connection.ConnectionString = cs;
 var adapter = f.CreateDataAdapter();
 c.CommandType = CommandType.Text;
 c.CommandText = "SELECT * FROM [csv2.txt]";
 adapter.SelectCommand = c;
 adapter.Fill(ds);
 ds.Dump(); 
 c.CommandText = "CREATE TABLE T1.TXT (F1 CHAR(10))";
 c.Connection.Open();
 c.ExecuteNonQuery();
 c.Connection.Close();
}

Sunday, June 12, 2011

SQL Server + IIS + Hansen8

When running Hansen8 under IIS it is convenient for developers (or at least me) to do as little as possible to get the environment working. If found (but keep forgetting) that to be the following:



1. Logins: Create a "SQL Server authentication" user and disable the password enforcement policy.

2. Server Roles: "public" only.

3. User Mapping: Assign the user to the Hansen database(s)



But do not forget to enable "SQL Server and Windows Authentication mode" under Server Properties|Security (right-click the server, select properties).



Once you do this you can add/modify the provider in the configuration editor to use this user/password instead of the integrated user (which doesn't work).

Sunday, May 8, 2011

WCF + REST

Good grief...after a long night and most of the morning I finally figured out (via trial-and-error, of course) how to correctly define my WCF contract to work with the dojox.rpc.Rest, dojox.rpc.Service and dojox.data.JsonRestStore.

Here is my interface for a "Echo" resource:


    [DataContract]
    public class EchoValue
    {
        [DataMember]
        public string Id;
        [DataMember]
        public string Value;
        [DataMember]
        public UInt64 Version;
    }

    [ServiceContract]
    public interface IDataServices
    {
        [OperationContract]
        [WebInvoke(Method = "DELETE", UriTemplate = "echo/{id}")]
        void DeleteEcho(string id);

        [OperationContract]
        [WebInvoke(Method = "PUT", UriTemplate = "echo/{id}", BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
        EchoValue PutEcho(string id, EchoValue data);

        [OperationContract]
        [WebInvoke(Method = "POST", UriTemplate = "echo", BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
        EchoValue AddEcho(EchoValue data);

        [OperationContract]
        [WebGet(ResponseFormat = WebMessageFormat.Json, UriTemplate="echo/{id}")]
        EchoValue GetEcho(string id);

        [OperationContract]
        [WebGet(ResponseFormat = WebMessageFormat.Json, UriTemplate = "echo")]
        List GetEchos();
    }

First, I hand-coded a service object to understand the details...

    var echoService = function (id) {
        return dojo.xhrGet({
            url: url + "/" + id,
            handleAs: "json",
            load: onLoad,
            error: onError
        });
    };
    dojo.mixin(echoService, {
        put: function (id, value) {
            return dojo.xhrPut({
                url: url + "/" + id,
                handleAs: "json",
                contentType: "application/json",
                putData: dojo.toJson({ Value: value }),
                load: onLoad,
                error: onError
            });
        },
        post: function (id, value) {
            return dojo.xhrPost({
                url: url,
                handleAs: "json",
                contentType: "application/json",
                postData: dojo.toJson({ Id: id, Value: value }),
                load: onLoad,
                error: onError
            });
        },
        "delete": function (id) {
            return dojo.xhrDelete({
                url: url + "/" + id,
                handleAs: "json",
                load: onLoad,
                error: onError
            });
        }
    });
And tested it...

    echoService["delete"]("X").then(function () {
        echoService.post("X", ++key).then(function () {
            echoService["delete"]("X").then(function () {
                echoService.post("X", ++key).then(function () {
                    echoService.put("X", ++key).then(function () {
                        echoService("X").then(function () {
                            dojo.publish("/ready");
                        }); ;
                    });
                });
            });
        });
    });

I also experimented with dojox.rpc.Rest...

  var restService = dojox.rpc.Rest(url + "/", true);
And tested it on its own and with dojox.data.JsonRestStore...

    var restStore = new dojox.data.JsonRestStore({ service: restService });
    restService.put("Y", dojo.toJson({ Value: ++key })).then(function () {
        restStore.fetchItemByIdentity({
            identity: "Y",
            onItem: function (data) {
                restStore.setValue(data, "Value", key++);
                restStore.save();
            },
            onError: onError
        });
    });

I also tested dojox.data.JsonRestStore on its own...

 var store = new dojox.data.JsonRestStore({ target: url, idAttribute: "id" });
 store.newItem({ id: "Z", Value: ++key });
 store.save();
 dojo.publish("/ready");
 
 dojo.subscribe("/ready", function () {
     store.fetchItemByIdentity({
         identity: "Z",
         onItem: function (data) {
             dojo.publish("/update", [data]);
         },
         onError: onError
     });
 });
 
 dojo.subscribe("/update", function (item) {
     store.setValue(item, "Value", ++key);
     store.save();
     dojo.publish("/delete", [item]);
 });
 
 dojo.subscribe("/delete", function (item) {
     store.deleteItem(item);
     store.save();
 });

Things to notice:
The dojox.rpc.Rest requires the URL to end in a "/"
The dojox.data.JsonRestStore defines idAttribute
If you define your WCF REST services "correct" the JsonRestStore works without requiring a service.
The POST and PUT body style is WebMessageBodyStyle.Bare, indicating that the parameter name "data" is not part of the request. Without this it is necessary to wrap the parameter in a "Data" container, for example, putData: dojo.toJson({ Data: {Value: 1} }).

links:
http://docs.dojocampus.org/dojox/rpc/Rest
http://docs.dojocampus.org/dojox/rpc/Service
http://dojotoolkit.org/reference-guide/dojox/data/JsonRestStore.html
http://dojotoolkit.org/reference-guide/dojo/data/ItemFileWriteStore.html
http://dojotoolkit.org/reference-guide/dojo/data/api/Write.html
using-the-dojo-toolkit-with-microsofts-wcf/
http://www.sitepen.com/blog/2008/11/21/effective-use-of-jsonreststore-referencing-lazy-loading-and-more/
http://www.sitepen.com/blog/2008/06/13/restful-json-dojo-data/
http://www.sitepen.com/blog/2008/03/19/pluggable-web-services-with-smd/

Dijit.ByType

I couldn't find anything like this in the dijit library so here's a way to get a dijit with a certain declaredClass:

        dijit.byType = function (type, scope) {
            var scope = scope || dojo.body();
            var result = [];
            var f = function (scope) {
                dojo.forEach(dijit.findWidgets(scope), function (w) {
                    if (w.declaredClass === type) result.push(w);
                    f(w.domNode);
                });
            };
            f(scope);
            return result;
        };

Usage:

dijit.byType("dijit.Tree", dojo.body());

Sunday, May 1, 2011

WCF + JSONP

How to call a WCF service running on one server from the dojo web application hosted on another server? Use JSONP.

Thanks to http://www.gutgames.com/ this was a fairly painless process.

First I had to create the WCF service:

    public class JsonServices : IJsonServices
    {
        [WebGet(ResponseFormat=WebMessageFormat.Json)]
        public string Echo(string value)
        {
            return value;
        }
    }

Then I had to make my web.config changes:
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
  <system.serviceModel>
    <services>
      <service name="Dojo16.services.JsonServices" behaviorConfiguration="JsonServicesBehavior">
        <endpoint address="" binding="webHttpBinding" contract="Dojo16.services.IJsonServices" behaviorConfiguration="JsonServicesBehavior" bindingConfiguration="JsonServicesBinding"/>
      </service>
    </services>
    <bindings>
      <webHttpBinding>
        <binding name="JsonServicesBinding" crossDomainScriptAccessEnabled="true"></binding>
      </webHttpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior name="JsonServicesBehavior">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
        <behavior name="">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
      <endpointBehaviors>
        <behavior name="JsonServicesBehavior">
          <webHttp/>
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
</configuration>

Finally, I had to test if from a browser:
<div id="workArea">Hello World!</div>
    <script type="text/javascript">
        var djConfig = { parseOnLoad: true };
    </script>
    <script src="http://ajax.googleapis.com/ajax/libs/dojo/1.6/dojo/dojo.xd.js" type="text/javascript"></script>
    <script type="text/javascript">
        dojo.require("dojo.string");
        dojo.require("dojo.io.script");
        dojo.ready(function () {
            dojo.byId("workArea").innerHTML = dojo.string.substitute("<b>Hello ${0}!</b>", ["Dojo"]);
            var targetNode = dojo.create("div", { innerHTML: "Target" });
            dojo.place(targetNode, "workArea", "after");
            var jsonpArgs = {
                url: "http://localhost:64290/Infor/Quiz/services/JsonServices.svc/Echo",
                callbackParamName: "callback",
                content: {
                    value: "dojo toolkit"
                },
                load: function (data) {
                    targetNode.innerHTML = "<pre>" + dojo.toJson(data, true) + "</pre>";
                },
                error: function (error) {
                    targetNode.innerHTML = "An unexpected error occurred: " + error;
                }
            };
            setTimeout(function () {
                dojo.io.script.get(jsonpArgs);
            }, 2000);
        });
    </script>

Links
http://www.gutgames.com/post/Enabling-JSONP-in-WCF.aspx

Saturday, April 2, 2011

Javascript Quiz: Question 2


I wanted to convert a string to a function, so I figured this would work:

  var expression = "x * x";
  var f1 = function(x) {
    return eval(expression);
  };
  f1(10);

And it did work, but I didn't want to call eval every time I invoked f1, so I made the outer function part of the eval statement:

  var expression= "x * x";
  expression = "(function (x) { return (" + expression + "); });"
  var f2 = eval(expression);
  f2(20);

This does not work in IE but complains, "Object expected". Specifically:
{
  "name": "TypeError", 
  "message": "Object expected", 
  "number": -2146823281, 
  "description": "Object expected"
}

Question: How can this be done?



Monday, March 14, 2011

Javascript Quiz: Question 1


Question: What is the output?
var o = {
  a: 1,
  b: 2,
  c: 3
};
for (var x in o) {
  setTimeout(function() {
    console.log(x);
  }, 1000 * Math.random());
}







The Dojo Toolkit has a d.forEach method which eliminates closure issues like this, but is designed to work on array or NodeList objects. I am not aware of any dojo methods for iterating over object properties.


function forEach(o, context, f) {
  var x;
  if (!f) {
    f = context;
    context = this;
  }
  for (x in o) {
    if (o.hasOwnProperty(x)) {
      f.call(context, x, o[x], o);
    }
  }
}




References
http://stackoverflow.com/questions/1963102/what-does-the-jslint-error-body-of-a-for-in-should-be-wrapped-in-an-if-statement
http://www.jslint.com/
http://www.google.com/search?q=js+closures

Friday, February 25, 2011

Dojo 1.6 - dojo.require (still) loads packages synchronously

I was under the impression that RequireJS was going to change the loader experience. In retrospect I now see the caveats and qualifiers (see links).

Given this block of code:
dojo.require("dojo.string");
dojo.ready(function () {
    alert(dojo.string.pad("hello", " ", 10));
});


What happens? Unfortunately it's the same thing that happened in 1.5. I was hoping the loader was modified to use RequireJS and load the package asynchronously but that is not the case. The dojo/string.js is loaded using http.open('GET', uri, false), which is a synchronous load:

  1. You can optionally provide a second parameter, omitModuleCheck, or rely on _global_omit_module_check, to determine if a test should be made to make sure "dojo.string" is actually defined after loaded.
  2. _loadedModules it tested to see if "dojo.string" is already loaded (it is not).
  3. _getModuleSymbols is called to convert "dojo.string" into a path (utilizes _modulePrefixes), resulting path is "./string.js"
  4. _loadPath("./string.js", "dojo.string") is called, because path is not absolute, baseUrl is used to calculate the uri, in my case this is "scripts/dojo/./string.js".
  5. Because omitModuleCheck was not true, _loadPath was called with a module name ("dojo.string") and because it was called with a module name it calls _loadUriAndCheck instead of _loadUri. Note that currentModule is assigned "dojo.string".
  6. _loadUriAndCheck calls _loadUri and later checks to make sure "dojo.string" was added to _loadedModules.
  7. _loadUri tests _loadedUrls to make sure "scripts/dojo/./string.js" is not already loaded. It then invokes _getText which does a synchronous GET and returns the javascript as a string. That value is dojo.eval'd, to load the code into memory.

NOTE: The currentModule was set for no apparent reason so I looked into it and found that it is also referenced by the "define" method (in loader.js) and used to convert "dojo.string" into "dojo/string". It only does this when two parameters are provided to the define method.


Related Links
RequireJS
RequireJS/AMD Module Forms

Friday, February 4, 2011

ESRI ArcGIS Javascript API

Adding Closable to InfoWindow

ESRI uses "hide" style to indicate the x in the corner of the infoWindow to dismiss/hide the infoWindow. If another .CSS gives meaning to "hide", such as display: none, then that little X will no longer appear in the infoWindow.

The problem with this is that esri.dijit.InfoWindow does not implement support for "closable".


Solution


coretech.ext.makeInfoWindowClosable = function(p) {
  var a = "closable",
      infoWindow = p.prototype;
  an = infoWindow._getAttrNames(a);
  infoWindow[an.s] = function(value) {
    this[a] = value;
    dojo.query(".user .titlebar .hide", this.domNode).forEach(function(n) {
      dojo.style(n, {
        display: value ? "block" : "none"
      });
    });
  };
  infoWindow[an.g] = function() {
    return this[a];
  };
};
coretech.ext.makeInfoWindowClosable(esri.dijit.InfoWindow);


Usage Example



var o = coretech.esriMap.infoWindow;
o.set("closable", false);
o.show(coretech.esriMap.extent.getCenter());


Links

ArcGIS JavaScript API

Wednesday, January 26, 2011

Dojo Pre-Localization (faster to develop and load)

Embedding Dojo Resources During Early Development

Want to code for localization but don't want to take the time to build a resource file (or wait for the resource file to load)?


Declare your resources in code like this:


dojo.setObject("coretech.nls.res.en_us", {
  "R1": "HELLO",
  "R2": "WORLD"
});
dojo.provide("coretech.nls.res.en_us");

Then use the resources like normal:

dojo.requireLocalization("coretech", "res");
dojo.addOnLoad(function() {
    var res = dojo.i18n.getLocalization("coretech", "res", this.lang);
    alert(res.R1);
    alert(dojo.toJson(res, true));
});

How do I know?

The dojo.provide method (1.5) is implemented in loader.js and looks like this:

return (d._loadedModules[resourceName] = d.getObject(resourceName, true));

Tuesday, January 25, 2011

Dojo on my Kindle

Scraping dojo campus to build an eBook

While the second edition of Dojo: The Definitive Guide would be ideal, it hasn't been written. In the meantime I've decided to grab the raw text from docs.dojocampus.org to read on my kindle. In order to do that I needed to get the names of what I was interested in:

Evaluate this code in a dojo environment:

var p, l = [];
for (p in dojo) {
  if (dojo.isFunction(dojo[p])) {
    l.push(p);
    console.log(p);
  }
};
dojo.filter(l, function(a) {
  if ("_" == a[0]) return false;
  if (a[0].toUpperCase() == a[0]) return false;
  return true;
}).sort().join("\n");

With this result I went to LinqPad and came up with this C# script:

void Main()
{
 // http://docs.dojocampus.org/dojo/subscribe?action=raw
 var urlTemplate = "http://docs.dojocampus.org/dojo/{0}?action=raw";
 var mods = new string[] {
 "addClass","addOnLoad","addOnUnload","addOnWindowUnload","anim",
 "animateProperty","attr",
 "blendColors","body","byId","cache","clone",
 "colorFromArray","colorFromHex","colorFromRgb","colorFromString",
 "connect","connectPublisher","contentBox","cookie","coords","create"
 ,"declare","delegate",
 "deprecated","destroy","disconnect","empty","eval","every","exists",
 "experimental","extend","fadeIn","fadeOut","fieldToObject","filter",
 "fixEvent",
 "forEach","formToJson","formToObject","formToQuery","fromJson",
 "getComputedStyle",
 "getNodeProp","getObject","hasAttr","hasClass","hitch","indexOf",
 "isAlien","isArray","isArrayLike",
 "isCopyKey","isDescendant",
 "isFunction","isObject","isString","lastIndexOf",
 "loadInit","loaded",
 "map","marginBox","mixin","moduleUrl","objectToQuery","partial","place",
 "platformRequire",
 "position","provide","publish","query","queryToObject","rawXhrPost",
 "rawXhrPut","ready",
 "registerModulePath","removeAttr","removeClass","replace","require",
 "requireAfterIf","requireIf",
 "requireLocalization","safeMixin","setContext","setObject",
 "setSelectable","some","stopEvent",
 "style","subscribe","toJson","toggleClass","trim","unloaded",
 "unsubscribe","when","windowUnloaded",
 "withDoc","withGlobal","xdRequireLocalization","xhr","xhrDelete",
 "xhrGet","xhrPost","xhrPut"
 };
 var outputDir = new System.IO.DirectoryInfo(@"g:\docs\dojo\");
 var maxGets = 50;
 var sb = new System.Text.StringBuilder();
 for (var i = 0; i= --maxGets) break;
   } catch (WebException ex) {
    ex.Dump(mods[i]);
    if (ex.Message.Contains("(503)")) {
     System.Threading.Thread.Sleep(60 * 60 * 1000);
    }    
   }
   System.Threading.Thread.Sleep(5000);
  }
 }
 System.IO.File.WriteAllText(System.IO.Path.Combine(outputDir.FullName, "dojo.methods.txt"), sb.ToString());
}

And finally I compressed "dojo.methods.txt" into a zip file and emailed it to myself@kindle.com. Now I have something to read tonight.

Monday, January 17, 2011

Dojo Dijit.Tree

Building a Javascript Object Explorer using the dojo Tree dijit.


Chapter 15 of Dojo: The Definitive Guide give a nice description of the Tree dijit using a TreeStoreModel or ItemFileReadStore.

Regarding the TreeStoreModel, Matthew Russell notes that "anything that presents this interface is just a valid model as the TreeStoreModel".


His comment give the answer to how one would build an object explorer.


The interface methods identified for readonly viewing are:

  • getRoot
  • mayHaveChildren
  • getChildren
  • getIdentity
  • getLabel


The interface was documented in the book but can also be found in the source code:

  • getRoot: function(onItem, onError)
  • mayHaveChildren: function(/*dojo.data.Item*/ item)
  • getChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ onComplete, /*function*/ onError)
  • getIdentity: function(/* item */ item)
  • getLabel: function(/*dojo.data.Item*/ item)


Armed with this information is was just a matter of writing the code:


var model = (function (args) {
    if (dojo.isString(args)) args = { root: args };
    args.root = args.root || "window";
    args.identifier = args.identifier || "id";
    args.label = args.label || "name";
    if (dojo.isString(args.root)) {
        var root = { parent: null };
        root[args.identifier] = args.root;
        root[args.label] = args.root;
        args.root = root;
    }
    if (!args.root.parent) {
        var parentPath = args.root[args.identifier].split(".");
        parentPath = parentPath.splice(0, parentPath.length - 1);
        parentPath = parentPath.join(".") || "dojo.global";
        args.root.parent = dojo.getObject(parentPath);
    }
    var result = {
        args: args,
        _getValue: function (item) {
            if (!item) return undefined;
            return dojo.getObject(item[args.identifier], false, args.root.parent);
        },
        getRoot: function (f1, f2) {
            f1(args.root);
        },
        mayHaveChildren: function (item) {
            var value = this._getValue(item);
            return dojo.isObject(value);
        },
        getIdentity: function (item) {
            return item[args.identifier];
        },
        getLabel: function (item) {
            return item[args.label];
        },
        getChildren: function (parentItem, callback) {
            var p = this._getValue(parentItem), children = [], itemName, child;
            for (itemName in p) {
                try {
                    child = { };
                    child[args.label] = itemName;
                    child[args.identifier] = parentItem[args.identifier] + '.' + itemName;
                    children.push(child);
                } catch (ex) {
                    console.log(ex);
                }
            }
            callback(children);
        }
    }
    return result;
} ("window"));

The args.identifier and args.label are in the interest of generality and don't add much value. Likewise _getValue requires the root parent and fully qualified identifiers. The alternative is to capture the value in the getChildren. Eliminate these features and the code is cut in half.


Links:
Nightly Build Tree Tests
Dojo Dictionary

Friday, January 14, 2011

ArcGIS Online Javascript API - Compact Build vs Standard Build

ESRI has (at least) two editions of the ArcGIS Online Javascript API.  One of those is the "compact" edition:

  1. http://serverapi.arcgisonline.com/jsapi/arcgis/?v=2.1 (550k)
  2. http://serverapi.arcgisonline.com/jsapi/arcgis/?v=2.1compact (355k)

Other than 200k, what is the difference?

The compact edition exclusively contains "esri.dijit.InfoWindowLite" whereas the basic edition contains (exclusively) the following:

"dijit._base", "dijit._base.focus", "dijit._base.place", "dijit._base.popup", "dijit._base.scroll", "dijit._base.sniff", "dijit._base.typematic", "dijit._base.wai", "dijit._base.window", "dijit._Container", "dijit._CssStateMixin", "dijit._HasDropDown", "dijit._Templated", "dijit._Widget", "dijit.form._FormWidget", "dijit.form.Button", "dijit.form.HorizontalRule", "dijit.form.HorizontalRuleLabels", "dijit.form.HorizontalSlider", "dijit.form.VerticalRule", "dijit.form.VerticalRuleLabels", "dijit.form.VerticalSlider", "dojo.AdapterRegistry", "dojo.cache", "dojo.cldr.nls.gregorian", "dojo.cldr.nls.gregorian.en_us", "dojo.cldr.nls.number", "dojo.cldr.nls.number.en_us", "dojo.cldr.supplemental", "dojo.date.locale", "dojo.date.stamp", "dojo.dnd.autoscroll", "dojo.dnd.common", "dojo.dnd.move", "dojo.dnd.Moveable", "dojo.dnd.Mover", "dojo.number", "dojo.parser", "dojo.regexp", "dojo.uacss", "dojo.window", "esri.dijit.InfoWindow", "esri.layers.agsimageservice", "esri.tasks._task", "esri.tasks.find", "esri.tasks.geometry", "esri.tasks.gp", "esri.tasks.identify", "esri.tasks.locator", "esri.tasks.na", "esri.tasks.query", "esri.tasks.route", "esri.toolbars._toolbar", "esri.toolbars.draw", "esri.toolbars.navigation", "esri.virtualearth.VEGeocoder", "esri.virtualearth.VETiledLayer"

How do I know this?

First, load the basic IDE:

(function() {
    var url = "http://serverapi.arcgisonline.com/jsapi/arcgis/?v=2.1",
        init = function() {
            console.log("Restore djConfig, AOL may need it, ESRI does not");
            djConfig = dojo.config;
            coretech.ext.inspect("esri21fDojo");
        };


        djConfig = {
            scopeMap: [
                ["dojo", "esri21fDojo"],
                ["dijit", "esri21fDijit"],
                ["dojox", "esri21fDojox"]
            ],
            addOnLoad: init
        };
        $LAB.script(url);
}());

Second, load the compact IDE:

(function() {
    var url = "http://serverapi.arcgisonline.com/jsapi/arcgis/?v=2.1compact",
        init = function() {
            console.log("Restore djConfig, AOL may need it, ESRI does not");
            djConfig = dojo.config;
            coretech.ext.inspect("esri21cDojo");
        };

        djConfig = {
            scopeMap: [
                ["dojo", "esri21cDojo"],
                ["dijit", "esri21cDijit"],
                ["dojox", "esri21cDojox"]
            ],
            addOnLoad: init
        };
        $LAB.script(url);
}());

Finally, compare the two:

var compact = esri21cDojo._loadedModules;var full = esri21Dojo._loadedModules;
var compactOnly = [];
for (var c in compact) {
  if (undefined === full[c]) compactOnly.push(c);
}
compactOnly.push("---------------------------------");
for (var f in full) {
  if (undefined === compact[f]) compactOnly.push(f);
}
dojo.toJson(compactOnly, true);

If you run the compact JS code through a beautifier you can see that InfoWindowLite declares a InfoWindow, presumably customized for smaller screens but also to avoid any dijit dependencies (there is still a dependency on dijit._base.manager):

    if (!dojo._hasResource["esri.dijit.InfoWindowLite"]) {
        dojo._hasResource["esri.dijit.InfoWindowLite"] = true;
        dojo.provide("esri.dijit.InfoWindowLite");
        dojo.declare("esri.dijit.InfoWindow", null, {...


Links