0

次のように動作するディレクティブを作成したい... HTML 側:

<input data-ng-model="modelLogistics.inputValue1" 
       data-currency="{decSep:','   ,    thSep:'.'}">

角度的には、コードのユーザー側では次のようになります。

controllerLogistics(...) {
    $scope.modelLogistics = {};
    $scope.modelLogistics.inputValue1 = 1234.23;
    ...
}

難しい部分は次のとおりです。フォーカスがあるかどうかに応じて、入力コントロールが2 つの方法で動作するようにします。

  • コントロールにフォーカスがある場合、小数点区切り記号 (decSep) のみを使用して数値を表示し、3 桁区切り記号 (thSep) を無視する必要があります。したがって、1234.23 は、ユーザーが編集する入力テキストに "1234,23" として表示されます ( HTML ディレクティブで decSep が ',' に設定されているため)。
  • コントロールがフォーカスを失った場合、小数点区切り記号 (decSep) と千単位区切り記号 (thSep) の両方を使用して数値を表示する必要があります。したがって、1234.23 は入力テキストに表示され、ユーザーには "1.234,23" (thSep) と表示されます。 HTML ディレクティブで「.」に設定されます)。

これまでの私のコードは次のとおりです。

function currency() {
    return {
        require: '?ngModel',
        link: function(scope:ng.IScope, element, attrs, ngModel) {
            if(!ngModel) return; // do nothing if no ng-model

            var options = scope.$eval(attrs.currency);
            if (options === undefined)          options = {};
            if (options.decSep === undefined)   options.decSep = ',';
            if (options.thSep === undefined)    options.thSep = '.';

            element.blur(function(e) {
                var parts = (ngModel.$viewValue || '').split(options.decSep);
                parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, options.thSep);
                element.val( parts.join(options.decSep));
            });

            ngModel.$render = () => {
                element.val(ngModel.$viewValue || '');
            }
       }
  }

...そしてそれは機能します - (a) モデルが数値ではなく文字列であり、(b) HTML のディレクティブ仕様に従って「有効な」数値でモデルを初期化する場合、つまりモデルを使用する場合数値 1234.23 ではなく、「1234,23」のような値

基になる数値 (文字列ではなく) を持ち、2 つのモード (編集/表示) を自動的に使用するように実装を変更する方法を理解するのが困難です。角度フィルター (つまり、'{{model.value | something}}' などの '|' 構文を見てきましたが、それが私がやろうとしていることに適合するかどうかはわかりません...

どんな助けでも大歓迎です。

編集

$formatters と $parsers を使用する他の通貨ソリューションを見てきましたが、私の場合、$viewValue は $modelValue だけでなく、コントロールにフォーカスがあるかどうかにも依存するため、このパターンは使用できません。 . つまり、要素がフォーカスされているかどうかをチェックするフォーマッターを追加するだけで、それは初めて機能します-ユーザーが他のコンポーネントをクリックしてフォーカスが失われた場合、モデルはまだ変更されていません-まだビューを更新する必要があります。

4

1 に答える 1

0

一日分の仕事の後...私はそれを持っています。

上記の EDIT で述べたように、ビュー ステートはモデルの状態だけでなく、コンポーネントにフォーカスがあるかどうかにも依存するため、ここでは $formatters と $parsers は機能しません。

ngModel.$modelValue、ngModel.$viewValue、および element.val() に明示的に割り当てて、自分で状態を維持します。

これは、一部の貧しい魂を助ける場合に備えて、フルコードです。無効な入力を最新の有効な入力に戻し、値が無効な場合は Bootstrap ポップオーバーをポップアップします。

function currency($timeout) {
    return {
        // We will change the model via this directive
        require: '?ngModel',

        link: function(scope:ng.IScope, element, attrs, ngModel) {
            if(!ngModel) return; // do nothing if no ng-model

            // Read the options passed in the directive
            var options = scope.$eval(attrs.currency);
            if (options === undefined)          options = {};
            if (options.min === undefined)      options.min = Number.NEGATIVE_INFINITY;
            if (options.max === undefined)      options.max = Number.POSITIVE_INFINITY;
            if (options.decimals === undefined) options.decimals = 0;
            if (options.decSep === undefined)   options.decSep = ',';
            if (options.thSep === undefined)    options.thSep = '.';

            // cache the validation regexp inside our options object (don't compile it all the time)
            var regex = "^[0-9]*(" + options.decSep + "([0-9]{0," + options.decimals + "}))?$";
            options.compiledRegEx = new RegExp(regex);

            // Use a Bootstrap popover to notify the user of erroneous data
            function showError(msg:string) {
                if (options.promise !== undefined) {
                    // An error popover is already there - cancel the timer, destroy the popover
                    $timeout.cancel(options.promise);
                    element.popover('destroy');
                }
                // Show the error
                element.popover({
                    animation:true, html:false, placement:'right', trigger:'manual', content:msg
                }).popover('show');
                // Schedule a popover destroy after 3000ms
                options.promise = $timeout(function() { element.popover('destroy'); }, 3000);
            }

            // Converters to and from between the model (number) and the two state strings (edit/view)

            function numberToEditText(n:number):string {
                if (!n) return ''; // the model may be undefined by the user
                return n.toString().split(localeDecSep).join(options.decSep);
            }

            function numberToViewText(n:number):string {
                if (!n) return ''; // the model may be undefined by the user
                var parts = n.toString().split(localeDecSep);
                // Using SO magic:  http://stackoverflow.com/questions/17294959/how-does-b-d3-d-g-work-for-adding-comma-on-numbers
                parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, options.thSep);
                return parts.join(options.decSep);
            }

            function editTextToNumber(t:string):number {
                return parseFloat(t.replace(options.thSep, '').replace(options.decSep, localeDecSep));
            }

            function viewTextToNumber(t:string):number {
                return parseFloat(t.replace(options.decSep, localeDecSep));
            }

            // For debugging
            //function log() {
            //    console.log('oldModelValue:' + options.oldModelValue);
            //    console.log('modelValue:' + ngModel.$modelValue);
            //    console.log('viewValue:' + ngModel.$viewValue);
            //}

            // On keyup, the element.val() has the input's new value - 
            // which may be invalid, violating our restrictions:
            element.keyup(function(e) {
                var newValue:string = element.val();
                if (!options.compiledRegEx.test(newValue)) {
                    // it fails the regex, it's not valid
                    //console.log('This is invalid due to regex: ' + newValue);
                    $timeout(function() {
                        // schedule a call to render, to reset element.val to the last known good value
                        ngModel.$render(true);
                    }, 0);
                    // Show a bootstrap popever error window, which will autohide after 3 seconds
                    showError(' Μόνο ' + options.decimals + ' δεκαδικά και μία υποδιαστολή (' + options.decSep +')');
                    return;
                }
                var newValueNumber:number = scope.$eval(newValue.replace(options.decSep, localeDecSep));
                if (newValueNumber>options.max || newValueNumber<options.min) {
                    // it fails the range check
                    //console.log('This is invalid due to range: ' + newValue);
                    $timeout(function() {
                        // schedule a call to render, to reset element.val to the last known good value
                        ngModel.$render(true);
                    }, 0);
                    // Show a bootstrap popever error window, which will autohide after 3 seconds
                    showError(' Από ' + options.min + ' έως ' + options.max);
                    return;
                }
                // The input may be empty - set the model to undefined then
                // ('unset' is a valid result for our model - think of SQL 'NULL')
                if (newValue === '') {
                    ngModel.$modelValue = undefined;
                    options.oldModelValue = undefined;
                } else {
                    // The new input value is solid - update the $modelValue
                    ngModel.$modelValue = editTextToNumber(newValue);
                    // ...and keep this as the last known good value
                    options.oldModelValue = ngModel.$modelValue;
                    //console.log("oldModelValue set to " + options.oldModelValue);
                }

                // If we reached here and a popover is still up, waiting to be killed,
                // then kill the timer and destroy the popover
                if (options.promise !== undefined) {
                    $timeout.cancel(options.promise);
                    element.popover('destroy');
                }
            });

            // schedule a call to render, to reset element.val to the last known good value
            element.focus(function(e) { ngModel.$render(true); });

            element.blur(function(e) { ngModel.$render(false); });

            // when the model changes, Angular will call this:
            ngModel.$render = (inFocus) => {
                // how to obtain the first content for the oldModelValue that we will revert to
                // when erroneous inputs are given in keyup() ?
                // simple: just copy it here, and update in keyup if the value is valid.
                options.oldModelValue = ngModel.$modelValue;
                //console.log("oldModelValue set to " + options.oldModelValue);
                if (!ngModel.$modelValue) {
                    element.val('');
                } else {
                    // Set the $viewValue to a proper representation, based on whether
                    // we are in edit or view mode.
                    // Initially I was calling element.is(":focus") here, but this was not working
                    // properly - so I hack a bit: I know $render will be called by Angular
                    // with no parameters (so inFocus will be undefined, which evaluates to false)
                    // and I only call it myself with true from within 'element.focus' above.
                    var m2v = inFocus?numberToEditText:numberToViewText;
                    var viewValue = m2v(ngModel.$modelValue);
                    ngModel.$viewValue = viewValue;
                    // And set the content of the DOM element to the proper representation.
                    element.val(viewValue);
                }
            }

            // we need the model of the input to update from the changes done by the user,
            // but only if it is valid - otherwise, we want to use the oldModelValue
            // (the last known good value).
            ngModel.$parsers.push(function(newValue) {
                if (newValue === '')
                    return undefined;
                if (!options.compiledRegEx.test(newValue))
                    return options.oldModelValue;
                var newValueNumber:number = scope.$eval(newValue.replace(options.decSep, localeDecSep));
                if (newValueNumber>options.max || newValueNumber<options.min)
                    return options.oldModelValue;
                // The input was solid, update the model.
                return viewTextToNumber(newValue);
            });
        }
    };
}
于 2013-10-02T14:51:50.453 に答える