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;
        }
    }
});

No comments:

Post a Comment