【问题标题】:creating a "responsive number" directive with angular and bootstrap使用 angular 和 bootstrap 创建“响应号”指令
【发布时间】:2015-02-04 02:39:02
【问题描述】:

我正在研究一个指令,该指令将允许根据该数字的可用空间量以友好的方式显示数字字段,例如,如果您有一个值为 10,000,000,000 的字段,这将如果没有足够的空间容纳 10,000,000,000 的全部值,则显示为 10B(B 表示十亿)。

我希望它可以与引导程序一起使用。我能想到的最好的方法是将元素包装在一个父元素中,该父元素将由引导程序调整大小(然后目标元素会做出相应的反应)。我能够做到这一点,但我想有更好的方法。

这是一个 HTML 示例(指令是“响应编号”属性):

<div class="container-fluid">
    <div class="row">
        <div class="col-md-4">Some Label: </div>
        <div class="col-md-8">
            <div responsive-number ng-model="someValue"></div>
        </div>
    </div>
</div>

该指令在初始渲染时将元素的最大宽度设置为父节点的 innerWidth()。这里的一个限制是你不能在父节点中包含任何其他元素,所以我认为必须有一个更好的,也许更被动的方法来通过响应调整大小的响应数字元素来解决这个问题(我只是在强制 responsive-number 元素保持在父容器的范围内时不确定如何执行此操作)。

以下是基于上述 HTML 为我工作的 javascript。我通过设置文本溢出样式来检测响应数字容器的值太大,以便通过剪辑隐藏溢出 - 这是父容器进入的地方,因为我需要设置最大大小元素到父容器的大小:

可以在此处找到一个 jsfiddle 示例,但为了方便起见,稍作修改以使用 jQuery 事件(但请注意,我知道这是一种不好的做法):http://jsfiddle.net/F52y5/63/

'use strict';

function MyCtrl($scope) {
    $scope.someValue = 100000000000000000;
}

app.directive('responsiveNumber', ['$filter', function ($filter) {
    return {
        require: 'ngModel',
        restrict: 'A',
        scope: true,
        link: function (scope, element, attrs, ctrl) {
            var scrollWidth = element[0].scrollWidth;
            var parentWidth = getParentWidth();
            var displayFriendlyValue = false;

            initializeResponsiveNumber();
            ctrl.$render();

            ctrl.$render = function (isResizeEvent) {
              var temp = shouldRenderFriendlyValue();

              if(isResizeEvent){
                // then the window was resized and the element is already formatted
                // so if it fits, then that's all she wrote..
                if (displayFriendlyValue == temp) {
                    return;
                }
              }

              displayFriendlyValue = temp;
              var viewValue = getTransformedViewValue(ctrl.$modelValue, displayFriendlyValue);
              ctrl.$viewValue = viewValue;
              element.html(ctrl.$viewValue);

              // after we've formatted the number it may not fit anymore
              // so if shouldRenderFriendlyValue() previously returned false
              // that means the unformatted number was not overflowing, but
              // the formatted number may overflow
              if (!displayFriendlyValue) {
                  // we check shouldRenderFriendlyValue() again
                  // because sizing can change after we set 
                  // element.html(ctrl.$viewValue);
                  displayFriendlyValue = shouldRenderFriendlyValue();
                  if (displayFriendlyValue) {
                    viewValue = getTransformedViewValue(ctrl.$modelValue, displayFriendlyValue);
                    ctrl.$viewValue = viewValue;
                    element.html(ctrl.$viewValue);
                    return;
                  }
              }
            };

            function getTransformedViewValue(modelValue, displayFriendlyValue){
              var result;
              // could add support for specifying native filter types 
              // (currency, for instance), but not necessary for this example
              if(displayFriendlyValue){
                result = makeFriendlyNumber(modelValue, 'number');
              } else {
                result = $filter('number')(modelValue);
              }

              return result;
            }

            function cleanNumber(num) {
                return (Math.round(num * 10) / 10);
            }

            function makeFriendlyNumber(num, filter) {
                var result;

                if (num >= 1000000000) {
                    result = $filter(filter)(cleanNumber(num / 1000000000)) + 'B';
                } else if (num >= 1000000) {
                    result = $filter(filter)(cleanNumber(num / 1000000)) + 'M';
                } else if (num >= 1000) {
                    result = $filter(filter)(cleanNumber(num / 1000)) + 'K';
                } else {
                    result = $filter(filter)(num);
                }

                return result.toString().replace(/(\.[\d]*)/, '');
            }

            function initializeResponsiveNumber() {
                element[0].style['overflow'] = 'hidden';
                element[0].style['-moz-text-overflow'] = 'clip';
                element[0].style['text-overflow'] = 'clip';

                updateElementSize(parentWidth);

                var debouncedResizeEvent = $scope.$on('debouncedresize', function (event) {
                    scope.$apply(function () {

                        var newParentWidth = getParentWidth();
                        if (newParentWidth == parentWidth) { return; }

                        parentWidth = newParentWidth;
                        updateElementSize(parentWidth);
                        ctrl.$render(true);
                    });
                });

                $scope.$on('$destroy', debouncedResizeEvent()};
            }

            function getParentWidth() {
                var innerWidth = angular.element(element[0].parentNode).innerWidth();
                return innerWidth;
            }

            function shouldRenderFriendlyValue() {
                scrollWidth = element[0].scrollWidth;
                if (element.innerWidth() < scrollWidth) {
                    return true;
                }

                return false;
            }

            function updateElementSize(width) {
                element[0].style.width = width + 'px';
            }
        }
    }
}]);

任何建议将不胜感激!

另外值得注意的是,我知道在某些情况下,此函数的渲染次数超出了必要的次数 - 但如果有更好的方法,则优化它是没有意义的。

---------------更新-------------

这里有些混乱,所以我想尝试简化问题陈述:

我如何知道是否有足够的空间来显示完整的值,或者我是否必须缩写它?

我已经展示了一种方法来解决这个问题,但我不相信这是最好的方法。

【问题讨论】:

  • 似乎付出了很多努力。我倾向于在所有情况下简单地过滤数字,或者在移动屏幕宽度上这样做。 stackoverflow.com/a/10601315/1264804
  • 赞成这个不错的实现......但是你的问题是什么?
  • 谢谢!似乎应该有更好的方法来解决这个问题,而不是必须使用父元素?
  • @isherwood:不幸的是,在这种情况下,以标准宽度(例如 762 像素)调整大小并没有增加太多价值 - 而且,这些值可能会根据用户输入而改变,所以它不仅必须这样做调整窗口大小 - 在数字更改后,可能会显示不友好的值,或者可能需要在以前的值较小的地方显示友好的值。我需要尽可能具体地显示数字,但信息量很大,因此有时最好显示友好的值以保持美观。

标签: javascript angularjs css twitter-bootstrap angularjs-directive


【解决方案1】:

在我看来,您可以使用过滤器来做到这一点,这只是一个关于它如何工作的粗略想法,为了代码简洁,我没有处理 1000-9999 范围,但这不应该是很多问题需要解决。

/**
 * var value = 12345678;
 *
 * value|niceNumber     => 11M
 * value|niceNumber:2   => 17.73M
 */
 app.filter('niceNumber', function () {
    return function (num, precision) {
        if (isNaN(parseFloat(num)) || !isFinite(num)) { return '-'; }
        if (typeof precision === 'undefined') { precision = 0; }
        var units = ['bytes', '', 'M', 'B', 'T', 'P'],
            number = Math.floor(Math.log(num) / Math.log(1000));

        return (num / Math.pow(1000, Math.floor(number))).toFixed(precision) + units[number];
    };
});

【讨论】:

  • 虽然我很欣赏你的回答,但问题的核心是 - 我怎么知道是否有足够的空间来显示全部价值或者我是否必须缩写它(我已经展示了一种方法来解决这个问题,但我不相信这是最好的方法)
  • 好的,但理想情况下,您希望以一致的方式表示您的数据,因为它可以帮助用户保持对事物运作方式的可靠心理模型。当他们使用一种设备或方向进入时将数字显示为 1,110,000,然后根据他们对设备或屏幕的操作切换到 1.11M,这会扰乱该心理模型并增加他们的认知负担。这让他们更难了。这可能不是一件大事或阻碍因素,但它仍然使它变得更加困难。
  • 是的,这是一个有效的观点,而且我承认我太忙于想出一个解决方案的挑战,以至于我忽略了这一点。也就是说 - 我仍然会对如何在不依赖标准屏幕尺寸的情况下最好地解决这个问题感兴趣。让指令告诉根范围它是显示友好值还是完整值很容易 - 如果至少有一个“响应数字”没有足够的空间来显示完整值,根scope 可以发布一个事件,告诉所有其他数字,对于给定的视图/上下文,以显示友好/缩写值
【解决方案2】:

我的建议是:不要这样做。以下是一些原因:

  • 响应式设计应该是可控的和一致的,而不是混乱的

    如果您按照您在此处建议的方式进行响应式设计(根据运行时分配的空间单独调整元素),您最终将几乎不知道各种结果(因为会有太多可能性)。此外,结果可能非常不一致(例如,不同的数字具有不同的宽度 - 相同数量的数字具有相同的空间量可能会或可能不会缩短)。

    最好只为您的内容提供足够的空间。如果您知道空间不足,您可以提供多种替代内容,并根据显示尺寸(始终如一地)切换到替代内容。例如。在宽度小于 480 像素的屏幕上,您可以隐藏“长号”选项并显示“短号”。

  • 内容格式应以最佳可读性为目标

    大数字通常不太可读。通常,您可能希望缩短它们(并提供单位)以获得更好的可读性,而不仅仅是在它们无法适应其扩展格式的地方。

    请记住,用户不只是读取一个数字。例如,比较多个数字是一种常见的用例,格式不一致可能会对它造成很大影响。

  • 查看每个元素的维度可能会影响性能

    AFAIK,没有事件通知您元素尺寸的变化,很多事情都可以触发这种变化(例如,向 DOM 添加不相关的元素可能会触发不同的样式)。因此,观察变化需要某种定期检查,当用于大量元素时可能会对性能产生影响。

【讨论】:

  • 好点在这里..我将不得不在用例的上下文中考虑更多一点(不幸的是我无法讨论)。有人要求我这样做,但我保留建议他们选择不同选项的权利。
  • 最后一点,我知道 ng-model 在这里没有意义,但 responsive-number 实际上是一个更大的指令的属性,它使元素的内容可编辑。出于本示例的目的,我已将必要的逻辑隔离到一个单独的指令中。
  • 啊,好的。我已经从答案中删除了关于ng-model 的建议。
  • 欣赏它 - 已经投票了,因为您的回答肯定很有帮助并且您提出了非常好的观点,但在这种特定情况下使用“响应式”缩写值仍然可能有意义
猜你喜欢
  • 1970-01-01
  • 2015-01-07
  • 2019-11-30
  • 2015-11-10
  • 2016-03-10
  • 1970-01-01
  • 2023-04-04
  • 2021-06-03
  • 1970-01-01
相关资源
最近更新 更多