Colourblind

Welcome to Colourblind.

This is the personal web space of Tom Milsom. As much as possible everything is free (as in speech and as in beer).


Make text: Smaller Bigger

Client-side templating with Handlebars.js

Posted by Tom on 30/11/2011 22:43:38

So back to data molestation in Javascript.

Now we have our post-processed data it's time to display it to the user.

Templating in Javascript seems to be in vogue at the moment, so there's a pretty decent selection to choose from. Originally I was using jquery-tmpl, but since that project got abandoned in favour of something more awesomer it was time to have a look at alternatives. I initially fell upon Mustache (to be honest, mainly because of the name) but founds myself butting up against the stringently 'logic-less' approach. I guess if you are stricter with your view-models it's less of an issue, but I just found it a little restrictive. Handlebars is an evolution of Mustache from Yehuda Katz (of Rails fame), and appears to scratch the itches that Mustache leaves me with in addition to being a superset of the Mustache syntax. Also, the name is still cool.

A quick note, before we get started. On my Mustache-based travels I stumbled across ICanHaz.js, which simplifies the process and has some really nice ideas. For example, on page load it grabs all of the templates and adds them to a globally available collection for ease of use. This becomes even more useful in Handlebars since it requires an additional compilation step. I ended up writing something very similar but considerably more primitive, and here it is:

   1:  var BandleHars = {
   2:      templateCache : {},
   3:      init : function() {
   4:          var templateList = $('script[type="text/html"]');
   5:          templateList.each(function(index, value) { BandleHars.templateCache[value.id] = Handlebars.compile($(value).html()); });
   6:          templateList.remove();
   7:      },
   8:      render : function(templateName, data) {
   9:          return this.templateCache[templateName](data)
  10:      }
  11:  };
  12:   
  13:  $(function() { BandleHars.init(); });

Right, let's get back to templating. First we need our data. I used a grab of the first few posts of this very site, pretty much as it's dumped out by System.Web.Script.Serialization.JavaScriptSerializer.

   1:  var data = [ 
   2:    {
   3:      "Comments" : [
   4:          { "Body" : ". . . the comment system.",
   5:            "CommentId" : 1,
   6:            "DateCreated" : "/Date(1248627151247)/",
   7:            "GravatarUrl" : "http://www.gravatar.com/avatar/d41d8cd98f00b204e9800998ecf8427e?d=identicon&s=64",
   8:            "Heading" : "Testing . . .",
   9:            "Poster" : "Tom",
  10:            "Website" : "http://monochromacy.net/"
  11:          },
  12:          { "Body" : "n/t",
  13:            "CommentId" : 2,
  14:            "DateCreated" : "/Date(1254127922440)/",
  15:            "GravatarUrl" : "http://www.gravatar.com/avatar/d41d8cd98f00b204e9800998ecf8427e?d=identicon&s=64",
  16:            "Heading" : "Notification Test",
  17:            "Poster" : "Test",
  18:            "Website" : ""
  19:          }
  20:        ],
  21:      "DateCreated" : "/Date(1248624603550)/",
  22:      "DateLastTouched" : "/Date(1248624603550)/",
  23:      "Heading" : "Oh hai!",
  24:      "PostId" : 1,
  25:      "Slug" : "Oh-hai",
  26:      "Summary" : "This weblog will be largely concerned with ASP.NET, C# and ...",
  27:      "Tags" : [ "Waffle" ]
  28:    },
  29:    /* SNIP */
  30:    { 
  31:      "Comments" : [  ],
  32:      "DateCreated" : "/Date(1261657386630)/",
  33:      "DateLastTouched" : "/Date(1261657386630)/",
  34:      "Heading" : "Introducing Compares Favourably",
  35:      "PostId" : 8,
  36:      "Slug" : "Introducing-Compares-Favourably",
  37:      "Summary" : "So I made a database schema comparison tool for MSSQL 2005.\nCompares Favourably\n<p ...",
  38:      "Tags" : [ "ComparesFavourably", "dotNet", "Projects" ]
  39:    },
  40:  ];
  41:   
  42:  var dataSet = new DataContainer(data, 3);
  43:  dataSet.update = function() 
  44:  {
  45:      // We'll come back to this . . .
  46:  };
  47:  dataSet.sortProperty = 'DateCreated';
  48:  $(function() { dataSet.update(); });

Here I'm setting up the DataContainer with three items to a page, sorted on the created data and then running the (currently empty) update function once the DOM has been loaded. Now it's time for some markup to display and manipulate our data.

   1:  <label>Paging</label>
   2:  <ul class="horizontal">
   3:      <li><a href="#" onclick="return dataSet.pageDown();">Page down</a></li>
   4:      <li><a href="#" onclick="return dataSet.pageUp();">Page up</a></li>
   5:  </ul>
   6:   
   7:  <label>Sort by</label>
   8:  <ul class="horizontal">
   9:      <li><a href="#" onclick="return dataSet.setSortProperty('Heading');">Heading</a></li>
  10:      <li><a href="#" onclick="return dataSet.setSortProperty('Summary');">Summary</a></li>
  11:      <li><a href="#" onclick="return dataSet.setSortProperty('Slug');">Slug</a></li>
  12:      <li><a href="#" onclick="return dataSet.setSortProperty('DateCreated');">Date Created</a></li>
  13:  </ul>

Nice and simple. This will page the data up and down, allow us to change the sort criteria, and includes some bonus hey-that's-not-what-label-elements-are-for abuse. Sue me. I figure a tag filter would make sense in this context and since this is dependant on the data it will need to be templated. First we need a container for our filter buttons:

   1:  <label>Tags</label>
   2:  <ul class="horizontal" id="tagsFilter"></ul>

And now a template for creating the filter links:

   1:  <script id="tagsFilterTemplate" type="text/html">
   2:  {{#each data}}<li><a href="#" onclick="return dataSet.addFilter('Tags', '{{this}}');">{{this}}</a></li>{{/each}}
   3:  <li><a href="#" onclick="return dataSet.removeFilter('Tags');">remove</a></li>
   4:  </script>

The above Bandlehars code (I hate that name already) will fetch and compile this template on body load, so now we just need to add this to our DataContainer update function to include the template rendering:

   1:  dataSet.update = function() 
   2:  {
   3:      // There's still more to come!
   4:      $('#tagsFilter').html(Bandlehars.render('tagsFilterTemplate', { data : dataSet.getPropertyValues('Tags') }));
   5:  };

Now, once the page is loaded and then whenever it is filtered, the list of available tags will be updated. This means we can drill down further using additional filters. Worth noting is that we're wrapping our data in another object, since Handlebars doesn't accept raw arrays as a data context. So that's the filters sorted. But we still can't see the data itself. It's time for another container and another template.

   1:  <div id="data"></div>
   2:   
   3:  <script id="dataTemplate" type="text/html">
   4:  {{#each data}}
   5:  <div>
   6:      <h2><a href="http://monochromacy.net/Post/{{Slug}}.aspx">{{Heading}}</a></h2>
   7:      <p><em>{{DateCreated}}</em></p>
   8:      <p>{{Summary}}</p>
   9:      <label>Tags</label>
  10:      <ul class="horizontal">{{#each Tags}}<li><a href="#" onclick="return dataSet.addFilter('Tags', '{{this}}');">{{this}}</a></li>{{/each}}</ul>
  11:  </div>
  12:  {{/each}}
  13:  </script>

Now to add it to our DataContainer update function.

   1:  dataSet.update = function() 
   2:  {
   3:      $('#data').html(Bandlehars.render('dataTemplate', { data : dataSet.getData() }));
   4:      $('#tagsFilter').html(Bandlehars.render('tagsFilterTemplate', { data : dataSet.getPropertyValues('Tags') }));
   5:  };

Well it's a start. What's next? How about comments.

   1:  <script id="dataTemplate" type="text/html">
   2:  {{#each data}}
   3:  <div>
   4:      <h2><a href="http://monochromacy.net/Post/{{Slug}}.aspx">{{Heading}}</a></h2>
   5:      <p><em>{{DateCreated}}</em></p>
   6:      <p>{{Summary}}</p>
   7:      <label>Tags</label>
   8:      <ul class="horizontal">{{#each Tags}}<li><a href="#" onclick="return dataSet.addFilter('Tags', '{{this}}');">{{this}}</a></li>{{/each}}</ul>
   9:  </div>
  10:  <br />
  11:  {{#if Comments.length}}
  12:  <div class="comments">
  13:      <h4>Comments</h4>
  14:      <ul>
  15:  {{#each Comments}}
  16:          <li>{{Heading}} - {{Body}} - {{Poster}}</li>
  17:  {{/each}}
  18:      </ul>
  19:  </div>
  20:  {{/if}}
  21:  <hr />
  22:  {{/each}}
  23:  </script>

As you can see, we've got a conditional part of the template in there. This is the kind of thing that Mustache consciously avoids, but I personally find quite useful.

A note about line 11 above. The Handlebars site says that the if block helper evaluates an empty array to false, and I also uncovered a pull request on Github mentioning this very thing that was merged on June 27. However, the latest download is the v1 beta-3 which was packaged on June 2, hence resorting to the .length trick. It looks like this won't be necessary after the next release.

Finally, .NET has done something weird and unseemly to our date values, so we need some way to tweak those back into shape. Thankfully another feature of Handlebars is the ability to add custom helpers.

   1:  Handlebars.registerHelper("dotNetDate", function(context) {
   2:      var m = context.match(/^\/Date\((\d+)\)\/$/);
   3:      var date = null;
   4:      if (m)
   5:          date = new Date(parseInt(m[1]));
   6:      return date.toLocaleDateString();
   7:  });

And we can invoke it using this:

{{dotNetDate DateCreated}}

Giving us our final version, which you can see running here:

DataContainer + Handlebars.js Example

The downsides? Unfortunately this is an SEO black hole since all of the real content is injected in via Javascript at page load. You'll need to render this on the server-side in order to receive any search engine attention at all. That said, there's Mustache implementations in a tonne of viable server-side languages and while the few changes to Handlebars almost certainly increase the complexity . . . hmmm. Time for another weekend project?

Comments (0)

Data Wrangling on the Client with Javascript

Posted by Tom on 24/10/2011 21:20:46

Filtering, sorting and paging. These three operations are the bread and butter of data display. And it really is a display concern, so why not push it as far as possible into the presentation layer? When it comes to webapps that means the browser. So I wrote a data wrapper in Javascript as part of one of my side projects. It goes by the highly uninspired name of DataContainer, because I was having an unoriginal day.

Setting up

Everything is contained in the DataContainer object, and the constructor takes the data itself as an array of objects, and the number of objects on each page.

var data = [
    {
        color: "red",
        value: "#f00",
        prime: true,
        rand: 1
    },
    {
        color: "green",
        value: "#0f0",
        prime: true,
        rand: 3
    },
    {
        color: "blue",
        value: "#00f",
        prime: true,
        rand: 1
    },
    {
        color: "cyan",
        value: "#0ff",
        prime: false,
        rand: 2
    },
    {
        color: "magenta",
        value: "#f0f",
        prime: false,
        rand: 1
    },
    {
        color: "yellow",
        value: "#ff0",
        prime: false,
        rand: 1
    },
    {
        color: "black",
        value: "#000",
        prime: false,
        rand: 3
    }
];

var pageSize = 10;
var dataSet = new DataContainer(data, pageSize);

As intimated above, there are three types of operations available in DataContainer - filtering, sorting and paging - and they are always performed in the following order.

  1. Filtering
  2. Sorting
  3. Paging

A call to getData takes the original dataset and then applies the filters, performs a sort and then splits the result onto the correct page and returns it. The most practical way of fetching data is through the use of callbacks. Whenever any operations are performed on the DataContainer which result in the resulting dataset changing the DataContainer's update function is called. If you want to update UI elements whenever the data is changed (and what else would you be using it for?) then simple pass the DataContainer a new update method with your own code. I'll be covering this in more detail in the next post - this is more about how to use the various DataContainer methods available.

Controls

Here's a brief rundown of the methods available on each DataContainer object.

Filtering

dataSet.addFilter(property, value);
dataSet.removeFilter(property);

Filtering is done based on the properties of each object in the initial array. Given the above data, if you make the call dataSet.addFilter("primary", true) then subsequent calls to getData will return on only those objects with the primary property set to true. If the initial data contains complex objects, then you can drill down into the object hierarchy using dot notation, the same way you would in code.

You can add multiple filters, allowing you filter the data more specifically. Also of note is the getPropertyValues method. When you pass this the name of a property it will apply the current filters to the dataset and then build a list of values of that property. The intent here is make building a drill-down UI nice and easy.

Sorting

dataSet.toggleSortDir();
dataSet.setSortProperty(property);

You can only sort by one property at a time. If you call the setSortProperty method a subsequent time with the same property name it will toggle the sort between ascending and descending.

Paging

dataSet.pageUp();
dataSet.pageDown();
dataSet.setPage(pageNum);

All pretty self explanatory. The pages are zero-indexed. You can also use the maxPage method to get the index of the final page.

Yay

There's real advantages to doing this stuff in the browser.

  • It means that your backend infrastructure can be swapped out easily. Wish to port from ASP.NET to Rails? It's not exactly free, but it's less work than it would be otherwise
  • No more sorting, filtering and paging gunk in your controllers

However . . .

  • Doesn't scale so good. For large datasets or crappy hosts you're going to kill the browser
  • If you want a non-Javascript fallback you'll need to implement everything ye olde way anyway

This is going to get long and boring so I'll move the second half of this over to second post in a couple of weeks. The next post will show how to create a dynamic UI over the top of DataContainer using Javascript templating.

Random burbling follows:

In my professional life we need to target everything when it comes to browsers. Sales ride on it and you can't just clam the site up because users are a bunch of dirty, IE6-using, tech-ignorant peasants. While we can create sites that rely heavily on Javascript we still need a Javascript-free fallback, and so we end up doing the work twice, in two different languages and on two different sides of the client-server divide. Usually this means we shy away from too much Javascript due to our time budget. Since this was part of an intranet project it was nice to be able to get down and write some real JS code, safe in the knowledge that if someone isn't using a supporting browser I can just walk over to the other side of the office and slap them. That said I did a trawl through some sites recently and a good proportion of them in our target market sector (fashion), simply don't work without Javascript. Maybe the days of non-Javascript fallbacks are past. Man, that would be great.

Comments (0)

You So Crazy - a silly, paranoia-inducing jQuery plugin

Posted by Tom on 02/05/2011 18:56:39

Have you ever wanted a jQuery plugin that causes your users to question their sanity? Then look no further!

You So Crazy inserts phrases into the paragraphs of an HTML document, which then disappear when they are scrolled out of view, only to reappear in a different place.

Demo

And here, as always, is the source:

   1:  (function($){
   2:      $.you_so_crazy = function(customPhrases, options) {
   3:      
   4:          var phrases = [
   5:              'feed me a stray cat'
   6:          ];
   7:          var settings = {
   8:              'highlight'     : false
   9:          };
  10:          
  11:          if (options)
  12:              $.extend(settings, options);
  13:          if (customPhrases)
  14:              phrases = phrases.concat(customPhrases);
  15:          
  16:          var currentElement = null;
  17:          var spotted = false;
  18:    
  19:          $(window).scroll(function() {
  20:              if (currentElement)
  21:              {
  22:                  if (element_on_screen(currentElement))
  23:                  {
  24:                      if (spotted)
  25:                      {
  26:                          select_element();
  27:                          spotted = false;
  28:                      }
  29:                  }
  30:                  else
  31:                  {
  32:                      if (!spotted)
  33:                          spotted = true;
  34:                  }
  35:              }
  36:          });
  37:          
  38:          var select_element = function() {
  39:          
  40:              if (currentElement)
  41:                  currentElement.remove();
  42:   
  43:              var target = null;
  44:              while(target == null)
  45:              {
  46:                  var paragraphs = $('p');
  47:                  var index = Math.floor(Math.random() * paragraphs.length);
  48:                  target = $(paragraphs[index]);
  49:                  
  50:                  if (element_on_screen(target))
  51:                      break;
  52:                      
  53:                  target = null;
  54:              }
  55:              
  56:              var tokens = target.html().split(' ');
  57:              var strIndex = Math.floor(Math.random() * tokens.length);
  58:              var phraseIndex = Math.floor(Math.random() * phrases.length);
  59:              var finalPhrase = '<span class="psycho42"' 
  60:                  + (settings.highlight ? ' style="background-color: #f00;">' : '>') 
  61:                  + phrases[phraseIndex] + '</span>';
  62:              tokens.splice(strIndex, 0, finalPhrase);
  63:              target.html(tokens.join(' '));
  64:              currentElement = target.find('span.psycho42');
  65:          };
  66:          
  67:          var element_on_screen = function(element)
  68:          {
  69:              var screen_position = element.offset();
  70:              screen_position.top -= $(document).scrollTop();
  71:              screen_position.left -= $(document).scrollLeft();
  72:              var windowSize = { 'left': $(window).width(), 'top': $(window).height() }
  73:              
  74:              return screen_position.top + element.height() < 0 
  75:                  || screen_position.left + element.width() < 0
  76:                  || screen_position.top > windowSize.top 
  77:                  || screen_position.left > windowSize.left;
  78:          }
  79:          
  80:          select_element();
  81:      };
  82:  })(jQuery);

This is a strong contender for the title of most pointless code I've ever written, but I had a spare hour and felt it was about time I added my own contribution to the swath of jQuery plugins out there.

Comments (0)

Regarding Your Performance - Remote Windows Performance Monitoring

Posted by Tom on 24/12/2010 11:36:52

So what with my work on Oh Teh Noes I was wondering if I could co-opt it to do performance monitoring. As it happens, I couldn't figure out how to do it without doing some serious square-pegging, but it did get me thinking about how I would like to attack the problem. And as is so often that case that eventually turned into me actually attacking the problem. That happens a lot. I nerd snipe myself daily.

And thus Regarding Your Performance was born. It consists of a Windows service which sits on the host machine and farms basic performance data, and a HTML page which collects that data from a list of hosts via AJAX and drops it into some graphs. It became an excuse for me to learn some new things, so I thought I'd share the experience. This is a fairly high-speed tour so you might want to saunter over to Github and grab the source

InstallUtil Can Die in a Fire

I wanted the agent (the side of the system which resides on the target host and harvests delicious, delicious performance information) to run as a Windows service. But there's one particular element of Windows services that I find especially uncomfortable . . .

InstallUtil.exe can piss right off.

I've been packaging that dowdy little app with all of my Windows Services for years now and enough is enough. It's time to find a more elegant solution. Fire up Reflector and it quickly becomes apparent that InstallUtil doesn't actually do a lot. In essence it simply creates a ManagedInstaller, which itself is a thin wrapper around a TransactedInstaller. By taking that code and then adding on the contents of the usual Service designer files you can roll your own pretty easily. I ended up using command line arguments to install and uninstall the service, resulting in a Main method like this:

   1:  static void Main(string[] args)
   2:  {
   3:      if (args.Length > 0)
   4:      {
   5:          if (args[0] == "-i" || args[0] == "-u")
   6:          {
   7:              ServiceInstaller serviceInstaller = new ServiceInstaller();
   8:              serviceInstaller.ServiceName = "Performance.Agent.Service";
   9:              serviceInstaller.StartType = ServiceStartMode.Automatic;
  10:              serviceInstaller.DisplayName = "Colourblind Performance Agent";
  11:              serviceInstaller.Description = "Agent for the Colourblind performance monitor";
  12:   
  13:              ServiceProcessInstaller processInstaller = new ServiceProcessInstaller();
  14:              processInstaller.Account = ServiceAccount.LocalSystem;
  15:              processInstaller.Username = null;
  16:              processInstaller.Password = null;
  17:   
  18:              TransactedInstaller installer = new TransactedInstaller();
  19:              installer.Installers.Add(processInstaller);
  20:              installer.Installers.Add(serviceInstaller);
  21:              installer.Context = new InstallContext("install.log", null);
  22:              installer.Context.Parameters.Add("assemblypath", Assembly.GetCallingAssembly().Location);
  23:   
  24:              if (args[0] == "-i")
  25:                  installer.Install(new Hashtable());
  26:              else if (args[0] == "-u")
  27:                  installer.Uninstall(null);
  28:          }
  29:      }
  30:      else
  31:      {
  32:          ServiceBase[] ServicesToRun;
  33:          ServicesToRun = new ServiceBase[] 
  34:          { 
  35:              new Service() 
  36:          };
  37:          ServiceBase.Run(ServicesToRun);
  38:      }
  39:  }

Next up, how to poll our host system for performance information.

Real-time System Information Voyeurism

PerformanceCounter is the .NETified way of querying the Windows for performance metrics.

   1:  PerformanceCounter cpu = new PerformanceCounter("Processor", "% Processor Time", "_Total");
   2:  PerformanceCounter memory = new PerformanceCounter("Memory", "Available MBytes");
   3:  PerformanceCounter requestsPerSecond = new PerformanceCounter("ASP.NET Applications", "Requests/Sec", "__Total__");

The strings you use to instantiate PerformanceCounters come firmly in the realm of magic. They're not terribly well documented and in all honesty you're better off just opening up Windows' Performance Monitor (WinKey-R 'perfmon') and seeing which counters you can add through that. It'll even give you a convenient description of quite what some of the more esoteric ones mean. Failing that you can enumerate them yourself

It would make a lot of sense to create our performance counters in the constructor, but it turns out they can spend a lot of time sitting around doing nothing which then causes net start to report a timeout. This is not the case if we move the object instantiation to an OnStart event of the service though, so that's what I ended up doing. The last thing on the startup todo list is to get the total amount of physical memory for the host, and for this we take a brief forray into the nightmare realm of WMI.

   1:  ManagementObjectSearcher wmi = new ManagementObjectSearcher("select * from Win32_ComputerSystem");
   2:  foreach (ManagementObject o in wmi.Get())
   3:      TotalPhysicalMemory += Convert.ToSingle(o["TotalPhysicalMemory"]) / (1024 * 1024);

Now we simply poll the counters every second and store the results in a dictionary.

   1:  Status["CpuUsage"] = cpu.NextValue().ToString();
   2:  Status["MemoryUsage"] = ((TotalPhysicalMemory - memory.NextValue()) * 100 / TotalPhysicalMemory).ToString();
   3:  Status["RequestsPerSecond"] = requestsPerSecond.NextValue().ToString();

All well and good, but now our service has shut-in syndrome, and that frankly will not fly.

Bare Bones HTTP

At this point I had only vague ideas about the form that my client would take. With that in mind I wanted something flexible enough that my later decisions wouldn't be affected by my earlier design choices. With that in mind I decided to go for HTTP as my application protocol, and JSON as my information format.

.NET's HttpListener provides an itty-bitty HTTP server, capable of asynchronous responses and not a lot else. That said, it's perfect for small HTTP-enabled apps like this.

   1:  string prefix = String.Format("http://+:{0}/", ConfigurationManager.AppSettings["ServerPort"]);
   2:  HttpListener httpListener = new HttpListener();
   3:  httpListener.Prefixes.Add(prefix);
   4:  httpListener.Start();
   5:   
   6:  // 10 worker threads
   7:  for (int i = 0; i < 10; i ++)
   8:      httpListener.BeginGetContext(new AsyncCallback(RequestCallback), null);

Besides that, you're on your own. You handle each request as you see fit in your request handler, and once you've done what you need to you spool up another listener to replace the one you've just 'used', then wait for more calls to come rolling in.

   1:  private void RequestCallback(IAsyncResult result)
   2:  {
   3:      HttpListenerContext context = null;
   4:      try
   5:      {
   6:          context = HttpListener.EndGetContext(result);
   7:   
   8:          string output = "{";
   9:          bool foo = false;
  10:          foreach (string key in Status.Keys)
  11:          {
  12:              output += String.Format("{2}\n\t{0} : '{1}'", key, Status[key], foo ? "," : "");
  13:              foo = true;
  14:          }
  15:          output += "\n}";
  16:   
  17:          Encoding encoding = new UTF8Encoding(false); // Whenever I have UTF8 problems it's BOM's fault
  18:          byte[] outputBytes = encoding.GetBytes(output);
  19:   
  20:          context.Response.OutputStream.Write(outputBytes, 0, outputBytes.Length);
  21:      }
  22:      finally
  23:      {
  24:          if (context != null && context.Response != null)
  25:              context.Response.Close();
  26:          HttpListener.BeginGetContext(new AsyncCallback(RequestCallback), null);
  27:      }
  28:  }

Now we can consume the one-trick web service via AJAX requests spawned from an HTML page!

That's Not a Client - It's an HTML Page

Damn right it's an HTML page. Why bother with apps and servers and all that cruft when all you need is a few lines of Javascript? The 'client' is a simple HTML page containing a list of active agents which are polled every second and the results are used as the data for a set of sparklines graphs care of a jQuery plugin. The only gotcha here is the scale on the requests/sec graph. We can treat CPU and memory usage as percentages, but requests/sec is an absolute value so you may need to tweak the y scale accordingly.

Hmmm. Hang on a minute . . .

Oh crap, I forgot about the Same Origin Policy

Every time I think of something cool to do with a web client the Same Origin Policy comes and bites me on the arse. SOP states that an XmlHttpRequest can only be sent to the same domain as the Javascript file which initiates it, and therefore prevents the kind of cross-domain AJAX shenanigans we're attempting here.

JSONP is a cheeky little hack designed to circumvent this very restriction. Rather than being just Javascript object literal notation, as is the case with JSON, JSONP actually returns a javascript source file which is fetched by the host page using a script block, dodging the SOP. When making a call to a JSONP-aware service you provide the name of the function that is ready and waiting to process the response, and the service wraps it's JSON response in a call to that function so that when the file is fetched by the client the response handler is executed. Conceptually it feels weird as you're essentially pulling a pushing, or maybe pushing a pull, but getting down to the brass tacks this means we have to add two lines to our request handler:

   1:  string callback = context.Request.QueryString["callback"];
   2:  output = String.Format("{0}({1})", callback, output);

On the client side there is a handy jQuery JSONP plugin which makes for a similarly smooth transition.

~fin

Those are the bits that interested me, at least. Feel free to grab the code and have an explore. It's a testament to the .NET framework that it packs so much into so few lines of working code and made for a fun train-journey-home-from-work project over a few days.

Comments (0)

Silly HTML Game

Posted by Tom on 27/09/2010 09:19:46

It's been a while since I ventured out with trepidation into the realm of HTML5. It was my first real post, in fact. I've not really touched it since, having been tempted by a cornucopia of glittering distractions, as I am wont to be.

However, I recently stumbled across the concept of a game jam, and have since been wondering how quickly I could crap out a simple game. Between that and the recently passed Javascript 10k, and all I needed was a concept.

Oh wow! A Silly HTML Game screenshot!

Play the game

There's not a lot to it at the moment. Balls spawn in the middle of the play are and move to the outside. Line up the rings allow them through. Simple! Ultimately I want the balls to have different effects when they reach the outside of the playing area: ball speed up and down, spawn speed up and down, extra lives, reverse controls, that kind of thing.

Source on Github

This one went from zero to prototype in a couple of hours, so I'm pretty happy with that turn-around. I have another game project I work on a little and the scope of that is large and growing (and as such very unlikely to ever see the light of day) so it was refreshing to force myself to make something simple and actually attainable.

Comments (0)

 
Older