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();
}
}
}
});
Resolutions to software engineering problems that I couldn't find anywhere else on the Internet. This is mostly here so I can find all my past solutions in one place.
Tuesday, April 29, 2014
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;
}
}
});
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;
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;
Subscribe to:
Posts (Atom)