Tuesday, April 29, 2014

Integer-Only Textbox in jQuery

I often have the need to only allow certain inputs in a textbox.  This is an example of how to limit the users' input to only numbers, backspace, delete, tab, and the arrow keys.  All other keys will be interrupted by a preventDefault() call that stops their default action from executing.  Bind this to the keypress or keydown event (you can also use keyup, but I don't like the way that works).

$("off", "keydown");
$("on", "keydown", function(e) {
    if (e.shiftKey) {
        e.preventDefault();
    } else {
        var nKeyCode = e.keyCode;
        //Ignore Backspace, Delete and Tab keys
        if (nKeyCode == 8 ||
            nKeyCode == 9 ||
            nKeyCode == 46 ||
            (e.keyCode >= 37 && e.keyCode <= 40)) {
            return;
        }
        if (nKeyCode < 95) {
            if (nKeyCode < 48 || nKeyCode > 57) {
                e.preventDefault();
            }
        } else {
            if (nKeyCode < 96 || nKeyCode > 105) {
                e.preventDefault();
            }
        }
    }
});

Angular JS Directives

I'm currently working on a project where we're refactoring a pretty standard MVC3 application to utilize plain HTML for the markup, with Angular JS as the MVVM structure and a Web API RESTful services implementation.  Up until the last week or so my part has been mostly limited to the server so I haven't done much with Angular.  Today, however, I got the opportunity to create a directive for the first time and it was awesome.

I had cause to apply a max length to a textarea element in IE 8 (yes, we're supporting IE 8, go ahead and feel sorry for us), which doesn't support HTML5.  There's a pretty awesome jQuery plug-in called jQuery Max Length that worked really well for us in the MVC3 implementation, but it wasn't the "Angular way" so we tried to port it over to a directive.

It took me about four hours to get it all put together, but I'm really happy with what I ended up with.  This directive works in IE 8 (though it could be a lot better to be honest) and it's "fast enough".  We didn't spend a ton of time tweaking this for performance in IE 8 because as an organization we're moving away from it in the near future (that's what they tell me anyway).  So here it is.  I have some modifications I want to make to it, but I'm happy with what I have here.

app.directive('myTextarea', function () {
    'use strict';

    return {
        restrict: 'A',
        replace: true,
        require: 'ngModel',
        template: function (el, attr) {
            return '<div><textarea ng-model=' + attr.ngModel +
                ' name=' + attr.name +
                ' class="' + attr.parentClass +
                '" max=' + attr.max +
                '></textarea><br /><span class="' +
                attr.childClass + '">0/' + attr.max +
                '</span></div>';
        },
        link: function (scope, el, attr, ctrl) {
            scope.$watch(function() { return ctrl.$modelValue; }, function() {

                // we have to get value from the DOM because Angular by default trims the input
                // the problem with that is if you're typing and backspace over a word, you also
                // (unwittingly) backspace over the space before the word
                var value = $(el).children('textarea').val();
                var resultingValue = value;
             
                    if (value.length == attr.max) {
                        $(el).children('textarea').addClass('maxlength-full');
                        changeColors($(el).children('span'), 'red');
                    }
                    else if (value.length > attr.max) {
                        $(el).children('textarea').addClass('maxlength-full');
                        changeColors($(el).children('span'), 'red');
                        resultingValue = value.substring(0, attr.max);
                    } else {
                        $(el).children('textarea').removeClass('maxlength-full');
                        if (value.length > (attr.max * .9)) {
                            changeColors($(el).children('span'), 'yellow');
                        } else {
                            changeColors($(el).children('span'), 'green');
                        }
                    }

                ctrl.$viewValue = resultingValue;
                $(el).children(1).val(ctrl.$viewValue);

                $(el).children('span').text(ctrl.$viewValue.length + '/' + attr.max);
            });
        }
    };
 
    function changeColors(el, color) {
        switch(color) {
            case 'red':
                el.removeClass('maxlength-feedback-green');
                el.removeClass('maxlength-feedback-yellow');
                el.addClass('maxlength-feedback-red');
                break;
            case 'yellow':
                el.removeClass('maxlength-feedback-green');
                el.removeClass('maxlength-feedback-red');
                el.addClass('maxlength-feedback-yellow');
                break;
            case 'green':
                el.removeClass('maxlength-feedback-yellow');
                el.removeClass('maxlength-feedback-red');
                el.addClass('maxlength-feedback-green');
                break;
        }
    }
});

Monday, April 28, 2014

Drop foreign keys

Sometimes you need to drop a table.  Sometimes that table doesn't want to be dropped.  It fights you like a two-year-old who was just told no more ice cream after they polished off the entire half gallon.  It's ugly.

Usually when this happens to me it's caused by foreign key references, either the table being dropped is referencing another table or another table is referencing the table to be dropped (yes, those are different things).  The script below should create a script to drop all foreign keys referencing the table to be dropped as well as all foreign keys in the table to be dropped.

I've only used this a couple of times and it worked like a charm.  You should note, however, that those constraints are probably there for a reason so you should seriously consider what you're doing before you arbitrarily drop them.

I got a large part of this from a StackOverflow answer here.

DECLARE @TARGET AS VARCHAR(10); -- SET to 'DROP' or 'CREATE' 
DECLARE @TARGET_TABLE AS SYSNAME; -- TABLE WHOSE FOREIGN-KEY WILL BE SCRIPTED 
DECLARE @TARGET_SCHEMA AS SYSNAME; -- SCHEMA OF THE TABLE 
SET @TARGET = 'DROP'; 
SET @TARGET_SCHEMA = 'dbo'; 
SET @TARGET_TABLE = 'TableToDrop'; 

-- Other variables 
DECLARE @schema_name SYSNAME; 
DECLARE @table_name SYSNAME; 
DECLARE @constraint_name SYSNAME; 
DECLARE @constraint_object_id INT; 
DECLARE @referenced_object_name SYSNAME; 
DECLARE @is_disabled BIT; 
DECLARE @is_not_for_replication BIT; 
DECLARE @is_not_trusted BIT; 
DECLARE @delete_referential_action TINYINT; 
DECLARE @update_referential_action TINYINT; 
DECLARE @tsql NVARCHAR(4000); 
DECLARE @tsql2 NVARCHAR(4000); 
DECLARE @fkCol SYSNAME; 
DECLARE @pkCol SYSNAME; 
DECLARE @col1 BIT; 
DECLARE fkcursor CURSOR FOR 
  (SELECT Object_schema_name(parent_object_id), 
          Object_name(parent_object_id), 
          name, 
          Object_name(referenced_object_id), 
          object_id, 
          is_disabled, 
          is_not_for_replication, 
          is_not_trusted, 
          delete_referential_action, 
          update_referential_action 
   FROM   sys.foreign_keys 
   WHERE  Object_name(parent_object_id) = @TARGET_TABLE 
          AND Object_schema_name(parent_object_id) = @TARGET_SCHEMA) 
  UNION ALL 
  (SELECT Object_schema_name(parent_object_id), 
          Object_name(parent_object_id), 
          name, 
          Object_name(referenced_object_id), 
          object_id, 
          is_disabled, 
          is_not_for_replication, 
          is_not_trusted, 
          delete_referential_action, 
          update_referential_action 
   FROM   sys.foreign_keys 
   WHERE  Object_name(referenced_object_id) = @TARGET_TABLE 
          AND Object_schema_name(parent_object_id) = @TARGET_SCHEMA) 
  ORDER  BY 1, 
            2; 

OPEN fkcursor; 

FETCH next FROM fkcursor INTO @schema_name, @table_name, @constraint_name, 
@referenced_object_name, @constraint_object_id, @is_disabled, 
@is_not_for_replication, @is_not_trusted, @delete_referential_action, 
@update_referential_action; 

WHILE @@FETCH_STATUS = 0 
  BEGIN 
      IF @TARGET <> 'CREATE' 
        SET @tsql = 'ALTER TABLE ' + Quotename(@schema_name) + '.' 
                    + Quotename(@table_name) 
                    + ' DROP CONSTRAINT ' 
                    + Quotename(@constraint_name) + ';'; 
      ELSE 
        BEGIN 
            SET @tsql = 'ALTER TABLE ' + Quotename(@schema_name) + '.' 
                        + Quotename(@table_name) + CASE @is_not_trusted WHEN 0 
                        THEN 
                        ' WITH CHECK ' ELSE ' WITH NOCHECK ' END 
                        + ' ADD CONSTRAINT ' 
                        + Quotename(@constraint_name) 
                        + ' FOREIGN KEY (' 
            SET @tsql2 = ''; 

            DECLARE columncursor CURSOR FOR 
              SELECT Col_name(fk.parent_object_id, fkc.parent_column_id), 
                     Col_name(fk.referenced_object_id, fkc.referenced_column_id) 
              FROM   sys.foreign_keys fk 
                     INNER JOIN sys.foreign_key_columns fkc 
                             ON fk.object_id = fkc.constraint_object_id 
              WHERE  fkc.constraint_object_id = @constraint_object_id 
              ORDER  BY fkc.constraint_column_id; 

            OPEN columncursor; 

            SET @col1 = 1; 

            FETCH next FROM columncursor INTO @fkCol, @pkCol; 

            WHILE @@FETCH_STATUS = 0 
              BEGIN 
                  IF ( @col1 = 1 ) 
                    SET @col1 = 0 
                  ELSE 
                    BEGIN 
                        SET @tsql = @tsql + ','; 
                        SET @tsql2 = @tsql2 + ','; 
                    END; 

                  SET @tsql = @tsql + Quotename(@fkCol); 
                  SET @tsql2 = @tsql2 + Quotename(@pkCol); 

                  FETCH next FROM columncursor INTO @fkCol, @pkCol; 
              END; 

            CLOSE columncursor; 

            DEALLOCATE columncursor; 

            SET @tsql = @tsql + ' ) REFERENCES ' 
                        + Quotename(@schema_name) + '.' 
                        + Quotename(@referenced_object_name) + ' (' 
                        + @tsql2 + ')'; 
            SET @tsql = @tsql + ' ON UPDATE ' + CASE @update_referential_action 
                        WHEN 0 
                        THEN 'NO ACTION ' 
                        WHEN 1 THEN 'CASCADE ' WHEN 2 THEN 'SET NULL ' ELSE 
                        'SET DEFAULT ' 
                        END + ' ON DELETE ' + CASE @delete_referential_action 
                        WHEN 
                        0 
                        THEN 
                        'NO ACTION ' WHEN 1 THEN 'CASCADE ' WHEN 2 THEN 
                        'SET NULL ' 
                        ELSE 
                        'SET DEFAULT ' END + CASE @is_not_for_replication WHEN 1 
                        THEN 
                        ' NOT FOR REPLICATION ' ELSE '' END 
                        + ';'; 
        END; 

      PRINT @tsql; 

      IF @TARGET = 'CREATE' 
        BEGIN 
            SET @tsql = 'ALTER TABLE ' + Quotename(@schema_name) + '.' 
                        + Quotename(@table_name) + CASE @is_disabled WHEN 0 THEN 
                        ' CHECK ' 
                        ELSE ' NOCHECK ' END + 'CONSTRAINT ' 
                        + Quotename(@constraint_name) + ';'; 

            PRINT @tsql; 
        END; 

      FETCH next FROM fkcursor INTO @schema_name, @table_name, @constraint_name, 
      @referenced_object_name, @constraint_object_id, @is_disabled, 
      @is_not_for_replication, @is_not_trusted, @delete_referential_action, 
      @update_referential_action; 
  END; 

CLOSE fkcursor; 

DEALLOCATE fkcursor;