<commit_before>/*********************************************
 *  Copyright (c) 2014 AnsibleWorks, Inc.
 */
  /**
 *  @ngdoc function
 *  @name lib.ansible.function:directives
 *  @description
 * Custom directives for form validation
 *
 */

'use strict';

/* global chkPass:false */

angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'JobsHelper'])

    // awpassmatch:  Add to password_confirm field. Will test if value
    //               matches that of 'input[name="password"]'
    .directive('awpassmatch', function() {
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {
                ctrl.$parsers.unshift( function(viewValue) {
                    var associated = attrs.awpassmatch,
                        password = $('input[name="' + associated + '"]').val();
                    if (viewValue === password) {
                        // it is valid
                        ctrl.$setValidity('awpassmatch', true);
                        return viewValue;
                    }
                    // Invalid, return undefined (no model update)
                    ctrl.$setValidity('awpassmatch', false);
                    return undefined;
                });
            }
        };
    })

    // caplitalize  Add to any input field where the first letter of each
    //              word should be capitalized. Use in place of css test-transform.
    //              For some reason "text-transform: capitalize" in breadcrumbs
    //              causes a break at each blank space. And of course,
    //              "autocapitalize='word'" only works in iOS. Use this as a fix.
    .directive('capitalize', function() {
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {
                ctrl.$parsers.unshift( function(viewValue) {
                    var values = viewValue.split(" "),
                        result = "", i;
                    for (i = 0; i < values.length; i++){
                        result += values[i].charAt(0).toUpperCase() + values[i].substr(1) + ' ';
                    }
                    result = result.trim();
                    if (result !== viewValue) {
                        ctrl.$setViewValue(result);
                        ctrl.$render();
                    }
                    return result;
                });
            }
        };
    })


.directive('surveyCheckboxes', function(){
    return {
        restrict: 'E',
        require: 'ngModel',
        scope: { ngModel: '=ngModel' },
        template: '<div class="survey_taker_input" ng-repeat="option in ngModel.options">' +
            '<input type="checkbox" ng-model="cbModel[option.value]" ' +
            'value="{{option.value}}" class="mc" ng-change="update(this.value)" />' +
            '<span>'+
            '{{option.value}}'+
            '</span>'+
            '</div>',
        link: function(scope, element, attrs, ctrl){
            scope.cbModel= {};
            ctrl.$setValidity('reqCheck', true);
            angular.forEach(scope.ngModel.value, function(value){
                scope.cbModel[value] = true;

            });

            if(scope.ngModel.required===true && scope.ngModel.value.length===0){
                ctrl.$setValidity('reqCheck', false);
            }

            ctrl.$parsers.unshift(function(){
                for (var c in scope.cbModel) {
                    if (scope.cbModel[c]) {
                        ctrl.$setValidity('checkbox', true);
                    }
                }
                ctrl.$setValidity('checkbox', false);
            });

            scope.update = function(){
                var val = [];
                angular.forEach(scope.cbModel, function(v,k){
                    if (v) {
                        val.push(k);
                    }
                });
                if (val.length>0){
                    scope.ngModel.value = val;
                    scope.$parent[scope.ngModel.name] = val;
                    ctrl.$setValidity('checkbox', true);
                    ctrl.$setValidity('reqCheck', true);
                }
                 else if(scope.ngModel.required===true){
                    ctrl.$setValidity('checkbox' , false);
                }
            };
        }
    };
})


    .directive('awSurveyQuestion', function() {
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {
                ctrl.$parsers.unshift( function(viewValue) {
                    var values = viewValue.split(" "),
                        result = "", i;
                    result += values[0].charAt(0).toUpperCase() + values[0].substr(1) + ' ';
                    for (i = 1; i < values.length; i++){
                        result += values[i] + ' ';
                    }
                    result = result.trim();
                    if (result !== viewValue) {
                        ctrl.$setViewValue(result);
                        ctrl.$render();
                    }
                    return result;
                });
            }
        };
    })

    .directive('awMin', ['Empty', function (Empty) {
        return {
            restrict: 'A',
            require: 'ngModel',
            link: function (scope, elem, attr, ctrl) {
                ctrl.$parsers.unshift( function(viewValue) {
                    var min = (attr.awMin) ? scope.$eval(attr.awMin)  :  -Infinity;
                    if (!Empty(min) && !Empty(viewValue) && Number(viewValue) < min) {
                        ctrl.$setValidity('awMin', false);
                        return undefined;
                    } else {
                        ctrl.$setValidity('awMin', true);
                        return viewValue;
                    }
                });
            }
        };
    }])

    .directive('awMax', ['Empty', function (Empty) {
        return {
            restrict: 'A',
            require: 'ngModel',
            link: function (scope, elem, attr, ctrl) {
                ctrl.$parsers.unshift( function(viewValue) {
                    var max = (attr.awMax) ? scope.$eval(attr.awMax)  :  Infinity;
                    if (!Empty(max) && !Empty(viewValue) && Number(viewValue) >  max) {
                        ctrl.$setValidity('awMax', false);
                        return undefined;
                    } else {
                        ctrl.$setValidity('awMax', true);
                        return viewValue;
                    }
                });
            }
        };
    }])

    .directive('smartFloat', function() {
        var FLOAT_REGEXP = /^\-?\d+((\.|\,)\d+)?$/;
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {
                ctrl.$parsers.unshift(function(viewValue) {
                    if (FLOAT_REGEXP.test(viewValue)) {
                        ctrl.$setValidity('float', true);
                        return parseFloat(viewValue.replace(',', '.'));
                    } else {
                        ctrl.$setValidity('float', false);
                        return undefined;
                    }
                });
            }
        };
    })

    // integer  Validate that input is of type integer. Taken from Angular developer
    //          guide, form examples. Add min and max directives, and this will check
    //          entered values is within the range.
    //
    //          Use input type of 'text'. Use of 'number' casuses browser validation to
    //          override/interfere with this directive.
    .directive('integer', function() {
        return {
            restrict: 'A',
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {
                ctrl.$parsers.unshift(function(viewValue) {
                    ctrl.$setValidity('min', true);
                    ctrl.$setValidity('max', true);
                    if (/^\-?\d*$/.test(viewValue)) {
                       // it is valid
                        ctrl.$setValidity('integer', true);
                        if ( elm.attr('min') &&
                            ( viewValue === '' || viewValue === null || parseInt(viewValue,10) < parseInt(elm.attr('min'),10) ) ) {
                            ctrl.$setValidity('min', false);
                            return undefined;
                        }
                        if ( elm.attr('max') && ( parseInt(viewValue,10) > parseInt(elm.attr('max'),10) ) ) {
                            ctrl.$setValidity('max', false);
                            return undefined;
                        }
                        return viewValue;
                    }
                    // Invalid, return undefined (no model update)
                    ctrl.$setValidity('integer', false);
                    return undefined;
                });
            }
        };
    })

    //the awSurveyVariableName directive checks if the field contains any spaces.
    // this could be elaborated in the future for other things we want to check this field against
    .directive('awSurveyVariableName', function() {
        var FLOAT_REGEXP = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/;
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {
                ctrl.$parsers.unshift(function(viewValue) {
                    if (FLOAT_REGEXP.test(viewValue) && viewValue.indexOf(' ') === -1) { //check for a spaces
                        ctrl.$setValidity('variable', true);
                        return viewValue;
                    } else {
                        ctrl.$setValidity('variable', false); // spaces found, therefore throw error
                        return undefined;
                    }
                });
            }
        };
    })

    //
    // awRequiredWhen: { variable: "<variable to watch for true|false>", init:"true|false" }
    //
    // Make a field required conditionally using a scope variable. If the scope variable is true, the
    // field will be required. Otherwise, the required attribute will be removed.
    //
    .directive('awRequiredWhen', function() {
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {

                function checkIt () {
                    var viewValue = elm.val(), label, validity = true;
                    if ( scope[attrs.awRequiredWhen] && (elm.attr('required') === null || elm.attr('required') === undefined) ) {
                        $(elm).attr('required','required');
                        if ($(elm).hasClass('lookup')) {
                            $(elm).parent().parent().parent().find('label').first().addClass('prepend-asterisk');
                        }
                        else {
                            $(elm).parent().parent().find('label').first().addClass('prepend-asterisk');
                        }
                    }
                    else if (!scope[attrs.awRequiredWhen]) {
                        elm.removeAttr('required');
                        if ($(elm).hasClass('lookup')) {
                            label = $(elm).parent().parent().parent().find('label').first();
                            label.removeClass('prepend-asterisk');
                        }
                        else {
                            $(elm).parent().parent().find('label').first().removeClass('prepend-asterisk');
                        }
                    }
                    if (scope[attrs.awRequiredWhen] && (viewValue === undefined || viewValue === null || viewValue === '')) {
                        validity = false;
                    }
                    ctrl.$setValidity('required', validity);
                }

                if (attrs.awrequiredInit !== undefined && attrs.awrequiredInit !== null) {
                    scope[attrs.awRequiredWhen] = attrs.awrequiredInit;
                    checkIt();
                }

                scope.$watch(attrs.awRequiredWhen, function() {
                    // watch for the aw-required-when expression to change value
                    checkIt();
                });

                scope.$watch($(elm).attr('name'), function() {
                    // watch for the field to change value
                    checkIt();
                });
            }
        };
    })

    // awPlaceholder: Dynamic placeholder set to a scope variable you want watched.
    //                Value will be place in field placeholder attribute.
    .directive('awPlaceholder', [ function() {
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs) {
                $(elm).attr('placeholder', scope[attrs.awPlaceholder]);
                scope.$watch(attrs.awPlaceholder, function(newVal) {
                    $(elm).attr('placeholder',newVal);
                });
            }
        };
    }])

    // lookup   Validate lookup value against API
    //
    .directive('awlookup', ['Rest', function(Rest) {
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {
                ctrl.$parsers.unshift( function(viewValue) {
                    if (viewValue !== '' && viewValue !== null) {
                        var url = elm.attr('data-url');
                        url = url.replace(/\:value/, encodeURI(viewValue));
                        scope[elm.attr('data-source')] = null;
                        Rest.setUrl(url);
                        Rest.get().then( function(data) {
                            var results = data.data.results;
                            if (results.length > 0) {
                                scope[elm.attr('data-source')] = results[0].id;
                                scope[elm.attr('name')] = results[0].name;
                                ctrl.$setValidity('required', true);
                                ctrl.$setValidity('awlookup', true);
                                return viewValue;
                            }
                            ctrl.$setValidity('required', true);
                            ctrl.$setValidity('awlookup', false);
                            return undefined;
                        });
                    }
                    else {
                        ctrl.$setValidity('awlookup', true);
                        scope[elm.attr('data-source')] = null;
                    }
                });
            }
        };
    }])

    //
    // awValidUrl
    //
    .directive('awValidUrl', [ function() {
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {
                ctrl.$parsers.unshift( function(viewValue) {
                    var validity = true, rgx, rgx2;
                    if (viewValue !== '') {
                        ctrl.$setValidity('required', true);
                        rgx = /^(https|http|ssh)\:\/\//;
                        rgx2 = /\@/g;
                        if (!rgx.test(viewValue) || rgx2.test(viewValue)) {
                            validity = false;
                        }
                    }
                    ctrl.$setValidity('awvalidurl', validity);
                });
            }
        };
    }])

    /*
     *  Enable TB tooltips. To add a tooltip to an element, include the following directive in
     *  the element's attributes:
     *
     *     aw-tool-tip="<< tooltip text here >>"
     *
     *  Include the standard TB data-XXX attributes to controll a tooltip's appearance.  We will
     *  default placement to the left and delay to the config setting.
     */
    .directive('awToolTip', ['$sce', function($sce) {
        return function(scope, element, attrs) {
            var delay = (attrs.delay !== undefined && attrs.delay !== null) ? attrs.delay : ($AnsibleConfig) ? $AnsibleConfig.tooltip_delay : {show: 500, hide: 100},
                placement;
            if (attrs.awTipPlacement) {
                placement = attrs.awTipPlacement;
            }
            else {
                placement = (attrs.placement !== undefined && attrs.placement !== null) ? attrs.placement : 'left';
            }

            $(element).on('hidden.bs.tooltip', function( ) {
                // TB3RC1 is leaving behind tooltip <div> elements. This will remove them
                // after a tooltip fades away. If not, they lay overtop of other elements and
                // honk up the page.
                $('.tooltip').each(function() {
                    $(this).remove();
                });
            });

            attrs.awToolTip = attrs.awToolTip.replace(/</g, "&lt;");
            attrs.awToolTip = attrs.awToolTip.replace(/>/g, "&gt;");
            attrs.awToolTip = $sce.getTrustedHtml(attrs.awToolTip);
            $(element).tooltip({
                placement: placement,
                delay: delay,
                html: true,
                title: attrs.awToolTip,
                container: 'body',
                trigger: 'hover focus'
            });

            if (attrs.tipWatch) {
                // Add dataTipWatch: 'variable_name'
                scope.$watch(attrs.tipWatch, function(newVal, oldVal) {
                    if (newVal !== oldVal) {
                        // Where did fixTitle come from?:
                        //   http://stackoverflow.com/questions/9501921/change-twitter-bootstrap-tooltip-content-on-click
                        $(element).tooltip('hide').attr('data-original-title', newVal).tooltip('fixTitle');
                    }
                });
            }
        };
    }])

    /*
     * This is a copy of awToolTip currently.
     * TODO: only display these tool tips if the length of the anchor *as interpolated* to be larger than the table cell
     */
    .directive('awToolTipEllipses', [ function() {
        return {
            link: function(scope, element, attrs) {

                var delay = (attrs.delay !== undefined && attrs.delay !== null) ? attrs.delay : ($AnsibleConfig) ? $AnsibleConfig.tooltip_delay : {show: 500, hide: 100},
                    placement;

                if (attrs.awTipPlacement) {
                    placement = attrs.awTipPlacement;
                }
                else {
                    placement = (attrs.placement !== undefined && attrs.placement !== null) ? attrs.placement : 'left';
                }

                $(element).on('hidden.bs.tooltip', function( ) {
                    // TB3RC1 is leaving behind tooltip <div> elements. This will remove them
                    // after a tooltip fades away. If not, they lay overtop of other elements and
                    // honk up the page.
                    $('.tooltip').each(function() {
                        $(this).remove();
                    });
                });

                $(element).tooltip({
                    placement: placement,
                    delay: delay,
                    html: true,
                    title: attrs.awToolTipEllipses,
                    container: 'body',
                    trigger: 'hover focus'
                });
            }
        };
    }])

    /*
     *  Enable TB pop-overs. To add a pop-over to an element, include the following directive in
     *  the element's attributes:
     *
     *     aw-pop-over="<< pop-over html here >>"
     *
     *  Include the standard TB data-XXX attributes to controll the pop-over's appearance.  We will
     *  default placement to the left, delay to 0 seconds, content type to HTML, and title to 'Help'.
     */
    .directive('awPopOver', ['$compile', function($compile) {
        return function(scope, element, attrs) {
            var placement = (attrs.placement !== undefined && attrs.placement !== null) ? attrs.placement : 'left',
                title = (attrs.title) ? attrs.title : (attrs.popoverTitle) ? attrs.popoverTitle : 'Help',
                container = (attrs.container !== undefined) ? attrs.container : false,
                trigger = (attrs.trigger !== undefined) ? attrs.trigger : 'manual',
                template = '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>',
                id_to_close = "";

            if (element[0].id) {
                template = '<div id="' + element[0].id + '_popover_container" class="popover" role="tooltip"><div class="arrow"></div><h3 id="' + element[0].id + '_popover_title" class="popover-title"></h3><div id="' + element[0].id + '_popover_content" class="popover-content"></div></div>';
            }

            if (attrs.awPopOverWatch) {
                $(element).popover({
                    placement: placement,
                    delay: 0,
                    title: title,
                    content: function() {
                        return scope[attrs.awPopOverWatch];
                    },
                    trigger: trigger,
                    html: true,
                    container: container,
                    template: template
                });
            } else {
                $(element).popover({
                    placement: placement,
                    delay: 0,
                    title: title,
                    content: attrs.awPopOver,
                    trigger: trigger,
                    html: true,
                    container: container,
                    template: template
                });
            }
            $(element).attr('tabindex',-1);

            $(element).one('click', showPopover);

            $(element).on('shown.bs.popover', function() {
                $('body').one('click.popover' + id_to_close, function(e) {
                    if ($(e.target).parents(id_to_close).length === 0) {
                        $(element).popover('hide');
                    }
                });

                $(document).on('keydown.popover', dismissOnEsc);

            });

            $(element).on('hidden.bs.popover', function() {
                $(element).off('click', dismissPopover);
                $(element).off('click', showPopover);
                $('body').off('click.popover.' + id_to_close);
                $(element).one('click', showPopover);
                $(document).off('keydown.popover', dismissOnEsc);
            });

            function showPopover(e) {
                console.log(element);
                e.stopPropagation();

                var self = $(this);

                // remove tool-tip
                try {
                    element.tooltip('hide');
                }
                catch(e) {
                    // ignore
                }

                // this is called on the help-link (over and over again)
                $('.help-link, .help-link-white').each( function() {
                    if (self.attr('id') !== $(this).attr('id')) {
                        try {
                            // not sure what this does different than the method above
                            $(this).popover('hide');
                        }
                        catch(e) {
                            // ignore
                        }
                    }
                });

                $('.popover').each(function() {
                    // remove lingering popover <div>. Seems to be a bug in TB3 RC1
                    $(this).remove();
                });
                $('.tooltip').each( function() {
                    // close any lingering tool tips
                    $(this).hide();
                });

                // set id_to_close of the actual open element
                id_to_close = "#" + $(element).attr('id') + "_popover_container";

                // $(element).one('click', dismissPopover);

                $(this).popover('toggle');

                $('.popover').each(function() {
                    $compile($(this))(scope);  //make nested directives work!
                });
            }

            function dismissPopover(e) {
                e.stopPropagation();
                $(element).popover('hide');
            }

            function dismissOnEsc(e) {
                if (e.keyCode === 27) {
                    $(element).popover('hide');
                    $('.popover').each(function() {<commit_msg>Fix jshint errors<commit_after>/*********************************************
 *  Copyright (c) 2014 AnsibleWorks, Inc.
 */
  /**
 *  @ngdoc function
 *  @name lib.ansible.function:directives
 *  @description
 * Custom directives for form validation
 *
 */

'use strict';

/* global chkPass:false */

angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'JobsHelper'])

    // awpassmatch:  Add to password_confirm field. Will test if value
    //               matches that of 'input[name="password"]'
    .directive('awpassmatch', function() {
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {
                ctrl.$parsers.unshift( function(viewValue) {
                    var associated = attrs.awpassmatch,
                        password = $('input[name="' + associated + '"]').val();
                    if (viewValue === password) {
                        // it is valid
                        ctrl.$setValidity('awpassmatch', true);
                        return viewValue;
                    }
                    // Invalid, return undefined (no model update)
                    ctrl.$setValidity('awpassmatch', false);
                    return undefined;
                });
            }
        };
    })

    // caplitalize  Add to any input field where the first letter of each
    //              word should be capitalized. Use in place of css test-transform.
    //              For some reason "text-transform: capitalize" in breadcrumbs
    //              causes a break at each blank space. And of course,
    //              "autocapitalize='word'" only works in iOS. Use this as a fix.
    .directive('capitalize', function() {
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {
                ctrl.$parsers.unshift( function(viewValue) {
                    var values = viewValue.split(" "),
                        result = "", i;
                    for (i = 0; i < values.length; i++){
                        result += values[i].charAt(0).toUpperCase() + values[i].substr(1) + ' ';
                    }
                    result = result.trim();
                    if (result !== viewValue) {
                        ctrl.$setViewValue(result);
                        ctrl.$render();
                    }
                    return result;
                });
            }
        };
    })


.directive('surveyCheckboxes', function(){
    return {
        restrict: 'E',
        require: 'ngModel',
        scope: { ngModel: '=ngModel' },
        template: '<div class="survey_taker_input" ng-repeat="option in ngModel.options">' +
            '<input type="checkbox" ng-model="cbModel[option.value]" ' +
            'value="{{option.value}}" class="mc" ng-change="update(this.value)" />' +
            '<span>'+
            '{{option.value}}'+
            '</span>'+
            '</div>',
        link: function(scope, element, attrs, ctrl){
            scope.cbModel= {};
            ctrl.$setValidity('reqCheck', true);
            angular.forEach(scope.ngModel.value, function(value){
                scope.cbModel[value] = true;

            });

            if(scope.ngModel.required===true && scope.ngModel.value.length===0){
                ctrl.$setValidity('reqCheck', false);
            }

            ctrl.$parsers.unshift(function(){
                for (var c in scope.cbModel) {
                    if (scope.cbModel[c]) {
                        ctrl.$setValidity('checkbox', true);
                    }
                }
                ctrl.$setValidity('checkbox', false);
            });

            scope.update = function(){
                var val = [];
                angular.forEach(scope.cbModel, function(v,k){
                    if (v) {
                        val.push(k);
                    }
                });
                if (val.length>0){
                    scope.ngModel.value = val;
                    scope.$parent[scope.ngModel.name] = val;
                    ctrl.$setValidity('checkbox', true);
                    ctrl.$setValidity('reqCheck', true);
                }
                 else if(scope.ngModel.required===true){
                    ctrl.$setValidity('checkbox' , false);
                }
            };
        }
    };
})


    .directive('awSurveyQuestion', function() {
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {
                ctrl.$parsers.unshift( function(viewValue) {
                    var values = viewValue.split(" "),
                        result = "", i;
                    result += values[0].charAt(0).toUpperCase() + values[0].substr(1) + ' ';
                    for (i = 1; i < values.length; i++){
                        result += values[i] + ' ';
                    }
                    result = result.trim();
                    if (result !== viewValue) {
                        ctrl.$setViewValue(result);
                        ctrl.$render();
                    }
                    return result;
                });
            }
        };
    })

    .directive('awMin', ['Empty', function (Empty) {
        return {
            restrict: 'A',
            require: 'ngModel',
            link: function (scope, elem, attr, ctrl) {
                ctrl.$parsers.unshift( function(viewValue) {
                    var min = (attr.awMin) ? scope.$eval(attr.awMin)  :  -Infinity;
                    if (!Empty(min) && !Empty(viewValue) && Number(viewValue) < min) {
                        ctrl.$setValidity('awMin', false);
                        return undefined;
                    } else {
                        ctrl.$setValidity('awMin', true);
                        return viewValue;
                    }
                });
            }
        };
    }])

    .directive('awMax', ['Empty', function (Empty) {
        return {
            restrict: 'A',
            require: 'ngModel',
            link: function (scope, elem, attr, ctrl) {
                ctrl.$parsers.unshift( function(viewValue) {
                    var max = (attr.awMax) ? scope.$eval(attr.awMax)  :  Infinity;
                    if (!Empty(max) && !Empty(viewValue) && Number(viewValue) >  max) {
                        ctrl.$setValidity('awMax', false);
                        return undefined;
                    } else {
                        ctrl.$setValidity('awMax', true);
                        return viewValue;
                    }
                });
            }
        };
    }])

    .directive('smartFloat', function() {
        var FLOAT_REGEXP = /^\-?\d+((\.|\,)\d+)?$/;
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {
                ctrl.$parsers.unshift(function(viewValue) {
                    if (FLOAT_REGEXP.test(viewValue)) {
                        ctrl.$setValidity('float', true);
                        return parseFloat(viewValue.replace(',', '.'));
                    } else {
                        ctrl.$setValidity('float', false);
                        return undefined;
                    }
                });
            }
        };
    })

    // integer  Validate that input is of type integer. Taken from Angular developer
    //          guide, form examples. Add min and max directives, and this will check
    //          entered values is within the range.
    //
    //          Use input type of 'text'. Use of 'number' casuses browser validation to
    //          override/interfere with this directive.
    .directive('integer', function() {
        return {
            restrict: 'A',
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {
                ctrl.$parsers.unshift(function(viewValue) {
                    ctrl.$setValidity('min', true);
                    ctrl.$setValidity('max', true);
                    if (/^\-?\d*$/.test(viewValue)) {
                       // it is valid
                        ctrl.$setValidity('integer', true);
                        if ( elm.attr('min') &&
                            ( viewValue === '' || viewValue === null || parseInt(viewValue,10) < parseInt(elm.attr('min'),10) ) ) {
                            ctrl.$setValidity('min', false);
                            return undefined;
                        }
                        if ( elm.attr('max') && ( parseInt(viewValue,10) > parseInt(elm.attr('max'),10) ) ) {
                            ctrl.$setValidity('max', false);
                            return undefined;
                        }
                        return viewValue;
                    }
                    // Invalid, return undefined (no model update)
                    ctrl.$setValidity('integer', false);
                    return undefined;
                });
            }
        };
    })

    //the awSurveyVariableName directive checks if the field contains any spaces.
    // this could be elaborated in the future for other things we want to check this field against
    .directive('awSurveyVariableName', function() {
        var FLOAT_REGEXP = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/;
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {
                ctrl.$parsers.unshift(function(viewValue) {
                    if (FLOAT_REGEXP.test(viewValue) && viewValue.indexOf(' ') === -1) { //check for a spaces
                        ctrl.$setValidity('variable', true);
                        return viewValue;
                    } else {
                        ctrl.$setValidity('variable', false); // spaces found, therefore throw error
                        return undefined;
                    }
                });
            }
        };
    })

    //
    // awRequiredWhen: { variable: "<variable to watch for true|false>", init:"true|false" }
    //
    // Make a field required conditionally using a scope variable. If the scope variable is true, the
    // field will be required. Otherwise, the required attribute will be removed.
    //
    .directive('awRequiredWhen', function() {
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {

                function checkIt () {
                    var viewValue = elm.val(), label, validity = true;
                    if ( scope[attrs.awRequiredWhen] && (elm.attr('required') === null || elm.attr('required') === undefined) ) {
                        $(elm).attr('required','required');
                        if ($(elm).hasClass('lookup')) {
                            $(elm).parent().parent().parent().find('label').first().addClass('prepend-asterisk');
                        }
                        else {
                            $(elm).parent().parent().find('label').first().addClass('prepend-asterisk');
                        }
                    }
                    else if (!scope[attrs.awRequiredWhen]) {
                        elm.removeAttr('required');
                        if ($(elm).hasClass('lookup')) {
                            label = $(elm).parent().parent().parent().find('label').first();
                            label.removeClass('prepend-asterisk');
                        }
                        else {
                            $(elm).parent().parent().find('label').first().removeClass('prepend-asterisk');
                        }
                    }
                    if (scope[attrs.awRequiredWhen] && (viewValue === undefined || viewValue === null || viewValue === '')) {
                        validity = false;
                    }
                    ctrl.$setValidity('required', validity);
                }

                if (attrs.awrequiredInit !== undefined && attrs.awrequiredInit !== null) {
                    scope[attrs.awRequiredWhen] = attrs.awrequiredInit;
                    checkIt();
                }

                scope.$watch(attrs.awRequiredWhen, function() {
                    // watch for the aw-required-when expression to change value
                    checkIt();
                });

                scope.$watch($(elm).attr('name'), function() {
                    // watch for the field to change value
                    checkIt();
                });
            }
        };
    })

    // awPlaceholder: Dynamic placeholder set to a scope variable you want watched.
    //                Value will be place in field placeholder attribute.
    .directive('awPlaceholder', [ function() {
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs) {
                $(elm).attr('placeholder', scope[attrs.awPlaceholder]);
                scope.$watch(attrs.awPlaceholder, function(newVal) {
                    $(elm).attr('placeholder',newVal);
                });
            }
        };
    }])

    // lookup   Validate lookup value against API
    //
    .directive('awlookup', ['Rest', function(Rest) {
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {
                ctrl.$parsers.unshift( function(viewValue) {
                    if (viewValue !== '' && viewValue !== null) {
                        var url = elm.attr('data-url');
                        url = url.replace(/\:value/, encodeURI(viewValue));
                        scope[elm.attr('data-source')] = null;
                        Rest.setUrl(url);
                        Rest.get().then( function(data) {
                            var results = data.data.results;
                            if (results.length > 0) {
                                scope[elm.attr('data-source')] = results[0].id;
                                scope[elm.attr('name')] = results[0].name;
                                ctrl.$setValidity('required', true);
                                ctrl.$setValidity('awlookup', true);
                                return viewValue;
                            }
                            ctrl.$setValidity('required', true);
                            ctrl.$setValidity('awlookup', false);
                            return undefined;
                        });
                    }
                    else {
                        ctrl.$setValidity('awlookup', true);
                        scope[elm.attr('data-source')] = null;
                    }
                });
            }
        };
    }])

    //
    // awValidUrl
    //
    .directive('awValidUrl', [ function() {
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {
                ctrl.$parsers.unshift( function(viewValue) {
                    var validity = true, rgx, rgx2;
                    if (viewValue !== '') {
                        ctrl.$setValidity('required', true);
                        rgx = /^(https|http|ssh)\:\/\//;
                        rgx2 = /\@/g;
                        if (!rgx.test(viewValue) || rgx2.test(viewValue)) {
                            validity = false;
                        }
                    }
                    ctrl.$setValidity('awvalidurl', validity);
                });
            }
        };
    }])

    /*
     *  Enable TB tooltips. To add a tooltip to an element, include the following directive in
     *  the element's attributes:
     *
     *     aw-tool-tip="<< tooltip text here >>"
     *
     *  Include the standard TB data-XXX attributes to controll a tooltip's appearance.  We will
     *  default placement to the left and delay to the config setting.
     */
    .directive('awToolTip', ['$sce', function($sce) {
        return function(scope, element, attrs) {
            var delay = (attrs.delay !== undefined && attrs.delay !== null) ? attrs.delay : ($AnsibleConfig) ? $AnsibleConfig.tooltip_delay : {show: 500, hide: 100},
                placement;
            if (attrs.awTipPlacement) {
                placement = attrs.awTipPlacement;
            }
            else {
                placement = (attrs.placement !== undefined && attrs.placement !== null) ? attrs.placement : 'left';
            }

            $(element).on('hidden.bs.tooltip', function( ) {
                // TB3RC1 is leaving behind tooltip <div> elements. This will remove them
                // after a tooltip fades away. If not, they lay overtop of other elements and
                // honk up the page.
                $('.tooltip').each(function() {
                    $(this).remove();
                });
            });

            attrs.awToolTip = attrs.awToolTip.replace(/</g, "&lt;");
            attrs.awToolTip = attrs.awToolTip.replace(/>/g, "&gt;");
            attrs.awToolTip = $sce.getTrustedHtml(attrs.awToolTip);
            $(element).tooltip({
                placement: placement,
                delay: delay,
                html: true,
                title: attrs.awToolTip,
                container: 'body',
                trigger: 'hover focus'
            });

            if (attrs.tipWatch) {
                // Add dataTipWatch: 'variable_name'
                scope.$watch(attrs.tipWatch, function(newVal, oldVal) {
                    if (newVal !== oldVal) {
                        // Where did fixTitle come from?:
                        //   http://stackoverflow.com/questions/9501921/change-twitter-bootstrap-tooltip-content-on-click
                        $(element).tooltip('hide').attr('data-original-title', newVal).tooltip('fixTitle');
                    }
                });
            }
        };
    }])

    /*
     * This is a copy of awToolTip currently.
     * TODO: only display these tool tips if the length of the anchor *as interpolated* to be larger than the table cell
     */
    .directive('awToolTipEllipses', [ function() {
        return {
            link: function(scope, element, attrs) {

                var delay = (attrs.delay !== undefined && attrs.delay !== null) ? attrs.delay : ($AnsibleConfig) ? $AnsibleConfig.tooltip_delay : {show: 500, hide: 100},
                    placement;

                if (attrs.awTipPlacement) {
                    placement = attrs.awTipPlacement;
                }
                else {
                    placement = (attrs.placement !== undefined && attrs.placement !== null) ? attrs.placement : 'left';
                }

                $(element).on('hidden.bs.tooltip', function( ) {
                    // TB3RC1 is leaving behind tooltip <div> elements. This will remove them
                    // after a tooltip fades away. If not, they lay overtop of other elements and
                    // honk up the page.
                    $('.tooltip').each(function() {
                        $(this).remove();
                    });
                });

                $(element).tooltip({
                    placement: placement,
                    delay: delay,
                    html: true,
                    title: attrs.awToolTipEllipses,
                    container: 'body',
                    trigger: 'hover focus'
                });
            }
        };
    }])

    /*
     *  Enable TB pop-overs. To add a pop-over to an element, include the following directive in
     *  the element's attributes:
     *
     *     aw-pop-over="<< pop-over html here >>"
     *
     *  Include the standard TB data-XXX attributes to controll the pop-over's appearance.  We will
     *  default placement to the left, delay to 0 seconds, content type to HTML, and title to 'Help'.
     */
    .directive('awPopOver', ['$compile', function($compile) {
        return function(scope, element, attrs) {
            var placement = (attrs.placement !== undefined && attrs.placement !== null) ? attrs.placement : 'left',
                title = (attrs.title) ? attrs.title : (attrs.popoverTitle) ? attrs.popoverTitle : 'Help',
                container = (attrs.container !== undefined) ? attrs.container : false,
                trigger = (attrs.trigger !== undefined) ? attrs.trigger : 'manual',
                template = '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>',
                id_to_close = "";

            if (element[0].id) {
                template = '<div id="' + element[0].id + '_popover_container" class="popover" role="tooltip"><div class="arrow"></div><h3 id="' + element[0].id + '_popover_title" class="popover-title"></h3><div id="' + element[0].id + '_popover_content" class="popover-content"></div></div>';
            }

            if (attrs.awPopOverWatch) {
                $(element).popover({
                    placement: placement,
                    delay: 0,
                    title: title,
                    content: function() {
                        return scope[attrs.awPopOverWatch];
                    },
                    trigger: trigger,
                    html: true,
                    container: container,
                    template: template
                });
            } else {
                $(element).popover({
                    placement: placement,
                    delay: 0,
                    title: title,
                    content: attrs.awPopOver,
                    trigger: trigger,
                    html: true,
                    container: container,
                    template: template
                });
            }
            $(element).attr('tabindex',-1);

            $(element).one('click', showPopover);

            $(element).on('shown.bs.popover', function() {
                $('body').one('click.popover' + id_to_close, function(e) {
                    if ($(e.target).parents(id_to_close).length === 0) {
                        $(element).popover('hide');
                    }
                });

                $(document).on('keydown.popover', dismissOnEsc);

            });

            $(element).on('hidden.bs.popover', function() {
                $(element).off('click', dismissPopover);
                $(element).off('click', showPopover);
                $('body').off('click.popover.' + id_to_close);
                $(element).one('click', showPopover);
                $(document).off('keydown.popover', dismissOnEsc);
            });

            function showPopover(e) {
                e.stopPropagation();

                var self = $(element);

                // remove tool-tip
                try {
                    element.tooltip('hide');
                }
                catch(ex) {
                    // ignore
                }

                // this is called on the help-link (over and over again)
                $('.help-link, .help-link-white').each( function() {
                    if (self.attr('id') !== $(this).attr('id')) {
                        try {
                            // not sure what this does different than the method above
                            $(this).popover('hide');
                        }
                        catch(e) {
                            // ignore
                        }
                    }
                });

                $('.popover').each(function() {
                    // remove lingering popover <div>. Seems to be a bug in TB3 RC1
                    $(this).remove();
                });
                $('.tooltip').each( function() {
                    // close any lingering tool tips
                    $(this).hide();
                });

                // set id_to_close of the actual open element
                id_to_close = "#" + $(element).attr('id') + "_popover_container";

                // $(element).one('click', dismissPopover);

                $(element).popover('toggle');

                $('.popover').each(function() {
                    $compile($(this))(scope);  //make nested directives work!
                });
            }

            function dismissPopover(e) {
                e.stopPropagation();
                $(element).popover('hide');
            }

            function dismissOnEsc(e) {
                if (e.keyCode === 27) {
                    $(element).popover('hide');
                    $('.popover').each(function() {
{'fraction_non_alphanumeric': 0.13142424056207624, 'fraction_numerical': 0.002908703578524755, 'mean_word_length': 1.2293255399789944, 'pattern_counts': {'":': 0, '<': 71, '<?xml version=': 0, '>': 71, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6424777', 'n_tokens_mistral': 12438, 'n_tokens_neox': 11624, 'n_words': 2619}