Tag Archives: missing

Display a Server-Supplied Drop Down List Using AngularJS

These are my notes on displaying a list of server-supplied objects in a drop down list using AngularJS.

Background

I have a server that supplies lists of lookup objects that are used in an AngularJS-based single-page application (SPA). The SPA obtains a list through an API call to the server. The server returns an ordered list of JSON objects. Every object in every list includes a key value, a display value, and supplementary data. For the purposes of this article, only the key and display values are of any concern.

For example, the SPA needs a list of units of measure. The server supplies a list of objects along these lines. They key value is called code and the display value is called display:

[javascript light=”true”]
[
{
code: "L",
display: "L",
description: "litres"
},
{
code: "ML",
display: "mL",
description: "millilitres"
},
… etc …
]
[/javascript]

In the SPA code, each lookup table is wrapped in its own Angular service.

From List of Objects to Dropdown Using <select>

Angular can be told to create a dropdown list using an array of objects thus:

[html light=”true”]
<select ng-model="product.uom"
        ng-options="u.display for u in units" />
[/html]

Here ng-options tells Angular to build the dropdown list showing the display attribute of each object. Whenever the user chooses an item, the entire associated object is stored in $scope.product.uom (uom means units of measure). For my purposes this is very handy since I want access to the entire object.

Defaulting to a Value

This works beautifully until an edit page is shown. When displaying data from the server, the dropdown shows a blank selection even though $scope.product.uom contains an object with all the correct values!

The problem is that Angular matches based on object references, not object contents. This can be illustrated thus:

[javascript]
var a = {foo: "bar"};
var b = {foo: "bar"};
var c = a;
[/javascript]

Variables a and b contain two separate objects that by chance have attributes with the same values. Variables a and c contain the same object pointer.

In the example above, Angular will recognize the value in $scope.product.uom only if it points to an object in the master list $scope.units. The fact that the server-supplied object has identical attributes is irrelevant — Angular only cares whether the object pointers are identical.

[Edit]

There are a couple of ways around this. If there is a unique key on the object, one can use the AngularJS track by clause to tell AngularJS to compare the keys. Continuing the above examples, each object has a code attribute that has a unique string which JavaScript can compare.

[html light=”true”]
<select ng-model="product.uom"
ng-options="u.display for u in units track by u.code" />
[/html]

[Original Article]

Otherwise,  when an object is loaded from the server for editing, the lookup values are replaced with pointers to the corresponding objects in the dropdown list. An unsophisticated but functional bit of code to perform this substitution might be:

[javascript]
// Wrapper function to retrieve a product
// from the server, keyed on productId.
apiProduct.lookup(productId, function(product) {
$scope.product = product;

// Replace the server-supplied lookup value
// with the matching value
// in the $scope.units array.
$scope.product.uom = lookup_by_code(product.uom.code, $scope.units);
});

function lookup_by_code(code, data) {
for(var i=0; i
if(data[i].code == code)
return data[i];

return null;
};
[/javascript]

Update

There is a JSFiddle that demonstrates the value/reference problem concisely.

Rudamentary WiX Orphan Prevention

The Problem

WiX has been a wonderful tool for producing MSIs despite a handful of deficiencies.

In a large project with multiple developers assemblies are added to and removed from the project on a continuing basis. Keeping WiX in sync with these changes is not well-supported. If an assembly is removed, WiX will complain that it can’t find the file and abort. New assemblies are a different story.

Ideally, a new assembly would be added to a WiX project by having heat run on it, the resulting XML massaged, and inserted into the correct component group. There is no mechanism for doing this automatically at this point.

My Solution

In order to prevent the WiX project from producing an MSI with missing files, I wrote a custom MSBuild task to compare the contents of the application’s build directory with the WiX source code. If any extra files are found, the custom task logs each orphan as an error, and the WiX project fails to build.

The following isn’t bullet-proof and lacks some necessary error checking, but I hope it sheds light on the core of my approach.

Step 1: Create a Custom Build Task to Detect Orphans

Creating the Custom Build Task

I’ll not detail creating a custom build task here, but refer you to some articles I found most useful:

  1. Bart de Smet’s excellent article The Custom MSBuild Task Cookbook
  2. The MSDN reference pages
    1. How to: Write a Task
    2. UsingTask Element (MSBuild)
    3. MSBuild Tasks
  3. Marcin Kawalerowicz’s Writing MSBuild Custom Task

The long and short of it is that a custom task is a simple class that descends from Microsoft.Build.Utilities.Task and overrides the Execute() method. Execute() returns a simple bool to indicate success or failure.

Required Parameters

This process needs two directories: where the application build files reside, and where the WiX source files reside. (This assumes that all respective files exist in a single directory.)

namespace WiXInstallValidator
{
    public class OrphanedFileCheck: Task
    {
.
.
.
    [Required]
    public string ArtifactsDirectory { get; set; }

    [Required]
    public string WiXSourceDirectory { get; set; }

The [Required] attribute ensures that the project file sets both of these parameters.

Fetching the File Names

In the Execute() method, loading the list of file names from the directories supplied by the ArtifactsDirectory and WiXSourceDirectory was straightforward.

//
// Extract list of WiX source files
//
IEnumerable lWixFiles =
    from f in System.IO.Directory.GetFiles(WiXSourceDirectory, "*.wxs")
    select f.ToLower();

//
// DLLs that exist in the build artifacts directory
//
IEnumerable lBuildFiles =
    from f in System.IO.Directory.GetFiles(ArtifactsDirectory, "*.dll")
    select System.IO.Path.GetFileName(f).ToLower();

The WiX source files need the path since we’re going to actually open the files. The DLL names have the path stripped off since we’re going to compare them against the WiX source code.

Extract Filenames from XML

The WiX code is nothing special. I have a variable $(var.BuildRoot) that points to the directory where the DLLs are found. Thus a typical WiX source file has the form:

 
  

      

     
      
        
              
                
             
.
.
.

XElement requires that we add the WiX namespace. I define that as a constant for code clarity. Thus the Linq code looks something like:

private const string BUILD_ROOT = "$(var.BuildRoot)\\";
private const string WIX_NAMESPACE = "{http://schemas.microsoft.com/wix/2006/wi}";
.
.
.
//
// Extract filenames from XML begining with $(var.BuildRoot)
//
IEnumerable lFilesInWiX =
    from f in lWixFiles
    from c in XElement.Load(f).Descendants(WIX_NAMESPACE + "File")
    where ((string)c.Attribute("Source")).StartsWith(BUILD_ROOT)
    select ((string)c.Attribute("Source")).Substring(BUILD_ROOT.Length).ToLower();

Compare the Lists of Files

Determining if there are new files in danger of not being included in the MSI only requires the simple statement:

//
// Files in the build artifacts directory which aren't in WiX
//
IEnumerablelOrphanedFiles = lBuildFiles.Except(lFilesInWiX);

Informing the User of Orphans

To create a nice list of errors, one uses the Log.LogError() method thus:

foreach (string s in lOrphanedFiles)
    Log.LogError("The file {0} appears to be missing from the WiX source code.", s);

Wrapping Up the Custom Task

Lastly, the custom task returns a boolean indicating success or failure.

return lOrphanedFiles.Count() == 0;

Step 2: Inserting the Custom Task Into the WiX Build

Including the Extension

The WiX project file has the extension wixproj. First we have to make MSBuild aware of the extension.

 
     
.
.
.

This is straight out of the articles on writing MSBuild custom tasks that I listed above.

Triggering the Extension

Again, this is straight out of the articles on writing MSBuild custom tasks that I listed above. There are some comments in the default WiX project file showing where to add custom tasks.

  
    
      
          
     

Conclusion

That’s it! This is something that I don’t do often, so recorded my personal notes here in case somebody else might benefit from my working through the problem.

Credits

Thanks to the wonderful WiX mailing list members, especially Simon and Blair who pointed me in this general direction.

Updates

2010-01-21
Added ToUpper() to the Linq expressions to avoid false positives due to case sensitivity.