Thursday, 25 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.

Friday, 19 July 2013

Presentation of Underscore.Js

Underscore.Js library presentation

I am working with a presentation of Underscore.Js Javascript library. The powerpoint file (.pptx) is available for download here:

Underscore.Js presentation [PPTX]



Underscore.Js presentation [PDF]

Created with Powerpoint 2013.

Wednesday, 17 July 2013

Using TypeScript to implement OOP inherited Loggers

TypeScript is a framework for creating compile type checking and simplifying OOP (Object Oriented Programming) in Javascript. It is a framework available on Codeplex and its primary initiator is Microsoft. The source code is freely available at the following Git url: https://git01.codeplex.com/typescript

Note, by using Git ,it did take some time to download the entire source code, as there are many files. I would like to have the Codeplex Projects available instead for Mercurial / Hg. The website of TypeScript is available here:
http://www.typescriptlang.org/

Note - there is also a video from Channel9 MSDN, where Anders Hejlsberg presents TypeScript: One of the employees from Microsoft involved in TypeScript then is Anders Hejlsberg. He is actually the core Developer. He is one of Microsoft best scientists/developers and he has also been very much involved in C# and LINQ. It is promising for TypeScript that such a well-known core developer is on the team. TypeScript offers a compiler, tsc (TypeScript compiler), that converts typescript files (.ts) to JavaScript files (.js).

TypeScript is basically a code generation language for Javascript that will make it easier to create and scale Object Oriented Javascript applications. But it is also more than that. It is an entire new way of developing Javascript that will let more developers with traditional C# background and application developers in general be able to create Javascript code with basic understanding in Javascript, but still let them write structured, object oriented Javascript. Many developers know enough Javascript to implement basic functionality, but to support Object Orientation via prototypes is a less common skill. Now, with the help of TypeScript, you can write code that looks a bit like C#, but generates the necessary Javascript to allow OOP paradigm and structuring of your code. You can also create modules, which are similar to namespaces, which are either external or internal modules. For exernal modules, RequireJs is often used. In this example we will use the simpler internal modules to create a simple set of loggers to be used in the web client with the help of TypeScript. Before I present the code, you can download a TypeScript Visual Studio 2012 plugin from here:

TypeScript Visual Studio 2012 plugin

Please note, there are more editors capable of editing typescript and generating Javascript, such as Sublime Text. There is though little doubt that the best editor for TypeScript files (.ts) is Visual Studio, preferably the 2012 Version. The files you reference in the html files will still be Javascript, of course. TypeScript will usually generate a .js file, a js.min file and a source map with the extension js.map, which is needed for debugging your TypeScript code.

To generate source map files, download the Web Essentials plugin for Visual Studio from here:

Visual Studio Web Essentials Extensions for VS2012

You can also download this extension from Tools-Extensions in Visual Studio. After you have installed the Web Essentials Extensions, go to the option Options-Web Essentials-TypeScript. Activate the options Generate Source Map, Show Preview Window, Minify Generated Javascript, Compile TypeScript on Save and Add Generated files to Project. You obviously want to check in all four file types into your Version Control System (VCS), the .ts, .js, .js.min and .js.map files. Note that editing the .ts file in Visual Studio will generate the .js, js.min and .js.map files. The .js file will be shown in a preview window. Experienced Javascript programmers that are comfortable with prototypes in Javascript will note how TypeScript generates closures, iffys (immediately executing functions) and use prototypes to support OOP. The problem is that the syntax does not scale well with deep inheritance and OOP in Javascript. TypeScript converts your TypeScript code to Javascript and aids you in realising OOP based Javascript and also provided compile type checking. Let's first generate a TypeScript Application. Note that you can add TypeScript to an existing MVC Application for example. It is easiest to begin with the project solution for TypeScript for initial testing, HTML Application With TypeScript:
Note that TypeScript do work with ordinary Web Projects also such as MVC Applications where the Views and Scripts resides (GUI Project in your solution).

You can add a new TypeScript file explicitly:

Check that your options for Web Essentials are correct before you continue:

I also add the following NuGet packages in this sample: jquery, jquery.ui.combined and toastr with the Install-Package command in Package Manager Console in VS2012. It is not uncommon to add RequireJs also if you will create external modules with TypeScript. Here, we will create a simple external module.

Let's look at a TypeScript file with Loggers next:
/// <reference path="TypeDefinitions/jqueryui.d.ts" />
/// <reference path="TypeDefinitions/jquery.d.ts" />
/// <reference path="TypeDefinitions/toastr.d.ts" />

// Module App.Util.Loggers 
module App.Util.Loggers {

    export enum LogLevel {
        Information  = 0,
        Warning = 1,
        Error = 2,    
    }

    // Interface ILogger
    export interface ILogger {
        Log(message: string): void;
        LogLevel: LogLevel; 
    }

    export class BaseLogger {

        private logLevel : LogLevel;

        constructor(logLevel: LogLevel) {
            this.LogLevel = logLevel;
        }
        
        get LogLevel() {
            return this.logLevel; 
        } 

        set LogLevel(logLevel) {
            if (this.logLevel != logLevel) {
                this.logLevel = logLevel; 
            }
        }

    }

    // Class DialogLogger
    export class DialogLogger extends BaseLogger implements ILogger {

        private anchorId: string; 
        private modal : bool;

        constructor(logLevel: LogLevel, anchorId : string, modal? : bool) {
            super(logLevel); 
            this.AnchorId = anchorId; 
            this.Modal = modal; 
        }

        public Log(message: string): void {

            var dialog = $("#" + this.AnchorId).dialog({
                title: message,
                buttons: {
                    Ok: function () {
                        $(this).dialog("close");
                    }
                },
                modal: this.Modal
            });                 

            switch (this.LogLevel) {
            
                case LogLevel.Information:
                    dialog.parent().addClass("ui-icon-information");       
                    break;

                case LogLevel.Error:               
                    dialog.parent().addClass("ui-state-error");       
                    break;

                case LogLevel.Warning:
                    dialog.parent().addClass("ui-state-warning");       
                    break;

            }
          
        }

        get AnchorId() {
            return this.anchorId;
        }

        set AnchorId(anchorId: string) {
            if (this.anchorId != anchorId) {
                this.anchorId = anchorId; 
            }
        }

        get Modal(){
            return this.modal; 
        }

        set Modal(modal : bool) {
            if (this.modal != modal) {
                this.modal = modal; 
            }
        }

    }

    //Class ToasterLogger
    export class ToasterLogger extends BaseLogger implements ILogger {

        constructor(logLevel: LogLevel) {
            super(logLevel);
        }

        public Log(message: string): void {

            switch (this.LogLevel) {

                case LogLevel.Information:
                    toastr.info(message);
                    break;

                case LogLevel.Error:
                    toastr.error(message); 
                    break;

                case LogLevel.Warning:
                    toastr.warning(message); 
                    break;

            }
        
        }    
    }

    //Class ConsoleLogger
    export class ConsoleLogger extends BaseLogger implements ILogger {

        constructor(logLevel: LogLevel) {
            super(logLevel); 
        }

        public Log(message: string): void {
            switch (this.LogLevel) {

                case LogLevel.Information:
                    console.info(message);
                    break;

                case LogLevel.Error:
                    console.error(message);
                    break;

                case LogLevel.Warning:
                    console.warn(message);
                    break;

            }
        }
    }


}


The Javascript generated from the Logger.ts TypeScript file is:
var __extends = this.__extends || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    __.prototype = b.prototype;
    d.prototype = new __();
};
var App;
(function (App) {
    (function (Util) {
        /// 
        /// 
        /// 
        // Module App.Util.Loggers
        (function (Loggers) {
            (function (LogLevel) {
                LogLevel[LogLevel["Information"] = 0] = "Information";
                LogLevel[LogLevel["Warning"] = 1] = "Warning";
                LogLevel[LogLevel["Error"] = 2] = "Error";
            })(Loggers.LogLevel || (Loggers.LogLevel = {}));
            var LogLevel = Loggers.LogLevel;

            var BaseLogger = (function () {
                function BaseLogger(logLevel) {
                    this.LogLevel = logLevel;
                }
                Object.defineProperty(BaseLogger.prototype, "LogLevel", {
                    get: function () {
                        return this.logLevel;
                    },
                    set: function (logLevel) {
                        if (this.logLevel != logLevel) {
                            this.logLevel = logLevel;
                        }
                    },
                    enumerable: true,
                    configurable: true
                });

                return BaseLogger;
            })();
            Loggers.BaseLogger = BaseLogger;

            // Class DialogLogger
            var DialogLogger = (function (_super) {
                __extends(DialogLogger, _super);
                function DialogLogger(logLevel, anchorId, modal) {
                    _super.call(this, logLevel);
                    this.AnchorId = anchorId;
                    this.Modal = modal;
                }
                DialogLogger.prototype.Log = function (message) {
                    var dialog = $("#" + this.AnchorId).dialog({
                        title: message,
                        buttons: {
                            Ok: function () {
                                $(this).dialog("close");
                            }
                        },
                        modal: this.Modal
                    });

                    switch (this.LogLevel) {
                        case LogLevel.Information:
                            dialog.parent().addClass("ui-icon-information");
                            break;

                        case LogLevel.Error:
                            dialog.parent().addClass("ui-state-error");
                            break;

                        case LogLevel.Warning:
                            dialog.parent().addClass("ui-state-warning");
                            break;
                    }
                };

                Object.defineProperty(DialogLogger.prototype, "AnchorId", {
                    get: function () {
                        return this.anchorId;
                    },
                    set: function (anchorId) {
                        if (this.anchorId != anchorId) {
                            this.anchorId = anchorId;
                        }
                    },
                    enumerable: true,
                    configurable: true
                });


                Object.defineProperty(DialogLogger.prototype, "Modal", {
                    get: function () {
                        return this.modal;
                    },
                    set: function (modal) {
                        if (this.modal != modal) {
                            this.modal = modal;
                        }
                    },
                    enumerable: true,
                    configurable: true
                });

                return DialogLogger;
            })(BaseLogger);
            Loggers.DialogLogger = DialogLogger;

            //Class ToasterLogger
            var ToasterLogger = (function (_super) {
                __extends(ToasterLogger, _super);
                function ToasterLogger(logLevel) {
                    _super.call(this, logLevel);
                }
                ToasterLogger.prototype.Log = function (message) {
                    switch (this.LogLevel) {
                        case LogLevel.Information:
                            toastr.info(message);
                            break;

                        case LogLevel.Error:
                            toastr.error(message);
                            break;

                        case LogLevel.Warning:
                            toastr.warning(message);
                            break;
                    }
                };
                return ToasterLogger;
            })(BaseLogger);
            Loggers.ToasterLogger = ToasterLogger;

            //Class ConsoleLogger
            var ConsoleLogger = (function (_super) {
                __extends(ConsoleLogger, _super);
                function ConsoleLogger(logLevel) {
                    _super.call(this, logLevel);
                }
                ConsoleLogger.prototype.Log = function (message) {
                    switch (this.LogLevel) {
                        case LogLevel.Information:
                            console.info(message);
                            break;

                        case LogLevel.Error:
                            console.error(message);
                            break;

                        case LogLevel.Warning:
                            console.warn(message);
                            break;
                    }
                };
                return ConsoleLogger;
            })(BaseLogger);
            Loggers.ConsoleLogger = ConsoleLogger;
        })(Util.Loggers || (Util.Loggers = {}));
        var Loggers = Util.Loggers;
    })(App.Util || (App.Util = {}));
    var Util = App.Util;
})(App || (App = {}));
//@ sourceMappingURL=Logger.js.map

As noted before, TypeScript generates this Javascript file and can also create a minified Version, in addition a sourceMappingURL is also set to give the Javascript file(s) a source map, so we can debug the TypeScript file. You edit the .ts file and edit the .ts file. TypeScript in other ways lets you work in a higher level Language than Javascript to let you work with more familiar code and also adds compilation type level safety and checking. Whenever you edit and save the .ts file, the generation of a .js, js.min and js.map files are done. Note that this can take some seconds when you work in the IDE. I expect future versions of TypeScript to have a quicker compiler. I have used TypeScript 0.9.1 in this example.

I test out the Logger.ts TypeScript file above with the following HTML:
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>TypeScript HTML App</title>
    <link rel="stylesheet" href="app.css" type="text/css" />
    <script src="app.js"></script>
    <link href="content/toastr.min.css" rel="stylesheet" />
    <script src="Scripts/jquery-1.6.4.min.js"></script>
    <script src="Scripts/jquery-ui-1.10.3.min.js"></script>
    <link href="content/themes/base/jquery.ui.all.css" rel="stylesheet" />
    <script src="Scripts/toastr.min.js"></script>
    <script src="Scripts/Logger.js"></script>
</head>
<body>
    <h1>TypeScript Logging demo</h1>

    <script type="text/javascript">

        function consoleLogger() {
            var logger = new App.Util.Loggers.ConsoleLogger(App.Util.Loggers.LogLevel.Warning);
            logger.Log("Hello from console logger!"); 
        }

        function toastrLogger() {
            var logger = new App.Util.Loggers.ToasterLogger(App.Util.Loggers.LogLevel.Information);
            logger.Log("Hello from toastr logger!");
        }

        function dialogLogger() {
            var logger = new App.Util.Loggers.DialogLogger(App.Util.Loggers.LogLevel.Error, "dialog-message", true);
            logger.Log("Hello from dialog logger!");
        }

    </script>

    <div id="content">

        <input type="button" value="Test console logger" onclick="consoleLogger()" />

        <input type="button" value="Test toastr logger" onclick="toastrLogger()" />

        <input type="button" value="Test dialog logger" onclick="dialogLogger()" />

        <div id="dialog-message" style="display: none">
            <p>This dialog was launched by App.Util.Loggers.DialogLogger!</p>
        </div>

    </div>

</body>
</html>
As you can see from the TypeScript code above, we generated Javascript where one BaseLogger class was inherited three times. We used the high level constructs of TypeScript such as:
  • class - Ability to defined a class/closure,container in TypeScript
  • module - Ability to create a module (internal in this case) that can assemble a collection of classes, Interfaces, Methods/functions in a namespace like collection or closure/container. Note I set the module name and namespace App.Util.Logging.
  • export - Here we define the visible members of our module
  • Interface - Interfaces or code contracts are a new concept in TypeScript - for Javascript that is.
  • enums - Enums are supported in TypeScript!
  • implements - keyword to implement an Interface
  • constructor - constructors are supported in TypeScript (you can have a multiple overloads)
  • extends - defines which baseclass to inherit
  • super - calling the base class constructor - this is required for derived classes in TypeScript.


We also use some features in TypeScript here:

  • Access level identifiers - we can use public, private - even static
  • Strong typing - we define a function parameter to be for example of type string by suffixing the data type with a colon in between. This is removed from the generated Javascript, but it gives you compile time type checking and safety previously not available in Javascript except another Third party framework
  • Compile time error checking - When you type errors in the .ts file, the errors are actually listed in the Error List in Visual Studio.


The code above also uses TypeScript Type definition files, with the extension .d.ts. You can see that I have put a reference at the top of the TypeScript file. For internal modules, you use this technique. For external modules, you can use RequireJs to avoid adding the script references in the HTML file, with the AMD loader.

Boris Yankov has collected a large collection of d.ts files here:
https://github.com/borisyankov/DefinitelyTyped These are required files, such that TypeScript can understand and consume Third party Libraries within the Typescript .ts file. A positive side effect of this is compile level syntax checking and IntelliSense. Using Third party libraries for Javascript such as Jquery, KnockoutJs, Toastr and Jquery UI (plus a ton of others) have never been more easy to work with. We all know the difficulties around writing correct Javascript with correct casing, well now TypeScript will throw in a compiler clever enough to check that you write correct TypeScript and consume Third party Libraries correct for you. This will make an impact on how we code Javascript.

Does this mean that Javascript will be "hidden" under the covers of TypeScript? No, definitely a solid background in Javascript is always positive when it comes to web Client Development. But TypeScript will enable developers not conformtable with Javascript to write more Javascript. And in the end, this will help more developers create better and more scalable Javascript Applications. There is no need to write pure Javascript always, when TypeScript can let you structure your code in a more natural manner with traditional OOP Language. This will help developers becoming more productive, deliver functionality quicker and help the industry shift a little bit more towards Javascript. Douglas Crockford has often ridiculed parts of Javascript as being a bunch of bologny, but there are also The Good Parts. I will actually say that TypeScript should be such a good part. It is of course important that the Javascript generated from the TypeScript is correct. With Anders Hejlsberg at the core team, we have no reason to speculate that TypeScript is really a promising Javascript framework, coming soon to a developer box near you.

Note: I have also used EcmaScript 5 Properties in the sample above. IE 8 should support ES5, and newer browsers too. To support Ecmascript 5, edit the Project file and make sure you add ES5 as the version:

<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
    <TypeScriptTarget>ES5</TypeScriptTarget>
    <TypeScriptIncludeComments>true</TypeScriptIncludeComments>
    <TypeScriptSourceMap>true</TypeScriptSourceMap>
    <TypeScriptModuleKind>AMD</TypeScriptModuleKind>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)' == 'Release'">
    <TypeScriptTarget>ES5</TypeScriptTarget>
    <TypeScriptIncludeComments>false</TypeScriptIncludeComments>
    <TypeScriptSourceMap>false</TypeScriptSourceMap>
    <TypeScriptModuleKind>AMD</TypeScriptModuleKind>
  </PropertyGroup>

Note in the Project file above, I set the TypeScriptTarget to ES5, to support Ecmascript 5 properties. I think that one must also use Properties in TypeScript if your target audience can use it. It is, after all a more correct way of doing OOP.

I have worked some with Javascript, but often I work individually on projects with Javascript. Collaborating and sharing Javascript is often hard to do. The structured code of TypeScript will make it easier for more developers and large part of entire teams to work with and enhance Javascript frameworks. More work will be done on the clients and richer user experiences will be the result (RIAs). This was just an introduction to TypeScript, but I find it a very promising technology. If you want to play around with the source code above, I have packaged the solution in a ZIP file here:
TypeScriptLogger.zip

Happy coding with TypeScript! I suggest you watch the video where Anders Hejlsberg explains TypeScript on the TypeScript website. If you have access to Pluralsight, there is a 4 1/2 hour long video covering TypeScript fundamentals in quite a complete manner. I have watched this entire course and it is quite good.

Saturday, 13 July 2013

MEF and Memory in MEF 2

MEF - or Managed Extensibility Framework - is Microsoft's official Inversion Of Control (IoC) and Dependency Injection (DI) framework. Although MEF is not a true DI container, it has a lot of features compared to many other IoC frameworks. However, MEF is often measured as a slow IoC framework in tests and therefore is not very tempting to use. MEF on the other side is readily available in .NET. If you use .NET 4.5, you will have access to MEF 2 features such as ExportFactory. The worst feature in MEF is arguable the way the IoC container disposes objects. If you have a nonshared Part or instance (i.e not a singleton, but a part you can instantiate multiple instances of), and this instance type implements IDisposable, most likely this object will not be disposed until the container is teared down. This is not a desired feature, as object instances will accumulate and memory consumption of your application using MEF will go up. With the aid of ExportFactory, we can be able to free up object instances on demand. Let's use the ServiceLocator to get our instances from ExportFactory. When you use ExportFactory, you call CreateExport(), which returns an ExportLifetimeContext<T>. This object can then be used to dispose the object. By disposing the ExportLifetimeContext<T>, the object that was created with the ExportFactory is truly disposed and released at the same time from the IoC, finally making it possible to gather with Garbage Collection (GC), in the end freeing up memory. The code for retrieving the object instance via an ExportFactory can look like this:

    [Export]
    public class ExportFactoryProvider<T>
    {

        [Import]
        public ExportFactory<T> Factory { get; set; }

    }

Let's test out this ExportFactoryProvider with service locator. First let's create a part we can test. Let's say we got a simple view model:

    [Export(typeof(ITestViewModel))] 
    public class TestViewModel : ITestViewModel, IDisposable
    {

        public void SayHello(string message)
        {
            Debug.WriteLine(string.Format("Hi {0}!", message));
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this); 
        }

        private void Dispose(bool p)
        {
            Console.WriteLine("Disposed TestViewModel");
        }

    }

We can then create an instance of the TestViewModel using ServiceLocator using this:

            var serviceFactory = ServiceLocator.Current.GetInstance<ExportFactoryProvider<ITestViewModel>>().Factory;

            var testViewModelExport = serviceFactory.CreateExport(); 
            MessageBox.Show("About to dispose an instance of ITestViewModel: ");

            testViewModelExport.Value.SayHello("Johnny");
            testViewModelExport.Dispose();

As you can see above, we must first use the ExportFactoryProvider, then call CreateExport, then use the Value property and then use the ExportLifetimeContext<T> to control the disposing of the object from the IoC container. But as you can see, we must do some juggling to keep a reference to the lifetime context and the instance itself. These two objects are inherently tied together. Let's create a generic class to keep them together.

    [Export]
    [PartCreationPolicy(System.ComponentModel.Composition.CreationPolicy.NonShared)]
    public class ExportFactoryInstantiator<T> : IPartImportsSatisfiedNotification
    {

        [Import]
        public ExportFactory<T> Factory { get; set; }

        public T Instance { get; private set; }

        private ExportLifetimeContext<T> lifeTime;

        public void OnImportsSatisfied()
        {
            lifeTime = Factory.CreateExport();
            Instance = lifeTime.Value;
        }

        public bool DisposeOnDemand()
        {
            if (lifeTime == null)
                return false;
            lifeTime.Dispose();
            return Instance == null; 
        }

    }

You can use the class above, ExportFactoryInstantiator to create NonShared parts (object instances) via ExportFactory and via the instance created access the Instance property to get to the actual object and use the method DisposeOnDemand, which returns true if the object was set to null during the execution of this method. You can also see that we use IPartImportsSatisfiedNotification to await the resolution.

            var anotherTestViewModel = ServiceLocator.Current.GetInstance<ExportFactoryInstantiator<ITestViewModel>>();
            anotherTestViewModel.Instance.SayHello("Bob");
            anotherTestViewModel.DisposeOnDemand(); 
Note that the instance now can be easily worked against. However, still it is a bit verbose, we can create a static method to help us with that:

        public static ExportFactoryInstantiator<T> ResolveViaExportFactory<T>()
        {
            return ServiceLocator.Current.GetInstance<ExportFactoryInstantiator<T>>();
        }

We have finally reduced the complexity of being able to dispose MEF parts on demand in our applications:

            var yetAnotherTestViewModel = ResolveViaExportFactory<ITestViewModel>();
            yetAnotherTestViewModel.Instance.SayHello("Joey");
            yetAnotherTestViewModel.DisposeOnDemand(); 

To sum up, MEF 2 provides a feature where we can now dispose parts from the container on demand. This is a great improvement and will reduce the issues around memory leaks substantially. If you have complex viewmodels, disposing a view model high up in the object graph will usually dispose all child view models, properties and so on and reduce memory usage. But always be careful when disposing objects from your container. You must be sure that the object will not be useful anymore. In this StackOverflow thread you can also see an example of how to use a Part only when you need it, inside a using block: Nicholas Blumhardt on using ServiceLocator and ExportFactory As you can see, I use Nicholas Blumhardt's code as a starting point and refine it a bit to reduce complexity and tie together the Instance and provide DisponseOnDemand method. You must yourself judge when to release a part, be it a view model or some other resource. The using block are handy when you are sure you do not need the instance more than temporarily. You should consider doing that instead of importing many parts to your view model. Yes, the object creation of your view models will be quicker and yes, the execution will in fact be slower because of the need to create imported parts on demand via ServiceLocator, but at the same time, your application will keep a very low memory profile. I haven't had the opportunity to test out the code above yet against production code, so do not use it in production without verifying its validity in for example Red Gate ANTS Memory Profiler and testing it. I would like to hear from other developers using MEF and have experienced its memory usage woes for more views about MEF. If you have also tips against efficiency and performance of MEF containers, please let us other MEF developers know.