Saturday, 15 February 2014

Grunt.Js presentasjon

Grunt.js presentasjon

Grunt.Js presentasjon kan lastes ned her:

http://www.aurstad.info/GruntJs.pdf [PDF]

Note to my English readers, the GruntJs presentation is written in Norwegian.

Saturday, 4 January 2014

Compressing a byte array in C# with GZipStream

In .NET 4.0 or later versions, it is possible to compress a byte array with GZipStream and therefore the GZip algorithm. The GZipStream can be outputted to an array or a file. The code below shows a wrapper class for compressing a byte array, decompressing it and a unit test that reads all the bytes in text file, then compresses it, decompresses it and checks that the decompressed byte array has the same byte values as the bytes read from the text file. Compression and decompression code next:

using System;
using System.IO;
using System.IO.Compression;

namespace TestCompression
{
    
    /// 
    /// Compresses or decompresses byte arrays using GZipStream
    /// 
    public static class ByteArrayCompressionUtility
    {

        private static int BUFFER_SIZE = 64*1024; //64kB

        public static byte[] Compress(byte[] inputData)
        {
            if (inputData == null)
                throw new ArgumentNullException("inputData must be non-null");

            using (var compressIntoMs = new MemoryStream())
            {
                using (var gzs = new BufferedStream(new GZipStream(compressIntoMs, 
                 CompressionMode.Compress), BUFFER_SIZE))
                {
                    gzs.Write(inputData, 0, inputData.Length);
                }
                return compressIntoMs.ToArray(); 
            }
        }

        public static byte[] Decompress(byte[] inputData)
        {
            if (inputData == null)
                throw new ArgumentNullException("inputData must be non-null");

            using (var compressedMs = new MemoryStream(inputData))
            {
                using (var decompressedMs = new MemoryStream())
                {
                    using (var gzs = new BufferedStream(new GZipStream(compressedMs, 
                     CompressionMode.Decompress), BUFFER_SIZE))
                    {
                        gzs.CopyTo(decompressedMs);
                    }
                    return decompressedMs.ToArray(); 
                }
            }
        }

        //private static void Pump(Stream input, Stream output)
        //{
        //    byte[] bytes = new byte[4096];
        //    int n;
        //    while ((n = input.Read(bytes, 0, bytes.Length)) != 0)
        //    {
        //        output.Write(bytes, 0, n); 
        //    }
        //}
        


    }

}


In the code, memorystreams are used and the ToArray() method is used to generate byte arrays. The GZipStream can have a compression mode of either Compress or Decompress. The GZipStream in the compress and decompress methods are wrapped with BufferedStream with a buffer size of 64kB. This is done to be able to handle larger files. I have tested this code in a unit test with a lorem ipsum generated text file about 5,5 MB. The unit test is shown next:

using System;
using NUnit.Framework;
using System.Text;
using System.IO;
using System.Linq;


namespace TestCompression.Test
{
    [TestFixture]
    public class UnitTest1
    {

        [Test]
        public void CompressAndUncompressString()
        {
            byte[] inputData = File.ReadAllBytes("Lorem1.txt");
            byte[] compressedData = ByteArrayCompressionUtility.Compress(inputData);
            byte[] decompressedData = ByteArrayCompressionUtility.Decompress(compressedData);

            Assert.IsNotEmpty(inputData);
            Assert.IsNotEmpty(decompressedData);
            Assert.IsTrue(inputData.SequenceEqual(decompressedData));

            Console.WriteLine("Compressed size: {0:F2}%", 
             100 * ((double)compressedData.Length / (double)decompressedData.Length));

            //string outputString = Encoding.UTF8.GetString(decompressedData);

        }

    }
}


Output of this unit test is shown next:

------ Test started: Assembly: TestCompression.Test.dll ------

Compressed size: 28,74%

1 passed, 0 failed, 0 skipped, took 18,87 seconds (NUnit 2.6.2).



To generate a lorem ipsum text file, you can use a lorep ipsum generator here: http://loripsum.net

Wednesday, 1 January 2014

RandomNumberGenerator in C#

To generate random numbers in C#, it is possible to use the class RandomNumberGenerator in System.Security.Cryptography namespace in .NET. This class can be easier to use with a simple wrapper class. The wrapper class provided here returns either an integer or an unsigned integer. The "randomness" is better in this class than in the default Random generator of .NET, the Random class. This class will for example emit the same random values for two instances instantiated at almost the same time of the Random class. The wrapper class looks like this:

 public static class RandomGenerator
    {

        private static readonly RandomNumberGenerator generator;

        static RandomGenerator()
        {
            generator = RandomNumberGenerator.Create();
        }

        public static int GetNext()
        {
            byte[] rndArray = new byte[4];
            generator.GetBytes(rndArray);
            return BitConverter.ToInt32(rndArray, 0);
        }

        public static uint GetNextUnsigned()
        {
            byte[] rndArray = new byte[4];
            generator.GetBytes(rndArray);
            return BitConverter.ToUInt32(rndArray, 0);
        }



    }

The class is in fact a static class with a static RandomNumberGenerator instance created in the static constructor. The methods to create a new random number uses the GetBytes method to fill a four byte array. We could of course generate longer arrays and create for example 64-bits integers, but here just a four byte array is used. Either an integer or unsigned integer is returned by the two respective methods for this. I have not bothered to refactor this simple class. The BitConverter class converts the byte array to int or unsigned int (32-bits) starting at index 0. We could also return other datatypes here than just integers. Simple unit test:

 [TestFixture]
    public class UnitTest1
    {

        [Test]
        public void GetNextInteger()
        {
            int random = RandomGenerator.GetNext();
            Debug.WriteLine(random);
        }

        [Test]
        public void GetNextUInteger()
        {
            uint random = RandomGenerator.GetNextUnsigned();
            Debug.WriteLine(random);
        }

    }

Sample output:

------ Test started: Assembly: TestRandomNumberGeneratorTest.dll ------

-1821995826

1013025195

2 passed, 0 failed, 0 skipped, took 0,42 seconds (NUnit 2.6.2).


If you would like random numbers in a specified range, for example 0 to 99, you could take the integer and do a modulo 100 operation, e.g RandomGenerator.GetNextUnsigned() % 100. Of course, this is tied to the desired range you want. If a range between for example -20 and 20 is desired, you could for example do something like: -20 + (RandomGenerator.GetNextUnsigned() % 41). The bottom line is that you should not entrust the randomness of System.Random class but use the RandomNumberGenerator class in System.Security.Cryptography if you want to generate random integers, signed or unsigned that exhibit more distributed randomness than the pseudorandomness of System.Random.

Thursday, 24 October 2013

Using Snoop and PowerShell to interrogate View Models in WPF applications

It is possible to use Snoop to interrogate View Models in WPF applications. If you first install Snoop, check that you run Snoop as an administrator, if you cannot attach to the process which is your WPF application. Start snooping the application, then move to the tab Powershell. Use Ctrl+Shift and click on the visual or ui element you want to inspect in the GUI. You will see a pink border around the ui element or visual and this will then be possible to inspect further. Go to the PowerShell tab and start interrogating the object. In the MVVM WPF application at my work, we can see the view model in the property DataContext.ViewModel. It is possible to further drill down to properties in the ViewModel quite easily. Example follows, I have selected a RadGridView in my application and select the Visual property, then look at the DataContext of the Visual, drill down further to the ModelCollection property, which in this particular example is a property of the data context object of which the selected visual is data bound to. This object contains a property of type ObservableCollection which has got some custom object inside. The collection object is then piped to the Select operator in Powershell and this Select operator projects the properties of each object in the ModelCollection, in this particular case I select OperationId, OperatingDate and OperationStatus. This is then further piped to Format-Table which is used with the argument -autoresize. The resulting selector is then:

$selected.Visual.DataContext.ModelCollection | Select OperationId, OperatingDate, OperationStatus | Format-Table -autosize

OperationId OperatingDate       OperationStatus
----------- -------------       ---------------
       2076                              Urgent
         70                              Urgent
       2162 23.10.2013 00:00:00       Operation
       2165 23.10.2013 00:00:00       Operation
       2196 23.10.2013 00:00:00       Operation
       2197 23.10.2013 00:00:00       Operation
          0 23.10.2013 00:00:00            None
          0 23.10.2013 00:00:00            None
          0 23.10.2013 00:00:00            None
          0 23.10.2013 00:00:00            None
          0 23.10.2013 00:00:00            None
          0 23.10.2013 00:00:00            None
          0 23.10.2013 00:00:00            None
          0 23.10.2013 00:00:00            None
          0 23.10.2013 00:00:00            None
          0 23.10.2013 00:00:00            None
          0 23.10.2013 00:00:00            None
       1913                              Urgent
       2192 23.10.2013 00:00:00       Operation
       2193 23.10.2013 00:00:00       Operation
       2194 23.10.2013 00:00:00       Operation
       2013 23.10.2013 00:00:00       Operation
       2195 23.10.2013 00:00:00       Operation
       2161 23.10.2013 00:00:00       Operation


It could be possible to pipe this further to the Export-Csv Powershell cmdlet and you can have Excel launch the resulting .csv file. This shows how SnoopWpf can be used to do powerful runtime analysis and inspection of WPF applications and also support Powershell operators. Both technical testers and WPF developers will find this useful. To get started, install Snoop Wpf created by the brilliant developer Cory Plotts and can be downloaded from here: Snoop WPF

Friday, 26 July 2013

Creating a simple autocomplete control with Bootstrap for with a remote JSON service

I have worked a bit with the Twitter Bootstrap javascript library / framework and testet out the TypeAhead control. I could not find a complete example of how to get the autocomplete items from a remote JSON service, so I testet it out myself. Let's first define a web service (ASP.NET) which will return the list of sovereign states in the world:
//using System;
//using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Web;
using System.Web.Script.Services;
using System.Web.Services;

namespace TestBootstrapCss
{
    /// <summary>
    /// Summary description for CountryService
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    [System.Web.Script.Services.ScriptService]
    public class CountryService : System.Web.Services.WebService
    {

        [WebMethod]
        [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
        public Country[] GetCountries(string query)
        {
            if (cachedCountryList == null)
                cachedCountryList = GetCountryList();

            var result = cachedCountryList.Where(c => c.Name.StartsWith(query, System.StringComparison.CurrentCultureIgnoreCase)).ToArray();
            return result;
        }

        public class Country
        {
            public int Id { get; set; }

            public string Name { get; set; }

            public int Population { get; set; }

            public double WorldPercentage { get; set; }

        }

        private static Country[] cachedCountryList = null; 

        public Country[] GetCountryList()
        {
            return new Country[] {
                new Country { Id =1,Name = "China",Population = 1354040000 ,WorldPercentage = 19.07 },
                new Country { Id =2,Name = "India",Population = 1210569573 ,WorldPercentage = 17.05 },
                new Country { Id =3,Name = "United States",Population = 316318000 ,WorldPercentage = 4.45 },
                new Country { Id =4,Name = "Indonesia",Population = 237641326 ,WorldPercentage = 3.35 },
                new Country { Id =5,Name = "Brazil",Population = 193946886 ,WorldPercentage = 2.73 },
                new Country { Id =6,Name = "Pakistan",Population = 183766000 ,WorldPercentage = 2.59 },
                new Country { Id =7,Name = "Nigeria",Population = 173615000 ,WorldPercentage = 2.45 },
                new Country { Id =8,Name = "Bangladesh",Population = 152518015 ,WorldPercentage = 2.15 },
                new Country { Id =9,Name = "Russia",Population = 143400000 ,WorldPercentage = 2.02 },
                new Country { Id =10,Name = "Japan",Population = 127350000 ,WorldPercentage = 1.79 },
                new Country { Id =11,Name = "Mexico",Population = 118419000 ,WorldPercentage = 1.67 },
                new Country { Id =12,Name = "Philippines",Population = 98100000 ,WorldPercentage = 1.38 },
                new Country { Id =13,Name = "Vietnam",Population = 88780000 ,WorldPercentage = 1.25 },
                new Country { Id =14,Name = "Ethiopia",Population = 86613986 ,WorldPercentage = 1.22 },
                new Country { Id =15,Name = "Egypt",Population = 83661000 ,WorldPercentage = 1.18 },
                new Country { Id =16,Name = "Germany",Population = 80493000 ,WorldPercentage = 1.13 },
                new Country { Id =17,Name = "Iran",Population = 76768000 ,WorldPercentage = 1.08 },
                new Country { Id =18,Name = "Turkey",Population = 75627384 ,WorldPercentage = 1.07 },
                new Country { Id =19,Name = "Democratic Republic of the Congo",Population = 67514000 ,WorldPercentage = 0.95 },
                new Country { Id =20,Name = "Thailand",Population = 65926261 ,WorldPercentage = 0.93 },
                new Country { Id =21,Name = "France[9]",Population = 65684000 ,WorldPercentage = 0.92 },
                new Country { Id =22,Name = "United Kingdom",Population = 63181775 ,WorldPercentage = 0.89 },
                new Country { Id =23,Name = "Italy",Population = 59704082 ,WorldPercentage = 0.84 },
                new Country { Id =24,Name = "Myanmar",Population = 53259000 ,WorldPercentage = 0.75 },
                new Country { Id =25,Name = "South Africa",Population = 52981991 ,WorldPercentage = 0.75 },
                new Country { Id =26,Name = "South Korea",Population = 50219669 ,WorldPercentage = 0.71 },
                new Country { Id =27,Name = "Colombia",Population = 47159000 ,WorldPercentage = 0.66 },
                new Country { Id =28,Name = "Spain",Population = 47059533 ,WorldPercentage = 0.66 },
                new Country { Id =29,Name = "Ukraine",Population = 45495252 ,WorldPercentage = 0.64 },
                new Country { Id =30,Name = "Tanzania",Population = 44928923 ,WorldPercentage = 0.63 },
                new Country { Id =31,Name = "Kenya",Population = 44354000 ,WorldPercentage = 0.62 },
                new Country { Id =32,Name = "Argentina",Population = 40117096 ,WorldPercentage = 0.57 },
                new Country { Id =33,Name = "Poland",Population = 38533299 ,WorldPercentage = 0.54 },
                new Country { Id =34,Name = "Sudan",Population = 37964000 ,WorldPercentage = 0.53 },
                new Country { Id =35,Name = "Algeria",Population = 37900000 ,WorldPercentage = 0.53 },
                new Country { Id =36,Name = "Canada",Population = 35141542 ,WorldPercentage = 0.49 },
                new Country { Id =37,Name = "Uganda",Population = 34131400 ,WorldPercentage = 0.48 },
                new Country { Id =38,Name = "Iraq",Population = 33330000 ,WorldPercentage = 0.47 },
                new Country { Id =39,Name = "Morocco",Population = 32973200 ,WorldPercentage = 0.46 },
                new Country { Id =40,Name = "Peru",Population = 30475144 ,WorldPercentage = 0.43 },
                new Country { Id =41,Name = "Malaysia",Population = 29747000 ,WorldPercentage = 0.42 },
                new Country { Id =42,Name = "Uzbekistan",Population = 29559100 ,WorldPercentage = 0.42 },
                new Country { Id =43,Name = "Saudi Arabia",Population = 29195895 ,WorldPercentage = 0.41 },
                new Country { Id =44,Name = "Venezuela",Population = 28946101 ,WorldPercentage = 0.41 },
                new Country { Id =45,Name = "Nepal",Population = 26494504 ,WorldPercentage = 0.37 },
                new Country { Id =46,Name = "Afghanistan",Population = 25500100 ,WorldPercentage = 0.36 },
                new Country { Id =47,Name = "North Korea",Population = 24895000 ,WorldPercentage = 0.35 },
                new Country { Id =48,Name = "Ghana",Population = 24658823 ,WorldPercentage = 0.35 },
                new Country { Id =49,Name = "Yemen",Population = 24527000 ,WorldPercentage = 0.35 },
                new Country { Id =50,Name = "Mozambique",Population = 24366112 ,WorldPercentage = 0.34 },
                new Country { Id =51,Name = "Taiwan[10]",Population = 23340136 ,WorldPercentage = 0.33 },
                new Country { Id =52,Name = "Ivory Coast",Population = 23202000 ,WorldPercentage = 0.33 },
                new Country { Id =53,Name = "Australia",Population = 23096296 ,WorldPercentage = 0.33 },
                new Country { Id =54,Name = "Syria",Population = 21377000 ,WorldPercentage = 0.3 },
                new Country { Id =55,Name = "Madagascar",Population = 20696070 ,WorldPercentage = 0.29 },
                new Country { Id =56,Name = "Angola",Population = 20609294 ,WorldPercentage = 0.29 },
                new Country { Id =57,Name = "Cameroon",Population = 20386799 ,WorldPercentage = 0.29 },
                new Country { Id =58,Name = "Sri Lanka",Population = 20277597 ,WorldPercentage = 0.29 },
                new Country { Id =59,Name = "Romania",Population = 20121641 ,WorldPercentage = 0.28 },
                new Country { Id =60,Name = "Burkina Faso",Population = 17322796 ,WorldPercentage = 0.24 },
                new Country { Id =61,Name = "Niger",Population = 17129076 ,WorldPercentage = 0.24 },
                new Country { Id =62,Name = "Kazakhstan",Population = 17010000 ,WorldPercentage = 0.24 },
                new Country { Id =63,Name = "Netherlands",Population = 16793700 ,WorldPercentage = 0.24 },
                new Country { Id =64,Name = "Chile",Population = 16634603 ,WorldPercentage = 0.23 },
                new Country { Id =65,Name = "Ecuador",Population = 15535800 ,WorldPercentage = 0.22 },
                new Country { Id =66,Name = "Guatemala",Population = 15438384 ,WorldPercentage = 0.22 },
                new Country { Id =67,Name = "Mali",Population = 15302000 ,WorldPercentage = 0.22 },
                new Country { Id =68,Name = "Cambodia",Population = 15135000 ,WorldPercentage = 0.21 },
                new Country { Id =69,Name = "Malawi",Population = 14388600 ,WorldPercentage = 0.2 },
                new Country { Id =70,Name = "Senegal",Population = 13567338 ,WorldPercentage = 0.19 },
                new Country { Id =71,Name = "Zambia",Population = 13092666 ,WorldPercentage = 0.18 },
                new Country { Id =72,Name = "Zimbabwe",Population = 12973808 ,WorldPercentage = 0.18 },
                new Country { Id =73,Name = "Chad",Population = 12825000 ,WorldPercentage = 0.18 },
                new Country { Id =74,Name = "South Sudan",Population = 11296000 ,WorldPercentage = 0.16 },
                new Country { Id =75,Name = "Cuba",Population = 11163934 ,WorldPercentage = 0.16 },
                new Country { Id =76,Name = "Belgium",Population = 11156136 ,WorldPercentage = 0.16 },
                new Country { Id =77,Name = "Guinea",Population = 10824200 ,WorldPercentage = 0.15 },
                new Country { Id =78,Name = "Greece",Population = 10815197 ,WorldPercentage = 0.15 },
                new Country { Id =79,Name = "Tunisia",Population = 10777500 ,WorldPercentage = 0.15 },
                new Country { Id =80,Name = "Portugal",Population = 10562178 ,WorldPercentage = 0.15 },
                new Country { Id =81,Name = "Rwanda",Population = 10537222 ,WorldPercentage = 0.15 },
                new Country { Id =82,Name = "Czech Republic",Population = 10512800 ,WorldPercentage = 0.15 },
                new Country { Id =83,Name = "Somalia[11]",Population = 10496000 ,WorldPercentage = 0.15 },
                new Country { Id =84,Name = "Haiti",Population = 10413211 ,WorldPercentage = 0.15 },
                new Country { Id =85,Name = "Bolivia",Population = 10389913 ,WorldPercentage = 0.15 },
                new Country { Id =86,Name = "Benin",Population = 10323000 ,WorldPercentage = 0.15 },
                new Country { Id =87,Name = "Burundi",Population = 10163000 ,WorldPercentage = 0.14 },
                new Country { Id =88,Name = "Hungary",Population = 9906000 ,WorldPercentage = 0.14 },
                new Country { Id =89,Name = "Sweden",Population = 9588569 ,WorldPercentage = 0.14 },
                new Country { Id =90,Name = "Belarus",Population = 9458700 ,WorldPercentage = 0.13 },
                new Country { Id =91,Name = "Dominican Republic",Population = 9445281 ,WorldPercentage = 0.13 },
                new Country { Id =92,Name = "Azerbaijan",Population = 9235100 ,WorldPercentage = 0.13 },
                new Country { Id =93,Name = "Austria",Population = 8464554 ,WorldPercentage = 0.12 },
                new Country { Id =94,Name = "Honduras",Population = 8385072 ,WorldPercentage = 0.12 },
                new Country { Id =95,Name = "United Arab Emirates",Population = 8264070 ,WorldPercentage = 0.12 },
                new Country { Id =96,Name = "Switzerland",Population = 8058100 ,WorldPercentage = 0.11 },
                new Country { Id =97,Name = "Israel",Population = 8034900 ,WorldPercentage = 0.11 },
                new Country { Id =98,Name = "Tajikistan",Population = 8000000 ,WorldPercentage = 0.11 },
                new Country { Id =99,Name = "Bulgaria",Population = 7282041 ,WorldPercentage = 0.1 },
                new Country { Id =100,Name = "Serbia[12]",Population = 7241295 ,WorldPercentage = 0.1 },
                new Country { Id =101,Name = "Hong Kong (China)",Population = 7173900 ,WorldPercentage = 0.1 },
                new Country { Id =102,Name = "Papua New Guinea",Population = 7059653 ,WorldPercentage = 0.099 },
                new Country { Id =103,Name = "Paraguay",Population = 6672631 ,WorldPercentage = 0.094 },
                new Country { Id =104,Name = "Laos",Population = 6580800 ,WorldPercentage = 0.093 },
                new Country { Id =105,Name = "Jordan",Population = 6467500 ,WorldPercentage = 0.09 },
                new Country { Id =106,Name = "Eritrea",Population = 6333000 ,WorldPercentage = 0.089 },
                new Country { Id =107,Name = "Libya",Population = 6202000 ,WorldPercentage = 0.087 },
                new Country { Id =108,Name = "Togo",Population = 6191155 ,WorldPercentage = 0.087 },
                new Country { Id =109,Name = "El Salvador",Population = 6183000 ,WorldPercentage = 0.087 },
                new Country { Id =110,Name = "Sierra Leone",Population = 6092000 ,WorldPercentage = 0.086 },
                new Country { Id =111,Name = "Nicaragua",Population = 6071045 ,WorldPercentage = 0.086 },
                new Country { Id =112,Name = "Denmark",Population = 5605836 ,WorldPercentage = 0.079 },
                new Country { Id =113,Name = "Kyrgyzstan",Population = 5551900 ,WorldPercentage = 0.078 },
                new Country { Id =114,Name = "Finland",Population = 5434357 ,WorldPercentage = 0.077 },
                new Country { Id =115,Name = "Slovakia",Population = 5410728 ,WorldPercentage = 0.076 },
                new Country { Id =116,Name = "Singapore",Population = 5312400 ,WorldPercentage = 0.075 },
                new Country { Id =117,Name = "Turkmenistan",Population = 5240000 ,WorldPercentage = 0.074 },
                new Country { Id =118,Name = "Norway",Population = 5063709 ,WorldPercentage = 0.071 },
                new Country { Id =119,Name = "Lebanon",Population = 4822000 ,WorldPercentage = 0.068 },
                new Country { Id =120,Name = "Costa Rica",Population = 4667096 ,WorldPercentage = 0.066 },
                new Country { Id =121,Name = "Central African Republic",Population = 4616000 ,WorldPercentage = 0.065 },
                new Country { Id =122,Name = "Ireland",Population = 4585400 ,WorldPercentage = 0.065 },
                new Country { Id =123,Name = "Georgia[13]",Population = 4483800 ,WorldPercentage = 0.063 },
                new Country { Id =124,Name = "New Zealand",Population = 4471380 ,WorldPercentage = 0.063 },
                new Country { Id =125,Name = "Republic of the Congo",Population = 4448000 ,WorldPercentage = 0.063 },
                new Country { Id =126,Name = "Palestine[14]",Population = 4420549 ,WorldPercentage = 0.062 },
                new Country { Id =127,Name = "Liberia",Population = 4294000 ,WorldPercentage = 0.06 },
                new Country { Id =128,Name = "Croatia",Population = 4290612 ,WorldPercentage = 0.06 },
                new Country { Id =129,Name = "Bosnia and Herzegovina",Population = 3839737 ,WorldPercentage = 0.054 },
                new Country { Id =130,Name = "Oman",Population = 3831553 ,WorldPercentage = 0.054 },
                new Country { Id =131,Name = "Puerto Rico (USA)",Population = 3667084 ,WorldPercentage = 0.052 },
                new Country { Id =132,Name = "Kuwait",Population = 3582054 ,WorldPercentage = 0.05 },
                new Country { Id =133,Name = "Moldova[15]",Population = 3559500 ,WorldPercentage = 0.05 },
                new Country { Id =134,Name = "Mauritania",Population = 3461041 ,WorldPercentage = 0.049 },
                new Country { Id =135,Name = "Panama",Population = 3405813 ,WorldPercentage = 0.048 },
                new Country { Id =136,Name = "Uruguay",Population = 3286314 ,WorldPercentage = 0.046 },
                new Country { Id =137,Name = "Armenia",Population = 3031200 ,WorldPercentage = 0.043 },
                new Country { Id =138,Name = "Lithuania",Population = 2960733 ,WorldPercentage = 0.042 },
                new Country { Id =139,Name = "Albania",Population = 2821977 ,WorldPercentage = 0.04 },
                new Country { Id =140,Name = "Mongolia",Population = 2754685 ,WorldPercentage = 0.039 },
                new Country { Id =141,Name = "Jamaica",Population = 2711476 ,WorldPercentage = 0.038 },
                new Country { Id =142,Name = "Namibia",Population = 2113077 ,WorldPercentage = 0.03 },
                new Country { Id =143,Name = "Lesotho",Population = 2074000 ,WorldPercentage = 0.029 },
                new Country { Id =144,Name = "Slovenia",Population = 2060461 ,WorldPercentage = 0.029 },
                new Country { Id =145,Name = "Macedonia",Population = 2059794 ,WorldPercentage = 0.029 },
                new Country { Id =146,Name = "Botswana",Population = 2024904 ,WorldPercentage = 0.029 },
                new Country { Id =147,Name = "Latvia",Population = 2013400 ,WorldPercentage = 0.028 },
                new Country { Id =148,Name = "Qatar",Population = 1963124 ,WorldPercentage = 0.028 },
                new Country { Id =149,Name = "Gambia",Population = 1849000 ,WorldPercentage = 0.026 },
                new Country { Id =150,Name = "Guinea-Bissau",Population = 1704000 ,WorldPercentage = 0.024 },
                new Country { Id =151,Name = "Gabon",Population = 1672000 ,WorldPercentage = 0.024 },
                new Country { Id =152,Name = "Equatorial Guinea",Population = 1622000 ,WorldPercentage = 0.023 },
                new Country { Id =153,Name = "Trinidad and Tobago",Population = 1328019 ,WorldPercentage = 0.019 },
                new Country { Id =154,Name = "Estonia",Population = 1286540 ,WorldPercentage = 0.018 },
                new Country { Id =155,Name = "Mauritius",Population = 1257900 ,WorldPercentage = 0.018 },
                new Country { Id =156,Name = "Swaziland",Population = 1250000 ,WorldPercentage = 0.018 },
                new Country { Id =157,Name = "Bahrain",Population = 1234571 ,WorldPercentage = 0.017 },
                new Country { Id =158,Name = "Timor-Leste",Population = 1066409 ,WorldPercentage = 0.015 },
                new Country { Id =159,Name = "Djibouti",Population = 864618 ,WorldPercentage = 0.012 },
                new Country { Id =160,Name = "Cyprus[16]",Population = 862 ,WorldPercentage = 0.012 },
                new Country { Id =161,Name = "Fiji",Population = 858038 ,WorldPercentage = 0.012 },
                new Country { Id =162,Name = "Réunion (France)",Population = 821136 ,WorldPercentage = 0.012 },
                new Country { Id =163,Name = "Guyana",Population = 784894 ,WorldPercentage = 0.011 },
                new Country { Id =164,Name = "Bhutan",Population = 73674 ,WorldPercentage = 0.01 },
                new Country { Id =165,Name = "Comoros",Population = 7243 ,WorldPercentage = 0.01 },
                new Country { Id =166,Name = "Montenegro",Population = 620029 ,WorldPercentage = 0.0087 },
                new Country { Id =167,Name = "Macau (China)",Population = 582 ,WorldPercentage = 0.0082 },
                new Country { Id =168,Name = "Western Sahara[17]",Population = 567 ,WorldPercentage = 0.008 },
                new Country { Id =169,Name = "Solomon Islands",Population = 561 ,WorldPercentage = 0.0079 },
                new Country { Id =170,Name = "Luxembourg",Population = 537 ,WorldPercentage = 0.0076 },
                new Country { Id =171,Name = "Suri,Name",Population = 534189 ,WorldPercentage = 0.0075 },
                new Country { Id =172,Name = "Cape Verde",Population = 491875 ,WorldPercentage = 0.0069 },
                new Country { Id =173,Name = "Malta",Population = 416055 ,WorldPercentage = 0.0059 },
                new Country { Id =174,Name = "Guadeloupe (France)",Population = 403355 ,WorldPercentage = 0.0057 },
                new Country { Id =175,Name = "Martinique (France)",Population = 394173 ,WorldPercentage = 0.0056 },
                new Country { Id =176,Name = "Brunei",Population = 393162 ,WorldPercentage = 0.0055 },
                new Country { Id =177,Name = "Bahamas",Population = 351461 ,WorldPercentage = 0.0049 },
                new Country { Id =178,Name = "Iceland",Population = 32381 ,WorldPercentage = 0.0045 },
                new Country { Id =179,Name = "Maldives",Population = 31728 ,WorldPercentage = 0.0045 },
                new Country { Id =180,Name = "Belize",Population = 312971 ,WorldPercentage = 0.0044 },
                new Country { Id =181,Name = "Barbados",Population = 2742 ,WorldPercentage = 0.0039 },
                new Country { Id =182,Name = "French Polynesia (France)",Population = 26827 ,WorldPercentage = 0.0038 },
                new Country { Id =183,Name = "Vanuatu",Population = 258213 ,WorldPercentage = 0.0036 },
                new Country { Id =184,Name = "New Caledonia (France)",Population = 255651 ,WorldPercentage = 0.0036 },
                new Country { Id =185,Name = "French Guiana (France)",Population = 22904 ,WorldPercentage = 0.0032 },
                new Country { Id =186,Name = "Mayotte (France)",Population = 2126 ,WorldPercentage = 0.003 },
                new Country { Id =187,Name = "Samoa",Population = 18782 ,WorldPercentage = 0.0026 },
                new Country { Id =188,Name = "São Tomé and Príncipe",Population = 187356 ,WorldPercentage = 0.0026 },
                new Country { Id =189,Name = "Saint Lucia",Population = 166526 ,WorldPercentage = 0.0023 },
                new Country { Id =190,Name = "Guam (USA)",Population = 159358 ,WorldPercentage = 0.0022 },
                new Country { Id =191,Name = "Curaçao (Netherlands)",Population = 150563 ,WorldPercentage = 0.0021 },
                new Country { Id =192,Name = "Saint Vincent and the Grenadines",Population = 109 ,WorldPercentage = 0.0015 },
                new Country { Id =193,Name = "United States Virgin Islands (USA)",Population = 106405 ,WorldPercentage = 0.0015 },
                new Country { Id =194,Name = "Kiribati",Population = 104573 ,WorldPercentage = 0.0015 },
                new Country { Id =195,Name = "Grenada",Population = 103328 ,WorldPercentage = 0.0015 },
                new Country { Id =196,Name = "Tonga",Population = 103036 ,WorldPercentage = 0.0015 },
                new Country { Id =197,Name = "Federated States of Micronesia",Population = 101823 ,WorldPercentage = 0.0014 },
                new Country { Id =198,Name = "Aruba (Netherlands)",Population = 101484 ,WorldPercentage = 0.0014 },
                new Country { Id =199,Name = "Jersey (UK)",Population = 97857 ,WorldPercentage = 0.0014 },
                new Country { Id =200,Name = "Seychelles",Population = 90945 ,WorldPercentage = 0.0013 },
                new Country { Id =201,Name = "Antigua and Barbuda",Population = 86295 ,WorldPercentage = 0.0012 },
                new Country { Id =202,Name = "Isle of Man (UK)",Population = 84497 ,WorldPercentage = 0.0012 },
                new Country { Id =203,Name = "Andorra",Population = 76246 ,WorldPercentage = 0.0011 },
                new Country { Id =204,Name = "Dominica",Population = 71293 ,WorldPercentage = 0.001 },
                new Country { Id =205,Name = "Bermuda (UK)",Population = 64237 ,WorldPercentage = 0.0009 },
                new Country { Id =206,Name = "Guernsey (UK)",Population = 62431 ,WorldPercentage = 0.00088 },
                new Country { Id =207,Name = "Greenland (Denmark)",Population = 5637 ,WorldPercentage = 0.00079 },
                new Country { Id =208,Name = "Marshall Islands",Population = 55548 ,WorldPercentage = 0.00078 },
                new Country { Id =209,Name = "American Samoa (USA)",Population = 55519 ,WorldPercentage = 0.00078 },
                new Country { Id =210,Name = "Cayman Islands (UK)",Population = 55456 ,WorldPercentage = 0.00078 },
                new Country { Id =211,Name = "Saint Kitts and Nevis",Population = 54 ,WorldPercentage = 0.00076 },
                new Country { Id =212,Name = "Northern Mariana Islands (USA)",Population = 53883 ,WorldPercentage = 0.00076 },
                new Country { Id =213,Name = "Faroe Islands (Denmark)",Population = 48244 ,WorldPercentage = 0.00068 },
                new Country { Id =214,Name = "Sint Maarten (Netherlands)",Population = 37429 ,WorldPercentage = 0.00053 },
                new Country { Id =215,Name = "Saint Martin (France)",Population = 36979 ,WorldPercentage = 0.00052 },
                new Country { Id =216,Name = "Liechtenstein",Population = 36842 ,WorldPercentage = 0.00052 },
                new Country { Id =217,Name = "Monaco",Population = 36136 ,WorldPercentage = 0.00051 },
                new Country { Id =218,Name = "San Marino",Population = 32382 ,WorldPercentage = 0.00046 },
                new Country { Id =219,Name = "Turks and Caicos Islands (UK)",Population = 31458 ,WorldPercentage = 0.00044 },
                new Country { Id =220,Name = "Gibraltar (UK)",Population = 29752 ,WorldPercentage = 0.00042 },
                new Country { Id =221,Name = "British Virgin Islands (UK)",Population = 29537 ,WorldPercentage = 0.00042 },
                new Country { Id =222,Name = "Åland Islands (Finland)",Population = 28502 ,WorldPercentage = 0.0004 },
                new Country { Id =223,Name = "Caribbean Netherlands (Netherlands)",Population = 21133 ,WorldPercentage = 0.0003 },
                new Country { Id =224,Name = "Palau",Population = 2077 ,WorldPercentage = 0.00029 },
                new Country { Id =225,Name = "Cook Islands (NZ)",Population = 14974 ,WorldPercentage = 0.00021 },
                new Country { Id =226,Name = "Anguilla (UK)",Population = 13452 ,WorldPercentage = 0.00019 },
                new Country { Id =227,Name = "Wallis and Futuna (France)",Population = 13152 ,WorldPercentage = 0.00019 },
                new Country { Id =228,Name = "Tuvalu",Population = 11264 ,WorldPercentage = 0.00016 },
                new Country { Id =229,Name = "Nauru",Population = 9945 ,WorldPercentage = 0.00014 },
                new Country { Id =230,Name = "Saint Barthélemy (France)",Population = 8938 ,WorldPercentage = 0.00013 },
                new Country { Id =231,Name = "Saint Pierre and Miquelon (France)",Population = 6081 ,WorldPercentage = 0.00006 },
                new Country { Id =232,Name = "Montserrat (UK)",Population = 4922 ,WorldPercentage = 0.00005 },
                new Country { Id =233,Name = "Saint Helena Ascension and Tristan da Cunha (UK)",Population = 4 ,WorldPercentage = 0.00004 },
                new Country { Id =234,Name = "Svalbard and Jan Mayen (Norway)",Population = 2655 ,WorldPercentage = 0.00003 },
                new Country { Id =235,Name = "Falkland Islands (UK)",Population = 2563 ,WorldPercentage = 0.00003 },
                new Country { Id =236,Name = "Norfolk Island (Australia)",Population = 2302 ,WorldPercentage = 0.00002 },
                new Country { Id =237,Name = "Christmas Island (Australia)",Population = 2072 ,WorldPercentage = 0.00002 },
                new Country { Id =238,Name = "Niue (NZ)",Population = 1613 ,WorldPercentage = 0.00002 },
                new Country { Id =239,Name = "Tokelau (NZ)",Population = 1411 ,WorldPercentage = 0.00001 },
                new Country { Id =240,Name = " Vatican City",Population = 800 ,WorldPercentage = 0.00001 },
                new Country { Id =241,Name = "Cocos (Keeling) Islands (Australia)",Population = 550 ,WorldPercentage = 0.000001 },
                new Country { Id =242,Name = "Pitcairn Islands (UK)",Population = 66 ,WorldPercentage = 0.0000000 }
            };

        }

    }

}

As you can see, we must tag the service with the ScriptService and tag the WebMethod with the ScriptMethod attribute, specifying the JSON responseformat. We also need to add some to our web.config:

<?xml version="1.0"?>

<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->

<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />

    <webServices>
      <protocols>
        <add name="HttpGet" />
        <add name="HttpPost"/>
      </protocols>
    </webServices>

  </system.web>

</configuration>


We also need some GUI here to test out the TypeAhead control:


<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head<
    <title></title>
    <link href="Content/bootstrap.css" rel="stylesheet" />
    <link href="Content/bootstrap-responsive.css" rel="stylesheet" />
</head>
<body>

    <div class="row">

    <div class="span12">
    <input type="text" id="tbCountries" /> 
   

    </div class="row">
   
    <script src="Scripts/jquery-2.0.3.js"></script>
    <script src="Scripts/bootstrap.js"></script>
    <script src="Scripts/underscore.js"></script>

    <script type="text/javascript">

        $(function () {
         

            var countries;

            $("#tbCountries").typeahead({
                source: function (query, process) {
                    return $.ajax({
                        type: "POST",
                        url: 'http://localhost:52523/CountryService.asmx/GetCountries',
                        data: JSON.stringify({query: query }),
                        contentType: "application/json; charset=utf-8",
                        dataType: "json",
                        success:
                        function (data) {
                            countries = data.d; 
                            var countryIds = _.map(countries, function(c){
                                return c.Id; 
                            }); 
                            return process(countryIds);
                        }
                    });
                },
                matcher: function (item) {
                    return true;
                },
                sorter: function (items) {
                    return items;
                },
                highlighter: function (id) {
                    var country = _.find(countries, function (c) {
                        return c.Id == id;
                    });
                    return country.Name + " Population: " + country.Population; 
                },
                updater: function (id) {
                    var country = _.find(countries, function (c) {
                        return c.Id == id;
                    });
                    return country.Name;
                }
            });

        });

    </script>
</body>
</html>


To sum up, to make the TypeAhead control work in my example, I had to implement stub implementations of matcher, sorter, highlighter (must be implemented such that the custom Country object instances return in JSON object is visible, this is a display template in other words) and updater (will update the text field with the autocomplete / TypeAhead feature). Source also sets a global variable countries here for simplicity, and continues processing the ids, you can see that we use Underscore.Js here for the array processing. The highlighter and updater method uses the find method of Underscore.js to find the relevant country. The end result is a functioning auto complete example for the Bootstrap TypeAhead control that uses a remote JSON service. I am not sure why the data.d variable was returned from the service. I did not specify "data.d" anywhere in my code, but this is where I could find the returned JSON data. It is not very elegant to set the countries to a global variable, we could of course encapsulate this better using a "provider" to encapsulate the data for encapsulation and consistency protection. But the key point of this article was to help others getting started with the TypeAhead control in Bootstrap with remote JSON calls returning objects with properties and not just simple string arrays. I find the Bootstrap documentation from Twitter as good, but not exemplary. It is sometimes hard to find good programming examples on the different controls. The end result is an autocomplete control that looks nice and can display compound data:

Wednesday, 24 July 2013

Structuring your Javascript code - Revealing prototype pattern and namespaces

Creating Spaghetti-code in Javascript is not difficult. There are no clear pattern in Javascript for encapsulation for novice developers of Javascript, unless you really are familiar with what is possible with Javascript. Most developers learn Javascript in an ad-hoc manner, just adding functions to a Javascript file and keep on adding code to their monolithic design until they realize that a pattern must be used to structure the Javascript code. Of course, today, with the huge focus on Javascript, there are multiple patterns to follow and coding Javascript is a more mature topic that it used to be. There are four designs which we can choose when we structure our code (possible more):
  • Module pattern
  • Prototype pattern
  • Revealing module pattern
  • Revealing prototype pattern
We will in this article focus on the last, the revealing prototype pattern. This pattern allows refactoring the Javascript code, i.e. extensibility, and it also offers encapsulation. It is the most flexible and still rigid solution. It is not difficult to understand either, once you see an example which I will present next. In our example, we use LocalStorage, which is allows saving data on the client side through the browser. It is quicker and safer than ordinary cookies. We will encapsulate the logic of saving to the LocalStorage with a simple class-like structure in Javascript. Of course, I could have used TypeScript here and just create a class, but this article is more focused about explaining the concepts so you can recognize this pattern when you read Javascript code later on that uses this pattern and understand what is going on. Let's look at the Javascript class following the revealing prototype pattern next:

var AppUtils = AppUtils || {}; 

AppUtils.LocalStorageUtility = function(storageKeys, sourceElements, feedbackElement) {
    this.storageKeys = storageKeys;
    this.sourceElements = sourceElements; 
    this.feedbackElement = feedbackElement;
};

AppUtils.LocalStorageUtility.prototype = (function () {

        var storeSettings = function (thisObj) {
            for (var i = 0; i < thisObj.storageKeys.length; i++) {
                var storageKey = thisObj.storageKeys[i]; 
                var elementName = thisObj.sourceElements[i];
                localStorage.setItem(storageKey, $("#" + elementName).val());  
            }
            $("#" + thisObj.feedbackElement).text("Local setting saved!");
        },
        loadSettings = function (thisObj) {

            for (var i = 0; i < thisObj.storageKeys.length; i++) {
                var storageKey = thisObj.storageKeys[i]; 
                var elementName = thisObj.sourceElements[i];
                $("#" + elementName).val(localStorage.getItem(storageKey)); 
            }

            $("#" + thisObj.feedbackElement).text("Local setting loaded!");
        },
        clearSettings = function () {
            localStorage.clear(); 
        },
         hasLocalStorage = function () {
            return typeof (Storage) !== "undefined"; 
        }

        return {
            StoreSettings: storeSettings,
            LoadSettings: loadSettings,
            ClearSettings: clearSettings,
            HasLocalStorage: hasLocalStorage
        };

})(); 

The consumer of this class-like structure in Javascript will provide two string arrays which is the local storage key names and the elements to read and store local storage values from. I do not do any checking here that the two arrays are the same length or that they are of the correct type (arrays preferably with string of course). This is what TypeScript version of this code would specify. Remember, TypeScript is much easier to use than manually creating class like structs like we do here, but the concepts are anyways important to understand. In the revealing prototype pattern we return an object literal and the public part is exposed here, i.e. the revealing prototype. In addition, we call it revealing because the prototype is self invoking. The private parts are what is outside of the return statement above. We point to the private methods, and this is fine, and we alias them to a capitalized version to let the user call into the private methods. If we want public fields We also need a GUI for the Javascript code here:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Localstorage demo</title>
</head>

<script src="Scripts/jquery-2.0.3.min.js"></script>
    <script src="Scripts/LocalStorageUtility.js"></script>
    
    <script type="text/javascript">

        $(document).ready(function () {
            var ls = new AppUtils.LocalStorageUtility(["Name", "City"], ["tbName", "tbCity"], "lblFeedback"); 

            if (ls.HasLocalStorage) {
                $("#lblFeedback").text("Local storage is enabled!"); 
            }
            else {
                $("#lblFeedback").text("Local storage is disabled!"); 
            }

            $("#btnLoadSettings").click(function () { ls.LoadSettings(ls); }); 
            $("#btnSaveSettings").click(function () { ls.StoreSettings(ls); }); 
            $("#btnClearSettings").click(ls.ClearSettings); 
        }); 

    </script>

<body>

    <table>
        <tr>
            <td>Name</td><td><input id="tbName" type="text" /></td>
            <td>City</td><td><input id="tbCity" type="text" /></td>
        </tr>
    </table>
  
    <button id="btnLoadSettings" type="button">Load settings</button>

    <button id="btnSaveSettings" type="button">Save settings</button>

    <button id="btnClearSettings" type="button">Clear settings</button>    

    <p id="lblFeedback" style="color: darkgreen">ready</p>

</body>
</html>


As you can see, with this pattern we must keep track of the this pointer in the prototype definition. There are alternatives, such as bind, and call, but I wanted to keep the example clear. In the jQuery load method above we define click handlers that pass in the instance of the LocalStorageUtility, which we have instantiated, such that the prototype gets the correct instance. As you perhaps know, the prototype must bind to the correct this pointer, and here we explicitly pass it in (i.e. the instance of LocalStorageUtility). Finally, note that we define a namespace here with the "or empty object literal" technique. This allows us to hide the entire class from the global namespace and improve or encapsulation even more. If you have comments to the code above, please let me know. I have worked some with Javascript of course, but I am still learning a lot about it.