Insights and discoveries
from deep in the weeds
Outsharked

Friday, August 19, 2011

New feature for ImageMapster: Resize image maps in real time

I have been adding a lot of features to ImageMapster, my jQuery plugin for imagemap manipulation, over the last couple months. Unfortunately its been piecemeal and I have not had time to update the (awful) project home page to demonstrate the new features. But this is pretty neat so I wanted to put it out there before I fix up the web site. Kind of a natural extension to that little resizer tool I have here.. it took a bit of doing to make it all work with the internals of the plug-in, but now you can just plop in any image + imagemap, and make it any size you want. ImageMapster will take care of scaling everything.

With a little cleverness using divs and hidden overflow, you ought to be able to make this zoom to an area that's been clicked. Start with a higher-resolution image than what you want to show on screen initially, and the detail will be preserved when you zoom. (That will get added to the plugin down the road too!)

First click on a couple states to make some selections... then click one of the buttons.

Does it work for you? It seems to be good in everything I've tried so far. Please let me know if it breaks on your browser.

(may break your browser if too big)

Try mousing over the map while it's resizing, depending on the browser, it even kinda works! I don't actually change the imagemap itself until the end, so it's not going to detect the mouse in the right position. But because of the way canvas proportions work, it will still highlight an area properly (maybe just not the one you were over while in mid-animation). It's interesting to see how the browser handles all that mayhem.

Code is pretty much this:
<img style="width:720px;border:0;margin:auto;" id="usa_image" src="http://www.outsharked.com/images/usa_map_720.png" usemap="#usa">

$(document).ready(function() {
    $('#usa_image').mapster({mapKey:'state'});

    $('#make_small').bind('click',function() {
        $('#usa_image').mapster('resize',200,0,1000);
    });
    $('#make_big').bind('click',function() {
        $('#usa_image').mapster('resize',720,0,1000);
    });
    $('#make_any').bind('click',function() {
        $('#usa_image').mapster('resize',$('#new_size').val(),0,1000);
    });
});

Thursday, August 18, 2011

sharpLinter: run JSLint and JSHint in Visual Studio and from the command line

sharpLinter Resources

There are a few options out there for automating "linting" your Javascript within the VS IDE. The most prominent one JSLint for Visual Studio 2010, a VS extension. Then there are a bunch of different relatively hacky ways that people have come up with to do this. None of them really did it for me, so I rolled something new. The result is sharpLinter, a C# command line application and class library for running JSLINT (or JSHINT) against your Javascript files.

This work is based on Luke Page's early crack at linting in Visual Studio from late 2010. Luke went on to create the extension I mentioned before, offering some UI integration.

OK, so there's already a VS extension, why sharpLinter?

This plug-in is cool, but I found it had some annoying shortcomings. It kept forgetting what files I'd told it to exclude from processing, so I'd end up having thousands of errors from every 3rd party script included in my project before long. These exclusions had to be specified in the GUI, one at a time or with multiple-click-select. Argh! There seems to be no way to just tell it to include or exclude entire paths or patterns, which is what I really wanted. Configuration was fairly limited, and the feature to skip blocks within files didn't seem to be recognized either. And finally, you were stuck with whatever JSLINT or JSHINT it had compiled into it!

Well, all that could probably be addressed by the author or some other industrious soul. But ultimately, it was designed to be an extension for Visual Studio. While I wanted that, I also something I could easily integrate with automated processes, or as a quick and dirty way to run against any file in any situation. I wanted a library and command line tool.

It's not a real Visual Studio extension. That means you need to configure it as an "external tool." But it produces output formatted for VS, so you can still just click on a line in the output window, and it will jump directly to that file and line. Frankly, all the config features in the the VS extension seem like more of a hindrance than a benefit to me. The lint options are limited to what was part of the script when the extension was coded, and the settings all seemed to work erratically. All I really wanted was to set up a configuration for my project, then "run, click, and go to the error." That's all here.
Example output within Visual Studio. Enlarge.

sharpLinter is all these things.

But wait, there's more!

If you act now you also get instant minification!
sharpLinter can be configured to automatically minify your scripts after they pass validation using Yahoo YUI compressor, or Dean Edwards' JSPacker. Your choice, or let sharpLinter decide how to minimize, and it will just use the one that produces the smallest script. There's even an option to preserve the first comment block of your script in the output, so you can keep your credit & license information intact, if you choose.

To keep you safe from version confusion, if you have this option enabled and a script fails, any existing minimized script matching the output pattern for just the file that failed will be deleted.

How's it work?

Complete usage instructions and command-line options are in the readme on github. Basic command-line usage is as follows:
sharplinter [-[r]f path/*.js] [o options] [-v]
        [-c sharplinter.conf] [-j jslint.js] [-y]
        [-p[h] yui|packer|best mask] [-k]
        [-i ignore-start ignore-end] [-if text] [-of "format"]
The options let you:
  • [-[r]f] specify a file, folder, or grep mask to parse. If [r] is included, will recurse subfolders too.
  • [-c] specify a file with global configuration options
  • [-j] specify the actual code to run the checks (hopefully, one of JSLINT or JSHINT). If you leave this out, sharpLinter will look for a file called jslint.js in its executable directory. If that's not found, it will use the code embedded in itself (JSHINT as of 8/15/11, at the time of this writing).
  • [-o] specify options to pass directly to the linter
  • [-p[h]] tell it to minimize the output, using a particular (or best) strategy, and define a template for the filename of the minimized version. If called with -h, then the first comment block will be preserved.
  • [-i] specify text to use as markers for ignore blocks, or [-if] to skip an entire file
  • [-y] tell it to run Yahoo YUI against the file as well as the linter, and report its errors too
  • [-v] be verbose -- will report lots of information about what its doing other than errors. Normally, only errors are reported, or a single success line if there were no errors.
  • [-of] define an output format string for the error reports using parameters for error, source, line, and character. So maybe you want to use this to feed something other than visual studio? You can format the output any way you want.
  • [-k] wait for a keystroke when finished
All the core functionality is wrapped into a class library, so it should be easy to integrate this into another project (e.g. not from the command line). One caveat - this must be compiled with x86 as the target platform. It will not work if "any" (e.g. x64 or x86) is specified, because the Neosis V8 engine wrapper is a 32 bit only application.

Enjoy, let me know if you find problems or have questions, or just fork it!

Tuesday, August 2, 2011

CsQuery: New features, tests, utility functions

This is an update to my announcement of the CsQuery project, a C# port of jQuery.

In the last few weeks I have added a lot more methods, features, tests, and have made substantial changes to the DOM model to more accurately mimic the HTML DOM objects. The project now includes an Nunit test suite that incorporates some tests migrated from the real jQuery test suite (as applicable) - though there are hundreds and hundreds and it will take time to move them all. So far I've gotten through all of Core, and part of Attribute and Traversal. There are a lot more to go. But I have been heartened by the fact that most migrated tests have passed; so far, most of the problems have had to do with unimplemented features rather than bugs.

Additionally, Styles are represented with a new CSSStyleDeclaration object that includes validation using the Visual Studio CSS validation XML data. Right now I just included a basic CSS3 implementation but this could easily be updated to permit selecting any validation scheme.

With the new Style mechanism, styles added programatically are validated against the schema, and errors are thrown if invalid styles are added. This checking can, of course, be skipped, if desired. It is also not performed while parsing HTML, since this would serve little purpose and probably fail a lot. But this also lets us ensure unit types are parsed and stored correctly in your own code, and that only valid enumeration values are used.

Finally, a number of utility functions and features have been added.


WebForms UpdatePanels can be parsed, manipulated, and re-rendered. The Server.CsQueryHttpContext object now contains an enumerable of AsyncPostbackData objects, which contains a CsQuery object of the data block as well as the identifying information for each UpdatePanel that is updated on an asynchronous postback. You can manipulate the data as usual, and the Render method will take care of reformattingso the ASP.NET client architecture can deal with it properly.

CsQuery.ParseJson - converts JSON to an ExpandoObject. Many methods that accept object in jQuery have been implemented to accept an object in CsQuery. Because it's not convenient to create true objects for every situation where an object structure is being used to pass data or parameters, as in jQuery, we implement this functionality using JSON and ExpanoObjects. For example:

dynamic css = CsQuery.ParseJSON("{ width: 10, height: 10, padding: '1px 10px 1px 10px'}");
CsQuery.Select("div.someClass").CssSet(css);
Methods that are designed to accept an object in jQuery have generally been implemnted in CsQuery to accept an object or an ExpandoObject, but for convenience, they can also accept just a string of JSON data, which will be parsed. Any string that begins with a curly brace will be treated as JSON data in these situations. If a string that is not JSON data but begins with a curly brace is required to be passed, use two curly braces to start the string "{{" to escape.

CsQuery.Extend - mimics jQuery.Extend using ExpandoObjects. You can pass it any legitimate objects (e.g. things with properties, not value types) as parameters, and it will return an ExpandoObject, merging same-named properties of each source object. If you pass a regular object as the target, it will only merge properties that have matches in the source object, since regular objects cannot be extended. Finally, you can pass JSON data directly, and it will be treated as an object and merged.

Examples:

class Person
{
    public string name;
    public float height;
    public float weight;
}
Person person = new Person();
person.height=60;
person.weight=170;

// or using anonymous classes

var person = new { name="", height=60, weight=170 };

// you can use @strings to improve readability, if you like
 
dynamic props = CsQuery.ParseJSON(@"{ 
                                      height: 72.5, 
                                      hairColor: 'brown'
                                    }");

// Merge data into an existing regular object

CsQuery.Extend(person,props);   // => person.height = 72.5
                                //    person.weight=170
                                //    hairColor is ignored since it couldn't be
                                //    added the the target

// Create a new ExpandoObject from existing objects

dynamic fullPerson = CsQuery.Extend(null,person, "{eyeColor: 'blue', noseType: 'ski-jump')");
                               // => fullPerson.height = 72.5
                               //    fullPerson.weight = 170
                               //    fullPerson.eyeColor="blue"
                               //    fullPerson.noseType="ski-jump"

// Extend an existing ExpandoObject

CsQuery.Extend(fullPerson,"{hairColor: 'brown'}");
                               // => fullPerson.height = 72.5
                               //    fullPerson.weight = 170
                               //    fullPerson.eyeColor="blue"
                               //    fullPerson.noseType="ski-jump"
                               //    fullPerson.hairColor="brown"

You can pass "null" as the first parameter (instead of an existing object), in which case an ExpandoObject containing the results of all the sources merged in order will be returned. Parameters may also be enumerable, in which case each member will be iterated and added to the list for processing.

Helper Methods - It's become clear that working with ExpandoObjects and JSON strings a lot is useful when passing data back and forth between client and server. It's also fundamental to how jQuery works. So in addition to adding support for JSON strings natively on methods where appropriate, and so I've added some extension methods to help with this, and other "javascript/C#" conversion issues:

public static bool IsJson(this string text)
True of the string appears to be JSON data (starts with "{" but not "{{")
public static bool IsTruthy(this object obj)
Resolves objects to "true" or "false" based on the Javascript equivalent truthyness of the data type and value.
public static object Clone(this object obj)
public static object Clone(this object obj, bool deep)
Returns a clone of any type of object, simplifying data structures to basic types. If the object is a value type, the value is returned. If it's enumerable, a List<object> is returned. If it's an object or an ExpandoObject, a new ExpandoObject is returned. The "deep" argument indicates that the values of each property should also be cloned.
public static ExpandoObject ToExpando(this object source)
public static ExpandoObject ToExpando(this object source, bool deep)
Converts a regular object to an ExpandoObject, or returns the original object if it's already an ExpandoObject (and deep=false)
public static string ToJSON(this object objectToSerialize)
public static T FromJSON(this string objectToDeserialize)
Convert to & from JSON. This is used directly by CsQuery.ParseJSON and CsQuery.ToJSON, but can also be used as extension methods of string and object. Many more methods and selector improvements added as a result of working through a lot of the jQuery tests. More to come...