FieldFor.directive.js 9.8 KB
(function (angular) {
    var module = angular.module('framework.directives.UI');

    module.directive('fieldFor', fieldForDirective);
    module.directive('formFor', formForDirective);
    module.filter('propSearch', propertySearchFilter);

    formForDirective.$inject = ['Model'];
    function formForDirective(Model) {
        return {
            restrict: 'A',
            scope: true,
            transclude: true,
            template: function (element, attrs) {
                return '<form name="formFor.form" class="transclude-target" autocomplete="off" novalidate></form>';
            },
            link: function (scope, element, attrs, ctrls, transcludeFn) {
                scope.formFor = (scope.formFor || {});
                scope.formFor.target = scope.$eval(attrs.formFor);
                scope.formFor.schema = (attrs.schema) ? Model[attrs.schema].$schema : scope.target.$$schema;
                scope.formFor.mode = 'EDIT';

                scope.$watch(function () {
                    return scope.target;
                }, function (newvalue) {

                });

                if (attrs.mode) {
                    scope.formFor.mode = attrs.mode;
                    attrs.$observe('mode', function (newvalue) {
                        scope.formFor.mode = newvalue;
                    });
                }

                transcludeFn(scope, function (clone, scope) {
                    element.children('.transclude-target').append(clone);
                });
            }
        }
    };

    fieldForDirective.$inject = ['Model'];
    function fieldForDirective(Model) {
        var defaults = {
            inputType: 'text',
            required: false,
            selectTheme: 'selectize',
            selectSelectedTemplate: '{{$select.selected.Name}}',
            selectOptionTemplate: '<div ng-bind-html="option.Name | highlight: $select.search"></div><small class="text-muted">{{option.Description}}</small>'
        };
        return {
            restrict: 'A',
            scope: true,
            template: function (element, attrs) {
                var formParent = element.closest('[form-for]'),
                    subformParent = element.closest('[sub-form-for]'),
                    schemaName = ((subformParent.length) ? subformParent : formParent).attr('schema'),
                    ctor = Model[schemaName],
                    schema = ctor && ctor.$schema,
                    fieldDef = schema && schema[attrs.fieldFor],
                    subForm = subformParent.attr('sub-form-for') || '';

                if (!fieldDef) {
                    return;
                }

                var inputConfig = angular.extend({ display: attrs.fieldFor, inputType: 'text' }, defaults, fieldDef, fieldDef.input, attrs);

                //need to fetch these.
                var labelText = attrs.display || attrs.fieldFor,
                    fieldName = attrs.fieldFor,
                    selectFrom = attrs.selectFrom;

                var template = '' +
                        '<div class="form-group">' +
                        '<label class="control-label" for="' + subForm + fieldName + 'Input">' + inputConfig.display + '</label>' +
                        '<div ng-if="formFor.mode === \'VIEW\'">' +
                        '<p class="form-control-static" ng-bind="::formFor.target.' + ((subForm) ? (subForm + '.') : '') + (inputConfig.displayField ? inputConfig.displayField : fieldName) + (inputConfig.displayFilters ? inputConfig.displayFilters : '') + '"></p>' +
                        '</div>' +
                        '<div ng-if="formFor.mode === \'EDIT\'">';

                if (selectFrom) {
                    template += '' +
                        '<ui-select ng-model="formFor.target.' + ((subForm) ? (subForm + '.') : '') + fieldName + '" name="' + subForm + fieldName + 'Input" theme="' + inputConfig.selectTheme + '" ng-disabled="' + (angular.isDefined(inputConfig.editable) && !inputConfig.editable) + '" ng-required="' + inputConfig.required + '">' +
                            '<ui-select-match allow-clear="true" placeholder="' + (inputConfig.placeholder || 'Select..') + '">' + inputConfig.selectSelectedTemplate + '</ui-select-match>' +
                            '<ui-select-choices repeat="option.Id as option in ' + selectFrom + ' | propSearch: { Name: $select.search }">' +
                                inputConfig.selectOptionTemplate +
                            '</ui-select-choices>'+
                        '</ui-select>';
                } else if (inputConfig.type === moment) {
                    template += '' +
                        '<div class="input-group">' +
                            buildElement('input', {
                                class: 'form-control',
                                type: 'text',
                                name: subForm + fieldName + 'Input',
                                ngModel: 'formFor.target.' + ((subForm) ? (subForm + '.') : '') + fieldName,
                                ngRequired: inputConfig.required,
                                datepickerPopup: 'dd/MM/yyyy',
                                isOpen: 'opened',
                                placeholder: 'DD/MM/YYYY',
                                ngDisabled: angular.isDefined(inputConfig.editable) && !inputConfig.editable
                            }) +
                            '<span class="input-group-btn">' +
                                '<button type="button" class="btn btn-default" ng-click="openCalendar($event)"><i class="glyphicon glyphicon-calendar"></i></button>' +
                            '</span>' +
                        '</div>';
                } else {
                    if (inputConfig.append || inputConfig.prepend) {
                        template += '<div class="input-group">';
                    }
                    if (inputConfig.prepend) {
                        template += '<span class="input-group-btn">' +
                                '<button type="button" class="btn btn-default" ng-click="' + inputConfig.prepend.click + '"><i class="' + inputConfig.prepend.icon + '"></i>' + inputConfig.prepend.text + '</button>' +
                        '</span>'
                    }
                    template += buildElement('input', {
                        class: 'form-control',
                        type: inputConfig.inputType,
                        name: subForm + fieldName + 'Input',
                        ngModel: 'formFor.target.' + ((subForm) ? (subForm + '.') : '') + fieldName,
                        ngRequired: inputConfig.required,
                        placeholder: inputConfig.placeholder
                    });
                    if (inputConfig.append) {
                        template += '<span class="input-group-btn">' +
                                '<button type="button" class="btn btn-default" ng-click="' + inputConfig.append.click + '"><i class="' + inputConfig.append.icon + '"></i>' + inputConfig.append.text + '</button>' +
                        '</span>'
                    }
                    if (inputConfig.append || inputConfig.prepend) {
                        template += '</div>';
                    }
                }

                template += '' +
                    '<div ng-messages="formFor.form.' + subForm + fieldName + 'Input.$error" class="help-block">' +
                        '<div ng-message="required">' + inputConfig.display + ' is required.</div>' +
                        '<div ng-message="parse">' + inputConfig.display + ' is incorrectly formatted.</div>' +
                        '<div ng-message="email">' + inputConfig.display + ' is not a valid e-mail.</div>' +
                        '</div>' +
                    '</div>';
                template += '</div>';
                return template;
            },
            link: function (scope, element, attrs) {
                scope.opened = false;

                scope.openCalendar = function (event) {
                    event.preventDefault();
                    event.stopPropagation();
                    scope.opened = true;
                }
            }
        };
    };

    function propertySearchFilter() {
        return function (items, props) {
            var out = [];

            if (angular.isArray(items)) {
                items.forEach(function (item) {
                    var itemMatches = false;

                    var keys = Object.keys(props);
                    for (var i = 0; i < keys.length; i++) {
                        var prop = keys[i];
                        var text = props[prop].toLowerCase();
                        if (item[prop].toString().toLowerCase().indexOf(text) !== -1) {
                            itemMatches = true;
                            break;
                        }
                    }

                    if (itemMatches) {
                        out.push(item);
                    }
                });
            } else {
                // Let the output be the input untouched
                out = items;
            }

            return out;
        };
    };

    function buildElement(elementType, attrs) {
        var result = '<' + elementType + ' ';

        angular.forEach(attrs, function (value, key) {
            if (angular.isDefined(value) && value !== null)
                result += normalise(key) + '="' + value + '"';
        });

        if (elementType === 'input') {
            result += ' />';
        } else {
            result += ' ></' + elementType + '>';
        }
        return result;
    }

    function normalise(input) {
        var result = '';
        for (var i = 0; i < input.length; i++) {
            var char = input[i];
            if (char == char.toUpperCase()) {
                result += '-' + char.toLowerCase();
            } else {
                result += char;
            }
        }
        return result;
    }

}(angular));