Insights and discoveries
from deep in the weeds
Outsharked

Tuesday, May 15, 2012

On Javascript Style

My two cents on the ongoing debate over punctuation in Javascript.

This two year old gist from Isaac Schlueter, author of NPM, has been a focal point for this debate. He describes a rationale for putting commas first, beginning with these simple words:

Note how the errors pop out in the comma-first style.

He's absolutely right. And that, right there, is why I think it's a bad style choice.

I want punctuation to disappear when it's not doing something important. Commas that separate array elements or object properties that are already on separate lines are not doing anything important. So putting commas first may save me from a few comma-related errors every day. (Which, of course, JSHINT will catch promptly, along with all the other errors I make every day). But I will spend ten or twelve hours a day, every day, looking at commas instead of variable names or object definitions. I will have to visually parse the punctuation for every single line I read, instead of not thinking about it.

I agree that putting commas first makes errors more apparent. I disagree that an entire aesthetic should be defined by this desire.

There are some arguments made over the course of this two-year-old (and still going strong) gist about the sorts of errors that automated syntax checking tools will not catch, also justifying this. For example, this is valid:

var x = [ ["asdf", "foo", "bar"],
          ["baz", "blerg", "boof"]
          ["quux", "antimatter"] ];

The parser will interpret the last element as a property indexer, which is wrong.

All I can say is, how much time do you spend hardcoding two-dimensional arrays? On the other hand, how much time do you spend reading object constructs and var statements? The latter are the foundation for pretty much every bit of code you will write in Javascript. The former is an anomaly.

Yes. You can come up with of situations where the error introduced by a missing comma would resemble valid code and not be caught by JSHINT. A comma-first syntax would probably have helped you avoid that problem.

I am not sure such a situation has ever come up in practice, though. This is OCD. This is carrying a bottle of antiseptic and spraying every surface before you touch it, because it (maybe) will prevent you from getting a cold once in a while. The cure is much worse than the disease!

Avoiding syntax errors is important. But is it more important than making code that can be read and understood with the least amount of effort? I don't think so. If comma-first adds even 1% to the amount of mental energy I need to read and understand some code, it's not worth it.

One final note. I'm not a purist at all. Use multiple var statements (but only before your first line of functional code, of course!) Use side effects. Don't use semicolons. I think the goal should be to write code that is the most easily understood, and a lot of "non-purist" techniques can be used to create terse code that's more readable than its longform version. I feel like a lot of the rhetoric on this topic has been about a debate over purism. It shouldn't be.

I have a tendency to use semicolons rigorously, but that's because I program in C# half the time and it's hard to get my brain to move easily back and forth between the two mindsets. But I completely understand the rationale often cited for using ASI: semicolons are visual clutter. Yup, they sure are! And I think that same rationale should apply doubly so for putting commas at the beginning of the line instead of the end. At least when punctuation is at the end of a line, our "left to right" brains can easily dismiss it. When it's at the beginning, you can't avoid it.

Thursday, March 29, 2012

Passing control from a custom HttpHandler to the default handler in asp.net

Using System.Web.Routing and IIS7 you can do some pretty interesting stuff with a web app. It also opens up a lot of possibilities for "modernizing" old webforms applications that are stuck with ugly paths and query strings by overriding the default handlers and parsing out the page path yourself.

One thing I wanted to do was to map certain paths back to an aspx page, but dynamically - e.g. I wanted to be able to parse out a path and using complex logic, build a new "real" path+querystring that I could just pass to the default handler. This way I can create nice clean API-like paths and map them to an old, ugly query-string based API. The routing part is a piece of cake. (Well, not really, but it's not a mystery). But how do you invoke the default handler from code? There's a baffling lack of info out there, so I figured I'd post a solution for future coders.

After some digging I realized that, actually, the default handler is just the plain old System.Web.UI.Page object. I tried to just create an instance of one and call ProcessRequest like you would any other handler. Nothing at all. No error, no output.

Microsoft is decidedly no help with this either. For Page.ProcessRequest, their documentation actually says:

You should not call this method.
Priceless!

Let us proceed to tread into "you have been warned" territory, though. The problem is you (apparently) aren't just supposed to create a new Page. You should use the PageHandlerFactory. Unfortunately, MS has inconveniently laden that thing with an internal constructor, so you can't make one of those, either.

Thanks to Robert's C# musings for the answer to this one, which is the key to solving this problem. You can't instantiate a class with an internal constructor, directly. But you can make yourself an instance of any class that doesn't call any constructor using a well-hidden GetUninitializedObject method. Since the constructor in the case of this factory is designed only to prevent us from using the factory, and doesn't actually do antyhing useful, not a problem. Once you've got access to the handler factory, the rest falls into place pretty easily. Here's some basic code that maps any path to default.aspx, converting the original path to a query parameter.

public void ProcessRequest(HttpContext context) {

    // the internal constructor doesn't do anything but prevent you from instantiating
    // the factory, so we can skip it.
    
    PageHandlerFactory factory =
        (PageHandlerFactory)System.Runtime.Serialization.FormatterServices
            .GetUninitializedObject(typeof(System.Web.UI.PageHandlerFactory));

    // you may want to use context.PathInfo - in my case I mapped a wildcard to this
    // handler so it's always blank.

     string newTarget  = "default.aspx"; 
     string newQueryString = "path="+context.Path;
     string oldQueryString = context.Request.QueryString.ToString();
     string queryString = newQueryString + oldQueryString!="" ? 
         "&" + newQueryString :
         "";

     // the 3rd parameter must be just the path to the file target (no querystring).
     // the 4th parameter should be the physical path to the file, though it also
     //   works fine if you pass an empty string - perhaps that's only to override
     //   the usual presentation based on the path?

     var handler = factory.GetHandler(context, "GET",newTarget,
         context.Request.MapPath(context,newTarget));

     // Update the context object as it should appear to your page/app, and
     // assign your new handler.

     context.RewritePath(newTarget, "", queryString);
     context.Handler = handler;

     // .. and done

     handler.ProcessRequest(context);
}

The PageHandlerFactory can certainly be created statically so you don't have the overhead of reflection for every request. The actual "Page" handler must be created each time, though, because it's not marked as IsReusable.

Monday, March 12, 2012

SharpLinter now works with inline scripts in HTML files

It will look for embedded scripts and only validate what's inside legal <script type="text/javascript"></script> blocks. Any file that's not called *.js or *.javascript will be treated this way.

Go get Sharplinter 1.0.2. You can also just download the binary distribution.

SharpLinter is a C# command-line tool for validating javascript. It is highly configurable, can produce output in customized formats, and integrates with Visual Studio. It should work with any editor or tool that supports using external tools to process files. The readme includes instructions for use with Visual Studio, Sublime Text 2, and Textpad.

Wednesday, March 7, 2012

Area groups in ImageMapster

ImageMapster has had the capability to form complex area groups for some time, but never got around to documenting it well. More recently, I added a keys feature that lets you get the keys associated with an area (or another key), and also the ability to pass rendering options directly with the set method. This opens up a lot of possibilities for area manipulation that were possible but not very easy before.

I put together an example showing how area groups can be used, along with these new features, to have a great deal of control over the effects.

Take a look at it on jsFiddle and play around! I'm going to start a library of interactive examples to include on the web site. If you like it, let me know, or put together one that shows your own techniques so I can share it!

Complete documentation is on the project web site.


Intro to the ImageMapster Area Groups example:


This example shows how to use mapKey to create groups that you can use to control sets of areas independently.
  • Each area in the imagemap has a custom attribute data-state. This defines the groups that each area belongs to.
  • The mapKey: 'data-state' option identifies this attribute for the imagemap. The values in the mapKey can be used to select, deselect or highlight areas or groups of areas from code.
  • Areas can belong to more than one group. In this example, New England states belong to three groups: a state code like "ME", the group "new-england", and possibly the group "really-cold".
  • Options can be set for area groups. These options only apply when the group is activated using its group name. Notice if you click a New England state, it's red (like the other states) but when you activate it using "new-england" or "really-cold", it's blue.
  • When you mouse over an area, the first group in the list determines what gets highlighted. In this example, most states are actually defined by more than one area HTML element. The first value of data-state is the state code, ensuring that when you mouse over a state, all the different areas that make it up get highlighted together, even if they aren't connected. New England states and Hawaii are good examples of this (the islands are separate, and the New England states have separate text markers).
  • Areas are separate logical entities. You will notice if you click one of the group links, then highlight a state in New England, it highlights again (in a different color, per the render_select options). This means that area groups are not *directly* a good way to just act as if the user selected each area in the group, but...
  • You can use the keys method to get the primary keys for a group, and the get_options method to get the options, and set them manually. Click "New England As Separate States" below. The "Texas with Custom Options" is a simpler case of setting custom rendering options.

Tuesday, March 6, 2012

SharpLinter now supports Sublime Text 2

Okay, okay. Nothing at all has changed with SharpLinter - github, and Sublime Text 2 supports pretty much anything that generates consistent output with the right config :)

Here's how to add a build system for Sublime Text 2 that uses SharpLinter for Javasript files.
  1. Select Tools -> Build System -> New Build System
  2. Enter the following to create a build config that works against javascript files:
{
    "cmd": ["sharplinter", "-v","-ph","best","*.min.js", "$file"],
    "file_regex": "^(.*?)\\(([0-9]+)\\): ()(.*)$",
    "selector": "source.js"
}

Save it, and your're done. The regex should match SharpLinter's default output and let you use F4 and shift+F4 to navigate any errors within your file.

The "cmd" property for Submlime Text 2 should contain the command followed by any options you want, so this could be as simple as

...
"cmd": ["sharplinter", "$file"],
...

to run SharpLinter with default options against the active file. The options above are just the ones I like to use, which provide verbose output, minify to *.min.js on success, and use the best compression method (usually yui).

Go ahead and set yourself up with SublimeOnSaveBuild and you can have it run every time you save automatically.

Thursday, February 16, 2012

Dragging and Dropping onto HTML Image Maps

A user of my jQuery plugin ImageMapster, while working through a problem, asked if it was possible to set up an imagemap that you could drop things onto. I had never done this before, but it seemed like an interesting problem with lots of potential applications, so I set about trying to solve it. A the bottom of this page is a simple example using Marvin the Martian that works, and I'll explain how before we get there.

Using jQueryUI's draggable method, it's easy to create things that can be dragged. Things get a little tricky because of z-indexes though. When you drag something, it needs to have a highest z-index on the page, or it will disappear behind things when you drag it over them. But at the same time, an imagemap will not activate unless it has the highest z-index. A paradox!

Luckily, when using ImageMapster, things are already set up so you can have your dragging element on top, and still have an active imagemap. This is the very nature of how it works: when binding an image, ImageMapster makes a copy of the image to use as a backdrop, and then makes your original image invisible though CSS using opacity:0. So all you need to do is make sure the z-index of your drag element is between those two things. A little understanding of ImageMapster's element scheme is all that's needed:

The topmost layer, the HTML image map itself, isn't really a layer in that you don't need to set the z-index of the elements explicitly. However it does act like a layer in that mouse events over the areas will supercede events over the image itself. But basically, the original image is the topmost element.

Finally, all this stuff is wrapped up in a div. This is useful to know because it means you can use jQuery's siblings method to easily change the z-index of everything that's important except your original image.

The code below is the basic logic you'll need to make something draggable over a live imagemap.

    
    var img = $('#my-image-map'), item = $('#draggable-object');

    img.mapster();
    ...

    // after binding with imageMapster, set the z-index of the image's siblings 
    // to zero (the lowest). The image copy that is created, as well as the canvases 
    // that render the highlight effects, are all siblings of the original image 
    // after binding ImageMapster.
    
    // IMPORTANT: This must be done *after* mapster has finished binding -- see the
    // actual code below for the use of "onConfigured" to do this

    img.siblings().css('z-index',0);

    // set the image itself to something higher. 
    
    img.css('z-index',10);

    // the draggable element should have a z-index inbetween the visible
    // background and effects images (which are all set to 0 now) and the
    // original image+imagemap (now set to 10).

    item.css('z-index',5);


That's almost all you need to do. What happens when you drop the martian somewhere? It now has a z-index that is lower than the original image. Even though the imagemap is not visible, and the martian is, you won't be able to grab the martian again, because once you drop it, it's old z-index takes effect.

To address this, you need a little more sleight of hand. When someone first grabs the draggable element, change its z-index to be a value between the two image layers. When they drop it, though, change it to something that's higher than the original image, so it will be on top and can be picked up again.

In Action!

Here's the functioning example, all the code follows (or just use your browser to look at it). Drag the martian onto Mars to "win". Any other planets will give you a negative response, and nothing happens if you drop him in space. I've also set it up on jsfiddle.net. Enjoy!

Help me get home!

Code:


Internet Explorer notes: This doesn't seem to work in IE < 9 -- in that the imagemap areas are not activated on mouseover while dragging. I'll address this, and present a solution, in my next post.

    var img = $('#planets'), martian=$('#martian');
    
    img.mapster({ 
        mapKey: 'alt',
        fillOpacity: 0.8, 
        fillColor: 'ff0000', 
        stroke: true,
        strokeWeight: 2, 
        strokeColor: '00ff00',
        onConfigured: function() {
            // this will be called only after ImageMapster is done setting up
            img.siblings().css('z-index',0);
            img.css('z-index',10);
        }
    });   

    martian.css('z-index',5)
        .draggable({
            drag: function() {
                $(this).css('z-index', 5)
            }
        });

    img.droppable({
        drop: function(e,ui) {
            // set z-index to the highest, so it can be dragged again
            $(ui.draggable).css('z-index',20);
 
            // returns the mapKey value of the currently highlighted item
            var landing=img.mapster('highlight');

            if (landing=='Mars') {
                alert("Thanks for bringing me home!!!");
                martian.draggable('disable').hide('slow');
            } else if (landing) {
                 alert("I don't live on " +  landing);       
            }
        }
    });


Wednesday, December 21, 2011

Panasonic UB94 wireless adapter driver

Got a new Panasonic flat screen TV for Christmas from my wife (she must really love me!!) and it came with this little USB wireless Ethernet adapter, I guess so they can say the TV supports wireless networks. Like any self-respecting nerd I have a wired network in my house and a switch underneath my TV so of course I don't need this. But do I look like the guy that's NOT going to figure out how I can use it on a computer?

It was a little tricky to track down drivers that work with this so I thought I'd share for future googlers. The device reports itself as a "UB94" when you plug it in. I figured out from some sketchy "driver download"/spam sites that it has an Atheros 7010 chipset, which supports 802.11n. Hey, an upgrade from my old G adapter!

 Atheros doesn't seem to provide reference drivers directly to the public, unfortunately. Some more searching revealed this chipset is shared by the Netgear WNA1100, for which drivers can be downloaded from Netgear. Probably many other devices as well.

To get this thing to work on Windows 7 follow these steps. For Windows XP, per a user's comment, it's almost the same, with one minor change noted in the steps below. I've only tested this on Win 7 though.

In the interest in not infringing on anyone's copyright I'll just tell you what to edit rather than posting a driver inf file.

  1. Download & install drivers from Netgear for the WNA1100.

  2. Locate the driver info file, probably:

    C:\Program Files (x86)\NETGEAR\WNA1100\Driver\WIN764\netathurx.inf

  3. Under the [Manufacturer] section, add one line:

    %PANASONIC%   = Panasonic, NTamd64
    

  4. Add a new section after the section for [VERIZON.NTamd64] (actually it probably doesn't matter where you add this, but this seemed as good a place as any)

    [PANASONIC.NTamd64]
    ; DisplayName                 Section                 DeviceID
    ; -----------                 -------                 --------
    %PANASONIC.DeviceDesc.7010% = ATHER_DEV_7010.ndi,     USB\VID_04DA&PID_3904
    

    For XP, it's the same except the section should be called:

    [PANASONIC]

  5. At the very end in the [Strings] section add this line:

    PANASONIC.DeviceDesc.7010    = "Panasonic UB94 USB Adapter"

    This is the text that will appear in device manager. Feel free to personalize.




After that, I just went to the broken device in Device Manager and updated the driver, pointing it to the folder above. If you want you can un-install all the Netgear software and just keep the three driver files -- that is all that's needed.

There, you just saved $19.99!