Cymen's Blog

Archive for the ‘Programming’ Category

jQuery JCrop plugin, Chrome/Webkit: fails to initialize after the first attempt

leave a comment

I recently ran into a bug when using recent versions of Chrome and the jQuery JCrop plugin. I am adding some HTML to a jQuery UI modal window that contains the image I want to crop. I am initializing JCrop using the onload event of the image within this HTML. That worked fine in the past but stopped working sometime in the past year or so.

According to issue 7731 on chromium, the cause of this problem is that Webkit is more strict — the onload event is only triggered the first time the image is loaded. So the issue was the onload event triggered immediately when the modal window was shown — it triggered before the image was displayed (except for the first time). This is a tricky thing to work around. I verified that this was my issue by appending to the image URL the current timestamp (so + ‘?’ + new Date().getTime()) in order to force the browser to reload the image each time. That did fix the problem but it also introduced UI lag as the image had to be fetched each time a crop was attempted.

I put in this short term fix: put my JCrop initialization code in a function named crop and then (still bound to the load event), attempt to initialize it. If the image isn’t loaded, try again in 25ms up to 5 times. I can tell if the image is loaded by checking for a height > 0. The code:

    $('#cropbox').load(function (event) {
        var boxHeight = Math.floor($('#crop').closest('.ui-dialog').height() * 0.8);

        var $img = $(event.target);
        var productDiv = $('div#' + id);
        var sku = $('div.sku', productDiv).text();
        var product = productsData[sku];
        var height, width;

        // put crop loading code into function -- see comment below about chrome hack
        var crop = function () {
            height = $img.height();
            width = $img.width();

            var jcrop = $.Jcrop(
                ...
                });

            ...
        }

        // Ugly hack for chrome -- the load event triggers before the image is actually displayed/in DOM
        // but only on crop attempts after the initial one. One way to detect this is to check if the image
        // height is 0 -- if so, retry.
        // Update: issue probably this: http://code.google.com/p/chromium/issues/detail?id=7731#c12

        var worked = false;
        var attempts = 0;
        var attempt = function () {
            if (worked) return;

            height = $img.height();
            if (typeof height === 'number' && height > 0) {
                crop();
                worked = true;
            }
            else {
                attempts++;
                if (attempts < 5) {
                    // try again in 25 milliseconds
                    setTimeout(attempt, 25);
                }
                else {
                    alert('Bug with cropping image.');
                }
            }
        }

        attempt();
    });

I reported this problem in issue 63 for the JCrop plugin. Hopefully, there is a better way to do this however if you need a quick work around now...

Written by Cymen

November 10th, 2011 at 10:35 am

Mime types for ASP.NET

leave a comment

One of the annoyances working on the Windows/IIS stack is that getting mime types is a pain. They are located in multiple places and there is no really ideal “best practice” method to get mime types without what I consider overly-complicated solutions. In light of this observation I wrote a basic C# program that fetches the mime.types file from the Apache project and converts it to a C# Dictionary keyed by file extension. It is a basic program but might be useful for others wondering why in the world this is so complicated.

ApacheMimeTypesToDotNet on github

The output looks like this: ApacheMimeTypes.cs

using System;
using System.Collections.Generic;

namespace ApacheMimeTypes
{
	class Apache
	{
		public static Dictionary MimeTypes = new Dictionary
		{
			{ "123", "application/vnd.lotus-1-2-3" },
			{ "3dml", "text/vnd.in3d.3dml" },
			{ "3g2", "video/3gpp2" },
			{ "3gp", "video/3gpp" },
			{ "7z", "application/x-7z-compressed" },
			{ "aab", "application/x-authorware-bin" },
			{ "aac", "audio/x-aac" },
			{ "aam", "application/x-authorware-map" },
			{ "aas", "application/x-authorware-seg" },
			{ "abw", "application/x-abiword" },
			{ "ac", "application/pkix-attr-cert" },
			{ "acc", "application/vnd.americandynamics.acc" },
			{ "ace", "application/x-ace-compressed" },
			{ "acu", "application/vnd.acucobol" },
			{ "acutc", "application/vnd.acucorp" },
			{ "adp", "audio/adpcm" },
			{ "aep", "application/vnd.audiograph" },
			{ "afm", "application/x-font-type1" },
			{ "afp", "application/vnd.ibm.modcap" },
			{ "ahead", "application/vnd.ahead.space" },
			{ "ai", "application/postscript" },
			{ "aif", "audio/x-aiff" },
			{ "aifc", "audio/x-aiff" },
			{ "aiff", "audio/x-aiff" },
			{ "air", "application/vnd.adobe.air-application-installer-package+zip" },
			{ "ait", "application/vnd.dvb.ait" },
			{ "ami", "application/vnd.amiga.ami" },
			{ "apk", "application/vnd.android.package-archive" },
			{ "application", "application/x-ms-application" },
			{ "apr", "application/vnd.lotus-approach" },
			{ "asc", "application/pgp-signature" },
			{ "asf", "video/x-ms-asf" },
			{ "asm", "text/x-asm" },
			{ "aso", "application/vnd.accpac.simply.aso" },
			{ "asx", "video/x-ms-asf" },
			{ "atc", "application/vnd.acucorp" },
			{ "atom", "application/atom+xml" },
			{ "atomcat", "application/atomcat+xml" },
			{ "atomsvc", "application/atomsvc+xml" },
			{ "atx", "application/vnd.antix.game-component" },
			{ "au", "audio/basic" },
			{ "avi", "video/x-msvideo" },
			{ "aw", "application/applixware" },
			{ "azf", "application/vnd.airzip.filesecure.azf" },
			{ "azs", "application/vnd.airzip.filesecure.azs" },
			{ "azw", "application/vnd.amazon.ebook" },
			{ "bat", "application/x-msdownload" },
			{ "bcpio", "application/x-bcpio" },
			{ "bdf", "application/x-font-bdf" },
			{ "bdm", "application/vnd.syncml.dm+wbxml" },
			{ "bed", "application/vnd.realvnc.bed" },
			{ "bh2", "application/vnd.fujitsu.oasysprs" },
			{ "bin", "application/octet-stream" },
			{ "bmi", "application/vnd.bmi" },
			{ "bmp", "image/bmp" },
			{ "book", "application/vnd.framemaker" },
			{ "box", "application/vnd.previewsystems.box" },
			{ "boz", "application/x-bzip2" },
			{ "bpk", "application/octet-stream" },
			{ "btif", "image/prs.btif" },
			{ "bz", "application/x-bzip" },
			{ "bz2", "application/x-bzip2" },
			{ "c", "text/x-c" },
			{ "c11amc", "application/vnd.cluetrust.cartomobile-config" },
			{ "c11amz", "application/vnd.cluetrust.cartomobile-config-pkg" },
			{ "c4d", "application/vnd.clonk.c4group" },
			{ "c4f", "application/vnd.clonk.c4group" },
			{ "c4g", "application/vnd.clonk.c4group" },
			{ "c4p", "application/vnd.clonk.c4group" },
			{ "c4u", "application/vnd.clonk.c4group" },
			{ "cab", "application/vnd.ms-cab-compressed" },
			{ "car", "application/vnd.curl.car" },
			{ "cat", "application/vnd.ms-pki.seccat" },
			{ "cc", "text/x-c" },
			{ "cct", "application/x-director" },
			{ "ccxml", "application/ccxml+xml" },
			...
		};
	}
}

Written by Cymen

September 14th, 2011 at 12:28 pm

Yet another round on the ModelState PercentComplete() extension

one comment

So there were a few issues with the previous version — at least when I wanted to extend it for some custom calculations so here is yet another version:

        public static int PercentComplete(this ModelStateDictionary modelStateDictionary, int? ScalePercentWithValueAsZero = null, int? MaxValue = 100)
        {
            int totalItems = 0;
            int validItems = 0;
            int percentComplete = 0;

            if (MaxValue.HasValue && (MaxValue.Value < 0 || MaxValue.Value > 100))
                throw new ArgumentOutOfRangeException("MaxValue must between 0 and 100!");

            if (modelStateDictionary.IsValid)
            {
                percentComplete = MaxValue.Value;
            }
            else
            {
                foreach (var item in modelStateDictionary)
                {
                    totalItems++;
                    if (item.Value.Errors.Count == 0)
                        validItems++;
                }

                if (totalItems > 0)
                    percentComplete = (100 * validItems) / totalItems;

                if (ScalePercentWithValueAsZero.HasValue)
                {
                    if (ScalePercentWithValueAsZero.Value >= percentComplete)
                    {
                        percentComplete = 0;
                    }
                    else
                    {
                        percentComplete = Convert.ToInt32(Math.Ceiling((double)(percentComplete - ScalePercentWithValueAsZero.Value) / (100 - ScalePercentWithValueAsZero.Value) * 100));
                    }
                }

                if (MaxValue.HasValue)
                {
                    percentComplete = percentComplete * MaxValue.Value / 100;
                }
            }

            return percentComplete;
        }

And if a model has a particularly complicated percentage complete calculation in which one needs to manually check some things and add to the total that can be done:

        public int CustomPercentComplete(Func<int?, int?, int> PercentComplete)
        {
            // scale percent complete 50-100% as 0-100%
            int basePercentAsZero = 50;

            // actually, scale 50-100% as 0-91%
            int max = 91;            

            // call default PercentComplete
            int percentComplete = PercentComplete(basePercentAsZero, max);

            // implement here your custom percente complete for the remaining 9%

            return percentComplete;
        }

An example of calling the custom percentage complete calculator on your model:

        myModelInstance.CustomPercentComplete(ModelState.PercentComplete);

And if you need to call either calculator but your controller action doesn’t bind to an instance of the model you need to calculate the percentage on, you can add a method in your controller like this one (hopefully there is a better way — let me know if you know of one):

        // Work around for getting percentage complete when in another action where the model is not the application
        // like so:
        // int percentComplete = MyPercentComplete(application);
        private int MyPercentComplete(MyApplication application)
        {
            return Convert.ToInt32(MyPercentCompleteAction(application).Content);
        }

        // Work around for getting percentage complete when in another action where the model is not the application
        // like so:
        // int percentComplete = Convert.ToInt32(MyPercentComplete(application).Content);
        private ContentResult MyPercentCompleteAction(MyApplication application)
        {
            return Content(application.CustomPercentComplete(ModelState.PercentComplete).ToString());
        }

Written by Cymen

August 16th, 2011 at 10:09 am

Posted in ASP.NET MVC,C#

Published PHPGatewayInterface to github…

leave a comment

I wrote a hopefully generic class in PHP to proxy CGI scripts. The idea being that sometimes one wants to get the output of a CGI script and do various things with it before displaying it. In my particular case, wrap CVSWeb so the output can be put into a HTML DIV.

The code is published on github as PHPGatewayInterface.

Written by Cymen

August 29th, 2009 at 11:51 pm

Posted in PHP

Regular Expression Tools

leave a comment

Regular Expressions tools:

Written by Cymen

June 13th, 2009 at 6:54 pm

Shooting Yourself in the Foot with Perl

leave a comment

Returning Multiple Values

$error, $hash = function_a( ... );
if ( $error ) { error(); }    // never runs

Correction:

($error, $hash) = function_a( ... );
if ( $error ) { error(); }    // runs on error

Written by Cymen

December 22nd, 2008 at 10:14 am

Posted in Programming

Tagged with